From b5f49afdbae1c0c52e427909371568492934fa24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B6rn=20J=C3=B6rger?= Date: Thu, 30 Mar 2023 10:13:31 +0200 Subject: [PATCH] renormalized line endings with git config core.autocrlf true && git add --renormalize . --- data/Dockerfiles/dovecot/supervisord.conf | 40 +- data/Dockerfiles/netfilter/server.py | 1220 +- data/conf/dovecot/dovecot.folders.conf | 584 +- data/conf/rspamd/dynmaps/aliasexp.php | 348 +- data/conf/rspamd/dynmaps/bcc.php | 176 +- data/conf/rspamd/dynmaps/settings.php | 942 +- data/conf/rspamd/meta_exporter/pipe.php | 520 +- data/conf/rspamd/meta_exporter/pipe_rl.php | 96 +- data/conf/rspamd/meta_exporter/pushover.php | 550 +- data/conf/sogo/plist_ldap | 56 +- data/web/css/build/003-bootstrap-select.css | 14 +- data/web/css/build/014-mailcow.css | 744 +- data/web/css/site/admin.css | 170 +- data/web/css/site/debug.css | 72 +- data/web/css/site/edit.css | 84 +- data/web/css/site/index.css | 16 +- data/web/css/site/mailbox.css | 138 +- data/web/css/site/user.css | 268 +- data/web/inc/ajax/container_ctrl.php | 104 +- data/web/inc/ajax/destroy_tfa_auth.php | 12 +- data/web/inc/ajax/qitem_details.php | 414 +- data/web/inc/ajax/qr_gen.php | 20 +- .../inc/ajax/show_rspamd_global_filters.php | 4 +- data/web/inc/ajax/sieve_validation.php | 44 +- data/web/inc/ajax/syncjob_logs.php | 28 +- data/web/inc/ajax/transport_check.php | 298 +- data/web/inc/functions.acl.inc.php | 450 +- .../inc/functions.address_rewriting.inc.php | 872 +- data/web/inc/functions.admin.inc.php | 492 +- data/web/inc/functions.app_passwd.inc.php | 484 +- data/web/inc/functions.customize.inc.php | 630 +- data/web/inc/functions.dkim.inc.php | 650 +- data/web/inc/functions.docker.inc.php | 392 +- data/web/inc/functions.fail2ban.inc.php | 666 +- data/web/inc/functions.fwdhost.inc.php | 366 +- data/web/inc/functions.inc.php | 4810 +++---- data/web/inc/functions.mailbox.inc.php | 10540 ++++++++-------- data/web/inc/functions.mailq.inc.php | 242 +- data/web/inc/functions.oauth2.inc.php | 484 +- data/web/inc/functions.policy.inc.php | 640 +- data/web/inc/functions.pushover.inc.php | 446 +- data/web/inc/functions.quarantine.inc.php | 1682 +-- .../inc/functions.quota_notification.inc.php | 300 +- data/web/inc/functions.ratelimit.inc.php | 482 +- data/web/inc/functions.rspamd.inc.php | 422 +- .../web/inc/functions.tls_policy_maps.inc.php | 344 +- data/web/inc/functions.transports.inc.php | 1016 +- .../lib/WebAuthn/rootCertificates/huawei.pem | 62 +- .../soundasleep/html2text/tests/anchors.html | 24 +- .../soundasleep/html2text/tests/basic.html | 40 +- .../soundasleep/html2text/tests/table.html | 104 +- .../soundasleep/html2text/tests/test3.txt | 2 +- .../soundasleep/html2text/tests/test4.txt | 8 +- data/web/js/build/006-formcache.min.js | 18 +- data/web/js/build/009-numberedtextarea.min.js | 2 +- data/web/js/build/011-api.js | 794 +- data/web/json_api.php | 3922 +++--- data/web/oauth/profile.php | 64 +- data/web/oauth/token.php | 8 +- 59 files changed, 19210 insertions(+), 19210 deletions(-) diff --git a/data/Dockerfiles/dovecot/supervisord.conf b/data/Dockerfiles/dovecot/supervisord.conf index a7698640..5a3cacde 100644 --- a/data/Dockerfiles/dovecot/supervisord.conf +++ b/data/Dockerfiles/dovecot/supervisord.conf @@ -1,20 +1,20 @@ -[supervisord] -nodaemon=true -user=root -pidfile=/var/run/supervisord.pid - -[program:syslog-ng] -command=/usr/sbin/syslog-ng --foreground --no-caps -stdout_logfile=/dev/stdout -stdout_logfile_maxbytes=0 -stderr_logfile=/dev/stderr -stderr_logfile_maxbytes=0 -autostart=true - -[program:dovecot] -command=/usr/sbin/dovecot -F -autorestart=true - -[eventlistener:processes] -command=/usr/local/sbin/stop-supervisor.sh -events=PROCESS_STATE_STOPPED, PROCESS_STATE_EXITED, PROCESS_STATE_FATAL +[supervisord] +nodaemon=true +user=root +pidfile=/var/run/supervisord.pid + +[program:syslog-ng] +command=/usr/sbin/syslog-ng --foreground --no-caps +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autostart=true + +[program:dovecot] +command=/usr/sbin/dovecot -F +autorestart=true + +[eventlistener:processes] +command=/usr/local/sbin/stop-supervisor.sh +events=PROCESS_STATE_STOPPED, PROCESS_STATE_EXITED, PROCESS_STATE_FATAL diff --git a/data/Dockerfiles/netfilter/server.py b/data/Dockerfiles/netfilter/server.py index 698137bf..2ae09a33 100644 --- a/data/Dockerfiles/netfilter/server.py +++ b/data/Dockerfiles/netfilter/server.py @@ -1,610 +1,610 @@ -#!/usr/bin/env python3 - -import re -import os -import sys -import time -import atexit -import signal -import ipaddress -from collections import Counter -from random import randint -from threading import Thread -from threading import Lock -import redis -import json -import iptc -import dns.resolver -import dns.exception - -while True: - try: - redis_slaveof_ip = os.getenv('REDIS_SLAVEOF_IP', '') - redis_slaveof_port = os.getenv('REDIS_SLAVEOF_PORT', '') - if "".__eq__(redis_slaveof_ip): - r = redis.StrictRedis(host=os.getenv('IPV4_NETWORK', '172.22.1') + '.249', decode_responses=True, port=6379, db=0) - else: - r = redis.StrictRedis(host=redis_slaveof_ip, decode_responses=True, port=redis_slaveof_port, db=0) - r.ping() - except Exception as ex: - print('%s - trying again in 3 seconds' % (ex)) - time.sleep(3) - else: - break - -pubsub = r.pubsub() - -WHITELIST = [] -BLACKLIST= [] - -bans = {} - -quit_now = False -exit_code = 0 -lock = Lock() - -def log(priority, message): - tolog = {} - tolog['time'] = int(round(time.time())) - tolog['priority'] = priority - tolog['message'] = message - r.lpush('NETFILTER_LOG', json.dumps(tolog, ensure_ascii=False)) - print(message) - -def logWarn(message): - log('warn', message) - -def logCrit(message): - log('crit', message) - -def logInfo(message): - log('info', message) - -def refreshF2boptions(): - global f2boptions - global quit_now - global exit_code - - f2boptions = {} - - if not r.get('F2B_OPTIONS'): - f2boptions['ban_time'] = r.get('F2B_BAN_TIME') - f2boptions['max_ban_time'] = r.get('F2B_MAX_BAN_TIME') - f2boptions['ban_time_increment'] = r.get('F2B_BAN_TIME_INCREMENT') - f2boptions['max_attempts'] = r.get('F2B_MAX_ATTEMPTS') - f2boptions['retry_window'] = r.get('F2B_RETRY_WINDOW') - f2boptions['netban_ipv4'] = r.get('F2B_NETBAN_IPV4') - f2boptions['netban_ipv6'] = r.get('F2B_NETBAN_IPV6') - else: - try: - f2boptions = json.loads(r.get('F2B_OPTIONS')) - except ValueError: - print('Error loading F2B options: F2B_OPTIONS is not json') - quit_now = True - exit_code = 2 - - verifyF2boptions(f2boptions) - r.set('F2B_OPTIONS', json.dumps(f2boptions, ensure_ascii=False)) - -def verifyF2boptions(f2boptions): - verifyF2boption(f2boptions,'ban_time', 1800) - verifyF2boption(f2boptions,'max_ban_time', 10000) - verifyF2boption(f2boptions,'ban_time_increment', True) - verifyF2boption(f2boptions,'max_attempts', 10) - verifyF2boption(f2boptions,'retry_window', 600) - verifyF2boption(f2boptions,'netban_ipv4', 32) - verifyF2boption(f2boptions,'netban_ipv6', 128) - -def verifyF2boption(f2boptions, f2boption, f2bdefault): - f2boptions[f2boption] = f2boptions[f2boption] if f2boption in f2boptions and f2boptions[f2boption] is not None else f2bdefault - -def refreshF2bregex(): - global f2bregex - global quit_now - global exit_code - if not r.get('F2B_REGEX'): - f2bregex = {} - f2bregex[1] = 'mailcow UI: Invalid password for .+ by ([0-9a-f\.:]+)' - f2bregex[2] = 'Rspamd UI: Invalid password by ([0-9a-f\.:]+)' - f2bregex[3] = 'warning: .*\[([0-9a-f\.:]+)\]: SASL .+ authentication failed: (?!.*Connection lost to authentication server).+' - f2bregex[4] = 'warning: non-SMTP command from .*\[([0-9a-f\.:]+)]:.+' - f2bregex[5] = 'NOQUEUE: reject: RCPT from \[([0-9a-f\.:]+)].+Protocol error.+' - f2bregex[6] = '-login: Disconnected.+ \(auth failed, .+\): user=.*, method=.+, rip=([0-9a-f\.:]+),' - f2bregex[7] = '-login: Aborted login.+ \(auth failed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+' - f2bregex[8] = '-login: Aborted login.+ \(tried to use disallowed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+' - f2bregex[9] = 'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked' - f2bregex[10] = '([0-9a-f\.:]+) \"GET \/SOGo\/.* HTTP.+\" 403 .+' - r.set('F2B_REGEX', json.dumps(f2bregex, ensure_ascii=False)) - else: - try: - f2bregex = {} - f2bregex = json.loads(r.get('F2B_REGEX')) - except ValueError: - print('Error loading F2B options: F2B_REGEX is not json') - quit_now = True - exit_code = 2 - -if r.exists('F2B_LOG'): - r.rename('F2B_LOG', 'NETFILTER_LOG') - -def mailcowChainOrder(): - global lock - global quit_now - global exit_code - while not quit_now: - time.sleep(10) - with lock: - filter4_table = iptc.Table(iptc.Table.FILTER) - filter6_table = iptc.Table6(iptc.Table6.FILTER) - filter4_table.refresh() - filter6_table.refresh() - for f in [filter4_table, filter6_table]: - forward_chain = iptc.Chain(f, 'FORWARD') - input_chain = iptc.Chain(f, 'INPUT') - for chain in [forward_chain, input_chain]: - target_found = False - for position, item in enumerate(chain.rules): - if item.target.name == 'MAILCOW': - target_found = True - if position > 2: - logCrit('Error in %s chain order: MAILCOW on position %d, restarting container' % (chain.name, position)) - quit_now = True - exit_code = 2 - if not target_found: - logCrit('Error in %s chain: MAILCOW target not found, restarting container' % (chain.name)) - quit_now = True - exit_code = 2 - -def ban(address): - global lock - refreshF2boptions() - BAN_TIME = int(f2boptions['ban_time']) - BAN_TIME_INCREMENT = bool(f2boptions['ban_time_increment']) - MAX_ATTEMPTS = int(f2boptions['max_attempts']) - RETRY_WINDOW = int(f2boptions['retry_window']) - NETBAN_IPV4 = '/' + str(f2boptions['netban_ipv4']) - NETBAN_IPV6 = '/' + str(f2boptions['netban_ipv6']) - - ip = ipaddress.ip_address(address) - if type(ip) is ipaddress.IPv6Address and ip.ipv4_mapped: - ip = ip.ipv4_mapped - address = str(ip) - if ip.is_private or ip.is_loopback: - return - - self_network = ipaddress.ip_network(address) - - with lock: - temp_whitelist = set(WHITELIST) - - if temp_whitelist: - for wl_key in temp_whitelist: - wl_net = ipaddress.ip_network(wl_key, False) - if wl_net.overlaps(self_network): - logInfo('Address %s is whitelisted by rule %s' % (self_network, wl_net)) - return - - net = ipaddress.ip_network((address + (NETBAN_IPV4 if type(ip) is ipaddress.IPv4Address else NETBAN_IPV6)), strict=False) - net = str(net) - - if not net in bans: - bans[net] = {'attempts': 0, 'last_attempt': 0, 'ban_counter': 0} - - bans[net]['attempts'] += 1 - bans[net]['last_attempt'] = time.time() - - if bans[net]['attempts'] >= MAX_ATTEMPTS: - cur_time = int(round(time.time())) - NET_BAN_TIME = BAN_TIME if not BAN_TIME_INCREMENT else BAN_TIME * 2 ** bans[net]['ban_counter'] - logCrit('Banning %s for %d minutes' % (net, NET_BAN_TIME / 60 )) - if type(ip) is ipaddress.IPv4Address: - with lock: - chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), 'MAILCOW') - rule = iptc.Rule() - rule.src = net - target = iptc.Target(rule, "REJECT") - rule.target = target - if rule not in chain.rules: - chain.insert_rule(rule) - else: - with lock: - chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), 'MAILCOW') - rule = iptc.Rule6() - rule.src = net - target = iptc.Target(rule, "REJECT") - rule.target = target - if rule not in chain.rules: - chain.insert_rule(rule) - r.hset('F2B_ACTIVE_BANS', '%s' % net, cur_time + NET_BAN_TIME) - else: - logWarn('%d more attempts in the next %d seconds until %s is banned' % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net)) - -def unban(net): - global lock - if not net in bans: - logInfo('%s is not banned, skipping unban and deleting from queue (if any)' % net) - r.hdel('F2B_QUEUE_UNBAN', '%s' % net) - return - logInfo('Unbanning %s' % net) - if type(ipaddress.ip_network(net)) is ipaddress.IPv4Network: - with lock: - chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), 'MAILCOW') - rule = iptc.Rule() - rule.src = net - target = iptc.Target(rule, "REJECT") - rule.target = target - if rule in chain.rules: - chain.delete_rule(rule) - else: - with lock: - chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), 'MAILCOW') - rule = iptc.Rule6() - rule.src = net - target = iptc.Target(rule, "REJECT") - rule.target = target - if rule in chain.rules: - chain.delete_rule(rule) - r.hdel('F2B_ACTIVE_BANS', '%s' % net) - r.hdel('F2B_QUEUE_UNBAN', '%s' % net) - if net in bans: - bans[net]['attempts'] = 0 - bans[net]['ban_counter'] += 1 - -def permBan(net, unban=False): - global lock - if type(ipaddress.ip_network(net, strict=False)) is ipaddress.IPv4Network: - with lock: - chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), 'MAILCOW') - rule = iptc.Rule() - rule.src = net - target = iptc.Target(rule, "REJECT") - rule.target = target - if rule not in chain.rules and not unban: - logCrit('Add host/network %s to blacklist' % net) - chain.insert_rule(rule) - r.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time()))) - elif rule in chain.rules and unban: - logCrit('Remove host/network %s from blacklist' % net) - chain.delete_rule(rule) - r.hdel('F2B_PERM_BANS', '%s' % net) - else: - with lock: - chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), 'MAILCOW') - rule = iptc.Rule6() - rule.src = net - target = iptc.Target(rule, "REJECT") - rule.target = target - if rule not in chain.rules and not unban: - logCrit('Add host/network %s to blacklist' % net) - chain.insert_rule(rule) - r.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time()))) - elif rule in chain.rules and unban: - logCrit('Remove host/network %s from blacklist' % net) - chain.delete_rule(rule) - r.hdel('F2B_PERM_BANS', '%s' % net) - -def quit(signum, frame): - global quit_now - quit_now = True - -def clear(): - global lock - logInfo('Clearing all bans') - for net in bans.copy(): - unban(net) - with lock: - filter4_table = iptc.Table(iptc.Table.FILTER) - filter6_table = iptc.Table6(iptc.Table6.FILTER) - for filter_table in [filter4_table, filter6_table]: - filter_table.autocommit = False - forward_chain = iptc.Chain(filter_table, "FORWARD") - input_chain = iptc.Chain(filter_table, "INPUT") - mailcow_chain = iptc.Chain(filter_table, "MAILCOW") - if mailcow_chain in filter_table.chains: - for rule in mailcow_chain.rules: - mailcow_chain.delete_rule(rule) - for rule in forward_chain.rules: - if rule.target.name == 'MAILCOW': - forward_chain.delete_rule(rule) - for rule in input_chain.rules: - if rule.target.name == 'MAILCOW': - input_chain.delete_rule(rule) - filter_table.delete_chain("MAILCOW") - filter_table.commit() - filter_table.refresh() - filter_table.autocommit = True - r.delete('F2B_ACTIVE_BANS') - r.delete('F2B_PERM_BANS') - pubsub.unsubscribe() - -def watch(): - logInfo('Watching Redis channel F2B_CHANNEL') - pubsub.subscribe('F2B_CHANNEL') - - global quit_now - global exit_code - - while not quit_now: - try: - for item in pubsub.listen(): - refreshF2bregex() - for rule_id, rule_regex in f2bregex.items(): - if item['data'] and item['type'] == 'message': - try: - result = re.search(rule_regex, item['data']) - except re.error: - result = False - if result: - addr = result.group(1) - ip = ipaddress.ip_address(addr) - if ip.is_private or ip.is_loopback: - continue - logWarn('%s matched rule id %s (%s)' % (addr, rule_id, item['data'])) - ban(addr) - except Exception as ex: - logWarn('Error reading log line from pubsub: %s' % ex) - quit_now = True - exit_code = 2 - -def snat4(snat_target): - global lock - global quit_now - - def get_snat4_rule(): - rule = iptc.Rule() - rule.src = os.getenv('IPV4_NETWORK', '172.22.1') + '.0/24' - rule.dst = '!' + rule.src - target = rule.create_target("SNAT") - target.to_source = snat_target - match = rule.create_match("comment") - match.comment = f'{int(round(time.time()))}' - return rule - - while not quit_now: - time.sleep(10) - with lock: - try: - table = iptc.Table('nat') - table.refresh() - chain = iptc.Chain(table, 'POSTROUTING') - table.autocommit = False - new_rule = get_snat4_rule() - - if not chain.rules: - # if there are no rules in the chain, insert the new rule directly - logInfo(f'Added POSTROUTING rule for source network {new_rule.src} to SNAT target {snat_target}') - chain.insert_rule(new_rule) - else: - for position, rule in enumerate(chain.rules): - if not hasattr(rule.target, 'parameter'): - continue - match = all(( - new_rule.get_src() == rule.get_src(), - new_rule.get_dst() == rule.get_dst(), - new_rule.target.parameters == rule.target.parameters, - new_rule.target.name == rule.target.name - )) - if position == 0: - if not match: - logInfo(f'Added POSTROUTING rule for source network {new_rule.src} to SNAT target {snat_target}') - chain.insert_rule(new_rule) - else: - if match: - logInfo(f'Remove rule for source network {new_rule.src} to SNAT target {snat_target} from POSTROUTING chain at position {position}') - chain.delete_rule(rule) - - table.commit() - table.autocommit = True - except: - print('Error running SNAT4, retrying...') - -def snat6(snat_target): - global lock - global quit_now - - def get_snat6_rule(): - rule = iptc.Rule6() - rule.src = os.getenv('IPV6_NETWORK', 'fd4d:6169:6c63:6f77::/64') - rule.dst = '!' + rule.src - target = rule.create_target("SNAT") - target.to_source = snat_target - return rule - - while not quit_now: - time.sleep(10) - with lock: - try: - table = iptc.Table6('nat') - table.refresh() - chain = iptc.Chain(table, 'POSTROUTING') - table.autocommit = False - if get_snat6_rule() not in chain.rules: - logInfo('Added POSTROUTING rule for source network %s to SNAT target %s' % (get_snat6_rule().src, snat_target)) - chain.insert_rule(get_snat6_rule()) - table.commit() - else: - for position, item in enumerate(chain.rules): - if item == get_snat6_rule(): - if position != 0: - chain.delete_rule(get_snat6_rule()) - table.commit() - table.autocommit = True - except: - print('Error running SNAT6, retrying...') - -def autopurge(): - while not quit_now: - time.sleep(10) - refreshF2boptions() - BAN_TIME = int(f2boptions['ban_time']) - MAX_BAN_TIME = int(f2boptions['max_ban_time']) - BAN_TIME_INCREMENT = bool(f2boptions['ban_time_increment']) - MAX_ATTEMPTS = int(f2boptions['max_attempts']) - QUEUE_UNBAN = r.hgetall('F2B_QUEUE_UNBAN') - if QUEUE_UNBAN: - for net in QUEUE_UNBAN: - unban(str(net)) - for net in bans.copy(): - if bans[net]['attempts'] >= MAX_ATTEMPTS: - NET_BAN_TIME = BAN_TIME if not BAN_TIME_INCREMENT else BAN_TIME * 2 ** bans[net]['ban_counter'] - TIME_SINCE_LAST_ATTEMPT = time.time() - bans[net]['last_attempt'] - if TIME_SINCE_LAST_ATTEMPT > NET_BAN_TIME or TIME_SINCE_LAST_ATTEMPT > MAX_BAN_TIME: - unban(net) - -def isIpNetwork(address): - try: - ipaddress.ip_network(address, False) - except ValueError: - return False - return True - - -def genNetworkList(list): - resolver = dns.resolver.Resolver() - hostnames = [] - networks = [] - for key in list: - if isIpNetwork(key): - networks.append(key) - else: - hostnames.append(key) - for hostname in hostnames: - hostname_ips = [] - for rdtype in ['A', 'AAAA']: - try: - answer = resolver.resolve(qname=hostname, rdtype=rdtype, lifetime=3) - except dns.exception.Timeout: - logInfo('Hostname %s timedout on resolve' % hostname) - break - except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): - continue - except dns.exception.DNSException as dnsexception: - logInfo('%s' % dnsexception) - continue - for rdata in answer: - hostname_ips.append(rdata.to_text()) - networks.extend(hostname_ips) - return set(networks) - -def whitelistUpdate(): - global lock - global quit_now - global WHITELIST - while not quit_now: - start_time = time.time() - list = r.hgetall('F2B_WHITELIST') - new_whitelist = [] - if list: - new_whitelist = genNetworkList(list) - with lock: - if Counter(new_whitelist) != Counter(WHITELIST): - WHITELIST = new_whitelist - logInfo('Whitelist was changed, it has %s entries' % len(WHITELIST)) - time.sleep(60.0 - ((time.time() - start_time) % 60.0)) - -def blacklistUpdate(): - global quit_now - global BLACKLIST - while not quit_now: - start_time = time.time() - list = r.hgetall('F2B_BLACKLIST') - new_blacklist = [] - if list: - new_blacklist = genNetworkList(list) - if Counter(new_blacklist) != Counter(BLACKLIST): - addban = set(new_blacklist).difference(BLACKLIST) - delban = set(BLACKLIST).difference(new_blacklist) - BLACKLIST = new_blacklist - logInfo('Blacklist was changed, it has %s entries' % len(BLACKLIST)) - if addban: - for net in addban: - permBan(net=net) - if delban: - for net in delban: - permBan(net=net, unban=True) - time.sleep(60.0 - ((time.time() - start_time) % 60.0)) - -def initChain(): - # Is called before threads start, no locking - print("Initializing mailcow netfilter chain") - # IPv4 - if not iptc.Chain(iptc.Table(iptc.Table.FILTER), "MAILCOW") in iptc.Table(iptc.Table.FILTER).chains: - iptc.Table(iptc.Table.FILTER).create_chain("MAILCOW") - for c in ['FORWARD', 'INPUT']: - chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), c) - rule = iptc.Rule() - rule.src = '0.0.0.0/0' - rule.dst = '0.0.0.0/0' - target = iptc.Target(rule, "MAILCOW") - rule.target = target - if rule not in chain.rules: - chain.insert_rule(rule) - # IPv6 - if not iptc.Chain(iptc.Table6(iptc.Table6.FILTER), "MAILCOW") in iptc.Table6(iptc.Table6.FILTER).chains: - iptc.Table6(iptc.Table6.FILTER).create_chain("MAILCOW") - for c in ['FORWARD', 'INPUT']: - chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), c) - rule = iptc.Rule6() - rule.src = '::/0' - rule.dst = '::/0' - target = iptc.Target(rule, "MAILCOW") - rule.target = target - if rule not in chain.rules: - chain.insert_rule(rule) - -if __name__ == '__main__': - - # In case a previous session was killed without cleanup - clear() - # Reinit MAILCOW chain - initChain() - - watch_thread = Thread(target=watch) - watch_thread.daemon = True - watch_thread.start() - - if os.getenv('SNAT_TO_SOURCE') and os.getenv('SNAT_TO_SOURCE') != 'n': - try: - snat_ip = os.getenv('SNAT_TO_SOURCE') - snat_ipo = ipaddress.ip_address(snat_ip) - if type(snat_ipo) is ipaddress.IPv4Address: - snat4_thread = Thread(target=snat4,args=(snat_ip,)) - snat4_thread.daemon = True - snat4_thread.start() - except ValueError: - print(os.getenv('SNAT_TO_SOURCE') + ' is not a valid IPv4 address') - - if os.getenv('SNAT6_TO_SOURCE') and os.getenv('SNAT6_TO_SOURCE') != 'n': - try: - snat_ip = os.getenv('SNAT6_TO_SOURCE') - snat_ipo = ipaddress.ip_address(snat_ip) - if type(snat_ipo) is ipaddress.IPv6Address: - snat6_thread = Thread(target=snat6,args=(snat_ip,)) - snat6_thread.daemon = True - snat6_thread.start() - except ValueError: - print(os.getenv('SNAT6_TO_SOURCE') + ' is not a valid IPv6 address') - - autopurge_thread = Thread(target=autopurge) - autopurge_thread.daemon = True - autopurge_thread.start() - - mailcowchainwatch_thread = Thread(target=mailcowChainOrder) - mailcowchainwatch_thread.daemon = True - mailcowchainwatch_thread.start() - - blacklistupdate_thread = Thread(target=blacklistUpdate) - blacklistupdate_thread.daemon = True - blacklistupdate_thread.start() - - whitelistupdate_thread = Thread(target=whitelistUpdate) - whitelistupdate_thread.daemon = True - whitelistupdate_thread.start() - - signal.signal(signal.SIGTERM, quit) - atexit.register(clear) - - while not quit_now: - time.sleep(0.5) - - sys.exit(exit_code) +#!/usr/bin/env python3 + +import re +import os +import sys +import time +import atexit +import signal +import ipaddress +from collections import Counter +from random import randint +from threading import Thread +from threading import Lock +import redis +import json +import iptc +import dns.resolver +import dns.exception + +while True: + try: + redis_slaveof_ip = os.getenv('REDIS_SLAVEOF_IP', '') + redis_slaveof_port = os.getenv('REDIS_SLAVEOF_PORT', '') + if "".__eq__(redis_slaveof_ip): + r = redis.StrictRedis(host=os.getenv('IPV4_NETWORK', '172.22.1') + '.249', decode_responses=True, port=6379, db=0) + else: + r = redis.StrictRedis(host=redis_slaveof_ip, decode_responses=True, port=redis_slaveof_port, db=0) + r.ping() + except Exception as ex: + print('%s - trying again in 3 seconds' % (ex)) + time.sleep(3) + else: + break + +pubsub = r.pubsub() + +WHITELIST = [] +BLACKLIST= [] + +bans = {} + +quit_now = False +exit_code = 0 +lock = Lock() + +def log(priority, message): + tolog = {} + tolog['time'] = int(round(time.time())) + tolog['priority'] = priority + tolog['message'] = message + r.lpush('NETFILTER_LOG', json.dumps(tolog, ensure_ascii=False)) + print(message) + +def logWarn(message): + log('warn', message) + +def logCrit(message): + log('crit', message) + +def logInfo(message): + log('info', message) + +def refreshF2boptions(): + global f2boptions + global quit_now + global exit_code + + f2boptions = {} + + if not r.get('F2B_OPTIONS'): + f2boptions['ban_time'] = r.get('F2B_BAN_TIME') + f2boptions['max_ban_time'] = r.get('F2B_MAX_BAN_TIME') + f2boptions['ban_time_increment'] = r.get('F2B_BAN_TIME_INCREMENT') + f2boptions['max_attempts'] = r.get('F2B_MAX_ATTEMPTS') + f2boptions['retry_window'] = r.get('F2B_RETRY_WINDOW') + f2boptions['netban_ipv4'] = r.get('F2B_NETBAN_IPV4') + f2boptions['netban_ipv6'] = r.get('F2B_NETBAN_IPV6') + else: + try: + f2boptions = json.loads(r.get('F2B_OPTIONS')) + except ValueError: + print('Error loading F2B options: F2B_OPTIONS is not json') + quit_now = True + exit_code = 2 + + verifyF2boptions(f2boptions) + r.set('F2B_OPTIONS', json.dumps(f2boptions, ensure_ascii=False)) + +def verifyF2boptions(f2boptions): + verifyF2boption(f2boptions,'ban_time', 1800) + verifyF2boption(f2boptions,'max_ban_time', 10000) + verifyF2boption(f2boptions,'ban_time_increment', True) + verifyF2boption(f2boptions,'max_attempts', 10) + verifyF2boption(f2boptions,'retry_window', 600) + verifyF2boption(f2boptions,'netban_ipv4', 32) + verifyF2boption(f2boptions,'netban_ipv6', 128) + +def verifyF2boption(f2boptions, f2boption, f2bdefault): + f2boptions[f2boption] = f2boptions[f2boption] if f2boption in f2boptions and f2boptions[f2boption] is not None else f2bdefault + +def refreshF2bregex(): + global f2bregex + global quit_now + global exit_code + if not r.get('F2B_REGEX'): + f2bregex = {} + f2bregex[1] = 'mailcow UI: Invalid password for .+ by ([0-9a-f\.:]+)' + f2bregex[2] = 'Rspamd UI: Invalid password by ([0-9a-f\.:]+)' + f2bregex[3] = 'warning: .*\[([0-9a-f\.:]+)\]: SASL .+ authentication failed: (?!.*Connection lost to authentication server).+' + f2bregex[4] = 'warning: non-SMTP command from .*\[([0-9a-f\.:]+)]:.+' + f2bregex[5] = 'NOQUEUE: reject: RCPT from \[([0-9a-f\.:]+)].+Protocol error.+' + f2bregex[6] = '-login: Disconnected.+ \(auth failed, .+\): user=.*, method=.+, rip=([0-9a-f\.:]+),' + f2bregex[7] = '-login: Aborted login.+ \(auth failed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+' + f2bregex[8] = '-login: Aborted login.+ \(tried to use disallowed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+' + f2bregex[9] = 'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked' + f2bregex[10] = '([0-9a-f\.:]+) \"GET \/SOGo\/.* HTTP.+\" 403 .+' + r.set('F2B_REGEX', json.dumps(f2bregex, ensure_ascii=False)) + else: + try: + f2bregex = {} + f2bregex = json.loads(r.get('F2B_REGEX')) + except ValueError: + print('Error loading F2B options: F2B_REGEX is not json') + quit_now = True + exit_code = 2 + +if r.exists('F2B_LOG'): + r.rename('F2B_LOG', 'NETFILTER_LOG') + +def mailcowChainOrder(): + global lock + global quit_now + global exit_code + while not quit_now: + time.sleep(10) + with lock: + filter4_table = iptc.Table(iptc.Table.FILTER) + filter6_table = iptc.Table6(iptc.Table6.FILTER) + filter4_table.refresh() + filter6_table.refresh() + for f in [filter4_table, filter6_table]: + forward_chain = iptc.Chain(f, 'FORWARD') + input_chain = iptc.Chain(f, 'INPUT') + for chain in [forward_chain, input_chain]: + target_found = False + for position, item in enumerate(chain.rules): + if item.target.name == 'MAILCOW': + target_found = True + if position > 2: + logCrit('Error in %s chain order: MAILCOW on position %d, restarting container' % (chain.name, position)) + quit_now = True + exit_code = 2 + if not target_found: + logCrit('Error in %s chain: MAILCOW target not found, restarting container' % (chain.name)) + quit_now = True + exit_code = 2 + +def ban(address): + global lock + refreshF2boptions() + BAN_TIME = int(f2boptions['ban_time']) + BAN_TIME_INCREMENT = bool(f2boptions['ban_time_increment']) + MAX_ATTEMPTS = int(f2boptions['max_attempts']) + RETRY_WINDOW = int(f2boptions['retry_window']) + NETBAN_IPV4 = '/' + str(f2boptions['netban_ipv4']) + NETBAN_IPV6 = '/' + str(f2boptions['netban_ipv6']) + + ip = ipaddress.ip_address(address) + if type(ip) is ipaddress.IPv6Address and ip.ipv4_mapped: + ip = ip.ipv4_mapped + address = str(ip) + if ip.is_private or ip.is_loopback: + return + + self_network = ipaddress.ip_network(address) + + with lock: + temp_whitelist = set(WHITELIST) + + if temp_whitelist: + for wl_key in temp_whitelist: + wl_net = ipaddress.ip_network(wl_key, False) + if wl_net.overlaps(self_network): + logInfo('Address %s is whitelisted by rule %s' % (self_network, wl_net)) + return + + net = ipaddress.ip_network((address + (NETBAN_IPV4 if type(ip) is ipaddress.IPv4Address else NETBAN_IPV6)), strict=False) + net = str(net) + + if not net in bans: + bans[net] = {'attempts': 0, 'last_attempt': 0, 'ban_counter': 0} + + bans[net]['attempts'] += 1 + bans[net]['last_attempt'] = time.time() + + if bans[net]['attempts'] >= MAX_ATTEMPTS: + cur_time = int(round(time.time())) + NET_BAN_TIME = BAN_TIME if not BAN_TIME_INCREMENT else BAN_TIME * 2 ** bans[net]['ban_counter'] + logCrit('Banning %s for %d minutes' % (net, NET_BAN_TIME / 60 )) + if type(ip) is ipaddress.IPv4Address: + with lock: + chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), 'MAILCOW') + rule = iptc.Rule() + rule.src = net + target = iptc.Target(rule, "REJECT") + rule.target = target + if rule not in chain.rules: + chain.insert_rule(rule) + else: + with lock: + chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), 'MAILCOW') + rule = iptc.Rule6() + rule.src = net + target = iptc.Target(rule, "REJECT") + rule.target = target + if rule not in chain.rules: + chain.insert_rule(rule) + r.hset('F2B_ACTIVE_BANS', '%s' % net, cur_time + NET_BAN_TIME) + else: + logWarn('%d more attempts in the next %d seconds until %s is banned' % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net)) + +def unban(net): + global lock + if not net in bans: + logInfo('%s is not banned, skipping unban and deleting from queue (if any)' % net) + r.hdel('F2B_QUEUE_UNBAN', '%s' % net) + return + logInfo('Unbanning %s' % net) + if type(ipaddress.ip_network(net)) is ipaddress.IPv4Network: + with lock: + chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), 'MAILCOW') + rule = iptc.Rule() + rule.src = net + target = iptc.Target(rule, "REJECT") + rule.target = target + if rule in chain.rules: + chain.delete_rule(rule) + else: + with lock: + chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), 'MAILCOW') + rule = iptc.Rule6() + rule.src = net + target = iptc.Target(rule, "REJECT") + rule.target = target + if rule in chain.rules: + chain.delete_rule(rule) + r.hdel('F2B_ACTIVE_BANS', '%s' % net) + r.hdel('F2B_QUEUE_UNBAN', '%s' % net) + if net in bans: + bans[net]['attempts'] = 0 + bans[net]['ban_counter'] += 1 + +def permBan(net, unban=False): + global lock + if type(ipaddress.ip_network(net, strict=False)) is ipaddress.IPv4Network: + with lock: + chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), 'MAILCOW') + rule = iptc.Rule() + rule.src = net + target = iptc.Target(rule, "REJECT") + rule.target = target + if rule not in chain.rules and not unban: + logCrit('Add host/network %s to blacklist' % net) + chain.insert_rule(rule) + r.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time()))) + elif rule in chain.rules and unban: + logCrit('Remove host/network %s from blacklist' % net) + chain.delete_rule(rule) + r.hdel('F2B_PERM_BANS', '%s' % net) + else: + with lock: + chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), 'MAILCOW') + rule = iptc.Rule6() + rule.src = net + target = iptc.Target(rule, "REJECT") + rule.target = target + if rule not in chain.rules and not unban: + logCrit('Add host/network %s to blacklist' % net) + chain.insert_rule(rule) + r.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time()))) + elif rule in chain.rules and unban: + logCrit('Remove host/network %s from blacklist' % net) + chain.delete_rule(rule) + r.hdel('F2B_PERM_BANS', '%s' % net) + +def quit(signum, frame): + global quit_now + quit_now = True + +def clear(): + global lock + logInfo('Clearing all bans') + for net in bans.copy(): + unban(net) + with lock: + filter4_table = iptc.Table(iptc.Table.FILTER) + filter6_table = iptc.Table6(iptc.Table6.FILTER) + for filter_table in [filter4_table, filter6_table]: + filter_table.autocommit = False + forward_chain = iptc.Chain(filter_table, "FORWARD") + input_chain = iptc.Chain(filter_table, "INPUT") + mailcow_chain = iptc.Chain(filter_table, "MAILCOW") + if mailcow_chain in filter_table.chains: + for rule in mailcow_chain.rules: + mailcow_chain.delete_rule(rule) + for rule in forward_chain.rules: + if rule.target.name == 'MAILCOW': + forward_chain.delete_rule(rule) + for rule in input_chain.rules: + if rule.target.name == 'MAILCOW': + input_chain.delete_rule(rule) + filter_table.delete_chain("MAILCOW") + filter_table.commit() + filter_table.refresh() + filter_table.autocommit = True + r.delete('F2B_ACTIVE_BANS') + r.delete('F2B_PERM_BANS') + pubsub.unsubscribe() + +def watch(): + logInfo('Watching Redis channel F2B_CHANNEL') + pubsub.subscribe('F2B_CHANNEL') + + global quit_now + global exit_code + + while not quit_now: + try: + for item in pubsub.listen(): + refreshF2bregex() + for rule_id, rule_regex in f2bregex.items(): + if item['data'] and item['type'] == 'message': + try: + result = re.search(rule_regex, item['data']) + except re.error: + result = False + if result: + addr = result.group(1) + ip = ipaddress.ip_address(addr) + if ip.is_private or ip.is_loopback: + continue + logWarn('%s matched rule id %s (%s)' % (addr, rule_id, item['data'])) + ban(addr) + except Exception as ex: + logWarn('Error reading log line from pubsub: %s' % ex) + quit_now = True + exit_code = 2 + +def snat4(snat_target): + global lock + global quit_now + + def get_snat4_rule(): + rule = iptc.Rule() + rule.src = os.getenv('IPV4_NETWORK', '172.22.1') + '.0/24' + rule.dst = '!' + rule.src + target = rule.create_target("SNAT") + target.to_source = snat_target + match = rule.create_match("comment") + match.comment = f'{int(round(time.time()))}' + return rule + + while not quit_now: + time.sleep(10) + with lock: + try: + table = iptc.Table('nat') + table.refresh() + chain = iptc.Chain(table, 'POSTROUTING') + table.autocommit = False + new_rule = get_snat4_rule() + + if not chain.rules: + # if there are no rules in the chain, insert the new rule directly + logInfo(f'Added POSTROUTING rule for source network {new_rule.src} to SNAT target {snat_target}') + chain.insert_rule(new_rule) + else: + for position, rule in enumerate(chain.rules): + if not hasattr(rule.target, 'parameter'): + continue + match = all(( + new_rule.get_src() == rule.get_src(), + new_rule.get_dst() == rule.get_dst(), + new_rule.target.parameters == rule.target.parameters, + new_rule.target.name == rule.target.name + )) + if position == 0: + if not match: + logInfo(f'Added POSTROUTING rule for source network {new_rule.src} to SNAT target {snat_target}') + chain.insert_rule(new_rule) + else: + if match: + logInfo(f'Remove rule for source network {new_rule.src} to SNAT target {snat_target} from POSTROUTING chain at position {position}') + chain.delete_rule(rule) + + table.commit() + table.autocommit = True + except: + print('Error running SNAT4, retrying...') + +def snat6(snat_target): + global lock + global quit_now + + def get_snat6_rule(): + rule = iptc.Rule6() + rule.src = os.getenv('IPV6_NETWORK', 'fd4d:6169:6c63:6f77::/64') + rule.dst = '!' + rule.src + target = rule.create_target("SNAT") + target.to_source = snat_target + return rule + + while not quit_now: + time.sleep(10) + with lock: + try: + table = iptc.Table6('nat') + table.refresh() + chain = iptc.Chain(table, 'POSTROUTING') + table.autocommit = False + if get_snat6_rule() not in chain.rules: + logInfo('Added POSTROUTING rule for source network %s to SNAT target %s' % (get_snat6_rule().src, snat_target)) + chain.insert_rule(get_snat6_rule()) + table.commit() + else: + for position, item in enumerate(chain.rules): + if item == get_snat6_rule(): + if position != 0: + chain.delete_rule(get_snat6_rule()) + table.commit() + table.autocommit = True + except: + print('Error running SNAT6, retrying...') + +def autopurge(): + while not quit_now: + time.sleep(10) + refreshF2boptions() + BAN_TIME = int(f2boptions['ban_time']) + MAX_BAN_TIME = int(f2boptions['max_ban_time']) + BAN_TIME_INCREMENT = bool(f2boptions['ban_time_increment']) + MAX_ATTEMPTS = int(f2boptions['max_attempts']) + QUEUE_UNBAN = r.hgetall('F2B_QUEUE_UNBAN') + if QUEUE_UNBAN: + for net in QUEUE_UNBAN: + unban(str(net)) + for net in bans.copy(): + if bans[net]['attempts'] >= MAX_ATTEMPTS: + NET_BAN_TIME = BAN_TIME if not BAN_TIME_INCREMENT else BAN_TIME * 2 ** bans[net]['ban_counter'] + TIME_SINCE_LAST_ATTEMPT = time.time() - bans[net]['last_attempt'] + if TIME_SINCE_LAST_ATTEMPT > NET_BAN_TIME or TIME_SINCE_LAST_ATTEMPT > MAX_BAN_TIME: + unban(net) + +def isIpNetwork(address): + try: + ipaddress.ip_network(address, False) + except ValueError: + return False + return True + + +def genNetworkList(list): + resolver = dns.resolver.Resolver() + hostnames = [] + networks = [] + for key in list: + if isIpNetwork(key): + networks.append(key) + else: + hostnames.append(key) + for hostname in hostnames: + hostname_ips = [] + for rdtype in ['A', 'AAAA']: + try: + answer = resolver.resolve(qname=hostname, rdtype=rdtype, lifetime=3) + except dns.exception.Timeout: + logInfo('Hostname %s timedout on resolve' % hostname) + break + except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): + continue + except dns.exception.DNSException as dnsexception: + logInfo('%s' % dnsexception) + continue + for rdata in answer: + hostname_ips.append(rdata.to_text()) + networks.extend(hostname_ips) + return set(networks) + +def whitelistUpdate(): + global lock + global quit_now + global WHITELIST + while not quit_now: + start_time = time.time() + list = r.hgetall('F2B_WHITELIST') + new_whitelist = [] + if list: + new_whitelist = genNetworkList(list) + with lock: + if Counter(new_whitelist) != Counter(WHITELIST): + WHITELIST = new_whitelist + logInfo('Whitelist was changed, it has %s entries' % len(WHITELIST)) + time.sleep(60.0 - ((time.time() - start_time) % 60.0)) + +def blacklistUpdate(): + global quit_now + global BLACKLIST + while not quit_now: + start_time = time.time() + list = r.hgetall('F2B_BLACKLIST') + new_blacklist = [] + if list: + new_blacklist = genNetworkList(list) + if Counter(new_blacklist) != Counter(BLACKLIST): + addban = set(new_blacklist).difference(BLACKLIST) + delban = set(BLACKLIST).difference(new_blacklist) + BLACKLIST = new_blacklist + logInfo('Blacklist was changed, it has %s entries' % len(BLACKLIST)) + if addban: + for net in addban: + permBan(net=net) + if delban: + for net in delban: + permBan(net=net, unban=True) + time.sleep(60.0 - ((time.time() - start_time) % 60.0)) + +def initChain(): + # Is called before threads start, no locking + print("Initializing mailcow netfilter chain") + # IPv4 + if not iptc.Chain(iptc.Table(iptc.Table.FILTER), "MAILCOW") in iptc.Table(iptc.Table.FILTER).chains: + iptc.Table(iptc.Table.FILTER).create_chain("MAILCOW") + for c in ['FORWARD', 'INPUT']: + chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), c) + rule = iptc.Rule() + rule.src = '0.0.0.0/0' + rule.dst = '0.0.0.0/0' + target = iptc.Target(rule, "MAILCOW") + rule.target = target + if rule not in chain.rules: + chain.insert_rule(rule) + # IPv6 + if not iptc.Chain(iptc.Table6(iptc.Table6.FILTER), "MAILCOW") in iptc.Table6(iptc.Table6.FILTER).chains: + iptc.Table6(iptc.Table6.FILTER).create_chain("MAILCOW") + for c in ['FORWARD', 'INPUT']: + chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), c) + rule = iptc.Rule6() + rule.src = '::/0' + rule.dst = '::/0' + target = iptc.Target(rule, "MAILCOW") + rule.target = target + if rule not in chain.rules: + chain.insert_rule(rule) + +if __name__ == '__main__': + + # In case a previous session was killed without cleanup + clear() + # Reinit MAILCOW chain + initChain() + + watch_thread = Thread(target=watch) + watch_thread.daemon = True + watch_thread.start() + + if os.getenv('SNAT_TO_SOURCE') and os.getenv('SNAT_TO_SOURCE') != 'n': + try: + snat_ip = os.getenv('SNAT_TO_SOURCE') + snat_ipo = ipaddress.ip_address(snat_ip) + if type(snat_ipo) is ipaddress.IPv4Address: + snat4_thread = Thread(target=snat4,args=(snat_ip,)) + snat4_thread.daemon = True + snat4_thread.start() + except ValueError: + print(os.getenv('SNAT_TO_SOURCE') + ' is not a valid IPv4 address') + + if os.getenv('SNAT6_TO_SOURCE') and os.getenv('SNAT6_TO_SOURCE') != 'n': + try: + snat_ip = os.getenv('SNAT6_TO_SOURCE') + snat_ipo = ipaddress.ip_address(snat_ip) + if type(snat_ipo) is ipaddress.IPv6Address: + snat6_thread = Thread(target=snat6,args=(snat_ip,)) + snat6_thread.daemon = True + snat6_thread.start() + except ValueError: + print(os.getenv('SNAT6_TO_SOURCE') + ' is not a valid IPv6 address') + + autopurge_thread = Thread(target=autopurge) + autopurge_thread.daemon = True + autopurge_thread.start() + + mailcowchainwatch_thread = Thread(target=mailcowChainOrder) + mailcowchainwatch_thread.daemon = True + mailcowchainwatch_thread.start() + + blacklistupdate_thread = Thread(target=blacklistUpdate) + blacklistupdate_thread.daemon = True + blacklistupdate_thread.start() + + whitelistupdate_thread = Thread(target=whitelistUpdate) + whitelistupdate_thread.daemon = True + whitelistupdate_thread.start() + + signal.signal(signal.SIGTERM, quit) + atexit.register(clear) + + while not quit_now: + time.sleep(0.5) + + sys.exit(exit_code) diff --git a/data/conf/dovecot/dovecot.folders.conf b/data/conf/dovecot/dovecot.folders.conf index 99c9670f..5a5cc9e6 100644 --- a/data/conf/dovecot/dovecot.folders.conf +++ b/data/conf/dovecot/dovecot.folders.conf @@ -1,293 +1,293 @@ -namespace inbox { - inbox = yes - location = - separator = / - mailbox "Trash" { - auto = subscribe - special_use = \Trash - } - mailbox "Deleted Messages" { - special_use = \Trash - } - mailbox "Deleted Items" { - special_use = \Trash - } - mailbox "Rubbish" { - special_use = \Trash - } - mailbox "Gelöschte Objekte" { - special_use = \Trash - } - mailbox "Gelöschte Elemente" { - special_use = \Trash - } - mailbox "Papierkorb" { - special_use = \Trash - } - mailbox "Itens Excluidos" { - special_use = \Trash - } - mailbox "Itens Excluídos" { - special_use = \Trash - } - mailbox "Lixeira" { - special_use = \Trash - } - mailbox "Prullenbak" { - special_use = \Trash - } - mailbox "Odstránené položky" { - special_use = \Trash - } - mailbox "Koš" { - special_use = \Trash - } - mailbox "Verwijderde items" { - special_use = \Trash - } - mailbox "Удаленные" { - special_use = \Trash - } - mailbox "Удаленные элементы" { - special_use = \Trash - } - mailbox "Корзина" { - special_use = \Trash - } - mailbox "Видалені" { - special_use = \Trash - } - mailbox "Видалені елементи" { - special_use = \Trash - } - mailbox "Кошик" { - special_use = \Trash - } - mailbox "废件箱" { - special_use = \Trash - } - mailbox "已删除消息" { - special_use = \Trash - } - mailbox "已删除邮件" { - special_use = \Trash - } - mailbox "Archive" { - auto = subscribe - special_use = \Archive - } - mailbox "Archiv" { - special_use = \Archive - } - mailbox "Archives" { - special_use = \Archive - } - mailbox "Arquivo" { - special_use = \Archive - } - mailbox "Arquivos" { - special_use = \Archive - } - mailbox "Archief" { - special_use = \Archive - } - mailbox "Archív" { - special_use = \Archive - } - mailbox "Archivovať" { - special_use = \Archive - } - mailbox "归档" { - special_use = \Archive - } - mailbox "Архив" { - special_use = \Archive - } - mailbox "Архів" { - special_use = \Archive - } - mailbox "Sent" { - auto = subscribe - special_use = \Sent - } - mailbox "Sent Messages" { - special_use = \Sent - } - mailbox "Sent Items" { - special_use = \Sent - } - mailbox "已发送" { - special_use = \Sent - } - mailbox "已发送消息" { - special_use = \Sent - } - mailbox "已发送邮件" { - special_use = \Sent - } - mailbox "Отправленные" { - special_use = \Sent - } - mailbox "Отправленные элементы" { - special_use = \Sent - } - mailbox "Надіслані" { - special_use = \Sent - } - mailbox "Надіслані елементи" { - special_use = \Sent - } - mailbox "Gesendet" { - special_use = \Sent - } - mailbox "Gesendete Objekte" { - special_use = \Sent - } - mailbox "Gesendete Elemente" { - special_use = \Sent - } - mailbox "Itens Enviados" { - special_use = \Sent - } - mailbox "Enviados" { - special_use = \Sent - } - mailbox "Verzonden items" { - special_use = \Sent - } - mailbox "Verzonden" { - special_use = \Sent - } - mailbox "Odoslaná pošta" { - special_use = \Sent - } - mailbox "Odoslané" { - special_use = \Sent - } - mailbox "Drafts" { - auto = subscribe - special_use = \Drafts - } - mailbox "Entwürfe" { - special_use = \Drafts - } - mailbox "Rascunhos" { - special_use = \Drafts - } - mailbox "Concepten" { - special_use = \Drafts - } - mailbox "Koncepty" { - special_use = \Drafts - } - mailbox "草稿" { - special_use = \Drafts - } - mailbox "草稿箱" { - special_use = \Drafts - } - mailbox "Черновики" { - special_use = \Drafts - } - mailbox "Чернетки" { - special_use = \Drafts - } - mailbox "Junk" { - auto = subscribe - special_use = \Junk - } - mailbox "Junk-E-Mail" { - special_use = \Junk - } - mailbox "Junk E-Mail" { - special_use = \Junk - } - mailbox "Spam" { - special_use = \Junk - } - mailbox "Lixo Eletrônico" { - special_use = \Junk - } - mailbox "Nevyžiadaná pošta" { - special_use = \Junk - } - mailbox "Infikované položky" { - special_use = \Junk - } - mailbox "Ongewenste e-mail" { - special_use = \Junk - } - mailbox "垃圾" { - special_use = \Junk - } - mailbox "垃圾箱" { - special_use = \Junk - } - mailbox "Нежелательная почта" { - special_use = \Junk - } - mailbox "Спам" { - special_use = \Junk - } - mailbox "Небажана пошта" { - special_use = \Junk - } - mailbox "Koncepty" { - special_use = \Drafts - } - mailbox "Nevyžádaná pošta" { - special_use = \Junk - } - mailbox "Odstraněná pošta" { - special_use = \Trash - } - mailbox "Odeslaná pošta" { - special_use = \Sent - } - mailbox "Skräp" { - special_use = \Trash - } - mailbox "Borttagna Meddelanden" { - special_use = \Trash - } - mailbox "Arkiv" { - special_use = \Archive - } - mailbox "Arkeverat" { - special_use = \Archive - } - mailbox "Skickat" { - special_use = \Sent - } - mailbox "Skickade Meddelanden" { - special_use = \Sent - } - mailbox "Utkast" { - special_use = \Drafts - } - mailbox "Skraldespand" { - special_use = \Trash - } - mailbox "Slettet mails" { - special_use = \Trash - } - mailbox "Arkiv" { - special_use = \Archive - } - mailbox "Arkiveret mails" { - special_use = \Archive - } - mailbox "Sendt" { - special_use = \Sent - } - mailbox "Sendte mails" { - special_use = \Sent - } - mailbox "Udkast" { - special_use = \Drafts - } - mailbox "Kladde" { - special_use = \Drafts - } - prefix = +namespace inbox { + inbox = yes + location = + separator = / + mailbox "Trash" { + auto = subscribe + special_use = \Trash + } + mailbox "Deleted Messages" { + special_use = \Trash + } + mailbox "Deleted Items" { + special_use = \Trash + } + mailbox "Rubbish" { + special_use = \Trash + } + mailbox "Gelöschte Objekte" { + special_use = \Trash + } + mailbox "Gelöschte Elemente" { + special_use = \Trash + } + mailbox "Papierkorb" { + special_use = \Trash + } + mailbox "Itens Excluidos" { + special_use = \Trash + } + mailbox "Itens Excluídos" { + special_use = \Trash + } + mailbox "Lixeira" { + special_use = \Trash + } + mailbox "Prullenbak" { + special_use = \Trash + } + mailbox "Odstránené položky" { + special_use = \Trash + } + mailbox "Koš" { + special_use = \Trash + } + mailbox "Verwijderde items" { + special_use = \Trash + } + mailbox "Удаленные" { + special_use = \Trash + } + mailbox "Удаленные элементы" { + special_use = \Trash + } + mailbox "Корзина" { + special_use = \Trash + } + mailbox "Видалені" { + special_use = \Trash + } + mailbox "Видалені елементи" { + special_use = \Trash + } + mailbox "Кошик" { + special_use = \Trash + } + mailbox "废件箱" { + special_use = \Trash + } + mailbox "已删除消息" { + special_use = \Trash + } + mailbox "已删除邮件" { + special_use = \Trash + } + mailbox "Archive" { + auto = subscribe + special_use = \Archive + } + mailbox "Archiv" { + special_use = \Archive + } + mailbox "Archives" { + special_use = \Archive + } + mailbox "Arquivo" { + special_use = \Archive + } + mailbox "Arquivos" { + special_use = \Archive + } + mailbox "Archief" { + special_use = \Archive + } + mailbox "Archív" { + special_use = \Archive + } + mailbox "Archivovať" { + special_use = \Archive + } + mailbox "归档" { + special_use = \Archive + } + mailbox "Архив" { + special_use = \Archive + } + mailbox "Архів" { + special_use = \Archive + } + mailbox "Sent" { + auto = subscribe + special_use = \Sent + } + mailbox "Sent Messages" { + special_use = \Sent + } + mailbox "Sent Items" { + special_use = \Sent + } + mailbox "已发送" { + special_use = \Sent + } + mailbox "已发送消息" { + special_use = \Sent + } + mailbox "已发送邮件" { + special_use = \Sent + } + mailbox "Отправленные" { + special_use = \Sent + } + mailbox "Отправленные элементы" { + special_use = \Sent + } + mailbox "Надіслані" { + special_use = \Sent + } + mailbox "Надіслані елементи" { + special_use = \Sent + } + mailbox "Gesendet" { + special_use = \Sent + } + mailbox "Gesendete Objekte" { + special_use = \Sent + } + mailbox "Gesendete Elemente" { + special_use = \Sent + } + mailbox "Itens Enviados" { + special_use = \Sent + } + mailbox "Enviados" { + special_use = \Sent + } + mailbox "Verzonden items" { + special_use = \Sent + } + mailbox "Verzonden" { + special_use = \Sent + } + mailbox "Odoslaná pošta" { + special_use = \Sent + } + mailbox "Odoslané" { + special_use = \Sent + } + mailbox "Drafts" { + auto = subscribe + special_use = \Drafts + } + mailbox "Entwürfe" { + special_use = \Drafts + } + mailbox "Rascunhos" { + special_use = \Drafts + } + mailbox "Concepten" { + special_use = \Drafts + } + mailbox "Koncepty" { + special_use = \Drafts + } + mailbox "草稿" { + special_use = \Drafts + } + mailbox "草稿箱" { + special_use = \Drafts + } + mailbox "Черновики" { + special_use = \Drafts + } + mailbox "Чернетки" { + special_use = \Drafts + } + mailbox "Junk" { + auto = subscribe + special_use = \Junk + } + mailbox "Junk-E-Mail" { + special_use = \Junk + } + mailbox "Junk E-Mail" { + special_use = \Junk + } + mailbox "Spam" { + special_use = \Junk + } + mailbox "Lixo Eletrônico" { + special_use = \Junk + } + mailbox "Nevyžiadaná pošta" { + special_use = \Junk + } + mailbox "Infikované položky" { + special_use = \Junk + } + mailbox "Ongewenste e-mail" { + special_use = \Junk + } + mailbox "垃圾" { + special_use = \Junk + } + mailbox "垃圾箱" { + special_use = \Junk + } + mailbox "Нежелательная почта" { + special_use = \Junk + } + mailbox "Спам" { + special_use = \Junk + } + mailbox "Небажана пошта" { + special_use = \Junk + } + mailbox "Koncepty" { + special_use = \Drafts + } + mailbox "Nevyžádaná pošta" { + special_use = \Junk + } + mailbox "Odstraněná pošta" { + special_use = \Trash + } + mailbox "Odeslaná pošta" { + special_use = \Sent + } + mailbox "Skräp" { + special_use = \Trash + } + mailbox "Borttagna Meddelanden" { + special_use = \Trash + } + mailbox "Arkiv" { + special_use = \Archive + } + mailbox "Arkeverat" { + special_use = \Archive + } + mailbox "Skickat" { + special_use = \Sent + } + mailbox "Skickade Meddelanden" { + special_use = \Sent + } + mailbox "Utkast" { + special_use = \Drafts + } + mailbox "Skraldespand" { + special_use = \Trash + } + mailbox "Slettet mails" { + special_use = \Trash + } + mailbox "Arkiv" { + special_use = \Archive + } + mailbox "Arkiveret mails" { + special_use = \Archive + } + mailbox "Sendt" { + special_use = \Sent + } + mailbox "Sendte mails" { + special_use = \Sent + } + mailbox "Udkast" { + special_use = \Drafts + } + mailbox "Kladde" { + special_use = \Drafts + } + prefix = } \ No newline at end of file diff --git a/data/conf/rspamd/dynmaps/aliasexp.php b/data/conf/rspamd/dynmaps/aliasexp.php index 947a0244..2d756145 100644 --- a/data/conf/rspamd/dynmaps/aliasexp.php +++ b/data/conf/rspamd/dynmaps/aliasexp.php @@ -1,174 +1,174 @@ - PDO::ERRMODE_EXCEPTION, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - PDO::ATTR_EMULATE_PREPARES => false, -]; -try { - $pdo = new PDO($dsn, $database_user, $database_pass, $opt); -} -catch (PDOException $e) { - error_log("ALIASEXP: " . $e . PHP_EOL); - http_response_code(501); - exit; -} - -// Init Redis -$redis = new Redis(); -$redis->connect('redis-mailcow', 6379); - -function parse_email($email) { - if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false; - $a = strrpos($email, '@'); - return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1)); -} -if (!function_exists('getallheaders')) { - function getallheaders() { - if (!is_array($_SERVER)) { - return array(); - } - $headers = array(); - foreach ($_SERVER as $name => $value) { - if (substr($name, 0, 5) == 'HTTP_') { - $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; - } - } - return $headers; - } -} - -// Read headers -$headers = getallheaders(); -// Get rcpt -$rcpt = $headers['Rcpt']; -// Remove tag -$rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt); -// Parse email address -$parsed_rcpt = parse_email($rcpt); -// Create array of final mailboxes -$rcpt_final_mailboxes = array(); - -// Skip if not a mailcow handled domain -try { - if (!$redis->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) { - exit; - } -} -catch (RedisException $e) { - error_log("ALIASEXP: " . $e . PHP_EOL); - http_response_code(504); - exit; -} - -// Always assume rcpt is not a final mailbox but an alias for a mailbox or further aliases -// -// rcpt -// | -// mailbox <-- goto ---> alias1, alias2, mailbox2 -// | | -// mailbox3 | -// | -// alias3 ---> mailbox4 -// -try { - $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'"); - $stmt->execute(array( - ':rcpt' => $rcpt - )); - $gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; - if (empty($gotos)) { - $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'"); - $stmt->execute(array( - ':rcpt' => '@' . $parsed_rcpt['domain'] - )); - $gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; - } - if (empty($gotos)) { - $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :rcpt AND `active` = '1'"); - $stmt->execute(array(':rcpt' => $parsed_rcpt['domain'])); - $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; - if ($goto_branch) { - $gotos = $parsed_rcpt['local'] . '@' . $goto_branch; - } - } - $gotos_array = explode(',', $gotos); - - $loop_c = 0; - - while (count($gotos_array) != 0 && $loop_c <= 20) { - - // Loop through all found gotos - foreach ($gotos_array as $index => &$goto) { - error_log("ALIAS EXPANDER: http pipe: query " . $goto . " as username from mailbox" . PHP_EOL); - $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND (`active`= '1' OR `active`= '2');"); - $stmt->execute(array(':goto' => $goto)); - $username = $stmt->fetch(PDO::FETCH_ASSOC)['username']; - if (!empty($username)) { - error_log("ALIAS EXPANDER: http pipe: mailbox found: " . $username . PHP_EOL); - // Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate - if (!in_array($username, $rcpt_final_mailboxes)) { - $rcpt_final_mailboxes[] = $username; - } - } - else { - $parsed_goto = parse_email($goto); - if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) { - error_log("ALIAS EXPANDER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL); - } - else { - $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'"); - $stmt->execute(array(':goto' => $goto)); - $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; - if ($goto_branch) { - error_log("ALIAS EXPANDER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL); - $goto_branch_array = explode(',', $goto_branch); - } else { - $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` AND '1'"); - $stmt->execute(array(':domain' => $parsed_goto['domain'])); - $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; - if ($goto_branch) { - error_log("ALIAS EXPANDER: http pipe: goto domain " . $parsed_goto['domain'] . " is a domain alias branch for " . $goto_branch . PHP_EOL); - $goto_branch_array = array($parsed_goto['local'] . '@' . $goto_branch); - } - } - } - } - // goto item was processed, unset - unset($gotos_array[$index]); - } - - // Merge goto branch array derived from previous loop (if any), filter duplicates and unset goto branch array - if (!empty($goto_branch_array)) { - $gotos_array = array_unique(array_merge($gotos_array, $goto_branch_array)); - unset($goto_branch_array); - } - - // Reindex array - $gotos_array = array_values($gotos_array); - - // Force exit if loop cannot be solved - // Postfix does not allow for alias loops, so this should never happen. - $loop_c++; - error_log("ALIAS EXPANDER: http pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array) . PHP_EOL); - } -} -catch (PDOException $e) { - error_log("ALIAS EXPANDER: " . $e->getMessage() . PHP_EOL); - http_response_code(502); - exit; -} - -// Does also return the mailbox name if question == answer (query == mailbox) -if (count($rcpt_final_mailboxes) == 1) { - error_log("ALIASEXP: direct alias " . $rcpt . " expanded to " . $rcpt_final_mailboxes[0] . PHP_EOL); - echo trim($rcpt_final_mailboxes[0]); -} + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, +]; +try { + $pdo = new PDO($dsn, $database_user, $database_pass, $opt); +} +catch (PDOException $e) { + error_log("ALIASEXP: " . $e . PHP_EOL); + http_response_code(501); + exit; +} + +// Init Redis +$redis = new Redis(); +$redis->connect('redis-mailcow', 6379); + +function parse_email($email) { + if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false; + $a = strrpos($email, '@'); + return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1)); +} +if (!function_exists('getallheaders')) { + function getallheaders() { + if (!is_array($_SERVER)) { + return array(); + } + $headers = array(); + foreach ($_SERVER as $name => $value) { + if (substr($name, 0, 5) == 'HTTP_') { + $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; + } + } + return $headers; + } +} + +// Read headers +$headers = getallheaders(); +// Get rcpt +$rcpt = $headers['Rcpt']; +// Remove tag +$rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt); +// Parse email address +$parsed_rcpt = parse_email($rcpt); +// Create array of final mailboxes +$rcpt_final_mailboxes = array(); + +// Skip if not a mailcow handled domain +try { + if (!$redis->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) { + exit; + } +} +catch (RedisException $e) { + error_log("ALIASEXP: " . $e . PHP_EOL); + http_response_code(504); + exit; +} + +// Always assume rcpt is not a final mailbox but an alias for a mailbox or further aliases +// +// rcpt +// | +// mailbox <-- goto ---> alias1, alias2, mailbox2 +// | | +// mailbox3 | +// | +// alias3 ---> mailbox4 +// +try { + $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'"); + $stmt->execute(array( + ':rcpt' => $rcpt + )); + $gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; + if (empty($gotos)) { + $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'"); + $stmt->execute(array( + ':rcpt' => '@' . $parsed_rcpt['domain'] + )); + $gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; + } + if (empty($gotos)) { + $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :rcpt AND `active` = '1'"); + $stmt->execute(array(':rcpt' => $parsed_rcpt['domain'])); + $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; + if ($goto_branch) { + $gotos = $parsed_rcpt['local'] . '@' . $goto_branch; + } + } + $gotos_array = explode(',', $gotos); + + $loop_c = 0; + + while (count($gotos_array) != 0 && $loop_c <= 20) { + + // Loop through all found gotos + foreach ($gotos_array as $index => &$goto) { + error_log("ALIAS EXPANDER: http pipe: query " . $goto . " as username from mailbox" . PHP_EOL); + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND (`active`= '1' OR `active`= '2');"); + $stmt->execute(array(':goto' => $goto)); + $username = $stmt->fetch(PDO::FETCH_ASSOC)['username']; + if (!empty($username)) { + error_log("ALIAS EXPANDER: http pipe: mailbox found: " . $username . PHP_EOL); + // Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate + if (!in_array($username, $rcpt_final_mailboxes)) { + $rcpt_final_mailboxes[] = $username; + } + } + else { + $parsed_goto = parse_email($goto); + if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) { + error_log("ALIAS EXPANDER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL); + } + else { + $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'"); + $stmt->execute(array(':goto' => $goto)); + $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; + if ($goto_branch) { + error_log("ALIAS EXPANDER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL); + $goto_branch_array = explode(',', $goto_branch); + } else { + $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` AND '1'"); + $stmt->execute(array(':domain' => $parsed_goto['domain'])); + $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; + if ($goto_branch) { + error_log("ALIAS EXPANDER: http pipe: goto domain " . $parsed_goto['domain'] . " is a domain alias branch for " . $goto_branch . PHP_EOL); + $goto_branch_array = array($parsed_goto['local'] . '@' . $goto_branch); + } + } + } + } + // goto item was processed, unset + unset($gotos_array[$index]); + } + + // Merge goto branch array derived from previous loop (if any), filter duplicates and unset goto branch array + if (!empty($goto_branch_array)) { + $gotos_array = array_unique(array_merge($gotos_array, $goto_branch_array)); + unset($goto_branch_array); + } + + // Reindex array + $gotos_array = array_values($gotos_array); + + // Force exit if loop cannot be solved + // Postfix does not allow for alias loops, so this should never happen. + $loop_c++; + error_log("ALIAS EXPANDER: http pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array) . PHP_EOL); + } +} +catch (PDOException $e) { + error_log("ALIAS EXPANDER: " . $e->getMessage() . PHP_EOL); + http_response_code(502); + exit; +} + +// Does also return the mailbox name if question == answer (query == mailbox) +if (count($rcpt_final_mailboxes) == 1) { + error_log("ALIASEXP: direct alias " . $rcpt . " expanded to " . $rcpt_final_mailboxes[0] . PHP_EOL); + echo trim($rcpt_final_mailboxes[0]); +} diff --git a/data/conf/rspamd/dynmaps/bcc.php b/data/conf/rspamd/dynmaps/bcc.php index 3145feeb..87f91cae 100644 --- a/data/conf/rspamd/dynmaps/bcc.php +++ b/data/conf/rspamd/dynmaps/bcc.php @@ -1,88 +1,88 @@ - PDO::ERRMODE_EXCEPTION, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - PDO::ATTR_EMULATE_PREPARES => false, -]; -try { - $pdo = new PDO($dsn, $database_user, $database_pass, $opt); -} -catch (PDOException $e) { - error_log("BCC MAP SQL ERROR: " . $e . PHP_EOL); - http_response_code(501); - exit; -} - -function parse_email($email) { - if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false; - $a = strrpos($email, '@'); - return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1)); -} -if (!function_exists('getallheaders')) { - function getallheaders() { - if (!is_array($_SERVER)) { - return array(); - } - $headers = array(); - foreach ($_SERVER as $name => $value) { - if (substr($name, 0, 5) == 'HTTP_') { - $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; - } - } - return $headers; - } -} - -// Read headers -$headers = getallheaders(); -// Get rcpt -$rcpt = $headers['Rcpt']; -// Get from -$from = $headers['From']; -// Remove tags -$rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt); -$from = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $from); - -try { - if (!empty($rcpt)) { - $stmt = $pdo->prepare("SELECT `bcc_dest` FROM `bcc_maps` WHERE `type` = 'rcpt' AND `local_dest` = :local_dest AND `active` = '1'"); - $stmt->execute(array( - ':local_dest' => $rcpt - )); - $bcc_dest = $stmt->fetch(PDO::FETCH_ASSOC)['bcc_dest']; - if (!empty($bcc_dest) && filter_var($bcc_dest, FILTER_VALIDATE_EMAIL)) { - error_log("BCC MAP: returning ". $bcc_dest . " for " . $rcpt . PHP_EOL); - http_response_code(201); - echo trim($bcc_dest); - exit; - } - } - if (!empty($from)) { - $stmt = $pdo->prepare("SELECT `bcc_dest` FROM `bcc_maps` WHERE `type` = 'sender' AND `local_dest` = :local_dest AND `active` = '1'"); - $stmt->execute(array( - ':local_dest' => $from - )); - $bcc_dest = $stmt->fetch(PDO::FETCH_ASSOC)['bcc_dest']; - if (!empty($bcc_dest) && filter_var($bcc_dest, FILTER_VALIDATE_EMAIL)) { - error_log("BCC MAP: returning ". $bcc_dest . " for " . $from . PHP_EOL); - http_response_code(201); - echo trim($bcc_dest); - exit; - } - } -} -catch (PDOException $e) { - error_log("BCC MAP SQL ERROR: " . $e->getMessage() . PHP_EOL); - http_response_code(502); - exit; -} - + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, +]; +try { + $pdo = new PDO($dsn, $database_user, $database_pass, $opt); +} +catch (PDOException $e) { + error_log("BCC MAP SQL ERROR: " . $e . PHP_EOL); + http_response_code(501); + exit; +} + +function parse_email($email) { + if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false; + $a = strrpos($email, '@'); + return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1)); +} +if (!function_exists('getallheaders')) { + function getallheaders() { + if (!is_array($_SERVER)) { + return array(); + } + $headers = array(); + foreach ($_SERVER as $name => $value) { + if (substr($name, 0, 5) == 'HTTP_') { + $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; + } + } + return $headers; + } +} + +// Read headers +$headers = getallheaders(); +// Get rcpt +$rcpt = $headers['Rcpt']; +// Get from +$from = $headers['From']; +// Remove tags +$rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt); +$from = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $from); + +try { + if (!empty($rcpt)) { + $stmt = $pdo->prepare("SELECT `bcc_dest` FROM `bcc_maps` WHERE `type` = 'rcpt' AND `local_dest` = :local_dest AND `active` = '1'"); + $stmt->execute(array( + ':local_dest' => $rcpt + )); + $bcc_dest = $stmt->fetch(PDO::FETCH_ASSOC)['bcc_dest']; + if (!empty($bcc_dest) && filter_var($bcc_dest, FILTER_VALIDATE_EMAIL)) { + error_log("BCC MAP: returning ". $bcc_dest . " for " . $rcpt . PHP_EOL); + http_response_code(201); + echo trim($bcc_dest); + exit; + } + } + if (!empty($from)) { + $stmt = $pdo->prepare("SELECT `bcc_dest` FROM `bcc_maps` WHERE `type` = 'sender' AND `local_dest` = :local_dest AND `active` = '1'"); + $stmt->execute(array( + ':local_dest' => $from + )); + $bcc_dest = $stmt->fetch(PDO::FETCH_ASSOC)['bcc_dest']; + if (!empty($bcc_dest) && filter_var($bcc_dest, FILTER_VALIDATE_EMAIL)) { + error_log("BCC MAP: returning ". $bcc_dest . " for " . $from . PHP_EOL); + http_response_code(201); + echo trim($bcc_dest); + exit; + } + } +} +catch (PDOException $e) { + error_log("BCC MAP SQL ERROR: " . $e->getMessage() . PHP_EOL); + http_response_code(502); + exit; +} + diff --git a/data/conf/rspamd/dynmaps/settings.php b/data/conf/rspamd/dynmaps/settings.php index 274e9da2..08b06f38 100644 --- a/data/conf/rspamd/dynmaps/settings.php +++ b/data/conf/rspamd/dynmaps/settings.php @@ -1,471 +1,471 @@ - PDO::ERRMODE_EXCEPTION, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - PDO::ATTR_EMULATE_PREPARES => false, -]; -try { - $pdo = new PDO($dsn, $database_user, $database_pass, $opt); - $stmt = $pdo->query("SELECT '1' FROM `filterconf`"); -} -catch (PDOException $e) { - echo 'settings { }'; - exit; -} - -// Check if db changed and return header -$stmt = $pdo->prepare("SELECT GREATEST(COALESCE(MAX(UNIX_TIMESTAMP(UPDATE_TIME)), 1), COALESCE(MAX(UNIX_TIMESTAMP(CREATE_TIME)), 1)) AS `db_update_time` FROM `information_schema`.`tables` - WHERE (`TABLE_NAME` = 'filterconf' OR `TABLE_NAME` = 'settingsmap' OR `TABLE_NAME` = 'sogo_quick_contact' OR `TABLE_NAME` = 'alias') - AND TABLE_SCHEMA = :dbname;"); -$stmt->execute(array( - ':dbname' => $database_name -)); -$db_update_time = $stmt->fetch(PDO::FETCH_ASSOC)['db_update_time']; -if (empty($db_update_time)) { - $db_update_time = 1572048000; -} -if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && (strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $db_update_time)) { - header('Last-Modified: '.gmdate('D, d M Y H:i:s', $db_update_time).' GMT', true, 304); - exit; -} else { - header('Last-Modified: '.gmdate('D, d M Y H:i:s', $db_update_time).' GMT', true, 200); -} - -function parse_email($email) { - if (!filter_var($email, FILTER_VALIDATE_EMAIL)) return false; - $a = strrpos($email, '@'); - return array('local' => substr($email, 0, $a), 'domain' => substr($email, $a)); -} - -function normalize_email($email) { - $email = strtolower(str_replace('/', '\/', $email)); - $gm = "@gmail.com"; - if (substr_compare($email, $gm, -strlen($gm)) == 0) { - $email = explode('@', $email); - $email[0] = str_replace('.', '', $email[0]); - $email = implode('@', $email); - } - $gm_alt = "@googlemail.com"; - if (substr_compare($email, $gm_alt, -strlen($gm_alt)) == 0) { - $email = explode('@', $email); - $email[0] = str_replace('.', '', $email[0]); - $email[1] = str_replace('@', '', $gm); - $email = implode('@', $email); - } - if (str_contains($email, "+")) { - $email = explode('@', $email); - $user = explode('+', $email[0]); - $email[0] = $user[0]; - $email = implode('@', $email); - } - return $email; -} - -function wl_by_sogo() { - global $pdo; - $rcpt = array(); - $stmt = $pdo->query("SELECT DISTINCT(`sogo_folder_info`.`c_path2`) AS `user`, GROUP_CONCAT(`sogo_quick_contact`.`c_mail`) AS `contacts` FROM `sogo_folder_info` - INNER JOIN `sogo_quick_contact` ON `sogo_quick_contact`.`c_folder_id` = `sogo_folder_info`.`c_folder_id` - GROUP BY `c_path2`"); - $sogo_contacts = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($row = array_shift($sogo_contacts)) { - foreach (explode(',', $row['contacts']) as $contact) { - if (!filter_var($contact, FILTER_VALIDATE_EMAIL)) { - continue; - } - // Explicit from, no mime_from, no regex - envelope must match - // mailcow white and blacklists also cover mime_from - $rcpt[$row['user']][] = normalize_email($contact); - } - } - return $rcpt; -} - -function ucl_rcpts($object, $type) { - global $pdo; - $rcpt = array(); - if ($type == 'mailbox') { - // Standard aliases - $stmt = $pdo->prepare("SELECT `address` FROM `alias` - WHERE `goto` = :object_goto - AND `address` NOT LIKE '@%'"); - $stmt->execute(array( - ':object_goto' => $object - )); - $standard_aliases = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($row = array_shift($standard_aliases)) { - $local = parse_email($row['address'])['local']; - $domain = parse_email($row['address'])['domain']; - if (!empty($local) && !empty($domain)) { - $rcpt[] = '/^' . str_replace('/', '\/', $local) . '[+].*' . str_replace('/', '\/', $domain) . '$/i'; - } - $rcpt[] = str_replace('/', '\/', $row['address']); - } - // Aliases by alias domains - $stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias` FROM `mailbox` - LEFT OUTER JOIN `alias_domain` ON `mailbox`.`domain` = `alias_domain`.`target_domain` - WHERE `mailbox`.`username` = :object"); - $stmt->execute(array( - ':object' => $object - )); - $by_domain_aliases = $stmt->fetchAll(PDO::FETCH_ASSOC); - array_filter($by_domain_aliases); - while ($row = array_shift($by_domain_aliases)) { - if (!empty($row['alias'])) { - $local = parse_email($row['alias'])['local']; - $domain = parse_email($row['alias'])['domain']; - if (!empty($local) && !empty($domain)) { - $rcpt[] = '/^' . str_replace('/', '\/', $local) . '[+].*' . str_replace('/', '\/', $domain) . '$/i'; - } - $rcpt[] = str_replace('/', '\/', $row['alias']); - } - } - } - elseif ($type == 'domain') { - // Domain self - $rcpt[] = '/.*@' . $object . '/i'; - $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` - WHERE `target_domain` = :object"); - $stmt->execute(array(':object' => $object)); - $alias_domains = $stmt->fetchAll(PDO::FETCH_ASSOC); - array_filter($alias_domains); - while ($row = array_shift($alias_domains)) { - $rcpt[] = '/.*@' . $row['alias_domain'] . '/i'; - } - } - return $rcpt; -} -?> -settings { - watchdog { - priority = 10; - rcpt_mime = "/null@localhost/i"; - from_mime = "/watchdog@localhost/i"; - apply "default" { - symbols_disabled = ["HISTORY_SAVE", "ARC", "ARC_SIGNED", "DKIM", "DKIM_SIGNED", "CLAM_VIRUS"]; - want_spam = yes; - actions { - reject = 9999.0; - greylist = 9998.0; - "add header" = 9997.0; - } - - } - } -query("SELECT DISTINCT `object` FROM `filterconf` WHERE `option` = 'highspamlevel' OR `option` = 'lowspamlevel'"); -$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - -while ($row = array_shift($rows)) { - $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['object']); -?> - score_ { - priority = 4; - - rcpt = ; -prepare("SELECT `option`, `value` FROM `filterconf` - WHERE (`option` = 'highspamlevel' OR `option` = 'lowspamlevel') - AND `object`= :object"); - $stmt->execute(array(':object' => $row['object'])); - $spamscore = $stmt->fetchAll(PDO::FETCH_COLUMN|PDO::FETCH_GROUP); -?> - apply "default" { - actions { - reject = ; - greylist = ; - "add header" = ; - } - } - } - $contacts) { - $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $user); -?> - whitelist_sogo_ { - - from = ; - - priority = 4; - - rcpt = ; - - apply "default" { - SOGO_CONTACT = -99.0; - } - symbols [ - "SOGO_CONTACT" - ] - } -query("SELECT DISTINCT `object` FROM `filterconf` WHERE `option` = 'whitelist_from'"); -$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); -while ($row = array_shift($rows)) { - $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['object']); -?> - whitelist_ { -prepare("SELECT `value` FROM `filterconf` - WHERE `object`= :object - AND `option` = 'whitelist_from'"); - $stmt->execute(array(':object' => $row['object'])); - $list_items = $stmt->fetchAll(PDO::FETCH_ASSOC); - foreach ($list_items as $item) { -?> - from = "//i"; - - priority = 5; - - rcpt = ; - - priority = 6; - - rcpt = ; - - apply "default" { - MAILCOW_WHITE = -999.0; - } - symbols [ - "MAILCOW_WHITE" - ] - } - whitelist_mime_ { - - from_mime = "//i"; - - priority = 5; - - rcpt = ; - - priority = 6; - - rcpt = ; - - apply "default" { - MAILCOW_WHITE = -999.0; - } - symbols [ - "MAILCOW_WHITE" - ] - } -query("SELECT DISTINCT `object` FROM `filterconf` WHERE `option` = 'blacklist_from'"); -$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); -while ($row = array_shift($rows)) { - $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['object']); -?> - blacklist_ { -prepare("SELECT `value` FROM `filterconf` - WHERE `object`= :object - AND `option` = 'blacklist_from'"); - $stmt->execute(array(':object' => $row['object'])); - $list_items = $stmt->fetchAll(PDO::FETCH_ASSOC); - foreach ($list_items as $item) { -?> - from = "//i"; - - priority = 5; - - rcpt = ; - - priority = 6; - - rcpt = ; - - apply "default" { - MAILCOW_BLACK = 999.0; - } - symbols [ - "MAILCOW_BLACK" - ] - } - blacklist_header_ { - - from_mime = "//i"; - - priority = 5; - - rcpt = ; - - priority = 6; - - rcpt = ; - - apply "default" { - MAILCOW_BLACK = 999.0; - } - symbols [ - "MAILCOW_BLACK" - ] - } - - ham_trap { - - rcpt = ; - - priority = 9; - apply "default" { - symbols_enabled = ["HISTORY_SAVE"]; - } - symbols [ - "HAM_TRAP" - ] - } - - spam_trap { - - rcpt = ; - - priority = 9; - apply "default" { - symbols_enabled = ["HISTORY_SAVE"]; - } - symbols [ - "SPAM_TRAP" - ] - } -query("SELECT `id`, `content` FROM `settingsmap` WHERE `active` = '1'"); -$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); -while ($row = array_shift($rows)) { - $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['id']); -?> - additional_settings_ { - - } - -} + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, +]; +try { + $pdo = new PDO($dsn, $database_user, $database_pass, $opt); + $stmt = $pdo->query("SELECT '1' FROM `filterconf`"); +} +catch (PDOException $e) { + echo 'settings { }'; + exit; +} + +// Check if db changed and return header +$stmt = $pdo->prepare("SELECT GREATEST(COALESCE(MAX(UNIX_TIMESTAMP(UPDATE_TIME)), 1), COALESCE(MAX(UNIX_TIMESTAMP(CREATE_TIME)), 1)) AS `db_update_time` FROM `information_schema`.`tables` + WHERE (`TABLE_NAME` = 'filterconf' OR `TABLE_NAME` = 'settingsmap' OR `TABLE_NAME` = 'sogo_quick_contact' OR `TABLE_NAME` = 'alias') + AND TABLE_SCHEMA = :dbname;"); +$stmt->execute(array( + ':dbname' => $database_name +)); +$db_update_time = $stmt->fetch(PDO::FETCH_ASSOC)['db_update_time']; +if (empty($db_update_time)) { + $db_update_time = 1572048000; +} +if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && (strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $db_update_time)) { + header('Last-Modified: '.gmdate('D, d M Y H:i:s', $db_update_time).' GMT', true, 304); + exit; +} else { + header('Last-Modified: '.gmdate('D, d M Y H:i:s', $db_update_time).' GMT', true, 200); +} + +function parse_email($email) { + if (!filter_var($email, FILTER_VALIDATE_EMAIL)) return false; + $a = strrpos($email, '@'); + return array('local' => substr($email, 0, $a), 'domain' => substr($email, $a)); +} + +function normalize_email($email) { + $email = strtolower(str_replace('/', '\/', $email)); + $gm = "@gmail.com"; + if (substr_compare($email, $gm, -strlen($gm)) == 0) { + $email = explode('@', $email); + $email[0] = str_replace('.', '', $email[0]); + $email = implode('@', $email); + } + $gm_alt = "@googlemail.com"; + if (substr_compare($email, $gm_alt, -strlen($gm_alt)) == 0) { + $email = explode('@', $email); + $email[0] = str_replace('.', '', $email[0]); + $email[1] = str_replace('@', '', $gm); + $email = implode('@', $email); + } + if (str_contains($email, "+")) { + $email = explode('@', $email); + $user = explode('+', $email[0]); + $email[0] = $user[0]; + $email = implode('@', $email); + } + return $email; +} + +function wl_by_sogo() { + global $pdo; + $rcpt = array(); + $stmt = $pdo->query("SELECT DISTINCT(`sogo_folder_info`.`c_path2`) AS `user`, GROUP_CONCAT(`sogo_quick_contact`.`c_mail`) AS `contacts` FROM `sogo_folder_info` + INNER JOIN `sogo_quick_contact` ON `sogo_quick_contact`.`c_folder_id` = `sogo_folder_info`.`c_folder_id` + GROUP BY `c_path2`"); + $sogo_contacts = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($sogo_contacts)) { + foreach (explode(',', $row['contacts']) as $contact) { + if (!filter_var($contact, FILTER_VALIDATE_EMAIL)) { + continue; + } + // Explicit from, no mime_from, no regex - envelope must match + // mailcow white and blacklists also cover mime_from + $rcpt[$row['user']][] = normalize_email($contact); + } + } + return $rcpt; +} + +function ucl_rcpts($object, $type) { + global $pdo; + $rcpt = array(); + if ($type == 'mailbox') { + // Standard aliases + $stmt = $pdo->prepare("SELECT `address` FROM `alias` + WHERE `goto` = :object_goto + AND `address` NOT LIKE '@%'"); + $stmt->execute(array( + ':object_goto' => $object + )); + $standard_aliases = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($standard_aliases)) { + $local = parse_email($row['address'])['local']; + $domain = parse_email($row['address'])['domain']; + if (!empty($local) && !empty($domain)) { + $rcpt[] = '/^' . str_replace('/', '\/', $local) . '[+].*' . str_replace('/', '\/', $domain) . '$/i'; + } + $rcpt[] = str_replace('/', '\/', $row['address']); + } + // Aliases by alias domains + $stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias` FROM `mailbox` + LEFT OUTER JOIN `alias_domain` ON `mailbox`.`domain` = `alias_domain`.`target_domain` + WHERE `mailbox`.`username` = :object"); + $stmt->execute(array( + ':object' => $object + )); + $by_domain_aliases = $stmt->fetchAll(PDO::FETCH_ASSOC); + array_filter($by_domain_aliases); + while ($row = array_shift($by_domain_aliases)) { + if (!empty($row['alias'])) { + $local = parse_email($row['alias'])['local']; + $domain = parse_email($row['alias'])['domain']; + if (!empty($local) && !empty($domain)) { + $rcpt[] = '/^' . str_replace('/', '\/', $local) . '[+].*' . str_replace('/', '\/', $domain) . '$/i'; + } + $rcpt[] = str_replace('/', '\/', $row['alias']); + } + } + } + elseif ($type == 'domain') { + // Domain self + $rcpt[] = '/.*@' . $object . '/i'; + $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` + WHERE `target_domain` = :object"); + $stmt->execute(array(':object' => $object)); + $alias_domains = $stmt->fetchAll(PDO::FETCH_ASSOC); + array_filter($alias_domains); + while ($row = array_shift($alias_domains)) { + $rcpt[] = '/.*@' . $row['alias_domain'] . '/i'; + } + } + return $rcpt; +} +?> +settings { + watchdog { + priority = 10; + rcpt_mime = "/null@localhost/i"; + from_mime = "/watchdog@localhost/i"; + apply "default" { + symbols_disabled = ["HISTORY_SAVE", "ARC", "ARC_SIGNED", "DKIM", "DKIM_SIGNED", "CLAM_VIRUS"]; + want_spam = yes; + actions { + reject = 9999.0; + greylist = 9998.0; + "add header" = 9997.0; + } + + } + } +query("SELECT DISTINCT `object` FROM `filterconf` WHERE `option` = 'highspamlevel' OR `option` = 'lowspamlevel'"); +$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + +while ($row = array_shift($rows)) { + $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['object']); +?> + score_ { + priority = 4; + + rcpt = ; +prepare("SELECT `option`, `value` FROM `filterconf` + WHERE (`option` = 'highspamlevel' OR `option` = 'lowspamlevel') + AND `object`= :object"); + $stmt->execute(array(':object' => $row['object'])); + $spamscore = $stmt->fetchAll(PDO::FETCH_COLUMN|PDO::FETCH_GROUP); +?> + apply "default" { + actions { + reject = ; + greylist = ; + "add header" = ; + } + } + } + $contacts) { + $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $user); +?> + whitelist_sogo_ { + + from = ; + + priority = 4; + + rcpt = ; + + apply "default" { + SOGO_CONTACT = -99.0; + } + symbols [ + "SOGO_CONTACT" + ] + } +query("SELECT DISTINCT `object` FROM `filterconf` WHERE `option` = 'whitelist_from'"); +$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); +while ($row = array_shift($rows)) { + $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['object']); +?> + whitelist_ { +prepare("SELECT `value` FROM `filterconf` + WHERE `object`= :object + AND `option` = 'whitelist_from'"); + $stmt->execute(array(':object' => $row['object'])); + $list_items = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($list_items as $item) { +?> + from = "//i"; + + priority = 5; + + rcpt = ; + + priority = 6; + + rcpt = ; + + apply "default" { + MAILCOW_WHITE = -999.0; + } + symbols [ + "MAILCOW_WHITE" + ] + } + whitelist_mime_ { + + from_mime = "//i"; + + priority = 5; + + rcpt = ; + + priority = 6; + + rcpt = ; + + apply "default" { + MAILCOW_WHITE = -999.0; + } + symbols [ + "MAILCOW_WHITE" + ] + } +query("SELECT DISTINCT `object` FROM `filterconf` WHERE `option` = 'blacklist_from'"); +$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); +while ($row = array_shift($rows)) { + $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['object']); +?> + blacklist_ { +prepare("SELECT `value` FROM `filterconf` + WHERE `object`= :object + AND `option` = 'blacklist_from'"); + $stmt->execute(array(':object' => $row['object'])); + $list_items = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($list_items as $item) { +?> + from = "//i"; + + priority = 5; + + rcpt = ; + + priority = 6; + + rcpt = ; + + apply "default" { + MAILCOW_BLACK = 999.0; + } + symbols [ + "MAILCOW_BLACK" + ] + } + blacklist_header_ { + + from_mime = "//i"; + + priority = 5; + + rcpt = ; + + priority = 6; + + rcpt = ; + + apply "default" { + MAILCOW_BLACK = 999.0; + } + symbols [ + "MAILCOW_BLACK" + ] + } + + ham_trap { + + rcpt = ; + + priority = 9; + apply "default" { + symbols_enabled = ["HISTORY_SAVE"]; + } + symbols [ + "HAM_TRAP" + ] + } + + spam_trap { + + rcpt = ; + + priority = 9; + apply "default" { + symbols_enabled = ["HISTORY_SAVE"]; + } + symbols [ + "SPAM_TRAP" + ] + } +query("SELECT `id`, `content` FROM `settingsmap` WHERE `active` = '1'"); +$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); +while ($row = array_shift($rows)) { + $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['id']); +?> + additional_settings_ { + + } + +} diff --git a/data/conf/rspamd/meta_exporter/pipe.php b/data/conf/rspamd/meta_exporter/pipe.php index 88e66e8e..50b226df 100644 --- a/data/conf/rspamd/meta_exporter/pipe.php +++ b/data/conf/rspamd/meta_exporter/pipe.php @@ -1,260 +1,260 @@ - PDO::ERRMODE_EXCEPTION, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - PDO::ATTR_EMULATE_PREPARES => false, -]; -try { - $pdo = new PDO($dsn, $database_user, $database_pass, $opt); -} -catch (PDOException $e) { - error_log("QUARANTINE: " . $e . PHP_EOL); - http_response_code(501); - exit; -} -// Init Redis -$redis = new Redis(); -$redis->connect('redis-mailcow', 6379); - -// Functions -function parse_email($email) { - if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false; - $a = strrpos($email, '@'); - return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1)); -} -if (!function_exists('getallheaders')) { - function getallheaders() { - if (!is_array($_SERVER)) { - return array(); - } - $headers = array(); - foreach ($_SERVER as $name => $value) { - if (substr($name, 0, 5) == 'HTTP_') { - $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; - } - } - return $headers; - } -} - -$raw_data_content = file_get_contents('php://input'); -$raw_data = mb_convert_encoding($raw_data_content, 'HTML-ENTITIES', "UTF-8"); -$headers = getallheaders(); - -$qid = $headers['X-Rspamd-Qid']; -$fuzzy = $headers['X-Rspamd-Fuzzy']; -$subject = $headers['X-Rspamd-Subject']; -$score = $headers['X-Rspamd-Score']; -$rcpts = $headers['X-Rspamd-Rcpt']; -$user = $headers['X-Rspamd-User']; -$ip = $headers['X-Rspamd-Ip']; -$action = $headers['X-Rspamd-Action']; -$sender = $headers['X-Rspamd-From']; -$symbols = $headers['X-Rspamd-Symbols']; - -$raw_size = (int)$_SERVER['CONTENT_LENGTH']; - -if (empty($sender)) { - error_log("QUARANTINE: Unknown sender, assuming empty-env-from@localhost" . PHP_EOL); - $sender = 'empty-env-from@localhost'; -} - -if ($fuzzy == 'unknown') { - $fuzzy = '[]'; -} - -try { - $max_size = (int)$redis->Get('Q_MAX_SIZE'); - if (($max_size * 1048576) < $raw_size) { - error_log(sprintf("QUARANTINE: Message too large: %d b exceeds %d b", $raw_size, ($max_size * 1048576)) . PHP_EOL); - http_response_code(505); - exit; - } - if ($exclude_domains = $redis->Get('Q_EXCLUDE_DOMAINS')) { - $exclude_domains = json_decode($exclude_domains, true); - } - $retention_size = (int)$redis->Get('Q_RETENTION_SIZE'); -} -catch (RedisException $e) { - error_log("QUARANTINE: " . $e . PHP_EOL); - http_response_code(504); - exit; -} - -$rcpt_final_mailboxes = array(); - -// Loop through all rcpts -foreach (json_decode($rcpts, true) as $rcpt) { - // Remove tag - $rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt); - - // Break rcpt into local part and domain part - $parsed_rcpt = parse_email($rcpt); - - // Skip if not a mailcow handled domain - try { - if (!$redis->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) { - continue; - } - } - catch (RedisException $e) { - error_log("QUARANTINE: " . $e . PHP_EOL); - http_response_code(504); - exit; - } - - // Skip if domain is excluded - if (in_array($parsed_rcpt['domain'], $exclude_domains)) { - error_log(sprintf("QUARANTINE: Skipped domain %s", $parsed_rcpt['domain']) . PHP_EOL); - continue; - } - - // Always assume rcpt is not a final mailbox but an alias for a mailbox or further aliases - // - // rcpt - // | - // mailbox <-- goto ---> alias1, alias2, mailbox2 - // | | - // mailbox3 | - // | - // alias3 ---> mailbox4 - // - try { - $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'"); - $stmt->execute(array( - ':rcpt' => $rcpt - )); - $gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; - if (empty($gotos)) { - $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'"); - $stmt->execute(array( - ':rcpt' => '@' . $parsed_rcpt['domain'] - )); - $gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; - } - if (empty($gotos)) { - $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :rcpt AND `active` = '1'"); - $stmt->execute(array(':rcpt' => $parsed_rcpt['domain'])); - $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; - if ($goto_branch) { - $gotos = $parsed_rcpt['local'] . '@' . $goto_branch; - } - } - $gotos_array = explode(',', $gotos); - - $loop_c = 0; - - while (count($gotos_array) != 0 && $loop_c <= 20) { - - // Loop through all found gotos - foreach ($gotos_array as $index => &$goto) { - error_log("RCPT RESOVLER: http pipe: query " . $goto . " as username from mailbox" . PHP_EOL); - $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND (`active`= '1' OR `active`= '2');"); - $stmt->execute(array(':goto' => $goto)); - $username = $stmt->fetch(PDO::FETCH_ASSOC)['username']; - if (!empty($username)) { - error_log("RCPT RESOVLER: http pipe: mailbox found: " . $username . PHP_EOL); - // Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate - if (!in_array($username, $rcpt_final_mailboxes)) { - $rcpt_final_mailboxes[] = $username; - } - } - else { - $parsed_goto = parse_email($goto); - if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) { - error_log("RCPT RESOVLER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL); - } - else { - $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'"); - $stmt->execute(array(':goto' => $goto)); - $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; - if ($goto_branch) { - error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL); - $goto_branch_array = explode(',', $goto_branch); - } else { - $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` AND '1'"); - $stmt->execute(array(':domain' => $parsed_goto['domain'])); - $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; - if ($goto_branch) { - error_log("RCPT RESOVLER: http pipe: goto domain " . $parsed_goto['domain'] . " is a domain alias branch for " . $goto_branch . PHP_EOL); - $goto_branch_array = array($parsed_goto['local'] . '@' . $goto_branch); - } - } - } - } - // goto item was processed, unset - unset($gotos_array[$index]); - } - - // Merge goto branch array derived from previous loop (if any), filter duplicates and unset goto branch array - if (!empty($goto_branch_array)) { - $gotos_array = array_unique(array_merge($gotos_array, $goto_branch_array)); - unset($goto_branch_array); - } - - // Reindex array - $gotos_array = array_values($gotos_array); - - // Force exit if loop cannot be solved - // Postfix does not allow for alias loops, so this should never happen. - $loop_c++; - error_log("RCPT RESOVLER: http pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array) . PHP_EOL); - } - } - catch (PDOException $e) { - error_log("RCPT RESOVLER: " . $e->getMessage() . PHP_EOL); - http_response_code(502); - exit; - } -} - -foreach ($rcpt_final_mailboxes as $rcpt_final) { - error_log("QUARANTINE: quarantine pipe: processing quarantine message for rcpt " . $rcpt_final . PHP_EOL); - try { - $stmt = $pdo->prepare("INSERT INTO `quarantine` (`qid`, `subject`, `score`, `sender`, `rcpt`, `symbols`, `user`, `ip`, `msg`, `action`, `fuzzy_hashes`) - VALUES (:qid, :subject, :score, :sender, :rcpt, :symbols, :user, :ip, :msg, :action, :fuzzy_hashes)"); - $stmt->execute(array( - ':qid' => $qid, - ':subject' => $subject, - ':score' => $score, - ':sender' => $sender, - ':rcpt' => $rcpt_final, - ':symbols' => $symbols, - ':user' => $user, - ':ip' => $ip, - ':msg' => $raw_data, - ':action' => $action, - ':fuzzy_hashes' => $fuzzy - )); - $stmt = $pdo->prepare('DELETE FROM `quarantine` WHERE `rcpt` = :rcpt AND `id` NOT IN ( - SELECT `id` - FROM ( - SELECT `id` - FROM `quarantine` - WHERE `rcpt` = :rcpt2 - ORDER BY id DESC - LIMIT :retention_size - ) x - );'); - $stmt->execute(array( - ':rcpt' => $rcpt_final, - ':rcpt2' => $rcpt_final, - ':retention_size' => $retention_size - )); - } - catch (PDOException $e) { - error_log("QUARANTINE: " . $e->getMessage() . PHP_EOL); - http_response_code(503); - exit; - } -} - + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, +]; +try { + $pdo = new PDO($dsn, $database_user, $database_pass, $opt); +} +catch (PDOException $e) { + error_log("QUARANTINE: " . $e . PHP_EOL); + http_response_code(501); + exit; +} +// Init Redis +$redis = new Redis(); +$redis->connect('redis-mailcow', 6379); + +// Functions +function parse_email($email) { + if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false; + $a = strrpos($email, '@'); + return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1)); +} +if (!function_exists('getallheaders')) { + function getallheaders() { + if (!is_array($_SERVER)) { + return array(); + } + $headers = array(); + foreach ($_SERVER as $name => $value) { + if (substr($name, 0, 5) == 'HTTP_') { + $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; + } + } + return $headers; + } +} + +$raw_data_content = file_get_contents('php://input'); +$raw_data = mb_convert_encoding($raw_data_content, 'HTML-ENTITIES', "UTF-8"); +$headers = getallheaders(); + +$qid = $headers['X-Rspamd-Qid']; +$fuzzy = $headers['X-Rspamd-Fuzzy']; +$subject = $headers['X-Rspamd-Subject']; +$score = $headers['X-Rspamd-Score']; +$rcpts = $headers['X-Rspamd-Rcpt']; +$user = $headers['X-Rspamd-User']; +$ip = $headers['X-Rspamd-Ip']; +$action = $headers['X-Rspamd-Action']; +$sender = $headers['X-Rspamd-From']; +$symbols = $headers['X-Rspamd-Symbols']; + +$raw_size = (int)$_SERVER['CONTENT_LENGTH']; + +if (empty($sender)) { + error_log("QUARANTINE: Unknown sender, assuming empty-env-from@localhost" . PHP_EOL); + $sender = 'empty-env-from@localhost'; +} + +if ($fuzzy == 'unknown') { + $fuzzy = '[]'; +} + +try { + $max_size = (int)$redis->Get('Q_MAX_SIZE'); + if (($max_size * 1048576) < $raw_size) { + error_log(sprintf("QUARANTINE: Message too large: %d b exceeds %d b", $raw_size, ($max_size * 1048576)) . PHP_EOL); + http_response_code(505); + exit; + } + if ($exclude_domains = $redis->Get('Q_EXCLUDE_DOMAINS')) { + $exclude_domains = json_decode($exclude_domains, true); + } + $retention_size = (int)$redis->Get('Q_RETENTION_SIZE'); +} +catch (RedisException $e) { + error_log("QUARANTINE: " . $e . PHP_EOL); + http_response_code(504); + exit; +} + +$rcpt_final_mailboxes = array(); + +// Loop through all rcpts +foreach (json_decode($rcpts, true) as $rcpt) { + // Remove tag + $rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt); + + // Break rcpt into local part and domain part + $parsed_rcpt = parse_email($rcpt); + + // Skip if not a mailcow handled domain + try { + if (!$redis->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) { + continue; + } + } + catch (RedisException $e) { + error_log("QUARANTINE: " . $e . PHP_EOL); + http_response_code(504); + exit; + } + + // Skip if domain is excluded + if (in_array($parsed_rcpt['domain'], $exclude_domains)) { + error_log(sprintf("QUARANTINE: Skipped domain %s", $parsed_rcpt['domain']) . PHP_EOL); + continue; + } + + // Always assume rcpt is not a final mailbox but an alias for a mailbox or further aliases + // + // rcpt + // | + // mailbox <-- goto ---> alias1, alias2, mailbox2 + // | | + // mailbox3 | + // | + // alias3 ---> mailbox4 + // + try { + $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'"); + $stmt->execute(array( + ':rcpt' => $rcpt + )); + $gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; + if (empty($gotos)) { + $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'"); + $stmt->execute(array( + ':rcpt' => '@' . $parsed_rcpt['domain'] + )); + $gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; + } + if (empty($gotos)) { + $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :rcpt AND `active` = '1'"); + $stmt->execute(array(':rcpt' => $parsed_rcpt['domain'])); + $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; + if ($goto_branch) { + $gotos = $parsed_rcpt['local'] . '@' . $goto_branch; + } + } + $gotos_array = explode(',', $gotos); + + $loop_c = 0; + + while (count($gotos_array) != 0 && $loop_c <= 20) { + + // Loop through all found gotos + foreach ($gotos_array as $index => &$goto) { + error_log("RCPT RESOVLER: http pipe: query " . $goto . " as username from mailbox" . PHP_EOL); + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND (`active`= '1' OR `active`= '2');"); + $stmt->execute(array(':goto' => $goto)); + $username = $stmt->fetch(PDO::FETCH_ASSOC)['username']; + if (!empty($username)) { + error_log("RCPT RESOVLER: http pipe: mailbox found: " . $username . PHP_EOL); + // Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate + if (!in_array($username, $rcpt_final_mailboxes)) { + $rcpt_final_mailboxes[] = $username; + } + } + else { + $parsed_goto = parse_email($goto); + if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) { + error_log("RCPT RESOVLER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL); + } + else { + $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'"); + $stmt->execute(array(':goto' => $goto)); + $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; + if ($goto_branch) { + error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL); + $goto_branch_array = explode(',', $goto_branch); + } else { + $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` AND '1'"); + $stmt->execute(array(':domain' => $parsed_goto['domain'])); + $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; + if ($goto_branch) { + error_log("RCPT RESOVLER: http pipe: goto domain " . $parsed_goto['domain'] . " is a domain alias branch for " . $goto_branch . PHP_EOL); + $goto_branch_array = array($parsed_goto['local'] . '@' . $goto_branch); + } + } + } + } + // goto item was processed, unset + unset($gotos_array[$index]); + } + + // Merge goto branch array derived from previous loop (if any), filter duplicates and unset goto branch array + if (!empty($goto_branch_array)) { + $gotos_array = array_unique(array_merge($gotos_array, $goto_branch_array)); + unset($goto_branch_array); + } + + // Reindex array + $gotos_array = array_values($gotos_array); + + // Force exit if loop cannot be solved + // Postfix does not allow for alias loops, so this should never happen. + $loop_c++; + error_log("RCPT RESOVLER: http pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array) . PHP_EOL); + } + } + catch (PDOException $e) { + error_log("RCPT RESOVLER: " . $e->getMessage() . PHP_EOL); + http_response_code(502); + exit; + } +} + +foreach ($rcpt_final_mailboxes as $rcpt_final) { + error_log("QUARANTINE: quarantine pipe: processing quarantine message for rcpt " . $rcpt_final . PHP_EOL); + try { + $stmt = $pdo->prepare("INSERT INTO `quarantine` (`qid`, `subject`, `score`, `sender`, `rcpt`, `symbols`, `user`, `ip`, `msg`, `action`, `fuzzy_hashes`) + VALUES (:qid, :subject, :score, :sender, :rcpt, :symbols, :user, :ip, :msg, :action, :fuzzy_hashes)"); + $stmt->execute(array( + ':qid' => $qid, + ':subject' => $subject, + ':score' => $score, + ':sender' => $sender, + ':rcpt' => $rcpt_final, + ':symbols' => $symbols, + ':user' => $user, + ':ip' => $ip, + ':msg' => $raw_data, + ':action' => $action, + ':fuzzy_hashes' => $fuzzy + )); + $stmt = $pdo->prepare('DELETE FROM `quarantine` WHERE `rcpt` = :rcpt AND `id` NOT IN ( + SELECT `id` + FROM ( + SELECT `id` + FROM `quarantine` + WHERE `rcpt` = :rcpt2 + ORDER BY id DESC + LIMIT :retention_size + ) x + );'); + $stmt->execute(array( + ':rcpt' => $rcpt_final, + ':rcpt2' => $rcpt_final, + ':retention_size' => $retention_size + )); + } + catch (PDOException $e) { + error_log("QUARANTINE: " . $e->getMessage() . PHP_EOL); + http_response_code(503); + exit; + } +} + diff --git a/data/conf/rspamd/meta_exporter/pipe_rl.php b/data/conf/rspamd/meta_exporter/pipe_rl.php index 5f7fd42c..0bb1b314 100644 --- a/data/conf/rspamd/meta_exporter/pipe_rl.php +++ b/data/conf/rspamd/meta_exporter/pipe_rl.php @@ -1,48 +1,48 @@ -connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT')); - } - else { - $redis->connect('redis-mailcow', 6379); - } -} -catch (Exception $e) { - exit; -} - -$raw_data_content = file_get_contents('php://input'); -$raw_data_decoded = json_decode($raw_data_content, true); - -$data['time'] = time(); -$data['rcpt'] = implode(', ', $raw_data_decoded['rcpt']); -$data['from'] = $raw_data_decoded['from']; -$data['user'] = $raw_data_decoded['user']; -$symbol_rl_key = array_search('RATELIMITED', array_column($raw_data_decoded['symbols'], 'name')); -$data['rl_info'] = implode($raw_data_decoded['symbols'][$symbol_rl_key]['options']); -preg_match('/(.+)\((.+)\)/i', $data['rl_info'], $rl_matches); -if (!empty($rl_matches[1]) && !empty($rl_matches[2])) { - $data['rl_name'] = $rl_matches[1]; - $data['rl_hash'] = $rl_matches[2]; -} -else { - $data['rl_name'] = 'err'; - $data['rl_hash'] = 'err'; -} -$data['qid'] = $raw_data_decoded['qid']; -$data['ip'] = $raw_data_decoded['ip']; -$data['message_id'] = $raw_data_decoded['message_id']; -$data['header_subject'] = implode(' ', $raw_data_decoded['header_subject']); -$data['header_from'] = implode(', ', $raw_data_decoded['header_from']); - -$redis->lpush('RL_LOG', json_encode($data)); -exit; - +connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT')); + } + else { + $redis->connect('redis-mailcow', 6379); + } +} +catch (Exception $e) { + exit; +} + +$raw_data_content = file_get_contents('php://input'); +$raw_data_decoded = json_decode($raw_data_content, true); + +$data['time'] = time(); +$data['rcpt'] = implode(', ', $raw_data_decoded['rcpt']); +$data['from'] = $raw_data_decoded['from']; +$data['user'] = $raw_data_decoded['user']; +$symbol_rl_key = array_search('RATELIMITED', array_column($raw_data_decoded['symbols'], 'name')); +$data['rl_info'] = implode($raw_data_decoded['symbols'][$symbol_rl_key]['options']); +preg_match('/(.+)\((.+)\)/i', $data['rl_info'], $rl_matches); +if (!empty($rl_matches[1]) && !empty($rl_matches[2])) { + $data['rl_name'] = $rl_matches[1]; + $data['rl_hash'] = $rl_matches[2]; +} +else { + $data['rl_name'] = 'err'; + $data['rl_hash'] = 'err'; +} +$data['qid'] = $raw_data_decoded['qid']; +$data['ip'] = $raw_data_decoded['ip']; +$data['message_id'] = $raw_data_decoded['message_id']; +$data['header_subject'] = implode(' ', $raw_data_decoded['header_subject']); +$data['header_from'] = implode(', ', $raw_data_decoded['header_from']); + +$redis->lpush('RL_LOG', json_encode($data)); +exit; + diff --git a/data/conf/rspamd/meta_exporter/pushover.php b/data/conf/rspamd/meta_exporter/pushover.php index 10265d15..106ee659 100644 --- a/data/conf/rspamd/meta_exporter/pushover.php +++ b/data/conf/rspamd/meta_exporter/pushover.php @@ -1,275 +1,275 @@ - PDO::ERRMODE_EXCEPTION, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - PDO::ATTR_EMULATE_PREPARES => false, -]; -try { - $pdo = new PDO($dsn, $database_user, $database_pass, $opt); -} -catch (PDOException $e) { - error_log("NOTIFY: " . $e . PHP_EOL); - http_response_code(501); - exit; -} -// Init Redis -$redis = new Redis(); -$redis->connect('redis-mailcow', 6379); - -// Functions -function parse_email($email) { - if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false; - $a = strrpos($email, '@'); - return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1)); -} -if (!function_exists('getallheaders')) { - function getallheaders() { - if (!is_array($_SERVER)) { - return array(); - } - $headers = array(); - foreach ($_SERVER as $name => $value) { - if (substr($name, 0, 5) == 'HTTP_') { - $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; - } - } - return $headers; - } -} - -$headers = getallheaders(); -$json_body = json_decode(file_get_contents('php://input')); - -$qid = $headers['X-Rspamd-Qid']; -$rcpts = $headers['X-Rspamd-Rcpt']; -$sender = $headers['X-Rspamd-From']; -$ip = $headers['X-Rspamd-Ip']; -$subject = $headers['X-Rspamd-Subject']; -$messageid= $json_body->message_id; -$priority = 0; - -$symbols_array = json_decode($headers['X-Rspamd-Symbols'], true); -if (is_array($symbols_array)) { - foreach ($symbols_array as $symbol) { - if ($symbol['name'] == 'HAS_X_PRIO_ONE') { - $priority = 1; - break; - } - } -} - -$sender_address = $json_body->header_from[0]; -$sender_name = '-'; -if (preg_match('/(?.*?)<(?
.*?)>/i', $sender_address, $matches)) { - $sender_address = $matches['address']; - $sender_name = trim($matches['name'], '"\' '); -} - -$to_address = $json_body->header_to[0]; -$to_name = '-'; -if (preg_match('/(?.*?)<(?
.*?)>/i', $to_address, $matches)) { - $to_address = $matches['address']; - $to_name = trim($matches['name'], '"\' '); -} - -$rcpt_final_mailboxes = array(); - -// Loop through all rcpts -foreach (json_decode($rcpts, true) as $rcpt) { - // Remove tag - $rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt); - - // Break rcpt into local part and domain part - $parsed_rcpt = parse_email($rcpt); - - // Skip if not a mailcow handled domain - try { - if (!$redis->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) { - continue; - } - } - catch (RedisException $e) { - error_log("NOTIFY: " . $e . PHP_EOL); - http_response_code(504); - exit; - } - - // Always assume rcpt is not a final mailbox but an alias for a mailbox or further aliases - // - // rcpt - // | - // mailbox <-- goto ---> alias1, alias2, mailbox2 - // | | - // mailbox3 | - // | - // alias3 ---> mailbox4 - // - try { - $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'"); - $stmt->execute(array( - ':rcpt' => $rcpt - )); - $gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; - if (empty($gotos)) { - $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'"); - $stmt->execute(array( - ':rcpt' => '@' . $parsed_rcpt['domain'] - )); - $gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; - } - if (empty($gotos)) { - $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :rcpt AND `active` = '1'"); - $stmt->execute(array(':rcpt' => $parsed_rcpt['domain'])); - $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; - if ($goto_branch) { - $gotos = $parsed_rcpt['local'] . '@' . $goto_branch; - } - } - $gotos_array = explode(',', $gotos); - - $loop_c = 0; - - while (count($gotos_array) != 0 && $loop_c <= 20) { - - // Loop through all found gotos - foreach ($gotos_array as $index => &$goto) { - error_log("RCPT RESOVLER: http pipe: query " . $goto . " as username from mailbox" . PHP_EOL); - $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND (`active`= '1' OR `active`= '2');"); - $stmt->execute(array(':goto' => $goto)); - $username = $stmt->fetch(PDO::FETCH_ASSOC)['username']; - if (!empty($username)) { - error_log("RCPT RESOVLER: http pipe: mailbox found: " . $username . PHP_EOL); - // Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate - if (!in_array($username, $rcpt_final_mailboxes)) { - $rcpt_final_mailboxes[] = $username; - } - } - else { - $parsed_goto = parse_email($goto); - if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) { - error_log("RCPT RESOVLER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL); - } - else { - $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'"); - $stmt->execute(array(':goto' => $goto)); - $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; - if ($goto_branch) { - error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL); - $goto_branch_array = explode(',', $goto_branch); - } else { - $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` AND '1'"); - $stmt->execute(array(':domain' => $parsed_goto['domain'])); - $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; - if ($goto_branch) { - error_log("RCPT RESOVLER: http pipe: goto domain " . $parsed_goto['domain'] . " is a domain alias branch for " . $goto_branch . PHP_EOL); - $goto_branch_array = array($parsed_goto['local'] . '@' . $goto_branch); - } - } - } - } - // goto item was processed, unset - unset($gotos_array[$index]); - } - - // Merge goto branch array derived from previous loop (if any), filter duplicates and unset goto branch array - if (!empty($goto_branch_array)) { - $gotos_array = array_unique(array_merge($gotos_array, $goto_branch_array)); - unset($goto_branch_array); - } - - // Reindex array - $gotos_array = array_values($gotos_array); - - // Force exit if loop cannot be solved - // Postfix does not allow for alias loops, so this should never happen. - $loop_c++; - error_log("RCPT RESOVLER: http pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array) . PHP_EOL); - } - } - catch (PDOException $e) { - error_log("RCPT RESOVLER: " . $e->getMessage() . PHP_EOL); - http_response_code(502); - exit; - } -} - - -foreach ($rcpt_final_mailboxes as $rcpt_final) { - error_log("NOTIFY: pushover pipe: processing pushover message for rcpt " . $rcpt_final . PHP_EOL); - $stmt = $pdo->prepare("SELECT * FROM `pushover` - WHERE `username` = :username AND `active` = '1'"); - $stmt->execute(array( - ':username' => $rcpt_final - )); - $api_data = $stmt->fetch(PDO::FETCH_ASSOC); - if (isset($api_data['key']) && isset($api_data['token'])) { - $title = (!empty($api_data['title'])) ? $api_data['title'] : 'Mail'; - $text = (!empty($api_data['text'])) ? $api_data['text'] : 'You\'ve got mail 📧'; - $attributes = json_decode($api_data['attributes'], true); - $senders = explode(',', $api_data['senders']); - $senders = array_filter($senders); - $senders_regex = $api_data['senders_regex']; - $sender_validated = false; - if (empty($senders) && empty($senders_regex)) { - $sender_validated = true; - } - else { - if (!empty($senders)) { - if (in_array($sender, $senders)) { - $sender_validated = true; - } - } - if (!empty($senders_regex) && $sender_validated !== true) { - if (preg_match($senders_regex, $sender)) { - $sender_validated = true; - } - } - } - if ($sender_validated === false) { - error_log("NOTIFY: pushover pipe: skipping unwanted sender " . $sender); - continue; - } - if ($attributes['only_x_prio'] == "1" && $priority == 0) { - error_log("NOTIFY: pushover pipe: mail has no X-Priority: 1 header, skipping"); - continue; - } - $post_fields = array( - "token" => $api_data['token'], - "user" => $api_data['key'], - "title" => sprintf("%s", str_replace( - array('{SUBJECT}', '{SENDER}', '{SENDER_NAME}', '{SENDER_ADDRESS}', '{TO_NAME}', '{TO_ADDRESS}', '{MSG_ID}'), - array($subject, $sender, $sender_name, $sender_address, $to_name, $to_address, $messageid), $title) - ), - "priority" => $priority, - "message" => sprintf("%s", str_replace( - array('{SUBJECT}', '{SENDER}', '{SENDER_NAME}', '{SENDER_ADDRESS}', '{TO_NAME}', '{TO_ADDRESS}', '{MSG_ID}', '\n'), - array($subject, $sender, $sender_name, $sender_address, $to_name, $to_address, $messageid, PHP_EOL), $text) - ), - "sound" => $attributes['sound'] ?? "pushover" - ); - if ($attributes['evaluate_x_prio'] == "1" && $priority == 1) { - $post_fields['expire'] = 600; - $post_fields['retry'] = 120; - $post_fields['priority'] = 2; - } - curl_setopt_array($ch = curl_init(), array( - CURLOPT_URL => "https://api.pushover.net/1/messages.json", - CURLOPT_POSTFIELDS => $post_fields, - CURLOPT_SAFE_UPLOAD => true, - CURLOPT_RETURNTRANSFER => true, - )); - $result = curl_exec($ch); - $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - curl_close($ch); - error_log("NOTIFY: result: " . $httpcode . PHP_EOL); - } -} + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, +]; +try { + $pdo = new PDO($dsn, $database_user, $database_pass, $opt); +} +catch (PDOException $e) { + error_log("NOTIFY: " . $e . PHP_EOL); + http_response_code(501); + exit; +} +// Init Redis +$redis = new Redis(); +$redis->connect('redis-mailcow', 6379); + +// Functions +function parse_email($email) { + if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false; + $a = strrpos($email, '@'); + return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1)); +} +if (!function_exists('getallheaders')) { + function getallheaders() { + if (!is_array($_SERVER)) { + return array(); + } + $headers = array(); + foreach ($_SERVER as $name => $value) { + if (substr($name, 0, 5) == 'HTTP_') { + $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; + } + } + return $headers; + } +} + +$headers = getallheaders(); +$json_body = json_decode(file_get_contents('php://input')); + +$qid = $headers['X-Rspamd-Qid']; +$rcpts = $headers['X-Rspamd-Rcpt']; +$sender = $headers['X-Rspamd-From']; +$ip = $headers['X-Rspamd-Ip']; +$subject = $headers['X-Rspamd-Subject']; +$messageid= $json_body->message_id; +$priority = 0; + +$symbols_array = json_decode($headers['X-Rspamd-Symbols'], true); +if (is_array($symbols_array)) { + foreach ($symbols_array as $symbol) { + if ($symbol['name'] == 'HAS_X_PRIO_ONE') { + $priority = 1; + break; + } + } +} + +$sender_address = $json_body->header_from[0]; +$sender_name = '-'; +if (preg_match('/(?.*?)<(?
.*?)>/i', $sender_address, $matches)) { + $sender_address = $matches['address']; + $sender_name = trim($matches['name'], '"\' '); +} + +$to_address = $json_body->header_to[0]; +$to_name = '-'; +if (preg_match('/(?.*?)<(?
.*?)>/i', $to_address, $matches)) { + $to_address = $matches['address']; + $to_name = trim($matches['name'], '"\' '); +} + +$rcpt_final_mailboxes = array(); + +// Loop through all rcpts +foreach (json_decode($rcpts, true) as $rcpt) { + // Remove tag + $rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt); + + // Break rcpt into local part and domain part + $parsed_rcpt = parse_email($rcpt); + + // Skip if not a mailcow handled domain + try { + if (!$redis->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) { + continue; + } + } + catch (RedisException $e) { + error_log("NOTIFY: " . $e . PHP_EOL); + http_response_code(504); + exit; + } + + // Always assume rcpt is not a final mailbox but an alias for a mailbox or further aliases + // + // rcpt + // | + // mailbox <-- goto ---> alias1, alias2, mailbox2 + // | | + // mailbox3 | + // | + // alias3 ---> mailbox4 + // + try { + $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'"); + $stmt->execute(array( + ':rcpt' => $rcpt + )); + $gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; + if (empty($gotos)) { + $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'"); + $stmt->execute(array( + ':rcpt' => '@' . $parsed_rcpt['domain'] + )); + $gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; + } + if (empty($gotos)) { + $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :rcpt AND `active` = '1'"); + $stmt->execute(array(':rcpt' => $parsed_rcpt['domain'])); + $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; + if ($goto_branch) { + $gotos = $parsed_rcpt['local'] . '@' . $goto_branch; + } + } + $gotos_array = explode(',', $gotos); + + $loop_c = 0; + + while (count($gotos_array) != 0 && $loop_c <= 20) { + + // Loop through all found gotos + foreach ($gotos_array as $index => &$goto) { + error_log("RCPT RESOVLER: http pipe: query " . $goto . " as username from mailbox" . PHP_EOL); + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND (`active`= '1' OR `active`= '2');"); + $stmt->execute(array(':goto' => $goto)); + $username = $stmt->fetch(PDO::FETCH_ASSOC)['username']; + if (!empty($username)) { + error_log("RCPT RESOVLER: http pipe: mailbox found: " . $username . PHP_EOL); + // Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate + if (!in_array($username, $rcpt_final_mailboxes)) { + $rcpt_final_mailboxes[] = $username; + } + } + else { + $parsed_goto = parse_email($goto); + if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) { + error_log("RCPT RESOVLER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL); + } + else { + $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'"); + $stmt->execute(array(':goto' => $goto)); + $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; + if ($goto_branch) { + error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL); + $goto_branch_array = explode(',', $goto_branch); + } else { + $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` AND '1'"); + $stmt->execute(array(':domain' => $parsed_goto['domain'])); + $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; + if ($goto_branch) { + error_log("RCPT RESOVLER: http pipe: goto domain " . $parsed_goto['domain'] . " is a domain alias branch for " . $goto_branch . PHP_EOL); + $goto_branch_array = array($parsed_goto['local'] . '@' . $goto_branch); + } + } + } + } + // goto item was processed, unset + unset($gotos_array[$index]); + } + + // Merge goto branch array derived from previous loop (if any), filter duplicates and unset goto branch array + if (!empty($goto_branch_array)) { + $gotos_array = array_unique(array_merge($gotos_array, $goto_branch_array)); + unset($goto_branch_array); + } + + // Reindex array + $gotos_array = array_values($gotos_array); + + // Force exit if loop cannot be solved + // Postfix does not allow for alias loops, so this should never happen. + $loop_c++; + error_log("RCPT RESOVLER: http pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array) . PHP_EOL); + } + } + catch (PDOException $e) { + error_log("RCPT RESOVLER: " . $e->getMessage() . PHP_EOL); + http_response_code(502); + exit; + } +} + + +foreach ($rcpt_final_mailboxes as $rcpt_final) { + error_log("NOTIFY: pushover pipe: processing pushover message for rcpt " . $rcpt_final . PHP_EOL); + $stmt = $pdo->prepare("SELECT * FROM `pushover` + WHERE `username` = :username AND `active` = '1'"); + $stmt->execute(array( + ':username' => $rcpt_final + )); + $api_data = $stmt->fetch(PDO::FETCH_ASSOC); + if (isset($api_data['key']) && isset($api_data['token'])) { + $title = (!empty($api_data['title'])) ? $api_data['title'] : 'Mail'; + $text = (!empty($api_data['text'])) ? $api_data['text'] : 'You\'ve got mail 📧'; + $attributes = json_decode($api_data['attributes'], true); + $senders = explode(',', $api_data['senders']); + $senders = array_filter($senders); + $senders_regex = $api_data['senders_regex']; + $sender_validated = false; + if (empty($senders) && empty($senders_regex)) { + $sender_validated = true; + } + else { + if (!empty($senders)) { + if (in_array($sender, $senders)) { + $sender_validated = true; + } + } + if (!empty($senders_regex) && $sender_validated !== true) { + if (preg_match($senders_regex, $sender)) { + $sender_validated = true; + } + } + } + if ($sender_validated === false) { + error_log("NOTIFY: pushover pipe: skipping unwanted sender " . $sender); + continue; + } + if ($attributes['only_x_prio'] == "1" && $priority == 0) { + error_log("NOTIFY: pushover pipe: mail has no X-Priority: 1 header, skipping"); + continue; + } + $post_fields = array( + "token" => $api_data['token'], + "user" => $api_data['key'], + "title" => sprintf("%s", str_replace( + array('{SUBJECT}', '{SENDER}', '{SENDER_NAME}', '{SENDER_ADDRESS}', '{TO_NAME}', '{TO_ADDRESS}', '{MSG_ID}'), + array($subject, $sender, $sender_name, $sender_address, $to_name, $to_address, $messageid), $title) + ), + "priority" => $priority, + "message" => sprintf("%s", str_replace( + array('{SUBJECT}', '{SENDER}', '{SENDER_NAME}', '{SENDER_ADDRESS}', '{TO_NAME}', '{TO_ADDRESS}', '{MSG_ID}', '\n'), + array($subject, $sender, $sender_name, $sender_address, $to_name, $to_address, $messageid, PHP_EOL), $text) + ), + "sound" => $attributes['sound'] ?? "pushover" + ); + if ($attributes['evaluate_x_prio'] == "1" && $priority == 1) { + $post_fields['expire'] = 600; + $post_fields['retry'] = 120; + $post_fields['priority'] = 2; + } + curl_setopt_array($ch = curl_init(), array( + CURLOPT_URL => "https://api.pushover.net/1/messages.json", + CURLOPT_POSTFIELDS => $post_fields, + CURLOPT_SAFE_UPLOAD => true, + CURLOPT_RETURNTRANSFER => true, + )); + $result = curl_exec($ch); + $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + error_log("NOTIFY: result: " . $httpcode . PHP_EOL); + } +} diff --git a/data/conf/sogo/plist_ldap b/data/conf/sogo/plist_ldap index d585a494..e6d0a82c 100644 --- a/data/conf/sogo/plist_ldap +++ b/data/conf/sogo/plist_ldap @@ -1,28 +1,28 @@ - + diff --git a/data/web/css/build/003-bootstrap-select.css b/data/web/css/build/003-bootstrap-select.css index e248901c..5a2aeaa7 100644 --- a/data/web/css/build/003-bootstrap-select.css +++ b/data/web/css/build/003-bootstrap-select.css @@ -1,10 +1,10 @@ -/*! - * Bootstrap-select v1.14.0-beta2 (https://developer.snapappointments.com/bootstrap-select) - * - * Copyright 2012-2021 SnapAppointments, LLC - * Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE) - */ - +/*! + * Bootstrap-select v1.14.0-beta2 (https://developer.snapappointments.com/bootstrap-select) + * + * Copyright 2012-2021 SnapAppointments, LLC + * Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE) + */ + @-webkit-keyframes bs-notify-fadeOut { 0% { opacity: 0.9; diff --git a/data/web/css/build/014-mailcow.css b/data/web/css/build/014-mailcow.css index 374d484d..06aaa833 100644 --- a/data/web/css/build/014-mailcow.css +++ b/data/web/css/build/014-mailcow.css @@ -1,372 +1,372 @@ -@font-face { - font-family: 'Noto Sans'; - font-style: normal; - font-weight: 400; - src: local(''), - url('/fonts/noto-sans-v12-latin_greek_cyrillic-regular.woff2') format('woff2'), - url('/fonts/noto-sans-v12-latin_greek_cyrillic-regular.woff') format('woff'); -} - -@font-face { - font-family: 'Noto Sans'; - font-style: normal; - font-weight: 700; - src: local(''), - url('/fonts/noto-sans-v12-latin_greek_cyrillic-700.woff2') format('woff2'), - url('/fonts/noto-sans-v12-latin_greek_cyrillic-700.woff') format('woff'); -} - -@font-face { - font-family: 'Noto Sans'; - font-style: italic; - font-weight: 400; - src: local(''), - url('/fonts/noto-sans-v12-latin_greek_cyrillic-italic.woff2') format('woff2'), - url('/fonts/noto-sans-v12-latin_greek_cyrillic-italic.woff') format('woff'); -} - -@font-face { - font-family: 'Noto Sans'; - font-style: italic; - font-weight: 700; - src: local(''), - url('/fonts/noto-sans-v12-latin_greek_cyrillic-700italic.woff2') format('woff2'), - url('/fonts/noto-sans-v12-latin_greek_cyrillic-700italic.woff') format('woff'); -} -#maxmsgsize { min-width: 80px; } -#slider1 .slider-selection { - background: #FFD700; -} -#slider1 .slider-track-high { - background: #FF4500; -} -#slider1 .slider-track-low { - background: #66CD00; -} -.striped:nth-child(odd) { - background-color: #fff; -} -.striped:nth-child(even) { - background-color: #fafafa; - border:1px solid white; -} -.btn { - text-transform: none; -} -.btn * { - pointer-events: none; -} -.textarea-code { - font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace; - background:transparent !important; -} -.navbar-nav { - margin: 0; -} -.navbar-nav .nav-item { - flex-direction: column; - display: flex; - padding: 0 10px !important; -} -.navbar-nav .nav-link { - height: 44px; - display: flex; - align-items: center; - padding: 0 10px !important; -} -.navbar-fixed-bottom .navbar-collapse, -.navbar-fixed-top .navbar-collapse { - max-height: 1000px -} -.bi { - display: inline-block; - font-size: 12pt; -} -.btn .bi { - display: inline-block; - font-size: inherit; -} -.btn-group-xs > .btn, .btn-xs { - padding: .25rem .4rem; - font-size: .875rem; - line-height: 1rem; - border-radius: .2rem; -} -.icon-spin { - animation-name: spin; - animation-duration: 2000ms; - animation-iteration-count: infinite; - animation-timing-function: linear; - -webkit-animation: spin 2000ms infinite linear; -} -.dropdown-menu { - font-size: 0.9rem; -} -@-webkit-keyframes spin { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(359deg); - transform: rotate(359deg); - } -} -@keyframes spin { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(359deg); - transform: rotate(359deg); - } -} -@keyframes blink { - 50% { - color: transparent - } -} -.loader-dot { - animation: 1s blink infinite -} -.loader-dot:nth-child(2) { - animation-delay: 250ms -} -.loader-dot:nth-child(3) { - animation-delay: 500ms -} - -pre{white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word;} -/* Fix modal moving content left */ -body.modal-open { - overflow: inherit; - padding-right: inherit !important; -} -body { - font-family: "Noto Sans","Helvetica Neue",Helvetica,Arial,sans-serif; - font-size: 10.5pt; - line-height: 1.5; -} -html { - font-family: "Noto Sans","Helvetica Neue",Helvetica,Arial,sans-serif; - font-size: 10.5pt; - line-height: 1.5; -} -#mailcow-alert { - position: fixed; - bottom: 8px; - right: 25px; - min-width: 350px; - max-width: 550px; - z-index: 2000; -} -.input-group-sm .btn { margin-top: 0 !important } -legend { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - -o-user-select: none; - user-select: none; - font-size: 1.2rem; -} -.navbar .navbar-brand { - padding-top: 5px; -} -.navbar .navbar-brand img { - height: 40px; -} -.mailcow-logo img { - max-width: 250px; -} -.lang-link-disabled a { - pointer-events: none; -} -.lang-link-disabled { - cursor: not-allowed; -} -.overlay { - background: #fff; - position: absolute; - z-index: 10000; - top: 0; right: 0; bottom: 0; left: 0; - opacity: 0.7; -} -.bootstrap-select.btn-group .no-results { - display: none; -} -.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary { - color: rgb(197, 197, 197) !important; -} -.haveibeenpwned { - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} -.full-width-select { - width: 100%!important; -} -.tooltip { - font-family: inherit; - font-size: 0.8rem; -} -.progress-bar { - font-size: 0.8rem; - line-height: 14px; -} -.footer { - margin-top: 27px; - margin-bottom: 20px; - color: #959595; - display: flex; - flex-direction: column; -} -.footer .version { - margin-left: auto; - margin-top: 20px; -} -.slave-info { - padding: 15px 0px 15px 15px; - font-weight: bold; -} -.alert-hr { - margin:3px 0px; - border-bottom:1px solid #f5f5f5!important; - opacity: 0.3; -} -.btn-input-missing, -.btn-input-missing:hover, -.btn-input-missing:active, -.btn-input-missing:focus, -.btn-input-missing:active:hover, -.btn-input-missing:active:focus { - color: #000 !important; - background-color: #ff2f24 !important; - border-color: #e21207 !important; -} -.navbar-nav > li { - font-size: 1rem !important; -} -.dropdown-menu > li > a { - font-size: 1rem !important; -} -.label { - font-size:inherit; -} -[class^="bi-"]::before, [class*=" bi-"]::before { - vertical-align: -0.2em !important; -} -legend > [class^="bi-"]::before, legend > [class*=" bi-"]::before { - vertical-align: 0em !important; -} -code { - font-size: inherit; -} -.bootstrap-select.btn-group.show-tick .dropdown-menu li.selected a span.check-mark { - margin-top: 0px; -} -.flag-icon { - margin-right: 5px; -} - -.dropdown-header { - font-weight: 600; -} - - -.tag-box { - display: flex; - flex-wrap: wrap; - height: auto; -} -.tag-badge { - transition: 200ms linear; - margin-top: 5px; - margin-bottom: 5px; - margin-left: 2px; - margin-right: 2px; -} -.tag-badge.btn-badge { - cursor: pointer; -} -.tag-badge .bi { - font-size: 12px; -} -.tag-badge.btn-badge:hover { - filter: brightness(0.9); -} -.tag-input { - margin-left: 10px; - border: 0 !important; - flex: 1; - height: 24px; - min-width: 150px; -} -.tag-input:focus { - outline: none; -} -.tag-add { - padding: 0 5px 0 5px; - align-items: center; - display: inline-flex; -} - -#dnstable { - overflow-x: auto!important; -} -.well { - border: 1px solid #dfdfdf; - background-color: #f9f9f9; - padding: 10px; -} - - -.btn-check-label { - color: #555; -} - -.caret { - transform: rotate(0deg); -} -a[aria-expanded='true'] > .caret, -button[aria-expanded='true'] > .caret { - transform: rotate(-180deg); -} - -.list-group-details { - background: #fff; -} -.list-group-header { - background: #f7f7f7; -} - - -.bg-primary, .alert-primary, .btn-primary { - background-color: #0F688D !important; - border-color: #0d526d !important; -} -.bg-info, .alert-info, .btn-info { - background-color: #148DBC !important; - border-color: #127ea8 !important; -} - -.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary { - color: rgb(137 137 137)!important; -} - -.progress { - background-color: #d5d5d5; -} - - -.btn-outline-secondary:hover { - background-color: #f0f0f0; -} -.btn.btn-outline-secondary { - border-color: #cfcfcf !important; -} -.btn-check:checked+.btn-outline-secondary, .btn-check:active+.btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show { - background-color: #f0f0f0 !important; -} +@font-face { + font-family: 'Noto Sans'; + font-style: normal; + font-weight: 400; + src: local(''), + url('/fonts/noto-sans-v12-latin_greek_cyrillic-regular.woff2') format('woff2'), + url('/fonts/noto-sans-v12-latin_greek_cyrillic-regular.woff') format('woff'); +} + +@font-face { + font-family: 'Noto Sans'; + font-style: normal; + font-weight: 700; + src: local(''), + url('/fonts/noto-sans-v12-latin_greek_cyrillic-700.woff2') format('woff2'), + url('/fonts/noto-sans-v12-latin_greek_cyrillic-700.woff') format('woff'); +} + +@font-face { + font-family: 'Noto Sans'; + font-style: italic; + font-weight: 400; + src: local(''), + url('/fonts/noto-sans-v12-latin_greek_cyrillic-italic.woff2') format('woff2'), + url('/fonts/noto-sans-v12-latin_greek_cyrillic-italic.woff') format('woff'); +} + +@font-face { + font-family: 'Noto Sans'; + font-style: italic; + font-weight: 700; + src: local(''), + url('/fonts/noto-sans-v12-latin_greek_cyrillic-700italic.woff2') format('woff2'), + url('/fonts/noto-sans-v12-latin_greek_cyrillic-700italic.woff') format('woff'); +} +#maxmsgsize { min-width: 80px; } +#slider1 .slider-selection { + background: #FFD700; +} +#slider1 .slider-track-high { + background: #FF4500; +} +#slider1 .slider-track-low { + background: #66CD00; +} +.striped:nth-child(odd) { + background-color: #fff; +} +.striped:nth-child(even) { + background-color: #fafafa; + border:1px solid white; +} +.btn { + text-transform: none; +} +.btn * { + pointer-events: none; +} +.textarea-code { + font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace; + background:transparent !important; +} +.navbar-nav { + margin: 0; +} +.navbar-nav .nav-item { + flex-direction: column; + display: flex; + padding: 0 10px !important; +} +.navbar-nav .nav-link { + height: 44px; + display: flex; + align-items: center; + padding: 0 10px !important; +} +.navbar-fixed-bottom .navbar-collapse, +.navbar-fixed-top .navbar-collapse { + max-height: 1000px +} +.bi { + display: inline-block; + font-size: 12pt; +} +.btn .bi { + display: inline-block; + font-size: inherit; +} +.btn-group-xs > .btn, .btn-xs { + padding: .25rem .4rem; + font-size: .875rem; + line-height: 1rem; + border-radius: .2rem; +} +.icon-spin { + animation-name: spin; + animation-duration: 2000ms; + animation-iteration-count: infinite; + animation-timing-function: linear; + -webkit-animation: spin 2000ms infinite linear; +} +.dropdown-menu { + font-size: 0.9rem; +} +@-webkit-keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} +@keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} +@keyframes blink { + 50% { + color: transparent + } +} +.loader-dot { + animation: 1s blink infinite +} +.loader-dot:nth-child(2) { + animation-delay: 250ms +} +.loader-dot:nth-child(3) { + animation-delay: 500ms +} + +pre{white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word;} +/* Fix modal moving content left */ +body.modal-open { + overflow: inherit; + padding-right: inherit !important; +} +body { + font-family: "Noto Sans","Helvetica Neue",Helvetica,Arial,sans-serif; + font-size: 10.5pt; + line-height: 1.5; +} +html { + font-family: "Noto Sans","Helvetica Neue",Helvetica,Arial,sans-serif; + font-size: 10.5pt; + line-height: 1.5; +} +#mailcow-alert { + position: fixed; + bottom: 8px; + right: 25px; + min-width: 350px; + max-width: 550px; + z-index: 2000; +} +.input-group-sm .btn { margin-top: 0 !important } +legend { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; + font-size: 1.2rem; +} +.navbar .navbar-brand { + padding-top: 5px; +} +.navbar .navbar-brand img { + height: 40px; +} +.mailcow-logo img { + max-width: 250px; +} +.lang-link-disabled a { + pointer-events: none; +} +.lang-link-disabled { + cursor: not-allowed; +} +.overlay { + background: #fff; + position: absolute; + z-index: 10000; + top: 0; right: 0; bottom: 0; left: 0; + opacity: 0.7; +} +.bootstrap-select.btn-group .no-results { + display: none; +} +.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary { + color: rgb(197, 197, 197) !important; +} +.haveibeenpwned { + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.full-width-select { + width: 100%!important; +} +.tooltip { + font-family: inherit; + font-size: 0.8rem; +} +.progress-bar { + font-size: 0.8rem; + line-height: 14px; +} +.footer { + margin-top: 27px; + margin-bottom: 20px; + color: #959595; + display: flex; + flex-direction: column; +} +.footer .version { + margin-left: auto; + margin-top: 20px; +} +.slave-info { + padding: 15px 0px 15px 15px; + font-weight: bold; +} +.alert-hr { + margin:3px 0px; + border-bottom:1px solid #f5f5f5!important; + opacity: 0.3; +} +.btn-input-missing, +.btn-input-missing:hover, +.btn-input-missing:active, +.btn-input-missing:focus, +.btn-input-missing:active:hover, +.btn-input-missing:active:focus { + color: #000 !important; + background-color: #ff2f24 !important; + border-color: #e21207 !important; +} +.navbar-nav > li { + font-size: 1rem !important; +} +.dropdown-menu > li > a { + font-size: 1rem !important; +} +.label { + font-size:inherit; +} +[class^="bi-"]::before, [class*=" bi-"]::before { + vertical-align: -0.2em !important; +} +legend > [class^="bi-"]::before, legend > [class*=" bi-"]::before { + vertical-align: 0em !important; +} +code { + font-size: inherit; +} +.bootstrap-select.btn-group.show-tick .dropdown-menu li.selected a span.check-mark { + margin-top: 0px; +} +.flag-icon { + margin-right: 5px; +} + +.dropdown-header { + font-weight: 600; +} + + +.tag-box { + display: flex; + flex-wrap: wrap; + height: auto; +} +.tag-badge { + transition: 200ms linear; + margin-top: 5px; + margin-bottom: 5px; + margin-left: 2px; + margin-right: 2px; +} +.tag-badge.btn-badge { + cursor: pointer; +} +.tag-badge .bi { + font-size: 12px; +} +.tag-badge.btn-badge:hover { + filter: brightness(0.9); +} +.tag-input { + margin-left: 10px; + border: 0 !important; + flex: 1; + height: 24px; + min-width: 150px; +} +.tag-input:focus { + outline: none; +} +.tag-add { + padding: 0 5px 0 5px; + align-items: center; + display: inline-flex; +} + +#dnstable { + overflow-x: auto!important; +} +.well { + border: 1px solid #dfdfdf; + background-color: #f9f9f9; + padding: 10px; +} + + +.btn-check-label { + color: #555; +} + +.caret { + transform: rotate(0deg); +} +a[aria-expanded='true'] > .caret, +button[aria-expanded='true'] > .caret { + transform: rotate(-180deg); +} + +.list-group-details { + background: #fff; +} +.list-group-header { + background: #f7f7f7; +} + + +.bg-primary, .alert-primary, .btn-primary { + background-color: #0F688D !important; + border-color: #0d526d !important; +} +.bg-info, .alert-info, .btn-info { + background-color: #148DBC !important; + border-color: #127ea8 !important; +} + +.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary { + color: rgb(137 137 137)!important; +} + +.progress { + background-color: #d5d5d5; +} + + +.btn-outline-secondary:hover { + background-color: #f0f0f0; +} +.btn.btn-outline-secondary { + border-color: #cfcfcf !important; +} +.btn-check:checked+.btn-outline-secondary, .btn-check:active+.btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show { + background-color: #f0f0f0 !important; +} diff --git a/data/web/css/site/admin.css b/data/web/css/site/admin.css index e49046b0..2c2fc643 100644 --- a/data/web/css/site/admin.css +++ b/data/web/css/site/admin.css @@ -1,86 +1,86 @@ -.pagination a { - text-decoration: none !important; -} -.panel.panel-default { - overflow: visible !important; -} -.table-responsive { - overflow: visible !important; -} -.table-responsive { - overflow-x: scroll !important; -} -body { - overflow-y:scroll; -} -/* Fix modal moving content left */ -body.modal-open { - overflow-y:scroll; - padding-right: inherit !important; -} -@media (min-width: 992px) { - .container { - width: 80%; - } -} -.mass-actions-admin { - user-select: none; -} -.inputMissingAttr { - border-color: #FF4136; -} -.rotate { - -moz-transition: all 0.3s linear; - -webkit-transition: all 0.3s linear; - transition: all 0.3s linear; -} -.rotate.animation { - -ms-transform:rotateX(180deg); - -moz-transform:rotateX(180deg); - -webkit-transform:rotateX(180deg); - transform:rotateX(180deg); -} -.anchor { - display: block; - height: 65px; - margin-top: -65px; - visibility: hidden; -} -.thumbnail img { - min-height:100px; - height:100px; -} -.nav-tabs>li>a { - z-index: 1; -} -.table-condensed .input-sm { - width: 100%!important; -} -.table-condensed > thead > tr > th, .table-condensed > tbody > tr > th, .table-condensed > tfoot > tr > th, .table-condensed > thead > tr > td, .table-condensed > tbody > tr > td, .table-condensed > tfoot > tr > td { - padding: 3px; -} -table tbody tr { - cursor: pointer; -} -table tbody tr td input[type="checkbox"] { - cursor: pointer; -} -#quarantine_template { - margin:20px; -} -.regex-input { - font-family: Consolas,monaco,monospace; - font-size: 1rem; -} -.label-keys { - font-size:100%; - margin: 0px !important; - white-space: normal !important; -} -.key-action { - font-weight:bold; - color:white !important; -} -.dkim-label { - margin: 0 0 8px !important; +.pagination a { + text-decoration: none !important; +} +.panel.panel-default { + overflow: visible !important; +} +.table-responsive { + overflow: visible !important; +} +.table-responsive { + overflow-x: scroll !important; +} +body { + overflow-y:scroll; +} +/* Fix modal moving content left */ +body.modal-open { + overflow-y:scroll; + padding-right: inherit !important; +} +@media (min-width: 992px) { + .container { + width: 80%; + } +} +.mass-actions-admin { + user-select: none; +} +.inputMissingAttr { + border-color: #FF4136; +} +.rotate { + -moz-transition: all 0.3s linear; + -webkit-transition: all 0.3s linear; + transition: all 0.3s linear; +} +.rotate.animation { + -ms-transform:rotateX(180deg); + -moz-transform:rotateX(180deg); + -webkit-transform:rotateX(180deg); + transform:rotateX(180deg); +} +.anchor { + display: block; + height: 65px; + margin-top: -65px; + visibility: hidden; +} +.thumbnail img { + min-height:100px; + height:100px; +} +.nav-tabs>li>a { + z-index: 1; +} +.table-condensed .input-sm { + width: 100%!important; +} +.table-condensed > thead > tr > th, .table-condensed > tbody > tr > th, .table-condensed > tfoot > tr > th, .table-condensed > thead > tr > td, .table-condensed > tbody > tr > td, .table-condensed > tfoot > tr > td { + padding: 3px; +} +table tbody tr { + cursor: pointer; +} +table tbody tr td input[type="checkbox"] { + cursor: pointer; +} +#quarantine_template { + margin:20px; +} +.regex-input { + font-family: Consolas,monaco,monospace; + font-size: 1rem; +} +.label-keys { + font-size:100%; + margin: 0px !important; + white-space: normal !important; +} +.key-action { + font-weight:bold; + color:white !important; +} +.dkim-label { + margin: 0 0 8px !important; } \ No newline at end of file diff --git a/data/web/css/site/debug.css b/data/web/css/site/debug.css index 6e15c2b0..0a212c7b 100644 --- a/data/web/css/site/debug.css +++ b/data/web/css/site/debug.css @@ -1,36 +1,36 @@ -.pagination a { - text-decoration: none !important; -} -.panel.panel-default { - overflow: visible !important; -} -.table-responsive { - overflow: visible !important; -} -@media screen and (max-width: 1280px) { - .table-responsive { - overflow-x: scroll !important; - } -} -.footer-add-item { - display:block; - text-align: center; - font-style: italic; - padding: 10px; - background: #F5F5F5; -} -@media (min-width: 992px) { - .container { - width: 80%; - } -} -.mass-actions-debug { - user-select: none; -} -.inputMissingAttr { - border-color: #FF4136; -} -.table-lines { - vertical-align: inherit; -} - +.pagination a { + text-decoration: none !important; +} +.panel.panel-default { + overflow: visible !important; +} +.table-responsive { + overflow: visible !important; +} +@media screen and (max-width: 1280px) { + .table-responsive { + overflow-x: scroll !important; + } +} +.footer-add-item { + display:block; + text-align: center; + font-style: italic; + padding: 10px; + background: #F5F5F5; +} +@media (min-width: 992px) { + .container { + width: 80%; + } +} +.mass-actions-debug { + user-select: none; +} +.inputMissingAttr { + border-color: #FF4136; +} +.table-lines { + vertical-align: inherit; +} + diff --git a/data/web/css/site/edit.css b/data/web/css/site/edit.css index b0d93839..18c87b13 100644 --- a/data/web/css/site/edit.css +++ b/data/web/css/site/edit.css @@ -1,42 +1,42 @@ -.pagination a { - text-decoration: none !important; -} -.panel.panel-default { - overflow: visible !important; -} -.table-responsive { - overflow: visible !important; -} -@media screen and (max-width: 767px) { - .table-responsive { - overflow-x: scroll !important; - } -} -.footer-add-item { - display:block; - text-align: center; - font-style: italic; - padding: 10px; - background: #F5F5F5; -} -.mass-actions-user { - user-select: none; -} -.inputMissingAttr { - border-color: #FF4136; -} -.rotate { - -moz-transition: all 0.3s linear; - -webkit-transition: all 0.3s linear; - transition: all 0.3s linear; -} -.rotate.animation { - -ms-transform:rotateX(180deg); - -moz-transform:rotateX(180deg); - -webkit-transform:rotateX(180deg); - transform:rotateX(180deg); -} -#sender_acl_disabled { - display:none; - margin-top:10px; -} +.pagination a { + text-decoration: none !important; +} +.panel.panel-default { + overflow: visible !important; +} +.table-responsive { + overflow: visible !important; +} +@media screen and (max-width: 767px) { + .table-responsive { + overflow-x: scroll !important; + } +} +.footer-add-item { + display:block; + text-align: center; + font-style: italic; + padding: 10px; + background: #F5F5F5; +} +.mass-actions-user { + user-select: none; +} +.inputMissingAttr { + border-color: #FF4136; +} +.rotate { + -moz-transition: all 0.3s linear; + -webkit-transition: all 0.3s linear; + transition: all 0.3s linear; +} +.rotate.animation { + -ms-transform:rotateX(180deg); + -moz-transform:rotateX(180deg); + -webkit-transform:rotateX(180deg); + transform:rotateX(180deg); +} +#sender_acl_disabled { + display:none; + margin-top:10px; +} diff --git a/data/web/css/site/index.css b/data/web/css/site/index.css index 41438a87..dc4487ae 100644 --- a/data/web/css/site/index.css +++ b/data/web/css/site/index.css @@ -1,8 +1,8 @@ -@media (max-width: 500px) { - #top { - padding-top: 15px !important; - } -} -.ui-announcement-alert { - margin: 10px 0px 10px 0px; -} +@media (max-width: 500px) { + #top { + padding-top: 15px !important; + } +} +.ui-announcement-alert { + margin: 10px 0px 10px 0px; +} diff --git a/data/web/css/site/mailbox.css b/data/web/css/site/mailbox.css index f62ead31..40da42bc 100644 --- a/data/web/css/site/mailbox.css +++ b/data/web/css/site/mailbox.css @@ -1,69 +1,69 @@ -.pagination a { - text-decoration: none !important; -} -.panel.panel-default { - overflow: visible !important; -} -.table-responsive { - overflow: inherit !important; -} -.table-responsive { - overflow-x: scroll !important; -} -.btn-group { - width: max-content; -} -.footer-add-item { - display:block; - text-align: center; - font-style: italic; - padding: 10px; - background: #F5F5F5; -} -@media (min-width: 992px) { - .container { - width: 100%; - } -} -@media (min-width: 1920px) { - .container { - width: 80%; - } -} -.mass-actions-mailbox { - user-select: none; -} -.inputMissingAttr { - border-color: #FF4136; -} -.dns-found { - max-width: 300px; - word-break: break-all; -} -.dns-recommended { - max-width: 150px; - word-break: break-all; -} -.table-lines { - vertical-align: inherit; -} -#logText { - font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace; - font-size:smaller; -} -table tbody tr { - cursor: pointer; -} -table tbody tr td input[type="checkbox"] { - cursor: pointer; -} -.label-last-login .bi { - font-size: 8pt !important; -} -.label-last-login { - line-height: 2.2; - color: #4a4a4a!important; - padding: .2em .4em .3em !important; - background-color: #ececec!important; -} - +.pagination a { + text-decoration: none !important; +} +.panel.panel-default { + overflow: visible !important; +} +.table-responsive { + overflow: inherit !important; +} +.table-responsive { + overflow-x: scroll !important; +} +.btn-group { + width: max-content; +} +.footer-add-item { + display:block; + text-align: center; + font-style: italic; + padding: 10px; + background: #F5F5F5; +} +@media (min-width: 992px) { + .container { + width: 100%; + } +} +@media (min-width: 1920px) { + .container { + width: 80%; + } +} +.mass-actions-mailbox { + user-select: none; +} +.inputMissingAttr { + border-color: #FF4136; +} +.dns-found { + max-width: 300px; + word-break: break-all; +} +.dns-recommended { + max-width: 150px; + word-break: break-all; +} +.table-lines { + vertical-align: inherit; +} +#logText { + font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace; + font-size:smaller; +} +table tbody tr { + cursor: pointer; +} +table tbody tr td input[type="checkbox"] { + cursor: pointer; +} +.label-last-login .bi { + font-size: 8pt !important; +} +.label-last-login { + line-height: 2.2; + color: #4a4a4a!important; + padding: .2em .4em .3em !important; + background-color: #ececec!important; +} + diff --git a/data/web/css/site/user.css b/data/web/css/site/user.css index b1148d5f..a294c7ed 100644 --- a/data/web/css/site/user.css +++ b/data/web/css/site/user.css @@ -1,134 +1,134 @@ -.pagination a { - text-decoration: none !important; -} -.panel.panel-default { - overflow: visible !important; -} -.table-responsive { - overflow: visible !important; -} -@media screen and (max-width: 767px) { - .table-responsive { - overflow-x: scroll !important; - } -} -.footer-add-item { - display:block; - text-align: center; - font-style: italic; - padding: 10px; - background: #F5F5F5; -} -.mass-actions-user { - user-select: none; -} -.inputMissingAttr { - border-color: #FF4136; -} -#logText { - white-space: pre-wrap; - white-space: -moz-pre-wrap; - white-space: -o-pre-wrap; - word-wrap: break-word; -} -body { - overflow-y:scroll; -} - -table tbody tr { - cursor: pointer; -} - -table tbody tr td input[type="checkbox"] { - cursor: pointer; -} -.label-keys { - font-size:100%; - margin: 0px !important; - white-space: normal !important; -} -.key-action { - font-weight:bold; - color:white !important; -} -svg { - display: inline-block; - vertical-align: middle; -} -.c-1-color, .label-ham { - background: #28b62c; - background: -webkit-linear-gradient(to right, #28b62c, #fff233); - background: linear-gradient(to right, #28b62c, #fff233); - color: #000; -} -.c-2-color, .label-spam { - background: #fff233; - background: -webkit-linear-gradient(to right, #fff233, #ff4136); - background: linear-gradient(to right, #fff233, #ff4136); - color: #000; -} -.c-3-color, .label-reject{ - background: #ff4136; - color: #fff; -} -#spam_score { - margin-bottom: 10px; -} -.noUi-handle { - border: 1px solid #e2e2e2; - border-radius: 0px; - background: #eee; - cursor: default; - box-shadow: none; - border-top-width: 0px; - border-right-width: 1px; - border-bottom-width: 4px; - border-left-width: 1px; -} -.noUi-handle:hover { - background-color: #eee; - border-color: #e2e2e2; - margin-top: 1px; -border-bottom-width: 3px; -} -.noUi-handle::after, .noUi-handle::before { - background: #c6c6c6; - width: 2px; -} -.noUi-target { - background: transparent; - border-radius: 0px; - border: 1px solid #D3D3D3; - box-shadow: none; -} -.noUi-connects { - border-radius: 0px; -} -.label-ham, -.label-spam, -.label-reject { - padding: .1em .5em .1em; - font-size: inherit; - font-weight: 400; -} -.clear-last-logins { - cursor: pointer; - font-size:90%; - font-style: italic; - color: #158cba; - user-select:none; -} -.ip-location-flag { - border-radius: 4px; - top: 3px; -} -.recent-login-success { - margin-top:2px; - margin-right:10px; -} -.label-protocol-access { - line-height: 2; -} -.help-block-mt-0 { - margin-top: 0px; -} +.pagination a { + text-decoration: none !important; +} +.panel.panel-default { + overflow: visible !important; +} +.table-responsive { + overflow: visible !important; +} +@media screen and (max-width: 767px) { + .table-responsive { + overflow-x: scroll !important; + } +} +.footer-add-item { + display:block; + text-align: center; + font-style: italic; + padding: 10px; + background: #F5F5F5; +} +.mass-actions-user { + user-select: none; +} +.inputMissingAttr { + border-color: #FF4136; +} +#logText { + white-space: pre-wrap; + white-space: -moz-pre-wrap; + white-space: -o-pre-wrap; + word-wrap: break-word; +} +body { + overflow-y:scroll; +} + +table tbody tr { + cursor: pointer; +} + +table tbody tr td input[type="checkbox"] { + cursor: pointer; +} +.label-keys { + font-size:100%; + margin: 0px !important; + white-space: normal !important; +} +.key-action { + font-weight:bold; + color:white !important; +} +svg { + display: inline-block; + vertical-align: middle; +} +.c-1-color, .label-ham { + background: #28b62c; + background: -webkit-linear-gradient(to right, #28b62c, #fff233); + background: linear-gradient(to right, #28b62c, #fff233); + color: #000; +} +.c-2-color, .label-spam { + background: #fff233; + background: -webkit-linear-gradient(to right, #fff233, #ff4136); + background: linear-gradient(to right, #fff233, #ff4136); + color: #000; +} +.c-3-color, .label-reject{ + background: #ff4136; + color: #fff; +} +#spam_score { + margin-bottom: 10px; +} +.noUi-handle { + border: 1px solid #e2e2e2; + border-radius: 0px; + background: #eee; + cursor: default; + box-shadow: none; + border-top-width: 0px; + border-right-width: 1px; + border-bottom-width: 4px; + border-left-width: 1px; +} +.noUi-handle:hover { + background-color: #eee; + border-color: #e2e2e2; + margin-top: 1px; +border-bottom-width: 3px; +} +.noUi-handle::after, .noUi-handle::before { + background: #c6c6c6; + width: 2px; +} +.noUi-target { + background: transparent; + border-radius: 0px; + border: 1px solid #D3D3D3; + box-shadow: none; +} +.noUi-connects { + border-radius: 0px; +} +.label-ham, +.label-spam, +.label-reject { + padding: .1em .5em .1em; + font-size: inherit; + font-weight: 400; +} +.clear-last-logins { + cursor: pointer; + font-size:90%; + font-style: italic; + color: #158cba; + user-select:none; +} +.ip-location-flag { + border-radius: 4px; + top: 3px; +} +.recent-login-success { + margin-top:2px; + margin-right:10px; +} +.label-protocol-access { + line-height: 2; +} +.help-block-mt-0 { + margin-top: 0px; +} diff --git a/data/web/inc/ajax/container_ctrl.php b/data/web/inc/ajax/container_ctrl.php index f0e220f9..c93d86a8 100644 --- a/data/web/inc/ajax/container_ctrl.php +++ b/data/web/inc/ajax/container_ctrl.php @@ -1,52 +1,52 @@ -OK' : 'Error: ' . $response['msg'] . ''; - if ($response['type'] == "success") { - break; - } - usleep(1500000); - $retry++; - } - echo (!isset($last_response)) ? 'Already running' : $last_response; - } - if ($_GET['action'] == "stop") { - header('Content-Type: text/html; charset=utf-8'); - $retry = 0; - while (docker('info', $_GET['service'])['State']['Running'] == 1 && $retry <= 3) { - $response = docker('post', $_GET['service'], 'stop'); - $response = json_decode($response, true); - $last_response = ($response['type'] == "success") ? 'OK' : 'Error: ' . $response['msg'] . ''; - if ($response['type'] == "success") { - break; - } - usleep(1500000); - $retry++; - } - echo (!isset($last_response)) ? 'Not running' : $last_response; - } - if ($_GET['action'] == "restart") { - header('Content-Type: text/html; charset=utf-8'); - $response = docker('post', $_GET['service'], 'restart'); - $response = json_decode($response, true); - $last_response = ($response['type'] == "success") ? 'OK' : 'Error: ' . $response['msg'] . ''; - echo (!isset($last_response)) ? 'Cannot restart container' : $last_response; - } - if ($_GET['action'] == "logs") { - $lines = (empty($_GET['lines']) || !is_numeric($_GET['lines'])) ? 1000 : $_GET['lines']; - header('Content-Type: text/plain; charset=utf-8'); - print_r(preg_split('/\n/', docker('logs', $_GET['service'], $lines))); - } -} - -?> +OK' : 'Error: ' . $response['msg'] . ''; + if ($response['type'] == "success") { + break; + } + usleep(1500000); + $retry++; + } + echo (!isset($last_response)) ? 'Already running' : $last_response; + } + if ($_GET['action'] == "stop") { + header('Content-Type: text/html; charset=utf-8'); + $retry = 0; + while (docker('info', $_GET['service'])['State']['Running'] == 1 && $retry <= 3) { + $response = docker('post', $_GET['service'], 'stop'); + $response = json_decode($response, true); + $last_response = ($response['type'] == "success") ? 'OK' : 'Error: ' . $response['msg'] . ''; + if ($response['type'] == "success") { + break; + } + usleep(1500000); + $retry++; + } + echo (!isset($last_response)) ? 'Not running' : $last_response; + } + if ($_GET['action'] == "restart") { + header('Content-Type: text/html; charset=utf-8'); + $response = docker('post', $_GET['service'], 'restart'); + $response = json_decode($response, true); + $last_response = ($response['type'] == "success") ? 'OK' : 'Error: ' . $response['msg'] . ''; + echo (!isset($last_response)) ? 'Cannot restart container' : $last_response; + } + if ($_GET['action'] == "logs") { + $lines = (empty($_GET['lines']) || !is_numeric($_GET['lines'])) ? 1000 : $_GET['lines']; + header('Content-Type: text/plain; charset=utf-8'); + print_r(preg_split('/\n/', docker('logs', $_GET['service'], $lines))); + } +} + +?> diff --git a/data/web/inc/ajax/destroy_tfa_auth.php b/data/web/inc/ajax/destroy_tfa_auth.php index 07873b55..2f0f4189 100644 --- a/data/web/inc/ajax/destroy_tfa_auth.php +++ b/data/web/inc/ajax/destroy_tfa_auth.php @@ -1,6 +1,6 @@ - + diff --git a/data/web/inc/ajax/qitem_details.php b/data/web/inc/ajax/qitem_details.php index 1611c822..26af131c 100644 --- a/data/web/inc/ajax/qitem_details.php +++ b/data/web/inc/ajax/qitem_details.php @@ -1,207 +1,207 @@ -getAddresses($headerName); - foreach ($addresses as $address) { - if (filter_var($address['address'], FILTER_VALIDATE_EMAIL)) { - $list[] = array('address' => $address['address'], 'type' => $headerName); - } - } -} - -if (!empty($_GET['hash']) && ctype_alnum($_GET['hash'])) { - $mailc = quarantine('hash_details', $_GET['hash']); - if ($mailc === false) { - echo json_encode(array('error' => 'Message invalid')); - exit; - } - if (strlen($mailc['msg']) > 10485760) { - echo json_encode(array('error' => 'Message size exceeds 10 MiB.')); - exit; - } - if (!empty($mailc['msg'])) { - // Init message array - $data = array(); - // Init parser - $mail_parser = new PhpMimeMailParser\Parser(); - $html2text = new Html2Text\Html2Text(); - // Load msg to parser - $mail_parser->setText($mailc['msg']); - // Get mail recipients - { - $recipientsList = array(); - addAddresses($recipientsList, $mail_parser, 'to'); - addAddresses($recipientsList, $mail_parser, 'cc'); - addAddresses($recipientsList, $mail_parser, 'bcc'); - $recipientsList[] = array('address' => $mailc['rcpt'], 'type' => 'smtp'); - $data['recipients'] = $recipientsList; - } - // Get from - $data['header_from'] = $mail_parser->getHeader('from'); - $data['env_from'] = $mailc['sender']; - // Get rspamd score - $data['score'] = $mailc['score']; - // Get rspamd action - $data['action'] = $mailc['action']; - // Get rspamd symbols - $data['symbols'] = json_decode($mailc['symbols']); - // Get fuzzy hashes - $data['fuzzy_hashes'] = json_decode($mailc['fuzzy_hashes']); - $data['subject'] = mb_convert_encoding($mail_parser->getHeader('subject'), "UTF-8", "auto"); - (empty($data['subject'])) ? $data['subject'] = '-' : null; - echo json_encode($data); - } -} -elseif (!empty($_GET['id']) && ctype_alnum($_GET['id'])) { - if (!isset($_SESSION['mailcow_cc_role'])) { - echo json_encode(array('error' => 'Access denied')); - exit(); - } - $tmpdir = '/tmp/' . $_GET['id'] . '/'; - $mailc = quarantine('details', $_GET['id']); - if ($mailc === false) { - echo json_encode(array('error' => 'Access denied')); - exit; - } - if (strlen($mailc['msg']) > 10485760) { - echo json_encode(array('error' => 'Message size exceeds 10 MiB.')); - exit; - } - if (!empty($mailc['msg'])) { - if (isset($_GET['quick_release'])) { - $hash = hash('sha256', $mailc['id'] . $mailc['qid']); - header('Location: /qhandler/release/' . $hash); - exit; - } - if (isset($_GET['quick_delete'])) { - $hash = hash('sha256', $mailc['id'] . $mailc['qid']); - header('Location: /qhandler/delete/' . $hash); - exit; - } - // Init message array - $data = array(); - // Init parser - $mail_parser = new PhpMimeMailParser\Parser(); - $html2text = new Html2Text\Html2Text(); - // Load msg to parser - $mail_parser->setText($mailc['msg']); - - // Get mail recipients - { - $recipientsList = array(); - addAddresses($recipientsList, $mail_parser, 'to'); - addAddresses($recipientsList, $mail_parser, 'cc'); - addAddresses($recipientsList, $mail_parser, 'bcc'); - $recipientsList[] = array('address' => $mailc['rcpt'], 'type' => 'smtp'); - $data['recipients'] = $recipientsList; - } - // Get from - $data['header_from'] = $mail_parser->getHeader('from'); - $data['env_from'] = $mailc['sender']; - // Get rspamd score - $data['score'] = $mailc['score']; - // Get rspamd action - $data['action'] = $mailc['action']; - // Get rspamd symbols - $data['symbols'] = json_decode($mailc['symbols']); - // Get fuzzy hashes - $data['fuzzy_hashes'] = json_decode($mailc['fuzzy_hashes']); - // Get text/plain content - $data['text_plain'] = $mail_parser->getMessageBody('text'); - if (!json_encode($data['text_plain'])) $data['text_plain'] = ''; - // Get html content and convert to text - $data['text_html'] = $html2text->convert($mail_parser->getMessageBody('html')); - if (empty($data['text_plain']) && empty($data['text_html'])) { - // Failed to parse content, try raw - $text = trim(substr($mailc['msg'], strpos($mailc['msg'], "\r\n\r\n") + 1)); - // Only return html->text - $data['text_plain'] = 'Parser failed, assuming HTML'; - $data['text_html'] = $html2text->convert($text); - } - (empty($data['text_plain'])) ? $data['text_plain'] = '-' : null; - // Get subject - $data['subject'] = $mail_parser->getHeader('subject'); - $data['subject'] = mb_convert_encoding($mail_parser->getHeader('subject'), "UTF-8", "auto"); - (empty($data['subject'])) ? $data['subject'] = '-' : null; - // Get attachments - if (is_dir($tmpdir)) { - rrmdir($tmpdir); - } - mkdir('/tmp/' . $_GET['id']); - $mail_parser->saveAttachments($tmpdir, true); - $atts = $mail_parser->getAttachments(true); - if (count($atts) > 0) { - foreach ($atts as $key => $val) { - $data['attachments'][$key] = array( - // Index - // 0 => file name - // 1 => mime type - // 2 => file size - // 3 => vt link by sha256 - $val->getFilename(), - $val->getContentType(), - filesize($tmpdir . $val->getFilename()), - 'https://www.virustotal.com/file/' . hash_file('SHA256', $tmpdir . $val->getFilename()) . '/analysis/' - ); - } - } - if (isset($_GET['eml'])) { - $dl_filename = filter_var($data['subject'], FILTER_SANITIZE_STRING); - $dl_filename = strlen($dl_filename) > 30 ? substr($dl_filename,0,30) : $dl_filename; - header('Pragma: public'); - header('Expires: 0'); - header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); - header('Cache-Control: private', false); - header('Content-Type: message/rfc822'); - header('Content-Disposition: attachment; filename="'. $dl_filename . '.eml";'); - header('Content-Transfer-Encoding: binary'); - header('Content-Length: ' . strlen($mailc['msg'])); - echo $mailc['msg']; - exit; - } - if (isset($_GET['att'])) { - if ($_SESSION['acl']['quarantine_attachments'] == 0) { - exit(json_encode('Forbidden')); - } - $dl_id = intval($_GET['att']); - $dl_filename = filter_var($data['attachments'][$dl_id][0], FILTER_SANITIZE_STRING); - $dl_filename_short = strlen($dl_filename) > 20 ? substr($dl_filename, 0, 20) : $dl_filename; - $dl_filename_extension = pathinfo($tmpdir . $dl_filename)['extension']; - $dl_filename_short = preg_replace('/\.' . $dl_filename_extension . '$/', '', $dl_filename_short); - if (!is_dir($tmpdir . $dl_filename) && file_exists($tmpdir . $dl_filename)) { - header('Pragma: public'); - header('Expires: 0'); - header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); - header('Cache-Control: private', false); - header('Content-Type: ' . $data['attachments'][$dl_id][1]); - header('Content-Disposition: attachment; filename="'. $dl_filename_short . '.' . $dl_filename_extension . '";'); - header('Content-Transfer-Encoding: binary'); - header('Content-Length: ' . $data['attachments'][$dl_id][2]); - readfile($tmpdir . $dl_filename); - exit; - } - } - echo json_encode($data); - } - -} -?> +getAddresses($headerName); + foreach ($addresses as $address) { + if (filter_var($address['address'], FILTER_VALIDATE_EMAIL)) { + $list[] = array('address' => $address['address'], 'type' => $headerName); + } + } +} + +if (!empty($_GET['hash']) && ctype_alnum($_GET['hash'])) { + $mailc = quarantine('hash_details', $_GET['hash']); + if ($mailc === false) { + echo json_encode(array('error' => 'Message invalid')); + exit; + } + if (strlen($mailc['msg']) > 10485760) { + echo json_encode(array('error' => 'Message size exceeds 10 MiB.')); + exit; + } + if (!empty($mailc['msg'])) { + // Init message array + $data = array(); + // Init parser + $mail_parser = new PhpMimeMailParser\Parser(); + $html2text = new Html2Text\Html2Text(); + // Load msg to parser + $mail_parser->setText($mailc['msg']); + // Get mail recipients + { + $recipientsList = array(); + addAddresses($recipientsList, $mail_parser, 'to'); + addAddresses($recipientsList, $mail_parser, 'cc'); + addAddresses($recipientsList, $mail_parser, 'bcc'); + $recipientsList[] = array('address' => $mailc['rcpt'], 'type' => 'smtp'); + $data['recipients'] = $recipientsList; + } + // Get from + $data['header_from'] = $mail_parser->getHeader('from'); + $data['env_from'] = $mailc['sender']; + // Get rspamd score + $data['score'] = $mailc['score']; + // Get rspamd action + $data['action'] = $mailc['action']; + // Get rspamd symbols + $data['symbols'] = json_decode($mailc['symbols']); + // Get fuzzy hashes + $data['fuzzy_hashes'] = json_decode($mailc['fuzzy_hashes']); + $data['subject'] = mb_convert_encoding($mail_parser->getHeader('subject'), "UTF-8", "auto"); + (empty($data['subject'])) ? $data['subject'] = '-' : null; + echo json_encode($data); + } +} +elseif (!empty($_GET['id']) && ctype_alnum($_GET['id'])) { + if (!isset($_SESSION['mailcow_cc_role'])) { + echo json_encode(array('error' => 'Access denied')); + exit(); + } + $tmpdir = '/tmp/' . $_GET['id'] . '/'; + $mailc = quarantine('details', $_GET['id']); + if ($mailc === false) { + echo json_encode(array('error' => 'Access denied')); + exit; + } + if (strlen($mailc['msg']) > 10485760) { + echo json_encode(array('error' => 'Message size exceeds 10 MiB.')); + exit; + } + if (!empty($mailc['msg'])) { + if (isset($_GET['quick_release'])) { + $hash = hash('sha256', $mailc['id'] . $mailc['qid']); + header('Location: /qhandler/release/' . $hash); + exit; + } + if (isset($_GET['quick_delete'])) { + $hash = hash('sha256', $mailc['id'] . $mailc['qid']); + header('Location: /qhandler/delete/' . $hash); + exit; + } + // Init message array + $data = array(); + // Init parser + $mail_parser = new PhpMimeMailParser\Parser(); + $html2text = new Html2Text\Html2Text(); + // Load msg to parser + $mail_parser->setText($mailc['msg']); + + // Get mail recipients + { + $recipientsList = array(); + addAddresses($recipientsList, $mail_parser, 'to'); + addAddresses($recipientsList, $mail_parser, 'cc'); + addAddresses($recipientsList, $mail_parser, 'bcc'); + $recipientsList[] = array('address' => $mailc['rcpt'], 'type' => 'smtp'); + $data['recipients'] = $recipientsList; + } + // Get from + $data['header_from'] = $mail_parser->getHeader('from'); + $data['env_from'] = $mailc['sender']; + // Get rspamd score + $data['score'] = $mailc['score']; + // Get rspamd action + $data['action'] = $mailc['action']; + // Get rspamd symbols + $data['symbols'] = json_decode($mailc['symbols']); + // Get fuzzy hashes + $data['fuzzy_hashes'] = json_decode($mailc['fuzzy_hashes']); + // Get text/plain content + $data['text_plain'] = $mail_parser->getMessageBody('text'); + if (!json_encode($data['text_plain'])) $data['text_plain'] = ''; + // Get html content and convert to text + $data['text_html'] = $html2text->convert($mail_parser->getMessageBody('html')); + if (empty($data['text_plain']) && empty($data['text_html'])) { + // Failed to parse content, try raw + $text = trim(substr($mailc['msg'], strpos($mailc['msg'], "\r\n\r\n") + 1)); + // Only return html->text + $data['text_plain'] = 'Parser failed, assuming HTML'; + $data['text_html'] = $html2text->convert($text); + } + (empty($data['text_plain'])) ? $data['text_plain'] = '-' : null; + // Get subject + $data['subject'] = $mail_parser->getHeader('subject'); + $data['subject'] = mb_convert_encoding($mail_parser->getHeader('subject'), "UTF-8", "auto"); + (empty($data['subject'])) ? $data['subject'] = '-' : null; + // Get attachments + if (is_dir($tmpdir)) { + rrmdir($tmpdir); + } + mkdir('/tmp/' . $_GET['id']); + $mail_parser->saveAttachments($tmpdir, true); + $atts = $mail_parser->getAttachments(true); + if (count($atts) > 0) { + foreach ($atts as $key => $val) { + $data['attachments'][$key] = array( + // Index + // 0 => file name + // 1 => mime type + // 2 => file size + // 3 => vt link by sha256 + $val->getFilename(), + $val->getContentType(), + filesize($tmpdir . $val->getFilename()), + 'https://www.virustotal.com/file/' . hash_file('SHA256', $tmpdir . $val->getFilename()) . '/analysis/' + ); + } + } + if (isset($_GET['eml'])) { + $dl_filename = filter_var($data['subject'], FILTER_SANITIZE_STRING); + $dl_filename = strlen($dl_filename) > 30 ? substr($dl_filename,0,30) : $dl_filename; + header('Pragma: public'); + header('Expires: 0'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header('Cache-Control: private', false); + header('Content-Type: message/rfc822'); + header('Content-Disposition: attachment; filename="'. $dl_filename . '.eml";'); + header('Content-Transfer-Encoding: binary'); + header('Content-Length: ' . strlen($mailc['msg'])); + echo $mailc['msg']; + exit; + } + if (isset($_GET['att'])) { + if ($_SESSION['acl']['quarantine_attachments'] == 0) { + exit(json_encode('Forbidden')); + } + $dl_id = intval($_GET['att']); + $dl_filename = filter_var($data['attachments'][$dl_id][0], FILTER_SANITIZE_STRING); + $dl_filename_short = strlen($dl_filename) > 20 ? substr($dl_filename, 0, 20) : $dl_filename; + $dl_filename_extension = pathinfo($tmpdir . $dl_filename)['extension']; + $dl_filename_short = preg_replace('/\.' . $dl_filename_extension . '$/', '', $dl_filename_short); + if (!is_dir($tmpdir . $dl_filename) && file_exists($tmpdir . $dl_filename)) { + header('Pragma: public'); + header('Expires: 0'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header('Cache-Control: private', false); + header('Content-Type: ' . $data['attachments'][$dl_id][1]); + header('Content-Disposition: attachment; filename="'. $dl_filename_short . '.' . $dl_filename_extension . '";'); + header('Content-Transfer-Encoding: binary'); + header('Content-Length: ' . $data['attachments'][$dl_id][2]); + readfile($tmpdir . $dl_filename); + exit; + } + } + echo json_encode($data); + } + +} +?> diff --git a/data/web/inc/ajax/qr_gen.php b/data/web/inc/ajax/qr_gen.php index 267d65ad..dc92a164 100644 --- a/data/web/inc/ajax/qr_gen.php +++ b/data/web/inc/ajax/qr_gen.php @@ -1,10 +1,10 @@ -getQRCodeImageAsDataUri($_SESSION['mailcow_cc_username'], $_GET['token']); -} -?> +getQRCodeImageAsDataUri($_SESSION['mailcow_cc_username'], $_GET['token']); +} +?> diff --git a/data/web/inc/ajax/show_rspamd_global_filters.php b/data/web/inc/ajax/show_rspamd_global_filters.php index 7c7affe8..c6bb47b5 100644 --- a/data/web/inc/ajax/show_rspamd_global_filters.php +++ b/data/web/inc/ajax/show_rspamd_global_filters.php @@ -1,3 +1,3 @@ - 'danger', 'msg' => $lang['danger']['script_empty'])); - exit(); - } - $sieve->parse($_GET['script']); - } - catch (Exception $e) { - echo json_encode(array('type' => 'danger', 'msg' => $e->getMessage())); - exit(); - } - echo json_encode(array('type' => 'success', 'msg' => $lang['add']['validation_success'])); -} -?> + 'danger', 'msg' => $lang['danger']['script_empty'])); + exit(); + } + $sieve->parse($_GET['script']); + } + catch (Exception $e) { + echo json_encode(array('type' => 'danger', 'msg' => $e->getMessage())); + exit(); + } + echo json_encode(array('type' => 'success', 'msg' => $lang['add']['validation_success'])); +} +?> diff --git a/data/web/inc/ajax/syncjob_logs.php b/data/web/inc/ajax/syncjob_logs.php index 9c521aec..a16a0d67 100644 --- a/data/web/inc/ajax/syncjob_logs.php +++ b/data/web/inc/ajax/syncjob_logs.php @@ -1,14 +1,14 @@ - + diff --git a/data/web/inc/ajax/transport_check.php b/data/web/inc/ajax/transport_check.php index aa429edf..48846835 100644 --- a/data/web/inc/ajax/transport_check.php +++ b/data/web/inc/ajax/transport_check.php @@ -1,149 +1,149 @@ -'; - } - else { - echo 'No MX records for ' . $hostname . ' were found in DNS, skipping and using hostname as next-hop.
'; - } - } - // Use port 25 if no port was given - $port = (empty($port)) ? 25 : $port; - $username = $transport_details['username']; - $password = $transport_details['password']; - - $mail = new PHPMailer; - $mail->Timeout = 15; - $mail->SMTPOptions = array( - 'ssl' => array( - 'verify_peer' => false, - 'verify_peer_name' => false, - 'allow_self_signed' => true - ) - ); - $mail->SMTPDebug = 3; - // smtp: and smtp_enforced_tls: do not support wrapped tls, todo? - // change postfix map to detect wrapped tls or add a checkbox to toggle wrapped tls - // if ($port == 465) { - // $mail->SMTPSecure = "ssl"; - // } - $mail->Debugoutput = function($str, $level) { - foreach(preg_split("/((\r?\n)|(\r\n?)|\n)/", $str) as $line){ - if (empty($line)) { continue; } - if (preg_match("/SERVER \-\> CLIENT: 2\d\d.+/i", $line)) { - echo '' . htmlspecialchars($line) . '
'; - } - elseif (preg_match("/SERVER \-\> CLIENT: 3\d\d.+/i", $line)) { - echo '' . htmlspecialchars($line) . '
'; - } - elseif (preg_match("/SERVER \-\> CLIENT: 4\d\d.+/i", $line)) { - echo '' . htmlspecialchars($line) . '
'; - } - elseif (preg_match("/SERVER \-\> CLIENT: 5\d\d.+/i", $line)) { - echo '' . htmlspecialchars($line) . '
'; - } - elseif (preg_match("/CLIENT \-\> SERVER:.+/i", $line)) { - echo '' . htmlspecialchars($line) . '
'; - } - elseif (preg_match("/^(?!SERVER|CLIENT|Connection:|\)).+$/i", $line)) { - echo '    ↪ ' . htmlspecialchars($line) . '
'; - } - else { - echo htmlspecialchars($line) . '
'; - } - } - }; - $mail->isSMTP(); - $mail->Host = $hostname; - if (!empty($username)) { - $mail->SMTPAuth = true; - $mail->Username = $username; - $mail->Password = $password; - } - $mail->Port = $port; - $mail->setFrom($mail_from, 'Mailer'); - $mail->Subject = 'A subject for a SMTP test'; - $mail->addAddress($mail_rcpt, 'Joe Null'); - $mail->Body = 'This is our test body'; - $mail->send(); - } - else { - echo "Unknown transport."; - } -} -else { - echo "Permission denied."; -} +'; + } + else { + echo 'No MX records for ' . $hostname . ' were found in DNS, skipping and using hostname as next-hop.
'; + } + } + // Use port 25 if no port was given + $port = (empty($port)) ? 25 : $port; + $username = $transport_details['username']; + $password = $transport_details['password']; + + $mail = new PHPMailer; + $mail->Timeout = 15; + $mail->SMTPOptions = array( + 'ssl' => array( + 'verify_peer' => false, + 'verify_peer_name' => false, + 'allow_self_signed' => true + ) + ); + $mail->SMTPDebug = 3; + // smtp: and smtp_enforced_tls: do not support wrapped tls, todo? + // change postfix map to detect wrapped tls or add a checkbox to toggle wrapped tls + // if ($port == 465) { + // $mail->SMTPSecure = "ssl"; + // } + $mail->Debugoutput = function($str, $level) { + foreach(preg_split("/((\r?\n)|(\r\n?)|\n)/", $str) as $line){ + if (empty($line)) { continue; } + if (preg_match("/SERVER \-\> CLIENT: 2\d\d.+/i", $line)) { + echo '' . htmlspecialchars($line) . '
'; + } + elseif (preg_match("/SERVER \-\> CLIENT: 3\d\d.+/i", $line)) { + echo '' . htmlspecialchars($line) . '
'; + } + elseif (preg_match("/SERVER \-\> CLIENT: 4\d\d.+/i", $line)) { + echo '' . htmlspecialchars($line) . '
'; + } + elseif (preg_match("/SERVER \-\> CLIENT: 5\d\d.+/i", $line)) { + echo '' . htmlspecialchars($line) . '
'; + } + elseif (preg_match("/CLIENT \-\> SERVER:.+/i", $line)) { + echo '' . htmlspecialchars($line) . '
'; + } + elseif (preg_match("/^(?!SERVER|CLIENT|Connection:|\)).+$/i", $line)) { + echo '    ↪ ' . htmlspecialchars($line) . '
'; + } + else { + echo htmlspecialchars($line) . '
'; + } + } + }; + $mail->isSMTP(); + $mail->Host = $hostname; + if (!empty($username)) { + $mail->SMTPAuth = true; + $mail->Username = $username; + $mail->Password = $password; + } + $mail->Port = $port; + $mail->setFrom($mail_from, 'Mailer'); + $mail->Subject = 'A subject for a SMTP test'; + $mail->addAddress($mail_rcpt, 'Joe Null'); + $mail->Body = 'This is our test body'; + $mail->send(); + } + else { + echo "Unknown transport."; + } +} +else { + echo "Permission denied."; +} diff --git a/data/web/inc/functions.acl.inc.php b/data/web/inc/functions.acl.inc.php index ffce9f44..75e9dfe3 100644 --- a/data/web/inc/functions.acl.inc.php +++ b/data/web/inc/functions.acl.inc.php @@ -1,226 +1,226 @@ - $acl_val) { - $acl_post[$acl_val] = 1; - } - // Users cannot change their own ACL - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username) - || ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin')) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'access_denied' - ); - continue; - } - // Read all available acl options by calling acl(get) - // Set all available acl options we cannot find in the post data to 0, else 1 - $is_now = acl('get', 'user', $username); - if (!empty($is_now)) { - foreach ($is_now as $acl_now_name => $acl_now_val) { - $set_acls[$acl_now_name] = (isset($acl_post[$acl_now_name])) ? 1 : 0; - } - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'Cannot determine current ACL' - ); - continue; - } - foreach ($set_acls as $set_acl_key => $set_acl_val) { - $stmt = $pdo->prepare("UPDATE `user_acl` SET " . $set_acl_key . " = " . intval($set_acl_val) . " - WHERE `username` = :username"); - $stmt->execute(array( - ':username' => $username, - )); - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => array('acl_saved', $username) - ); - } - break; - case 'domainadmin': - if ($_SESSION['mailcow_cc_role'] != 'admin') { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - if (!is_array($_data['username'])) { - $usernames = array(); - $usernames[] = $_data['username']; - } - else { - $usernames = $_data['username']; - } - foreach ($usernames as $username) { - // Cast to array for single selections - $acls = (array)$_data['da_acl']; - // Create associative array from index array - // All set items are given 1 as value - foreach ($acls as $acl_key => $acl_val) { - $acl_post[$acl_val] = 1; - } - // Users cannot change their own ACL - if ($_SESSION['mailcow_cc_role'] != 'admin') { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'access_denied' - ); - continue; - } - // Read all available acl options by calling acl(get) - // Set all available acl options we cannot find in the post data to 0, else 1 - $is_now = acl('get', 'domainadmin', $username); - if (!empty($is_now)) { - foreach ($is_now as $acl_now_name => $acl_now_val) { - $set_acls[$acl_now_name] = (isset($acl_post[$acl_now_name])) ? 1 : 0; - } - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'Cannot determine current ACL' - ); - continue; - } - foreach ($set_acls as $set_acl_key => $set_acl_val) { - $stmt = $pdo->prepare("UPDATE `da_acl` SET " . $set_acl_key . " = " . intval($set_acl_val) . " - WHERE `username` = :username"); - $stmt->execute(array( - ':username' => $username, - )); - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => array('acl_saved', $username) - ); - } - break; - } - break; - case 'get': - switch ($_scope) { - case 'user': - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - return false; - } - $stmt = $pdo->prepare("SELECT * FROM `user_acl` WHERE `username` = :username"); - $stmt->execute(array(':username' => $_data)); - $data = $stmt->fetch(PDO::FETCH_ASSOC); - if ($_SESSION['mailcow_cc_role'] == 'domainadmin') { - // Domain admins cannot see, add or remove user ACLs they don't have access to by themselves - // Editing a user will use acl("get", "user") to determine granted ACLs and therefore block unallowed access escalation via form editing - $self_da_acl = acl('get', 'domainadmin', $_SESSION['mailcow_cc_username']); - foreach ($self_da_acl as $self_da_acl_key => $self_da_acl_val) { - if ($self_da_acl_val == 0) { - unset($data[$self_da_acl_key]); - } - } - } - if (!empty($data)) { - unset($data['username']); - return $data; - } - else { - return false; - } - break; - case 'domainadmin': - if ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin') { - return false; - } - if ($_SESSION['mailcow_cc_role'] == 'domainadmin' && $_SESSION['mailcow_cc_username'] != $_data) { - return false; - } - $stmt = $pdo->prepare("SELECT * FROM `da_acl` WHERE `username` = :username"); - $stmt->execute(array(':username' => $_data)); - $data = $stmt->fetch(PDO::FETCH_ASSOC); - if (!empty($data)) { - unset($data['username']); - return $data; - } - else { - return false; - } - break; - } - break; - case 'to_session': - if (!isset($_SESSION['mailcow_cc_role'])) { - return false; - } - unset($_SESSION['acl']); - $username = strtolower(trim($_SESSION['mailcow_cc_username'])); - // Admins get access to all modules - if ($_SESSION['mailcow_cc_role'] == 'admin' || - (isset($_SESSION["dual-login"]["role"]) && $_SESSION["dual-login"]["role"] == 'admin')) { - $stmt = $pdo->query("SHOW COLUMNS FROM `user_acl` WHERE `Field` != 'username';"); - $acl_all = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($row = array_shift($acl_all)) { - $acl['acl'][$row['Field']] = 1; - } - $stmt = $pdo->query("SHOW COLUMNS FROM `da_acl` WHERE `Field` != 'username';"); - $acl_all = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($row = array_shift($acl_all)) { - $acl['acl'][$row['Field']] = 1; - } - } - elseif ($_SESSION['mailcow_cc_role'] == 'domainadmin' || - (isset($_SESSION["dual-login"]["role"]) && $_SESSION["dual-login"]["role"] == 'domainadmin')) { - // Read all exting user_acl modules and set to 1 - $stmt = $pdo->query("SHOW COLUMNS FROM `user_acl` WHERE `Field` != 'username';"); - $acl_all = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($row = array_shift($acl_all)) { - $acl['acl'][$row['Field']] = 1; - } - // Read da_acl rules for current user, OVERWRITE overlapping modules - // This prevents access to a users sync jobs, when a domain admins was not given access to sync jobs - $stmt = $pdo->prepare("SELECT * FROM `da_acl` WHERE `username` = :username"); - $stmt->execute(array(':username' => (isset($_SESSION["dual-login"]["username"])) ? $_SESSION["dual-login"]["username"] : $username)); - $acl_user = $stmt->fetch(PDO::FETCH_ASSOC); - foreach ($acl_user as $acl_user_key => $acl_user_val) { - $acl['acl'][$acl_user_key] = $acl_user_val; - } - unset($acl['acl']['username']); - } - elseif ($_SESSION['mailcow_cc_role'] == 'user') { - $stmt = $pdo->prepare("SELECT * FROM `user_acl` WHERE `username` = :username"); - $stmt->execute(array(':username' => $username)); - $acl['acl'] = $stmt->fetch(PDO::FETCH_ASSOC); - unset($acl['acl']['username']); - } - if (!empty($acl)) { - $_SESSION = array_merge($_SESSION, $acl); - } - break; - } + $acl_val) { + $acl_post[$acl_val] = 1; + } + // Users cannot change their own ACL + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username) + || ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin')) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'access_denied' + ); + continue; + } + // Read all available acl options by calling acl(get) + // Set all available acl options we cannot find in the post data to 0, else 1 + $is_now = acl('get', 'user', $username); + if (!empty($is_now)) { + foreach ($is_now as $acl_now_name => $acl_now_val) { + $set_acls[$acl_now_name] = (isset($acl_post[$acl_now_name])) ? 1 : 0; + } + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'Cannot determine current ACL' + ); + continue; + } + foreach ($set_acls as $set_acl_key => $set_acl_val) { + $stmt = $pdo->prepare("UPDATE `user_acl` SET " . $set_acl_key . " = " . intval($set_acl_val) . " + WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('acl_saved', $username) + ); + } + break; + case 'domainadmin': + if ($_SESSION['mailcow_cc_role'] != 'admin') { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + if (!is_array($_data['username'])) { + $usernames = array(); + $usernames[] = $_data['username']; + } + else { + $usernames = $_data['username']; + } + foreach ($usernames as $username) { + // Cast to array for single selections + $acls = (array)$_data['da_acl']; + // Create associative array from index array + // All set items are given 1 as value + foreach ($acls as $acl_key => $acl_val) { + $acl_post[$acl_val] = 1; + } + // Users cannot change their own ACL + if ($_SESSION['mailcow_cc_role'] != 'admin') { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'access_denied' + ); + continue; + } + // Read all available acl options by calling acl(get) + // Set all available acl options we cannot find in the post data to 0, else 1 + $is_now = acl('get', 'domainadmin', $username); + if (!empty($is_now)) { + foreach ($is_now as $acl_now_name => $acl_now_val) { + $set_acls[$acl_now_name] = (isset($acl_post[$acl_now_name])) ? 1 : 0; + } + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'Cannot determine current ACL' + ); + continue; + } + foreach ($set_acls as $set_acl_key => $set_acl_val) { + $stmt = $pdo->prepare("UPDATE `da_acl` SET " . $set_acl_key . " = " . intval($set_acl_val) . " + WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('acl_saved', $username) + ); + } + break; + } + break; + case 'get': + switch ($_scope) { + case 'user': + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + return false; + } + $stmt = $pdo->prepare("SELECT * FROM `user_acl` WHERE `username` = :username"); + $stmt->execute(array(':username' => $_data)); + $data = $stmt->fetch(PDO::FETCH_ASSOC); + if ($_SESSION['mailcow_cc_role'] == 'domainadmin') { + // Domain admins cannot see, add or remove user ACLs they don't have access to by themselves + // Editing a user will use acl("get", "user") to determine granted ACLs and therefore block unallowed access escalation via form editing + $self_da_acl = acl('get', 'domainadmin', $_SESSION['mailcow_cc_username']); + foreach ($self_da_acl as $self_da_acl_key => $self_da_acl_val) { + if ($self_da_acl_val == 0) { + unset($data[$self_da_acl_key]); + } + } + } + if (!empty($data)) { + unset($data['username']); + return $data; + } + else { + return false; + } + break; + case 'domainadmin': + if ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin') { + return false; + } + if ($_SESSION['mailcow_cc_role'] == 'domainadmin' && $_SESSION['mailcow_cc_username'] != $_data) { + return false; + } + $stmt = $pdo->prepare("SELECT * FROM `da_acl` WHERE `username` = :username"); + $stmt->execute(array(':username' => $_data)); + $data = $stmt->fetch(PDO::FETCH_ASSOC); + if (!empty($data)) { + unset($data['username']); + return $data; + } + else { + return false; + } + break; + } + break; + case 'to_session': + if (!isset($_SESSION['mailcow_cc_role'])) { + return false; + } + unset($_SESSION['acl']); + $username = strtolower(trim($_SESSION['mailcow_cc_username'])); + // Admins get access to all modules + if ($_SESSION['mailcow_cc_role'] == 'admin' || + (isset($_SESSION["dual-login"]["role"]) && $_SESSION["dual-login"]["role"] == 'admin')) { + $stmt = $pdo->query("SHOW COLUMNS FROM `user_acl` WHERE `Field` != 'username';"); + $acl_all = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($acl_all)) { + $acl['acl'][$row['Field']] = 1; + } + $stmt = $pdo->query("SHOW COLUMNS FROM `da_acl` WHERE `Field` != 'username';"); + $acl_all = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($acl_all)) { + $acl['acl'][$row['Field']] = 1; + } + } + elseif ($_SESSION['mailcow_cc_role'] == 'domainadmin' || + (isset($_SESSION["dual-login"]["role"]) && $_SESSION["dual-login"]["role"] == 'domainadmin')) { + // Read all exting user_acl modules and set to 1 + $stmt = $pdo->query("SHOW COLUMNS FROM `user_acl` WHERE `Field` != 'username';"); + $acl_all = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($acl_all)) { + $acl['acl'][$row['Field']] = 1; + } + // Read da_acl rules for current user, OVERWRITE overlapping modules + // This prevents access to a users sync jobs, when a domain admins was not given access to sync jobs + $stmt = $pdo->prepare("SELECT * FROM `da_acl` WHERE `username` = :username"); + $stmt->execute(array(':username' => (isset($_SESSION["dual-login"]["username"])) ? $_SESSION["dual-login"]["username"] : $username)); + $acl_user = $stmt->fetch(PDO::FETCH_ASSOC); + foreach ($acl_user as $acl_user_key => $acl_user_val) { + $acl['acl'][$acl_user_key] = $acl_user_val; + } + unset($acl['acl']['username']); + } + elseif ($_SESSION['mailcow_cc_role'] == 'user') { + $stmt = $pdo->prepare("SELECT * FROM `user_acl` WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $acl['acl'] = $stmt->fetch(PDO::FETCH_ASSOC); + unset($acl['acl']['username']); + } + if (!empty($acl)) { + $_SESSION = array_merge($_SESSION, $acl); + } + break; + } } \ No newline at end of file diff --git a/data/web/inc/functions.address_rewriting.inc.php b/data/web/inc/functions.address_rewriting.inc.php index 8193c052..13a734d3 100644 --- a/data/web/inc/functions.address_rewriting.inc.php +++ b/data/web/inc/functions.address_rewriting.inc.php @@ -1,436 +1,436 @@ - 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - $local_dest = strtolower(trim($_data['local_dest'])); - $bcc_dest = $_data['bcc_dest']; - $active = intval($_data['active']); - $type = $_data['type']; - if ($type != 'sender' && $type != 'rcpt') { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => 'invalid_bcc_map_type' - ); - return false; - } - if (empty($bcc_dest)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => 'bcc_empty' - ); - return false; - } - if (is_valid_domain_name($local_dest)) { - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $local_dest)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - $domain = idn_to_ascii($local_dest, 0, INTL_IDNA_VARIANT_UTS46); - $local_dest_sane = '@' . idn_to_ascii($local_dest, 0, INTL_IDNA_VARIANT_UTS46); - } - elseif (filter_var($local_dest, FILTER_VALIDATE_EMAIL)) { - $mailbox = mailbox('get', 'mailbox_details', $local_dest); - if ($mailbox === false && array_key_exists($local_dest, array_merge($direct_aliases, $shared_aliases)) === false) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $local_dest) && - !hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $local_dest)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - $domain = idn_to_ascii(substr(strstr($local_dest, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46); - $local_dest_sane = $local_dest; - } - else { - return false; - } - if (!filter_var($bcc_dest, FILTER_VALIDATE_EMAIL)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('bcc_must_be_email', htmlspecialchars($bcc_dest)) - ); - return false; - } - - $stmt = $pdo->prepare("SELECT `id` FROM `bcc_maps` - WHERE `local_dest` = :local_dest AND `type` = :type"); - $stmt->execute(array(':local_dest' => $local_dest_sane, ':type' => $type)); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - - if ($num_results != 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('bcc_exists', htmlspecialchars($local_dest_sane), $type) - ); - return false; - } - $stmt = $pdo->prepare("INSERT INTO `bcc_maps` (`local_dest`, `bcc_dest`, `domain`, `active`, `type`) VALUES - (:local_dest, :bcc_dest, :domain, :active, :type)"); - $stmt->execute(array( - ':local_dest' => $local_dest_sane, - ':bcc_dest' => $bcc_dest, - ':domain' => $domain, - ':active' => $active, - ':type' => $type - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => 'bcc_saved' - ); - break; - case 'edit': - if (!isset($_SESSION['acl']['bcc_maps']) || $_SESSION['acl']['bcc_maps'] != "1" ) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - $ids = (array)$_data['id']; - foreach ($ids as $id) { - $is_now = bcc('details', $id); - if (!empty($is_now)) { - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; - $bcc_dest = (!empty($_data['bcc_dest'])) ? $_data['bcc_dest'] : $is_now['bcc_dest']; - $local_dest = $is_now['local_dest']; - $type = (!empty($_data['type'])) ? $_data['type'] : $is_now['type']; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - if (!filter_var($bcc_dest, FILTER_VALIDATE_EMAIL)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('bcc_must_be_email', $bcc_dest) - ); - continue; - } - if (empty($bcc_dest)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('bcc_must_be_email', $bcc_dest) - ); - continue; - } - $stmt = $pdo->prepare("SELECT `id` FROM `bcc_maps` - WHERE `local_dest` = :local_dest AND `type` = :type"); - $stmt->execute(array(':local_dest' => $local_dest, ':type' => $type)); - $id_now = $stmt->fetch(PDO::FETCH_ASSOC)['id']; - - if (isset($id_now) && $id_now != $id) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('bcc_exists', htmlspecialchars($local_dest), $type) - ); - continue; - } - - $stmt = $pdo->prepare("UPDATE `bcc_maps` SET `bcc_dest` = :bcc_dest, `active` = :active, `type` = :type WHERE `id`= :id"); - $stmt->execute(array( - ':bcc_dest' => $bcc_dest, - ':active' => $active, - ':type' => $type, - ':id' => $id - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('bcc_edited', $bcc_dest) - ); - } - break; - case 'details': - $bccdata = array(); - $id = intval($_data); - - $stmt = $pdo->prepare("SELECT `id`, - `local_dest`, - `bcc_dest`, - `active`, - `type`, - `created`, - `domain`, - `modified` FROM `bcc_maps` - WHERE `id` = :id"); - $stmt->execute(array(':id' => $id)); - $bccdata = $stmt->fetch(PDO::FETCH_ASSOC); - - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $bccdata['domain'])) { - $bccdata = null; - return false; - } - return $bccdata; - break; - case 'get': - $bccdata = array(); - $all_items = array(); - $id = intval($_data); - - $stmt = $pdo->query("SELECT `id`, `domain` FROM `bcc_maps`"); - $all_items = $stmt->fetchAll(PDO::FETCH_ASSOC); - - foreach ($all_items as $i) { - if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $i['domain'])) { - $bccdata[] = $i['id']; - } - } - $all_items = null; - return $bccdata; - break; - case 'delete': - if (!isset($_SESSION['acl']['bcc_maps']) || $_SESSION['acl']['bcc_maps'] != "1" ) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - $ids = (array)$_data['id']; - foreach ($ids as $id) { - if (!is_numeric($id)) { - return false; - } - $stmt = $pdo->prepare("SELECT `domain` FROM `bcc_maps` WHERE id = :id"); - $stmt->execute(array(':id' => $id)); - $domain = $stmt->fetch(PDO::FETCH_ASSOC)['domain']; - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - $stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `id`= :id"); - $stmt->execute(array(':id' => $id)); - - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('bcc_deleted', $id) - ); - } - break; - } -} - -function recipient_map($_action, $_data = null, $attr = null) { - global $pdo; - global $lang; - if ($_SESSION['mailcow_cc_role'] != "admin") { - return false; - } - switch ($_action) { - case 'add': - $old_dest = strtolower(trim($_data['recipient_map_old'])); - if (substr($old_dest, 0, 1) == '@') { - $old_dest = substr($old_dest, 1); - } - $new_dest = strtolower(trim($_data['recipient_map_new'])); - $active = intval($_data['active']); - if (is_valid_domain_name($old_dest)) { - $old_dest_sane = '@' . idn_to_ascii($old_dest, 0, INTL_IDNA_VARIANT_UTS46); - } - elseif (filter_var($old_dest, FILTER_VALIDATE_EMAIL)) { - $old_dest_sane = $old_dest; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('invalid_recipient_map_old', htmlspecialchars($old_dest)) - ); - return false; - } - if (!filter_var($new_dest, FILTER_VALIDATE_EMAIL)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('invalid_recipient_map_new', htmlspecialchars($new_dest)) - ); - return false; - } - $rmaps = recipient_map('get'); - foreach ($rmaps as $rmap) { - if (recipient_map('details', $rmap)['recipient_map_old'] == $old_dest_sane) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('recipient_map_entry_exists', htmlspecialchars($old_dest_sane)) - ); - return false; - } - } - $stmt = $pdo->prepare("INSERT INTO `recipient_maps` (`old_dest`, `new_dest`, `active`) VALUES - (:old_dest, :new_dest, :active)"); - $stmt->execute(array( - ':old_dest' => $old_dest_sane, - ':new_dest' => $new_dest, - ':active' => $active - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('recipient_map_entry_saved', htmlspecialchars($old_dest_sane)) - ); - break; - case 'edit': - $ids = (array)$_data['id']; - foreach ($ids as $id) { - $is_now = recipient_map('details', $id); - if (!empty($is_now)) { - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; - $new_dest = (!empty($_data['recipient_map_new'])) ? $_data['recipient_map_new'] : $is_now['recipient_map_new']; - $old_dest = (!empty($_data['recipient_map_old'])) ? $_data['recipient_map_old'] : $is_now['recipient_map_old']; - if (substr($old_dest, 0, 1) == '@') { - $old_dest = substr($old_dest, 1); - } - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - if (is_valid_domain_name($old_dest)) { - $old_dest_sane = '@' . idn_to_ascii($old_dest, 0, INTL_IDNA_VARIANT_UTS46); - } - elseif (filter_var($old_dest, FILTER_VALIDATE_EMAIL)) { - $old_dest_sane = $old_dest; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('invalid_recipient_map_old', htmlspecialchars($old_dest)) - ); - continue; - } - if (!filter_var($new_dest, FILTER_VALIDATE_EMAIL)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('invalid_recipient_map_new', htmlspecialchars($new_dest)) - ); - continue; - } - $rmaps = recipient_map('get'); - foreach ($rmaps as $rmap) { - if ($rmap == $id) { continue; } - if (recipient_map('details', $rmap)['recipient_map_old'] == $old_dest_sane) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('recipient_map_entry_exists', htmlspecialchars($old_dest_sane)) - ); - return false; - } - } - $stmt = $pdo->prepare("UPDATE `recipient_maps` SET - `old_dest` = :old_dest, - `new_dest` = :new_dest, - `active` = :active - WHERE `id`= :id"); - $stmt->execute(array( - ':old_dest' => $old_dest_sane, - ':new_dest' => $new_dest, - ':active' => $active, - ':id' => $id - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('recipient_map_entry_saved', htmlspecialchars($old_dest_sane)) - ); - } - break; - case 'details': - $mapdata = array(); - $id = intval($_data); - - $stmt = $pdo->prepare("SELECT `id`, - `old_dest` AS `recipient_map_old`, - `new_dest` AS `recipient_map_new`, - `active`, - `created`, - `modified` FROM `recipient_maps` - WHERE `id` = :id"); - $stmt->execute(array(':id' => $id)); - $mapdata = $stmt->fetch(PDO::FETCH_ASSOC); - - return $mapdata; - break; - case 'get': - $mapdata = array(); - $all_items = array(); - $id = intval($_data); - - $stmt = $pdo->query("SELECT `id` FROM `recipient_maps`"); - $all_items = $stmt->fetchAll(PDO::FETCH_ASSOC); - - foreach ($all_items as $i) { - $mapdata[] = $i['id']; - } - $all_items = null; - return $mapdata; - break; - case 'delete': - $ids = (array)$_data['id']; - foreach ($ids as $id) { - if (!is_numeric($id)) { - return false; - } - $stmt = $pdo->prepare("DELETE FROM `recipient_maps` WHERE `id`= :id"); - $stmt->execute(array(':id' => $id)); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('recipient_map_entry_deleted', htmlspecialchars($id)) - ); - } - break; - } -} + 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + $local_dest = strtolower(trim($_data['local_dest'])); + $bcc_dest = $_data['bcc_dest']; + $active = intval($_data['active']); + $type = $_data['type']; + if ($type != 'sender' && $type != 'rcpt') { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => 'invalid_bcc_map_type' + ); + return false; + } + if (empty($bcc_dest)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => 'bcc_empty' + ); + return false; + } + if (is_valid_domain_name($local_dest)) { + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $local_dest)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + $domain = idn_to_ascii($local_dest, 0, INTL_IDNA_VARIANT_UTS46); + $local_dest_sane = '@' . idn_to_ascii($local_dest, 0, INTL_IDNA_VARIANT_UTS46); + } + elseif (filter_var($local_dest, FILTER_VALIDATE_EMAIL)) { + $mailbox = mailbox('get', 'mailbox_details', $local_dest); + if ($mailbox === false && array_key_exists($local_dest, array_merge($direct_aliases, $shared_aliases)) === false) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $local_dest) && + !hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $local_dest)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + $domain = idn_to_ascii(substr(strstr($local_dest, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46); + $local_dest_sane = $local_dest; + } + else { + return false; + } + if (!filter_var($bcc_dest, FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('bcc_must_be_email', htmlspecialchars($bcc_dest)) + ); + return false; + } + + $stmt = $pdo->prepare("SELECT `id` FROM `bcc_maps` + WHERE `local_dest` = :local_dest AND `type` = :type"); + $stmt->execute(array(':local_dest' => $local_dest_sane, ':type' => $type)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + + if ($num_results != 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('bcc_exists', htmlspecialchars($local_dest_sane), $type) + ); + return false; + } + $stmt = $pdo->prepare("INSERT INTO `bcc_maps` (`local_dest`, `bcc_dest`, `domain`, `active`, `type`) VALUES + (:local_dest, :bcc_dest, :domain, :active, :type)"); + $stmt->execute(array( + ':local_dest' => $local_dest_sane, + ':bcc_dest' => $bcc_dest, + ':domain' => $domain, + ':active' => $active, + ':type' => $type + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => 'bcc_saved' + ); + break; + case 'edit': + if (!isset($_SESSION['acl']['bcc_maps']) || $_SESSION['acl']['bcc_maps'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + $ids = (array)$_data['id']; + foreach ($ids as $id) { + $is_now = bcc('details', $id); + if (!empty($is_now)) { + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; + $bcc_dest = (!empty($_data['bcc_dest'])) ? $_data['bcc_dest'] : $is_now['bcc_dest']; + $local_dest = $is_now['local_dest']; + $type = (!empty($_data['type'])) ? $_data['type'] : $is_now['type']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + if (!filter_var($bcc_dest, FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('bcc_must_be_email', $bcc_dest) + ); + continue; + } + if (empty($bcc_dest)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('bcc_must_be_email', $bcc_dest) + ); + continue; + } + $stmt = $pdo->prepare("SELECT `id` FROM `bcc_maps` + WHERE `local_dest` = :local_dest AND `type` = :type"); + $stmt->execute(array(':local_dest' => $local_dest, ':type' => $type)); + $id_now = $stmt->fetch(PDO::FETCH_ASSOC)['id']; + + if (isset($id_now) && $id_now != $id) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('bcc_exists', htmlspecialchars($local_dest), $type) + ); + continue; + } + + $stmt = $pdo->prepare("UPDATE `bcc_maps` SET `bcc_dest` = :bcc_dest, `active` = :active, `type` = :type WHERE `id`= :id"); + $stmt->execute(array( + ':bcc_dest' => $bcc_dest, + ':active' => $active, + ':type' => $type, + ':id' => $id + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('bcc_edited', $bcc_dest) + ); + } + break; + case 'details': + $bccdata = array(); + $id = intval($_data); + + $stmt = $pdo->prepare("SELECT `id`, + `local_dest`, + `bcc_dest`, + `active`, + `type`, + `created`, + `domain`, + `modified` FROM `bcc_maps` + WHERE `id` = :id"); + $stmt->execute(array(':id' => $id)); + $bccdata = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $bccdata['domain'])) { + $bccdata = null; + return false; + } + return $bccdata; + break; + case 'get': + $bccdata = array(); + $all_items = array(); + $id = intval($_data); + + $stmt = $pdo->query("SELECT `id`, `domain` FROM `bcc_maps`"); + $all_items = $stmt->fetchAll(PDO::FETCH_ASSOC); + + foreach ($all_items as $i) { + if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $i['domain'])) { + $bccdata[] = $i['id']; + } + } + $all_items = null; + return $bccdata; + break; + case 'delete': + if (!isset($_SESSION['acl']['bcc_maps']) || $_SESSION['acl']['bcc_maps'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + $ids = (array)$_data['id']; + foreach ($ids as $id) { + if (!is_numeric($id)) { + return false; + } + $stmt = $pdo->prepare("SELECT `domain` FROM `bcc_maps` WHERE id = :id"); + $stmt->execute(array(':id' => $id)); + $domain = $stmt->fetch(PDO::FETCH_ASSOC)['domain']; + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `id`= :id"); + $stmt->execute(array(':id' => $id)); + + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('bcc_deleted', $id) + ); + } + break; + } +} + +function recipient_map($_action, $_data = null, $attr = null) { + global $pdo; + global $lang; + if ($_SESSION['mailcow_cc_role'] != "admin") { + return false; + } + switch ($_action) { + case 'add': + $old_dest = strtolower(trim($_data['recipient_map_old'])); + if (substr($old_dest, 0, 1) == '@') { + $old_dest = substr($old_dest, 1); + } + $new_dest = strtolower(trim($_data['recipient_map_new'])); + $active = intval($_data['active']); + if (is_valid_domain_name($old_dest)) { + $old_dest_sane = '@' . idn_to_ascii($old_dest, 0, INTL_IDNA_VARIANT_UTS46); + } + elseif (filter_var($old_dest, FILTER_VALIDATE_EMAIL)) { + $old_dest_sane = $old_dest; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('invalid_recipient_map_old', htmlspecialchars($old_dest)) + ); + return false; + } + if (!filter_var($new_dest, FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('invalid_recipient_map_new', htmlspecialchars($new_dest)) + ); + return false; + } + $rmaps = recipient_map('get'); + foreach ($rmaps as $rmap) { + if (recipient_map('details', $rmap)['recipient_map_old'] == $old_dest_sane) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('recipient_map_entry_exists', htmlspecialchars($old_dest_sane)) + ); + return false; + } + } + $stmt = $pdo->prepare("INSERT INTO `recipient_maps` (`old_dest`, `new_dest`, `active`) VALUES + (:old_dest, :new_dest, :active)"); + $stmt->execute(array( + ':old_dest' => $old_dest_sane, + ':new_dest' => $new_dest, + ':active' => $active + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('recipient_map_entry_saved', htmlspecialchars($old_dest_sane)) + ); + break; + case 'edit': + $ids = (array)$_data['id']; + foreach ($ids as $id) { + $is_now = recipient_map('details', $id); + if (!empty($is_now)) { + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; + $new_dest = (!empty($_data['recipient_map_new'])) ? $_data['recipient_map_new'] : $is_now['recipient_map_new']; + $old_dest = (!empty($_data['recipient_map_old'])) ? $_data['recipient_map_old'] : $is_now['recipient_map_old']; + if (substr($old_dest, 0, 1) == '@') { + $old_dest = substr($old_dest, 1); + } + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + if (is_valid_domain_name($old_dest)) { + $old_dest_sane = '@' . idn_to_ascii($old_dest, 0, INTL_IDNA_VARIANT_UTS46); + } + elseif (filter_var($old_dest, FILTER_VALIDATE_EMAIL)) { + $old_dest_sane = $old_dest; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('invalid_recipient_map_old', htmlspecialchars($old_dest)) + ); + continue; + } + if (!filter_var($new_dest, FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('invalid_recipient_map_new', htmlspecialchars($new_dest)) + ); + continue; + } + $rmaps = recipient_map('get'); + foreach ($rmaps as $rmap) { + if ($rmap == $id) { continue; } + if (recipient_map('details', $rmap)['recipient_map_old'] == $old_dest_sane) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('recipient_map_entry_exists', htmlspecialchars($old_dest_sane)) + ); + return false; + } + } + $stmt = $pdo->prepare("UPDATE `recipient_maps` SET + `old_dest` = :old_dest, + `new_dest` = :new_dest, + `active` = :active + WHERE `id`= :id"); + $stmt->execute(array( + ':old_dest' => $old_dest_sane, + ':new_dest' => $new_dest, + ':active' => $active, + ':id' => $id + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('recipient_map_entry_saved', htmlspecialchars($old_dest_sane)) + ); + } + break; + case 'details': + $mapdata = array(); + $id = intval($_data); + + $stmt = $pdo->prepare("SELECT `id`, + `old_dest` AS `recipient_map_old`, + `new_dest` AS `recipient_map_new`, + `active`, + `created`, + `modified` FROM `recipient_maps` + WHERE `id` = :id"); + $stmt->execute(array(':id' => $id)); + $mapdata = $stmt->fetch(PDO::FETCH_ASSOC); + + return $mapdata; + break; + case 'get': + $mapdata = array(); + $all_items = array(); + $id = intval($_data); + + $stmt = $pdo->query("SELECT `id` FROM `recipient_maps`"); + $all_items = $stmt->fetchAll(PDO::FETCH_ASSOC); + + foreach ($all_items as $i) { + $mapdata[] = $i['id']; + } + $all_items = null; + return $mapdata; + break; + case 'delete': + $ids = (array)$_data['id']; + foreach ($ids as $id) { + if (!is_numeric($id)) { + return false; + } + $stmt = $pdo->prepare("DELETE FROM `recipient_maps` WHERE `id`= :id"); + $stmt->execute(array(':id' => $id)); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('recipient_map_entry_deleted', htmlspecialchars($id)) + ); + } + break; + } +} diff --git a/data/web/inc/functions.admin.inc.php b/data/web/inc/functions.admin.inc.php index 9f42fd72..4696fcd0 100644 --- a/data/web/inc/functions.admin.inc.php +++ b/data/web/inc/functions.admin.inc.php @@ -1,246 +1,246 @@ - 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - global $pdo; - global $lang; - $_data_log = $_data; - !isset($_data_log['password']) ?: $_data_log['password'] = '*'; - !isset($_data_log['password2']) ?: $_data_log['password2'] = '*'; - switch ($_action) { - case 'add': - $username = strtolower(trim($_data['username'])); - $password = $_data['password']; - $password2 = $_data['password2']; - $active = intval($_data['active']); - if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username)) || empty ($username) || $username == 'API') { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('username_invalid', $username) - ); - return false; - } - - $stmt = $pdo->prepare("SELECT `username` FROM `admin` - WHERE `username` = :username"); - $stmt->execute(array(':username' => $username)); - $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - - $stmt = $pdo->prepare("SELECT `username` FROM `domain_admins` - WHERE `username` = :username"); - $stmt->execute(array(':username' => $username)); - $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - - foreach ($num_results as $num_results_each) { - if ($num_results_each != 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('object_exists', htmlspecialchars($username)) - ); - return false; - } - } - if (password_check($password, $password2) !== true) { - return false; - } - $password_hashed = hash_password($password); - $stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `active`) - VALUES (:username, :password_hashed, '1', :active)"); - $stmt->execute(array( - ':username' => $username, - ':password_hashed' => $password_hashed, - ':active' => $active - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('admin_added', htmlspecialchars($username)) - ); - break; - case 'edit': - if (!is_array($_data['username'])) { - $usernames = array(); - $usernames[] = $_data['username']; - } - else { - $usernames = $_data['username']; - } - foreach ($usernames as $username) { - $is_now = admin('details', $username); - if (!empty($is_now)) { - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; - $username_new = (!empty($_data['username_new'])) ? $_data['username_new'] : $is_now['username']; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - continue; - } - $password = $_data['password']; - $password2 = $_data['password2']; - if ($active == 0) { - $left_active = 0; - foreach (admin('get') as $admin) { - $left_active = $left_active + admin('details', $admin)['active']; - } - if ($left_active == 1) { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'no_active_admin' - ); - continue; - } - } - if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username_new))) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('username_invalid', $username_new) - ); - continue; - } - if ($username_new != $username) { - if (!empty(admin('details', $username_new)['username'])) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('username_invalid', $username_new) - ); - continue; - } - } - if (!empty($password)) { - if (password_check($password, $password2) !== true) { - return false; - } - $password_hashed = hash_password($password); - $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed WHERE `username` = :username"); - $stmt->execute(array( - ':password_hashed' => $password_hashed, - ':username_new' => $username_new, - ':username' => $username, - ':active' => $active - )); - if (isset($_data['disable_tfa'])) { - $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username"); - $stmt->execute(array(':username' => $username)); - } - else { - $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username"); - $stmt->execute(array(':username_new' => $username_new, ':username' => $username)); - } - } - else { - $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active WHERE `username` = :username"); - $stmt->execute(array( - ':username_new' => $username_new, - ':username' => $username, - ':active' => $active - )); - if (isset($_data['disable_tfa'])) { - $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username"); - $stmt->execute(array(':username' => $username)); - } - else { - $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username"); - $stmt->execute(array(':username_new' => $username_new, ':username' => $username)); - } - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('admin_modified', htmlspecialchars($username)) - ); - } - return true; - break; - case 'delete': - $usernames = (array)$_data['username']; - foreach ($usernames as $username) { - if ($_SESSION['mailcow_cc_username'] == $username) { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'cannot_delete_self' - ); - continue; - } - if (empty(admin('details', $username))) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('username_invalid', $username) - ); - continue; - } - $stmt = $pdo->prepare("DELETE FROM `admin` WHERE `username` = :username"); - $stmt->execute(array( - ':username' => $username, - )); - $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username"); - $stmt->execute(array( - ':username' => $username, - )); - $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username"); - $stmt->execute(array( - ':username' => $username, - )); - $stmt = $pdo->prepare("DELETE FROM `fido2` WHERE `username` = :username"); - $stmt->execute(array( - ':username' => $username, - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('admin_removed', htmlspecialchars($username)) - ); - } - break; - case 'get': - $admins = array(); - $stmt = $pdo->query("SELECT `username` FROM `admin` WHERE `superadmin` = '1'"); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($row = array_shift($rows)) { - $admins[] = $row['username']; - } - return $admins; - break; - case 'details': - $admindata = array(); - $stmt = $pdo->prepare("SELECT - `tfa`.`active` AS `tfa_active`, - `admin`.`username`, - `admin`.`created`, - `admin`.`active` AS `active` - FROM `admin` - LEFT OUTER JOIN `tfa` ON `tfa`.`username`=`admin`.`username` - WHERE `admin`.`username`= :admin AND `superadmin` = '1'"); - $stmt->execute(array( - ':admin' => $_data - )); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (empty($row)) { - return false; - } - $admindata['username'] = $row['username']; - $admindata['tfa_active'] = (is_null($row['tfa_active'])) ? 0 : $row['tfa_active']; - $admindata['tfa_active_int'] = (is_null($row['tfa_active'])) ? 0 : $row['tfa_active']; - $admindata['active'] = $row['active']; - $admindata['active_int'] = $row['active']; - $admindata['created'] = $row['created']; - return $admindata; - break; - } -} + 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + global $pdo; + global $lang; + $_data_log = $_data; + !isset($_data_log['password']) ?: $_data_log['password'] = '*'; + !isset($_data_log['password2']) ?: $_data_log['password2'] = '*'; + switch ($_action) { + case 'add': + $username = strtolower(trim($_data['username'])); + $password = $_data['password']; + $password2 = $_data['password2']; + $active = intval($_data['active']); + if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username)) || empty ($username) || $username == 'API') { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('username_invalid', $username) + ); + return false; + } + + $stmt = $pdo->prepare("SELECT `username` FROM `admin` + WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + + $stmt = $pdo->prepare("SELECT `username` FROM `domain_admins` + WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + + foreach ($num_results as $num_results_each) { + if ($num_results_each != 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('object_exists', htmlspecialchars($username)) + ); + return false; + } + } + if (password_check($password, $password2) !== true) { + return false; + } + $password_hashed = hash_password($password); + $stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `active`) + VALUES (:username, :password_hashed, '1', :active)"); + $stmt->execute(array( + ':username' => $username, + ':password_hashed' => $password_hashed, + ':active' => $active + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('admin_added', htmlspecialchars($username)) + ); + break; + case 'edit': + if (!is_array($_data['username'])) { + $usernames = array(); + $usernames[] = $_data['username']; + } + else { + $usernames = $_data['username']; + } + foreach ($usernames as $username) { + $is_now = admin('details', $username); + if (!empty($is_now)) { + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; + $username_new = (!empty($_data['username_new'])) ? $_data['username_new'] : $is_now['username']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + continue; + } + $password = $_data['password']; + $password2 = $_data['password2']; + if ($active == 0) { + $left_active = 0; + foreach (admin('get') as $admin) { + $left_active = $left_active + admin('details', $admin)['active']; + } + if ($left_active == 1) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'no_active_admin' + ); + continue; + } + } + if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username_new))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('username_invalid', $username_new) + ); + continue; + } + if ($username_new != $username) { + if (!empty(admin('details', $username_new)['username'])) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('username_invalid', $username_new) + ); + continue; + } + } + if (!empty($password)) { + if (password_check($password, $password2) !== true) { + return false; + } + $password_hashed = hash_password($password); + $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed WHERE `username` = :username"); + $stmt->execute(array( + ':password_hashed' => $password_hashed, + ':username_new' => $username_new, + ':username' => $username, + ':active' => $active + )); + if (isset($_data['disable_tfa'])) { + $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + } + else { + $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username"); + $stmt->execute(array(':username_new' => $username_new, ':username' => $username)); + } + } + else { + $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active WHERE `username` = :username"); + $stmt->execute(array( + ':username_new' => $username_new, + ':username' => $username, + ':active' => $active + )); + if (isset($_data['disable_tfa'])) { + $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + } + else { + $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username"); + $stmt->execute(array(':username_new' => $username_new, ':username' => $username)); + } + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('admin_modified', htmlspecialchars($username)) + ); + } + return true; + break; + case 'delete': + $usernames = (array)$_data['username']; + foreach ($usernames as $username) { + if ($_SESSION['mailcow_cc_username'] == $username) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'cannot_delete_self' + ); + continue; + } + if (empty(admin('details', $username))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('username_invalid', $username) + ); + continue; + } + $stmt = $pdo->prepare("DELETE FROM `admin` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + $stmt = $pdo->prepare("DELETE FROM `fido2` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('admin_removed', htmlspecialchars($username)) + ); + } + break; + case 'get': + $admins = array(); + $stmt = $pdo->query("SELECT `username` FROM `admin` WHERE `superadmin` = '1'"); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($rows)) { + $admins[] = $row['username']; + } + return $admins; + break; + case 'details': + $admindata = array(); + $stmt = $pdo->prepare("SELECT + `tfa`.`active` AS `tfa_active`, + `admin`.`username`, + `admin`.`created`, + `admin`.`active` AS `active` + FROM `admin` + LEFT OUTER JOIN `tfa` ON `tfa`.`username`=`admin`.`username` + WHERE `admin`.`username`= :admin AND `superadmin` = '1'"); + $stmt->execute(array( + ':admin' => $_data + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (empty($row)) { + return false; + } + $admindata['username'] = $row['username']; + $admindata['tfa_active'] = (is_null($row['tfa_active'])) ? 0 : $row['tfa_active']; + $admindata['tfa_active_int'] = (is_null($row['tfa_active'])) ? 0 : $row['tfa_active']; + $admindata['active'] = $row['active']; + $admindata['active_int'] = $row['active']; + $admindata['created'] = $row['created']; + return $admindata; + break; + } +} diff --git a/data/web/inc/functions.app_passwd.inc.php b/data/web/inc/functions.app_passwd.inc.php index b493fc91..cb3dc56b 100644 --- a/data/web/inc/functions.app_passwd.inc.php +++ b/data/web/inc/functions.app_passwd.inc.php @@ -1,242 +1,242 @@ - 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - else { - $username = $_data['username']; - } - } - else { - $username = $_SESSION['mailcow_cc_username']; - } - switch ($_action) { - case 'add': - $app_name = htmlspecialchars(trim($_data['app_name'])); - $password = $_data['app_passwd']; - $password2 = $_data['app_passwd2']; - $active = intval($_data['active']); - $protocols = (array)$_data['protocols']; - $imap_access = (in_array('imap_access', $protocols)) ? 1 : 0; - $dav_access = (in_array('dav_access', $protocols)) ? 1 : 0; - $smtp_access = (in_array('smtp_access', $protocols)) ? 1 : 0; - $eas_access = (in_array('eas_access', $protocols)) ? 1 : 0; - $pop3_access = (in_array('pop3_access', $protocols)) ? 1 : 0; - $sieve_access = (in_array('sieve_access', $protocols)) ? 1 : 0; - $domain = mailbox('get', 'mailbox_details', $username)['domain']; - if (empty($domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'password_complexity' - ); - return false; - } - if ($password != $password2) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'password_mismatch' - ); - return false; - } - $password_hashed = hash_password($password); - if (empty($app_name)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'app_name_empty' - ); - return false; - } - $stmt = $pdo->prepare("INSERT INTO `app_passwd` (`name`, `mailbox`, `domain`, `password`, `imap_access`, `smtp_access`, `eas_access`, `dav_access`, `pop3_access`, `sieve_access`, `active`) - VALUES (:app_name, :mailbox, :domain, :password, :imap_access, :smtp_access, :eas_access, :dav_access, :pop3_access, :sieve_access, :active)"); - $stmt->execute(array( - ':app_name' => $app_name, - ':mailbox' => $username, - ':domain' => $domain, - ':password' => $password_hashed, - ':imap_access' => $imap_access, - ':smtp_access' => $smtp_access, - ':eas_access' => $eas_access, - ':dav_access' => $dav_access, - ':pop3_access' => $pop3_access, - ':sieve_access' => $sieve_access, - ':active' => $active - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'app_passwd_added' - ); - break; - case 'edit': - $ids = (array)$_data['id']; - foreach ($ids as $id) { - $is_now = app_passwd('details', $id); - if (!empty($is_now)) { - $app_name = (!empty($_data['app_name'])) ? $_data['app_name'] : $is_now['name']; - $password = (!empty($_data['password'])) ? $_data['password'] : null; - $password2 = (!empty($_data['password2'])) ? $_data['password2'] : null; - if (isset($_data['protocols'])) { - $protocols = (array)$_data['protocols']; - $imap_access = (in_array('imap_access', $protocols)) ? 1 : 0; - $dav_access = (in_array('dav_access', $protocols)) ? 1 : 0; - $smtp_access = (in_array('smtp_access', $protocols)) ? 1 : 0; - $eas_access = (in_array('eas_access', $protocols)) ? 1 : 0; - $pop3_access = (in_array('pop3_access', $protocols)) ? 1 : 0; - $sieve_access = (in_array('sieve_access', $protocols)) ? 1 : 0; - } - else { - $imap_access = $is_now['imap_access']; - $smtp_access = $is_now['smtp_access']; - $dav_access = $is_now['dav_access']; - $eas_access = $is_now['eas_access']; - $pop3_access = $is_now['pop3_access']; - $sieve_access = $is_now['sieve_access']; - } - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('app_passwd_id_invalid', $id) - ); - continue; - } - $app_name = htmlspecialchars(trim($app_name)); - if (!empty($password) && !empty($password2)) { - if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'password_complexity' - ); - continue; - } - if ($password != $password2) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'password_mismatch' - ); - continue; - } - $password_hashed = hash_password($password); - $stmt = $pdo->prepare("UPDATE `app_passwd` SET - `password` = :password_hashed - WHERE `mailbox` = :username AND `id` = :id"); - $stmt->execute(array( - ':password_hashed' => $password_hashed, - ':username' => $username, - ':id' => $id - )); - } - - $stmt = $pdo->prepare("UPDATE `app_passwd` SET - `name` = :app_name, - `mailbox` = :username, - `imap_access` = :imap_access, - `smtp_access` = :smtp_access, - `eas_access` = :eas_access, - `dav_access` = :dav_access, - `pop3_access` = :pop3_access, - `sieve_access` = :sieve_access, - `active` = :active - WHERE `id` = :id"); - $stmt->execute(array( - ':app_name' => $app_name, - ':username' => $username, - ':imap_access' => $imap_access, - ':smtp_access' => $smtp_access, - ':eas_access' => $eas_access, - ':dav_access' => $dav_access, - ':pop3_access' => $pop3_access, - ':sieve_access' => $sieve_access, - ':active' => $active, - ':id' => $id - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('object_modified', htmlspecialchars(implode(', ', $ids))) - ); - } - break; - case 'delete': - $ids = (array)$_data['id']; - foreach ($ids as $id) { - $stmt = $pdo->prepare("SELECT `mailbox` FROM `app_passwd` WHERE `id` = :id"); - $stmt->execute(array(':id' => $id)); - $mailbox = $stmt->fetch(PDO::FETCH_ASSOC)['mailbox']; - if (empty($mailbox)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'app_passwd_id_invalid' - ); - return false; - } - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $mailbox)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $stmt = $pdo->prepare("DELETE FROM `app_passwd` WHERE `id`= :id"); - $stmt->execute(array(':id' => $id)); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('app_passwd_removed', htmlspecialchars($id)) - ); - } - break; - case 'get': - $app_passwds = array(); - $stmt = $pdo->prepare("SELECT `id`, `name` FROM `app_passwd` WHERE `mailbox` = :username"); - $stmt->execute(array(':username' => $username)); - $app_passwds = $stmt->fetchAll(PDO::FETCH_ASSOC); - return $app_passwds; - break; - case 'details': - $app_passwd_data = array(); - $stmt = $pdo->prepare("SELECT * - FROM `app_passwd` - WHERE `id` = :id"); - $stmt->execute(array(':id' => $_data)); - $app_passwd_data = $stmt->fetch(PDO::FETCH_ASSOC); - if (empty($app_passwd_data)) { - return false; - } - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $app_passwd_data['mailbox'])) { - $app_passwd_data = array(); - return false; - } - $app_passwd_data['name'] = htmlspecialchars(trim($app_passwd_data['name'])); - return $app_passwd_data; - break; - } -} + 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + else { + $username = $_data['username']; + } + } + else { + $username = $_SESSION['mailcow_cc_username']; + } + switch ($_action) { + case 'add': + $app_name = htmlspecialchars(trim($_data['app_name'])); + $password = $_data['app_passwd']; + $password2 = $_data['app_passwd2']; + $active = intval($_data['active']); + $protocols = (array)$_data['protocols']; + $imap_access = (in_array('imap_access', $protocols)) ? 1 : 0; + $dav_access = (in_array('dav_access', $protocols)) ? 1 : 0; + $smtp_access = (in_array('smtp_access', $protocols)) ? 1 : 0; + $eas_access = (in_array('eas_access', $protocols)) ? 1 : 0; + $pop3_access = (in_array('pop3_access', $protocols)) ? 1 : 0; + $sieve_access = (in_array('sieve_access', $protocols)) ? 1 : 0; + $domain = mailbox('get', 'mailbox_details', $username)['domain']; + if (empty($domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'password_complexity' + ); + return false; + } + if ($password != $password2) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'password_mismatch' + ); + return false; + } + $password_hashed = hash_password($password); + if (empty($app_name)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'app_name_empty' + ); + return false; + } + $stmt = $pdo->prepare("INSERT INTO `app_passwd` (`name`, `mailbox`, `domain`, `password`, `imap_access`, `smtp_access`, `eas_access`, `dav_access`, `pop3_access`, `sieve_access`, `active`) + VALUES (:app_name, :mailbox, :domain, :password, :imap_access, :smtp_access, :eas_access, :dav_access, :pop3_access, :sieve_access, :active)"); + $stmt->execute(array( + ':app_name' => $app_name, + ':mailbox' => $username, + ':domain' => $domain, + ':password' => $password_hashed, + ':imap_access' => $imap_access, + ':smtp_access' => $smtp_access, + ':eas_access' => $eas_access, + ':dav_access' => $dav_access, + ':pop3_access' => $pop3_access, + ':sieve_access' => $sieve_access, + ':active' => $active + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'app_passwd_added' + ); + break; + case 'edit': + $ids = (array)$_data['id']; + foreach ($ids as $id) { + $is_now = app_passwd('details', $id); + if (!empty($is_now)) { + $app_name = (!empty($_data['app_name'])) ? $_data['app_name'] : $is_now['name']; + $password = (!empty($_data['password'])) ? $_data['password'] : null; + $password2 = (!empty($_data['password2'])) ? $_data['password2'] : null; + if (isset($_data['protocols'])) { + $protocols = (array)$_data['protocols']; + $imap_access = (in_array('imap_access', $protocols)) ? 1 : 0; + $dav_access = (in_array('dav_access', $protocols)) ? 1 : 0; + $smtp_access = (in_array('smtp_access', $protocols)) ? 1 : 0; + $eas_access = (in_array('eas_access', $protocols)) ? 1 : 0; + $pop3_access = (in_array('pop3_access', $protocols)) ? 1 : 0; + $sieve_access = (in_array('sieve_access', $protocols)) ? 1 : 0; + } + else { + $imap_access = $is_now['imap_access']; + $smtp_access = $is_now['smtp_access']; + $dav_access = $is_now['dav_access']; + $eas_access = $is_now['eas_access']; + $pop3_access = $is_now['pop3_access']; + $sieve_access = $is_now['sieve_access']; + } + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('app_passwd_id_invalid', $id) + ); + continue; + } + $app_name = htmlspecialchars(trim($app_name)); + if (!empty($password) && !empty($password2)) { + if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'password_complexity' + ); + continue; + } + if ($password != $password2) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'password_mismatch' + ); + continue; + } + $password_hashed = hash_password($password); + $stmt = $pdo->prepare("UPDATE `app_passwd` SET + `password` = :password_hashed + WHERE `mailbox` = :username AND `id` = :id"); + $stmt->execute(array( + ':password_hashed' => $password_hashed, + ':username' => $username, + ':id' => $id + )); + } + + $stmt = $pdo->prepare("UPDATE `app_passwd` SET + `name` = :app_name, + `mailbox` = :username, + `imap_access` = :imap_access, + `smtp_access` = :smtp_access, + `eas_access` = :eas_access, + `dav_access` = :dav_access, + `pop3_access` = :pop3_access, + `sieve_access` = :sieve_access, + `active` = :active + WHERE `id` = :id"); + $stmt->execute(array( + ':app_name' => $app_name, + ':username' => $username, + ':imap_access' => $imap_access, + ':smtp_access' => $smtp_access, + ':eas_access' => $eas_access, + ':dav_access' => $dav_access, + ':pop3_access' => $pop3_access, + ':sieve_access' => $sieve_access, + ':active' => $active, + ':id' => $id + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('object_modified', htmlspecialchars(implode(', ', $ids))) + ); + } + break; + case 'delete': + $ids = (array)$_data['id']; + foreach ($ids as $id) { + $stmt = $pdo->prepare("SELECT `mailbox` FROM `app_passwd` WHERE `id` = :id"); + $stmt->execute(array(':id' => $id)); + $mailbox = $stmt->fetch(PDO::FETCH_ASSOC)['mailbox']; + if (empty($mailbox)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'app_passwd_id_invalid' + ); + return false; + } + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $mailbox)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $stmt = $pdo->prepare("DELETE FROM `app_passwd` WHERE `id`= :id"); + $stmt->execute(array(':id' => $id)); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('app_passwd_removed', htmlspecialchars($id)) + ); + } + break; + case 'get': + $app_passwds = array(); + $stmt = $pdo->prepare("SELECT `id`, `name` FROM `app_passwd` WHERE `mailbox` = :username"); + $stmt->execute(array(':username' => $username)); + $app_passwds = $stmt->fetchAll(PDO::FETCH_ASSOC); + return $app_passwds; + break; + case 'details': + $app_passwd_data = array(); + $stmt = $pdo->prepare("SELECT * + FROM `app_passwd` + WHERE `id` = :id"); + $stmt->execute(array(':id' => $_data)); + $app_passwd_data = $stmt->fetch(PDO::FETCH_ASSOC); + if (empty($app_passwd_data)) { + return false; + } + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $app_passwd_data['mailbox'])) { + $app_passwd_data = array(); + return false; + } + $app_passwd_data['name'] = htmlspecialchars(trim($app_passwd_data['name'])); + return $app_passwd_data; + break; + } +} diff --git a/data/web/inc/functions.customize.inc.php b/data/web/inc/functions.customize.inc.php index 6025d97d..5ac98bf0 100644 --- a/data/web/inc/functions.customize.inc.php +++ b/data/web/inc/functions.customize.inc.php @@ -1,315 +1,315 @@ - 'danger', - 'log' => array(__FUNCTION__, $_action, $_item, $_data), - 'msg' => 'demo_mode_enabled' - ); - return false; - } - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_item, $_data), - 'msg' => 'access_denied' - ); - return false; - } - switch ($_item) { - case 'main_logo': - if (in_array($_data['main_logo']['type'], array('image/gif', 'image/jpeg', 'image/pjpeg', 'image/x-png', 'image/png', 'image/svg+xml'))) { - try { - if (file_exists($_data['main_logo']['tmp_name']) !== true) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_item, $_data), - 'msg' => 'img_tmp_missing' - ); - return false; - } - $image = new Imagick($_data['main_logo']['tmp_name']); - if ($image->valid() !== true) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_item, $_data), - 'msg' => 'img_invalid' - ); - return false; - } - $image->destroy(); - } - catch (ImagickException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_item, $_data), - 'msg' => 'img_invalid' - ); - return false; - } - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_item, $_data), - 'msg' => 'invalid_mime_type' - ); - return false; - } - try { - $redis->Set('MAIN_LOGO', 'data:' . $_data['main_logo']['type'] . ';base64,' . base64_encode(file_get_contents($_data['main_logo']['tmp_name']))); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_item, $_data), - 'msg' => array('redis_error', $e) - ); - return false; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_item, $_data), - 'msg' => 'upload_success' - ); - break; - } - break; - case 'edit': - // disable functionality when demo mode is enabled - if ($GLOBALS["DEMO_MODE"]) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_item, $_data), - 'msg' => 'demo_mode_enabled' - ); - return false; - } - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_item, $_data), - 'msg' => 'access_denied' - ); - return false; - } - switch ($_item) { - case 'app_links': - $apps = (array)$_data['app']; - $links = (array)$_data['href']; - $out = array(); - if (count($apps) == count($links)) { - for ($i = 0; $i < count($apps); $i++) { - $out[] = array($apps[$i] => $links[$i]); - } - try { - $redis->set('APP_LINKS', json_encode($out)); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_item, $_data), - 'msg' => array('redis_error', $e) - ); - return false; - } - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_item, $_data), - 'msg' => 'app_links' - ); - break; - case 'ui_texts': - $title_name = $_data['title_name']; - $main_name = $_data['main_name']; - $apps_name = $_data['apps_name']; - $help_text = $_data['help_text']; - $ui_footer = $_data['ui_footer']; - $ui_announcement_text = $_data['ui_announcement_text']; - $ui_announcement_type = (in_array($_data['ui_announcement_type'], array('info', 'warning', 'danger'))) ? $_data['ui_announcement_type'] : false; - $ui_announcement_active = (!empty($_data['ui_announcement_active']) ? 1 : 0); - - try { - $redis->set('TITLE_NAME', htmlspecialchars($title_name)); - $redis->set('MAIN_NAME', htmlspecialchars($main_name)); - $redis->set('APPS_NAME', htmlspecialchars($apps_name)); - $redis->set('HELP_TEXT', $help_text); - $redis->set('UI_FOOTER', $ui_footer); - $redis->set('UI_ANNOUNCEMENT_TEXT', $ui_announcement_text); - $redis->set('UI_ANNOUNCEMENT_TYPE', $ui_announcement_type); - $redis->set('UI_ANNOUNCEMENT_ACTIVE', $ui_announcement_active); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_item, $_data), - 'msg' => array('redis_error', $e) - ); - return false; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_item, $_data), - 'msg' => 'ui_texts' - ); - break; - case 'ip_check': - $ip_check = ($_data['ip_check_opt_in'] == "1") ? 1 : 0; - try { - $redis->set('IP_CHECK', $ip_check); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_item, $_data), - 'msg' => array('redis_error', $e) - ); - return false; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_item, $_data), - 'msg' => 'ip_check_opt_in_modified' - ); - break; - } - break; - case 'delete': - // disable functionality when demo mode is enabled - if ($GLOBALS["DEMO_MODE"]) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_item, $_data), - 'msg' => 'demo_mode_enabled' - ); - return false; - } - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_item, $_data), - 'msg' => 'access_denied' - ); - return false; - } - switch ($_item) { - case 'main_logo': - try { - if ($redis->del('MAIN_LOGO')) { - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_item, $_data), - 'msg' => 'reset_main_logo' - ); - return true; - } - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_item, $_data), - 'msg' => array('redis_error', $e) - ); - return false; - } - break; - } - break; - case 'get': - switch ($_item) { - case 'app_links': - try { - $app_links = json_decode($redis->get('APP_LINKS'), true); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_item, $_data), - 'msg' => array('redis_error', $e) - ); - return false; - } - return ($app_links) ? $app_links : false; - break; - case 'main_logo': - try { - return $redis->get('MAIN_LOGO'); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_item, $_data), - 'msg' => array('redis_error', $e) - ); - return false; - } - break; - case 'ui_texts': - try { - $data['title_name'] = ($title_name = $redis->get('TITLE_NAME')) ? $title_name : 'mailcow UI'; - $data['main_name'] = ($main_name = $redis->get('MAIN_NAME')) ? $main_name : 'mailcow UI'; - $data['apps_name'] = ($apps_name = $redis->get('APPS_NAME')) ? $apps_name : $lang['header']['apps']; - $data['help_text'] = ($help_text = $redis->get('HELP_TEXT')) ? $help_text : false; - if (!empty($redis->get('UI_IMPRESS'))) { - $redis->set('UI_FOOTER', $redis->get('UI_IMPRESS')); - $redis->del('UI_IMPRESS'); - } - $data['ui_footer'] = ($ui_footer = $redis->get('UI_FOOTER')) ? $ui_footer : false; - $data['ui_announcement_text'] = ($ui_announcement_text = $redis->get('UI_ANNOUNCEMENT_TEXT')) ? $ui_announcement_text : false; - $data['ui_announcement_type'] = ($ui_announcement_type = $redis->get('UI_ANNOUNCEMENT_TYPE')) ? $ui_announcement_type : false; - $data['ui_announcement_active'] = ($redis->get('UI_ANNOUNCEMENT_ACTIVE') == 1) ? 1 : 0; - return $data; - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_item, $_data), - 'msg' => array('redis_error', $e) - ); - return false; - } - break; - case 'main_logo_specs': - try { - $image = new Imagick(); - $img_data = explode('base64,', customize('get', 'main_logo')); - if ($img_data[1]) { - $image->readImageBlob(base64_decode($img_data[1])); - return $image->identifyImage(); - } - return false; - } - catch (ImagickException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_item, $_data), - 'msg' => 'imagick_exception' - ); - return false; - } - break; - case 'ip_check': - try { - $ip_check = ($ip_check = $redis->get('IP_CHECK')) ? $ip_check : 0; - return $ip_check; - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_item, $_data), - 'msg' => array('redis_error', $e) - ); - return false; - } - break; - } - break; - } -} + 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => 'demo_mode_enabled' + ); + return false; + } + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => 'access_denied' + ); + return false; + } + switch ($_item) { + case 'main_logo': + if (in_array($_data['main_logo']['type'], array('image/gif', 'image/jpeg', 'image/pjpeg', 'image/x-png', 'image/png', 'image/svg+xml'))) { + try { + if (file_exists($_data['main_logo']['tmp_name']) !== true) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => 'img_tmp_missing' + ); + return false; + } + $image = new Imagick($_data['main_logo']['tmp_name']); + if ($image->valid() !== true) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => 'img_invalid' + ); + return false; + } + $image->destroy(); + } + catch (ImagickException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => 'img_invalid' + ); + return false; + } + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => 'invalid_mime_type' + ); + return false; + } + try { + $redis->Set('MAIN_LOGO', 'data:' . $_data['main_logo']['type'] . ';base64,' . base64_encode(file_get_contents($_data['main_logo']['tmp_name']))); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => array('redis_error', $e) + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => 'upload_success' + ); + break; + } + break; + case 'edit': + // disable functionality when demo mode is enabled + if ($GLOBALS["DEMO_MODE"]) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => 'demo_mode_enabled' + ); + return false; + } + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => 'access_denied' + ); + return false; + } + switch ($_item) { + case 'app_links': + $apps = (array)$_data['app']; + $links = (array)$_data['href']; + $out = array(); + if (count($apps) == count($links)) { + for ($i = 0; $i < count($apps); $i++) { + $out[] = array($apps[$i] => $links[$i]); + } + try { + $redis->set('APP_LINKS', json_encode($out)); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => array('redis_error', $e) + ); + return false; + } + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => 'app_links' + ); + break; + case 'ui_texts': + $title_name = $_data['title_name']; + $main_name = $_data['main_name']; + $apps_name = $_data['apps_name']; + $help_text = $_data['help_text']; + $ui_footer = $_data['ui_footer']; + $ui_announcement_text = $_data['ui_announcement_text']; + $ui_announcement_type = (in_array($_data['ui_announcement_type'], array('info', 'warning', 'danger'))) ? $_data['ui_announcement_type'] : false; + $ui_announcement_active = (!empty($_data['ui_announcement_active']) ? 1 : 0); + + try { + $redis->set('TITLE_NAME', htmlspecialchars($title_name)); + $redis->set('MAIN_NAME', htmlspecialchars($main_name)); + $redis->set('APPS_NAME', htmlspecialchars($apps_name)); + $redis->set('HELP_TEXT', $help_text); + $redis->set('UI_FOOTER', $ui_footer); + $redis->set('UI_ANNOUNCEMENT_TEXT', $ui_announcement_text); + $redis->set('UI_ANNOUNCEMENT_TYPE', $ui_announcement_type); + $redis->set('UI_ANNOUNCEMENT_ACTIVE', $ui_announcement_active); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => array('redis_error', $e) + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => 'ui_texts' + ); + break; + case 'ip_check': + $ip_check = ($_data['ip_check_opt_in'] == "1") ? 1 : 0; + try { + $redis->set('IP_CHECK', $ip_check); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => array('redis_error', $e) + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => 'ip_check_opt_in_modified' + ); + break; + } + break; + case 'delete': + // disable functionality when demo mode is enabled + if ($GLOBALS["DEMO_MODE"]) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => 'demo_mode_enabled' + ); + return false; + } + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => 'access_denied' + ); + return false; + } + switch ($_item) { + case 'main_logo': + try { + if ($redis->del('MAIN_LOGO')) { + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => 'reset_main_logo' + ); + return true; + } + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => array('redis_error', $e) + ); + return false; + } + break; + } + break; + case 'get': + switch ($_item) { + case 'app_links': + try { + $app_links = json_decode($redis->get('APP_LINKS'), true); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => array('redis_error', $e) + ); + return false; + } + return ($app_links) ? $app_links : false; + break; + case 'main_logo': + try { + return $redis->get('MAIN_LOGO'); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => array('redis_error', $e) + ); + return false; + } + break; + case 'ui_texts': + try { + $data['title_name'] = ($title_name = $redis->get('TITLE_NAME')) ? $title_name : 'mailcow UI'; + $data['main_name'] = ($main_name = $redis->get('MAIN_NAME')) ? $main_name : 'mailcow UI'; + $data['apps_name'] = ($apps_name = $redis->get('APPS_NAME')) ? $apps_name : $lang['header']['apps']; + $data['help_text'] = ($help_text = $redis->get('HELP_TEXT')) ? $help_text : false; + if (!empty($redis->get('UI_IMPRESS'))) { + $redis->set('UI_FOOTER', $redis->get('UI_IMPRESS')); + $redis->del('UI_IMPRESS'); + } + $data['ui_footer'] = ($ui_footer = $redis->get('UI_FOOTER')) ? $ui_footer : false; + $data['ui_announcement_text'] = ($ui_announcement_text = $redis->get('UI_ANNOUNCEMENT_TEXT')) ? $ui_announcement_text : false; + $data['ui_announcement_type'] = ($ui_announcement_type = $redis->get('UI_ANNOUNCEMENT_TYPE')) ? $ui_announcement_type : false; + $data['ui_announcement_active'] = ($redis->get('UI_ANNOUNCEMENT_ACTIVE') == 1) ? 1 : 0; + return $data; + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => array('redis_error', $e) + ); + return false; + } + break; + case 'main_logo_specs': + try { + $image = new Imagick(); + $img_data = explode('base64,', customize('get', 'main_logo')); + if ($img_data[1]) { + $image->readImageBlob(base64_decode($img_data[1])); + return $image->identifyImage(); + } + return false; + } + catch (ImagickException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => 'imagick_exception' + ); + return false; + } + break; + case 'ip_check': + try { + $ip_check = ($ip_check = $redis->get('IP_CHECK')) ? $ip_check : 0; + return $ip_check; + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => array('redis_error', $e) + ); + return false; + } + break; + } + break; + } +} diff --git a/data/web/inc/functions.dkim.inc.php b/data/web/inc/functions.dkim.inc.php index 29b32d5d..a64af795 100644 --- a/data/web/inc/functions.dkim.inc.php +++ b/data/web/inc/functions.dkim.inc.php @@ -1,325 +1,325 @@ - 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => array('dkim_domain_or_sel_invalid', $domain) - ); - continue; - } - if ($redis->hGet('DKIM_PUB_KEYS', $domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => array('dkim_domain_or_sel_invalid', $domain) - ); - continue; - } - if (!ctype_alnum(str_replace(['-', '_'], '', $dkim_selector))) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => array('dkim_domain_or_sel_invalid', $domain) - ); - continue; - } - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => array('access_denied', $domain) - ); - continue; - } - $config = array( - "digest_alg" => "sha256", - "private_key_bits" => $key_length, - "private_key_type" => OPENSSL_KEYTYPE_RSA, - ); - if ($keypair_ressource = openssl_pkey_new($config)) { - $key_details = openssl_pkey_get_details($keypair_ressource); - $pubKey = implode(array_slice( - array_filter( - explode(PHP_EOL, $key_details['key']) - ), 1, -1) - ); - // Save public key and selector to redis - try { - $redis->hSet('DKIM_PUB_KEYS', $domain, $pubKey); - $redis->hSet('DKIM_SELECTORS', $domain, $dkim_selector); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => array('redis_error', $e) - ); - continue; - } - // Export private key and save private key to redis - openssl_pkey_export($keypair_ressource, $privKey); - if (isset($privKey) && !empty($privKey)) { - try { - $redis->hSet('DKIM_PRIV_KEYS', $dkim_selector . '.' . $domain, trim($privKey)); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => array('redis_error', $e) - ); - continue; - } - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => array('dkim_added', $domain) - ); - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => array('dkim_domain_or_sel_invalid', $domain) - ); - continue; - } - } - break; - case 'duplicate': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'access_denied' - ); - return false; - } - $from_domain = $_data['from_domain']; - $from_domain_dkim = dkim('details', $from_domain, true); - if (empty($from_domain_dkim)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => array('dkim_domain_or_sel_invalid', $from_domain) - ); - continue; - } - $to_domains = (array)$_data['to_domain']; - $to_domains = array_filter($to_domains); - foreach ($to_domains as $to_domain) { - try { - $redis->hSet('DKIM_PUB_KEYS', $to_domain, $from_domain_dkim['pubkey']); - $redis->hSet('DKIM_SELECTORS', $to_domain, $from_domain_dkim['dkim_selector']); - $redis->hSet('DKIM_PRIV_KEYS', $from_domain_dkim['dkim_selector'] . '.' . $to_domain, base64_decode(trim($from_domain_dkim['privkey']))); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => array('redis_error', $e) - ); - continue; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => array('dkim_duplicated', $from_domain, $to_domain) - ); - } - break; - case 'import': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'access_denied' - ); - return false; - } - $private_key_input = trim($_data['private_key_file']); - $overwrite_existing = intval($_data['overwrite_existing']); - $private_key_normalized = preg_replace('~\r\n?~', "\n", $private_key_input); - $private_key = openssl_pkey_get_private($private_key_normalized); - if ($ssl_error = openssl_error_string()) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => array('private_key_error', $ssl_error) - ); - return false; - } - // Explode by nl - $pem_public_key_array = explode(PHP_EOL, trim(openssl_pkey_get_details($private_key)['key'])); - // Remove first and last line/item - array_shift($pem_public_key_array); - array_pop($pem_public_key_array); - // Implode as single string - $pem_public_key = implode('', (array)$pem_public_key_array); - $dkim_selector = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : 'dkim'; - $domain = $_data['domain']; - if (!is_valid_domain_name($domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => array('dkim_domain_or_sel_invalid', $domain) - ); - return false; - } - if ($redis->hGet('DKIM_PUB_KEYS', $domain)) { - if ($overwrite_existing == 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => array('dkim_domain_or_sel_exists', $domain) - ); - return false; - } - } - if (!ctype_alnum($dkim_selector)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => array('dkim_domain_or_sel_invalid', $domain) - ); - return false; - } - try { - dkim('delete', array('domains' => $domain)); - $redis->hSet('DKIM_PUB_KEYS', $domain, $pem_public_key); - $redis->hSet('DKIM_SELECTORS', $domain, $dkim_selector); - $redis->hSet('DKIM_PRIV_KEYS', $dkim_selector . '.' . $domain, $private_key_normalized); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => array('redis_error', $e) - ); - return false; - } - unset($private_key_normalized); - unset($private_key); - unset($private_key_input); - try { - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => array('redis_error', $e) - ); - return false; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => array('dkim_added', $domain) - ); - return true; - break; - case 'details': - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data) && $_SESSION['mailcow_cc_role'] != "admin") { - return false; - } - $dkimdata = array(); - if ($redis_dkim_key_data = $redis->hGet('DKIM_PUB_KEYS', $_data)) { - $dkimdata['pubkey'] = $redis_dkim_key_data; - if (strlen($dkimdata['pubkey']) < 391) { - $dkimdata['length'] = "1024"; - } - elseif (strlen($dkimdata['pubkey']) < 736) { - $dkimdata['length'] = "2048"; - } - elseif (strlen($dkimdata['pubkey']) < 1416) { - $dkimdata['length'] = "4096"; - } - else { - $dkimdata['length'] = ">= 8192"; - } - if ($GLOBALS['SPLIT_DKIM_255'] === true) { - $dkim_txt_tmp = str_split('v=DKIM1;k=rsa;t=s;s=email;p=' . $redis_dkim_key_data, 255); - $dkimdata['dkim_txt'] = sprintf('"%s"', implode('" "', (array)$dkim_txt_tmp ) ); - } - else { - $dkimdata['dkim_txt'] = 'v=DKIM1;k=rsa;t=s;s=email;p=' . $redis_dkim_key_data; - } - $dkimdata['dkim_selector'] = $redis->hGet('DKIM_SELECTORS', $_data); - if ($GLOBALS['SHOW_DKIM_PRIV_KEYS'] || $privkey == true) { - $dkimdata['privkey'] = base64_encode($redis->hGet('DKIM_PRIV_KEYS', $dkimdata['dkim_selector'] . '.' . $_data)); - } - else { - $dkimdata['privkey'] = ''; - } - } - return $dkimdata; - break; - case 'blind': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'access_denied' - ); - return false; - } - $blinddkim = array(); - foreach ($redis->hKeys('DKIM_PUB_KEYS') as $redis_dkim_domain) { - $blinddkim[] = $redis_dkim_domain; - } - return array_diff($blinddkim, array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains'))); - break; - case 'delete': - $domains = (array)$_data['domains']; - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'access_denied' - ); - return false; - } - foreach ($domains as $domain) { - if (!is_valid_domain_name($domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => array('dkim_domain_or_sel_invalid', $domain) - ); - continue; - } - try { - $selector = $redis->hGet('DKIM_SELECTORS', $domain); - $redis->hDel('DKIM_PUB_KEYS', $domain); - $redis->hDel('DKIM_PRIV_KEYS', $selector . '.' . $domain); - $redis->hDel('DKIM_SELECTORS', $domain); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => array('redis_error', $e) - ); - continue; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => array('dkim_removed', htmlspecialchars($domain)) - ); - } - break; - } -} + 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('dkim_domain_or_sel_invalid', $domain) + ); + continue; + } + if ($redis->hGet('DKIM_PUB_KEYS', $domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('dkim_domain_or_sel_invalid', $domain) + ); + continue; + } + if (!ctype_alnum(str_replace(['-', '_'], '', $dkim_selector))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('dkim_domain_or_sel_invalid', $domain) + ); + continue; + } + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('access_denied', $domain) + ); + continue; + } + $config = array( + "digest_alg" => "sha256", + "private_key_bits" => $key_length, + "private_key_type" => OPENSSL_KEYTYPE_RSA, + ); + if ($keypair_ressource = openssl_pkey_new($config)) { + $key_details = openssl_pkey_get_details($keypair_ressource); + $pubKey = implode(array_slice( + array_filter( + explode(PHP_EOL, $key_details['key']) + ), 1, -1) + ); + // Save public key and selector to redis + try { + $redis->hSet('DKIM_PUB_KEYS', $domain, $pubKey); + $redis->hSet('DKIM_SELECTORS', $domain, $dkim_selector); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('redis_error', $e) + ); + continue; + } + // Export private key and save private key to redis + openssl_pkey_export($keypair_ressource, $privKey); + if (isset($privKey) && !empty($privKey)) { + try { + $redis->hSet('DKIM_PRIV_KEYS', $dkim_selector . '.' . $domain, trim($privKey)); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('redis_error', $e) + ); + continue; + } + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('dkim_added', $domain) + ); + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('dkim_domain_or_sel_invalid', $domain) + ); + continue; + } + } + break; + case 'duplicate': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'access_denied' + ); + return false; + } + $from_domain = $_data['from_domain']; + $from_domain_dkim = dkim('details', $from_domain, true); + if (empty($from_domain_dkim)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('dkim_domain_or_sel_invalid', $from_domain) + ); + continue; + } + $to_domains = (array)$_data['to_domain']; + $to_domains = array_filter($to_domains); + foreach ($to_domains as $to_domain) { + try { + $redis->hSet('DKIM_PUB_KEYS', $to_domain, $from_domain_dkim['pubkey']); + $redis->hSet('DKIM_SELECTORS', $to_domain, $from_domain_dkim['dkim_selector']); + $redis->hSet('DKIM_PRIV_KEYS', $from_domain_dkim['dkim_selector'] . '.' . $to_domain, base64_decode(trim($from_domain_dkim['privkey']))); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('redis_error', $e) + ); + continue; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('dkim_duplicated', $from_domain, $to_domain) + ); + } + break; + case 'import': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'access_denied' + ); + return false; + } + $private_key_input = trim($_data['private_key_file']); + $overwrite_existing = intval($_data['overwrite_existing']); + $private_key_normalized = preg_replace('~\r\n?~', "\n", $private_key_input); + $private_key = openssl_pkey_get_private($private_key_normalized); + if ($ssl_error = openssl_error_string()) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('private_key_error', $ssl_error) + ); + return false; + } + // Explode by nl + $pem_public_key_array = explode(PHP_EOL, trim(openssl_pkey_get_details($private_key)['key'])); + // Remove first and last line/item + array_shift($pem_public_key_array); + array_pop($pem_public_key_array); + // Implode as single string + $pem_public_key = implode('', (array)$pem_public_key_array); + $dkim_selector = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : 'dkim'; + $domain = $_data['domain']; + if (!is_valid_domain_name($domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('dkim_domain_or_sel_invalid', $domain) + ); + return false; + } + if ($redis->hGet('DKIM_PUB_KEYS', $domain)) { + if ($overwrite_existing == 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('dkim_domain_or_sel_exists', $domain) + ); + return false; + } + } + if (!ctype_alnum($dkim_selector)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('dkim_domain_or_sel_invalid', $domain) + ); + return false; + } + try { + dkim('delete', array('domains' => $domain)); + $redis->hSet('DKIM_PUB_KEYS', $domain, $pem_public_key); + $redis->hSet('DKIM_SELECTORS', $domain, $dkim_selector); + $redis->hSet('DKIM_PRIV_KEYS', $dkim_selector . '.' . $domain, $private_key_normalized); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('redis_error', $e) + ); + return false; + } + unset($private_key_normalized); + unset($private_key); + unset($private_key_input); + try { + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('redis_error', $e) + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('dkim_added', $domain) + ); + return true; + break; + case 'details': + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data) && $_SESSION['mailcow_cc_role'] != "admin") { + return false; + } + $dkimdata = array(); + if ($redis_dkim_key_data = $redis->hGet('DKIM_PUB_KEYS', $_data)) { + $dkimdata['pubkey'] = $redis_dkim_key_data; + if (strlen($dkimdata['pubkey']) < 391) { + $dkimdata['length'] = "1024"; + } + elseif (strlen($dkimdata['pubkey']) < 736) { + $dkimdata['length'] = "2048"; + } + elseif (strlen($dkimdata['pubkey']) < 1416) { + $dkimdata['length'] = "4096"; + } + else { + $dkimdata['length'] = ">= 8192"; + } + if ($GLOBALS['SPLIT_DKIM_255'] === true) { + $dkim_txt_tmp = str_split('v=DKIM1;k=rsa;t=s;s=email;p=' . $redis_dkim_key_data, 255); + $dkimdata['dkim_txt'] = sprintf('"%s"', implode('" "', (array)$dkim_txt_tmp ) ); + } + else { + $dkimdata['dkim_txt'] = 'v=DKIM1;k=rsa;t=s;s=email;p=' . $redis_dkim_key_data; + } + $dkimdata['dkim_selector'] = $redis->hGet('DKIM_SELECTORS', $_data); + if ($GLOBALS['SHOW_DKIM_PRIV_KEYS'] || $privkey == true) { + $dkimdata['privkey'] = base64_encode($redis->hGet('DKIM_PRIV_KEYS', $dkimdata['dkim_selector'] . '.' . $_data)); + } + else { + $dkimdata['privkey'] = ''; + } + } + return $dkimdata; + break; + case 'blind': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'access_denied' + ); + return false; + } + $blinddkim = array(); + foreach ($redis->hKeys('DKIM_PUB_KEYS') as $redis_dkim_domain) { + $blinddkim[] = $redis_dkim_domain; + } + return array_diff($blinddkim, array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains'))); + break; + case 'delete': + $domains = (array)$_data['domains']; + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'access_denied' + ); + return false; + } + foreach ($domains as $domain) { + if (!is_valid_domain_name($domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('dkim_domain_or_sel_invalid', $domain) + ); + continue; + } + try { + $selector = $redis->hGet('DKIM_SELECTORS', $domain); + $redis->hDel('DKIM_PUB_KEYS', $domain); + $redis->hDel('DKIM_PRIV_KEYS', $selector . '.' . $domain); + $redis->hDel('DKIM_SELECTORS', $domain); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('redis_error', $e) + ); + continue; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('dkim_removed', htmlspecialchars($domain)) + ); + } + break; + } +} diff --git a/data/web/inc/functions.docker.inc.php b/data/web/inc/functions.docker.inc.php index 78efac03..8be4518d 100644 --- a/data/web/inc/functions.docker.inc.php +++ b/data/web/inc/functions.docker.inc.php @@ -1,196 +1,196 @@ -Get('F2B_OPTIONS'), true); - $f2b_options['regex'] = json_decode($redis->Get('F2B_REGEX'), true); - $wl = $redis->hGetAll('F2B_WHITELIST'); - if (is_array($wl)) { - foreach ($wl as $key => $value) { - $tmp_wl_data[] = $key; - } - if (isset($tmp_wl_data)) { - natsort($tmp_wl_data); - $f2b_options['whitelist'] = implode(PHP_EOL, (array)$tmp_wl_data); - } - else { - $f2b_options['whitelist'] = ""; - } - } - else { - $f2b_options['whitelist'] = ""; - } - $bl = $redis->hGetAll('F2B_BLACKLIST'); - if (is_array($bl)) { - foreach ($bl as $key => $value) { - $tmp_bl_data[] = $key; - } - if (isset($tmp_bl_data)) { - natsort($tmp_bl_data); - $f2b_options['blacklist'] = implode(PHP_EOL, (array)$tmp_bl_data); - } - else { - $f2b_options['blacklist'] = ""; - } - } - else { - $f2b_options['blacklist'] = ""; - } - $pb = $redis->hGetAll('F2B_PERM_BANS'); - if (is_array($pb)) { - foreach ($pb as $key => $value) { - $f2b_options['perm_bans'][] = array( - 'network'=>$key, - 'ip' => strtok($key,'/') - ); - - } - } - else { - $f2b_options['perm_bans'] = ""; - } - $active_bans = $redis->hGetAll('F2B_ACTIVE_BANS'); - $queue_unban = $redis->hGetAll('F2B_QUEUE_UNBAN'); - if (is_array($active_bans)) { - foreach ($active_bans as $network => $banned_until) { - $queued_for_unban = (isset($queue_unban[$network]) && $queue_unban[$network] == 1) ? 1 : 0; - $difference = $banned_until - time(); - $f2b_options['active_bans'][] = array( - 'queued_for_unban' => $queued_for_unban, - 'network' => $network, - 'ip' => strtok($network,'/'), - 'banned_until' => sprintf('%02dh %02dm %02ds', ($difference/3600), ($difference/60%60), $difference%60) - ); - } - } - else { - $f2b_options['active_bans'] = ""; - } - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('redis_error', $e) - ); - return false; - } - return $f2b_options; - break; - case 'edit': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - // Start to read actions, if any - if (isset($_data['action'])) { - // Reset regex filters - if ($_data['action'] == "reset-regex") { - try { - $redis->Del('F2B_REGEX'); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('redis_error', $e) - ); - return false; - } - // Rules will also be recreated on log events, but rules may seem empty for a second in the UI - docker('post', 'netfilter-mailcow', 'restart'); - $fail_count = 0; - $regex_result = json_decode($redis->Get('F2B_REGEX'), true); - while (empty($regex_result) && $fail_count < 10) { - $regex_result = json_decode($redis->Get('F2B_REGEX'), true); - $fail_count++; - sleep(1); - } - if ($fail_count >= 10) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('reset_f2b_regex') - ); - return false; - } - } - elseif ($_data['action'] == "edit-regex") { - if (!empty($_data['regex'])) { - $rule_id = 1; - $regex_array = array(); - foreach($_data['regex'] as $regex) { - $regex_array[$rule_id] = $regex; - $rule_id++; - } - if (!empty($regex_array)) { - $redis->Set('F2B_REGEX', json_encode($regex_array, JSON_UNESCAPED_SLASHES)); - } - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('object_modified', htmlspecialchars($network)) - ); - return true; - } - - // Start actions in dependency of network - if (!empty($_data['network'])) { - $networks = (array)$_data['network']; - foreach ($networks as $network) { - // Unban network - if ($_data['action'] == "unban") { - if (valid_network($network)) { - try { - $redis->hSet('F2B_QUEUE_UNBAN', $network, 1); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('redis_error', $e) - ); - continue; - } - } - } - // Whitelist network - elseif ($_data['action'] == "whitelist") { - if (empty($network)) { continue; } - if (valid_network($network)) { - try { - $redis->hSet('F2B_WHITELIST', $network, 1); - $redis->hDel('F2B_BLACKLIST', $network, 1); - $redis->hSet('F2B_QUEUE_UNBAN', $network, 1); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('redis_error', $e) - ); - continue; - } - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('network_host_invalid', $network) - ); - continue; - } - } - // Blacklist network - elseif ($_data['action'] == "blacklist") { - if (empty($network)) { continue; } - if (valid_network($network) && !in_array($network, array( - '0.0.0.0', - '0.0.0.0/0', - getenv('IPV4_NETWORK') . '0/24', - getenv('IPV4_NETWORK') . '0', - getenv('IPV6_NETWORK') - ))) { - try { - $redis->hSet('F2B_BLACKLIST', $network, 1); - $redis->hDel('F2B_WHITELIST', $network, 1); - //$response = docker('post', 'netfilter-mailcow', 'restart'); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('redis_error', $e) - ); - continue; - } - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('network_host_invalid', $network) - ); - continue; - } - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('object_modified', htmlspecialchars($network)) - ); - } - return true; - } - } - // Start default edit without specific action - $is_now = fail2ban('get'); - if (!empty($is_now)) { - $ban_time = intval((isset($_data['ban_time'])) ? $_data['ban_time'] : $is_now['ban_time']); - $ban_time_increment = (isset($_data['ban_time_increment']) && $_data['ban_time_increment'] == "1") ? 1 : 0; - $max_attempts = intval((isset($_data['max_attempts'])) ? $_data['max_attempts'] : $is_now['max_attempts']); - $max_ban_time = intval((isset($_data['max_ban_time'])) ? $_data['max_ban_time'] : $is_now['max_ban_time']); - $retry_window = intval((isset($_data['retry_window'])) ? $_data['retry_window'] : $is_now['retry_window']); - $netban_ipv4 = intval((isset($_data['netban_ipv4'])) ? $_data['netban_ipv4'] : $is_now['netban_ipv4']); - $netban_ipv6 = intval((isset($_data['netban_ipv6'])) ? $_data['netban_ipv6'] : $is_now['netban_ipv6']); - $wl = (isset($_data['whitelist'])) ? $_data['whitelist'] : $is_now['whitelist']; - $bl = (isset($_data['blacklist'])) ? $_data['blacklist'] : $is_now['blacklist']; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $f2b_options = array(); - $f2b_options['ban_time'] = ($ban_time < 60) ? 60 : $ban_time; - $f2b_options['ban_time_increment'] = ($ban_time_increment == 1) ? true : false; - $f2b_options['max_ban_time'] = ($max_ban_time < 60) ? 60 : $max_ban_time; - $f2b_options['netban_ipv4'] = ($netban_ipv4 < 8) ? 8 : $netban_ipv4; - $f2b_options['netban_ipv6'] = ($netban_ipv6 < 8) ? 8 : $netban_ipv6; - $f2b_options['netban_ipv4'] = ($netban_ipv4 > 32) ? 32 : $netban_ipv4; - $f2b_options['netban_ipv6'] = ($netban_ipv6 > 128) ? 128 : $netban_ipv6; - $f2b_options['max_attempts'] = ($max_attempts < 1) ? 1 : $max_attempts; - $f2b_options['retry_window'] = ($retry_window < 1) ? 1 : $retry_window; - try { - $redis->Set('F2B_OPTIONS', json_encode($f2b_options)); - $redis->Del('F2B_WHITELIST'); - $redis->Del('F2B_BLACKLIST'); - if(!empty($wl)) { - $wl_array = array_map('trim', preg_split( "/( |,|;|\n)/", $wl)); - $wl_array = array_filter($wl_array); - if (is_array($wl_array)) { - foreach ($wl_array as $wl_item) { - if (valid_network($wl_item) || valid_hostname($wl_item)) { - $redis->hSet('F2B_WHITELIST', $wl_item, 1); - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('network_host_invalid', $wl_item) - ); - continue; - } - } - } - } - if(!empty($bl)) { - $bl_array = array_map('trim', preg_split( "/( |,|;|\n)/", $bl)); - $bl_array = array_filter($bl_array); - if (is_array($bl_array)) { - foreach ($bl_array as $bl_item) { - if (valid_network($bl_item) && !in_array($bl_item, array( - '0.0.0.0', - '0.0.0.0/0', - getenv('IPV4_NETWORK') . '0/24', - getenv('IPV4_NETWORK') . '0', - getenv('IPV6_NETWORK') - ))) { - $redis->hSet('F2B_BLACKLIST', $bl_item, 1); - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('network_host_invalid', $bl_item) - ); - continue; - } - } - } - } - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('redis_error', $e) - ); - return false; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'f2b_modified' - ); - break; - } -} +Get('F2B_OPTIONS'), true); + $f2b_options['regex'] = json_decode($redis->Get('F2B_REGEX'), true); + $wl = $redis->hGetAll('F2B_WHITELIST'); + if (is_array($wl)) { + foreach ($wl as $key => $value) { + $tmp_wl_data[] = $key; + } + if (isset($tmp_wl_data)) { + natsort($tmp_wl_data); + $f2b_options['whitelist'] = implode(PHP_EOL, (array)$tmp_wl_data); + } + else { + $f2b_options['whitelist'] = ""; + } + } + else { + $f2b_options['whitelist'] = ""; + } + $bl = $redis->hGetAll('F2B_BLACKLIST'); + if (is_array($bl)) { + foreach ($bl as $key => $value) { + $tmp_bl_data[] = $key; + } + if (isset($tmp_bl_data)) { + natsort($tmp_bl_data); + $f2b_options['blacklist'] = implode(PHP_EOL, (array)$tmp_bl_data); + } + else { + $f2b_options['blacklist'] = ""; + } + } + else { + $f2b_options['blacklist'] = ""; + } + $pb = $redis->hGetAll('F2B_PERM_BANS'); + if (is_array($pb)) { + foreach ($pb as $key => $value) { + $f2b_options['perm_bans'][] = array( + 'network'=>$key, + 'ip' => strtok($key,'/') + ); + + } + } + else { + $f2b_options['perm_bans'] = ""; + } + $active_bans = $redis->hGetAll('F2B_ACTIVE_BANS'); + $queue_unban = $redis->hGetAll('F2B_QUEUE_UNBAN'); + if (is_array($active_bans)) { + foreach ($active_bans as $network => $banned_until) { + $queued_for_unban = (isset($queue_unban[$network]) && $queue_unban[$network] == 1) ? 1 : 0; + $difference = $banned_until - time(); + $f2b_options['active_bans'][] = array( + 'queued_for_unban' => $queued_for_unban, + 'network' => $network, + 'ip' => strtok($network,'/'), + 'banned_until' => sprintf('%02dh %02dm %02ds', ($difference/3600), ($difference/60%60), $difference%60) + ); + } + } + else { + $f2b_options['active_bans'] = ""; + } + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + return $f2b_options; + break; + case 'edit': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + // Start to read actions, if any + if (isset($_data['action'])) { + // Reset regex filters + if ($_data['action'] == "reset-regex") { + try { + $redis->Del('F2B_REGEX'); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + // Rules will also be recreated on log events, but rules may seem empty for a second in the UI + docker('post', 'netfilter-mailcow', 'restart'); + $fail_count = 0; + $regex_result = json_decode($redis->Get('F2B_REGEX'), true); + while (empty($regex_result) && $fail_count < 10) { + $regex_result = json_decode($redis->Get('F2B_REGEX'), true); + $fail_count++; + sleep(1); + } + if ($fail_count >= 10) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('reset_f2b_regex') + ); + return false; + } + } + elseif ($_data['action'] == "edit-regex") { + if (!empty($_data['regex'])) { + $rule_id = 1; + $regex_array = array(); + foreach($_data['regex'] as $regex) { + $regex_array[$rule_id] = $regex; + $rule_id++; + } + if (!empty($regex_array)) { + $redis->Set('F2B_REGEX', json_encode($regex_array, JSON_UNESCAPED_SLASHES)); + } + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('object_modified', htmlspecialchars($network)) + ); + return true; + } + + // Start actions in dependency of network + if (!empty($_data['network'])) { + $networks = (array)$_data['network']; + foreach ($networks as $network) { + // Unban network + if ($_data['action'] == "unban") { + if (valid_network($network)) { + try { + $redis->hSet('F2B_QUEUE_UNBAN', $network, 1); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + continue; + } + } + } + // Whitelist network + elseif ($_data['action'] == "whitelist") { + if (empty($network)) { continue; } + if (valid_network($network)) { + try { + $redis->hSet('F2B_WHITELIST', $network, 1); + $redis->hDel('F2B_BLACKLIST', $network, 1); + $redis->hSet('F2B_QUEUE_UNBAN', $network, 1); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + continue; + } + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('network_host_invalid', $network) + ); + continue; + } + } + // Blacklist network + elseif ($_data['action'] == "blacklist") { + if (empty($network)) { continue; } + if (valid_network($network) && !in_array($network, array( + '0.0.0.0', + '0.0.0.0/0', + getenv('IPV4_NETWORK') . '0/24', + getenv('IPV4_NETWORK') . '0', + getenv('IPV6_NETWORK') + ))) { + try { + $redis->hSet('F2B_BLACKLIST', $network, 1); + $redis->hDel('F2B_WHITELIST', $network, 1); + //$response = docker('post', 'netfilter-mailcow', 'restart'); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + continue; + } + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('network_host_invalid', $network) + ); + continue; + } + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('object_modified', htmlspecialchars($network)) + ); + } + return true; + } + } + // Start default edit without specific action + $is_now = fail2ban('get'); + if (!empty($is_now)) { + $ban_time = intval((isset($_data['ban_time'])) ? $_data['ban_time'] : $is_now['ban_time']); + $ban_time_increment = (isset($_data['ban_time_increment']) && $_data['ban_time_increment'] == "1") ? 1 : 0; + $max_attempts = intval((isset($_data['max_attempts'])) ? $_data['max_attempts'] : $is_now['max_attempts']); + $max_ban_time = intval((isset($_data['max_ban_time'])) ? $_data['max_ban_time'] : $is_now['max_ban_time']); + $retry_window = intval((isset($_data['retry_window'])) ? $_data['retry_window'] : $is_now['retry_window']); + $netban_ipv4 = intval((isset($_data['netban_ipv4'])) ? $_data['netban_ipv4'] : $is_now['netban_ipv4']); + $netban_ipv6 = intval((isset($_data['netban_ipv6'])) ? $_data['netban_ipv6'] : $is_now['netban_ipv6']); + $wl = (isset($_data['whitelist'])) ? $_data['whitelist'] : $is_now['whitelist']; + $bl = (isset($_data['blacklist'])) ? $_data['blacklist'] : $is_now['blacklist']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $f2b_options = array(); + $f2b_options['ban_time'] = ($ban_time < 60) ? 60 : $ban_time; + $f2b_options['ban_time_increment'] = ($ban_time_increment == 1) ? true : false; + $f2b_options['max_ban_time'] = ($max_ban_time < 60) ? 60 : $max_ban_time; + $f2b_options['netban_ipv4'] = ($netban_ipv4 < 8) ? 8 : $netban_ipv4; + $f2b_options['netban_ipv6'] = ($netban_ipv6 < 8) ? 8 : $netban_ipv6; + $f2b_options['netban_ipv4'] = ($netban_ipv4 > 32) ? 32 : $netban_ipv4; + $f2b_options['netban_ipv6'] = ($netban_ipv6 > 128) ? 128 : $netban_ipv6; + $f2b_options['max_attempts'] = ($max_attempts < 1) ? 1 : $max_attempts; + $f2b_options['retry_window'] = ($retry_window < 1) ? 1 : $retry_window; + try { + $redis->Set('F2B_OPTIONS', json_encode($f2b_options)); + $redis->Del('F2B_WHITELIST'); + $redis->Del('F2B_BLACKLIST'); + if(!empty($wl)) { + $wl_array = array_map('trim', preg_split( "/( |,|;|\n)/", $wl)); + $wl_array = array_filter($wl_array); + if (is_array($wl_array)) { + foreach ($wl_array as $wl_item) { + if (valid_network($wl_item) || valid_hostname($wl_item)) { + $redis->hSet('F2B_WHITELIST', $wl_item, 1); + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('network_host_invalid', $wl_item) + ); + continue; + } + } + } + } + if(!empty($bl)) { + $bl_array = array_map('trim', preg_split( "/( |,|;|\n)/", $bl)); + $bl_array = array_filter($bl_array); + if (is_array($bl_array)) { + foreach ($bl_array as $bl_item) { + if (valid_network($bl_item) && !in_array($bl_item, array( + '0.0.0.0', + '0.0.0.0/0', + getenv('IPV4_NETWORK') . '0/24', + getenv('IPV4_NETWORK') . '0', + getenv('IPV6_NETWORK') + ))) { + $redis->hSet('F2B_BLACKLIST', $bl_item, 1); + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('network_host_invalid', $bl_item) + ); + continue; + } + } + } + } + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'f2b_modified' + ); + break; + } +} diff --git a/data/web/inc/functions.fwdhost.inc.php b/data/web/inc/functions.fwdhost.inc.php index d7ac2567..ab311efa 100644 --- a/data/web/inc/functions.fwdhost.inc.php +++ b/data/web/inc/functions.fwdhost.inc.php @@ -1,183 +1,183 @@ - 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $source = $_data['hostname']; - $host = trim($_data['hostname']); - $filter_spam = (isset($_data['filter_spam']) && $_data['filter_spam'] == 1) ? 1 : 0; - if (preg_match('/^[0-9a-fA-F:\/]+$/', $host)) { // IPv6 address - $hosts = array($host); - } - elseif (preg_match('/^[0-9\.\/]+$/', $host)) { // IPv4 address - $hosts = array($host); - } - else { - $hosts = get_outgoing_hosts_best_guess($host); - } - if (empty($hosts)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('invalid_host', htmlspecialchars($host)) - ); - return false; - } - foreach ($hosts as $host) { - try { - $redis->hSet('WHITELISTED_FWD_HOST', $host, $source); - if ($filter_spam == 0) { - $redis->hSet('KEEP_SPAM', $host, 1); - } - elseif ($redis->hGet('KEEP_SPAM', $host)) { - $redis->hDel('KEEP_SPAM', $host); - } - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('redis_error', $e) - ); - return false; - } - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('forwarding_host_added', htmlspecialchars(implode(', ', (array)$hosts))) - ); - break; - case 'edit': - global $lang; - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $fwdhosts = (array)$_data['fwdhost']; - foreach ($fwdhosts as $fwdhost) { - $is_now = fwdhost('details', $fwdhost); - if (!empty($is_now)) { - $keep_spam = (isset($_data['keep_spam'])) ? $_data['keep_spam'] : $is_now['keep_spam']; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - continue; - } - try { - if ($keep_spam == 1) { - $redis->hSet('KEEP_SPAM', $fwdhost, 1); - } - else { - $redis->hDel('KEEP_SPAM', $fwdhost); - } - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('redis_error', $e) - ); - continue; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('object_modified', htmlspecialchars($fwdhost)) - ); - } - break; - case 'delete': - $hosts = (array)$_data['forwardinghost']; - foreach ($hosts as $host) { - try { - $redis->hDel('WHITELISTED_FWD_HOST', $host); - $redis->hDel('KEEP_SPAM', $host); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('redis_error', $e) - ); - continue; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('forwarding_host_removed', htmlspecialchars($host)) - ); - } - break; - case 'get': - if ($_SESSION['mailcow_cc_role'] != "admin") { - return false; - } - $fwdhostsdata = array(); - try { - $fwd_hosts = $redis->hGetAll('WHITELISTED_FWD_HOST'); - if (!empty($fwd_hosts)) { - foreach ($fwd_hosts as $fwd_host => $source) { - $keep_spam = ($redis->hGet('KEEP_SPAM', $fwd_host)) ? "yes" : "no"; - $fwdhostsdata[] = array( - 'host' => $fwd_host, - 'source' => $source, - 'keep_spam' => $keep_spam - ); - } - } - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('redis_error', $e) - ); - return false; - } - return $fwdhostsdata; - break; - case 'details': - $fwdhostdetails = array(); - if (!isset($_data) || empty($_data)) { - return false; - } - try { - if ($source = $redis->hGet('WHITELISTED_FWD_HOST', $_data)) { - $fwdhostdetails['host'] = $_data; - $fwdhostdetails['source'] = $source; - $fwdhostdetails['keep_spam'] = ($redis->hGet('KEEP_SPAM', $_data)) ? "yes" : "no"; - } - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('redis_error', $e) - ); - return false; - } - return $fwdhostdetails; - break; - } -} + 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $source = $_data['hostname']; + $host = trim($_data['hostname']); + $filter_spam = (isset($_data['filter_spam']) && $_data['filter_spam'] == 1) ? 1 : 0; + if (preg_match('/^[0-9a-fA-F:\/]+$/', $host)) { // IPv6 address + $hosts = array($host); + } + elseif (preg_match('/^[0-9\.\/]+$/', $host)) { // IPv4 address + $hosts = array($host); + } + else { + $hosts = get_outgoing_hosts_best_guess($host); + } + if (empty($hosts)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('invalid_host', htmlspecialchars($host)) + ); + return false; + } + foreach ($hosts as $host) { + try { + $redis->hSet('WHITELISTED_FWD_HOST', $host, $source); + if ($filter_spam == 0) { + $redis->hSet('KEEP_SPAM', $host, 1); + } + elseif ($redis->hGet('KEEP_SPAM', $host)) { + $redis->hDel('KEEP_SPAM', $host); + } + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('forwarding_host_added', htmlspecialchars(implode(', ', (array)$hosts))) + ); + break; + case 'edit': + global $lang; + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $fwdhosts = (array)$_data['fwdhost']; + foreach ($fwdhosts as $fwdhost) { + $is_now = fwdhost('details', $fwdhost); + if (!empty($is_now)) { + $keep_spam = (isset($_data['keep_spam'])) ? $_data['keep_spam'] : $is_now['keep_spam']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + continue; + } + try { + if ($keep_spam == 1) { + $redis->hSet('KEEP_SPAM', $fwdhost, 1); + } + else { + $redis->hDel('KEEP_SPAM', $fwdhost); + } + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + continue; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('object_modified', htmlspecialchars($fwdhost)) + ); + } + break; + case 'delete': + $hosts = (array)$_data['forwardinghost']; + foreach ($hosts as $host) { + try { + $redis->hDel('WHITELISTED_FWD_HOST', $host); + $redis->hDel('KEEP_SPAM', $host); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + continue; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('forwarding_host_removed', htmlspecialchars($host)) + ); + } + break; + case 'get': + if ($_SESSION['mailcow_cc_role'] != "admin") { + return false; + } + $fwdhostsdata = array(); + try { + $fwd_hosts = $redis->hGetAll('WHITELISTED_FWD_HOST'); + if (!empty($fwd_hosts)) { + foreach ($fwd_hosts as $fwd_host => $source) { + $keep_spam = ($redis->hGet('KEEP_SPAM', $fwd_host)) ? "yes" : "no"; + $fwdhostsdata[] = array( + 'host' => $fwd_host, + 'source' => $source, + 'keep_spam' => $keep_spam + ); + } + } + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + return $fwdhostsdata; + break; + case 'details': + $fwdhostdetails = array(); + if (!isset($_data) || empty($_data)) { + return false; + } + try { + if ($source = $redis->hGet('WHITELISTED_FWD_HOST', $_data)) { + $fwdhostdetails['host'] = $_data; + $fwdhostdetails['source'] = $source; + $fwdhostdetails['keep_spam'] = ($redis->hGet('KEEP_SPAM', $_data)) ? "yes" : "no"; + } + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + return $fwdhostdetails; + break; + } +} diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index de1855fa..1ba1b7ee 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -1,2405 +1,2405 @@ -1) { - $bits=(int)($ar[1]); - } - else { - $bits = $iplen * 8; - } - for ($c=0; $bits>0; $c++) { - $bytemask = ($bits < 8) ? 0xff ^ ((1 << (8-$bits))-1) : 0xff; - if (((ord($ipb[$c]) ^ ord($ip1b[$c])) & $bytemask) != 0) { - continue 2; - } - $bits-=8; - } - return true; - } - return false; -} -function hash_password($password) { - // default_pass_scheme is determined in vars.inc.php (or corresponding local file) - // in case default pass scheme is not defined, falling back to BLF-CRYPT. - global $default_pass_scheme; - $pw_hash = NULL; - // support pre-hashed passwords - if (preg_match('/^{(ARGON2I|ARGON2ID|BLF-CRYPT|CLEAR|CLEARTEXT|CRYPT|DES-CRYPT|LDAP-MD5|MD5|MD5-CRYPT|PBKDF2|PLAIN|PLAIN-MD4|PLAIN-MD5|PLAIN-TRUNC|PLAIN-TRUNC|SHA|SHA1|SHA256|SHA256-CRYPT|SHA512|SHA512-CRYPT|SMD5|SSHA|SSHA256|SSHA512)}/i', $password)) { - $pw_hash = $password; - } - else { - switch (strtoupper($default_pass_scheme)) { - case "SSHA": - $salt_str = bin2hex(openssl_random_pseudo_bytes(8)); - $pw_hash = "{SSHA}".base64_encode(hash('sha1', $password . $salt_str, true) . $salt_str); - break; - case "SSHA256": - $salt_str = bin2hex(openssl_random_pseudo_bytes(8)); - $pw_hash = "{SSHA256}".base64_encode(hash('sha256', $password . $salt_str, true) . $salt_str); - break; - case "SSHA512": - $salt_str = bin2hex(openssl_random_pseudo_bytes(8)); - $pw_hash = "{SSHA512}".base64_encode(hash('sha512', $password . $salt_str, true) . $salt_str); - break; - case "BLF-CRYPT": - default: - $pw_hash = "{BLF-CRYPT}" . password_hash($password, PASSWORD_BCRYPT); - break; - } - } - return $pw_hash; -} -function password_complexity($_action, $_data = null) { - global $redis; - global $lang; - switch ($_action) { - case 'edit': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'access_denied' - ); - return false; - } - $is_now = password_complexity('get'); - if (!empty($is_now)) { - $length = (isset($_data['length']) && intval($_data['length']) >= 3) ? intval($_data['length']) : $is_now['length']; - $chars = (isset($_data['chars'])) ? intval($_data['chars']) : $is_now['chars']; - $lowerupper = (isset($_data['lowerupper'])) ? intval($_data['lowerupper']) : $is_now['lowerupper']; - $special_chars = (isset($_data['special_chars'])) ? intval($_data['special_chars']) : $is_now['special_chars']; - $numbers = (isset($_data['numbers'])) ? intval($_data['numbers']) : $is_now['numbers']; - } - try { - $redis->hMSet('PASSWD_POLICY', [ - 'length' => $length, - 'chars' => $chars, - 'special_chars' => $special_chars, - 'lowerupper' => $lowerupper, - 'numbers' => $numbers - ]); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => array('redis_error', $e) - ); - return false; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'password_policy_saved' - ); - break; - case 'get': - try { - $length = $redis->hGet('PASSWD_POLICY', 'length'); - $chars = $redis->hGet('PASSWD_POLICY', 'chars'); - $special_chars = $redis->hGet('PASSWD_POLICY', 'special_chars'); - $lowerupper = $redis->hGet('PASSWD_POLICY', 'lowerupper'); - $numbers = $redis->hGet('PASSWD_POLICY', 'numbers'); - return array( - 'length' => $length, - 'chars' => $chars, - 'special_chars' => $special_chars, - 'lowerupper' => $lowerupper, - 'numbers' => $numbers - ); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => array('redis_error', $e) - ); - return false; - } - return false; - break; - case 'html': - $policies = password_complexity('get'); - foreach ($policies as $name => $value) { - if ($value != 0) { - $policy_text[] = sprintf($lang['admin']["password_policy_$name"], $value); - } - } - return '

- ' . implode('
- ', (array)$policy_text) . '

'; - break; - } -} -function password_check($password1, $password2) { - $password_complexity = password_complexity('get'); - - if (empty($password1) || empty($password2)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type), - 'msg' => 'password_complexity' - ); - return false; - } - - if ($password1 != $password2) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type), - 'msg' => 'password_mismatch' - ); - return false; - } - - $given_password['length'] = strlen($password1); - $given_password['special_chars'] = preg_match('/[^a-zA-Z\d]/', $password1); - $given_password['chars'] = preg_match('/[a-zA-Z]/',$password1); - $given_password['numbers'] = preg_match('/\d/', $password1); - $lower = strlen(preg_replace("/[^a-z]/", '', $password1)); - $upper = strlen(preg_replace("/[^A-Z]/", '', $password1)); - $given_password['lowerupper'] = ($lower > 0 && $upper > 0) ? true : false; - - if ( - ($given_password['length'] < $password_complexity['length']) || - ($password_complexity['special_chars'] == 1 && (intval($given_password['special_chars']) != $password_complexity['special_chars'])) || - ($password_complexity['chars'] == 1 && (intval($given_password['chars']) != $password_complexity['chars'])) || - ($password_complexity['numbers'] == 1 && (intval($given_password['numbers']) != $password_complexity['numbers'])) || - ($password_complexity['lowerupper'] == 1 && (intval($given_password['lowerupper']) != $password_complexity['lowerupper'])) - ) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type), - 'msg' => 'password_complexity' - ); - return false; - } - - return true; -} -function last_login($action, $username, $sasl_limit_days = 7, $ui_offset = 1) { - global $pdo; - global $redis; - $sasl_limit_days = intval($sasl_limit_days); - switch ($action) { - case 'get': - if (filter_var($username, FILTER_VALIDATE_EMAIL) && hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { - $stmt = $pdo->prepare('SELECT `real_rip`, MAX(`datetime`) as `datetime`, `service`, `app_password`, MAX(`app_passwd`.`name`) as `app_password_name` FROM `sasl_log` - LEFT OUTER JOIN `app_passwd` on `sasl_log`.`app_password` = `app_passwd`.`id` - WHERE `username` = :username - AND HOUR(TIMEDIFF(NOW(), `datetime`)) < :sasl_limit_days - GROUP BY `real_rip`, `service`, `app_password` - ORDER BY `datetime` DESC;'); - $stmt->execute(array(':username' => $username, ':sasl_limit_days' => ($sasl_limit_days * 24))); - $sasl = $stmt->fetchAll(PDO::FETCH_ASSOC); - foreach ($sasl as $k => $v) { - if (!filter_var($sasl[$k]['real_rip'], FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { - $sasl[$k]['real_rip'] = 'Web/EAS/Internal (' . $sasl[$k]['real_rip'] . ')'; - } - elseif (filter_var($sasl[$k]['real_rip'], FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { - try { - $sasl[$k]['location'] = $redis->hGet('IP_SHORTCOUNTRY', $sasl[$k]['real_rip']); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('redis_error', $e) - ); - return false; - } - if (!$sasl[$k]['location']) { - $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL,"https://dfdata.bella.network/lookup/" . $sasl[$k]['real_rip']); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); - curl_setopt($curl, CURLOPT_USERAGENT, 'Moocow'); - curl_setopt($curl, CURLOPT_TIMEOUT, 5); - $ip_data = curl_exec($curl); - if (!curl_errno($curl)) { - $ip_data_array = json_decode($ip_data, true); - if ($ip_data_array !== false and !empty($ip_data_array['location']['shortcountry'])) { - $sasl[$k]['location'] = $ip_data_array['location']['shortcountry']; - try { - $redis->hSet('IP_SHORTCOUNTRY', $sasl[$k]['real_rip'], $ip_data_array['location']['shortcountry']); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('redis_error', $e) - ); - curl_close($curl); - return false; - } - } - } - curl_close($curl); - } - } - } - } - else { - $sasl = array(); - } - if ($_SESSION['mailcow_cc_role'] == "admin" || $username == $_SESSION['mailcow_cc_username']) { - $stmt = $pdo->prepare('SELECT `remote`, `time` FROM `logs` - WHERE JSON_EXTRACT(`call`, "$[0]") = "check_login" - AND JSON_EXTRACT(`call`, "$[1]") = :username - AND `type` = "success" ORDER BY `time` DESC LIMIT 1 OFFSET :offset'); - $stmt->execute(array( - ':username' => $username, - ':offset' => $ui_offset - )); - $ui = $stmt->fetch(PDO::FETCH_ASSOC); - } - else { - $ui = array(); - } - - return array('ui' => $ui, 'sasl' => $sasl); - break; - case 'reset': - if (filter_var($username, FILTER_VALIDATE_EMAIL) && hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { - $stmt = $pdo->prepare('DELETE FROM `sasl_log` - WHERE `username` = :username'); - $stmt->execute(array(':username' => $username)); - } - if ($_SESSION['mailcow_cc_role'] == "admin" || $username == $_SESSION['mailcow_cc_username']) { - $stmt = $pdo->prepare('DELETE FROM `logs` - WHERE JSON_EXTRACT(`call`, "$[0]") = "check_login" - AND JSON_EXTRACT(`call`, "$[1]") = :username - AND `type` = "success"'); - $stmt->execute(array(':username' => $username)); - } - return true; - break; - } - -} -function flush_memcached() { - try { - $m = new Memcached(); - $m->addServer('memcached', 11211); - $m->flush(); - } - catch ( Exception $e ) { - // Dunno - } -} -function sys_mail($_data) { - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__), - 'msg' => 'access_denied' - ); - return false; - } - $excludes = $_data['mass_exclude']; - $includes = $_data['mass_include']; - $mailboxes = array(); - $mass_from = $_data['mass_from']; - $mass_text = $_data['mass_text']; - $mass_html = $_data['mass_html']; - $mass_subject = $_data['mass_subject']; - if (!filter_var($mass_from, FILTER_VALIDATE_EMAIL)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__), - 'msg' => 'from_invalid' - ); - return false; - } - if (empty($mass_subject)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__), - 'msg' => 'subject_empty' - ); - return false; - } - if (empty($mass_text)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__), - 'msg' => 'text_empty' - ); - return false; - } - $domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains')); - foreach ($domains as $domain) { - foreach (mailbox('get', 'mailboxes', $domain) as $mailbox) { - $mailboxes[] = $mailbox; - } - } - if (!empty($includes)) { - $rcpts = array_intersect($mailboxes, $includes); - } - elseif (!empty($excludes)) { - $rcpts = array_diff($mailboxes, $excludes); - } - else { - $rcpts = $mailboxes; - } - if (!empty($rcpts)) { - ini_set('max_execution_time', 0); - ini_set('max_input_time', 0); - $mail = new PHPMailer; - $mail->Timeout = 10; - $mail->SMTPOptions = array( - 'ssl' => array( - 'verify_peer' => false, - 'verify_peer_name' => false, - 'allow_self_signed' => true - ) - ); - $mail->isSMTP(); - $mail->Host = 'dovecot-mailcow'; - $mail->SMTPAuth = false; - $mail->Port = 24; - $mail->setFrom($mass_from); - $mail->Subject = $mass_subject; - $mail->CharSet ="UTF-8"; - if (!empty($mass_html)) { - $mail->Body = $mass_html; - $mail->AltBody = $mass_text; - } - else { - $mail->Body = $mass_text; - } - $mail->XMailer = 'MooMassMail'; - foreach ($rcpts as $rcpt) { - $mail->AddAddress($rcpt); - if (!$mail->send()) { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__), - 'msg' => 'Mailer error (RCPT "' . htmlspecialchars($rcpt) . '"): ' . str_replace('https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting', '', $mail->ErrorInfo) - ); - } - $mail->ClearAllRecipients(); - } - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__), - 'msg' => 'Mass mail job completed, sent ' . count($rcpts) . ' mails' - ); -} -function logger($_data = false) { - /* - logger() will be called as last function - To manually log a message, logger needs to be called like below. - - logger(array( - 'return' => array( - array( - 'type' => 'danger', - 'log' => array(__FUNCTION__), - 'msg' => $err - ) - ) - )); - - These messages will not be printed as alert box. - To do so, push them to $_SESSION['return'] and do not call logger as they will be included automatically: - - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $user, '*'), - 'msg' => $err - ); - */ - global $pdo; - if (!$_data) { - $_data = $_SESSION; - } - if (!empty($_data['return'])) { - $task = substr(strtoupper(md5(uniqid(rand(), true))), 0, 6); - foreach ($_data['return'] as $return) { - $type = $return['type']; - $msg = null; - if (isset($return['msg'])) { - $msg = json_encode($return['msg'], JSON_UNESCAPED_UNICODE); - } - $call = null; - if (isset($return['log'])) { - $call = json_encode($return['log'], JSON_UNESCAPED_UNICODE); - } - if (!empty($_SESSION["dual-login"]["username"])) { - $user = $_SESSION["dual-login"]["username"] . ' => ' . $_SESSION['mailcow_cc_username']; - $role = $_SESSION["dual-login"]["role"] . ' => ' . $_SESSION['mailcow_cc_role']; - } - elseif (!empty($_SESSION['mailcow_cc_username'])) { - $user = $_SESSION['mailcow_cc_username']; - $role = $_SESSION['mailcow_cc_role']; - } - else { - $user = 'unauthenticated'; - $role = 'unauthenticated'; - } - // We cannot log when logs is missing... - try { - $stmt = $pdo->prepare("INSERT INTO `logs` (`type`, `task`, `msg`, `call`, `user`, `role`, `remote`, `time`) VALUES - (:type, :task, :msg, :call, :user, :role, :remote, UNIX_TIMESTAMP())"); - $stmt->execute(array( - ':type' => $type, - ':task' => $task, - ':call' => $call, - ':msg' => $msg, - ':user' => $user, - ':role' => $role, - ':remote' => get_remote_ip() - )); - } - catch (Exception $e) { - // Do nothing - } - } - } - else { - return true; - } -} -function hasDomainAccess($username, $role, $domain) { - global $pdo; - if (!filter_var($username, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) { - return false; - } - if (empty($domain) || !is_valid_domain_name($domain)) { - return false; - } - if ($role != 'admin' && $role != 'domainadmin') { - return false; - } - if ($role == 'admin') { - $stmt = $pdo->prepare("SELECT `domain` FROM `domain` - WHERE `domain` = :domain"); - $stmt->execute(array(':domain' => $domain)); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` - WHERE `alias_domain` = :domain"); - $stmt->execute(array(':domain' => $domain)); - $num_results = $num_results + count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - return true; - } - } - elseif ($role == 'domainadmin') { - $stmt = $pdo->prepare("SELECT `domain` FROM `domain_admins` - WHERE ( - `active`='1' - AND `username` = :username - AND (`domain` = :domain1 OR `domain` = (SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain2)) - )"); - $stmt->execute(array(':username' => $username, ':domain1' => $domain, ':domain2' => $domain)); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if (!empty($num_results)) { - return true; - } - } - return false; -} -function hasMailboxObjectAccess($username, $role, $object) { - global $pdo; - if (empty($username) || empty($role) || empty($object)) { - return false; - } - if (!filter_var(html_entity_decode(rawurldecode($username)), FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) { - return false; - } - if ($role != 'admin' && $role != 'domainadmin' && $role != 'user') { - return false; - } - if ($username == $object) { - return true; - } - $stmt = $pdo->prepare("SELECT `domain` FROM `mailbox` WHERE `username` = :object"); - $stmt->execute(array(':object' => $object)); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (isset($row['domain']) && hasDomainAccess($username, $role, $row['domain'])) { - return true; - } - return false; -} -// does also verify mailboxes as a mailbox is a alias == goto -function hasAliasObjectAccess($username, $role, $object) { - global $pdo; - if (empty($username) || empty($role) || empty($object)) { - return false; - } - if (!filter_var(html_entity_decode(rawurldecode($username)), FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) { - return false; - } - if ($role != 'admin' && $role != 'domainadmin' && $role != 'user') { - return false; - } - $stmt = $pdo->prepare("SELECT `domain` FROM `alias` WHERE `address` = :object"); - $stmt->execute(array(':object' => $object)); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (isset($row['domain']) && hasDomainAccess($username, $role, $row['domain'])) { - return true; - } - return false; -} -function pem_to_der($pem_key) { - // Need to remove BEGIN/END PUBLIC KEY - $lines = explode("\n", trim($pem_key)); - unset($lines[count($lines)-1]); - unset($lines[0]); - return base64_decode(implode('', $lines)); -} -function expand_ipv6($ip) { - $hex = unpack("H*hex", inet_pton($ip)); - $ip = substr(preg_replace("/([A-f0-9]{4})/", "$1:", $hex['hex']), 0, -1); - return $ip; -} -function generate_tlsa_digest($hostname, $port, $starttls = null) { - if (!is_valid_domain_name($hostname)) { - return "Not a valid hostname"; - } - if (empty($starttls)) { - $context = stream_context_create(array("ssl" => array("capture_peer_cert" => true, 'verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true))); - $stream = stream_socket_client('ssl://' . $hostname . ':' . $port, $error_nr, $error_msg, 5, STREAM_CLIENT_CONNECT, $context); - if (!$stream) { - $error_msg = isset($error_msg) ? $error_msg : '-'; - return $error_nr . ': ' . $error_msg; - } - } - else { - $stream = stream_socket_client('tcp://' . $hostname . ':' . $port, $error_nr, $error_msg, 5); - if (!$stream) { - return $error_nr . ': ' . $error_msg; - } - $banner = fread($stream, 512 ); - if (preg_match("/^220/i", $banner)) { // SMTP - fwrite($stream,"HELO tlsa.generator.local\r\n"); - fread($stream, 512); - fwrite($stream,"STARTTLS\r\n"); - fread($stream, 512); - } - elseif (preg_match("/imap.+starttls/i", $banner)) { // IMAP - fwrite($stream,"A1 STARTTLS\r\n"); - fread($stream, 512); - } - elseif (preg_match("/^\+OK/", $banner)) { // POP3 - fwrite($stream,"STLS\r\n"); - fread($stream, 512); - } - elseif (preg_match("/^OK/m", $banner)) { // Sieve - fwrite($stream,"STARTTLS\r\n"); - fread($stream, 512); - } - else { - return 'Unknown banner: "' . htmlspecialchars(trim($banner)) . '"'; - } - // Upgrade connection - stream_set_blocking($stream, true); - stream_context_set_option($stream, 'ssl', 'capture_peer_cert', true); - stream_context_set_option($stream, 'ssl', 'verify_peer', false); - stream_context_set_option($stream, 'ssl', 'verify_peer_name', false); - stream_context_set_option($stream, 'ssl', 'allow_self_signed', true); - stream_socket_enable_crypto($stream, true, STREAM_CRYPTO_METHOD_ANY_CLIENT); - stream_set_blocking($stream, false); - } - $params = stream_context_get_params($stream); - if (!empty($params['options']['ssl']['peer_certificate'])) { - $key_resource = openssl_pkey_get_public($params['options']['ssl']['peer_certificate']); - // We cannot get ['rsa']['n'], the binary data would contain BEGIN/END PUBLIC KEY - $key_data = openssl_pkey_get_details($key_resource)['key']; - return '3 1 1 ' . openssl_digest(pem_to_der($key_data), 'sha256'); - } - else { - return 'Error: Cannot read peer certificate'; - } -} -function alertbox_log_parser($_data) { - global $lang; - if (isset($_data['return'])) { - foreach ($_data['return'] as $return) { - // Get type - $type = $return['type']; - // If a lang[type][msg] string exists, use it as message - if (isset($return['type']) && isset($return['msg']) && !is_array($return['msg'])) { - if (isset($lang[$return['type']][$return['msg']])) { - $msg = $lang[$return['type']][$return['msg']]; - } - else { - $msg = $return['msg']; - } - } - // If msg is an array, use first element as language string and run printf on it with remaining array elements - elseif (is_array($return['msg'])) { - $msg = array_shift($return['msg']); - $msg = vsprintf( - $lang[$return['type']][$msg], - $return['msg'] - ); - } - else { - $msg = '-'; - } - $log_array[] = array('msg' => $msg, 'type' => json_encode($type)); - } - if (!empty($log_array)) { - return $log_array; - } - } - return false; -} -function verify_salted_hash($hash, $password, $algo, $salt_length) { - // Decode hash - $dhash = base64_decode($hash); - // Get first n bytes of binary which equals a SSHA hash - $ohash = substr($dhash, 0, $salt_length); - // Remove SSHA hash from decoded hash to get original salt string - $osalt = str_replace($ohash, '', $dhash); - // Check single salted SSHA hash against extracted hash - if (hash_equals(hash($algo, $password . $osalt, true), $ohash)) { - return true; - } - return false; -} -function verify_hash($hash, $password) { - if (preg_match('/^{(.+)}(.+)/i', $hash, $hash_array)) { - $scheme = strtoupper($hash_array[1]); - $hash = $hash_array[2]; - switch ($scheme) { - case "ARGON2I": - case "ARGON2ID": - case "BLF-CRYPT": - case "CRYPT": - case "DES-CRYPT": - case "MD5-CRYPT": - case "MD5": - case "SHA256-CRYPT": - case "SHA512-CRYPT": - return password_verify($password, $hash); - - case "CLEAR": - case "CLEARTEXT": - case "PLAIN": - return $password == $hash; - - case "LDAP-MD5": - $hash = base64_decode($hash); - return hash_equals(hash('md5', $password, true), $hash); - - case "PBKDF2": - $components = explode('$', $hash); - $salt = $components[2]; - $rounds = $components[3]; - $hash = $components[4]; - return hash_equals(hash_pbkdf2('sha1', $password, $salt, $rounds), $hash); - - case "PLAIN-MD4": - return hash_equals(hash('md4', $password), $hash); - - case "PLAIN-MD5": - return md5($password) == $hash; - - case "PLAIN-TRUNC": - $components = explode('-', $hash); - if (count($components) > 1) { - $trunc_len = $components[0]; - $trunc_password = $components[1]; - - return substr($password, 0, $trunc_len) == $trunc_password; - } else { - return $password == $hash; - } - - case "SHA": - case "SHA1": - case "SHA256": - case "SHA512": - // SHA is an alias for SHA1 - $scheme = $scheme == "SHA" ? "sha1" : strtolower($scheme); - $hash = base64_decode($hash); - return hash_equals(hash($scheme, $password, true), $hash); - - case "SMD5": - return verify_salted_hash($hash, $password, 'md5', 16); - - case "SSHA": - return verify_salted_hash($hash, $password, 'sha1', 20); - - case "SSHA256": - return verify_salted_hash($hash, $password, 'sha256', 32); - - case "SSHA512": - return verify_salted_hash($hash, $password, 'sha512', 64); - - default: - return false; - } - } - return false; -} -function check_login($user, $pass, $app_passwd_data = false) { - global $pdo; - global $redis; - global $imap_server; - - if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $user, '*'), - 'msg' => 'malformed_username' - ); - return false; - } - - // Validate admin - $user = strtolower(trim($user)); - $stmt = $pdo->prepare("SELECT `password` FROM `admin` - WHERE `superadmin` = '1' - AND `active` = '1' - AND `username` = :user"); - $stmt->execute(array(':user' => $user)); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - foreach ($rows as $row) { - // verify password - if (verify_hash($row['password'], $pass)) { - // check for tfa authenticators - $authenticators = get_tfa($user); - if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) { - // active tfa authenticators found, set pending user login - $_SESSION['pending_mailcow_cc_username'] = $user; - $_SESSION['pending_mailcow_cc_role'] = "admin"; - $_SESSION['pending_tfa_methods'] = $authenticators['additional']; - unset($_SESSION['ldelay']); - $_SESSION['return'][] = array( - 'type' => 'info', - 'log' => array(__FUNCTION__, $user, '*'), - 'msg' => 'awaiting_tfa_confirmation' - ); - return "pending"; - } else { - unset($_SESSION['ldelay']); - // Reactivate TFA if it was set to "deactivate TFA for next login" - $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user"); - $stmt->execute(array(':user' => $user)); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $user, '*'), - 'msg' => array('logged_in_as', $user) - ); - return "admin"; - } - } - } - - // Validate domain admin - $stmt = $pdo->prepare("SELECT `password` FROM `admin` - WHERE `superadmin` = '0' - AND `active`='1' - AND `username` = :user"); - $stmt->execute(array(':user' => $user)); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - foreach ($rows as $row) { - // verify password - if (verify_hash($row['password'], $pass) !== false) { - // check for tfa authenticators - $authenticators = get_tfa($user); - if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) { - $_SESSION['pending_mailcow_cc_username'] = $user; - $_SESSION['pending_mailcow_cc_role'] = "domainadmin"; - $_SESSION['pending_tfa_methods'] = $authenticators['additional']; - unset($_SESSION['ldelay']); - $_SESSION['return'][] = array( - 'type' => 'info', - 'log' => array(__FUNCTION__, $user, '*'), - 'msg' => 'awaiting_tfa_confirmation' - ); - return "pending"; - } - else { - unset($_SESSION['ldelay']); - // Reactivate TFA if it was set to "deactivate TFA for next login" - $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user"); - $stmt->execute(array(':user' => $user)); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $user, '*'), - 'msg' => array('logged_in_as', $user) - ); - return "domainadmin"; - } - } - } - - // Validate mailbox user - $stmt = $pdo->prepare("SELECT `password` FROM `mailbox` - INNER JOIN domain on mailbox.domain = domain.domain - WHERE `kind` NOT REGEXP 'location|thing|group' - AND `mailbox`.`active`='1' - AND `domain`.`active`='1' - AND `username` = :user"); - $stmt->execute(array(':user' => $user)); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - if ($app_passwd_data['eas'] === true) { - $stmt = $pdo->prepare("SELECT `app_passwd`.`password` as `password`, `app_passwd`.`id` as `app_passwd_id` FROM `app_passwd` - INNER JOIN `mailbox` ON `mailbox`.`username` = `app_passwd`.`mailbox` - INNER JOIN `domain` ON `mailbox`.`domain` = `domain`.`domain` - WHERE `mailbox`.`kind` NOT REGEXP 'location|thing|group' - AND `mailbox`.`active` = '1' - AND `domain`.`active` = '1' - AND `app_passwd`.`active` = '1' - AND `app_passwd`.`eas_access` = '1' - AND `app_passwd`.`mailbox` = :user"); - $stmt->execute(array(':user' => $user)); - $rows = array_merge($rows, $stmt->fetchAll(PDO::FETCH_ASSOC)); - } - elseif ($app_passwd_data['dav'] === true) { - $stmt = $pdo->prepare("SELECT `app_passwd`.`password` as `password`, `app_passwd`.`id` as `app_passwd_id` FROM `app_passwd` - INNER JOIN `mailbox` ON `mailbox`.`username` = `app_passwd`.`mailbox` - INNER JOIN `domain` ON `mailbox`.`domain` = `domain`.`domain` - WHERE `mailbox`.`kind` NOT REGEXP 'location|thing|group' - AND `mailbox`.`active` = '1' - AND `domain`.`active` = '1' - AND `app_passwd`.`active` = '1' - AND `app_passwd`.`dav_access` = '1' - AND `app_passwd`.`mailbox` = :user"); - $stmt->execute(array(':user' => $user)); - $rows = array_merge($rows, $stmt->fetchAll(PDO::FETCH_ASSOC)); - } - foreach ($rows as $row) { - // verify password - if (verify_hash($row['password'], $pass) !== false) { - if (!array_key_exists("app_passwd_id", $row)){ - // password is not a app password - // check for tfa authenticators - $authenticators = get_tfa($user); - if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && - $app_passwd_data['eas'] !== true && $app_passwd_data['dav'] !== true) { - // authenticators found, init TFA flow - $_SESSION['pending_mailcow_cc_username'] = $user; - $_SESSION['pending_mailcow_cc_role'] = "user"; - $_SESSION['pending_tfa_methods'] = $authenticators['additional']; - unset($_SESSION['ldelay']); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $user, '*'), - 'msg' => array('logged_in_as', $user) - ); - return "pending"; - } else if (!isset($authenticators['additional']) || !is_array($authenticators['additional']) || count($authenticators['additional']) == 0) { - // no authenticators found, login successfull - // Reactivate TFA if it was set to "deactivate TFA for next login" - $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user"); - $stmt->execute(array(':user' => $user)); - - unset($_SESSION['ldelay']); - return "user"; - } - } elseif ($app_passwd_data['eas'] === true || $app_passwd_data['dav'] === true) { - // password is a app password - $service = ($app_passwd_data['eas'] === true) ? 'EAS' : 'DAV'; - $stmt = $pdo->prepare("REPLACE INTO sasl_log (`service`, `app_password`, `username`, `real_rip`) VALUES (:service, :app_id, :username, :remote_addr)"); - $stmt->execute(array( - ':service' => $service, - ':app_id' => $row['app_passwd_id'], - ':username' => $user, - ':remote_addr' => ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR']) - )); - - unset($_SESSION['ldelay']); - return "user"; - } - } - } - - if (!isset($_SESSION['ldelay'])) { - $_SESSION['ldelay'] = "0"; - $redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']); - error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']); - } - elseif (!isset($_SESSION['mailcow_cc_username'])) { - $_SESSION['ldelay'] = $_SESSION['ldelay']+0.5; - $redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']); - error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']); - } - - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $user, '*'), - 'msg' => 'login_failed' - ); - - sleep($_SESSION['ldelay']); - return false; -} -function formatBytes($size, $precision = 2) { - if(!is_numeric($size)) { - return "0"; - } - $base = log($size, 1024); - $suffixes = array(' Byte', ' KiB', ' MiB', ' GiB', ' TiB'); - if ($size == "0") { - return "0"; - } - return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)]; -} -function update_sogo_static_view() { - if (getenv('SKIP_SOGO') == "y") { - return true; - } - global $pdo; - global $lang; - $stmt = $pdo->query("SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES - WHERE TABLE_NAME = 'sogo_view'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $stmt = $pdo->query("REPLACE INTO _sogo_static_view (`c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings`) - SELECT `c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings` from sogo_view"); - $stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');"); - } - flush_memcached(); -} -function edit_user_account($_data) { - global $lang; - global $pdo; - $_data_log = $_data; - !isset($_data_log['user_new_pass']) ?: $_data_log['user_new_pass'] = '*'; - !isset($_data_log['user_new_pass2']) ?: $_data_log['user_new_pass2'] = '*'; - !isset($_data_log['user_old_pass']) ?: $_data_log['user_old_pass'] = '*'; - $username = $_SESSION['mailcow_cc_username']; - $role = $_SESSION['mailcow_cc_role']; - $password_old = $_data['user_old_pass']; - if (filter_var($username, FILTER_VALIDATE_EMAIL === false) || $role != 'user') { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $stmt = $pdo->prepare("SELECT `password` FROM `mailbox` - WHERE `kind` NOT REGEXP 'location|thing|group' - AND `username` = :user"); - $stmt->execute(array(':user' => $username)); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (!verify_hash($row['password'], $password_old)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - if (!empty($_data['user_new_pass']) && !empty($_data['user_new_pass2'])) { - $password_new = $_data['user_new_pass']; - $password_new2 = $_data['user_new_pass2']; - if (password_check($password_new, $password_new2) !== true) { - return false; - } - $password_hashed = hash_password($password_new); - $stmt = $pdo->prepare("UPDATE `mailbox` SET `password` = :password_hashed, - `attributes` = JSON_SET(`attributes`, '$.force_pw_update', '0'), - `attributes` = JSON_SET(`attributes`, '$.passwd_update', NOW()) - WHERE `username` = :username"); - $stmt->execute(array( - ':password_hashed' => $password_hashed, - ':username' => $username - )); - } - update_sogo_static_view(); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => array('mailbox_modified', htmlspecialchars($username)) - ); -} -function user_get_alias_details($username) { - global $pdo; - global $lang; - $data['direct_aliases'] = array(); - $data['shared_aliases'] = array(); - if ($_SESSION['mailcow_cc_role'] == "user") { - $username = $_SESSION['mailcow_cc_username']; - } - if (!filter_var($username, FILTER_VALIDATE_EMAIL)) { - return false; - } - if (!hasMailboxObjectAccess($username, $_SESSION['mailcow_cc_role'], $username)) { - return false; - } - $data['address'] = $username; - $stmt = $pdo->prepare("SELECT `address` AS `shared_aliases`, `public_comment` FROM `alias` - WHERE `goto` REGEXP :username_goto - AND `address` NOT LIKE '@%' - AND `goto` != :username_goto2 - AND `address` != :username_address"); - $stmt->execute(array( - ':username_goto' => '(^|,)'.$username.'($|,)', - ':username_goto2' => $username, - ':username_address' => $username - )); - $run = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($row = array_shift($run)) { - $data['shared_aliases'][$row['shared_aliases']]['public_comment'] = htmlspecialchars($row['public_comment']); - //$data['shared_aliases'][] = $row['shared_aliases']; - } - - $stmt = $pdo->prepare("SELECT `address` AS `direct_aliases`, `public_comment` FROM `alias` - WHERE `goto` = :username_goto - AND `address` NOT LIKE '@%' - AND `address` != :username_address"); - $stmt->execute( - array( - ':username_goto' => $username, - ':username_address' => $username - )); - $run = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($row = array_shift($run)) { - $data['direct_aliases'][$row['direct_aliases']]['public_comment'] = htmlspecialchars($row['public_comment']); - } - $stmt = $pdo->prepare("SELECT CONCAT(local_part, '@', alias_domain) AS `ad_alias`, `alias_domain` FROM `mailbox` - LEFT OUTER JOIN `alias_domain` on `target_domain` = `domain` - WHERE `username` = :username ;"); - $stmt->execute(array(':username' => $username)); - $run = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($row = array_shift($run)) { - if (empty($row['ad_alias'])) { - continue; - } - $data['direct_aliases'][$row['ad_alias']]['public_comment'] = $lang['add']['alias_domain']; - $data['alias_domains'][] = $row['alias_domain']; - } - $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`send_as` SEPARATOR ', '), '') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :username AND `send_as` NOT LIKE '@%';"); - $stmt->execute(array(':username' => $username)); - $run = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($row = array_shift($run)) { - $data['aliases_also_send_as'] = $row['send_as']; - } - $stmt = $pdo->prepare("SELECT CONCAT_WS(', ', IFNULL(GROUP_CONCAT(DISTINCT `send_as` SEPARATOR ', '), ''), GROUP_CONCAT(DISTINCT CONCAT('@',`alias_domain`) SEPARATOR ', ')) AS `send_as` FROM `sender_acl` LEFT JOIN `alias_domain` ON `alias_domain`.`target_domain` = TRIM(LEADING '@' FROM `send_as`) WHERE `logged_in_as` = :username AND `send_as` LIKE '@%';"); - $stmt->execute(array(':username' => $username)); - $run = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($row = array_shift($run)) { - $data['aliases_send_as_all'] = $row['send_as']; - } - $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '') as `address` FROM `alias` WHERE `goto` REGEXP :username AND `address` LIKE '@%';"); - $stmt->execute(array(':username' => '(^|,)'.$username.'($|,)')); - $run = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($row = array_shift($run)) { - $data['is_catch_all'] = $row['address']; - } - return $data; -} -function is_valid_domain_name($domain_name) { - if (empty($domain_name)) { - return false; - } - $domain_name = idn_to_ascii($domain_name, 0, INTL_IDNA_VARIANT_UTS46); - return (preg_match("/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i", $domain_name) - && preg_match("/^.{1,253}$/", $domain_name) - && preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name)); -} -function set_tfa($_data) { - global $pdo; - global $yubi; - global $tfa; - $_data_log = $_data; - $access_denied = null; - !isset($_data_log['confirm_password']) ?: $_data_log['confirm_password'] = '*'; - $username = $_SESSION['mailcow_cc_username']; - - // check for empty user and role - if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) $access_denied = true; - - // check admin confirm password - if ($access_denied === null) { - $stmt = $pdo->prepare("SELECT `password` FROM `admin` - WHERE `username` = :username"); - $stmt->execute(array(':username' => $username)); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - if ($row) { - if (!verify_hash($row['password'], $_data["confirm_password"])) $access_denied = true; - else $access_denied = false; - } - } - - // check mailbox confirm password - if ($access_denied === null) { - $stmt = $pdo->prepare("SELECT `password` FROM `mailbox` - WHERE `username` = :username"); - $stmt->execute(array(':username' => $username)); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - if ($row) { - if (!verify_hash($row['password'], $_data["confirm_password"])) $access_denied = true; - else $access_denied = false; - } - } - - // set access_denied error - if ($access_denied){ - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - - switch ($_data["tfa_method"]) { - case "yubi_otp": - $key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"]; - $yubico_id = $_data['yubico_id']; - $yubico_key = $_data['yubico_key']; - $yubi = new Auth_Yubico($yubico_id, $yubico_key); - if (!$yubi) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - if (!ctype_alnum($_data["otp_token"]) || strlen($_data["otp_token"]) != 44) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'tfa_token_invalid' - ); - return false; - } - $yauth = $yubi->verify($_data["otp_token"]); - if (PEAR::isError($yauth)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => array('yotp_verification_failed', $yauth->getMessage()) - ); - return false; - } - try { - // We could also do a modhex translation here - $yubico_modhex_id = substr($_data["otp_token"], 0, 12); - $stmt = $pdo->prepare("DELETE FROM `tfa` - WHERE `username` = :username - AND (`authmech` = 'yubi_otp' AND `secret` LIKE :modhex)"); - $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id)); - $stmt = $pdo->prepare("INSERT INTO `tfa` (`key_id`, `username`, `authmech`, `active`, `secret`) VALUES - (:key_id, :username, 'yubi_otp', '1', :secret)"); - $stmt->execute(array(':key_id' => $key_id, ':username' => $username, ':secret' => $yubico_id . ':' . $yubico_key . ':' . $yubico_modhex_id)); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => array('mysql_error', $e) - ); - return false; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => array('object_modified', htmlspecialchars($username)) - ); - break; - case "totp": - $key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"]; - if ($tfa->verifyCode($_POST['totp_secret'], $_POST['totp_confirm_token']) === true) { - //$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username"); - //$stmt->execute(array(':username' => $username)); - $stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `secret`, `active`) VALUES (?, ?, 'totp', ?, '1')"); - $stmt->execute(array($username, $key_id, $_POST['totp_secret'])); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => array('object_modified', $username) - ); - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'totp_verification_failed' - ); - } - break; - case "webauthn": - $key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"]; - - $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": - $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username"); - $stmt->execute(array(':username' => $username)); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => array('object_modified', htmlspecialchars($username)) - ); - break; - } -} -function fido2($_data) { - global $pdo; - $_data_log = $_data; - // Not logging registration data, only actions - // Silent errors for "get" requests - switch ($_data["action"]) { - case "register": - $username = $_SESSION['mailcow_cc_username']; - if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data["action"]), - 'msg' => 'access_denied' - ); - return false; - } - $stmt = $pdo->prepare("INSERT INTO `fido2` (`username`, `rpId`, `credentialPublicKey`, `certificateChain`, `certificate`, `certificateIssuer`, `certificateSubject`, `signatureCounter`, `AAGUID`, `credentialId`) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); - $stmt->execute(array( - $username, - $_data['registration']->rpId, - $_data['registration']->credentialPublicKey, - $_data['registration']->certificateChain, - $_data['registration']->certificate, - $_data['registration']->certificateIssuer, - $_data['registration']->certificateSubject, - $_data['registration']->signatureCounter, - $_data['registration']->AAGUID, - $_data['registration']->credentialId) - ); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_data["action"]), - 'msg' => array('object_modified', $username) - ); - break; - case "get_user_cids": - // Used to exclude existing CredentialIds while registering - $username = $_SESSION['mailcow_cc_username']; - if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) { - return false; - } - $stmt = $pdo->prepare("SELECT `credentialId` FROM `fido2` WHERE `username` = :username"); - $stmt->execute(array(':username' => $username)); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - $cids[] = $row['credentialId']; - } - return $cids; - break; - case "get_all_cids": - // Only needed when using fido2 with username - $stmt = $pdo->query("SELECT `credentialId` FROM `fido2`"); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - $cids[] = $row['credentialId']; - } - return $cids; - break; - case "get_by_b64cid": - if (!isset($_data['cid']) || empty($_data['cid'])) { - return false; - } - $stmt = $pdo->prepare("SELECT `certificateSubject`, `username`, `credentialPublicKey`, SHA2(`credentialId`, 256) AS `cid` FROM `fido2` WHERE `credentialId` = :cid"); - $stmt->execute(array(':cid' => base64_decode($_data['cid']))); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (empty($row) || empty($row['credentialPublicKey']) || empty($row['username'])) { - return false; - } - $data['pub_key'] = $row['credentialPublicKey']; - $data['username'] = $row['username']; - $data['subject'] = $row['certificateSubject']; - $data['cid'] = $row['cid']; - return $data; - break; - case "get_friendly_names": - $username = $_SESSION['mailcow_cc_username']; - if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) { - return false; - } - $stmt = $pdo->prepare("SELECT SHA2(`credentialId`, 256) AS `cid`, `created`, `certificateSubject`, `friendlyName` FROM `fido2` WHERE `username` = :username"); - $stmt->execute(array(':username' => $username)); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - $fns[] = array( - "subject" => (empty($row['certificateSubject']) ? 'Unknown (' . $row['created'] . ')' : $row['certificateSubject']), - "fn" => $row['friendlyName'], - "cid" => $row['cid'] - ); - } - return $fns; - break; - case "unset_fido2_key": - $username = $_SESSION['mailcow_cc_username']; - if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data["action"]), - 'msg' => 'access_denied' - ); - return false; - } - $stmt = $pdo->prepare("DELETE FROM `fido2` WHERE `username` = :username AND SHA2(`credentialId`, 256) = :cid"); - $stmt->execute(array( - ':username' => $username, - ':cid' => $_data['post_data']['unset_fido2_key'] - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => array('object_modified', htmlspecialchars($username)) - ); - break; - case "edit_fn": - $username = $_SESSION['mailcow_cc_username']; - if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data["action"]), - 'msg' => 'access_denied' - ); - return false; - } - $stmt = $pdo->prepare("UPDATE `fido2` SET `friendlyName` = :friendlyName WHERE SHA2(`credentialId`, 256) = :cid AND `username` = :username"); - $stmt->execute(array( - ':username' => $username, - ':friendlyName' => $_data['fido2_attrs']['fido2_fn'], - ':cid' => $_data['fido2_attrs']['fido2_cid'] - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => array('object_modified', htmlspecialchars($username)) - ); - break; - } -} -function unset_tfa_key($_data) { - // Can only unset own keys - // Needs at least one key left - global $pdo; - global $lang; - $_data_log = $_data; - $access_denied = null; - $id = intval($_data['unset_tfa_key']); - $username = $_SESSION['mailcow_cc_username']; - - // check for empty user and role - if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) $access_denied = true; - - try { - if (!is_numeric($id)) $access_denied = true; - - // set access_denied error - if ($access_denied){ - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - - // check if it's last key - $stmt = $pdo->prepare("SELECT COUNT(*) AS `keys` FROM `tfa` - WHERE `username` = :username AND `active` = '1'"); - $stmt->execute(array(':username' => $username)); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - if ($row['keys'] == "1") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => 'last_key' - ); - return false; - } - - // delete key - $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `id` = :id"); - $stmt->execute(array(':username' => $username, ':id' => $id)); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => array('object_modified', $username) - ); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_data_log), - 'msg' => array('mysql_error', $e) - ); - return false; - } -} -function get_tfa($username = null, $id = null) { - global $pdo; - if (isset($_SESSION['mailcow_cc_username'])) { - $username = $_SESSION['mailcow_cc_username']; - } - elseif (empty($username)) { - return false; - } - - if (!isset($id)){ - // fetch all tfa methods - just get information about possible authenticators - $stmt = $pdo->prepare("SELECT `id`, `key_id`, `authmech` FROM `tfa` - WHERE `username` = :username AND `active` = '1'"); - $stmt->execute(array(':username' => $username)); - $results = $stmt->fetchAll(PDO::FETCH_ASSOC); - - // no tfa methods found - if (count($results) == 0) { - $data['name'] = 'none'; - $data['pretty'] = "-"; - $data['additional'] = array(); - return $data; - } - - $data['additional'] = $results; - return $data; - } else { - // fetch specific authenticator details by id - $stmt = $pdo->prepare("SELECT * FROM `tfa` - WHERE `username` = :username AND `id` = :id AND `active` = '1'"); - $stmt->execute(array(':username' => $username, ':id' => $id)); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - - if (isset($row["authmech"])) { - switch ($row["authmech"]) { - case "yubi_otp": - $data['name'] = "yubi_otp"; - $data['pretty'] = "Yubico OTP"; - $stmt = $pdo->prepare("SELECT `id`, `key_id`, RIGHT(`secret`, 12) AS 'modhex' FROM `tfa` WHERE `authmech` = 'yubi_otp' AND `username` = :username AND `id` = :id"); - $stmt->execute(array( - ':username' => $username, - ':id' => $id - )); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - $data['additional'][] = $row; - } - return $data; - break; - // u2f - deprecated, should be removed - case "u2f": - $data['name'] = "u2f"; - $data['pretty'] = "Fido U2F"; - $stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username AND `id` = :id"); - $stmt->execute(array( - ':username' => $username, - ':id' => $id - )); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - $data['additional'][] = $row; - } - return $data; - break; - case "hotp": - $data['name'] = "hotp"; - $data['pretty'] = "HMAC-based OTP"; - return $data; - break; - case "totp": - $data['name'] = "totp"; - $data['pretty'] = "Time-based OTP"; - $stmt = $pdo->prepare("SELECT `id`, `key_id`, `secret` FROM `tfa` WHERE `authmech` = 'totp' AND `username` = :username AND `id` = :id"); - $stmt->execute(array( - ':username' => $username, - ':id' => $id - )); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - $data['additional'][] = $row; - } - return $data; - break; - case "webauthn": - $data['name'] = "webauthn"; - $data['pretty'] = "WebAuthn"; - $stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'webauthn' AND `username` = :username AND `id` = :id"); - $stmt->execute(array( - ':username' => $username, - ':id' => $id - )); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - $data['additional'][] = $row; - } - return $data; - break; - default: - $data['name'] = 'none'; - $data['pretty'] = "-"; - return $data; - break; - } - } - else { - $data['name'] = 'none'; - $data['pretty'] = "-"; - return $data; - } - } -} -function verify_tfa_login($username, $_data) { - global $pdo; - global $yubi; - global $u2f; - global $tfa; - global $WebAuthn; - - if ($_data['tfa_method'] != 'u2f'){ - - switch ($_data["tfa_method"]) { - case "yubi_otp": - if (!ctype_alnum($_data['token']) || strlen($_data['token']) != 44) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $username, '*'), - 'msg' => array('yotp_verification_failed', 'token length error') - ); - return false; - } - $yubico_modhex_id = substr($_data['token'], 0, 12); - $stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa` - WHERE `username` = :username - AND `authmech` = 'yubi_otp' - AND `active` = '1' - AND `secret` LIKE :modhex"); - $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id)); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - $yubico_auth = explode(':', $row['secret']); - $yubi = new Auth_Yubico($yubico_auth[0], $yubico_auth[1]); - $yauth = $yubi->verify($_data['token']); - if (PEAR::isError($yauth)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $username, '*'), - 'msg' => array('yotp_verification_failed', $yauth->getMessage()) - ); - return false; - } - else { - $_SESSION['tfa_id'] = $row['id']; - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $username, '*'), - 'msg' => 'verified_yotp_login' - ); - return true; - } - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $username, '*'), - 'msg' => array('yotp_verification_failed', 'unknown') - ); - return false; - break; - case "hotp": - return false; - break; - case "totp": - try { - $stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa` - WHERE `username` = :username - AND `authmech` = 'totp' - AND `id` = :id - AND `active`='1'"); - $stmt->execute(array(':username' => $username, ':id' => $_data['id'])); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - foreach ($rows as $row) { - if ($tfa->verifyCode($row['secret'], $_data['token']) === true) { - $_SESSION['tfa_id'] = $row['id']; - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $username, '*'), - 'msg' => 'verified_totp_login' - ); - return true; - } - } - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $username, '*'), - 'msg' => 'totp_verification_failed' - ); - return false; - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $username, '*'), - 'msg' => array('mysql_error', $e) - ); - return false; - } - break; - 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 `id`, `key_id`, `keyHandle`, `username`, `publicKey` FROM `tfa` WHERE `id` = :id AND `active`='1'"); - $stmt->execute(array(':id' => $_data['id'])); - $process_webauthn = $stmt->fetch(PDO::FETCH_ASSOC); - - if (empty($process_webauthn)){ - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $username, '*'), - 'msg' => array('webauthn_authenticator_failed') - ); - return false; - } - - if (empty($process_webauthn['publicKey']) || $process_webauthn['publicKey'] === false) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $username, '*'), - 'msg' => array('webauthn_publickey_failed') - ); - return false; - } - - if ($process_webauthn['username'] != $_SESSION['pending_mailcow_cc_username']){ - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $username, '*'), - 'msg' => array('webauthn_username_failed') - ); - 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 (!empty($row['username'])) { - $_SESSION["mailcow_cc_role"] = "user"; - } else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $username, '*'), - 'msg' => array('webauthn_role_failed') - ); - return false; - } - } - - $_SESSION["mailcow_cc_username"] = $process_webauthn['username']; - $_SESSION['tfa_id'] = $process_webauthn['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: - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $username, '*'), - 'msg' => 'unknown_tfa_method' - ); - return false; - break; - } - - return false; - } else { - // delete old keys that used u2f - $stmt = $pdo->prepare("SELECT * FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username"); - $stmt->execute(array(':username' => $username)); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - if (count($rows) == 0) return false; - - $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username"); - $stmt->execute(array(':username' => $username)); - return true; - } -} -function admin_api($access, $action, $data = null) { - global $pdo; - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__), - 'msg' => 'access_denied' - ); - return false; - } - if ($access !== "ro" && $access !== "rw") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__), - 'msg' => 'invalid access type' - ); - return false; - } - if ($action == "edit") { - $active = (!empty($data['active'])) ? 1 : 0; - $skip_ip_check = (isset($data['skip_ip_check'])) ? 1 : 0; - $allow_from = array(); - if (isset($data['allow_from'])) { - $allow_from = array_map('trim', preg_split( "/( |,|;|\n)/", $data['allow_from'])); - } - foreach ($allow_from as $key => $val) { - if (empty($val)) { - unset($allow_from[$key]); - continue; - } - if (valid_network($val) !== true) { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $data), - 'msg' => array('ip_invalid', htmlspecialchars($allow_from[$key])) - ); - unset($allow_from[$key]); - continue; - } - } - $allow_from = implode(',', array_unique(array_filter($allow_from))); - if (empty($allow_from) && $skip_ip_check == 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $data), - 'msg' => 'ip_list_empty' - ); - return false; - } - $api_key = implode('-', array( - strtoupper(bin2hex(random_bytes(3))), - strtoupper(bin2hex(random_bytes(3))), - strtoupper(bin2hex(random_bytes(3))), - strtoupper(bin2hex(random_bytes(3))), - strtoupper(bin2hex(random_bytes(3))) - )); - $stmt = $pdo->query("SELECT `api_key` FROM `api` WHERE `access` = '" . $access . "'"); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if (empty($num_results)) { - $stmt = $pdo->prepare("INSERT INTO `api` (`api_key`, `skip_ip_check`, `active`, `allow_from`, `access`) - VALUES (:api_key, :skip_ip_check, :active, :allow_from, :access);"); - $stmt->execute(array( - ':api_key' => $api_key, - ':skip_ip_check' => $skip_ip_check, - ':active' => $active, - ':allow_from' => $allow_from, - ':access' => $access - )); - } - else { - if ($skip_ip_check == 0) { - $stmt = $pdo->prepare("UPDATE `api` SET `skip_ip_check` = :skip_ip_check, - `active` = :active, - `allow_from` = :allow_from - WHERE `access` = :access;"); - $stmt->execute(array( - ':active' => $active, - ':skip_ip_check' => $skip_ip_check, - ':allow_from' => $allow_from, - ':access' => $access - )); - } - else { - $stmt = $pdo->prepare("UPDATE `api` SET `skip_ip_check` = :skip_ip_check, - `active` = :active - WHERE `access` = :access;"); - $stmt->execute(array( - ':active' => $active, - ':skip_ip_check' => $skip_ip_check, - ':access' => $access - )); - } - } - } - elseif ($action == "regen_key") { - $api_key = implode('-', array( - strtoupper(bin2hex(random_bytes(3))), - strtoupper(bin2hex(random_bytes(3))), - strtoupper(bin2hex(random_bytes(3))), - strtoupper(bin2hex(random_bytes(3))), - strtoupper(bin2hex(random_bytes(3))) - )); - $stmt = $pdo->prepare("UPDATE `api` SET `api_key` = :api_key WHERE `access` = :access"); - $stmt->execute(array( - ':api_key' => $api_key, - ':access' => $access - )); - } - elseif ($action == "get") { - $stmt = $pdo->query("SELECT * FROM `api` WHERE `access` = '" . $access . "'"); - $apidata = $stmt->fetch(PDO::FETCH_ASSOC); - if ($apidata !== false) { - $apidata['allow_from'] = str_replace(',', PHP_EOL, $apidata['allow_from']); - } - return $apidata; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $data), - 'msg' => 'admin_api_modified' - ); -} -function license($action, $data = null) { - global $pdo; - global $redis; - global $lang; - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__), - 'msg' => 'access_denied' - ); - return false; - } - switch ($action) { - case "verify": - // Keep result until revalidate button is pressed or session expired - $stmt = $pdo->query("SELECT `version` FROM `versions` WHERE `application` = 'GUID'"); - $versions = $stmt->fetch(PDO::FETCH_ASSOC); - $post = array('guid' => $versions['version']); - $curl = curl_init('https://verify.mailcow.email'); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); - curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10); - curl_setopt($curl, CURLOPT_POSTFIELDS, $post); - $response = curl_exec($curl); - curl_close($curl); - $json_return = json_decode($response, true); - if ($response && $json_return) { - if ($json_return['response'] === "ok") { - $_SESSION['gal']['valid'] = "true"; - $_SESSION['gal']['c'] = $json_return['c']; - $_SESSION['gal']['s'] = $json_return['s']; - if ($json_return['m'] == 'NoMoore') { - $_SESSION['gal']['m'] = '🐄'; - } - else { - $_SESSION['gal']['m'] = str_repeat('🐄', substr_count($json_return['m'], 'o')); - } - } - elseif ($json_return['response'] === "invalid") { - $_SESSION['gal']['valid'] = "false"; - $_SESSION['gal']['c'] = $lang['mailbox']['no']; - $_SESSION['gal']['s'] = $lang['mailbox']['no']; - $_SESSION['gal']['m'] = $lang['mailbox']['no']; - } - } - else { - $_SESSION['gal']['valid'] = "false"; - $_SESSION['gal']['c'] = $lang['danger']['temp_error']; - $_SESSION['gal']['s'] = $lang['danger']['temp_error']; - $_SESSION['gal']['m'] = $lang['danger']['temp_error']; - } - try { - // json_encode needs "true"/"false" instead of true/false, to not encode it to 0 or 1 - $redis->Set('LICENSE_STATUS_CACHE', json_encode($_SESSION['gal'])); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('redis_error', $e) - ); - return false; - } - return $_SESSION['gal']['valid']; - break; - case "guid": - $stmt = $pdo->query("SELECT `version` FROM `versions` WHERE `application` = 'GUID'"); - $versions = $stmt->fetch(PDO::FETCH_ASSOC); - return $versions['version']; - break; - } -} -function rspamd_ui($action, $data = null) { - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__), - 'msg' => 'access_denied' - ); - return false; - } - switch ($action) { - case "edit": - $rspamd_ui_pass = $data['rspamd_ui_pass']; - $rspamd_ui_pass2 = $data['rspamd_ui_pass2']; - if (empty($rspamd_ui_pass) || empty($rspamd_ui_pass2)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, '*', '*'), - 'msg' => 'password_empty' - ); - return false; - } - if ($rspamd_ui_pass != $rspamd_ui_pass2) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, '*', '*'), - 'msg' => 'password_mismatch' - ); - return false; - } - if (strlen($rspamd_ui_pass) < 6) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, '*', '*'), - 'msg' => 'rspamd_ui_pw_length' - ); - return false; - } - $docker_return = docker('post', 'rspamd-mailcow', 'exec', array('cmd' => 'rspamd', 'task' => 'worker_password', 'raw' => $rspamd_ui_pass), array('Content-Type: application/json')); - if ($docker_return_array = json_decode($docker_return, true)) { - if ($docker_return_array['type'] == 'success') { - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, '*', '*'), - 'msg' => 'rspamd_ui_pw_set' - ); - return true; - } - else { - $_SESSION['return'][] = array( - 'type' => $docker_return_array['type'], - 'log' => array(__FUNCTION__, '*', '*'), - 'msg' => $docker_return_array['msg'] - ); - return false; - } - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, '*', '*'), - 'msg' => 'unknown' - ); - return false; - } - break; - } -} - -function get_logs($application, $lines = false) { - if ($lines === false) { - $lines = $GLOBALS['LOG_LINES'] - 1; - } - elseif(is_numeric($lines) && $lines >= 1) { - $lines = abs(intval($lines) - 1); - } - else { - list ($from, $to) = explode('-', $lines); - $from = intval($from); - $to = intval($to); - if ($from < 1 || $to < $from) { return false; } - } - global $redis; - global $pdo; - if ($_SESSION['mailcow_cc_role'] != "admin") { - return false; - } - // SQL - if ($application == "mailcow-ui") { - if (isset($from) && isset($to)) { - $stmt = $pdo->prepare("SELECT * FROM `logs` ORDER BY `id` DESC LIMIT :from, :to"); - $stmt->execute(array( - ':from' => $from - 1, - ':to' => $to - )); - $data = $stmt->fetchAll(PDO::FETCH_ASSOC); - } - else { - $stmt = $pdo->prepare("SELECT * FROM `logs` ORDER BY `id` DESC LIMIT :lines"); - $stmt->execute(array( - ':lines' => $lines + 1, - )); - $data = $stmt->fetchAll(PDO::FETCH_ASSOC); - } - if (is_array($data)) { - return $data; - } - } - if ($application == "sasl") { - if (isset($from) && isset($to)) { - $stmt = $pdo->prepare("SELECT * FROM `sasl_log` ORDER BY `datetime` DESC LIMIT :from, :to"); - $stmt->execute(array( - ':from' => $from - 1, - ':to' => $to - )); - $data = $stmt->fetchAll(PDO::FETCH_ASSOC); - } - else { - $stmt = $pdo->prepare("SELECT * FROM `sasl_log` ORDER BY `datetime` DESC LIMIT :lines"); - $stmt->execute(array( - ':lines' => $lines + 1, - )); - $data = $stmt->fetchAll(PDO::FETCH_ASSOC); - } - if (is_array($data)) { - return $data; - } - } - // Redis - if ($application == "dovecot-mailcow") { - if (isset($from) && isset($to)) { - $data = $redis->lRange('DOVECOT_MAILLOG', $from - 1, $to - 1); - } - else { - $data = $redis->lRange('DOVECOT_MAILLOG', 0, $lines); - } - if ($data) { - foreach ($data as $json_line) { - $data_array[] = json_decode($json_line, true); - } - return $data_array; - } - } - if ($application == "postfix-mailcow") { - if (isset($from) && isset($to)) { - $data = $redis->lRange('POSTFIX_MAILLOG', $from - 1, $to - 1); - } - else { - $data = $redis->lRange('POSTFIX_MAILLOG', 0, $lines); - } - if ($data) { - foreach ($data as $json_line) { - $data_array[] = json_decode($json_line, true); - } - return $data_array; - } - } - if ($application == "sogo-mailcow") { - if (isset($from) && isset($to)) { - $data = $redis->lRange('SOGO_LOG', $from - 1, $to - 1); - } - else { - $data = $redis->lRange('SOGO_LOG', 0, $lines); - } - if ($data) { - foreach ($data as $json_line) { - $data_array[] = json_decode($json_line, true); - } - return $data_array; - } - } - if ($application == "watchdog-mailcow") { - if (isset($from) && isset($to)) { - $data = $redis->lRange('WATCHDOG_LOG', $from - 1, $to - 1); - } - else { - $data = $redis->lRange('WATCHDOG_LOG', 0, $lines); - } - if ($data) { - foreach ($data as $json_line) { - $data_array[] = json_decode($json_line, true); - } - return $data_array; - } - } - if ($application == "acme-mailcow") { - if (isset($from) && isset($to)) { - $data = $redis->lRange('ACME_LOG', $from - 1, $to - 1); - } - else { - $data = $redis->lRange('ACME_LOG', 0, $lines); - } - if ($data) { - foreach ($data as $json_line) { - $data_array[] = json_decode($json_line, true); - } - return $data_array; - } - } - if ($application == "ratelimited") { - if (isset($from) && isset($to)) { - $data = $redis->lRange('RL_LOG', $from - 1, $to - 1); - } - else { - $data = $redis->lRange('RL_LOG', 0, $lines); - } - if ($data) { - foreach ($data as $json_line) { - $data_array[] = json_decode($json_line, true); - } - return $data_array; - } - } - if ($application == "api-mailcow") { - if (isset($from) && isset($to)) { - $data = $redis->lRange('API_LOG', $from - 1, $to - 1); - } - else { - $data = $redis->lRange('API_LOG', 0, $lines); - } - if ($data) { - foreach ($data as $json_line) { - $data_array[] = json_decode($json_line, true); - } - return $data_array; - } - } - if ($application == "netfilter-mailcow") { - if (isset($from) && isset($to)) { - $data = $redis->lRange('NETFILTER_LOG', $from - 1, $to - 1); - } - else { - $data = $redis->lRange('NETFILTER_LOG', 0, $lines); - } - if ($data) { - foreach ($data as $json_line) { - $data_array[] = json_decode($json_line, true); - } - return $data_array; - } - } - if ($application == "autodiscover-mailcow") { - if (isset($from) && isset($to)) { - $data = $redis->lRange('AUTODISCOVER_LOG', $from - 1, $to - 1); - } - else { - $data = $redis->lRange('AUTODISCOVER_LOG', 0, $lines); - } - if ($data) { - foreach ($data as $json_line) { - $data_array[] = json_decode($json_line, true); - } - return $data_array; - } - } - if ($application == "rspamd-history") { - $curl = curl_init(); - curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); - if (!is_numeric($lines)) { - list ($from, $to) = explode('-', $lines); - curl_setopt($curl, CURLOPT_URL,"http://rspamd/history?from=" . intval($from) . "&to=" . intval($to)); - } - else { - curl_setopt($curl, CURLOPT_URL,"http://rspamd/history?to=" . intval($lines)); - } - curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); - $history = curl_exec($curl); - if (!curl_errno($curl)) { - $data_array = json_decode($history, true); - curl_close($curl); - return $data_array['rows']; - } - curl_close($curl); - return false; - } - if ($application == "rspamd-stats") { - $curl = curl_init(); - curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); - curl_setopt($curl, CURLOPT_URL,"http://rspamd/stat"); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); - $stats = curl_exec($curl); - if (!curl_errno($curl)) { - $data_array = json_decode($stats, true); - curl_close($curl); - return $data_array; - } - curl_close($curl); - return false; - } - return false; -} -function getGUID() { - if (function_exists('com_create_guid')) { - return com_create_guid(); - } - mt_srand((double)microtime()*10000);//optional for php 4.2.0 and up. - $charid = strtoupper(md5(uniqid(rand(), true))); - $hyphen = chr(45);// "-" - return substr($charid, 0, 8).$hyphen - .substr($charid, 8, 4).$hyphen - .substr($charid,12, 4).$hyphen - .substr($charid,16, 4).$hyphen - .substr($charid,20,12); -} -function solr_status() { - $curl = curl_init(); - $endpoint = 'http://solr:8983/solr/admin/cores'; - $params = array( - 'action' => 'STATUS', - 'core' => 'dovecot-fts', - 'indexInfo' => 'true' - ); - $url = $endpoint . '?' . http_build_query($params); - curl_setopt($curl, CURLOPT_URL, $url); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_POST, 0); - curl_setopt($curl, CURLOPT_TIMEOUT, 10); - $response_core = curl_exec($curl); - if ($response_core === false) { - $err = curl_error($curl); - curl_close($curl); - return false; - } - else { - curl_close($curl); - $curl = curl_init(); - $status_core = json_decode($response_core, true); - $url = 'http://solr:8983/solr/admin/info/system'; - curl_setopt($curl, CURLOPT_URL, $url); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_POST, 0); - curl_setopt($curl, CURLOPT_TIMEOUT, 10); - $response_sysinfo = curl_exec($curl); - if ($response_sysinfo === false) { - $err = curl_error($curl); - curl_close($curl); - return false; - } - else { - curl_close($curl); - $status_sysinfo = json_decode($response_sysinfo, true); - $status = array_merge($status_core, $status_sysinfo); - return (!empty($status['status']['dovecot-fts']) && !empty($status['jvm']['memory'])) ? $status : false; - } - return (!empty($status['status']['dovecot-fts'])) ? $status['status']['dovecot-fts'] : false; - } - return false; -} - -function cleanupJS($ignore = '', $folder = '/tmp/*.js') { - $now = time(); - foreach (glob($folder) as $filename) { - if(strpos($filename, $ignore) !== false) { - continue; - } - if (is_file($filename)) { - if ($now - filemtime($filename) >= 60 * 60) { - unlink($filename); - } - } - } -} - -function cleanupCSS($ignore = '', $folder = '/tmp/*.css') { - $now = time(); - foreach (glob($folder) as $filename) { - if(strpos($filename, $ignore) !== false) { - continue; - } - if (is_file($filename)) { - if ($now - filemtime($filename) >= 60 * 60) { - unlink($filename); - } - } - } -} - -?> +1) { + $bits=(int)($ar[1]); + } + else { + $bits = $iplen * 8; + } + for ($c=0; $bits>0; $c++) { + $bytemask = ($bits < 8) ? 0xff ^ ((1 << (8-$bits))-1) : 0xff; + if (((ord($ipb[$c]) ^ ord($ip1b[$c])) & $bytemask) != 0) { + continue 2; + } + $bits-=8; + } + return true; + } + return false; +} +function hash_password($password) { + // default_pass_scheme is determined in vars.inc.php (or corresponding local file) + // in case default pass scheme is not defined, falling back to BLF-CRYPT. + global $default_pass_scheme; + $pw_hash = NULL; + // support pre-hashed passwords + if (preg_match('/^{(ARGON2I|ARGON2ID|BLF-CRYPT|CLEAR|CLEARTEXT|CRYPT|DES-CRYPT|LDAP-MD5|MD5|MD5-CRYPT|PBKDF2|PLAIN|PLAIN-MD4|PLAIN-MD5|PLAIN-TRUNC|PLAIN-TRUNC|SHA|SHA1|SHA256|SHA256-CRYPT|SHA512|SHA512-CRYPT|SMD5|SSHA|SSHA256|SSHA512)}/i', $password)) { + $pw_hash = $password; + } + else { + switch (strtoupper($default_pass_scheme)) { + case "SSHA": + $salt_str = bin2hex(openssl_random_pseudo_bytes(8)); + $pw_hash = "{SSHA}".base64_encode(hash('sha1', $password . $salt_str, true) . $salt_str); + break; + case "SSHA256": + $salt_str = bin2hex(openssl_random_pseudo_bytes(8)); + $pw_hash = "{SSHA256}".base64_encode(hash('sha256', $password . $salt_str, true) . $salt_str); + break; + case "SSHA512": + $salt_str = bin2hex(openssl_random_pseudo_bytes(8)); + $pw_hash = "{SSHA512}".base64_encode(hash('sha512', $password . $salt_str, true) . $salt_str); + break; + case "BLF-CRYPT": + default: + $pw_hash = "{BLF-CRYPT}" . password_hash($password, PASSWORD_BCRYPT); + break; + } + } + return $pw_hash; +} +function password_complexity($_action, $_data = null) { + global $redis; + global $lang; + switch ($_action) { + case 'edit': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'access_denied' + ); + return false; + } + $is_now = password_complexity('get'); + if (!empty($is_now)) { + $length = (isset($_data['length']) && intval($_data['length']) >= 3) ? intval($_data['length']) : $is_now['length']; + $chars = (isset($_data['chars'])) ? intval($_data['chars']) : $is_now['chars']; + $lowerupper = (isset($_data['lowerupper'])) ? intval($_data['lowerupper']) : $is_now['lowerupper']; + $special_chars = (isset($_data['special_chars'])) ? intval($_data['special_chars']) : $is_now['special_chars']; + $numbers = (isset($_data['numbers'])) ? intval($_data['numbers']) : $is_now['numbers']; + } + try { + $redis->hMSet('PASSWD_POLICY', [ + 'length' => $length, + 'chars' => $chars, + 'special_chars' => $special_chars, + 'lowerupper' => $lowerupper, + 'numbers' => $numbers + ]); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('redis_error', $e) + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'password_policy_saved' + ); + break; + case 'get': + try { + $length = $redis->hGet('PASSWD_POLICY', 'length'); + $chars = $redis->hGet('PASSWD_POLICY', 'chars'); + $special_chars = $redis->hGet('PASSWD_POLICY', 'special_chars'); + $lowerupper = $redis->hGet('PASSWD_POLICY', 'lowerupper'); + $numbers = $redis->hGet('PASSWD_POLICY', 'numbers'); + return array( + 'length' => $length, + 'chars' => $chars, + 'special_chars' => $special_chars, + 'lowerupper' => $lowerupper, + 'numbers' => $numbers + ); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('redis_error', $e) + ); + return false; + } + return false; + break; + case 'html': + $policies = password_complexity('get'); + foreach ($policies as $name => $value) { + if ($value != 0) { + $policy_text[] = sprintf($lang['admin']["password_policy_$name"], $value); + } + } + return '

- ' . implode('
- ', (array)$policy_text) . '

'; + break; + } +} +function password_check($password1, $password2) { + $password_complexity = password_complexity('get'); + + if (empty($password1) || empty($password2)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type), + 'msg' => 'password_complexity' + ); + return false; + } + + if ($password1 != $password2) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type), + 'msg' => 'password_mismatch' + ); + return false; + } + + $given_password['length'] = strlen($password1); + $given_password['special_chars'] = preg_match('/[^a-zA-Z\d]/', $password1); + $given_password['chars'] = preg_match('/[a-zA-Z]/',$password1); + $given_password['numbers'] = preg_match('/\d/', $password1); + $lower = strlen(preg_replace("/[^a-z]/", '', $password1)); + $upper = strlen(preg_replace("/[^A-Z]/", '', $password1)); + $given_password['lowerupper'] = ($lower > 0 && $upper > 0) ? true : false; + + if ( + ($given_password['length'] < $password_complexity['length']) || + ($password_complexity['special_chars'] == 1 && (intval($given_password['special_chars']) != $password_complexity['special_chars'])) || + ($password_complexity['chars'] == 1 && (intval($given_password['chars']) != $password_complexity['chars'])) || + ($password_complexity['numbers'] == 1 && (intval($given_password['numbers']) != $password_complexity['numbers'])) || + ($password_complexity['lowerupper'] == 1 && (intval($given_password['lowerupper']) != $password_complexity['lowerupper'])) + ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type), + 'msg' => 'password_complexity' + ); + return false; + } + + return true; +} +function last_login($action, $username, $sasl_limit_days = 7, $ui_offset = 1) { + global $pdo; + global $redis; + $sasl_limit_days = intval($sasl_limit_days); + switch ($action) { + case 'get': + if (filter_var($username, FILTER_VALIDATE_EMAIL) && hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $stmt = $pdo->prepare('SELECT `real_rip`, MAX(`datetime`) as `datetime`, `service`, `app_password`, MAX(`app_passwd`.`name`) as `app_password_name` FROM `sasl_log` + LEFT OUTER JOIN `app_passwd` on `sasl_log`.`app_password` = `app_passwd`.`id` + WHERE `username` = :username + AND HOUR(TIMEDIFF(NOW(), `datetime`)) < :sasl_limit_days + GROUP BY `real_rip`, `service`, `app_password` + ORDER BY `datetime` DESC;'); + $stmt->execute(array(':username' => $username, ':sasl_limit_days' => ($sasl_limit_days * 24))); + $sasl = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($sasl as $k => $v) { + if (!filter_var($sasl[$k]['real_rip'], FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { + $sasl[$k]['real_rip'] = 'Web/EAS/Internal (' . $sasl[$k]['real_rip'] . ')'; + } + elseif (filter_var($sasl[$k]['real_rip'], FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { + try { + $sasl[$k]['location'] = $redis->hGet('IP_SHORTCOUNTRY', $sasl[$k]['real_rip']); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + if (!$sasl[$k]['location']) { + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL,"https://dfdata.bella.network/lookup/" . $sasl[$k]['real_rip']); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_USERAGENT, 'Moocow'); + curl_setopt($curl, CURLOPT_TIMEOUT, 5); + $ip_data = curl_exec($curl); + if (!curl_errno($curl)) { + $ip_data_array = json_decode($ip_data, true); + if ($ip_data_array !== false and !empty($ip_data_array['location']['shortcountry'])) { + $sasl[$k]['location'] = $ip_data_array['location']['shortcountry']; + try { + $redis->hSet('IP_SHORTCOUNTRY', $sasl[$k]['real_rip'], $ip_data_array['location']['shortcountry']); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + curl_close($curl); + return false; + } + } + } + curl_close($curl); + } + } + } + } + else { + $sasl = array(); + } + if ($_SESSION['mailcow_cc_role'] == "admin" || $username == $_SESSION['mailcow_cc_username']) { + $stmt = $pdo->prepare('SELECT `remote`, `time` FROM `logs` + WHERE JSON_EXTRACT(`call`, "$[0]") = "check_login" + AND JSON_EXTRACT(`call`, "$[1]") = :username + AND `type` = "success" ORDER BY `time` DESC LIMIT 1 OFFSET :offset'); + $stmt->execute(array( + ':username' => $username, + ':offset' => $ui_offset + )); + $ui = $stmt->fetch(PDO::FETCH_ASSOC); + } + else { + $ui = array(); + } + + return array('ui' => $ui, 'sasl' => $sasl); + break; + case 'reset': + if (filter_var($username, FILTER_VALIDATE_EMAIL) && hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $stmt = $pdo->prepare('DELETE FROM `sasl_log` + WHERE `username` = :username'); + $stmt->execute(array(':username' => $username)); + } + if ($_SESSION['mailcow_cc_role'] == "admin" || $username == $_SESSION['mailcow_cc_username']) { + $stmt = $pdo->prepare('DELETE FROM `logs` + WHERE JSON_EXTRACT(`call`, "$[0]") = "check_login" + AND JSON_EXTRACT(`call`, "$[1]") = :username + AND `type` = "success"'); + $stmt->execute(array(':username' => $username)); + } + return true; + break; + } + +} +function flush_memcached() { + try { + $m = new Memcached(); + $m->addServer('memcached', 11211); + $m->flush(); + } + catch ( Exception $e ) { + // Dunno + } +} +function sys_mail($_data) { + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__), + 'msg' => 'access_denied' + ); + return false; + } + $excludes = $_data['mass_exclude']; + $includes = $_data['mass_include']; + $mailboxes = array(); + $mass_from = $_data['mass_from']; + $mass_text = $_data['mass_text']; + $mass_html = $_data['mass_html']; + $mass_subject = $_data['mass_subject']; + if (!filter_var($mass_from, FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__), + 'msg' => 'from_invalid' + ); + return false; + } + if (empty($mass_subject)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__), + 'msg' => 'subject_empty' + ); + return false; + } + if (empty($mass_text)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__), + 'msg' => 'text_empty' + ); + return false; + } + $domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains')); + foreach ($domains as $domain) { + foreach (mailbox('get', 'mailboxes', $domain) as $mailbox) { + $mailboxes[] = $mailbox; + } + } + if (!empty($includes)) { + $rcpts = array_intersect($mailboxes, $includes); + } + elseif (!empty($excludes)) { + $rcpts = array_diff($mailboxes, $excludes); + } + else { + $rcpts = $mailboxes; + } + if (!empty($rcpts)) { + ini_set('max_execution_time', 0); + ini_set('max_input_time', 0); + $mail = new PHPMailer; + $mail->Timeout = 10; + $mail->SMTPOptions = array( + 'ssl' => array( + 'verify_peer' => false, + 'verify_peer_name' => false, + 'allow_self_signed' => true + ) + ); + $mail->isSMTP(); + $mail->Host = 'dovecot-mailcow'; + $mail->SMTPAuth = false; + $mail->Port = 24; + $mail->setFrom($mass_from); + $mail->Subject = $mass_subject; + $mail->CharSet ="UTF-8"; + if (!empty($mass_html)) { + $mail->Body = $mass_html; + $mail->AltBody = $mass_text; + } + else { + $mail->Body = $mass_text; + } + $mail->XMailer = 'MooMassMail'; + foreach ($rcpts as $rcpt) { + $mail->AddAddress($rcpt); + if (!$mail->send()) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__), + 'msg' => 'Mailer error (RCPT "' . htmlspecialchars($rcpt) . '"): ' . str_replace('https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting', '', $mail->ErrorInfo) + ); + } + $mail->ClearAllRecipients(); + } + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__), + 'msg' => 'Mass mail job completed, sent ' . count($rcpts) . ' mails' + ); +} +function logger($_data = false) { + /* + logger() will be called as last function + To manually log a message, logger needs to be called like below. + + logger(array( + 'return' => array( + array( + 'type' => 'danger', + 'log' => array(__FUNCTION__), + 'msg' => $err + ) + ) + )); + + These messages will not be printed as alert box. + To do so, push them to $_SESSION['return'] and do not call logger as they will be included automatically: + + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $user, '*'), + 'msg' => $err + ); + */ + global $pdo; + if (!$_data) { + $_data = $_SESSION; + } + if (!empty($_data['return'])) { + $task = substr(strtoupper(md5(uniqid(rand(), true))), 0, 6); + foreach ($_data['return'] as $return) { + $type = $return['type']; + $msg = null; + if (isset($return['msg'])) { + $msg = json_encode($return['msg'], JSON_UNESCAPED_UNICODE); + } + $call = null; + if (isset($return['log'])) { + $call = json_encode($return['log'], JSON_UNESCAPED_UNICODE); + } + if (!empty($_SESSION["dual-login"]["username"])) { + $user = $_SESSION["dual-login"]["username"] . ' => ' . $_SESSION['mailcow_cc_username']; + $role = $_SESSION["dual-login"]["role"] . ' => ' . $_SESSION['mailcow_cc_role']; + } + elseif (!empty($_SESSION['mailcow_cc_username'])) { + $user = $_SESSION['mailcow_cc_username']; + $role = $_SESSION['mailcow_cc_role']; + } + else { + $user = 'unauthenticated'; + $role = 'unauthenticated'; + } + // We cannot log when logs is missing... + try { + $stmt = $pdo->prepare("INSERT INTO `logs` (`type`, `task`, `msg`, `call`, `user`, `role`, `remote`, `time`) VALUES + (:type, :task, :msg, :call, :user, :role, :remote, UNIX_TIMESTAMP())"); + $stmt->execute(array( + ':type' => $type, + ':task' => $task, + ':call' => $call, + ':msg' => $msg, + ':user' => $user, + ':role' => $role, + ':remote' => get_remote_ip() + )); + } + catch (Exception $e) { + // Do nothing + } + } + } + else { + return true; + } +} +function hasDomainAccess($username, $role, $domain) { + global $pdo; + if (!filter_var($username, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) { + return false; + } + if (empty($domain) || !is_valid_domain_name($domain)) { + return false; + } + if ($role != 'admin' && $role != 'domainadmin') { + return false; + } + if ($role == 'admin') { + $stmt = $pdo->prepare("SELECT `domain` FROM `domain` + WHERE `domain` = :domain"); + $stmt->execute(array(':domain' => $domain)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` + WHERE `alias_domain` = :domain"); + $stmt->execute(array(':domain' => $domain)); + $num_results = $num_results + count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + return true; + } + } + elseif ($role == 'domainadmin') { + $stmt = $pdo->prepare("SELECT `domain` FROM `domain_admins` + WHERE ( + `active`='1' + AND `username` = :username + AND (`domain` = :domain1 OR `domain` = (SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain2)) + )"); + $stmt->execute(array(':username' => $username, ':domain1' => $domain, ':domain2' => $domain)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if (!empty($num_results)) { + return true; + } + } + return false; +} +function hasMailboxObjectAccess($username, $role, $object) { + global $pdo; + if (empty($username) || empty($role) || empty($object)) { + return false; + } + if (!filter_var(html_entity_decode(rawurldecode($username)), FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) { + return false; + } + if ($role != 'admin' && $role != 'domainadmin' && $role != 'user') { + return false; + } + if ($username == $object) { + return true; + } + $stmt = $pdo->prepare("SELECT `domain` FROM `mailbox` WHERE `username` = :object"); + $stmt->execute(array(':object' => $object)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (isset($row['domain']) && hasDomainAccess($username, $role, $row['domain'])) { + return true; + } + return false; +} +// does also verify mailboxes as a mailbox is a alias == goto +function hasAliasObjectAccess($username, $role, $object) { + global $pdo; + if (empty($username) || empty($role) || empty($object)) { + return false; + } + if (!filter_var(html_entity_decode(rawurldecode($username)), FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) { + return false; + } + if ($role != 'admin' && $role != 'domainadmin' && $role != 'user') { + return false; + } + $stmt = $pdo->prepare("SELECT `domain` FROM `alias` WHERE `address` = :object"); + $stmt->execute(array(':object' => $object)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (isset($row['domain']) && hasDomainAccess($username, $role, $row['domain'])) { + return true; + } + return false; +} +function pem_to_der($pem_key) { + // Need to remove BEGIN/END PUBLIC KEY + $lines = explode("\n", trim($pem_key)); + unset($lines[count($lines)-1]); + unset($lines[0]); + return base64_decode(implode('', $lines)); +} +function expand_ipv6($ip) { + $hex = unpack("H*hex", inet_pton($ip)); + $ip = substr(preg_replace("/([A-f0-9]{4})/", "$1:", $hex['hex']), 0, -1); + return $ip; +} +function generate_tlsa_digest($hostname, $port, $starttls = null) { + if (!is_valid_domain_name($hostname)) { + return "Not a valid hostname"; + } + if (empty($starttls)) { + $context = stream_context_create(array("ssl" => array("capture_peer_cert" => true, 'verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true))); + $stream = stream_socket_client('ssl://' . $hostname . ':' . $port, $error_nr, $error_msg, 5, STREAM_CLIENT_CONNECT, $context); + if (!$stream) { + $error_msg = isset($error_msg) ? $error_msg : '-'; + return $error_nr . ': ' . $error_msg; + } + } + else { + $stream = stream_socket_client('tcp://' . $hostname . ':' . $port, $error_nr, $error_msg, 5); + if (!$stream) { + return $error_nr . ': ' . $error_msg; + } + $banner = fread($stream, 512 ); + if (preg_match("/^220/i", $banner)) { // SMTP + fwrite($stream,"HELO tlsa.generator.local\r\n"); + fread($stream, 512); + fwrite($stream,"STARTTLS\r\n"); + fread($stream, 512); + } + elseif (preg_match("/imap.+starttls/i", $banner)) { // IMAP + fwrite($stream,"A1 STARTTLS\r\n"); + fread($stream, 512); + } + elseif (preg_match("/^\+OK/", $banner)) { // POP3 + fwrite($stream,"STLS\r\n"); + fread($stream, 512); + } + elseif (preg_match("/^OK/m", $banner)) { // Sieve + fwrite($stream,"STARTTLS\r\n"); + fread($stream, 512); + } + else { + return 'Unknown banner: "' . htmlspecialchars(trim($banner)) . '"'; + } + // Upgrade connection + stream_set_blocking($stream, true); + stream_context_set_option($stream, 'ssl', 'capture_peer_cert', true); + stream_context_set_option($stream, 'ssl', 'verify_peer', false); + stream_context_set_option($stream, 'ssl', 'verify_peer_name', false); + stream_context_set_option($stream, 'ssl', 'allow_self_signed', true); + stream_socket_enable_crypto($stream, true, STREAM_CRYPTO_METHOD_ANY_CLIENT); + stream_set_blocking($stream, false); + } + $params = stream_context_get_params($stream); + if (!empty($params['options']['ssl']['peer_certificate'])) { + $key_resource = openssl_pkey_get_public($params['options']['ssl']['peer_certificate']); + // We cannot get ['rsa']['n'], the binary data would contain BEGIN/END PUBLIC KEY + $key_data = openssl_pkey_get_details($key_resource)['key']; + return '3 1 1 ' . openssl_digest(pem_to_der($key_data), 'sha256'); + } + else { + return 'Error: Cannot read peer certificate'; + } +} +function alertbox_log_parser($_data) { + global $lang; + if (isset($_data['return'])) { + foreach ($_data['return'] as $return) { + // Get type + $type = $return['type']; + // If a lang[type][msg] string exists, use it as message + if (isset($return['type']) && isset($return['msg']) && !is_array($return['msg'])) { + if (isset($lang[$return['type']][$return['msg']])) { + $msg = $lang[$return['type']][$return['msg']]; + } + else { + $msg = $return['msg']; + } + } + // If msg is an array, use first element as language string and run printf on it with remaining array elements + elseif (is_array($return['msg'])) { + $msg = array_shift($return['msg']); + $msg = vsprintf( + $lang[$return['type']][$msg], + $return['msg'] + ); + } + else { + $msg = '-'; + } + $log_array[] = array('msg' => $msg, 'type' => json_encode($type)); + } + if (!empty($log_array)) { + return $log_array; + } + } + return false; +} +function verify_salted_hash($hash, $password, $algo, $salt_length) { + // Decode hash + $dhash = base64_decode($hash); + // Get first n bytes of binary which equals a SSHA hash + $ohash = substr($dhash, 0, $salt_length); + // Remove SSHA hash from decoded hash to get original salt string + $osalt = str_replace($ohash, '', $dhash); + // Check single salted SSHA hash against extracted hash + if (hash_equals(hash($algo, $password . $osalt, true), $ohash)) { + return true; + } + return false; +} +function verify_hash($hash, $password) { + if (preg_match('/^{(.+)}(.+)/i', $hash, $hash_array)) { + $scheme = strtoupper($hash_array[1]); + $hash = $hash_array[2]; + switch ($scheme) { + case "ARGON2I": + case "ARGON2ID": + case "BLF-CRYPT": + case "CRYPT": + case "DES-CRYPT": + case "MD5-CRYPT": + case "MD5": + case "SHA256-CRYPT": + case "SHA512-CRYPT": + return password_verify($password, $hash); + + case "CLEAR": + case "CLEARTEXT": + case "PLAIN": + return $password == $hash; + + case "LDAP-MD5": + $hash = base64_decode($hash); + return hash_equals(hash('md5', $password, true), $hash); + + case "PBKDF2": + $components = explode('$', $hash); + $salt = $components[2]; + $rounds = $components[3]; + $hash = $components[4]; + return hash_equals(hash_pbkdf2('sha1', $password, $salt, $rounds), $hash); + + case "PLAIN-MD4": + return hash_equals(hash('md4', $password), $hash); + + case "PLAIN-MD5": + return md5($password) == $hash; + + case "PLAIN-TRUNC": + $components = explode('-', $hash); + if (count($components) > 1) { + $trunc_len = $components[0]; + $trunc_password = $components[1]; + + return substr($password, 0, $trunc_len) == $trunc_password; + } else { + return $password == $hash; + } + + case "SHA": + case "SHA1": + case "SHA256": + case "SHA512": + // SHA is an alias for SHA1 + $scheme = $scheme == "SHA" ? "sha1" : strtolower($scheme); + $hash = base64_decode($hash); + return hash_equals(hash($scheme, $password, true), $hash); + + case "SMD5": + return verify_salted_hash($hash, $password, 'md5', 16); + + case "SSHA": + return verify_salted_hash($hash, $password, 'sha1', 20); + + case "SSHA256": + return verify_salted_hash($hash, $password, 'sha256', 32); + + case "SSHA512": + return verify_salted_hash($hash, $password, 'sha512', 64); + + default: + return false; + } + } + return false; +} +function check_login($user, $pass, $app_passwd_data = false) { + global $pdo; + global $redis; + global $imap_server; + + if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $user, '*'), + 'msg' => 'malformed_username' + ); + return false; + } + + // Validate admin + $user = strtolower(trim($user)); + $stmt = $pdo->prepare("SELECT `password` FROM `admin` + WHERE `superadmin` = '1' + AND `active` = '1' + AND `username` = :user"); + $stmt->execute(array(':user' => $user)); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($rows as $row) { + // verify password + if (verify_hash($row['password'], $pass)) { + // check for tfa authenticators + $authenticators = get_tfa($user); + if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) { + // active tfa authenticators found, set pending user login + $_SESSION['pending_mailcow_cc_username'] = $user; + $_SESSION['pending_mailcow_cc_role'] = "admin"; + $_SESSION['pending_tfa_methods'] = $authenticators['additional']; + unset($_SESSION['ldelay']); + $_SESSION['return'][] = array( + 'type' => 'info', + 'log' => array(__FUNCTION__, $user, '*'), + 'msg' => 'awaiting_tfa_confirmation' + ); + return "pending"; + } else { + unset($_SESSION['ldelay']); + // Reactivate TFA if it was set to "deactivate TFA for next login" + $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user"); + $stmt->execute(array(':user' => $user)); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $user, '*'), + 'msg' => array('logged_in_as', $user) + ); + return "admin"; + } + } + } + + // Validate domain admin + $stmt = $pdo->prepare("SELECT `password` FROM `admin` + WHERE `superadmin` = '0' + AND `active`='1' + AND `username` = :user"); + $stmt->execute(array(':user' => $user)); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($rows as $row) { + // verify password + if (verify_hash($row['password'], $pass) !== false) { + // check for tfa authenticators + $authenticators = get_tfa($user); + if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) { + $_SESSION['pending_mailcow_cc_username'] = $user; + $_SESSION['pending_mailcow_cc_role'] = "domainadmin"; + $_SESSION['pending_tfa_methods'] = $authenticators['additional']; + unset($_SESSION['ldelay']); + $_SESSION['return'][] = array( + 'type' => 'info', + 'log' => array(__FUNCTION__, $user, '*'), + 'msg' => 'awaiting_tfa_confirmation' + ); + return "pending"; + } + else { + unset($_SESSION['ldelay']); + // Reactivate TFA if it was set to "deactivate TFA for next login" + $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user"); + $stmt->execute(array(':user' => $user)); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $user, '*'), + 'msg' => array('logged_in_as', $user) + ); + return "domainadmin"; + } + } + } + + // Validate mailbox user + $stmt = $pdo->prepare("SELECT `password` FROM `mailbox` + INNER JOIN domain on mailbox.domain = domain.domain + WHERE `kind` NOT REGEXP 'location|thing|group' + AND `mailbox`.`active`='1' + AND `domain`.`active`='1' + AND `username` = :user"); + $stmt->execute(array(':user' => $user)); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + if ($app_passwd_data['eas'] === true) { + $stmt = $pdo->prepare("SELECT `app_passwd`.`password` as `password`, `app_passwd`.`id` as `app_passwd_id` FROM `app_passwd` + INNER JOIN `mailbox` ON `mailbox`.`username` = `app_passwd`.`mailbox` + INNER JOIN `domain` ON `mailbox`.`domain` = `domain`.`domain` + WHERE `mailbox`.`kind` NOT REGEXP 'location|thing|group' + AND `mailbox`.`active` = '1' + AND `domain`.`active` = '1' + AND `app_passwd`.`active` = '1' + AND `app_passwd`.`eas_access` = '1' + AND `app_passwd`.`mailbox` = :user"); + $stmt->execute(array(':user' => $user)); + $rows = array_merge($rows, $stmt->fetchAll(PDO::FETCH_ASSOC)); + } + elseif ($app_passwd_data['dav'] === true) { + $stmt = $pdo->prepare("SELECT `app_passwd`.`password` as `password`, `app_passwd`.`id` as `app_passwd_id` FROM `app_passwd` + INNER JOIN `mailbox` ON `mailbox`.`username` = `app_passwd`.`mailbox` + INNER JOIN `domain` ON `mailbox`.`domain` = `domain`.`domain` + WHERE `mailbox`.`kind` NOT REGEXP 'location|thing|group' + AND `mailbox`.`active` = '1' + AND `domain`.`active` = '1' + AND `app_passwd`.`active` = '1' + AND `app_passwd`.`dav_access` = '1' + AND `app_passwd`.`mailbox` = :user"); + $stmt->execute(array(':user' => $user)); + $rows = array_merge($rows, $stmt->fetchAll(PDO::FETCH_ASSOC)); + } + foreach ($rows as $row) { + // verify password + if (verify_hash($row['password'], $pass) !== false) { + if (!array_key_exists("app_passwd_id", $row)){ + // password is not a app password + // check for tfa authenticators + $authenticators = get_tfa($user); + if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && + $app_passwd_data['eas'] !== true && $app_passwd_data['dav'] !== true) { + // authenticators found, init TFA flow + $_SESSION['pending_mailcow_cc_username'] = $user; + $_SESSION['pending_mailcow_cc_role'] = "user"; + $_SESSION['pending_tfa_methods'] = $authenticators['additional']; + unset($_SESSION['ldelay']); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $user, '*'), + 'msg' => array('logged_in_as', $user) + ); + return "pending"; + } else if (!isset($authenticators['additional']) || !is_array($authenticators['additional']) || count($authenticators['additional']) == 0) { + // no authenticators found, login successfull + // Reactivate TFA if it was set to "deactivate TFA for next login" + $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user"); + $stmt->execute(array(':user' => $user)); + + unset($_SESSION['ldelay']); + return "user"; + } + } elseif ($app_passwd_data['eas'] === true || $app_passwd_data['dav'] === true) { + // password is a app password + $service = ($app_passwd_data['eas'] === true) ? 'EAS' : 'DAV'; + $stmt = $pdo->prepare("REPLACE INTO sasl_log (`service`, `app_password`, `username`, `real_rip`) VALUES (:service, :app_id, :username, :remote_addr)"); + $stmt->execute(array( + ':service' => $service, + ':app_id' => $row['app_passwd_id'], + ':username' => $user, + ':remote_addr' => ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR']) + )); + + unset($_SESSION['ldelay']); + return "user"; + } + } + } + + if (!isset($_SESSION['ldelay'])) { + $_SESSION['ldelay'] = "0"; + $redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']); + error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']); + } + elseif (!isset($_SESSION['mailcow_cc_username'])) { + $_SESSION['ldelay'] = $_SESSION['ldelay']+0.5; + $redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']); + error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']); + } + + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $user, '*'), + 'msg' => 'login_failed' + ); + + sleep($_SESSION['ldelay']); + return false; +} +function formatBytes($size, $precision = 2) { + if(!is_numeric($size)) { + return "0"; + } + $base = log($size, 1024); + $suffixes = array(' Byte', ' KiB', ' MiB', ' GiB', ' TiB'); + if ($size == "0") { + return "0"; + } + return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)]; +} +function update_sogo_static_view() { + if (getenv('SKIP_SOGO') == "y") { + return true; + } + global $pdo; + global $lang; + $stmt = $pdo->query("SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_NAME = 'sogo_view'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $stmt = $pdo->query("REPLACE INTO _sogo_static_view (`c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings`) + SELECT `c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings` from sogo_view"); + $stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');"); + } + flush_memcached(); +} +function edit_user_account($_data) { + global $lang; + global $pdo; + $_data_log = $_data; + !isset($_data_log['user_new_pass']) ?: $_data_log['user_new_pass'] = '*'; + !isset($_data_log['user_new_pass2']) ?: $_data_log['user_new_pass2'] = '*'; + !isset($_data_log['user_old_pass']) ?: $_data_log['user_old_pass'] = '*'; + $username = $_SESSION['mailcow_cc_username']; + $role = $_SESSION['mailcow_cc_role']; + $password_old = $_data['user_old_pass']; + if (filter_var($username, FILTER_VALIDATE_EMAIL === false) || $role != 'user') { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $stmt = $pdo->prepare("SELECT `password` FROM `mailbox` + WHERE `kind` NOT REGEXP 'location|thing|group' + AND `username` = :user"); + $stmt->execute(array(':user' => $username)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (!verify_hash($row['password'], $password_old)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + if (!empty($_data['user_new_pass']) && !empty($_data['user_new_pass2'])) { + $password_new = $_data['user_new_pass']; + $password_new2 = $_data['user_new_pass2']; + if (password_check($password_new, $password_new2) !== true) { + return false; + } + $password_hashed = hash_password($password_new); + $stmt = $pdo->prepare("UPDATE `mailbox` SET `password` = :password_hashed, + `attributes` = JSON_SET(`attributes`, '$.force_pw_update', '0'), + `attributes` = JSON_SET(`attributes`, '$.passwd_update', NOW()) + WHERE `username` = :username"); + $stmt->execute(array( + ':password_hashed' => $password_hashed, + ':username' => $username + )); + } + update_sogo_static_view(); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => array('mailbox_modified', htmlspecialchars($username)) + ); +} +function user_get_alias_details($username) { + global $pdo; + global $lang; + $data['direct_aliases'] = array(); + $data['shared_aliases'] = array(); + if ($_SESSION['mailcow_cc_role'] == "user") { + $username = $_SESSION['mailcow_cc_username']; + } + if (!filter_var($username, FILTER_VALIDATE_EMAIL)) { + return false; + } + if (!hasMailboxObjectAccess($username, $_SESSION['mailcow_cc_role'], $username)) { + return false; + } + $data['address'] = $username; + $stmt = $pdo->prepare("SELECT `address` AS `shared_aliases`, `public_comment` FROM `alias` + WHERE `goto` REGEXP :username_goto + AND `address` NOT LIKE '@%' + AND `goto` != :username_goto2 + AND `address` != :username_address"); + $stmt->execute(array( + ':username_goto' => '(^|,)'.$username.'($|,)', + ':username_goto2' => $username, + ':username_address' => $username + )); + $run = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($run)) { + $data['shared_aliases'][$row['shared_aliases']]['public_comment'] = htmlspecialchars($row['public_comment']); + //$data['shared_aliases'][] = $row['shared_aliases']; + } + + $stmt = $pdo->prepare("SELECT `address` AS `direct_aliases`, `public_comment` FROM `alias` + WHERE `goto` = :username_goto + AND `address` NOT LIKE '@%' + AND `address` != :username_address"); + $stmt->execute( + array( + ':username_goto' => $username, + ':username_address' => $username + )); + $run = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($run)) { + $data['direct_aliases'][$row['direct_aliases']]['public_comment'] = htmlspecialchars($row['public_comment']); + } + $stmt = $pdo->prepare("SELECT CONCAT(local_part, '@', alias_domain) AS `ad_alias`, `alias_domain` FROM `mailbox` + LEFT OUTER JOIN `alias_domain` on `target_domain` = `domain` + WHERE `username` = :username ;"); + $stmt->execute(array(':username' => $username)); + $run = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($run)) { + if (empty($row['ad_alias'])) { + continue; + } + $data['direct_aliases'][$row['ad_alias']]['public_comment'] = $lang['add']['alias_domain']; + $data['alias_domains'][] = $row['alias_domain']; + } + $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`send_as` SEPARATOR ', '), '') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :username AND `send_as` NOT LIKE '@%';"); + $stmt->execute(array(':username' => $username)); + $run = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($run)) { + $data['aliases_also_send_as'] = $row['send_as']; + } + $stmt = $pdo->prepare("SELECT CONCAT_WS(', ', IFNULL(GROUP_CONCAT(DISTINCT `send_as` SEPARATOR ', '), ''), GROUP_CONCAT(DISTINCT CONCAT('@',`alias_domain`) SEPARATOR ', ')) AS `send_as` FROM `sender_acl` LEFT JOIN `alias_domain` ON `alias_domain`.`target_domain` = TRIM(LEADING '@' FROM `send_as`) WHERE `logged_in_as` = :username AND `send_as` LIKE '@%';"); + $stmt->execute(array(':username' => $username)); + $run = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($run)) { + $data['aliases_send_as_all'] = $row['send_as']; + } + $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '') as `address` FROM `alias` WHERE `goto` REGEXP :username AND `address` LIKE '@%';"); + $stmt->execute(array(':username' => '(^|,)'.$username.'($|,)')); + $run = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($run)) { + $data['is_catch_all'] = $row['address']; + } + return $data; +} +function is_valid_domain_name($domain_name) { + if (empty($domain_name)) { + return false; + } + $domain_name = idn_to_ascii($domain_name, 0, INTL_IDNA_VARIANT_UTS46); + return (preg_match("/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i", $domain_name) + && preg_match("/^.{1,253}$/", $domain_name) + && preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name)); +} +function set_tfa($_data) { + global $pdo; + global $yubi; + global $tfa; + $_data_log = $_data; + $access_denied = null; + !isset($_data_log['confirm_password']) ?: $_data_log['confirm_password'] = '*'; + $username = $_SESSION['mailcow_cc_username']; + + // check for empty user and role + if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) $access_denied = true; + + // check admin confirm password + if ($access_denied === null) { + $stmt = $pdo->prepare("SELECT `password` FROM `admin` + WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if ($row) { + if (!verify_hash($row['password'], $_data["confirm_password"])) $access_denied = true; + else $access_denied = false; + } + } + + // check mailbox confirm password + if ($access_denied === null) { + $stmt = $pdo->prepare("SELECT `password` FROM `mailbox` + WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if ($row) { + if (!verify_hash($row['password'], $_data["confirm_password"])) $access_denied = true; + else $access_denied = false; + } + } + + // set access_denied error + if ($access_denied){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + + switch ($_data["tfa_method"]) { + case "yubi_otp": + $key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"]; + $yubico_id = $_data['yubico_id']; + $yubico_key = $_data['yubico_key']; + $yubi = new Auth_Yubico($yubico_id, $yubico_key); + if (!$yubi) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + if (!ctype_alnum($_data["otp_token"]) || strlen($_data["otp_token"]) != 44) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => 'tfa_token_invalid' + ); + return false; + } + $yauth = $yubi->verify($_data["otp_token"]); + if (PEAR::isError($yauth)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => array('yotp_verification_failed', $yauth->getMessage()) + ); + return false; + } + try { + // We could also do a modhex translation here + $yubico_modhex_id = substr($_data["otp_token"], 0, 12); + $stmt = $pdo->prepare("DELETE FROM `tfa` + WHERE `username` = :username + AND (`authmech` = 'yubi_otp' AND `secret` LIKE :modhex)"); + $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id)); + $stmt = $pdo->prepare("INSERT INTO `tfa` (`key_id`, `username`, `authmech`, `active`, `secret`) VALUES + (:key_id, :username, 'yubi_otp', '1', :secret)"); + $stmt->execute(array(':key_id' => $key_id, ':username' => $username, ':secret' => $yubico_id . ':' . $yubico_key . ':' . $yubico_modhex_id)); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => array('mysql_error', $e) + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => array('object_modified', htmlspecialchars($username)) + ); + break; + case "totp": + $key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"]; + if ($tfa->verifyCode($_POST['totp_secret'], $_POST['totp_confirm_token']) === true) { + //$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username"); + //$stmt->execute(array(':username' => $username)); + $stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `secret`, `active`) VALUES (?, ?, 'totp', ?, '1')"); + $stmt->execute(array($username, $key_id, $_POST['totp_secret'])); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => array('object_modified', $username) + ); + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => 'totp_verification_failed' + ); + } + break; + case "webauthn": + $key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"]; + + $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": + $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => array('object_modified', htmlspecialchars($username)) + ); + break; + } +} +function fido2($_data) { + global $pdo; + $_data_log = $_data; + // Not logging registration data, only actions + // Silent errors for "get" requests + switch ($_data["action"]) { + case "register": + $username = $_SESSION['mailcow_cc_username']; + if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data["action"]), + 'msg' => 'access_denied' + ); + return false; + } + $stmt = $pdo->prepare("INSERT INTO `fido2` (`username`, `rpId`, `credentialPublicKey`, `certificateChain`, `certificate`, `certificateIssuer`, `certificateSubject`, `signatureCounter`, `AAGUID`, `credentialId`) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + $stmt->execute(array( + $username, + $_data['registration']->rpId, + $_data['registration']->credentialPublicKey, + $_data['registration']->certificateChain, + $_data['registration']->certificate, + $_data['registration']->certificateIssuer, + $_data['registration']->certificateSubject, + $_data['registration']->signatureCounter, + $_data['registration']->AAGUID, + $_data['registration']->credentialId) + ); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_data["action"]), + 'msg' => array('object_modified', $username) + ); + break; + case "get_user_cids": + // Used to exclude existing CredentialIds while registering + $username = $_SESSION['mailcow_cc_username']; + if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) { + return false; + } + $stmt = $pdo->prepare("SELECT `credentialId` FROM `fido2` WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $cids[] = $row['credentialId']; + } + return $cids; + break; + case "get_all_cids": + // Only needed when using fido2 with username + $stmt = $pdo->query("SELECT `credentialId` FROM `fido2`"); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $cids[] = $row['credentialId']; + } + return $cids; + break; + case "get_by_b64cid": + if (!isset($_data['cid']) || empty($_data['cid'])) { + return false; + } + $stmt = $pdo->prepare("SELECT `certificateSubject`, `username`, `credentialPublicKey`, SHA2(`credentialId`, 256) AS `cid` FROM `fido2` WHERE `credentialId` = :cid"); + $stmt->execute(array(':cid' => base64_decode($_data['cid']))); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (empty($row) || empty($row['credentialPublicKey']) || empty($row['username'])) { + return false; + } + $data['pub_key'] = $row['credentialPublicKey']; + $data['username'] = $row['username']; + $data['subject'] = $row['certificateSubject']; + $data['cid'] = $row['cid']; + return $data; + break; + case "get_friendly_names": + $username = $_SESSION['mailcow_cc_username']; + if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) { + return false; + } + $stmt = $pdo->prepare("SELECT SHA2(`credentialId`, 256) AS `cid`, `created`, `certificateSubject`, `friendlyName` FROM `fido2` WHERE `username` = :username"); + $stmt->execute(array(':username' => $username)); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $fns[] = array( + "subject" => (empty($row['certificateSubject']) ? 'Unknown (' . $row['created'] . ')' : $row['certificateSubject']), + "fn" => $row['friendlyName'], + "cid" => $row['cid'] + ); + } + return $fns; + break; + case "unset_fido2_key": + $username = $_SESSION['mailcow_cc_username']; + if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data["action"]), + 'msg' => 'access_denied' + ); + return false; + } + $stmt = $pdo->prepare("DELETE FROM `fido2` WHERE `username` = :username AND SHA2(`credentialId`, 256) = :cid"); + $stmt->execute(array( + ':username' => $username, + ':cid' => $_data['post_data']['unset_fido2_key'] + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => array('object_modified', htmlspecialchars($username)) + ); + break; + case "edit_fn": + $username = $_SESSION['mailcow_cc_username']; + if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data["action"]), + 'msg' => 'access_denied' + ); + return false; + } + $stmt = $pdo->prepare("UPDATE `fido2` SET `friendlyName` = :friendlyName WHERE SHA2(`credentialId`, 256) = :cid AND `username` = :username"); + $stmt->execute(array( + ':username' => $username, + ':friendlyName' => $_data['fido2_attrs']['fido2_fn'], + ':cid' => $_data['fido2_attrs']['fido2_cid'] + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => array('object_modified', htmlspecialchars($username)) + ); + break; + } +} +function unset_tfa_key($_data) { + // Can only unset own keys + // Needs at least one key left + global $pdo; + global $lang; + $_data_log = $_data; + $access_denied = null; + $id = intval($_data['unset_tfa_key']); + $username = $_SESSION['mailcow_cc_username']; + + // check for empty user and role + if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) $access_denied = true; + + try { + if (!is_numeric($id)) $access_denied = true; + + // set access_denied error + if ($access_denied){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + + // check if it's last key + $stmt = $pdo->prepare("SELECT COUNT(*) AS `keys` FROM `tfa` + WHERE `username` = :username AND `active` = '1'"); + $stmt->execute(array(':username' => $username)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if ($row['keys'] == "1") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => 'last_key' + ); + return false; + } + + // delete key + $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `id` = :id"); + $stmt->execute(array(':username' => $username, ':id' => $id)); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => array('object_modified', $username) + ); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_data_log), + 'msg' => array('mysql_error', $e) + ); + return false; + } +} +function get_tfa($username = null, $id = null) { + global $pdo; + if (isset($_SESSION['mailcow_cc_username'])) { + $username = $_SESSION['mailcow_cc_username']; + } + elseif (empty($username)) { + return false; + } + + if (!isset($id)){ + // fetch all tfa methods - just get information about possible authenticators + $stmt = $pdo->prepare("SELECT `id`, `key_id`, `authmech` FROM `tfa` + WHERE `username` = :username AND `active` = '1'"); + $stmt->execute(array(':username' => $username)); + $results = $stmt->fetchAll(PDO::FETCH_ASSOC); + + // no tfa methods found + if (count($results) == 0) { + $data['name'] = 'none'; + $data['pretty'] = "-"; + $data['additional'] = array(); + return $data; + } + + $data['additional'] = $results; + return $data; + } else { + // fetch specific authenticator details by id + $stmt = $pdo->prepare("SELECT * FROM `tfa` + WHERE `username` = :username AND `id` = :id AND `active` = '1'"); + $stmt->execute(array(':username' => $username, ':id' => $id)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + if (isset($row["authmech"])) { + switch ($row["authmech"]) { + case "yubi_otp": + $data['name'] = "yubi_otp"; + $data['pretty'] = "Yubico OTP"; + $stmt = $pdo->prepare("SELECT `id`, `key_id`, RIGHT(`secret`, 12) AS 'modhex' FROM `tfa` WHERE `authmech` = 'yubi_otp' AND `username` = :username AND `id` = :id"); + $stmt->execute(array( + ':username' => $username, + ':id' => $id + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $data['additional'][] = $row; + } + return $data; + break; + // u2f - deprecated, should be removed + case "u2f": + $data['name'] = "u2f"; + $data['pretty'] = "Fido U2F"; + $stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username AND `id` = :id"); + $stmt->execute(array( + ':username' => $username, + ':id' => $id + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $data['additional'][] = $row; + } + return $data; + break; + case "hotp": + $data['name'] = "hotp"; + $data['pretty'] = "HMAC-based OTP"; + return $data; + break; + case "totp": + $data['name'] = "totp"; + $data['pretty'] = "Time-based OTP"; + $stmt = $pdo->prepare("SELECT `id`, `key_id`, `secret` FROM `tfa` WHERE `authmech` = 'totp' AND `username` = :username AND `id` = :id"); + $stmt->execute(array( + ':username' => $username, + ':id' => $id + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $data['additional'][] = $row; + } + return $data; + break; + case "webauthn": + $data['name'] = "webauthn"; + $data['pretty'] = "WebAuthn"; + $stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'webauthn' AND `username` = :username AND `id` = :id"); + $stmt->execute(array( + ':username' => $username, + ':id' => $id + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $data['additional'][] = $row; + } + return $data; + break; + default: + $data['name'] = 'none'; + $data['pretty'] = "-"; + return $data; + break; + } + } + else { + $data['name'] = 'none'; + $data['pretty'] = "-"; + return $data; + } + } +} +function verify_tfa_login($username, $_data) { + global $pdo; + global $yubi; + global $u2f; + global $tfa; + global $WebAuthn; + + if ($_data['tfa_method'] != 'u2f'){ + + switch ($_data["tfa_method"]) { + case "yubi_otp": + if (!ctype_alnum($_data['token']) || strlen($_data['token']) != 44) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => array('yotp_verification_failed', 'token length error') + ); + return false; + } + $yubico_modhex_id = substr($_data['token'], 0, 12); + $stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa` + WHERE `username` = :username + AND `authmech` = 'yubi_otp' + AND `active` = '1' + AND `secret` LIKE :modhex"); + $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + $yubico_auth = explode(':', $row['secret']); + $yubi = new Auth_Yubico($yubico_auth[0], $yubico_auth[1]); + $yauth = $yubi->verify($_data['token']); + if (PEAR::isError($yauth)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => array('yotp_verification_failed', $yauth->getMessage()) + ); + return false; + } + else { + $_SESSION['tfa_id'] = $row['id']; + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => 'verified_yotp_login' + ); + return true; + } + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => array('yotp_verification_failed', 'unknown') + ); + return false; + break; + case "hotp": + return false; + break; + case "totp": + try { + $stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa` + WHERE `username` = :username + AND `authmech` = 'totp' + AND `id` = :id + AND `active`='1'"); + $stmt->execute(array(':username' => $username, ':id' => $_data['id'])); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($rows as $row) { + if ($tfa->verifyCode($row['secret'], $_data['token']) === true) { + $_SESSION['tfa_id'] = $row['id']; + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => 'verified_totp_login' + ); + return true; + } + } + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => 'totp_verification_failed' + ); + return false; + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => array('mysql_error', $e) + ); + return false; + } + break; + 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 `id`, `key_id`, `keyHandle`, `username`, `publicKey` FROM `tfa` WHERE `id` = :id AND `active`='1'"); + $stmt->execute(array(':id' => $_data['id'])); + $process_webauthn = $stmt->fetch(PDO::FETCH_ASSOC); + + if (empty($process_webauthn)){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => array('webauthn_authenticator_failed') + ); + return false; + } + + if (empty($process_webauthn['publicKey']) || $process_webauthn['publicKey'] === false) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => array('webauthn_publickey_failed') + ); + return false; + } + + if ($process_webauthn['username'] != $_SESSION['pending_mailcow_cc_username']){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => array('webauthn_username_failed') + ); + 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 (!empty($row['username'])) { + $_SESSION["mailcow_cc_role"] = "user"; + } else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => array('webauthn_role_failed') + ); + return false; + } + } + + $_SESSION["mailcow_cc_username"] = $process_webauthn['username']; + $_SESSION['tfa_id'] = $process_webauthn['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: + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $username, '*'), + 'msg' => 'unknown_tfa_method' + ); + return false; + break; + } + + return false; + } else { + // delete old keys that used u2f + $stmt = $pdo->prepare("SELECT * FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username"); + $stmt->execute(array(':username' => $username)); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + if (count($rows) == 0) return false; + + $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username"); + $stmt->execute(array(':username' => $username)); + return true; + } +} +function admin_api($access, $action, $data = null) { + global $pdo; + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__), + 'msg' => 'access_denied' + ); + return false; + } + if ($access !== "ro" && $access !== "rw") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__), + 'msg' => 'invalid access type' + ); + return false; + } + if ($action == "edit") { + $active = (!empty($data['active'])) ? 1 : 0; + $skip_ip_check = (isset($data['skip_ip_check'])) ? 1 : 0; + $allow_from = array(); + if (isset($data['allow_from'])) { + $allow_from = array_map('trim', preg_split( "/( |,|;|\n)/", $data['allow_from'])); + } + foreach ($allow_from as $key => $val) { + if (empty($val)) { + unset($allow_from[$key]); + continue; + } + if (valid_network($val) !== true) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $data), + 'msg' => array('ip_invalid', htmlspecialchars($allow_from[$key])) + ); + unset($allow_from[$key]); + continue; + } + } + $allow_from = implode(',', array_unique(array_filter($allow_from))); + if (empty($allow_from) && $skip_ip_check == 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $data), + 'msg' => 'ip_list_empty' + ); + return false; + } + $api_key = implode('-', array( + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))) + )); + $stmt = $pdo->query("SELECT `api_key` FROM `api` WHERE `access` = '" . $access . "'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if (empty($num_results)) { + $stmt = $pdo->prepare("INSERT INTO `api` (`api_key`, `skip_ip_check`, `active`, `allow_from`, `access`) + VALUES (:api_key, :skip_ip_check, :active, :allow_from, :access);"); + $stmt->execute(array( + ':api_key' => $api_key, + ':skip_ip_check' => $skip_ip_check, + ':active' => $active, + ':allow_from' => $allow_from, + ':access' => $access + )); + } + else { + if ($skip_ip_check == 0) { + $stmt = $pdo->prepare("UPDATE `api` SET `skip_ip_check` = :skip_ip_check, + `active` = :active, + `allow_from` = :allow_from + WHERE `access` = :access;"); + $stmt->execute(array( + ':active' => $active, + ':skip_ip_check' => $skip_ip_check, + ':allow_from' => $allow_from, + ':access' => $access + )); + } + else { + $stmt = $pdo->prepare("UPDATE `api` SET `skip_ip_check` = :skip_ip_check, + `active` = :active + WHERE `access` = :access;"); + $stmt->execute(array( + ':active' => $active, + ':skip_ip_check' => $skip_ip_check, + ':access' => $access + )); + } + } + } + elseif ($action == "regen_key") { + $api_key = implode('-', array( + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))), + strtoupper(bin2hex(random_bytes(3))) + )); + $stmt = $pdo->prepare("UPDATE `api` SET `api_key` = :api_key WHERE `access` = :access"); + $stmt->execute(array( + ':api_key' => $api_key, + ':access' => $access + )); + } + elseif ($action == "get") { + $stmt = $pdo->query("SELECT * FROM `api` WHERE `access` = '" . $access . "'"); + $apidata = $stmt->fetch(PDO::FETCH_ASSOC); + if ($apidata !== false) { + $apidata['allow_from'] = str_replace(',', PHP_EOL, $apidata['allow_from']); + } + return $apidata; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $data), + 'msg' => 'admin_api_modified' + ); +} +function license($action, $data = null) { + global $pdo; + global $redis; + global $lang; + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__), + 'msg' => 'access_denied' + ); + return false; + } + switch ($action) { + case "verify": + // Keep result until revalidate button is pressed or session expired + $stmt = $pdo->query("SELECT `version` FROM `versions` WHERE `application` = 'GUID'"); + $versions = $stmt->fetch(PDO::FETCH_ASSOC); + $post = array('guid' => $versions['version']); + $curl = curl_init('https://verify.mailcow.email'); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10); + curl_setopt($curl, CURLOPT_POSTFIELDS, $post); + $response = curl_exec($curl); + curl_close($curl); + $json_return = json_decode($response, true); + if ($response && $json_return) { + if ($json_return['response'] === "ok") { + $_SESSION['gal']['valid'] = "true"; + $_SESSION['gal']['c'] = $json_return['c']; + $_SESSION['gal']['s'] = $json_return['s']; + if ($json_return['m'] == 'NoMoore') { + $_SESSION['gal']['m'] = '🐄'; + } + else { + $_SESSION['gal']['m'] = str_repeat('🐄', substr_count($json_return['m'], 'o')); + } + } + elseif ($json_return['response'] === "invalid") { + $_SESSION['gal']['valid'] = "false"; + $_SESSION['gal']['c'] = $lang['mailbox']['no']; + $_SESSION['gal']['s'] = $lang['mailbox']['no']; + $_SESSION['gal']['m'] = $lang['mailbox']['no']; + } + } + else { + $_SESSION['gal']['valid'] = "false"; + $_SESSION['gal']['c'] = $lang['danger']['temp_error']; + $_SESSION['gal']['s'] = $lang['danger']['temp_error']; + $_SESSION['gal']['m'] = $lang['danger']['temp_error']; + } + try { + // json_encode needs "true"/"false" instead of true/false, to not encode it to 0 or 1 + $redis->Set('LICENSE_STATUS_CACHE', json_encode($_SESSION['gal'])); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + return $_SESSION['gal']['valid']; + break; + case "guid": + $stmt = $pdo->query("SELECT `version` FROM `versions` WHERE `application` = 'GUID'"); + $versions = $stmt->fetch(PDO::FETCH_ASSOC); + return $versions['version']; + break; + } +} +function rspamd_ui($action, $data = null) { + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__), + 'msg' => 'access_denied' + ); + return false; + } + switch ($action) { + case "edit": + $rspamd_ui_pass = $data['rspamd_ui_pass']; + $rspamd_ui_pass2 = $data['rspamd_ui_pass2']; + if (empty($rspamd_ui_pass) || empty($rspamd_ui_pass2)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, '*', '*'), + 'msg' => 'password_empty' + ); + return false; + } + if ($rspamd_ui_pass != $rspamd_ui_pass2) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, '*', '*'), + 'msg' => 'password_mismatch' + ); + return false; + } + if (strlen($rspamd_ui_pass) < 6) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, '*', '*'), + 'msg' => 'rspamd_ui_pw_length' + ); + return false; + } + $docker_return = docker('post', 'rspamd-mailcow', 'exec', array('cmd' => 'rspamd', 'task' => 'worker_password', 'raw' => $rspamd_ui_pass), array('Content-Type: application/json')); + if ($docker_return_array = json_decode($docker_return, true)) { + if ($docker_return_array['type'] == 'success') { + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, '*', '*'), + 'msg' => 'rspamd_ui_pw_set' + ); + return true; + } + else { + $_SESSION['return'][] = array( + 'type' => $docker_return_array['type'], + 'log' => array(__FUNCTION__, '*', '*'), + 'msg' => $docker_return_array['msg'] + ); + return false; + } + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, '*', '*'), + 'msg' => 'unknown' + ); + return false; + } + break; + } +} + +function get_logs($application, $lines = false) { + if ($lines === false) { + $lines = $GLOBALS['LOG_LINES'] - 1; + } + elseif(is_numeric($lines) && $lines >= 1) { + $lines = abs(intval($lines) - 1); + } + else { + list ($from, $to) = explode('-', $lines); + $from = intval($from); + $to = intval($to); + if ($from < 1 || $to < $from) { return false; } + } + global $redis; + global $pdo; + if ($_SESSION['mailcow_cc_role'] != "admin") { + return false; + } + // SQL + if ($application == "mailcow-ui") { + if (isset($from) && isset($to)) { + $stmt = $pdo->prepare("SELECT * FROM `logs` ORDER BY `id` DESC LIMIT :from, :to"); + $stmt->execute(array( + ':from' => $from - 1, + ':to' => $to + )); + $data = $stmt->fetchAll(PDO::FETCH_ASSOC); + } + else { + $stmt = $pdo->prepare("SELECT * FROM `logs` ORDER BY `id` DESC LIMIT :lines"); + $stmt->execute(array( + ':lines' => $lines + 1, + )); + $data = $stmt->fetchAll(PDO::FETCH_ASSOC); + } + if (is_array($data)) { + return $data; + } + } + if ($application == "sasl") { + if (isset($from) && isset($to)) { + $stmt = $pdo->prepare("SELECT * FROM `sasl_log` ORDER BY `datetime` DESC LIMIT :from, :to"); + $stmt->execute(array( + ':from' => $from - 1, + ':to' => $to + )); + $data = $stmt->fetchAll(PDO::FETCH_ASSOC); + } + else { + $stmt = $pdo->prepare("SELECT * FROM `sasl_log` ORDER BY `datetime` DESC LIMIT :lines"); + $stmt->execute(array( + ':lines' => $lines + 1, + )); + $data = $stmt->fetchAll(PDO::FETCH_ASSOC); + } + if (is_array($data)) { + return $data; + } + } + // Redis + if ($application == "dovecot-mailcow") { + if (isset($from) && isset($to)) { + $data = $redis->lRange('DOVECOT_MAILLOG', $from - 1, $to - 1); + } + else { + $data = $redis->lRange('DOVECOT_MAILLOG', 0, $lines); + } + if ($data) { + foreach ($data as $json_line) { + $data_array[] = json_decode($json_line, true); + } + return $data_array; + } + } + if ($application == "postfix-mailcow") { + if (isset($from) && isset($to)) { + $data = $redis->lRange('POSTFIX_MAILLOG', $from - 1, $to - 1); + } + else { + $data = $redis->lRange('POSTFIX_MAILLOG', 0, $lines); + } + if ($data) { + foreach ($data as $json_line) { + $data_array[] = json_decode($json_line, true); + } + return $data_array; + } + } + if ($application == "sogo-mailcow") { + if (isset($from) && isset($to)) { + $data = $redis->lRange('SOGO_LOG', $from - 1, $to - 1); + } + else { + $data = $redis->lRange('SOGO_LOG', 0, $lines); + } + if ($data) { + foreach ($data as $json_line) { + $data_array[] = json_decode($json_line, true); + } + return $data_array; + } + } + if ($application == "watchdog-mailcow") { + if (isset($from) && isset($to)) { + $data = $redis->lRange('WATCHDOG_LOG', $from - 1, $to - 1); + } + else { + $data = $redis->lRange('WATCHDOG_LOG', 0, $lines); + } + if ($data) { + foreach ($data as $json_line) { + $data_array[] = json_decode($json_line, true); + } + return $data_array; + } + } + if ($application == "acme-mailcow") { + if (isset($from) && isset($to)) { + $data = $redis->lRange('ACME_LOG', $from - 1, $to - 1); + } + else { + $data = $redis->lRange('ACME_LOG', 0, $lines); + } + if ($data) { + foreach ($data as $json_line) { + $data_array[] = json_decode($json_line, true); + } + return $data_array; + } + } + if ($application == "ratelimited") { + if (isset($from) && isset($to)) { + $data = $redis->lRange('RL_LOG', $from - 1, $to - 1); + } + else { + $data = $redis->lRange('RL_LOG', 0, $lines); + } + if ($data) { + foreach ($data as $json_line) { + $data_array[] = json_decode($json_line, true); + } + return $data_array; + } + } + if ($application == "api-mailcow") { + if (isset($from) && isset($to)) { + $data = $redis->lRange('API_LOG', $from - 1, $to - 1); + } + else { + $data = $redis->lRange('API_LOG', 0, $lines); + } + if ($data) { + foreach ($data as $json_line) { + $data_array[] = json_decode($json_line, true); + } + return $data_array; + } + } + if ($application == "netfilter-mailcow") { + if (isset($from) && isset($to)) { + $data = $redis->lRange('NETFILTER_LOG', $from - 1, $to - 1); + } + else { + $data = $redis->lRange('NETFILTER_LOG', 0, $lines); + } + if ($data) { + foreach ($data as $json_line) { + $data_array[] = json_decode($json_line, true); + } + return $data_array; + } + } + if ($application == "autodiscover-mailcow") { + if (isset($from) && isset($to)) { + $data = $redis->lRange('AUTODISCOVER_LOG', $from - 1, $to - 1); + } + else { + $data = $redis->lRange('AUTODISCOVER_LOG', 0, $lines); + } + if ($data) { + foreach ($data as $json_line) { + $data_array[] = json_decode($json_line, true); + } + return $data_array; + } + } + if ($application == "rspamd-history") { + $curl = curl_init(); + curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); + if (!is_numeric($lines)) { + list ($from, $to) = explode('-', $lines); + curl_setopt($curl, CURLOPT_URL,"http://rspamd/history?from=" . intval($from) . "&to=" . intval($to)); + } + else { + curl_setopt($curl, CURLOPT_URL,"http://rspamd/history?to=" . intval($lines)); + } + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + $history = curl_exec($curl); + if (!curl_errno($curl)) { + $data_array = json_decode($history, true); + curl_close($curl); + return $data_array['rows']; + } + curl_close($curl); + return false; + } + if ($application == "rspamd-stats") { + $curl = curl_init(); + curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); + curl_setopt($curl, CURLOPT_URL,"http://rspamd/stat"); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + $stats = curl_exec($curl); + if (!curl_errno($curl)) { + $data_array = json_decode($stats, true); + curl_close($curl); + return $data_array; + } + curl_close($curl); + return false; + } + return false; +} +function getGUID() { + if (function_exists('com_create_guid')) { + return com_create_guid(); + } + mt_srand((double)microtime()*10000);//optional for php 4.2.0 and up. + $charid = strtoupper(md5(uniqid(rand(), true))); + $hyphen = chr(45);// "-" + return substr($charid, 0, 8).$hyphen + .substr($charid, 8, 4).$hyphen + .substr($charid,12, 4).$hyphen + .substr($charid,16, 4).$hyphen + .substr($charid,20,12); +} +function solr_status() { + $curl = curl_init(); + $endpoint = 'http://solr:8983/solr/admin/cores'; + $params = array( + 'action' => 'STATUS', + 'core' => 'dovecot-fts', + 'indexInfo' => 'true' + ); + $url = $endpoint . '?' . http_build_query($params); + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_POST, 0); + curl_setopt($curl, CURLOPT_TIMEOUT, 10); + $response_core = curl_exec($curl); + if ($response_core === false) { + $err = curl_error($curl); + curl_close($curl); + return false; + } + else { + curl_close($curl); + $curl = curl_init(); + $status_core = json_decode($response_core, true); + $url = 'http://solr:8983/solr/admin/info/system'; + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_POST, 0); + curl_setopt($curl, CURLOPT_TIMEOUT, 10); + $response_sysinfo = curl_exec($curl); + if ($response_sysinfo === false) { + $err = curl_error($curl); + curl_close($curl); + return false; + } + else { + curl_close($curl); + $status_sysinfo = json_decode($response_sysinfo, true); + $status = array_merge($status_core, $status_sysinfo); + return (!empty($status['status']['dovecot-fts']) && !empty($status['jvm']['memory'])) ? $status : false; + } + return (!empty($status['status']['dovecot-fts'])) ? $status['status']['dovecot-fts'] : false; + } + return false; +} + +function cleanupJS($ignore = '', $folder = '/tmp/*.js') { + $now = time(); + foreach (glob($folder) as $filename) { + if(strpos($filename, $ignore) !== false) { + continue; + } + if (is_file($filename)) { + if ($now - filemtime($filename) >= 60 * 60) { + unlink($filename); + } + } + } +} + +function cleanupCSS($ignore = '', $folder = '/tmp/*.css') { + $now = time(); + foreach (glob($folder) as $filename) { + if(strpos($filename, $ignore) !== false) { + continue; + } + if (is_file($filename)) { + if ($now - filemtime($filename) >= 60 * 60) { + unlink($filename); + } + } + } +} + +?> diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index 4529ee7b..24b9f810 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -1,5270 +1,5270 @@ - 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - if (isset($_data['username']) && filter_var($_data['username'], FILTER_VALIDATE_EMAIL)) { - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data['username'])) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - else { - $username = $_data['username']; - } - } - else { - $username = $_SESSION['mailcow_cc_username']; - } - if (isset($_data["validity"]) && !filter_var($_data["validity"], FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 87600)))) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'validity_missing' - ); - return false; - } - else { - // Default to 1 yr - $_data["validity"] = 8760; - } - $domain = $_data['domain']; - $valid_domains[] = mailbox('get', 'mailbox_details', $username)['domain']; - $valid_alias_domains = user_get_alias_details($username)['alias_domains']; - if (!empty($valid_alias_domains)) { - $valid_domains = array_merge($valid_domains, $valid_alias_domains); - } - if (!in_array($domain, $valid_domains)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'domain_invalid' - ); - return false; - } - $validity = strtotime("+" . $_data["validity"] . " hour"); - $stmt = $pdo->prepare("INSERT INTO `spamalias` (`address`, `goto`, `validity`) VALUES - (:address, :goto, :validity)"); - $stmt->execute(array( - ':address' => readable_random_string(rand(rand(3, 9), rand(3, 9))) . '.' . readable_random_string(rand(rand(3, 9), rand(3, 9))) . '@' . $domain, - ':goto' => $username, - ':validity' => $validity - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('mailbox_modified', $username) - ); - break; - case 'global_filter': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - $sieve = new Sieve\SieveParser(); - $script_data = $_data['script_data']; - $script_data = str_replace("\r\n", "\n", $script_data); // windows -> unix - $script_data = str_replace("\r", "\n", $script_data); // remaining -> unix - $filter_type = $_data['filter_type']; - try { - $sieve->parse($script_data); - } - catch (Exception $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('sieve_error', $e->getMessage()) - ); - return false; - } - if ($filter_type == 'prefilter') { - try { - if (file_exists('/global_sieve/before')) { - $filter_handle = fopen('/global_sieve/before', 'w'); - if (!$filter_handle) { - throw new Exception($lang['danger']['file_open_error']); - } - fwrite($filter_handle, $script_data); - fclose($filter_handle); - } - $restart_response = json_decode(docker('post', 'dovecot-mailcow', 'restart'), true); - if ($restart_response['type'] == "success") { - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'dovecot_restart_success' - ); - } - else { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'dovecot_restart_failed' - ); - } - } - catch (Exception $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('global_filter_write_error', htmlspecialchars($e->getMessage())) - ); - return false; - } - } - elseif ($filter_type == 'postfilter') { - try { - if (file_exists('/global_sieve/after')) { - $filter_handle = fopen('/global_sieve/after', 'w'); - if (!$filter_handle) { - throw new Exception($lang['danger']['file_open_error']); - } - fwrite($filter_handle, $script_data); - fclose($filter_handle); - } - $restart_response = json_decode(docker('post', 'dovecot-mailcow', 'restart'), true); - if ($restart_response['type'] == "success") { - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'dovecot_restart_success' - ); - } - else { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'dovecot_restart_failed' - ); - } - } - catch (Exception $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('global_filter_write_error', htmlspecialchars($e->getMessage())) - ); - return false; - } - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'invalid_filter_type' - ); - return false; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'global_filter_written' - ); - return true; - case 'filter': - $sieve = new Sieve\SieveParser(); - if (!isset($_SESSION['acl']['filters']) || $_SESSION['acl']['filters'] != "1" ) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - if (isset($_data['username']) && filter_var($_data['username'], FILTER_VALIDATE_EMAIL)) { - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data['username'])) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - else { - $username = $_data['username']; - } - } - elseif ($_SESSION['mailcow_cc_role'] == "user") { - $username = $_SESSION['mailcow_cc_username']; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'no_user_defined' - ); - return false; - } - $active = intval($_data['active']); - $script_data = $_data['script_data']; - $script_desc = $_data['script_desc']; - $filter_type = $_data['filter_type']; - if (empty($script_data)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'script_empty' - ); - return false; - } - try { - $sieve->parse($script_data); - } - catch (Exception $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('sieve_error', $e->getMessage()) - ); - return false; - } - if (empty($script_data) || empty($script_desc)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'value_missing' - ); - return false; - } - if ($filter_type != 'postfilter' && $filter_type != 'prefilter') { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'filter_type' - ); - return false; - } - if (!empty($active)) { - $script_name = 'active'; - $stmt = $pdo->prepare("UPDATE `sieve_filters` SET `script_name` = 'inactive' WHERE `username` = :username AND `filter_type` = :filter_type"); - $stmt->execute(array( - ':username' => $username, - ':filter_type' => $filter_type - )); - } - else { - $script_name = 'inactive'; - } - $stmt = $pdo->prepare("INSERT INTO `sieve_filters` (`username`, `script_data`, `script_desc`, `script_name`, `filter_type`) - VALUES (:username, :script_data, :script_desc, :script_name, :filter_type)"); - $stmt->execute(array( - ':username' => $username, - ':script_data' => $script_data, - ':script_desc' => $script_desc, - ':script_name' => $script_name, - ':filter_type' => $filter_type - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('mailbox_modified', $username) - ); - break; - case 'syncjob': - if (!isset($_SESSION['acl']['syncjobs']) || $_SESSION['acl']['syncjobs'] != "1" ) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - if (isset($_data['username']) && filter_var($_data['username'], FILTER_VALIDATE_EMAIL)) { - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data['username'])) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - else { - $username = $_data['username']; - } - } - elseif ($_SESSION['mailcow_cc_role'] == "user") { - $username = $_SESSION['mailcow_cc_username']; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'no_user_defined' - ); - return false; - } - $active = intval($_data['active']); - $subscribeall = intval($_data['subscribeall']); - $delete2duplicates = intval($_data['delete2duplicates']); - $delete1 = intval($_data['delete1']); - $delete2 = intval($_data['delete2']); - $timeout1 = intval($_data['timeout1']); - $timeout2 = intval($_data['timeout2']); - $skipcrossduplicates = intval($_data['skipcrossduplicates']); - $automap = intval($_data['automap']); - $port1 = $_data['port1']; - $host1 = strtolower($_data['host1']); - $password1 = $_data['password1']; - $exclude = $_data['exclude']; - $maxage = $_data['maxage']; - $maxbytespersecond = $_data['maxbytespersecond']; - $subfolder2 = $_data['subfolder2']; - $user1 = $_data['user1']; - $mins_interval = $_data['mins_interval']; - $enc1 = $_data['enc1']; - $custom_params = (empty(trim($_data['custom_params']))) ? '' : trim($_data['custom_params']); - - // validate custom params - foreach (explode('-', $custom_params) as $param){ - if(empty($param)) continue; - - // extract option - if (str_contains($param, '=')) $param = explode('=', $param)[0]; - else $param = rtrim($param, ' '); - // remove first char if first char is - - if ($param[0] == '-') $param = ltrim($param, $param[0]); - - if (str_contains($param, ' ')) { - // bad char - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'bad character SPACE' - ); - return false; - } - - // check if param is whitelisted - if (!in_array(strtolower($param), $GLOBALS["IMAPSYNC_OPTIONS"]["whitelist"])){ - // bad option - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'bad option '. $param - ); - return false; - } - } - if (empty($subfolder2)) { - $subfolder2 = ""; - } - if (!isset($maxage) || !filter_var($maxage, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) { - $maxage = "0"; - } - if (!isset($timeout1) || !filter_var($timeout1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) { - $timeout1 = "600"; - } - if (!isset($timeout2) || !filter_var($timeout2, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) { - $timeout2 = "600"; - } - if (!isset($maxbytespersecond) || !filter_var($maxbytespersecond, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 125000000)))) { - $maxbytespersecond = "0"; - } - if (!filter_var($port1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 65535)))) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - if (!filter_var($mins_interval, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 43800)))) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - // if (!is_valid_domain_name($host1)) { - // $_SESSION['return'][] = array( - // 'type' => 'danger', - // 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - // 'msg' => 'access_denied' - // ); - // return false; - // } - if ($enc1 != "TLS" && $enc1 != "SSL" && $enc1 != "PLAIN") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - if (@preg_match("/" . $exclude . "/", null) === false) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - $stmt = $pdo->prepare("SELECT '1' FROM `imapsync` - WHERE `user2` = :user2 AND `user1` = :user1 AND `host1` = :host1"); - $stmt->execute(array(':user1' => $user1, ':user2' => $username, ':host1' => $host1)); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('object_exists', htmlspecialchars($host1 . ' / ' . $user1)) - ); - return false; - } - $stmt = $pdo->prepare("INSERT INTO `imapsync` (`user2`, `exclude`, `delete1`, `delete2`, `timeout1`, `timeout2`, `automap`, `skipcrossduplicates`, `maxbytespersecond`, `subscribeall`, `maxage`, `subfolder2`, `host1`, `authmech1`, `user1`, `password1`, `mins_interval`, `port1`, `enc1`, `delete2duplicates`, `custom_params`, `active`) - VALUES (:user2, :exclude, :delete1, :delete2, :timeout1, :timeout2, :automap, :skipcrossduplicates, :maxbytespersecond, :subscribeall, :maxage, :subfolder2, :host1, :authmech1, :user1, :password1, :mins_interval, :port1, :enc1, :delete2duplicates, :custom_params, :active)"); - $stmt->execute(array( - ':user2' => $username, - ':custom_params' => $custom_params, - ':exclude' => $exclude, - ':maxage' => $maxage, - ':delete1' => $delete1, - ':delete2' => $delete2, - ':timeout1' => $timeout1, - ':timeout2' => $timeout2, - ':automap' => $automap, - ':skipcrossduplicates' => $skipcrossduplicates, - ':maxbytespersecond' => $maxbytespersecond, - ':subscribeall' => $subscribeall, - ':subfolder2' => $subfolder2, - ':host1' => $host1, - ':authmech1' => 'PLAIN', - ':user1' => $user1, - ':password1' => $password1, - ':mins_interval' => $mins_interval, - ':port1' => $port1, - ':enc1' => $enc1, - ':delete2duplicates' => $delete2duplicates, - ':active' => $active, - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('mailbox_modified', $username) - ); - break; - case 'domain': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), - 'msg' => 'access_denied' - ); - return false; - } - $domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46); - $description = $_data['description']; - if (empty($description)) $description = $domain; - $tags = (array)$_data['tags']; - $aliases = (int)$_data['aliases']; - $mailboxes = (int)$_data['mailboxes']; - $defquota = (int)$_data['defquota']; - $maxquota = (int)$_data['maxquota']; - $restart_sogo = (int)$_data['restart_sogo']; - $quota = (int)$_data['quota']; - if ($defquota > $maxquota) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'mailbox_defquota_exceeds_mailbox_maxquota' - ); - return false; - } - if ($maxquota > $quota) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'mailbox_quota_exceeds_domain_quota' - ); - return false; - } - if ($defquota == "0" || empty($defquota)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'defquota_empty' - ); - return false; - } - if ($maxquota == "0" || empty($maxquota)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'maxquota_empty' - ); - return false; - } - $active = intval($_data['active']); - $relay_all_recipients = intval($_data['relay_all_recipients']); - $relay_unknown_only = intval($_data['relay_unknown_only']); - $backupmx = intval($_data['backupmx']); - $gal = intval($_data['gal']); - if ($relay_all_recipients == 1) { - $backupmx = '1'; - } - if ($relay_unknown_only == 1) { - $backupmx = 1; - $relay_all_recipients = 1; - } - if (!is_valid_domain_name($domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'domain_invalid' - ); - return false; - } - foreach (array($quota, $maxquota, $mailboxes, $aliases) as $data) { - if (!is_numeric($data)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('object_is_not_numeric', htmlspecialchars($data)) - ); - return false; - } - } - $stmt = $pdo->prepare("SELECT `domain` FROM `domain` - WHERE `domain` = :domain"); - $stmt->execute(array(':domain' => $domain)); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` - WHERE `alias_domain` = :domain"); - $stmt->execute(array(':domain' => $domain)); - $num_results = $num_results + count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('domain_exists', htmlspecialchars($domain)) - ); - return false; - } - if ($domain == getenv('MAILCOW_HOSTNAME')) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'domain_cannot_match_hostname' - ); - return false; - } - - $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `external` = 1 AND `send_as` LIKE :domain"); - $stmt->execute(array( - ':domain' => '%@' . $domain - )); - // save domain - $stmt = $pdo->prepare("INSERT INTO `domain` (`domain`, `description`, `aliases`, `mailboxes`, `defquota`, `maxquota`, `quota`, `backupmx`, `gal`, `active`, `relay_unknown_only`, `relay_all_recipients`) - VALUES (:domain, :description, :aliases, :mailboxes, :defquota, :maxquota, :quota, :backupmx, :gal, :active, :relay_unknown_only, :relay_all_recipients)"); - $stmt->execute(array( - ':domain' => $domain, - ':description' => $description, - ':aliases' => $aliases, - ':mailboxes' => $mailboxes, - ':defquota' => $defquota, - ':maxquota' => $maxquota, - ':quota' => $quota, - ':backupmx' => $backupmx, - ':gal' => $gal, - ':active' => $active, - ':relay_unknown_only' => $relay_unknown_only, - ':relay_all_recipients' => $relay_all_recipients - )); - // save tags - foreach($tags as $index => $tag){ - if (empty($tag)) continue; - if ($index > $GLOBALS['TAGGING_LIMIT']) { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('tag_limit_exceeded', 'limit '.$GLOBALS['TAGGING_LIMIT']) - ); - break; - } - $stmt = $pdo->prepare("INSERT INTO `tags_domain` (`domain`, `tag_name`) VALUES (:domain, :tag_name)"); - $stmt->execute(array( - ':domain' => $domain, - ':tag_name' => $tag, - )); - } - - try { - $redis->hSet('DOMAIN_MAP', $domain, 1); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('redis_error', $e) - ); - return false; - } - if (!empty(intval($_data['rl_value']))) { - ratelimit('edit', 'domain', array('rl_value' => $_data['rl_value'], 'rl_frame' => $_data['rl_frame'], 'object' => $domain)); - } - if (!empty($_data['key_size']) && !empty($_data['dkim_selector'])) { - if (!empty($redis->hGet('DKIM_SELECTORS', $domain))) { - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'domain_add_dkim_available' - ); - } - else { - dkim('add', array('key_size' => $_data['key_size'], 'dkim_selector' => $_data['dkim_selector'], 'domains' => $domain)); - } - } - if (!empty($restart_sogo)) { - $restart_response = json_decode(docker('post', 'sogo-mailcow', 'restart'), true); - if ($restart_response['type'] == "success") { - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('domain_added', htmlspecialchars($domain)) - ); - return true; - } - else { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'domain_added_sogo_failed' - ); - return false; - } - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('domain_added', htmlspecialchars($domain)) - ); - return true; - break; - case 'alias': - $addresses = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['address'])); - $gotos = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['goto'])); - $active = intval($_data['active']); - $sogo_visible = intval($_data['sogo_visible']); - $goto_null = intval($_data['goto_null']); - $goto_spam = intval($_data['goto_spam']); - $goto_ham = intval($_data['goto_ham']); - $private_comment = $_data['private_comment']; - $public_comment = $_data['public_comment']; - if (strlen($private_comment) > 160 | strlen($public_comment) > 160){ - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'comment_too_long' - ); - return false; - } - if (empty($addresses[0])) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'alias_empty' - ); - return false; - } - if (empty($gotos[0]) && ($goto_null + $goto_spam + $goto_ham == 0)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'goto_empty' - ); - return false; - } - if ($goto_null == "1") { - $goto = "null@localhost"; - } - elseif ($goto_spam == "1") { - $goto = "spam@localhost"; - } - elseif ($goto_ham == "1") { - $goto = "ham@localhost"; - } - else { - foreach ($gotos as $i => &$goto) { - if (empty($goto)) { - continue; - } - $goto_domain = idn_to_ascii(substr(strstr($goto, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46); - $goto_local_part = strstr($goto, '@', true); - $goto = $goto_local_part.'@'.$goto_domain; - $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` - WHERE `kind` REGEXP 'location|thing|group' - AND `username`= :goto"); - $stmt->execute(array(':goto' => $goto)); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('goto_invalid', htmlspecialchars($goto)) - ); - unset($gotos[$i]); - continue; - } - if (!filter_var($goto, FILTER_VALIDATE_EMAIL) === true) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('goto_invalid', htmlspecialchars($goto)) - ); - unset($gotos[$i]); - continue; - } - } - $gotos = array_unique($gotos); - $gotos = array_filter($gotos); - if (empty($gotos)) { return false; } - $goto = implode(",", (array)$gotos); - } - foreach ($addresses as $address) { - if (empty($address)) { - continue; - } - if (in_array($address, $gotos)) { - continue; - } - $domain = idn_to_ascii(substr(strstr($address, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46); - $local_part = strstr($address, '@', true); - $address = $local_part.'@'.$domain; - $domaindata = mailbox('get', 'domain_details', $domain); - if (is_array($domaindata) && $domaindata['aliases_left'] == "0") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'max_alias_exceeded' - ); - return false; - } - $stmt = $pdo->prepare("SELECT `address` FROM `alias` - WHERE `address`= :address OR `address` IN ( - SELECT `username` FROM `mailbox`, `alias_domain` - WHERE ( - `alias_domain`.`alias_domain` = :address_d - AND `mailbox`.`username` = CONCAT(:address_l, '@', alias_domain.target_domain)))"); - $stmt->execute(array( - ':address' => $address, - ':address_l' => $local_part, - ':address_d' => $domain - )); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('is_alias_or_mailbox', htmlspecialchars($address)) - ); - continue; - } - $stmt = $pdo->prepare("SELECT `domain` FROM `domain` - WHERE `domain`= :domain1 OR `domain` = (SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain2)"); - $stmt->execute(array(':domain1' => $domain, ':domain2' => $domain)); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results == 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('domain_not_found', htmlspecialchars($domain)) - ); - continue; - } - $stmt = $pdo->prepare("SELECT `address` FROM `spamalias` - WHERE `address`= :address"); - $stmt->execute(array(':address' => $address)); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('is_spam_alias', htmlspecialchars($address)) - ); - continue; - } - if ((!filter_var($address, FILTER_VALIDATE_EMAIL) === true) && !empty($local_part)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('alias_invalid', $address) - ); - continue; - } - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - $stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `public_comment`, `private_comment`, `goto`, `domain`, `sogo_visible`, `active`) - VALUES (:address, :public_comment, :private_comment, :goto, :domain, :sogo_visible, :active)"); - if (!filter_var($address, FILTER_VALIDATE_EMAIL) === true) { - $stmt->execute(array( - ':address' => '@'.$domain, - ':public_comment' => $public_comment, - ':private_comment' => $private_comment, - ':address' => '@'.$domain, - ':goto' => $goto, - ':domain' => $domain, - ':sogo_visible' => $sogo_visible, - ':active' => $active - )); - } - else { - $stmt->execute(array( - ':address' => $address, - ':public_comment' => $public_comment, - ':private_comment' => $private_comment, - ':goto' => $goto, - ':domain' => $domain, - ':sogo_visible' => $sogo_visible, - ':active' => $active - )); - } - $id = $pdo->lastInsertId(); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('alias_added', $address, $id) - ); - } - break; - case 'alias_domain': - $active = intval($_data['active']); - $alias_domains = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['alias_domain'])); - $alias_domains = array_filter($alias_domains); - $target_domain = idn_to_ascii(strtolower(trim($_data['target_domain'])), 0, INTL_IDNA_VARIANT_UTS46); - if (!isset($_SESSION['acl']['alias_domains']) || $_SESSION['acl']['alias_domains'] != "1" ) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - if (!is_valid_domain_name($target_domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'target_domain_invalid' - ); - return false; - } - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $target_domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - foreach ($alias_domains as $i => $alias_domain) { - $alias_domain = idn_to_ascii(strtolower(trim($alias_domain)), 0, INTL_IDNA_VARIANT_UTS46); - if (!is_valid_domain_name($alias_domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('alias_domain_invalid', htmlspecialchars(alias_domain)) - ); - continue; - } - if ($alias_domain == $target_domain) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('aliasd_targetd_identical', htmlspecialchars($target_domain)) - ); - continue; - } - $stmt = $pdo->prepare("SELECT `domain` FROM `domain` - WHERE `domain`= :target_domain"); - $stmt->execute(array(':target_domain' => $target_domain)); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results == 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('targetd_not_found', htmlspecialchars($target_domain)) - ); - continue; - } - $stmt = $pdo->prepare("SELECT `domain` FROM `domain` - WHERE `domain`= :target_domain AND `backupmx` = '1'"); - $stmt->execute(array(':target_domain' => $target_domain)); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results == 1) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('targetd_relay_domain', htmlspecialchars($target_domain)) - ); - continue; - } - $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `alias_domain`= :alias_domain - UNION - SELECT `domain` FROM `domain` WHERE `domain`= :alias_domain_in_domain"); - $stmt->execute(array(':alias_domain' => $alias_domain, ':alias_domain_in_domain' => $alias_domain)); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('alias_domain_invalid', $alias_domain) - ); - continue; - } - $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `external` = 1 AND `send_as` LIKE :domain"); - $stmt->execute(array( - ':domain' => '%@' . $domain - )); - $stmt = $pdo->prepare("INSERT INTO `alias_domain` (`alias_domain`, `target_domain`, `active`) - VALUES (:alias_domain, :target_domain, :active)"); - $stmt->execute(array( - ':alias_domain' => $alias_domain, - ':target_domain' => $target_domain, - ':active' => $active - )); - try { - $redis->hSet('DOMAIN_MAP', $alias_domain, 1); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('redis_error', $e) - ); - return false; - } - if (!empty(intval($_data['rl_value']))) { - ratelimit('edit', 'domain', array('rl_value' => $_data['rl_value'], 'rl_frame' => $_data['rl_frame'], 'object' => $alias_domain)); - } - if (!empty($_data['key_size']) && !empty($_data['dkim_selector'])) { - if (!empty($redis->hGet('DKIM_SELECTORS', $alias_domain))) { - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'domain_add_dkim_available' - ); - } - else { - dkim('add', array('key_size' => $_data['key_size'], 'dkim_selector' => $_data['dkim_selector'], 'domains' => $alias_domain)); - } - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('aliasd_added', htmlspecialchars($alias_domain)) - ); - } - break; - case 'mailbox': - $local_part = strtolower(trim($_data['local_part'])); - $domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46); - $username = $local_part . '@' . $domain; - if (!filter_var($username, FILTER_VALIDATE_EMAIL)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'mailbox_invalid' - ); - return false; - } - if (empty($_data['local_part'])) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'mailbox_invalid' - ); - return false; - } - $password = $_data['password']; - $password2 = $_data['password2']; - $name = ltrim(rtrim($_data['name'], '>'), '<'); - $tags = $_data['tags']; - $quota_m = intval($_data['quota']); - if ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['unlimited_quota'] != "1") && $quota_m === 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'unlimited_quota_acl' - ); - return false; - } - if (empty($name)) { - $name = $local_part; - } - if (isset($_data['protocol_access'])) { - $_data['protocol_access'] = (array)$_data['protocol_access']; - $_data['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : 0; - $_data['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0; - $_data['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0; - $_data['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0; - } - $active = intval($_data['active']); - $force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update']); - $tls_enforce_in = (isset($_data['tls_enforce_in'])) ? intval($_data['tls_enforce_in']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in']); - $tls_enforce_out = (isset($_data['tls_enforce_out'])) ? intval($_data['tls_enforce_out']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out']); - $sogo_access = (isset($_data['sogo_access'])) ? intval($_data['sogo_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['sogo_access']); - $imap_access = (isset($_data['imap_access'])) ? intval($_data['imap_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']); - $pop3_access = (isset($_data['pop3_access'])) ? intval($_data['pop3_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']); - $smtp_access = (isset($_data['smtp_access'])) ? intval($_data['smtp_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']); - $sieve_access = (isset($_data['sieve_access'])) ? intval($_data['sieve_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']); - $relayhost = (isset($_data['relayhost'])) ? intval($_data['relayhost']) : 0; - $quarantine_notification = (isset($_data['quarantine_notification'])) ? strval($_data['quarantine_notification']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']); - $quarantine_category = (isset($_data['quarantine_category'])) ? strval($_data['quarantine_category']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']); - $quota_b = ($quota_m * 1048576); - $mailbox_attrs = json_encode( - array( - 'force_pw_update' => strval($force_pw_update), - 'tls_enforce_in' => strval($tls_enforce_in), - 'tls_enforce_out' => strval($tls_enforce_out), - 'sogo_access' => strval($sogo_access), - 'imap_access' => strval($imap_access), - 'pop3_access' => strval($pop3_access), - 'smtp_access' => strval($smtp_access), - 'sieve_access' => strval($sieve_access), - 'relayhost' => strval($relayhost), - 'passwd_update' => time(), - 'mailbox_format' => strval($MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format']), - 'quarantine_notification' => strval($quarantine_notification), - 'quarantine_category' => strval($quarantine_category) - ) - ); - if (!is_valid_domain_name($domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'domain_invalid' - ); - return false; - } - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - $stmt = $pdo->prepare("SELECT `mailboxes`, `maxquota`, `quota` FROM `domain` - WHERE `domain` = :domain"); - $stmt->execute(array(':domain' => $domain)); - $DomainData = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt = $pdo->prepare("SELECT - COUNT(*) as count, - COALESCE(ROUND(SUM(`quota`)/1048576), 0) as `quota` - FROM `mailbox` - WHERE (`kind` = '' OR `kind` = NULL) - AND `domain` = :domain"); - $stmt->execute(array(':domain' => $domain)); - $MailboxData = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt = $pdo->prepare("SELECT `local_part` FROM `mailbox` WHERE `local_part` = :local_part and `domain`= :domain"); - $stmt->execute(array(':local_part' => $local_part, ':domain' => $domain)); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('object_exists', htmlspecialchars($username)) - ); - return false; - } - $stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE address= :username"); - $stmt->execute(array(':username' => $username)); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('is_alias', htmlspecialchars($username)) - ); - return false; - } - $stmt = $pdo->prepare("SELECT `address` FROM `spamalias` WHERE `address`= :username"); - $stmt->execute(array(':username' => $username)); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('is_spam_alias', htmlspecialchars($username)) - ); - return false; - } - $stmt = $pdo->prepare("SELECT `domain` FROM `domain` WHERE `domain`= :domain"); - $stmt->execute(array(':domain' => $domain)); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results == 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('domain_not_found', htmlspecialchars($domain)) - ); - return false; - } - if (password_check($password, $password2) !== true) { - return false; - } - $password_hashed = hash_password($password); - if ($MailboxData['count'] >= $DomainData['mailboxes']) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('max_mailbox_exceeded', $MailboxData['count'], $DomainData['mailboxes']) - ); - return false; - } - if ($quota_m > $DomainData['maxquota']) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('mailbox_quota_exceeded', $DomainData['maxquota']) - ); - return false; - } - if (($MailboxData['quota'] + $quota_m) > $DomainData['quota']) { - $quota_left_m = ($DomainData['quota'] - $MailboxData['quota']); - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('mailbox_quota_left_exceeded', $quota_left_m) - ); - return false; - } - $stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `quota`, `local_part`, `domain`, `attributes`, `active`) - VALUES (:username, :password_hashed, :name, :quota_b, :local_part, :domain, :mailbox_attrs, :active)"); - $stmt->execute(array( - ':username' => $username, - ':password_hashed' => $password_hashed, - ':name' => $name, - ':quota_b' => $quota_b, - ':local_part' => $local_part, - ':domain' => $domain, - ':mailbox_attrs' => $mailbox_attrs, - ':active' => $active - )); - $stmt = $pdo->prepare("UPDATE `mailbox` SET - `attributes` = JSON_SET(`attributes`, '$.passwd_update', NOW()) - WHERE `username` = :username"); - $stmt->execute(array( - ':username' => $username - )); - // save tags - foreach($tags as $index => $tag){ - if (empty($tag)) continue; - if ($index > $GLOBALS['TAGGING_LIMIT']) { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('tag_limit_exceeded', 'limit '.$GLOBALS['TAGGING_LIMIT']) - ); - break; - } - $stmt = $pdo->prepare("INSERT INTO `tags_mailbox` (`username`, `tag_name`) VALUES (:username, :tag_name)"); - $stmt->execute(array( - ':username' => $username, - ':tag_name' => $tag, - )); - } - $stmt = $pdo->prepare("INSERT INTO `quota2` (`username`, `bytes`, `messages`) - VALUES (:username, '0', '0') ON DUPLICATE KEY UPDATE `bytes` = '0', `messages` = '0';"); - $stmt->execute(array(':username' => $username)); - $stmt = $pdo->prepare("INSERT INTO `quota2replica` (`username`, `bytes`, `messages`) - VALUES (:username, '0', '0') ON DUPLICATE KEY UPDATE `bytes` = '0', `messages` = '0';"); - $stmt->execute(array(':username' => $username)); - $stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `goto`, `domain`, `active`) - VALUES (:username1, :username2, :domain, :active)"); - $stmt->execute(array( - ':username1' => $username, - ':username2' => $username, - ':domain' => $domain, - ':active' => $active - )); - - - if (isset($_data['acl'])) { - $_data['acl'] = (array)$_data['acl']; - $_data['spam_alias'] = (in_array('spam_alias', $_data['acl'])) ? 1 : 0; - $_data['tls_policy'] = (in_array('tls_policy', $_data['acl'])) ? 1 : 0; - $_data['spam_score'] = (in_array('spam_score', $_data['acl'])) ? 1 : 0; - $_data['spam_policy'] = (in_array('spam_policy', $_data['acl'])) ? 1 : 0; - $_data['delimiter_action'] = (in_array('delimiter_action', $_data['acl'])) ? 1 : 0; - $_data['syncjobs'] = (in_array('syncjobs', $_data['acl'])) ? 1 : 0; - $_data['eas_reset'] = (in_array('eas_reset', $_data['acl'])) ? 1 : 0; - $_data['sogo_profile_reset'] = (in_array('sogo_profile_reset', $_data['acl'])) ? 1 : 0; - $_data['pushover'] = (in_array('pushover', $_data['acl'])) ? 1 : 0; - $_data['quarantine'] = (in_array('quarantine', $_data['acl'])) ? 1 : 0; - $_data['quarantine_attachments'] = (in_array('quarantine_attachments', $_data['acl'])) ? 1 : 0; - $_data['quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0; - $_data['quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0; - $_data['app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0; - - $stmt = $pdo->prepare("INSERT INTO `user_acl` - (`username`, `spam_alias`, `tls_policy`, `spam_score`, `spam_policy`, `delimiter_action`, `syncjobs`, `eas_reset`, `sogo_profile_reset`, - `pushover`, `quarantine`, `quarantine_attachments`, `quarantine_notification`, `quarantine_category`, `app_passwds`) - VALUES (:username, :spam_alias, :tls_policy, :spam_score, :spam_policy, :delimiter_action, :syncjobs, :eas_reset, :sogo_profile_reset, - :pushover, :quarantine, :quarantine_attachments, :quarantine_notification, :quarantine_category, :app_passwds) "); - $stmt->execute(array( - ':username' => $username, - ':spam_alias' => $_data['spam_alias'], - ':tls_policy' => $_data['tls_policy'], - ':spam_score' => $_data['spam_score'], - ':spam_policy' => $_data['spam_policy'], - ':delimiter_action' => $_data['delimiter_action'], - ':syncjobs' => $_data['syncjobs'], - ':eas_reset' => $_data['eas_reset'], - ':sogo_profile_reset' => $_data['sogo_profile_reset'], - ':pushover' => $_data['pushover'], - ':quarantine' => $_data['quarantine'], - ':quarantine_attachments' => $_data['quarantine_attachments'], - ':quarantine_notification' => $_data['quarantine_notification'], - ':quarantine_category' => $_data['quarantine_category'], - ':app_passwds' => $_data['app_passwds'] - )); - } - else { - $stmt = $pdo->prepare("INSERT INTO `user_acl` (`username`) VALUES (:username)"); - $stmt->execute(array( - ':username' => $username - )); - } - - if (isset($_data['rl_frame']) && isset($_data['rl_value'])){ - ratelimit('edit', 'mailbox', array( - 'object' => $username, - 'rl_frame' => $_data['rl_frame'], - 'rl_value' => $_data['rl_value'] - )); - } - - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('mailbox_added', htmlspecialchars($username)) - ); - break; - case 'resource': - $domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46); - $description = $_data['description']; - $local_part = preg_replace('/[^\da-z]/i', '', preg_quote($description, '/')); - $name = $local_part . '@' . $domain; - $kind = $_data['kind']; - $multiple_bookings = intval($_data['multiple_bookings']); - $active = intval($_data['active']); - if (!filter_var($name, FILTER_VALIDATE_EMAIL)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'resource_invalid' - ); - return false; - } - if (empty($description)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'description_invalid' - ); - return false; - } - if (!isset($multiple_bookings) || $multiple_bookings < -1) { - $multiple_bookings = -1; - } - if ($kind != 'location' && $kind != 'group' && $kind != 'thing') { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'resource_invalid' - ); - return false; - } - if (!is_valid_domain_name($domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'domain_invalid' - ); - return false; - } - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :name"); - $stmt->execute(array(':name' => $name)); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('object_exists', htmlspecialchars($name)) - ); - return false; - } - $stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE address= :name"); - $stmt->execute(array(':name' => $name)); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('is_alias', htmlspecialchars($name)) - ); - return false; - } - $stmt = $pdo->prepare("SELECT `address` FROM `spamalias` WHERE `address`= :name"); - $stmt->execute(array(':name' => $name)); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('is_spam_alias', htmlspecialchars($name)) - ); - return false; - } - $stmt = $pdo->prepare("SELECT `domain` FROM `domain` WHERE `domain`= :domain"); - $stmt->execute(array(':domain' => $domain)); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results == 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('domain_not_found', htmlspecialchars($domain)) - ); - return false; - } - $stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `quota`, `local_part`, `domain`, `active`, `multiple_bookings`, `kind`) - VALUES (:name, 'RESOURCE', :description, 0, :local_part, :domain, :active, :multiple_bookings, :kind)"); - $stmt->execute(array( - ':name' => $name, - ':description' => $description, - ':local_part' => $local_part, - ':domain' => $domain, - ':active' => $active, - ':kind' => $kind, - ':multiple_bookings' => $multiple_bookings - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('resource_added', htmlspecialchars($name)) - ); - break; - case 'domain_templates': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), - 'msg' => 'access_denied' - ); - return false; - } - if (empty($_data["template"])){ - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), - 'msg' => 'template_name_invalid' - ); - return false; - } - - // check if template name exists, return false - $stmt = $pdo->prepare("SELECT id FROM `templates` WHERE `type` = :type AND `template` = :template"); - $stmt->execute(array( - ":type" => "domain", - ":template" => $_data["template"] - )); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - - if (!empty($row)){ - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), - 'msg' => array('template_exists', $_data["template"]) - ); - return false; - } - - // check attributes - $attr = array(); - $attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array(); - $attr['max_num_aliases_for_domain'] = (!empty($_data['max_num_aliases_for_domain'])) ? intval($_data['max_num_aliases_for_domain']) : 400; - $attr['max_num_mboxes_for_domain'] = (!empty($_data['max_num_mboxes_for_domain'])) ? intval($_data['max_num_mboxes_for_domain']) : 10; - $attr['def_quota_for_mbox'] = (!empty($_data['def_quota_for_mbox'])) ? intval($_data['def_quota_for_mbox']) * 1048576 : 3072 * 1048576; - $attr['max_quota_for_mbox'] = (!empty($_data['max_quota_for_mbox'])) ? intval($_data['max_quota_for_mbox']) * 1048576 : 10240 * 1048576; - $attr['max_quota_for_domain'] = (!empty($_data['max_quota_for_domain'])) ? intval($_data['max_quota_for_domain']) * 1048576 : 10240 * 1048576; - $attr['rl_frame'] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s"; - $attr['rl_value'] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : ""; - $attr['active'] = isset($_data['active']) ? intval($_data['active']) : 1; - $attr['gal'] = (isset($_data['gal'])) ? intval($_data['gal']) : 1; - $attr['backupmx'] = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : 0; - $attr['relay_all_recipients'] = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : 0; - $attr['relay_unknown_only'] = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : 0; - $attr['dkim_selector'] = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : "dkim"; - $attr['key_size'] = isset($_data['key_size']) ? intval($_data['key_size']) : 2048; - - // save template - $stmt = $pdo->prepare("INSERT INTO `templates` (`type`, `template`, `attributes`) - VALUES (:type, :template, :attributes)"); - $stmt->execute(array( - ":type" => "domain", - ":template" => $_data["template"], - ":attributes" => json_encode($attr) - )); - - // success - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('template_added', $_data["template"]) - ); - return true; - break; - case 'mailbox_templates': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), - 'msg' => 'access_denied' - ); - return false; - } - if (empty($_data["template"])){ - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), - 'msg' => 'template_name_invalid' - ); - return false; - } - - // check if template name exists, return false - $stmt = $pdo->prepare("SELECT id FROM `templates` WHERE `type` = :type AND `template` = :template"); - $stmt->execute(array( - ":type" => "mailbox", - ":template" => $_data["template"] - )); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (!empty($row)){ - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), - 'msg' => array('template_exists', $_data["template"]) - ); - return false; - } - - - // check attributes - $attr = array(); - $attr["quota"] = isset($_data['quota']) ? intval($_data['quota']) * 1048576 : 0; - $attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array(); - $attr["quarantine_notification"] = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']); - $attr["quarantine_category"] = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']); - $attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s"; - $attr["rl_value"] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : ""; - $attr["force_pw_update"] = isset($_data['force_pw_update']) ? intval($_data['force_pw_update']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update']); - $attr["sogo_access"] = isset($_data['sogo_access']) ? intval($_data['sogo_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['sogo_access']); - $attr["active"] = isset($_data['active']) ? intval($_data['active']) : 1; - $attr["tls_enforce_in"] = isset($_data['tls_enforce_in']) ? intval($_data['tls_enforce_in']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in']); - $attr["tls_enforce_out"] = isset($_data['tls_enforce_out']) ? intval($_data['tls_enforce_out']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out']); - if (isset($_data['protocol_access'])) { - $_data['protocol_access'] = (array)$_data['protocol_access']; - $attr['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']); - $attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']); - $attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']); - $attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']); - } - else { - $attr['imap_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']); - $attr['pop3_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']); - $attr['smtp_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']); - $attr['sieve_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']); - } - if (isset($_data['acl'])) { - $_data['acl'] = (array)$_data['acl']; - $attr['acl_spam_alias'] = (in_array('spam_alias', $_data['acl'])) ? 1 : 0; - $attr['acl_tls_policy'] = (in_array('tls_policy', $_data['acl'])) ? 1 : 0; - $attr['acl_spam_score'] = (in_array('spam_score', $_data['acl'])) ? 1 : 0; - $attr['acl_spam_policy'] = (in_array('spam_policy', $_data['acl'])) ? 1 : 0; - $attr['acl_delimiter_action'] = (in_array('delimiter_action', $_data['acl'])) ? 1 : 0; - $attr['acl_syncjobs'] = (in_array('syncjobs', $_data['acl'])) ? 1 : 0; - $attr['acl_eas_reset'] = (in_array('eas_reset', $_data['acl'])) ? 1 : 0; - $attr['acl_sogo_profile_reset'] = (in_array('sogo_profile_reset', $_data['acl'])) ? 1 : 0; - $attr['acl_pushover'] = (in_array('pushover', $_data['acl'])) ? 1 : 0; - $attr['acl_quarantine'] = (in_array('quarantine', $_data['acl'])) ? 1 : 0; - $attr['acl_quarantine_attachments'] = (in_array('quarantine_attachments', $_data['acl'])) ? 1 : 0; - $attr['acl_quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0; - $attr['acl_quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0; - $attr['acl_app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0; - } else { - $_data['acl'] = (array)$_data['acl']; - $attr['acl_spam_alias'] = 1; - $attr['acl_tls_policy'] = 1; - $attr['acl_spam_score'] = 1; - $attr['acl_spam_policy'] = 1; - $attr['acl_delimiter_action'] = 1; - $attr['acl_syncjobs'] = 0; - $attr['acl_eas_reset'] = 1; - $attr['acl_sogo_profile_reset'] = 0; - $attr['acl_pushover'] = 1; - $attr['acl_quarantine'] = 1; - $attr['acl_quarantine_attachments'] = 1; - $attr['acl_quarantine_notification'] = 1; - $attr['acl_quarantine_category'] = 1; - $attr['acl_app_passwds'] = 1; - } - - - - // save template - $stmt = $pdo->prepare("INSERT INTO `templates` (`type`, `template`, `attributes`) - VALUES (:type, :template, :attributes)"); - $stmt->execute(array( - ":type" => "mailbox", - ":template" => $_data["template"], - ":attributes" => json_encode($attr) - )); - - // success - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('template_added', $_data["template"]) - ); - return true; - break; - } - break; - case 'edit': - switch ($_type) { - case 'alias_domain': - $alias_domains = (array)$_data['alias_domain']; - foreach ($alias_domains as $alias_domain) { - $alias_domain = idn_to_ascii(strtolower(trim($alias_domain)), 0, INTL_IDNA_VARIANT_UTS46); - $is_now = mailbox('get', 'alias_domain_details', $alias_domain); - if (!empty($is_now)) { - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; - $target_domain = (!empty($_data['target_domain'])) ? idn_to_ascii(strtolower(trim($_data['target_domain'])), 0, INTL_IDNA_VARIANT_UTS46) : $is_now['target_domain']; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('alias_domain_invalid', htmlspecialchars($alias_domain)) - ); - continue; - } - if (!is_valid_domain_name($target_domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('target_domain_invalid', htmlspecialchars($target_domain)) - ); - continue; - } - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $target_domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - if (empty(mailbox('get', 'domain_details', $target_domain)) || !empty(mailbox('get', 'alias_domain_details', $target_domain))) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('target_domain_invalid', htmlspecialchars($target_domain)) - ); - continue; - } - $stmt = $pdo->prepare("UPDATE `alias_domain` SET - `target_domain` = :target_domain, - `active` = :active - WHERE `alias_domain` = :alias_domain"); - $stmt->execute(array( - ':alias_domain' => $alias_domain, - ':target_domain' => $target_domain, - ':active' => $active - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('aliasd_modified', htmlspecialchars($alias_domain)) - ); - } - break; - case 'tls_policy': - if (!is_array($_data['username'])) { - $usernames = array(); - $usernames[] = $_data['username']; - } - else { - $usernames = $_data['username']; - } - if (!isset($_SESSION['acl']['tls_policy']) || $_SESSION['acl']['tls_policy'] != "1" ) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - foreach ($usernames as $username) { - if (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - $is_now = mailbox('get', 'tls_policy', $username); - if (!empty($is_now)) { - $tls_enforce_in = (isset($_data['tls_enforce_in'])) ? intval($_data['tls_enforce_in']) : $is_now['tls_enforce_in']; - $tls_enforce_out = (isset($_data['tls_enforce_out'])) ? intval($_data['tls_enforce_out']) : $is_now['tls_enforce_out']; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - $stmt = $pdo->prepare("UPDATE `mailbox` - SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_out', :tls_out), - `attributes` = JSON_SET(`attributes`, '$.tls_enforce_in', :tls_in) - WHERE `username` = :username"); - $stmt->execute(array( - ':tls_out' => intval($tls_enforce_out), - ':tls_in' => intval($tls_enforce_in), - ':username' => $username - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('mailbox_modified', $username) - ); - } - break; - case 'quarantine_notification': - if (!is_array($_data['username'])) { - $usernames = array(); - $usernames[] = $_data['username']; - } - else { - $usernames = $_data['username']; - } - if (!isset($_SESSION['acl']['quarantine_notification']) || $_SESSION['acl']['quarantine_notification'] != "1" ) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - foreach ($usernames as $username) { - if (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - $is_now = mailbox('get', 'quarantine_notification', $username); - if (!empty($is_now)) { - $quarantine_notification = (isset($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : $is_now['quarantine_notification']; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - if (!in_array($quarantine_notification, array('never', 'hourly', 'daily', 'weekly'))) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - $stmt = $pdo->prepare("UPDATE `mailbox` - SET `attributes` = JSON_SET(`attributes`, '$.quarantine_notification', :quarantine_notification) - WHERE `username` = :username"); - $stmt->execute(array( - ':quarantine_notification' => $quarantine_notification, - ':username' => $username - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('mailbox_modified', $username) - ); - } - break; - case 'quarantine_category': - if (!is_array($_data['username'])) { - $usernames = array(); - $usernames[] = $_data['username']; - } - else { - $usernames = $_data['username']; - } - if (!isset($_SESSION['acl']['quarantine_category']) || $_SESSION['acl']['quarantine_category'] != "1" ) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - foreach ($usernames as $username) { - if (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - $is_now = mailbox('get', 'quarantine_category', $username); - if (!empty($is_now)) { - $quarantine_category = (isset($_data['quarantine_category'])) ? $_data['quarantine_category'] : $is_now['quarantine_category']; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - if (!in_array($quarantine_category, array('add_header', 'reject', 'all'))) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - $stmt = $pdo->prepare("UPDATE `mailbox` - SET `attributes` = JSON_SET(`attributes`, '$.quarantine_category', :quarantine_category) - WHERE `username` = :username"); - $stmt->execute(array( - ':quarantine_category' => $quarantine_category, - ':username' => $username - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('mailbox_modified', $username) - ); - } - break; - case 'spam_score': - if (!is_array($_data['username'])) { - $usernames = array(); - $usernames[] = $_data['username']; - } - else { - $usernames = $_data['username']; - } - if (!isset($_SESSION['acl']['spam_score']) || $_SESSION['acl']['spam_score'] != "1" ) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - foreach ($usernames as $username) { - if ($_data['spam_score'] == "default") { - $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username - AND (`option` = 'lowspamlevel' OR `option` = 'highspamlevel')"); - $stmt->execute(array( - ':username' => $username - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('mailbox_modified', $username) - ); - continue; - } - $lowspamlevel = explode(',', $_data['spam_score'])[0]; - $highspamlevel = explode(',', $_data['spam_score'])[1]; - if (!is_numeric($lowspamlevel) || !is_numeric($highspamlevel)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'Invalid spam score, format must be "1,2" where first is low and second is high spam value.' - ); - continue; - } - if ($lowspamlevel == $highspamlevel) { - $highspamlevel = $highspamlevel + 0.1; - } - $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username - AND (`option` = 'lowspamlevel' OR `option` = 'highspamlevel')"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("INSERT INTO `filterconf` (`object`, `option`, `value`) - VALUES (:username, 'highspamlevel', :highspamlevel)"); - $stmt->execute(array( - ':username' => $username, - ':highspamlevel' => $highspamlevel - )); - $stmt = $pdo->prepare("INSERT INTO `filterconf` (`object`, `option`, `value`) - VALUES (:username, 'lowspamlevel', :lowspamlevel)"); - $stmt->execute(array( - ':username' => $username, - ':lowspamlevel' => $lowspamlevel - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('mailbox_modified', $username) - ); - } - break; - case 'time_limited_alias': - if (!isset($_SESSION['acl']['spam_alias']) || $_SESSION['acl']['spam_alias'] != "1" ) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - if (!is_array($_data['address'])) { - $addresses = array(); - $addresses[] = $_data['address']; - } - else { - $addresses = $_data['address']; - } - foreach ($addresses as $address) { - $stmt = $pdo->prepare("SELECT `goto` FROM `spamalias` WHERE `address` = :address"); - $stmt->execute(array(':address' => $address)); - $goto = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $goto)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - if (empty($_data['validity'])) { - continue; - } - $validity = round((int)time() + ($_data['validity'] * 3600)); - $stmt = $pdo->prepare("UPDATE `spamalias` SET `validity` = :validity WHERE - `address` = :address"); - $stmt->execute(array( - ':address' => $address, - ':validity' => $validity - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('mailbox_modified', htmlspecialchars(implode(', ', (array)$usernames))) - ); - } - break; - case 'delimiter_action': - if (!is_array($_data['username'])) { - $usernames = array(); - $usernames[] = $_data['username']; - } - else { - $usernames = $_data['username']; - } - if (!isset($_SESSION['acl']['delimiter_action']) || $_SESSION['acl']['delimiter_action'] != "1" ) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - foreach ($usernames as $username) { - if (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - if (isset($_data['tagged_mail_handler']) && $_data['tagged_mail_handler'] == "subject") { - try { - $redis->hSet('RCPT_WANTS_SUBJECT_TAG', $username, 1); - $redis->hDel('RCPT_WANTS_SUBFOLDER_TAG', $username); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('redis_error', $e) - ); - continue; - } - } - else if (isset($_data['tagged_mail_handler']) && $_data['tagged_mail_handler'] == "subfolder") { - try { - $redis->hSet('RCPT_WANTS_SUBFOLDER_TAG', $username, 1); - $redis->hDel('RCPT_WANTS_SUBJECT_TAG', $username); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('redis_error', $e) - ); - continue; - } - } - else { - try { - $redis->hDel('RCPT_WANTS_SUBJECT_TAG', $username); - $redis->hDel('RCPT_WANTS_SUBFOLDER_TAG', $username); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('redis_error', $e) - ); - continue; - } - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('mailbox_modified', $username) - ); - } - break; - case 'syncjob': - if (!is_array($_data['id'])) { - $ids = array(); - $ids[] = $_data['id']; - } - else { - $ids = $_data['id']; - } - if (!isset($_SESSION['acl']['syncjobs']) || $_SESSION['acl']['syncjobs'] != "1" ) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - foreach ($ids as $id) { - $is_now = mailbox('get', 'syncjob_details', $id, array('with_password')); - if (!empty($is_now)) { - $username = $is_now['user2']; - $user1 = (!empty($_data['user1'])) ? $_data['user1'] : $is_now['user1']; - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; - $last_run = (isset($_data['last_run'])) ? NULL : $is_now['last_run']; - $success = (isset($_data['success'])) ? NULL : $is_now['success']; - $delete2duplicates = (isset($_data['delete2duplicates'])) ? intval($_data['delete2duplicates']) : $is_now['delete2duplicates']; - $subscribeall = (isset($_data['subscribeall'])) ? intval($_data['subscribeall']) : $is_now['subscribeall']; - $delete1 = (isset($_data['delete1'])) ? intval($_data['delete1']) : $is_now['delete1']; - $delete2 = (isset($_data['delete2'])) ? intval($_data['delete2']) : $is_now['delete2']; - $automap = (isset($_data['automap'])) ? intval($_data['automap']) : $is_now['automap']; - $skipcrossduplicates = (isset($_data['skipcrossduplicates'])) ? intval($_data['skipcrossduplicates']) : $is_now['skipcrossduplicates']; - $port1 = (!empty($_data['port1'])) ? $_data['port1'] : $is_now['port1']; - $password1 = (!empty($_data['password1'])) ? $_data['password1'] : $is_now['password1']; - $host1 = (!empty($_data['host1'])) ? $_data['host1'] : $is_now['host1']; - $subfolder2 = (isset($_data['subfolder2'])) ? $_data['subfolder2'] : $is_now['subfolder2']; - $enc1 = (!empty($_data['enc1'])) ? $_data['enc1'] : $is_now['enc1']; - $mins_interval = (!empty($_data['mins_interval'])) ? $_data['mins_interval'] : $is_now['mins_interval']; - $exclude = (isset($_data['exclude'])) ? $_data['exclude'] : $is_now['exclude']; - $custom_params = (isset($_data['custom_params'])) ? $_data['custom_params'] : $is_now['custom_params']; - $maxage = (isset($_data['maxage']) && $_data['maxage'] != "") ? intval($_data['maxage']) : $is_now['maxage']; - $maxbytespersecond = (isset($_data['maxbytespersecond']) && $_data['maxbytespersecond'] != "") ? intval($_data['maxbytespersecond']) : $is_now['maxbytespersecond']; - $timeout1 = (isset($_data['timeout1']) && $_data['timeout1'] != "") ? intval($_data['timeout1']) : $is_now['timeout1']; - $timeout2 = (isset($_data['timeout2']) && $_data['timeout2'] != "") ? intval($_data['timeout2']) : $is_now['timeout2']; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - - // validate custom params - foreach (explode('-', $custom_params) as $param){ - if(empty($param)) continue; - - // extract option - if (str_contains($param, '=')) $param = explode('=', $param)[0]; - else $param = rtrim($param, ' '); - // remove first char if first char is - - if ($param[0] == '-') $param = ltrim($param, $param[0]); - - if (str_contains($param, ' ')) { - // bad char - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'bad character SPACE' - ); - return false; - } - - // check if param is whitelisted - if (!in_array(strtolower($param), $GLOBALS["IMAPSYNC_OPTIONS"]["whitelist"])){ - // bad option - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'bad option '. $param - ); - return false; - } - } - if (empty($subfolder2)) { - $subfolder2 = ""; - } - if (!isset($maxage) || !filter_var($maxage, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) { - $maxage = "0"; - } - if (!isset($timeout1) || !filter_var($timeout1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) { - $timeout1 = "600"; - } - if (!isset($timeout2) || !filter_var($timeout2, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) { - $timeout2 = "600"; - } - if (!isset($maxbytespersecond) || !filter_var($maxbytespersecond, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 125000000)))) { - $maxbytespersecond = "0"; - } - if (!filter_var($port1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 65535)))) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - if (!filter_var($mins_interval, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 43800)))) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - if (!is_valid_domain_name($host1)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - if ($enc1 != "TLS" && $enc1 != "SSL" && $enc1 != "PLAIN") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - if (@preg_match("/" . $exclude . "/", null) === false) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - $stmt = $pdo->prepare("UPDATE `imapsync` SET `delete1` = :delete1, - `delete2` = :delete2, - `automap` = :automap, - `skipcrossduplicates` = :skipcrossduplicates, - `maxage` = :maxage, - `maxbytespersecond` = :maxbytespersecond, - `subfolder2` = :subfolder2, - `exclude` = :exclude, - `host1` = :host1, - `last_run` = :last_run, - `success` = :success, - `user1` = :user1, - `password1` = :password1, - `mins_interval` = :mins_interval, - `port1` = :port1, - `enc1` = :enc1, - `delete2duplicates` = :delete2duplicates, - `custom_params` = :custom_params, - `timeout1` = :timeout1, - `timeout2` = :timeout2, - `subscribeall` = :subscribeall, - `active` = :active - WHERE `id` = :id"); - $stmt->execute(array( - ':delete1' => $delete1, - ':delete2' => $delete2, - ':automap' => $automap, - ':skipcrossduplicates' => $skipcrossduplicates, - ':id' => $id, - ':exclude' => $exclude, - ':maxage' => $maxage, - ':maxbytespersecond' => $maxbytespersecond, - ':subfolder2' => $subfolder2, - ':host1' => $host1, - ':user1' => $user1, - ':password1' => $password1, - ':last_run' => $last_run, - ':success' => $success, - ':mins_interval' => $mins_interval, - ':port1' => $port1, - ':enc1' => $enc1, - ':delete2duplicates' => $delete2duplicates, - ':custom_params' => $custom_params, - ':timeout1' => $timeout1, - ':timeout2' => $timeout2, - ':subscribeall' => $subscribeall, - ':active' => $active, - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('mailbox_modified', $username) - ); - } - break; - case 'filter': - if (!isset($_SESSION['acl']['filters']) || $_SESSION['acl']['filters'] != "1" ) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - $sieve = new Sieve\SieveParser(); - if (!is_array($_data['id'])) { - $ids = array(); - $ids[] = $_data['id']; - } - else { - $ids = $_data['id']; - } - foreach ($ids as $id) { - $is_now = mailbox('get', 'filter_details', $id); - if (!empty($is_now)) { - $username = $is_now['username']; - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; - $script_desc = (!empty($_data['script_desc'])) ? $_data['script_desc'] : $is_now['script_desc']; - $script_data = (!empty($_data['script_data'])) ? $_data['script_data'] : $is_now['script_data']; - $filter_type = (!empty($_data['filter_type'])) ? $_data['filter_type'] : $is_now['filter_type']; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - try { - $sieve->parse($script_data); - } - catch (Exception $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('sieve_error', $e->getMessage()) - ); - continue; - } - if ($filter_type != 'postfilter' && $filter_type != 'prefilter') { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'filter_type' - ); - continue; - } - if ($active == '1') { - $script_name = 'active'; - $stmt = $pdo->prepare("UPDATE `sieve_filters` - SET `script_name` = 'inactive' - WHERE `username` = :username - AND `filter_type` = :filter_type"); - $stmt->execute(array( - ':username' => $username, - ':filter_type' => $filter_type - )); - } - else { - $script_name = 'inactive'; - } - $stmt = $pdo->prepare("UPDATE `sieve_filters` SET `script_desc` = :script_desc, `script_data` = :script_data, `script_name` = :script_name, `filter_type` = :filter_type - WHERE `id` = :id"); - $stmt->execute(array( - ':script_desc' => $script_desc, - ':script_data' => $script_data, - ':script_name' => $script_name, - ':filter_type' => $filter_type, - ':id' => $id - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('mailbox_modified', $username) - ); - } - break; - case 'alias': - if (!is_array($_data['id'])) { - $ids = array(); - $ids[] = $_data['id']; - } - else { - $ids = $_data['id']; - } - foreach ($ids as $id) { - $is_now = mailbox('get', 'alias_details', $id); - if (!empty($is_now)) { - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; - $sogo_visible = (isset($_data['sogo_visible'])) ? intval($_data['sogo_visible']) : $is_now['sogo_visible']; - $goto_null = (isset($_data['goto_null'])) ? intval($_data['goto_null']) : 0; - $goto_spam = (isset($_data['goto_spam'])) ? intval($_data['goto_spam']) : 0; - $goto_ham = (isset($_data['goto_ham'])) ? intval($_data['goto_ham']) : 0; - $public_comment = (isset($_data['public_comment'])) ? $_data['public_comment'] : $is_now['public_comment']; - $private_comment = (isset($_data['private_comment'])) ? $_data['private_comment'] : $is_now['private_comment']; - $goto = (!empty($_data['goto'])) ? $_data['goto'] : $is_now['goto']; - $address = (!empty($_data['address'])) ? $_data['address'] : $is_now['address']; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('alias_invalid', $address) - ); - continue; - } - if ($_data['expand_alias'] === true || $_data['expand_alias'] == 1) { - $stmt = $pdo->prepare("SELECT `address` FROM `alias` - WHERE `address` = :address - AND `domain` NOT IN ( - SELECT `alias_domain` FROM `alias_domain` - )"); - $stmt->execute(array( - ':address' => $address, - )); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results == 0) { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('is_not_primary_alias', htmlspecialchars($address)) - ); - continue; - } - $stmt = $pdo->prepare("SELECT `goto`, GROUP_CONCAT(CONCAT(SUBSTRING(`alias`.`address`, 1, LOCATE('@', `alias`.`address`) - 1), '@', `alias_domain`.`alias_domain`)) AS `missing_alias` - FROM `alias` JOIN `alias_domain` ON `alias_domain`.`target_domain` = `alias`.`domain` - WHERE CONCAT(SUBSTRING(`alias`.`address`, 1, LOCATE('@', `alias`.`address`) - 1), '@', `alias_domain`.`alias_domain`) NOT IN ( - SELECT `address` FROM `alias` WHERE `address` != `goto` - ) - AND `alias`.`address` NOT IN ( - SELECT `address` FROM `alias` WHERE `address` = `goto` - ) - AND `address` = :address ;"); - $stmt->execute(array( - ':address' => $address - )); - $missing_aliases = $stmt->fetch(PDO::FETCH_ASSOC); - if (!empty($missing_aliases['missing_alias'])) { - mailbox('add', 'alias', array( - 'address' => $missing_aliases['missing_alias'], - 'goto' => $missing_aliases['goto'], - 'sogo_visible' => 1, - 'active' => 1 - )); - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('alias_modified', htmlspecialchars($address)) - ); - continue; - } - $domain = idn_to_ascii(substr(strstr($address, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46); - if ($is_now['address'] != $address) { - $local_part = strstr($address, '@', true); - $address = $local_part.'@'.$domain; - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - if ((!filter_var($address, FILTER_VALIDATE_EMAIL) === true) && !empty($local_part)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('alias_invalid', $address) - ); - continue; - } - if (strtolower($is_now['address']) != strtolower($address)) { - $stmt = $pdo->prepare("SELECT `address` FROM `alias` - WHERE `address`= :address OR `address` IN ( - SELECT `username` FROM `mailbox`, `alias_domain` - WHERE ( - `alias_domain`.`alias_domain` = :address_d - AND `mailbox`.`username` = CONCAT(:address_l, '@', alias_domain.target_domain)))"); - $stmt->execute(array( - ':address' => $address, - ':address_l' => $local_part, - ':address_d' => $domain - )); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('is_alias_or_mailbox', htmlspecialchars($address)) - ); - continue; - } - } - $stmt = $pdo->prepare("SELECT `domain` FROM `domain` - WHERE `domain`= :domain1 OR `domain` = (SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain2)"); - $stmt->execute(array(':domain1' => $domain, ':domain2' => $domain)); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results == 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('domain_not_found', htmlspecialchars($domain)) - ); - continue; - } - $stmt = $pdo->prepare("SELECT `address` FROM `spamalias` - WHERE `address`= :address"); - $stmt->execute(array(':address' => $address)); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('is_spam_alias', htmlspecialchars($address)) - ); - continue; - } - } - if ($goto_null == "1") { - $goto = "null@localhost"; - } - elseif ($goto_spam == "1") { - $goto = "spam@localhost"; - } - elseif ($goto_ham == "1") { - $goto = "ham@localhost"; - } - else { - $gotos = array_map('trim', preg_split( "/( |,|;|\n)/", $goto)); - foreach ($gotos as $i => &$goto) { - if (empty($goto)) { - continue; - } - if (!filter_var($goto, FILTER_VALIDATE_EMAIL)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('goto_invalid', $goto) - ); - unset($gotos[$i]); - continue; - } - if ($goto == $address) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'alias_goto_identical' - ); - unset($gotos[$i]); - continue; - } - // Delete from sender_acl to prevent duplicates - $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE - `logged_in_as` = :goto AND - `send_as` = :address"); - $stmt->execute(array( - ':goto' => $goto, - ':address' => $address - )); - } - $gotos = array_unique($gotos); - $gotos = array_filter($gotos); - $goto = implode(",", (array)$gotos); - } - if (!empty($goto)) { - $stmt = $pdo->prepare("UPDATE `alias` SET - `address` = :address, - `public_comment` = :public_comment, - `private_comment` = :private_comment, - `domain` = :domain, - `goto` = :goto, - `sogo_visible`= :sogo_visible, - `active`= :active - WHERE `id` = :id"); - $stmt->execute(array( - ':address' => $address, - ':public_comment' => $public_comment, - ':private_comment' => $private_comment, - ':domain' => $domain, - ':goto' => $goto, - ':sogo_visible' => $sogo_visible, - ':active' => $active, - ':id' => $is_now['id'] - )); - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('alias_modified', htmlspecialchars($address)) - ); - } - break; - case 'domain': - if (!is_array($_data['domain'])) { - $domains = array(); - $domains[] = $_data['domain']; - } - else { - $domains = $_data['domain']; - } - foreach ($domains as $domain) { - $domain = idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46); - if (!is_valid_domain_name($domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'domain_invalid' - ); - continue; - } - if ($_SESSION['mailcow_cc_role'] == "domainadmin" && - hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { - $is_now = mailbox('get', 'domain_details', $domain); - if (!empty($is_now)) { - $gal = (isset($_data['gal'])) ? intval($_data['gal']) : $is_now['gal']; - $description = (!empty($_data['description']) && isset($_SESSION['acl']['domain_desc']) && $_SESSION['acl']['domain_desc'] == "1") ? $_data['description'] : $is_now['description']; - (int)$relayhost = (isset($_data['relayhost']) && isset($_SESSION['acl']['domain_relayhost']) && $_SESSION['acl']['domain_relayhost'] == "1") ? intval($_data['relayhost']) : intval($is_now['relayhost']); - $tags = (is_array($_data['tags']) ? $_data['tags'] : array()); - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'domain_invalid' - ); - continue; - } - - $stmt = $pdo->prepare("UPDATE `domain` SET - `description` = :description, - `gal` = :gal - WHERE `domain` = :domain"); - $stmt->execute(array( - ':description' => $description, - ':gal' => $gal, - ':domain' => $domain - )); - // save tags - foreach($tags as $index => $tag){ - if (empty($tag)) continue; - if ($index > $GLOBALS['TAGGING_LIMIT']) { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('tag_limit_exceeded', 'limit '.$GLOBALS['TAGGING_LIMIT']) - ); - break; - } - $stmt = $pdo->prepare("INSERT INTO `tags_domain` (`domain`, `tag_name`) VALUES (:domain, :tag_name)"); - $stmt->execute(array( - ':domain' => $domain, - ':tag_name' => $tag, - )); - } - - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('domain_modified', htmlspecialchars($domain)) - ); - } - elseif ($_SESSION['mailcow_cc_role'] == "admin") { - $is_now = mailbox('get', 'domain_details', $domain); - if (!empty($is_now)) { - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; - $backupmx = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : $is_now['backupmx']; - $gal = (isset($_data['gal'])) ? intval($_data['gal']) : $is_now['gal']; - $relay_all_recipients = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : $is_now['relay_all_recipients']; - $relay_unknown_only = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : $is_now['relay_unknown_only']; - $relayhost = (isset($_data['relayhost'])) ? intval($_data['relayhost']) : $is_now['relayhost']; - $aliases = (!empty($_data['aliases'])) ? $_data['aliases'] : $is_now['max_num_aliases_for_domain']; - $mailboxes = (isset($_data['mailboxes']) && $_data['mailboxes'] != '') ? intval($_data['mailboxes']) : $is_now['max_num_mboxes_for_domain']; - $defquota = (isset($_data['defquota']) && $_data['defquota'] != '') ? intval($_data['defquota']) : ($is_now['def_quota_for_mbox'] / 1048576); - $maxquota = (!empty($_data['maxquota'])) ? $_data['maxquota'] : ($is_now['max_quota_for_mbox'] / 1048576); - $quota = (!empty($_data['quota'])) ? $_data['quota'] : ($is_now['max_quota_for_domain'] / 1048576); - $description = (!empty($_data['description'])) ? $_data['description'] : $is_now['description']; - $tags = (is_array($_data['tags']) ? $_data['tags'] : array()); - if ($relay_all_recipients == '1') { - $backupmx = '1'; - } - if ($relay_unknown_only == '1') { - $backupmx = '1'; - $relay_all_recipients = '1'; - } - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'domain_invalid' - ); - continue; - } - // todo: should be using api here - $stmt = $pdo->prepare("SELECT - COUNT(*) AS count, - MAX(COALESCE(ROUND(`quota`/1048576), 0)) AS `biggest_mailbox`, - COALESCE(ROUND(SUM(`quota`)/1048576), 0) AS `quota_all` - FROM `mailbox` - WHERE (`kind` = '' OR `kind` = NULL) - AND domain = :domain"); - $stmt->execute(array(':domain' => $domain)); - $MailboxData = $stmt->fetch(PDO::FETCH_ASSOC); - // todo: should be using api here - $stmt = $pdo->prepare("SELECT COUNT(*) AS `count` FROM `alias` - WHERE domain = :domain - AND address NOT IN ( - SELECT `username` FROM `mailbox` - )"); - $stmt->execute(array(':domain' => $domain)); - $AliasData = $stmt->fetch(PDO::FETCH_ASSOC); - if ($defquota > $maxquota) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'mailbox_defquota_exceeds_mailbox_maxquota' - ); - continue; - } - if ($defquota == "0" || empty($defquota)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'defquota_empty' - ); - continue; - } - if ($maxquota > $quota) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'mailbox_quota_exceeds_domain_quota' - ); - continue; - } - if ($maxquota == "0" || empty($maxquota)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'maxquota_empty' - ); - continue; - } - if ($MailboxData['biggest_mailbox'] > $maxquota) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('max_quota_in_use', $MailboxData['biggest_mailbox']) - ); - continue; - } - if ($MailboxData['quota_all'] > $quota) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('domain_quota_m_in_use', $MailboxData['quota_all']) - ); - continue; - } - if ($MailboxData['count'] > $mailboxes) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('mailboxes_in_use', $MailboxData['count']) - ); - continue; - } - if ($AliasData['count'] > $aliases) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('aliases_in_use', $AliasData['count']) - ); - continue; - } - - $stmt = $pdo->prepare("UPDATE `domain` SET - `relay_all_recipients` = :relay_all_recipients, - `relay_unknown_only` = :relay_unknown_only, - `backupmx` = :backupmx, - `gal` = :gal, - `active` = :active, - `quota` = :quota, - `defquota` = :defquota, - `maxquota` = :maxquota, - `relayhost` = :relayhost, - `mailboxes` = :mailboxes, - `aliases` = :aliases, - `description` = :description - WHERE `domain` = :domain"); - $stmt->execute(array( - ':relay_all_recipients' => $relay_all_recipients, - ':relay_unknown_only' => $relay_unknown_only, - ':backupmx' => $backupmx, - ':gal' => $gal, - ':active' => $active, - ':quota' => $quota, - ':defquota' => $defquota, - ':maxquota' => $maxquota, - ':relayhost' => $relayhost, - ':mailboxes' => $mailboxes, - ':aliases' => $aliases, - ':description' => $description, - ':domain' => $domain - )); - // save tags - foreach($tags as $index => $tag){ - if (empty($tag)) continue; - if ($index > $GLOBALS['TAGGING_LIMIT']) { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('tag_limit_exceeded', 'limit '.$GLOBALS['TAGGING_LIMIT']) - ); - break; - } - $stmt = $pdo->prepare("INSERT INTO `tags_domain` (`domain`, `tag_name`) VALUES (:domain, :tag_name)"); - $stmt->execute(array( - ':domain' => $domain, - ':tag_name' => $tag, - )); - } - - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('domain_modified', htmlspecialchars($domain)) - ); - } - } - break; - case 'domain_templates': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), - 'msg' => 'access_denied' - ); - return false; - } - if (!is_array($_data['ids'])) { - $ids = array(); - $ids[] = $_data['ids']; - } - else { - $ids = $_data['ids']; - } - foreach ($ids as $id) { - $is_now = mailbox("get", "domain_templates", $id); - if (empty($is_now) || - $is_now["type"] != "domain"){ - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), - 'msg' => 'template_id_invalid' - ); - continue; - } - - // check name - if ($is_now["template"] == "Default" && $is_now["template"] != $_data["template"]){ - // keep template name of Default template - $_data["template"] = $is_now["template"]; - } - else { - $_data["template"] = (isset($_data["template"])) ? $_data["template"] : $is_now["template"]; - } - // check attributes - $attr = array(); - $attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array(); - $attr['max_num_aliases_for_domain'] = (isset($_data['max_num_aliases_for_domain'])) ? intval($_data['max_num_aliases_for_domain']) : 0; - $attr['max_num_mboxes_for_domain'] = (isset($_data['max_num_mboxes_for_domain'])) ? intval($_data['max_num_mboxes_for_domain']) : 0; - $attr['def_quota_for_mbox'] = (isset($_data['def_quota_for_mbox'])) ? intval($_data['def_quota_for_mbox']) * 1048576 : 0; - $attr['max_quota_for_mbox'] = (isset($_data['max_quota_for_mbox'])) ? intval($_data['max_quota_for_mbox']) * 1048576 : 0; - $attr['max_quota_for_domain'] = (isset($_data['max_quota_for_domain'])) ? intval($_data['max_quota_for_domain']) * 1048576 : 0; - $attr['rl_frame'] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s"; - $attr['rl_value'] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : ""; - $attr['active'] = isset($_data['active']) ? intval($_data['active']) : 1; - $attr['gal'] = (isset($_data['gal'])) ? intval($_data['gal']) : 1; - $attr['backupmx'] = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : 0; - $attr['relay_all_recipients'] = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : 0; - $attr['relay_unknown_only'] = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : 0; - $attr['dkim_selector'] = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : "dkim"; - $attr['key_size'] = isset($_data['key_size']) ? intval($_data['key_size']) : 2048; - - // update template - $stmt = $pdo->prepare("UPDATE `templates` - SET `template` = :template, `attributes` = :attributes - WHERE id = :id"); - $stmt->execute(array( - ":id" => $id , - ":template" => $_data["template"] , - ":attributes" => json_encode($attr) - )); - } - - - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('template_modified', $_data["template"]) - ); - return true; - break; - case 'mailbox': - if (!is_array($_data['username'])) { - $usernames = array(); - $usernames[] = $_data['username']; - } - else { - $usernames = $_data['username']; - } - foreach ($usernames as $username) { - if (!filter_var($username, FILTER_VALIDATE_EMAIL)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('username_invalid', $username) - ); - continue; - } - $is_now = mailbox('get', 'mailbox_details', $username); - if (isset($_data['protocol_access'])) { - $_data['protocol_access'] = (array)$_data['protocol_access']; - $_data['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : 0; - $_data['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0; - $_data['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0; - $_data['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0; - } - if (!empty($is_now)) { - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; - (int)$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($is_now['attributes']['force_pw_update']); - (int)$sogo_access = (isset($_data['sogo_access']) && isset($_SESSION['acl']['sogo_access']) && $_SESSION['acl']['sogo_access'] == "1") ? intval($_data['sogo_access']) : intval($is_now['attributes']['sogo_access']); - (int)$imap_access = (isset($_data['imap_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['imap_access']) : intval($is_now['attributes']['imap_access']); - (int)$pop3_access = (isset($_data['pop3_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['pop3_access']) : intval($is_now['attributes']['pop3_access']); - (int)$smtp_access = (isset($_data['smtp_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['smtp_access']) : intval($is_now['attributes']['smtp_access']); - (int)$sieve_access = (isset($_data['sieve_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['sieve_access']) : intval($is_now['attributes']['sieve_access']); - (int)$relayhost = (isset($_data['relayhost']) && isset($_SESSION['acl']['mailbox_relayhost']) && $_SESSION['acl']['mailbox_relayhost'] == "1") ? intval($_data['relayhost']) : intval($is_now['attributes']['relayhost']); - (int)$quota_m = (isset_has_content($_data['quota'])) ? intval($_data['quota']) : ($is_now['quota'] / 1048576); - $name = (!empty($_data['name'])) ? ltrim(rtrim($_data['name'], '>'), '<') : $is_now['name']; - $domain = $is_now['domain']; - $quota_b = $quota_m * 1048576; - $password = (!empty($_data['password'])) ? $_data['password'] : null; - $password2 = (!empty($_data['password2'])) ? $_data['password2'] : null; - $tags = (is_array($_data['tags']) ? $_data['tags'] : array()); - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - // if already 0 == ok - if ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['unlimited_quota'] != "1") && ($quota_m == 0 && $is_now['quota'] != 0)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'unlimited_quota_acl' - ); - return false; - } - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - $DomainData = mailbox('get', 'domain_details', $domain); - if ($quota_m > ($is_now['max_new_quota'] / 1048576)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('mailbox_quota_left_exceeded', ($is_now['max_new_quota'] / 1048576)) - ); - continue; - } - if ($quota_m > $DomainData['max_quota_for_mbox']) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('mailbox_quota_exceeded', $DomainData['max_quota_for_mbox']) - ); - continue; - } - $extra_acls = array(); - if (isset($_data['extended_sender_acl'])) { - if (!isset($_SESSION['acl']['extend_sender_acl']) || $_SESSION['acl']['extend_sender_acl'] != "1" ) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'extended_sender_acl_denied' - ); - } - else { - $extra_acls = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['extended_sender_acl'])); - foreach ($extra_acls as $i => &$extra_acl) { - if (empty($extra_acl)) { - continue; - } - if (substr($extra_acl, 0, 1) === "@") { - $extra_acl = ltrim($extra_acl, '@'); - } - if (!filter_var($extra_acl, FILTER_VALIDATE_EMAIL) && !is_valid_domain_name($extra_acl)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('extra_acl_invalid', htmlspecialchars($extra_acl)) - ); - unset($extra_acls[$i]); - continue; - } - $domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains')); - if (filter_var($extra_acl, FILTER_VALIDATE_EMAIL)) { - $extra_acl_domain = idn_to_ascii(substr(strstr($extra_acl, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46); - if (in_array($extra_acl_domain, $domains)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('extra_acl_invalid_domain', $extra_acl_domain) - ); - unset($extra_acls[$i]); - continue; - } - } - else { - if (in_array($extra_acl, $domains)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('extra_acl_invalid_domain', $extra_acl_domain) - ); - unset($extra_acls[$i]); - continue; - } - $extra_acl = '@' . $extra_acl; - } - } - $extra_acls = array_filter($extra_acls); - $extra_acls = array_values($extra_acls); - $extra_acls = array_unique($extra_acls); - $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `external` = 1 AND `logged_in_as` = :username"); - $stmt->execute(array( - ':username' => $username - )); - foreach ($extra_acls as $sender_acl_external) { - $stmt = $pdo->prepare("INSERT INTO `sender_acl` (`send_as`, `logged_in_as`, `external`) - VALUES (:sender_acl, :username, 1)"); - $stmt->execute(array( - ':sender_acl' => $sender_acl_external, - ':username' => $username - )); - } - } - } - if (isset($_data['sender_acl'])) { - // Get sender_acl items set by admin - $sender_acl_admin = array_merge( - mailbox('get', 'sender_acl_handles', $username)['sender_acl_domains']['ro'], - mailbox('get', 'sender_acl_handles', $username)['sender_acl_addresses']['ro'] - ); - // Get sender_acl items from POST array - // Set sender_acl_domain_admin to empty array if sender_acl contains "default" to trigger a reset - // Delete records from sender_acl if sender_acl contains "*" and set to array("*") - $_data['sender_acl'] = (array)$_data['sender_acl']; - if (in_array("*", $_data['sender_acl'])) { - $sender_acl_domain_admin = array('*'); - } - elseif (array("default") === $_data['sender_acl']) { - $sender_acl_domain_admin = array(); - } - else { - if (array_search('default', $_data['sender_acl']) !== false){ - unset($_data['sender_acl'][array_search('default', $_data['sender_acl'])]); - } - $sender_acl_domain_admin = $_data['sender_acl']; - } - if (!empty($sender_acl_domain_admin) || !empty($sender_acl_admin)) { - // Check items in POST array and skip invalid - foreach ($sender_acl_domain_admin as $key => $val) { - // Check for invalid domain or email format or not * - if (!filter_var($val, FILTER_VALIDATE_EMAIL) && !is_valid_domain_name(ltrim($val, '@')) && $val != '*') { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('sender_acl_invalid', $sender_acl_domain_admin[$key]) - ); - unset($sender_acl_domain_admin[$key]); - continue; - } - // Check if user has domain access (if object is domain) - $domain = ltrim($sender_acl_domain_admin[$key], '@'); - if (is_valid_domain_name($domain)) { - // Check for- and skip non-mailcow domains - $domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains')); - if (!empty($domains)) { - if (!in_array($domain, $domains)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('sender_acl_invalid', $sender_acl_domain_admin[$key]) - ); - unset($sender_acl_domain_admin[$key]); - continue; - } - } - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('sender_acl_invalid', $sender_acl_domain_admin[$key]) - ); - unset($sender_acl_domain_admin[$key]); - continue; - } - } - // Wildcard can only be used if role == admin - if ($val == '*' && $_SESSION['mailcow_cc_role'] != 'admin') { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('sender_acl_invalid', $sender_acl_domain_admin[$key]) - ); - unset($sender_acl_domain_admin[$key]); - continue; - } - // Check if user has alias access (if object is email) - if (filter_var($val, FILTER_VALIDATE_EMAIL)) { - if (!hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $val)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('sender_acl_invalid', $sender_acl_domain_admin[$key]) - ); - unset($sender_acl_domain_admin[$key]); - continue; - } - } - } - // Merge both arrays - $sender_acl_merged = array_merge($sender_acl_domain_admin, $sender_acl_admin); - // If merged array still contains "*", set it as only value - !in_array('*', $sender_acl_merged) ?: $sender_acl_merged = array('*'); - $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `external` = 0 AND `logged_in_as` = :username"); - $stmt->execute(array( - ':username' => $username - )); - $fixed_sender_aliases = mailbox('get', 'sender_acl_handles', $username)['fixed_sender_aliases']; - foreach ($sender_acl_merged as $sender_acl) { - $domain = ltrim($sender_acl, '@'); - if (is_valid_domain_name($domain)) { - $sender_acl = '@' . $domain; - } - // Don't add if allowed by alias - if (in_array($sender_acl, $fixed_sender_aliases)) { - continue; - } - $stmt = $pdo->prepare("INSERT INTO `sender_acl` (`send_as`, `logged_in_as`) - VALUES (:sender_acl, :username)"); - $stmt->execute(array( - ':sender_acl' => $sender_acl, - ':username' => $username - )); - } - } - else { - $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `external` = 0 AND `logged_in_as` = :username"); - $stmt->execute(array( - ':username' => $username - )); - } - } - if (!empty($password)) { - if (password_check($password, $password2) !== true) { - continue; - } - $password_hashed = hash_password($password); - $stmt = $pdo->prepare("UPDATE `mailbox` SET - `password` = :password_hashed, - `attributes` = JSON_SET(`attributes`, '$.passwd_update', NOW()) - WHERE `username` = :username"); - $stmt->execute(array( - ':password_hashed' => $password_hashed, - ':username' => $username - )); - } - // We could either set alias = 1 if alias = 2 or tune the Postfix alias table (that's what we did, TODO: do it the other way) - $stmt = $pdo->prepare("UPDATE `alias` SET - `active` = :active - WHERE `address` = :address"); - $stmt->execute(array( - ':address' => $username, - ':active' => $active - )); - $stmt = $pdo->prepare("UPDATE `mailbox` SET - `active` = :active, - `name`= :name, - `quota` = :quota_b, - `attributes` = JSON_SET(`attributes`, '$.force_pw_update', :force_pw_update), - `attributes` = JSON_SET(`attributes`, '$.sogo_access', :sogo_access), - `attributes` = JSON_SET(`attributes`, '$.imap_access', :imap_access), - `attributes` = JSON_SET(`attributes`, '$.sieve_access', :sieve_access), - `attributes` = JSON_SET(`attributes`, '$.pop3_access', :pop3_access), - `attributes` = JSON_SET(`attributes`, '$.relayhost', :relayhost), - `attributes` = JSON_SET(`attributes`, '$.smtp_access', :smtp_access) - WHERE `username` = :username"); - $stmt->execute(array( - ':active' => $active, - ':name' => $name, - ':quota_b' => $quota_b, - ':force_pw_update' => $force_pw_update, - ':sogo_access' => $sogo_access, - ':imap_access' => $imap_access, - ':pop3_access' => $pop3_access, - ':sieve_access' => $sieve_access, - ':smtp_access' => $smtp_access, - ':relayhost' => $relayhost, - ':username' => $username - )); - // save tags - foreach($tags as $index => $tag){ - if (empty($tag)) continue; - if ($index > $GLOBALS['TAGGING_LIMIT']) { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('tag_limit_exceeded', 'limit '.$GLOBALS['TAGGING_LIMIT']) - ); - break; - } - $stmt = $pdo->prepare("INSERT INTO `tags_mailbox` (`username`, `tag_name`) VALUES (:username, :tag_name)"); - $stmt->execute(array( - ':username' => $username, - ':tag_name' => $tag, - )); - } - - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('mailbox_modified', $username) - ); - } - break; - case 'mailbox_templates': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), - 'msg' => 'access_denied' - ); - return false; - } - if (!is_array($_data['ids'])) { - $ids = array(); - $ids[] = $_data['ids']; - } - else { - $ids = $_data['ids']; - } - foreach ($ids as $id) { - $is_now = mailbox("get", "mailbox_templates", $id); - if (empty($is_now) || - $is_now["type"] != "mailbox"){ - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), - 'msg' => 'template_id_invalid' - ); - continue; - } - - - // check name - if ($is_now["template"] == "Default" && $is_now["template"] != $_data["template"]){ - // keep template name of Default template - $_data["template"] = $is_now["template"]; - } - else { - $_data["template"] = (isset($_data["template"])) ? $_data["template"] : $is_now["template"]; - } - // check attributes - $attr = array(); - $attr["quota"] = isset($_data['quota']) ? intval($_data['quota']) * 1048576 : 0; - $attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : $is_now['tags']; - $attr["quarantine_notification"] = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : $is_now['quarantine_notification']; - $attr["quarantine_category"] = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : $is_now['quarantine_category']; - $attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : $is_now['rl_frame']; - $attr["rl_value"] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : $is_now['rl_value']; - $attr["force_pw_update"] = isset($_data['force_pw_update']) ? intval($_data['force_pw_update']) : $is_now['force_pw_update']; - $attr["sogo_access"] = isset($_data['sogo_access']) ? intval($_data['sogo_access']) : $is_now['sogo_access']; - $attr["active"] = isset($_data['active']) ? intval($_data['active']) : $is_now['active']; - $attr["tls_enforce_in"] = isset($_data['tls_enforce_in']) ? intval($_data['tls_enforce_in']) : $is_now['tls_enforce_in']; - $attr["tls_enforce_out"] = isset($_data['tls_enforce_out']) ? intval($_data['tls_enforce_out']) : $is_now['tls_enforce_out']; - if (isset($_data['protocol_access'])) { - $_data['protocol_access'] = (array)$_data['protocol_access']; - $attr['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : 0; - $attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0; - $attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0; - $attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0; - } - else { - foreach ($is_now as $key => $value){ - $attr[$key] = $is_now[$key]; - } - } - if (isset($_data['acl'])) { - $_data['acl'] = (array)$_data['acl']; - $attr['acl_spam_alias'] = (in_array('spam_alias', $_data['acl'])) ? 1 : 0; - $attr['acl_tls_policy'] = (in_array('tls_policy', $_data['acl'])) ? 1 : 0; - $attr['acl_spam_score'] = (in_array('spam_score', $_data['acl'])) ? 1 : 0; - $attr['acl_spam_policy'] = (in_array('spam_policy', $_data['acl'])) ? 1 : 0; - $attr['acl_delimiter_action'] = (in_array('delimiter_action', $_data['acl'])) ? 1 : 0; - $attr['acl_syncjobs'] = (in_array('syncjobs', $_data['acl'])) ? 1 : 0; - $attr['acl_eas_reset'] = (in_array('eas_reset', $_data['acl'])) ? 1 : 0; - $attr['acl_sogo_profile_reset'] = (in_array('sogo_profile_reset', $_data['acl'])) ? 1 : 0; - $attr['acl_pushover'] = (in_array('pushover', $_data['acl'])) ? 1 : 0; - $attr['acl_quarantine'] = (in_array('quarantine', $_data['acl'])) ? 1 : 0; - $attr['acl_quarantine_attachments'] = (in_array('quarantine_attachments', $_data['acl'])) ? 1 : 0; - $attr['acl_quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0; - $attr['acl_quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0; - $attr['acl_app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0; - } else { - foreach ($is_now as $key => $value){ - $attr[$key] = $is_now[$key]; - } - } - - - // update template - $stmt = $pdo->prepare("UPDATE `templates` - SET `template` = :template, `attributes` = :attributes - WHERE id = :id"); - $stmt->execute(array( - ":id" => $id , - ":template" => $_data["template"] , - ":attributes" => json_encode($attr) - )); - } - - - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('template_modified', $_data["template"]) - ); - return true; - break; - case 'resource': - if (!is_array($_data['name'])) { - $names = array(); - $names[] = $_data['name']; - } - else { - $names = $_data['name']; - } - foreach ($names as $name) { - $is_now = mailbox('get', 'resource_details', $name); - if (!empty($is_now)) { - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; - $multiple_bookings = (isset($_data['multiple_bookings'])) ? intval($_data['multiple_bookings']) : $is_now['multiple_bookings']; - $description = (!empty($_data['description'])) ? $_data['description'] : $is_now['description']; - $kind = (!empty($_data['kind'])) ? $_data['kind'] : $is_now['kind']; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('resource_invalid', htmlspecialchars($name)) - ); - continue; - } - if (!filter_var($name, FILTER_VALIDATE_EMAIL)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('resource_invalid', htmlspecialchars($name)) - ); - continue; - } - if (!isset($multiple_bookings) || $multiple_bookings < -1) { - $multiple_bookings = -1; - } - if (empty($description)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('description_invalid', htmlspecialchars($name)) - ); - continue; - } - if ($kind != 'location' && $kind != 'group' && $kind != 'thing') { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('resource_invalid', htmlspecialchars($name)) - ); - continue; - } - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $name)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - $stmt = $pdo->prepare("UPDATE `mailbox` SET - `active` = :active, - `name`= :description, - `kind`= :kind, - `multiple_bookings`= :multiple_bookings - WHERE `username` = :name"); - $stmt->execute(array( - ':active' => $active, - ':description' => $description, - ':multiple_bookings' => $multiple_bookings, - ':kind' => $kind, - ':name' => $name - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('resource_modified', htmlspecialchars($name)) - ); - } - break; - } - break; - case 'get': - switch ($_type) { - case 'sender_acl_handles': - if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") { - return false; - } - $data['sender_acl_domains']['ro'] = array(); - $data['sender_acl_domains']['rw'] = array(); - $data['sender_acl_domains']['selectable'] = array(); - $data['sender_acl_addresses']['ro'] = array(); - $data['sender_acl_addresses']['rw'] = array(); - $data['sender_acl_addresses']['selectable'] = array(); - $data['fixed_sender_aliases'] = array(); - $data['external_sender_aliases'] = array(); - // Fixed addresses - $stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `goto` REGEXP :goto AND `address` NOT LIKE '@%'"); - $stmt->execute(array(':goto' => '(^|,)'.$_data.'($|,)')); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($row = array_shift($rows)) { - $data['fixed_sender_aliases'][] = $row['address']; - } - $stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias_domain_alias` FROM `mailbox`, `alias_domain` - WHERE `alias_domain`.`target_domain` = `mailbox`.`domain` - AND `mailbox`.`username` = :username"); - $stmt->execute(array(':username' => $_data)); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($row = array_shift($rows)) { - if (!empty($row['alias_domain_alias'])) { - $data['fixed_sender_aliases'][] = $row['alias_domain_alias']; - } - } - // External addresses - $stmt = $pdo->prepare("SELECT `send_as` as `send_as_external` FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as AND `external` = '1'"); - $stmt->execute(array(':logged_in_as' => $_data)); - $exernal_rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($row = array_shift($exernal_rows)) { - if (!empty($row['send_as_external'])) { - $data['external_sender_aliases'][] = $row['send_as_external']; - } - } - // Return array $data['sender_acl_domains/addresses']['ro'] with read-only objects - // Return array $data['sender_acl_domains/addresses']['rw'] with read-write objects (can be deleted) - $stmt = $pdo->prepare("SELECT REPLACE(`send_as`, '@', '') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as AND `external` = '0' AND (`send_as` LIKE '@%' OR `send_as` = '*')"); - $stmt->execute(array(':logged_in_as' => $_data)); - $domain_rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($domain_row = array_shift($domain_rows)) { - if (is_valid_domain_name($domain_row['send_as']) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain_row['send_as'])) { - $data['sender_acl_domains']['ro'][] = $domain_row['send_as']; - continue; - } - if (is_valid_domain_name($domain_row['send_as']) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain_row['send_as'])) { - $data['sender_acl_domains']['rw'][] = $domain_row['send_as']; - continue; - } - if ($domain_row['send_as'] == '*' && $_SESSION['mailcow_cc_role'] != 'admin') { - $data['sender_acl_domains']['ro'][] = $domain_row['send_as']; - } - if ($domain_row['send_as'] == '*' && $_SESSION['mailcow_cc_role'] == 'admin') { - $data['sender_acl_domains']['rw'][] = $domain_row['send_as']; - } - } - $stmt = $pdo->prepare("SELECT `send_as` FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as AND `external` = '0' AND (`send_as` NOT LIKE '@%' AND `send_as` != '*')"); - $stmt->execute(array(':logged_in_as' => $_data)); - $address_rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($address_row = array_shift($address_rows)) { - if (filter_var($address_row['send_as'], FILTER_VALIDATE_EMAIL) && !hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $address_row['send_as'])) { - $data['sender_acl_addresses']['ro'][] = $address_row['send_as']; - continue; - } - if (filter_var($address_row['send_as'], FILTER_VALIDATE_EMAIL) && hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $address_row['send_as'])) { - $data['sender_acl_addresses']['rw'][] = $address_row['send_as']; - continue; - } - } - $stmt = $pdo->prepare("SELECT `domain` FROM `domain` - WHERE `domain` NOT IN ( - SELECT REPLACE(`send_as`, '@', '') FROM `sender_acl` - WHERE `logged_in_as` = :logged_in_as1 - AND `external` = '0' - AND `send_as` LIKE '@%') - UNION - SELECT '*' FROM `domain` - WHERE '*' NOT IN ( - SELECT `send_as` FROM `sender_acl` - WHERE `logged_in_as` = :logged_in_as2 - AND `external` = '0' - )"); - $stmt->execute(array( - ':logged_in_as1' => $_data, - ':logged_in_as2' => $_data - )); - $rows_domain = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($row_domain = array_shift($rows_domain)) { - if (is_valid_domain_name($row_domain['domain']) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row_domain['domain'])) { - $data['sender_acl_domains']['selectable'][] = $row_domain['domain']; - continue; - } - if ($row_domain['domain'] == '*' && $_SESSION['mailcow_cc_role'] == 'admin') { - $data['sender_acl_domains']['selectable'][] = $row_domain['domain']; - continue; - } - } - $stmt = $pdo->prepare("SELECT `address` FROM `alias` - WHERE `goto` != :goto - AND `address` NOT IN ( - SELECT `send_as` FROM `sender_acl` - WHERE `logged_in_as` = :logged_in_as - AND `external` = '0' - AND `send_as` NOT LIKE '@%')"); - $stmt->execute(array( - ':logged_in_as' => $_data, - ':goto' => $_data - )); - $rows_mbox = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($row = array_shift($rows_mbox)) { - // Aliases are not selectable - if (in_array($row['address'], $data['fixed_sender_aliases'])) { - continue; - } - if (filter_var($row['address'], FILTER_VALIDATE_EMAIL) && hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['address'])) { - $data['sender_acl_addresses']['selectable'][] = $row['address']; - } - } - return $data; - break; - case 'mailboxes': - $mailboxes = array(); - if (isset($_extra) && is_array($_extra) && isset($_data)) { - // get by domain and tags - $tags = is_array($_extra) ? $_extra : array(); - - $sql = ""; - foreach ($tags as $key => $tag) { - $sql = $sql."SELECT DISTINCT `username` FROM `tags_mailbox` WHERE `username` LIKE ? AND `tag_name` LIKE ?"; // distinct, avoid duplicates - if ($key === array_key_last($tags)) break; - $sql = $sql.' UNION DISTINCT '; // combine querys with union - distinct, avoid duplicates - } - - // prepend domain to array - $params = array(); - foreach ($tags as $key => $val){ - array_push($params, '%'.$_data.'%'); - array_push($params, '%'.$val.'%'); - } - $stmt = $pdo->prepare($sql); - $stmt->execute($params); - - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], explode('@', $row['username'])[1])) - $mailboxes[] = $row['username']; - } - } - elseif (isset($_data) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - // get by domain - $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE (`kind` = '' OR `kind` = NULL) AND `domain` = :domain"); - $stmt->execute(array( - ':domain' => $_data, - )); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - $mailboxes[] = $row['username']; - } - } - else { - $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE (`kind` = '' OR `kind` = NULL) AND (`domain` IN (SELECT `domain` FROM `domain_admins` WHERE `active` = '1' AND `username` = :username) OR 'admin' = :role)"); - $stmt->execute(array( - ':username' => $_SESSION['mailcow_cc_username'], - ':role' => $_SESSION['mailcow_cc_role'], - )); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - $mailboxes[] = $row['username']; - } - } - return $mailboxes; - break; - case 'tls_policy': - $attrs = array(); - if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - return false; - } - } - else { - $_data = $_SESSION['mailcow_cc_username']; - } - $stmt = $pdo->prepare("SELECT `attributes` FROM `mailbox` WHERE `username` = :username"); - $stmt->execute(array(':username' => $_data)); - $attrs = $stmt->fetch(PDO::FETCH_ASSOC); - $attrs = json_decode($attrs['attributes'], true); - return array( - 'tls_enforce_in' => $attrs['tls_enforce_in'], - 'tls_enforce_out' => $attrs['tls_enforce_out'] - ); - break; - case 'quarantine_notification': - $attrs = array(); - if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - return false; - } - } - else { - $_data = $_SESSION['mailcow_cc_username']; - } - $stmt = $pdo->prepare("SELECT `attributes` FROM `mailbox` WHERE `username` = :username"); - $stmt->execute(array(':username' => $_data)); - $attrs = $stmt->fetch(PDO::FETCH_ASSOC); - $attrs = json_decode($attrs['attributes'], true); - return $attrs['quarantine_notification']; - break; - case 'quarantine_category': - $attrs = array(); - if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - return false; - } - } - else { - $_data = $_SESSION['mailcow_cc_username']; - } - $stmt = $pdo->prepare("SELECT `attributes` FROM `mailbox` WHERE `username` = :username"); - $stmt->execute(array(':username' => $_data)); - $attrs = $stmt->fetch(PDO::FETCH_ASSOC); - $attrs = json_decode($attrs['attributes'], true); - return $attrs['quarantine_category']; - break; - case 'filters': - $filters = array(); - if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - return false; - } - } - else { - $_data = $_SESSION['mailcow_cc_username']; - } - $stmt = $pdo->prepare("SELECT `id` FROM `sieve_filters` WHERE `username` = :username"); - $stmt->execute(array(':username' => $_data)); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - $filters[] = $row['id']; - } - return $filters; - break; - case 'global_filter_details': - $global_filters = array(); - if ($_SESSION['mailcow_cc_role'] != "admin") { - return false; - } - $global_filters['prefilter'] = file_get_contents('/global_sieve/before'); - $global_filters['postfilter'] = file_get_contents('/global_sieve/after'); - return $global_filters; - break; - case 'filter_details': - $filter_details = array(); - if (!is_numeric($_data)) { - return false; - } - $stmt = $pdo->prepare("SELECT CASE `script_name` WHEN 'active' THEN 1 ELSE 0 END AS `active`, - id, - username, - filter_type, - script_data, - script_desc - FROM `sieve_filters` - WHERE `id` = :id"); - $stmt->execute(array(':id' => $_data)); - $filter_details = $stmt->fetch(PDO::FETCH_ASSOC); - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $filter_details['username'])) { - return false; - } - return $filter_details; - break; - case 'active_user_sieve': - $filter_details = array(); - if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - return false; - } - } - else { - $_data = $_SESSION['mailcow_cc_username']; - } - $exec_fields = array( - 'cmd' => 'sieve', - 'task' => 'list', - 'username' => $_data - ); - $filters = docker('post', 'dovecot-mailcow', 'exec', $exec_fields); - $filters = array_filter(preg_split("/(\r\n|\n|\r)/",$filters)); - foreach ($filters as $filter) { - if (preg_match('/.+ ACTIVE/i', $filter)) { - $exec_fields = array( - 'cmd' => 'sieve', - 'task' => 'print', - 'script_name' => substr($filter, 0, -7), - 'username' => $_data - ); - $script = docker('post', 'dovecot-mailcow', 'exec', $exec_fields); - // Remove first line - return preg_replace('/^.+\n/', '', $script); - } - } - return false; - break; - case 'syncjob_details': - $syncjobdetails = array(); - if (!is_numeric($_data)) { - return false; - } - if (isset($_extra) && in_array('no_log', $_extra)) { - $field_query = $pdo->query('SHOW FIELDS FROM `imapsync` WHERE FIELD NOT IN ("returned_text", "password1")'); - $fields = $field_query->fetchAll(PDO::FETCH_ASSOC); - while($field = array_shift($fields)) { - $shown_fields[] = $field['Field']; - } - $stmt = $pdo->prepare("SELECT " . implode(',', (array)$shown_fields) . ", - `active` - FROM `imapsync` WHERE id = :id"); - } - elseif (isset($_extra) && in_array('with_password', $_extra)) { - $stmt = $pdo->prepare("SELECT *, - `active` - FROM `imapsync` WHERE id = :id"); - } - else { - $field_query = $pdo->query('SHOW FIELDS FROM `imapsync` WHERE FIELD NOT IN ("password1")'); - $fields = $field_query->fetchAll(PDO::FETCH_ASSOC); - while($field = array_shift($fields)) { - $shown_fields[] = $field['Field']; - } - $stmt = $pdo->prepare("SELECT " . implode(',', (array)$shown_fields) . ", - `active` - FROM `imapsync` WHERE id = :id"); - } - $stmt->execute(array(':id' => $_data)); - $syncjobdetails = $stmt->fetch(PDO::FETCH_ASSOC); - if (!empty($syncjobdetails['returned_text'])) { - $syncjobdetails['log'] = $syncjobdetails['returned_text']; - } - else { - $syncjobdetails['log'] = ''; - } - unset($syncjobdetails['returned_text']); - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $syncjobdetails['user2'])) { - return false; - } - return $syncjobdetails; - break; - case 'syncjobs': - $syncjobdata = array(); - if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - return false; - } - } - else { - $_data = $_SESSION['mailcow_cc_username']; - } - $stmt = $pdo->prepare("SELECT `id` FROM `imapsync` WHERE `user2` = :username"); - $stmt->execute(array(':username' => $_data)); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - $syncjobdata[] = $row['id']; - } - return $syncjobdata; - break; - case 'spam_score': - $curl = curl_init(); - curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); - curl_setopt($curl, CURLOPT_URL,"http://rspamd/actions"); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); - $default_actions = curl_exec($curl); - if (!curl_errno($curl)) { - $data_array = json_decode($default_actions, true); - curl_close($curl); - foreach ($data_array as $data) { - if ($data['action'] == 'reject') { - $reject = $data['value']; - continue; - } - elseif ($data['action'] == 'add header') { - $add_header = $data['value']; - continue; - } - } - if (empty($add_header) || empty($reject)) { - // Assume default, set warning - $default = "5, 15"; - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'Could not determine servers default spam score, assuming default' - ); - } - else { - $default = $add_header . ', ' . $reject; - } - } - else { - // Assume default, set warning - $default = "5, 15"; - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'Could not determine servers default spam score, assuming default' - ); - } - curl_close($curl); - $policydata = array(); - if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - return false; - } - } - else { - $_data = $_SESSION['mailcow_cc_username']; - } - $stmt = $pdo->prepare("SELECT `value` FROM `filterconf` WHERE `object` = :username AND - (`option` = 'lowspamlevel' OR `option` = 'highspamlevel')"); - $stmt->execute(array(':username' => $_data)); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if (empty($num_results)) { - return $default; - } - else { - $stmt = $pdo->prepare("SELECT `value` FROM `filterconf` WHERE `option` = 'highspamlevel' AND `object` = :username"); - $stmt->execute(array(':username' => $_data)); - $highspamlevel = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt = $pdo->prepare("SELECT `value` FROM `filterconf` WHERE `option` = 'lowspamlevel' AND `object` = :username"); - $stmt->execute(array(':username' => $_data)); - $lowspamlevel = $stmt->fetch(PDO::FETCH_ASSOC); - return $lowspamlevel['value'].', '.$highspamlevel['value']; - } - break; - case 'time_limited_aliases': - $tladata = array(); - if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - return false; - } - } - else { - $_data = $_SESSION['mailcow_cc_username']; - } - $stmt = $pdo->prepare("SELECT `address`, - `goto`, - `validity`, - `created`, - `modified` - FROM `spamalias` - WHERE `goto` = :username - AND `validity` >= :unixnow"); - $stmt->execute(array(':username' => $_data, ':unixnow' => time())); - $tladata = $stmt->fetchAll(PDO::FETCH_ASSOC); - return $tladata; - break; - case 'delimiter_action': - $policydata = array(); - if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - return false; - } - } - else { - $_data = $_SESSION['mailcow_cc_username']; - } - try { - if ($redis->hGet('RCPT_WANTS_SUBJECT_TAG', $_data)) { - return "subject"; - } - elseif ($redis->hGet('RCPT_WANTS_SUBFOLDER_TAG', $_data)) { - return "subfolder"; - } - else { - return "none"; - } - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('redis_error', $e) - ); - return false; - } - break; - case 'resources': - $resources = array(); - if (isset($_data) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - return false; - } - elseif (isset($_data) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` REGEXP 'location|thing|group' AND `domain` = :domain"); - $stmt->execute(array( - ':domain' => $_data, - )); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - $resources[] = $row['username']; - } - } - else { - $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` REGEXP 'location|thing|group' AND `domain` IN (SELECT `domain` FROM `domain_admins` WHERE `active` = '1' AND `username` = :username) OR 'admin' = :role"); - $stmt->execute(array( - ':username' => $_SESSION['mailcow_cc_username'], - ':role' => $_SESSION['mailcow_cc_role'], - )); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - $resources[] = $row['username']; - } - } - return $resources; - break; - case 'alias_domains': - $aliasdomains = array(); - if (isset($_data) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - return false; - } - elseif (isset($_data) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` = :domain"); - $stmt->execute(array( - ':domain' => $_data, - )); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - $aliasdomains[] = $row['alias_domain']; - } - } - else { - $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` IN (SELECT `domain` FROM `domain_admins` WHERE `active` = '1' AND `username` = :username) OR 'admin' = :role"); - $stmt->execute(array( - ':username' => $_SESSION['mailcow_cc_username'], - ':role' => $_SESSION['mailcow_cc_role'], - )); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - $aliasdomains[] = $row['alias_domain']; - } - } - return $aliasdomains; - break; - case 'aliases': - $aliases = array(); - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - return false; - } - $stmt = $pdo->prepare("SELECT `id` FROM `alias` WHERE `address` != `goto` AND `domain` = :domain"); - $stmt->execute(array( - ':domain' => $_data, - )); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - $aliases[] = $row['id']; - } - return $aliases; - break; - case 'alias_details': - $aliasdata = array(); - $stmt = $pdo->prepare("SELECT - `id`, - `domain`, - `goto`, - `address`, - `public_comment`, - `private_comment`, - `active`, - `sogo_visible`, - `created`, - `modified` - FROM `alias` - WHERE (`id` = :id OR `address` = :address) AND `address` != `goto`"); - $stmt->execute(array( - ':id' => $_data, - ':address' => $_data, - )); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain"); - $stmt->execute(array( - ':domain' => $row['domain'], - )); - $row_alias_domain = $stmt->fetch(PDO::FETCH_ASSOC); - if (isset($row_alias_domain['target_domain']) && !empty($row_alias_domain['target_domain'])) { - $aliasdata['in_primary_domain'] = $row_alias_domain['target_domain']; - } - else { - $aliasdata['in_primary_domain'] = ""; - } - $aliasdata['id'] = $row['id']; - $aliasdata['domain'] = $row['domain']; - $aliasdata['public_comment'] = $row['public_comment']; - $aliasdata['private_comment'] = $row['private_comment']; - $aliasdata['domain'] = $row['domain']; - $aliasdata['goto'] = $row['goto']; - $aliasdata['address'] = $row['address']; - (!filter_var($aliasdata['address'], FILTER_VALIDATE_EMAIL)) ? $aliasdata['is_catch_all'] = 1 : $aliasdata['is_catch_all'] = 0; - $aliasdata['active'] = $row['active']; - $aliasdata['active_int'] = $row['active']; - $aliasdata['sogo_visible'] = $row['sogo_visible']; - $aliasdata['sogo_visible_int'] = $row['sogo_visible']; - $aliasdata['created'] = $row['created']; - $aliasdata['modified'] = $row['modified']; - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $aliasdata['domain'])) { - return false; - } - return $aliasdata; - break; - case 'alias_domain_details': - $aliasdomaindata = array(); - $rl = ratelimit('get', 'domain', $_data); - $stmt = $pdo->prepare("SELECT - `alias_domain`, - `target_domain`, - `active`, - `created`, - `modified` - FROM `alias_domain` - WHERE `alias_domain` = :aliasdomain"); - $stmt->execute(array( - ':aliasdomain' => $_data, - )); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt = $pdo->prepare("SELECT `backupmx` FROM `domain` WHERE `domain` = :target_domain"); - $stmt->execute(array( - ':target_domain' => $row['target_domain'] - )); - $row_parent = $stmt->fetch(PDO::FETCH_ASSOC); - $aliasdomaindata['alias_domain'] = $row['alias_domain']; - $aliasdomaindata['parent_is_backupmx'] = $row_parent['backupmx']; - $aliasdomaindata['target_domain'] = $row['target_domain']; - $aliasdomaindata['active'] = $row['active']; - $aliasdomaindata['active_int'] = $row['active']; - $aliasdomaindata['rl'] = $rl; - $aliasdomaindata['created'] = $row['created']; - $aliasdomaindata['modified'] = $row['modified']; - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $aliasdomaindata['target_domain'])) { - return false; - } - return $aliasdomaindata; - break; - case 'domains': - $domains = array(); - if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") { - return false; - } - - if (isset($_extra) && is_array($_extra)){ - // get by tags - $tags = is_array($_extra) ? $_extra : array(); - // add % as prefix and suffix to every element for relative searching - $tags = array_map(function($x){ return '%'.$x.'%'; }, $tags); - $sql = ""; - foreach ($tags as $key => $tag) { - $sql = $sql."SELECT DISTINCT `domain` FROM `tags_domain` WHERE `tag_name` LIKE ?"; // distinct, avoid duplicates - if ($key === array_key_last($tags)) break; - $sql = $sql.' UNION DISTINCT '; // combine querys with union - distinct, avoid duplicates - } - $stmt = $pdo->prepare($sql); - $stmt->execute($tags); - - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - if ($_SESSION['mailcow_cc_role'] == "admin") - $domains[] = $row['domain']; - elseif (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['domain'])) - $domains[] = $row['domain']; - } - } else { - // get all - $stmt = $pdo->prepare("SELECT `domain` FROM `domain` - WHERE (`domain` IN ( - SELECT `domain` from `domain_admins` - WHERE (`active`='1' AND `username` = :username)) - ) - OR 'admin'= :role"); - $stmt->execute(array( - ':username' => $_SESSION['mailcow_cc_username'], - ':role' => $_SESSION['mailcow_cc_role'], - )); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - $domains[] = $row['domain']; - } - } - - return $domains; - break; - case 'domain_details': - $domaindata = array(); - $_data = idn_to_ascii(strtolower(trim($_data)), 0, INTL_IDNA_VARIANT_UTS46); - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - return false; - } - $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain"); - $stmt->execute(array( - ':domain' => $_data - )); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (!empty($row)) { - $_data = $row['target_domain']; - } - $stmt = $pdo->prepare("SELECT - `domain`, - `description`, - `aliases`, - `mailboxes`, - `defquota`, - `maxquota`, - `created`, - `modified`, - `quota`, - `relayhost`, - `relay_all_recipients`, - `relay_unknown_only`, - `backupmx`, - `gal`, - `active` - FROM `domain` WHERE `domain`= :domain"); - $stmt->execute(array( - ':domain' => $_data - )); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (empty($row)) { - return false; - } - $stmt = $pdo->prepare("SELECT COUNT(`username`) AS `count`, - COALESCE(SUM(`quota`), 0) AS `in_use` - FROM `mailbox` - WHERE (`kind` = '' OR `kind` = NULL) - AND `domain` = :domain"); - $stmt->execute(array(':domain' => $row['domain'])); - $MailboxDataDomain = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt = $pdo->prepare("SELECT SUM(bytes) AS `bytes_total`, SUM(messages) AS `msgs_total` FROM `quota2` - WHERE `username` IN ( - SELECT `username` FROM `mailbox` - WHERE `domain` = :domain - );"); - $stmt->execute(array(':domain' => $row['domain'])); - $SumQuotaInUse = $stmt->fetch(PDO::FETCH_ASSOC); - $rl = ratelimit('get', 'domain', $_data); - $domaindata['max_new_mailbox_quota'] = ($row['quota'] * 1048576) - $MailboxDataDomain['in_use']; - if ($domaindata['max_new_mailbox_quota'] > ($row['maxquota'] * 1048576)) { - $domaindata['max_new_mailbox_quota'] = ($row['maxquota'] * 1048576); - } - $domaindata['def_new_mailbox_quota'] = $domaindata['max_new_mailbox_quota']; - if ($domaindata['def_new_mailbox_quota'] > ($row['defquota'] * 1048576)) { - $domaindata['def_new_mailbox_quota'] = ($row['defquota'] * 1048576); - } - $domaindata['quota_used_in_domain'] = $MailboxDataDomain['in_use']; - if (!empty($SumQuotaInUse['bytes_total'])) { - $domaindata['bytes_total'] = $SumQuotaInUse['bytes_total']; - } - else { - $domaindata['bytes_total'] = 0; - } - if (!empty($SumQuotaInUse['msgs_total'])) { - $domaindata['msgs_total'] = $SumQuotaInUse['msgs_total']; - } - else { - $domaindata['msgs_total'] = 0; - } - $domaindata['mboxes_in_domain'] = $MailboxDataDomain['count']; - $domaindata['mboxes_left'] = $row['mailboxes'] - $MailboxDataDomain['count']; - $domaindata['domain_name'] = $row['domain']; - $domaindata['description'] = $row['description']; - $domaindata['max_num_aliases_for_domain'] = $row['aliases']; - $domaindata['max_num_mboxes_for_domain'] = $row['mailboxes']; - $domaindata['def_quota_for_mbox'] = $row['defquota'] * 1048576; - $domaindata['max_quota_for_mbox'] = $row['maxquota'] * 1048576; - $domaindata['max_quota_for_domain'] = $row['quota'] * 1048576; - $domaindata['relayhost'] = $row['relayhost']; - $domaindata['backupmx'] = $row['backupmx']; - $domaindata['backupmx_int'] = $row['backupmx']; - $domaindata['gal'] = $row['gal']; - $domaindata['gal_int'] = $row['gal']; - $domaindata['rl'] = $rl; - $domaindata['active'] = $row['active']; - $domaindata['active_int'] = $row['active']; - $domaindata['relay_all_recipients'] = $row['relay_all_recipients']; - $domaindata['relay_all_recipients_int'] = $row['relay_all_recipients']; - $domaindata['relay_unknown_only'] = $row['relay_unknown_only']; - $domaindata['relay_unknown_only_int'] = $row['relay_unknown_only']; - $domaindata['created'] = $row['created']; - $domaindata['modified'] = $row['modified']; - $stmt = $pdo->prepare("SELECT COUNT(`address`) AS `alias_count` FROM `alias` - WHERE (`domain`= :domain OR `domain` IN (SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` = :domain2)) - AND `address` NOT IN ( - SELECT `username` FROM `mailbox` - )"); - $stmt->execute(array( - ':domain' => $_data, - ':domain2' => $_data - )); - $AliasDataDomain = $stmt->fetch(PDO::FETCH_ASSOC); - (isset($AliasDataDomain['alias_count'])) ? $domaindata['aliases_in_domain'] = $AliasDataDomain['alias_count'] : $domaindata['aliases_in_domain'] = "0"; - $domaindata['aliases_left'] = $row['aliases'] - $AliasDataDomain['alias_count']; - if ($_SESSION['mailcow_cc_role'] == "admin") - { - $stmt = $pdo->prepare("SELECT GROUP_CONCAT(`username` SEPARATOR ', ') AS domain_admins FROM `domain_admins` WHERE `domain` = :domain"); - $stmt->execute(array( - ':domain' => $_data - )); - $domain_admins = $stmt->fetch(PDO::FETCH_ASSOC); - (isset($domain_admins['domain_admins'])) ? $domaindata['domain_admins'] = $domain_admins['domain_admins'] : $domaindata['domain_admins'] = "-"; - } - $stmt = $pdo->prepare("SELECT `tag_name` - FROM `tags_domain` WHERE `domain`= :domain"); - $stmt->execute(array( - ':domain' => $_data - )); - $tags = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($tag = array_shift($tags)) { - $domaindata['tags'][] = $tag['tag_name']; - } - - return $domaindata; - break; - case 'domain_templates': - if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") { - return false; - } - $_data = (isset($_data)) ? intval($_data) : null; - - if (isset($_data)){ - $stmt = $pdo->prepare("SELECT * FROM `templates` - WHERE `id` = :id AND type = :type"); - $stmt->execute(array( - ":id" => $_data, - ":type" => "domain" - )); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - - if (empty($row)){ - return false; - } - - $row["attributes"] = json_decode($row["attributes"], true); - return $row; - } - else { - $stmt = $pdo->prepare("SELECT * FROM `templates` WHERE `type` = 'domain'"); - $stmt->execute(); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - - if (empty($rows)){ - return false; - } - - foreach($rows as $key => $row){ - $rows[$key]["attributes"] = json_decode($row["attributes"], true); - } - return $rows; - } - break; - case 'mailbox_details': - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - return false; - } - $mailboxdata = array(); - if (preg_match('/y|yes/i', getenv('MASTER'))) { - $stmt = $pdo->prepare("SELECT - `domain`.`backupmx`, - `mailbox`.`username`, - `mailbox`.`name`, - `mailbox`.`active`, - `mailbox`.`domain`, - `mailbox`.`local_part`, - `mailbox`.`quota`, - `mailbox`.`created`, - `mailbox`.`modified`, - `quota2`.`bytes`, - `attributes`, - `quota2`.`messages` - FROM `mailbox`, `quota2`, `domain` - WHERE (`mailbox`.`kind` = '' OR `mailbox`.`kind` = NULL) - AND `mailbox`.`username` = `quota2`.`username` - AND `domain`.`domain` = `mailbox`.`domain` - AND `mailbox`.`username` = :mailbox"); - } - else { - $stmt = $pdo->prepare("SELECT - `domain`.`backupmx`, - `mailbox`.`username`, - `mailbox`.`name`, - `mailbox`.`active`, - `mailbox`.`domain`, - `mailbox`.`local_part`, - `mailbox`.`quota`, - `mailbox`.`created`, - `mailbox`.`modified`, - `quota2replica`.`bytes`, - `attributes`, - `quota2replica`.`messages` - FROM `mailbox`, `quota2replica`, `domain` - WHERE (`mailbox`.`kind` = '' OR `mailbox`.`kind` = NULL) - AND `mailbox`.`username` = `quota2replica`.`username` - AND `domain`.`domain` = `mailbox`.`domain` - AND `mailbox`.`username` = :mailbox"); - } - $stmt->execute(array( - ':mailbox' => $_data, - )); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - - $mailboxdata['username'] = $row['username']; - $mailboxdata['active'] = $row['active']; - $mailboxdata['active_int'] = $row['active']; - $mailboxdata['domain'] = $row['domain']; - $mailboxdata['relayhost'] = $row['relayhost']; - $mailboxdata['name'] = $row['name']; - $mailboxdata['local_part'] = $row['local_part']; - $mailboxdata['quota'] = $row['quota']; - $mailboxdata['messages'] = $row['messages']; - $mailboxdata['attributes'] = json_decode($row['attributes'], true); - $mailboxdata['quota_used'] = intval($row['bytes']); - $mailboxdata['percent_in_use'] = ($row['quota'] == 0) ? '- ' : round((intval($row['bytes']) / intval($row['quota'])) * 100); - $mailboxdata['created'] = $row['created']; - $mailboxdata['modified'] = $row['modified']; - - if ($mailboxdata['percent_in_use'] === '- ') { - $mailboxdata['percent_class'] = "info"; - } - elseif ($mailboxdata['percent_in_use'] >= 90) { - $mailboxdata['percent_class'] = "danger"; - } - elseif ($mailboxdata['percent_in_use'] >= 75) { - $mailboxdata['percent_class'] = "warning"; - } - else { - $mailboxdata['percent_class'] = "success"; - } - - // Determine last logins - $stmt = $pdo->prepare("SELECT MAX(`datetime`) AS `datetime`, `service` FROM `sasl_log` - WHERE `username` = :mailbox - GROUP BY `service` DESC"); - $stmt->execute(array(':mailbox' => $_data)); - $SaslLogsData = $stmt->fetchAll(PDO::FETCH_ASSOC); - foreach ($SaslLogsData as $SaslLogs) { - if ($SaslLogs['service'] == 'imap') { - $last_imap_login = strtotime($SaslLogs['datetime']); - } - else if ($SaslLogs['service'] == 'smtp') { - $last_smtp_login = strtotime($SaslLogs['datetime']); - } - else if ($SaslLogs['service'] == 'pop3') { - $last_pop3_login = strtotime($SaslLogs['datetime']); - } - } - if (!isset($last_imap_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) { - $last_imap_login = 0; - } - if (!isset($last_smtp_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) { - $last_smtp_login = 0; - } - if (!isset($last_pop3_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) { - $last_pop3_login = 0; - } - $mailboxdata['last_imap_login'] = $last_imap_login; - $mailboxdata['last_smtp_login'] = $last_smtp_login; - $mailboxdata['last_pop3_login'] = $last_pop3_login; - - if (!isset($_extra) || $_extra != 'reduced') { - $rl = ratelimit('get', 'mailbox', $_data); - $stmt = $pdo->prepare("SELECT `maxquota`, `quota` FROM `domain` WHERE `domain` = :domain"); - $stmt->execute(array(':domain' => $row['domain'])); - $DomainQuota = $stmt->fetch(PDO::FETCH_ASSOC); - - $stmt = $pdo->prepare("SELECT IFNULL(COUNT(`active`), 0) AS `pushover_active` FROM `pushover` WHERE `username` = :username AND `active` = 1"); - $stmt->execute(array(':username' => $_data)); - $PushoverActive = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt = $pdo->prepare("SELECT COALESCE(SUM(`quota`), 0) as `in_use` FROM `mailbox` WHERE (`kind` = '' OR `kind` = NULL) AND `domain` = :domain AND `username` != :username"); - $stmt->execute(array(':domain' => $row['domain'], ':username' => $_data)); - $MailboxUsage = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt = $pdo->prepare("SELECT IFNULL(COUNT(`address`), 0) AS `sa_count` FROM `spamalias` WHERE `goto` = :address AND `validity` >= :unixnow"); - $stmt->execute(array(':address' => $_data, ':unixnow' => time())); - $SpamaliasUsage = $stmt->fetch(PDO::FETCH_ASSOC); - $mailboxdata['max_new_quota'] = ($DomainQuota['quota'] * 1048576) - $MailboxUsage['in_use']; - $mailboxdata['spam_aliases'] = $SpamaliasUsage['sa_count']; - $mailboxdata['pushover_active'] = ($PushoverActive['pushover_active'] == 1) ? 1 : 0; - if ($mailboxdata['max_new_quota'] > ($DomainQuota['maxquota'] * 1048576)) { - $mailboxdata['max_new_quota'] = ($DomainQuota['maxquota'] * 1048576); - } - if (!empty($rl)) { - $mailboxdata['rl'] = $rl; - $mailboxdata['rl_scope'] = 'mailbox'; - } - else { - $mailboxdata['rl'] = ratelimit('get', 'domain', $row['domain']); - $mailboxdata['rl_scope'] = 'domain'; - } - $mailboxdata['is_relayed'] = $row['backupmx']; - } - $stmt = $pdo->prepare("SELECT `tag_name` - FROM `tags_mailbox` WHERE `username`= :username"); - $stmt->execute(array( - ':username' => $_data - )); - $tags = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($tag = array_shift($tags)) { - $mailboxdata['tags'][] = $tag['tag_name']; - } - - return $mailboxdata; - break; - case 'mailbox_templates': - if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") { - return false; - } - $_data = (isset($_data)) ? intval($_data) : null; - - if (isset($_data)){ - $stmt = $pdo->prepare("SELECT * FROM `templates` - WHERE `id` = :id AND type = :type"); - $stmt->execute(array( - ":id" => $_data, - ":type" => "mailbox" - )); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - - if (empty($row)){ - return false; - } - - $row["attributes"] = json_decode($row["attributes"], true); - return $row; - } - else { - $stmt = $pdo->prepare("SELECT * FROM `templates` WHERE `type` = 'mailbox'"); - $stmt->execute(); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - - if (empty($rows)){ - return false; - } - - foreach($rows as $key => $row){ - $rows[$key]["attributes"] = json_decode($row["attributes"], true); - } - return $rows; - } - break; - case 'resource_details': - $resourcedata = array(); - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - return false; - } - $stmt = $pdo->prepare("SELECT - `username`, - `name`, - `kind`, - `multiple_bookings`, - `local_part`, - `active`, - `domain` - FROM `mailbox` WHERE `kind` REGEXP 'location|thing|group' AND `username` = :resource"); - $stmt->execute(array( - ':resource' => $_data, - )); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - $resourcedata['name'] = $row['username']; - $resourcedata['kind'] = $row['kind']; - $resourcedata['multiple_bookings'] = $row['multiple_bookings']; - $resourcedata['description'] = $row['name']; - $resourcedata['active'] = $row['active']; - $resourcedata['active_int'] = $row['active']; - $resourcedata['domain'] = $row['domain']; - $resourcedata['local_part'] = $row['local_part']; - if (!isset($resourcedata['domain']) || - (isset($resourcedata['domain']) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $resourcedata['domain']))) { - return false; - } - return $resourcedata; - break; - } - break; - case 'delete': - switch ($_type) { - case 'syncjob': - if (!is_array($_data['id'])) { - $ids = array(); - $ids[] = $_data['id']; - } - else { - $ids = $_data['id']; - } - if (!isset($_SESSION['acl']['syncjobs']) || $_SESSION['acl']['syncjobs'] != "1" ) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - foreach ($ids as $id) { - if (!is_numeric($id)) { - return false; - } - $stmt = $pdo->prepare("SELECT `user2` FROM `imapsync` WHERE id = :id"); - $stmt->execute(array(':id' => $id)); - $user2 = $stmt->fetch(PDO::FETCH_ASSOC)['user2']; - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $user2)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - $stmt = $pdo->prepare("DELETE FROM `imapsync` WHERE `id`= :id"); - $stmt->execute(array(':id' => $id)); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('deleted_syncjob', $id) - ); - } - break; - case 'filter': - if (!is_array($_data['id'])) { - $ids = array(); - $ids[] = $_data['id']; - } - else { - $ids = $_data['id']; - } - if (!isset($_SESSION['acl']['filters']) || $_SESSION['acl']['filters'] != "1" ) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - foreach ($ids as $id) { - if (!is_numeric($id)) { - continue; - } - $stmt = $pdo->prepare("SELECT `username` FROM `sieve_filters` WHERE id = :id"); - $stmt->execute(array(':id' => $id)); - $usr = $stmt->fetch(PDO::FETCH_ASSOC)['username']; - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $usr)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - $stmt = $pdo->prepare("DELETE FROM `sieve_filters` WHERE `id`= :id"); - $stmt->execute(array(':id' => $id)); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('delete_filter', $id) - ); - } - break; - case 'time_limited_alias': - if (!is_array($_data['address'])) { - $addresses = array(); - $addresses[] = $_data['address']; - } - else { - $addresses = $_data['address']; - } - if (!isset($_SESSION['acl']['spam_alias']) || $_SESSION['acl']['spam_alias'] != "1" ) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - foreach ($addresses as $address) { - $stmt = $pdo->prepare("SELECT `goto` FROM `spamalias` WHERE `address` = :address"); - $stmt->execute(array(':address' => $address)); - $goto = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $goto)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - $stmt = $pdo->prepare("DELETE FROM `spamalias` WHERE `goto` = :username AND `address` = :item"); - $stmt->execute(array( - ':username' => $goto, - ':item' => $address - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('mailbox_modified', htmlspecialchars($goto)) - ); - } - break; - case 'eas_cache': - if (!is_array($_data['username'])) { - $usernames = array(); - $usernames[] = $_data['username']; - } - else { - $usernames = $_data['username']; - } - if (!isset($_SESSION['acl']['eas_reset']) || $_SESSION['acl']['eas_reset'] != "1" ) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - foreach ($usernames as $username) { - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - $stmt = $pdo->prepare("DELETE FROM `sogo_cache_folder` WHERE `c_uid` = :username"); - $stmt->execute(array( - ':username' => $username - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('eas_reset', htmlspecialchars($username)) - ); - } - break; - case 'sogo_profile': - if (!is_array($_data['username'])) { - $usernames = array(); - $usernames[] = $_data['username']; - } - else { - $usernames = $_data['username']; - } - if (!isset($_SESSION['acl']['sogo_profile_reset']) || $_SESSION['acl']['sogo_profile_reset'] != "1" ) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - foreach ($usernames as $username) { - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - $stmt = $pdo->prepare("DELETE FROM `sogo_user_profile` WHERE `c_uid` = :username"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("DELETE FROM `sogo_cache_folder` WHERE `c_uid` = :username"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . $username . "/%' OR `c_uid` = :username"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("DELETE FROM `sogo_quick_contact` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("DELETE FROM `sogo_quick_appointment` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("DELETE FROM `sogo_folder_info` WHERE `c_path2` = :username"); - $stmt->execute(array( - ':username' => $username - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('sogo_profile_reset', htmlspecialchars($username)) - ); - } - break; - case 'domain': - if (!is_array($_data['domain'])) { - $domains = array(); - $domains[] = $_data['domain']; - } - else { - $domains = $_data['domain']; - } - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - foreach ($domains as $domain) { - if (!is_valid_domain_name($domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'domain_invalid' - ); - continue; - } - $domain = idn_to_ascii(strtolower(trim($domain)), 0, INTL_IDNA_VARIANT_UTS46); - $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` - WHERE `domain` = :domain"); - $stmt->execute(array(':domain' => $domain)); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0 || !empty($num_results)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('domain_not_empty', $domain) - ); - continue; - } - $exec_fields = array('cmd' => 'maildir', 'task' => 'cleanup', 'maildir' => $domain); - $maildir_gc = json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true); - if ($maildir_gc['type'] != 'success') { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'Could not move mail storage to garbage collector: ' . $maildir_gc['msg'] - ); - } - $stmt = $pdo->prepare("DELETE FROM `domain` WHERE `domain` = :domain"); - $stmt->execute(array( - ':domain' => $domain, - )); - $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `domain` = :domain"); - $stmt->execute(array( - ':domain' => $domain, - )); - $stmt = $pdo->prepare("DELETE FROM `alias` WHERE `domain` = :domain"); - $stmt->execute(array( - ':domain' => $domain, - )); - $stmt = $pdo->prepare("DELETE FROM `alias_domain` WHERE `target_domain` = :domain"); - $stmt->execute(array( - ':domain' => $domain, - )); - $stmt = $pdo->prepare("DELETE FROM `mailbox` WHERE `domain` = :domain"); - $stmt->execute(array( - ':domain' => $domain, - )); - $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` LIKE :domain"); - $stmt->execute(array( - ':domain' => '%@'.$domain, - )); - $stmt = $pdo->prepare("DELETE FROM `quota2` WHERE `username` LIKE :domain"); - $stmt->execute(array( - ':domain' => '%@'.$domain, - )); - $stmt = $pdo->prepare("DELETE FROM `pushover` WHERE `username` LIKE :domain"); - $stmt->execute(array( - ':domain' => '%@'.$domain, - )); - $stmt = $pdo->prepare("DELETE FROM `quota2replica` WHERE `username` LIKE :domain"); - $stmt->execute(array( - ':domain' => '%@'.$domain, - )); - $stmt = $pdo->prepare("DELETE FROM `spamalias` WHERE `address` LIKE :domain"); - $stmt->execute(array( - ':domain' => '%@'.$domain, - )); - $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :domain"); - $stmt->execute(array( - ':domain' => $domain, - )); - $stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `local_dest` = :domain"); - $stmt->execute(array( - ':domain' => $domain, - )); - $stmt = $pdo->query("DELETE FROM `admin` WHERE `superadmin` = 0 AND `username` NOT IN (SELECT `username`FROM `domain_admins`);"); - $stmt = $pdo->query("DELETE FROM `da_acl` WHERE `username` NOT IN (SELECT `username`FROM `domain_admins`);"); - try { - $redis->hDel('DOMAIN_MAP', $domain); - $redis->hDel('RL_VALUE', $domain); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('redis_error', $e) - ); - continue; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('domain_removed', htmlspecialchars($domain)) - ); - } - break; - case 'domain_templates': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - if (!is_array($_data['ids'])) { - $ids = array(); - $ids[] = $_data['ids']; - } - else { - $ids = $_data['ids']; - } - - - foreach ($ids as $id) { - // delete template - $stmt = $pdo->prepare("DELETE FROM `templates` - WHERE id = :id AND type = :type AND NOT template = :template"); - $stmt->execute(array( - ":id" => $id, - ":type" => "domain", - ":template" => "Default" - )); - - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('template_removed', htmlspecialchars($id)) - ); - return true; - } - break; - case 'alias': - if (!is_array($_data['id'])) { - $ids = array(); - $ids[] = $_data['id']; - } - else { - $ids = $_data['id']; - } - foreach ($ids as $id) { - $alias_data = mailbox('get', 'alias_details', $id); - if (empty($alias_data)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - $stmt = $pdo->prepare("DELETE FROM `alias` WHERE `id` = :id"); - $stmt->execute(array( - ':id' => $alias_data['id'] - )); - $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `send_as` = :alias_address"); - $stmt->execute(array( - ':alias_address' => $alias_data['address'] - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('alias_removed', htmlspecialchars($alias_data['address'])) - ); - } - break; - case 'alias_domain': - if (!is_array($_data['alias_domain'])) { - $alias_domains = array(); - $alias_domains[] = $_data['alias_domain']; - } - else { - $alias_domains = $_data['alias_domain']; - } - foreach ($alias_domains as $alias_domain) { - if (!is_valid_domain_name($alias_domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'domain_invalid' - ); - continue; - } - $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` - WHERE `alias_domain`= :alias_domain"); - $stmt->execute(array(':alias_domain' => $alias_domain)); - $DomainData = $stmt->fetch(PDO::FETCH_ASSOC); - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $DomainData['target_domain'])) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - $stmt = $pdo->prepare("DELETE FROM `alias_domain` WHERE `alias_domain` = :alias_domain"); - $stmt->execute(array( - ':alias_domain' => $alias_domain, - )); - $stmt = $pdo->prepare("DELETE FROM `alias` WHERE `domain` = :alias_domain"); - $stmt->execute(array( - ':alias_domain' => $alias_domain, - )); - $stmt = $pdo->prepare("DELETE FROM `spamalias` WHERE `address` LIKE :domain"); - $stmt->execute(array( - ':domain' => '%@'.$alias_domain, - )); - $stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `local_dest` = :alias_domain"); - $stmt->execute(array( - ':alias_domain' => $alias_domain, - )); - try { - $redis->hDel('DOMAIN_MAP', $alias_domain); - $redis->hDel('RL_VALUE', $domain); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('redis_error', $e) - ); - continue; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('alias_domain_removed', htmlspecialchars($alias_domain)) - ); - } - break; - case 'mailbox': - if (!is_array($_data['username'])) { - $usernames = array(); - $usernames[] = $_data['username']; - } - else { - $usernames = $_data['username']; - } - foreach ($usernames as $username) { - if (!filter_var($username, FILTER_VALIDATE_EMAIL)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - $mailbox_details = mailbox('get', 'mailbox_details', $username); - if (!empty($mailbox_details['domain']) && !empty($mailbox_details['local_part'])) { - $maildir = $mailbox_details['domain'] . '/' . $mailbox_details['local_part']; - $exec_fields = array('cmd' => 'maildir', 'task' => 'cleanup', 'maildir' => $maildir); - $maildir_gc = json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true); - if ($maildir_gc['type'] != 'success') { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'Could not move maildir to garbage collector: ' . $maildir_gc['msg'] - ); - } - } - else { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'Could not move maildir to garbage collector: variables local_part and/or domain empty' - ); - } - if (strtolower(getenv('SKIP_SOLR')) == 'n') { - $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, 'http://solr:8983/solr/dovecot-fts/update?commit=true'); - curl_setopt($curl, CURLOPT_HTTPHEADER,array('Content-Type: text/xml')); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_POSTFIELDS, 'user:' . $username . ''); - curl_setopt($curl, CURLOPT_TIMEOUT, 30); - $response = curl_exec($curl); - if ($response === false) { - $err = curl_error($curl); - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'Could not remove Solr index: ' . print_r($err, true) - ); - } - curl_close($curl); - } - $stmt = $pdo->prepare("DELETE FROM `alias` WHERE `goto` = :username"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("DELETE FROM `pushover` WHERE `username` = :username"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE `rcpt` = :username"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("DELETE FROM `quota2` WHERE `username` = :username"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("DELETE FROM `quota2replica` WHERE `username` = :username"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("DELETE FROM `mailbox` WHERE `username` = :username"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` = :username"); - $stmt->execute(array( - ':username' => $username - )); - // fk, better safe than sorry - $stmt = $pdo->prepare("DELETE FROM `user_acl` WHERE `username` = :username"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("DELETE FROM `spamalias` WHERE `goto` = :username"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("DELETE FROM `imapsync` WHERE `user2` = :username"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("DELETE FROM `sogo_user_profile` WHERE `c_uid` = :username"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("DELETE FROM `sogo_cache_folder` WHERE `c_uid` = :username"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . str_replace('%', '\%', $username) . "/%' OR `c_uid` = :username"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("DELETE FROM `sogo_quick_contact` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("DELETE FROM `sogo_quick_appointment` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("DELETE FROM `sogo_folder_info` WHERE `c_path2` = :username"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `local_dest` = :username"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("DELETE FROM `oauth_access_tokens` WHERE `user_id` = :username"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("DELETE FROM `oauth_refresh_tokens` WHERE `user_id` = :username"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("DELETE FROM `oauth_authorization_codes` WHERE `user_id` = :username"); - $stmt->execute(array( - ':username' => $username - )); - $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username"); - $stmt->execute(array( - ':username' => $username, - )); - $stmt = $pdo->prepare("DELETE FROM `fido2` WHERE `username` = :username"); - $stmt->execute(array( - ':username' => $username, - )); - $stmt = $pdo->prepare("SELECT `address`, `goto` FROM `alias` - WHERE `goto` REGEXP :username"); - $stmt->execute(array(':username' => '(^|,)'.$username.'($|,)')); - $GotoData = $stmt->fetchAll(PDO::FETCH_ASSOC); - foreach ($GotoData as $gotos) { - $goto_exploded = explode(',', $gotos['goto']); - if (($key = array_search($username, $goto_exploded)) !== false) { - unset($goto_exploded[$key]); - } - $gotos_rebuild = implode(',', (array)$goto_exploded); - $stmt = $pdo->prepare("UPDATE `alias` SET - `goto` = :goto - WHERE `address` = :address"); - $stmt->execute(array( - ':goto' => $gotos_rebuild, - ':address' => $gotos['address'] - )); - } - try { - $redis->hDel('RL_VALUE', $username); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('redis_error', $e) - ); - continue; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('mailbox_removed', htmlspecialchars($username)) - ); - } - break; - case 'mailbox_templates': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - if (!is_array($_data['ids'])) { - $ids = array(); - $ids[] = $_data['ids']; - } - else { - $ids = $_data['ids']; - } - - - foreach ($ids as $id) { - // delete template - $stmt = $pdo->prepare("DELETE FROM `templates` - WHERE id = :id AND type = :type AND NOT template = :template"); - $stmt->execute(array( - ":id" => $id, - ":type" => "mailbox", - ":template" => "Default" - )); - } - - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'template_removed' - ); - return true; - break; - case 'resource': - if (!is_array($_data['name'])) { - $names = array(); - $names[] = $_data['name']; - } - else { - $names = $_data['name']; - } - foreach ($names as $name) { - if (!filter_var($name, FILTER_VALIDATE_EMAIL)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $name)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - $stmt = $pdo->prepare("DELETE FROM `mailbox` WHERE `username` = :username"); - $stmt->execute(array( - ':username' => $name - )); - $stmt = $pdo->prepare("DELETE FROM `sogo_user_profile` WHERE `c_uid` = :username"); - $stmt->execute(array( - ':username' => $name - )); - $stmt = $pdo->prepare("DELETE FROM `sogo_cache_folder` WHERE `c_uid` = :username"); - $stmt->execute(array( - ':username' => $name - )); - $stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . $name . "/%' OR `c_uid` = :username"); - $stmt->execute(array( - ':username' => $name - )); - $stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); - $stmt->execute(array( - ':username' => $name - )); - $stmt = $pdo->prepare("DELETE FROM `sogo_quick_contact` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); - $stmt->execute(array( - ':username' => $name - )); - $stmt = $pdo->prepare("DELETE FROM `sogo_quick_appointment` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); - $stmt->execute(array( - ':username' => $name - )); - $stmt = $pdo->prepare("DELETE FROM `sogo_folder_info` WHERE `c_path2` = :username"); - $stmt->execute(array( - ':username' => $name - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('resource_removed', htmlspecialchars($name)) - ); - } - break; - case 'tags_domain': - if (!is_array($_data['domain'])) { - $domains = array(); - $domains[] = $_data['domain']; - } - else { - $domains = $_data['domain']; - } - $tags = $_data['tags']; - if (!is_array($tags)) $tags = array(); - - - $wasModified = false; - foreach ($domains as $domain) { - if (!is_valid_domain_name($domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'domain_invalid' - ); - continue; - } - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - - foreach($tags as $tag){ - // delete tag - $wasModified = true; - $stmt = $pdo->prepare("DELETE FROM `tags_domain` WHERE `domain` = :domain AND `tag_name` = :tag_name"); - $stmt->execute(array( - ':domain' => $domain, - ':tag_name' => $tag, - )); - } - } - - if (!$wasModified) return false; - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('domain_modified', $domain) - ); - break; - case 'tags_mailbox': - if (!is_array($_data['username'])) { - $usernames = array(); - $usernames[] = $_data['username']; - } - else { - $usernames = $_data['username']; - } - $tags = $_data['tags']; - if (!is_array($tags)) $tags = array(); - - $wasModified = false; - foreach ($usernames as $username) { - if (!filter_var($username, FILTER_VALIDATE_EMAIL)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'email invalid' - ); - continue; - } - - $is_now = mailbox('get', 'mailbox_details', $username); - $domain = $is_now['domain']; - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - - // delete tags - foreach($tags as $tag){ - $wasModified = true; - - $stmt = $pdo->prepare("DELETE FROM `tags_mailbox` WHERE `username` = :username AND `tag_name` = :tag_name"); - $stmt->execute(array( - ':username' => $username, - ':tag_name' => $tag, - )); - } - } - - if (!$wasModified) return false; - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('mailbox_modified', $username) - ); - break; - } - break; - } - if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'mailbox', 'resource')) && getenv('SKIP_SOGO') != "y") { - update_sogo_static_view(); - } -} + 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + if (isset($_data['username']) && filter_var($_data['username'], FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data['username'])) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + else { + $username = $_data['username']; + } + } + else { + $username = $_SESSION['mailcow_cc_username']; + } + if (isset($_data["validity"]) && !filter_var($_data["validity"], FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 87600)))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'validity_missing' + ); + return false; + } + else { + // Default to 1 yr + $_data["validity"] = 8760; + } + $domain = $_data['domain']; + $valid_domains[] = mailbox('get', 'mailbox_details', $username)['domain']; + $valid_alias_domains = user_get_alias_details($username)['alias_domains']; + if (!empty($valid_alias_domains)) { + $valid_domains = array_merge($valid_domains, $valid_alias_domains); + } + if (!in_array($domain, $valid_domains)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'domain_invalid' + ); + return false; + } + $validity = strtotime("+" . $_data["validity"] . " hour"); + $stmt = $pdo->prepare("INSERT INTO `spamalias` (`address`, `goto`, `validity`) VALUES + (:address, :goto, :validity)"); + $stmt->execute(array( + ':address' => readable_random_string(rand(rand(3, 9), rand(3, 9))) . '.' . readable_random_string(rand(rand(3, 9), rand(3, 9))) . '@' . $domain, + ':goto' => $username, + ':validity' => $validity + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_modified', $username) + ); + break; + case 'global_filter': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + $sieve = new Sieve\SieveParser(); + $script_data = $_data['script_data']; + $script_data = str_replace("\r\n", "\n", $script_data); // windows -> unix + $script_data = str_replace("\r", "\n", $script_data); // remaining -> unix + $filter_type = $_data['filter_type']; + try { + $sieve->parse($script_data); + } + catch (Exception $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('sieve_error', $e->getMessage()) + ); + return false; + } + if ($filter_type == 'prefilter') { + try { + if (file_exists('/global_sieve/before')) { + $filter_handle = fopen('/global_sieve/before', 'w'); + if (!$filter_handle) { + throw new Exception($lang['danger']['file_open_error']); + } + fwrite($filter_handle, $script_data); + fclose($filter_handle); + } + $restart_response = json_decode(docker('post', 'dovecot-mailcow', 'restart'), true); + if ($restart_response['type'] == "success") { + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'dovecot_restart_success' + ); + } + else { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'dovecot_restart_failed' + ); + } + } + catch (Exception $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('global_filter_write_error', htmlspecialchars($e->getMessage())) + ); + return false; + } + } + elseif ($filter_type == 'postfilter') { + try { + if (file_exists('/global_sieve/after')) { + $filter_handle = fopen('/global_sieve/after', 'w'); + if (!$filter_handle) { + throw new Exception($lang['danger']['file_open_error']); + } + fwrite($filter_handle, $script_data); + fclose($filter_handle); + } + $restart_response = json_decode(docker('post', 'dovecot-mailcow', 'restart'), true); + if ($restart_response['type'] == "success") { + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'dovecot_restart_success' + ); + } + else { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'dovecot_restart_failed' + ); + } + } + catch (Exception $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('global_filter_write_error', htmlspecialchars($e->getMessage())) + ); + return false; + } + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'invalid_filter_type' + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'global_filter_written' + ); + return true; + case 'filter': + $sieve = new Sieve\SieveParser(); + if (!isset($_SESSION['acl']['filters']) || $_SESSION['acl']['filters'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + if (isset($_data['username']) && filter_var($_data['username'], FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data['username'])) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + else { + $username = $_data['username']; + } + } + elseif ($_SESSION['mailcow_cc_role'] == "user") { + $username = $_SESSION['mailcow_cc_username']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'no_user_defined' + ); + return false; + } + $active = intval($_data['active']); + $script_data = $_data['script_data']; + $script_desc = $_data['script_desc']; + $filter_type = $_data['filter_type']; + if (empty($script_data)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'script_empty' + ); + return false; + } + try { + $sieve->parse($script_data); + } + catch (Exception $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('sieve_error', $e->getMessage()) + ); + return false; + } + if (empty($script_data) || empty($script_desc)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'value_missing' + ); + return false; + } + if ($filter_type != 'postfilter' && $filter_type != 'prefilter') { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'filter_type' + ); + return false; + } + if (!empty($active)) { + $script_name = 'active'; + $stmt = $pdo->prepare("UPDATE `sieve_filters` SET `script_name` = 'inactive' WHERE `username` = :username AND `filter_type` = :filter_type"); + $stmt->execute(array( + ':username' => $username, + ':filter_type' => $filter_type + )); + } + else { + $script_name = 'inactive'; + } + $stmt = $pdo->prepare("INSERT INTO `sieve_filters` (`username`, `script_data`, `script_desc`, `script_name`, `filter_type`) + VALUES (:username, :script_data, :script_desc, :script_name, :filter_type)"); + $stmt->execute(array( + ':username' => $username, + ':script_data' => $script_data, + ':script_desc' => $script_desc, + ':script_name' => $script_name, + ':filter_type' => $filter_type + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_modified', $username) + ); + break; + case 'syncjob': + if (!isset($_SESSION['acl']['syncjobs']) || $_SESSION['acl']['syncjobs'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + if (isset($_data['username']) && filter_var($_data['username'], FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data['username'])) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + else { + $username = $_data['username']; + } + } + elseif ($_SESSION['mailcow_cc_role'] == "user") { + $username = $_SESSION['mailcow_cc_username']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'no_user_defined' + ); + return false; + } + $active = intval($_data['active']); + $subscribeall = intval($_data['subscribeall']); + $delete2duplicates = intval($_data['delete2duplicates']); + $delete1 = intval($_data['delete1']); + $delete2 = intval($_data['delete2']); + $timeout1 = intval($_data['timeout1']); + $timeout2 = intval($_data['timeout2']); + $skipcrossduplicates = intval($_data['skipcrossduplicates']); + $automap = intval($_data['automap']); + $port1 = $_data['port1']; + $host1 = strtolower($_data['host1']); + $password1 = $_data['password1']; + $exclude = $_data['exclude']; + $maxage = $_data['maxage']; + $maxbytespersecond = $_data['maxbytespersecond']; + $subfolder2 = $_data['subfolder2']; + $user1 = $_data['user1']; + $mins_interval = $_data['mins_interval']; + $enc1 = $_data['enc1']; + $custom_params = (empty(trim($_data['custom_params']))) ? '' : trim($_data['custom_params']); + + // validate custom params + foreach (explode('-', $custom_params) as $param){ + if(empty($param)) continue; + + // extract option + if (str_contains($param, '=')) $param = explode('=', $param)[0]; + else $param = rtrim($param, ' '); + // remove first char if first char is - + if ($param[0] == '-') $param = ltrim($param, $param[0]); + + if (str_contains($param, ' ')) { + // bad char + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'bad character SPACE' + ); + return false; + } + + // check if param is whitelisted + if (!in_array(strtolower($param), $GLOBALS["IMAPSYNC_OPTIONS"]["whitelist"])){ + // bad option + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'bad option '. $param + ); + return false; + } + } + if (empty($subfolder2)) { + $subfolder2 = ""; + } + if (!isset($maxage) || !filter_var($maxage, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) { + $maxage = "0"; + } + if (!isset($timeout1) || !filter_var($timeout1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) { + $timeout1 = "600"; + } + if (!isset($timeout2) || !filter_var($timeout2, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) { + $timeout2 = "600"; + } + if (!isset($maxbytespersecond) || !filter_var($maxbytespersecond, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 125000000)))) { + $maxbytespersecond = "0"; + } + if (!filter_var($port1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 65535)))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + if (!filter_var($mins_interval, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 43800)))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + // if (!is_valid_domain_name($host1)) { + // $_SESSION['return'][] = array( + // 'type' => 'danger', + // 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + // 'msg' => 'access_denied' + // ); + // return false; + // } + if ($enc1 != "TLS" && $enc1 != "SSL" && $enc1 != "PLAIN") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + if (@preg_match("/" . $exclude . "/", null) === false) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + $stmt = $pdo->prepare("SELECT '1' FROM `imapsync` + WHERE `user2` = :user2 AND `user1` = :user1 AND `host1` = :host1"); + $stmt->execute(array(':user1' => $user1, ':user2' => $username, ':host1' => $host1)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('object_exists', htmlspecialchars($host1 . ' / ' . $user1)) + ); + return false; + } + $stmt = $pdo->prepare("INSERT INTO `imapsync` (`user2`, `exclude`, `delete1`, `delete2`, `timeout1`, `timeout2`, `automap`, `skipcrossduplicates`, `maxbytespersecond`, `subscribeall`, `maxage`, `subfolder2`, `host1`, `authmech1`, `user1`, `password1`, `mins_interval`, `port1`, `enc1`, `delete2duplicates`, `custom_params`, `active`) + VALUES (:user2, :exclude, :delete1, :delete2, :timeout1, :timeout2, :automap, :skipcrossduplicates, :maxbytespersecond, :subscribeall, :maxage, :subfolder2, :host1, :authmech1, :user1, :password1, :mins_interval, :port1, :enc1, :delete2duplicates, :custom_params, :active)"); + $stmt->execute(array( + ':user2' => $username, + ':custom_params' => $custom_params, + ':exclude' => $exclude, + ':maxage' => $maxage, + ':delete1' => $delete1, + ':delete2' => $delete2, + ':timeout1' => $timeout1, + ':timeout2' => $timeout2, + ':automap' => $automap, + ':skipcrossduplicates' => $skipcrossduplicates, + ':maxbytespersecond' => $maxbytespersecond, + ':subscribeall' => $subscribeall, + ':subfolder2' => $subfolder2, + ':host1' => $host1, + ':authmech1' => 'PLAIN', + ':user1' => $user1, + ':password1' => $password1, + ':mins_interval' => $mins_interval, + ':port1' => $port1, + ':enc1' => $enc1, + ':delete2duplicates' => $delete2duplicates, + ':active' => $active, + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_modified', $username) + ); + break; + case 'domain': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), + 'msg' => 'access_denied' + ); + return false; + } + $domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46); + $description = $_data['description']; + if (empty($description)) $description = $domain; + $tags = (array)$_data['tags']; + $aliases = (int)$_data['aliases']; + $mailboxes = (int)$_data['mailboxes']; + $defquota = (int)$_data['defquota']; + $maxquota = (int)$_data['maxquota']; + $restart_sogo = (int)$_data['restart_sogo']; + $quota = (int)$_data['quota']; + if ($defquota > $maxquota) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'mailbox_defquota_exceeds_mailbox_maxquota' + ); + return false; + } + if ($maxquota > $quota) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'mailbox_quota_exceeds_domain_quota' + ); + return false; + } + if ($defquota == "0" || empty($defquota)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'defquota_empty' + ); + return false; + } + if ($maxquota == "0" || empty($maxquota)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'maxquota_empty' + ); + return false; + } + $active = intval($_data['active']); + $relay_all_recipients = intval($_data['relay_all_recipients']); + $relay_unknown_only = intval($_data['relay_unknown_only']); + $backupmx = intval($_data['backupmx']); + $gal = intval($_data['gal']); + if ($relay_all_recipients == 1) { + $backupmx = '1'; + } + if ($relay_unknown_only == 1) { + $backupmx = 1; + $relay_all_recipients = 1; + } + if (!is_valid_domain_name($domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'domain_invalid' + ); + return false; + } + foreach (array($quota, $maxquota, $mailboxes, $aliases) as $data) { + if (!is_numeric($data)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('object_is_not_numeric', htmlspecialchars($data)) + ); + return false; + } + } + $stmt = $pdo->prepare("SELECT `domain` FROM `domain` + WHERE `domain` = :domain"); + $stmt->execute(array(':domain' => $domain)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` + WHERE `alias_domain` = :domain"); + $stmt->execute(array(':domain' => $domain)); + $num_results = $num_results + count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('domain_exists', htmlspecialchars($domain)) + ); + return false; + } + if ($domain == getenv('MAILCOW_HOSTNAME')) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'domain_cannot_match_hostname' + ); + return false; + } + + $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `external` = 1 AND `send_as` LIKE :domain"); + $stmt->execute(array( + ':domain' => '%@' . $domain + )); + // save domain + $stmt = $pdo->prepare("INSERT INTO `domain` (`domain`, `description`, `aliases`, `mailboxes`, `defquota`, `maxquota`, `quota`, `backupmx`, `gal`, `active`, `relay_unknown_only`, `relay_all_recipients`) + VALUES (:domain, :description, :aliases, :mailboxes, :defquota, :maxquota, :quota, :backupmx, :gal, :active, :relay_unknown_only, :relay_all_recipients)"); + $stmt->execute(array( + ':domain' => $domain, + ':description' => $description, + ':aliases' => $aliases, + ':mailboxes' => $mailboxes, + ':defquota' => $defquota, + ':maxquota' => $maxquota, + ':quota' => $quota, + ':backupmx' => $backupmx, + ':gal' => $gal, + ':active' => $active, + ':relay_unknown_only' => $relay_unknown_only, + ':relay_all_recipients' => $relay_all_recipients + )); + // save tags + foreach($tags as $index => $tag){ + if (empty($tag)) continue; + if ($index > $GLOBALS['TAGGING_LIMIT']) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('tag_limit_exceeded', 'limit '.$GLOBALS['TAGGING_LIMIT']) + ); + break; + } + $stmt = $pdo->prepare("INSERT INTO `tags_domain` (`domain`, `tag_name`) VALUES (:domain, :tag_name)"); + $stmt->execute(array( + ':domain' => $domain, + ':tag_name' => $tag, + )); + } + + try { + $redis->hSet('DOMAIN_MAP', $domain, 1); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('redis_error', $e) + ); + return false; + } + if (!empty(intval($_data['rl_value']))) { + ratelimit('edit', 'domain', array('rl_value' => $_data['rl_value'], 'rl_frame' => $_data['rl_frame'], 'object' => $domain)); + } + if (!empty($_data['key_size']) && !empty($_data['dkim_selector'])) { + if (!empty($redis->hGet('DKIM_SELECTORS', $domain))) { + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'domain_add_dkim_available' + ); + } + else { + dkim('add', array('key_size' => $_data['key_size'], 'dkim_selector' => $_data['dkim_selector'], 'domains' => $domain)); + } + } + if (!empty($restart_sogo)) { + $restart_response = json_decode(docker('post', 'sogo-mailcow', 'restart'), true); + if ($restart_response['type'] == "success") { + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('domain_added', htmlspecialchars($domain)) + ); + return true; + } + else { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'domain_added_sogo_failed' + ); + return false; + } + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('domain_added', htmlspecialchars($domain)) + ); + return true; + break; + case 'alias': + $addresses = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['address'])); + $gotos = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['goto'])); + $active = intval($_data['active']); + $sogo_visible = intval($_data['sogo_visible']); + $goto_null = intval($_data['goto_null']); + $goto_spam = intval($_data['goto_spam']); + $goto_ham = intval($_data['goto_ham']); + $private_comment = $_data['private_comment']; + $public_comment = $_data['public_comment']; + if (strlen($private_comment) > 160 | strlen($public_comment) > 160){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'comment_too_long' + ); + return false; + } + if (empty($addresses[0])) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'alias_empty' + ); + return false; + } + if (empty($gotos[0]) && ($goto_null + $goto_spam + $goto_ham == 0)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'goto_empty' + ); + return false; + } + if ($goto_null == "1") { + $goto = "null@localhost"; + } + elseif ($goto_spam == "1") { + $goto = "spam@localhost"; + } + elseif ($goto_ham == "1") { + $goto = "ham@localhost"; + } + else { + foreach ($gotos as $i => &$goto) { + if (empty($goto)) { + continue; + } + $goto_domain = idn_to_ascii(substr(strstr($goto, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46); + $goto_local_part = strstr($goto, '@', true); + $goto = $goto_local_part.'@'.$goto_domain; + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` + WHERE `kind` REGEXP 'location|thing|group' + AND `username`= :goto"); + $stmt->execute(array(':goto' => $goto)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('goto_invalid', htmlspecialchars($goto)) + ); + unset($gotos[$i]); + continue; + } + if (!filter_var($goto, FILTER_VALIDATE_EMAIL) === true) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('goto_invalid', htmlspecialchars($goto)) + ); + unset($gotos[$i]); + continue; + } + } + $gotos = array_unique($gotos); + $gotos = array_filter($gotos); + if (empty($gotos)) { return false; } + $goto = implode(",", (array)$gotos); + } + foreach ($addresses as $address) { + if (empty($address)) { + continue; + } + if (in_array($address, $gotos)) { + continue; + } + $domain = idn_to_ascii(substr(strstr($address, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46); + $local_part = strstr($address, '@', true); + $address = $local_part.'@'.$domain; + $domaindata = mailbox('get', 'domain_details', $domain); + if (is_array($domaindata) && $domaindata['aliases_left'] == "0") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'max_alias_exceeded' + ); + return false; + } + $stmt = $pdo->prepare("SELECT `address` FROM `alias` + WHERE `address`= :address OR `address` IN ( + SELECT `username` FROM `mailbox`, `alias_domain` + WHERE ( + `alias_domain`.`alias_domain` = :address_d + AND `mailbox`.`username` = CONCAT(:address_l, '@', alias_domain.target_domain)))"); + $stmt->execute(array( + ':address' => $address, + ':address_l' => $local_part, + ':address_d' => $domain + )); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('is_alias_or_mailbox', htmlspecialchars($address)) + ); + continue; + } + $stmt = $pdo->prepare("SELECT `domain` FROM `domain` + WHERE `domain`= :domain1 OR `domain` = (SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain2)"); + $stmt->execute(array(':domain1' => $domain, ':domain2' => $domain)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results == 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('domain_not_found', htmlspecialchars($domain)) + ); + continue; + } + $stmt = $pdo->prepare("SELECT `address` FROM `spamalias` + WHERE `address`= :address"); + $stmt->execute(array(':address' => $address)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('is_spam_alias', htmlspecialchars($address)) + ); + continue; + } + if ((!filter_var($address, FILTER_VALIDATE_EMAIL) === true) && !empty($local_part)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('alias_invalid', $address) + ); + continue; + } + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `public_comment`, `private_comment`, `goto`, `domain`, `sogo_visible`, `active`) + VALUES (:address, :public_comment, :private_comment, :goto, :domain, :sogo_visible, :active)"); + if (!filter_var($address, FILTER_VALIDATE_EMAIL) === true) { + $stmt->execute(array( + ':address' => '@'.$domain, + ':public_comment' => $public_comment, + ':private_comment' => $private_comment, + ':address' => '@'.$domain, + ':goto' => $goto, + ':domain' => $domain, + ':sogo_visible' => $sogo_visible, + ':active' => $active + )); + } + else { + $stmt->execute(array( + ':address' => $address, + ':public_comment' => $public_comment, + ':private_comment' => $private_comment, + ':goto' => $goto, + ':domain' => $domain, + ':sogo_visible' => $sogo_visible, + ':active' => $active + )); + } + $id = $pdo->lastInsertId(); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('alias_added', $address, $id) + ); + } + break; + case 'alias_domain': + $active = intval($_data['active']); + $alias_domains = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['alias_domain'])); + $alias_domains = array_filter($alias_domains); + $target_domain = idn_to_ascii(strtolower(trim($_data['target_domain'])), 0, INTL_IDNA_VARIANT_UTS46); + if (!isset($_SESSION['acl']['alias_domains']) || $_SESSION['acl']['alias_domains'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + if (!is_valid_domain_name($target_domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'target_domain_invalid' + ); + return false; + } + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $target_domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + foreach ($alias_domains as $i => $alias_domain) { + $alias_domain = idn_to_ascii(strtolower(trim($alias_domain)), 0, INTL_IDNA_VARIANT_UTS46); + if (!is_valid_domain_name($alias_domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('alias_domain_invalid', htmlspecialchars(alias_domain)) + ); + continue; + } + if ($alias_domain == $target_domain) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('aliasd_targetd_identical', htmlspecialchars($target_domain)) + ); + continue; + } + $stmt = $pdo->prepare("SELECT `domain` FROM `domain` + WHERE `domain`= :target_domain"); + $stmt->execute(array(':target_domain' => $target_domain)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results == 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('targetd_not_found', htmlspecialchars($target_domain)) + ); + continue; + } + $stmt = $pdo->prepare("SELECT `domain` FROM `domain` + WHERE `domain`= :target_domain AND `backupmx` = '1'"); + $stmt->execute(array(':target_domain' => $target_domain)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results == 1) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('targetd_relay_domain', htmlspecialchars($target_domain)) + ); + continue; + } + $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `alias_domain`= :alias_domain + UNION + SELECT `domain` FROM `domain` WHERE `domain`= :alias_domain_in_domain"); + $stmt->execute(array(':alias_domain' => $alias_domain, ':alias_domain_in_domain' => $alias_domain)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('alias_domain_invalid', $alias_domain) + ); + continue; + } + $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `external` = 1 AND `send_as` LIKE :domain"); + $stmt->execute(array( + ':domain' => '%@' . $domain + )); + $stmt = $pdo->prepare("INSERT INTO `alias_domain` (`alias_domain`, `target_domain`, `active`) + VALUES (:alias_domain, :target_domain, :active)"); + $stmt->execute(array( + ':alias_domain' => $alias_domain, + ':target_domain' => $target_domain, + ':active' => $active + )); + try { + $redis->hSet('DOMAIN_MAP', $alias_domain, 1); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('redis_error', $e) + ); + return false; + } + if (!empty(intval($_data['rl_value']))) { + ratelimit('edit', 'domain', array('rl_value' => $_data['rl_value'], 'rl_frame' => $_data['rl_frame'], 'object' => $alias_domain)); + } + if (!empty($_data['key_size']) && !empty($_data['dkim_selector'])) { + if (!empty($redis->hGet('DKIM_SELECTORS', $alias_domain))) { + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'domain_add_dkim_available' + ); + } + else { + dkim('add', array('key_size' => $_data['key_size'], 'dkim_selector' => $_data['dkim_selector'], 'domains' => $alias_domain)); + } + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('aliasd_added', htmlspecialchars($alias_domain)) + ); + } + break; + case 'mailbox': + $local_part = strtolower(trim($_data['local_part'])); + $domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46); + $username = $local_part . '@' . $domain; + if (!filter_var($username, FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'mailbox_invalid' + ); + return false; + } + if (empty($_data['local_part'])) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'mailbox_invalid' + ); + return false; + } + $password = $_data['password']; + $password2 = $_data['password2']; + $name = ltrim(rtrim($_data['name'], '>'), '<'); + $tags = $_data['tags']; + $quota_m = intval($_data['quota']); + if ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['unlimited_quota'] != "1") && $quota_m === 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'unlimited_quota_acl' + ); + return false; + } + if (empty($name)) { + $name = $local_part; + } + if (isset($_data['protocol_access'])) { + $_data['protocol_access'] = (array)$_data['protocol_access']; + $_data['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : 0; + $_data['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0; + $_data['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0; + $_data['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0; + } + $active = intval($_data['active']); + $force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update']); + $tls_enforce_in = (isset($_data['tls_enforce_in'])) ? intval($_data['tls_enforce_in']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in']); + $tls_enforce_out = (isset($_data['tls_enforce_out'])) ? intval($_data['tls_enforce_out']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out']); + $sogo_access = (isset($_data['sogo_access'])) ? intval($_data['sogo_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['sogo_access']); + $imap_access = (isset($_data['imap_access'])) ? intval($_data['imap_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']); + $pop3_access = (isset($_data['pop3_access'])) ? intval($_data['pop3_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']); + $smtp_access = (isset($_data['smtp_access'])) ? intval($_data['smtp_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']); + $sieve_access = (isset($_data['sieve_access'])) ? intval($_data['sieve_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']); + $relayhost = (isset($_data['relayhost'])) ? intval($_data['relayhost']) : 0; + $quarantine_notification = (isset($_data['quarantine_notification'])) ? strval($_data['quarantine_notification']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']); + $quarantine_category = (isset($_data['quarantine_category'])) ? strval($_data['quarantine_category']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']); + $quota_b = ($quota_m * 1048576); + $mailbox_attrs = json_encode( + array( + 'force_pw_update' => strval($force_pw_update), + 'tls_enforce_in' => strval($tls_enforce_in), + 'tls_enforce_out' => strval($tls_enforce_out), + 'sogo_access' => strval($sogo_access), + 'imap_access' => strval($imap_access), + 'pop3_access' => strval($pop3_access), + 'smtp_access' => strval($smtp_access), + 'sieve_access' => strval($sieve_access), + 'relayhost' => strval($relayhost), + 'passwd_update' => time(), + 'mailbox_format' => strval($MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format']), + 'quarantine_notification' => strval($quarantine_notification), + 'quarantine_category' => strval($quarantine_category) + ) + ); + if (!is_valid_domain_name($domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'domain_invalid' + ); + return false; + } + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + $stmt = $pdo->prepare("SELECT `mailboxes`, `maxquota`, `quota` FROM `domain` + WHERE `domain` = :domain"); + $stmt->execute(array(':domain' => $domain)); + $DomainData = $stmt->fetch(PDO::FETCH_ASSOC); + $stmt = $pdo->prepare("SELECT + COUNT(*) as count, + COALESCE(ROUND(SUM(`quota`)/1048576), 0) as `quota` + FROM `mailbox` + WHERE (`kind` = '' OR `kind` = NULL) + AND `domain` = :domain"); + $stmt->execute(array(':domain' => $domain)); + $MailboxData = $stmt->fetch(PDO::FETCH_ASSOC); + $stmt = $pdo->prepare("SELECT `local_part` FROM `mailbox` WHERE `local_part` = :local_part and `domain`= :domain"); + $stmt->execute(array(':local_part' => $local_part, ':domain' => $domain)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('object_exists', htmlspecialchars($username)) + ); + return false; + } + $stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE address= :username"); + $stmt->execute(array(':username' => $username)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('is_alias', htmlspecialchars($username)) + ); + return false; + } + $stmt = $pdo->prepare("SELECT `address` FROM `spamalias` WHERE `address`= :username"); + $stmt->execute(array(':username' => $username)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('is_spam_alias', htmlspecialchars($username)) + ); + return false; + } + $stmt = $pdo->prepare("SELECT `domain` FROM `domain` WHERE `domain`= :domain"); + $stmt->execute(array(':domain' => $domain)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results == 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('domain_not_found', htmlspecialchars($domain)) + ); + return false; + } + if (password_check($password, $password2) !== true) { + return false; + } + $password_hashed = hash_password($password); + if ($MailboxData['count'] >= $DomainData['mailboxes']) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('max_mailbox_exceeded', $MailboxData['count'], $DomainData['mailboxes']) + ); + return false; + } + if ($quota_m > $DomainData['maxquota']) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_quota_exceeded', $DomainData['maxquota']) + ); + return false; + } + if (($MailboxData['quota'] + $quota_m) > $DomainData['quota']) { + $quota_left_m = ($DomainData['quota'] - $MailboxData['quota']); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_quota_left_exceeded', $quota_left_m) + ); + return false; + } + $stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `quota`, `local_part`, `domain`, `attributes`, `active`) + VALUES (:username, :password_hashed, :name, :quota_b, :local_part, :domain, :mailbox_attrs, :active)"); + $stmt->execute(array( + ':username' => $username, + ':password_hashed' => $password_hashed, + ':name' => $name, + ':quota_b' => $quota_b, + ':local_part' => $local_part, + ':domain' => $domain, + ':mailbox_attrs' => $mailbox_attrs, + ':active' => $active + )); + $stmt = $pdo->prepare("UPDATE `mailbox` SET + `attributes` = JSON_SET(`attributes`, '$.passwd_update', NOW()) + WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username + )); + // save tags + foreach($tags as $index => $tag){ + if (empty($tag)) continue; + if ($index > $GLOBALS['TAGGING_LIMIT']) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('tag_limit_exceeded', 'limit '.$GLOBALS['TAGGING_LIMIT']) + ); + break; + } + $stmt = $pdo->prepare("INSERT INTO `tags_mailbox` (`username`, `tag_name`) VALUES (:username, :tag_name)"); + $stmt->execute(array( + ':username' => $username, + ':tag_name' => $tag, + )); + } + $stmt = $pdo->prepare("INSERT INTO `quota2` (`username`, `bytes`, `messages`) + VALUES (:username, '0', '0') ON DUPLICATE KEY UPDATE `bytes` = '0', `messages` = '0';"); + $stmt->execute(array(':username' => $username)); + $stmt = $pdo->prepare("INSERT INTO `quota2replica` (`username`, `bytes`, `messages`) + VALUES (:username, '0', '0') ON DUPLICATE KEY UPDATE `bytes` = '0', `messages` = '0';"); + $stmt->execute(array(':username' => $username)); + $stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `goto`, `domain`, `active`) + VALUES (:username1, :username2, :domain, :active)"); + $stmt->execute(array( + ':username1' => $username, + ':username2' => $username, + ':domain' => $domain, + ':active' => $active + )); + + + if (isset($_data['acl'])) { + $_data['acl'] = (array)$_data['acl']; + $_data['spam_alias'] = (in_array('spam_alias', $_data['acl'])) ? 1 : 0; + $_data['tls_policy'] = (in_array('tls_policy', $_data['acl'])) ? 1 : 0; + $_data['spam_score'] = (in_array('spam_score', $_data['acl'])) ? 1 : 0; + $_data['spam_policy'] = (in_array('spam_policy', $_data['acl'])) ? 1 : 0; + $_data['delimiter_action'] = (in_array('delimiter_action', $_data['acl'])) ? 1 : 0; + $_data['syncjobs'] = (in_array('syncjobs', $_data['acl'])) ? 1 : 0; + $_data['eas_reset'] = (in_array('eas_reset', $_data['acl'])) ? 1 : 0; + $_data['sogo_profile_reset'] = (in_array('sogo_profile_reset', $_data['acl'])) ? 1 : 0; + $_data['pushover'] = (in_array('pushover', $_data['acl'])) ? 1 : 0; + $_data['quarantine'] = (in_array('quarantine', $_data['acl'])) ? 1 : 0; + $_data['quarantine_attachments'] = (in_array('quarantine_attachments', $_data['acl'])) ? 1 : 0; + $_data['quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0; + $_data['quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0; + $_data['app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0; + + $stmt = $pdo->prepare("INSERT INTO `user_acl` + (`username`, `spam_alias`, `tls_policy`, `spam_score`, `spam_policy`, `delimiter_action`, `syncjobs`, `eas_reset`, `sogo_profile_reset`, + `pushover`, `quarantine`, `quarantine_attachments`, `quarantine_notification`, `quarantine_category`, `app_passwds`) + VALUES (:username, :spam_alias, :tls_policy, :spam_score, :spam_policy, :delimiter_action, :syncjobs, :eas_reset, :sogo_profile_reset, + :pushover, :quarantine, :quarantine_attachments, :quarantine_notification, :quarantine_category, :app_passwds) "); + $stmt->execute(array( + ':username' => $username, + ':spam_alias' => $_data['spam_alias'], + ':tls_policy' => $_data['tls_policy'], + ':spam_score' => $_data['spam_score'], + ':spam_policy' => $_data['spam_policy'], + ':delimiter_action' => $_data['delimiter_action'], + ':syncjobs' => $_data['syncjobs'], + ':eas_reset' => $_data['eas_reset'], + ':sogo_profile_reset' => $_data['sogo_profile_reset'], + ':pushover' => $_data['pushover'], + ':quarantine' => $_data['quarantine'], + ':quarantine_attachments' => $_data['quarantine_attachments'], + ':quarantine_notification' => $_data['quarantine_notification'], + ':quarantine_category' => $_data['quarantine_category'], + ':app_passwds' => $_data['app_passwds'] + )); + } + else { + $stmt = $pdo->prepare("INSERT INTO `user_acl` (`username`) VALUES (:username)"); + $stmt->execute(array( + ':username' => $username + )); + } + + if (isset($_data['rl_frame']) && isset($_data['rl_value'])){ + ratelimit('edit', 'mailbox', array( + 'object' => $username, + 'rl_frame' => $_data['rl_frame'], + 'rl_value' => $_data['rl_value'] + )); + } + + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_added', htmlspecialchars($username)) + ); + break; + case 'resource': + $domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46); + $description = $_data['description']; + $local_part = preg_replace('/[^\da-z]/i', '', preg_quote($description, '/')); + $name = $local_part . '@' . $domain; + $kind = $_data['kind']; + $multiple_bookings = intval($_data['multiple_bookings']); + $active = intval($_data['active']); + if (!filter_var($name, FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'resource_invalid' + ); + return false; + } + if (empty($description)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'description_invalid' + ); + return false; + } + if (!isset($multiple_bookings) || $multiple_bookings < -1) { + $multiple_bookings = -1; + } + if ($kind != 'location' && $kind != 'group' && $kind != 'thing') { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'resource_invalid' + ); + return false; + } + if (!is_valid_domain_name($domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'domain_invalid' + ); + return false; + } + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :name"); + $stmt->execute(array(':name' => $name)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('object_exists', htmlspecialchars($name)) + ); + return false; + } + $stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE address= :name"); + $stmt->execute(array(':name' => $name)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('is_alias', htmlspecialchars($name)) + ); + return false; + } + $stmt = $pdo->prepare("SELECT `address` FROM `spamalias` WHERE `address`= :name"); + $stmt->execute(array(':name' => $name)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('is_spam_alias', htmlspecialchars($name)) + ); + return false; + } + $stmt = $pdo->prepare("SELECT `domain` FROM `domain` WHERE `domain`= :domain"); + $stmt->execute(array(':domain' => $domain)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results == 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('domain_not_found', htmlspecialchars($domain)) + ); + return false; + } + $stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `quota`, `local_part`, `domain`, `active`, `multiple_bookings`, `kind`) + VALUES (:name, 'RESOURCE', :description, 0, :local_part, :domain, :active, :multiple_bookings, :kind)"); + $stmt->execute(array( + ':name' => $name, + ':description' => $description, + ':local_part' => $local_part, + ':domain' => $domain, + ':active' => $active, + ':kind' => $kind, + ':multiple_bookings' => $multiple_bookings + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('resource_added', htmlspecialchars($name)) + ); + break; + case 'domain_templates': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), + 'msg' => 'access_denied' + ); + return false; + } + if (empty($_data["template"])){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), + 'msg' => 'template_name_invalid' + ); + return false; + } + + // check if template name exists, return false + $stmt = $pdo->prepare("SELECT id FROM `templates` WHERE `type` = :type AND `template` = :template"); + $stmt->execute(array( + ":type" => "domain", + ":template" => $_data["template"] + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!empty($row)){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), + 'msg' => array('template_exists', $_data["template"]) + ); + return false; + } + + // check attributes + $attr = array(); + $attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array(); + $attr['max_num_aliases_for_domain'] = (!empty($_data['max_num_aliases_for_domain'])) ? intval($_data['max_num_aliases_for_domain']) : 400; + $attr['max_num_mboxes_for_domain'] = (!empty($_data['max_num_mboxes_for_domain'])) ? intval($_data['max_num_mboxes_for_domain']) : 10; + $attr['def_quota_for_mbox'] = (!empty($_data['def_quota_for_mbox'])) ? intval($_data['def_quota_for_mbox']) * 1048576 : 3072 * 1048576; + $attr['max_quota_for_mbox'] = (!empty($_data['max_quota_for_mbox'])) ? intval($_data['max_quota_for_mbox']) * 1048576 : 10240 * 1048576; + $attr['max_quota_for_domain'] = (!empty($_data['max_quota_for_domain'])) ? intval($_data['max_quota_for_domain']) * 1048576 : 10240 * 1048576; + $attr['rl_frame'] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s"; + $attr['rl_value'] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : ""; + $attr['active'] = isset($_data['active']) ? intval($_data['active']) : 1; + $attr['gal'] = (isset($_data['gal'])) ? intval($_data['gal']) : 1; + $attr['backupmx'] = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : 0; + $attr['relay_all_recipients'] = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : 0; + $attr['relay_unknown_only'] = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : 0; + $attr['dkim_selector'] = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : "dkim"; + $attr['key_size'] = isset($_data['key_size']) ? intval($_data['key_size']) : 2048; + + // save template + $stmt = $pdo->prepare("INSERT INTO `templates` (`type`, `template`, `attributes`) + VALUES (:type, :template, :attributes)"); + $stmt->execute(array( + ":type" => "domain", + ":template" => $_data["template"], + ":attributes" => json_encode($attr) + )); + + // success + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('template_added', $_data["template"]) + ); + return true; + break; + case 'mailbox_templates': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), + 'msg' => 'access_denied' + ); + return false; + } + if (empty($_data["template"])){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), + 'msg' => 'template_name_invalid' + ); + return false; + } + + // check if template name exists, return false + $stmt = $pdo->prepare("SELECT id FROM `templates` WHERE `type` = :type AND `template` = :template"); + $stmt->execute(array( + ":type" => "mailbox", + ":template" => $_data["template"] + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (!empty($row)){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), + 'msg' => array('template_exists', $_data["template"]) + ); + return false; + } + + + // check attributes + $attr = array(); + $attr["quota"] = isset($_data['quota']) ? intval($_data['quota']) * 1048576 : 0; + $attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array(); + $attr["quarantine_notification"] = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']); + $attr["quarantine_category"] = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']); + $attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s"; + $attr["rl_value"] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : ""; + $attr["force_pw_update"] = isset($_data['force_pw_update']) ? intval($_data['force_pw_update']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update']); + $attr["sogo_access"] = isset($_data['sogo_access']) ? intval($_data['sogo_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['sogo_access']); + $attr["active"] = isset($_data['active']) ? intval($_data['active']) : 1; + $attr["tls_enforce_in"] = isset($_data['tls_enforce_in']) ? intval($_data['tls_enforce_in']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in']); + $attr["tls_enforce_out"] = isset($_data['tls_enforce_out']) ? intval($_data['tls_enforce_out']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out']); + if (isset($_data['protocol_access'])) { + $_data['protocol_access'] = (array)$_data['protocol_access']; + $attr['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']); + $attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']); + $attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']); + $attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']); + } + else { + $attr['imap_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']); + $attr['pop3_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']); + $attr['smtp_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']); + $attr['sieve_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']); + } + if (isset($_data['acl'])) { + $_data['acl'] = (array)$_data['acl']; + $attr['acl_spam_alias'] = (in_array('spam_alias', $_data['acl'])) ? 1 : 0; + $attr['acl_tls_policy'] = (in_array('tls_policy', $_data['acl'])) ? 1 : 0; + $attr['acl_spam_score'] = (in_array('spam_score', $_data['acl'])) ? 1 : 0; + $attr['acl_spam_policy'] = (in_array('spam_policy', $_data['acl'])) ? 1 : 0; + $attr['acl_delimiter_action'] = (in_array('delimiter_action', $_data['acl'])) ? 1 : 0; + $attr['acl_syncjobs'] = (in_array('syncjobs', $_data['acl'])) ? 1 : 0; + $attr['acl_eas_reset'] = (in_array('eas_reset', $_data['acl'])) ? 1 : 0; + $attr['acl_sogo_profile_reset'] = (in_array('sogo_profile_reset', $_data['acl'])) ? 1 : 0; + $attr['acl_pushover'] = (in_array('pushover', $_data['acl'])) ? 1 : 0; + $attr['acl_quarantine'] = (in_array('quarantine', $_data['acl'])) ? 1 : 0; + $attr['acl_quarantine_attachments'] = (in_array('quarantine_attachments', $_data['acl'])) ? 1 : 0; + $attr['acl_quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0; + $attr['acl_quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0; + $attr['acl_app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0; + } else { + $_data['acl'] = (array)$_data['acl']; + $attr['acl_spam_alias'] = 1; + $attr['acl_tls_policy'] = 1; + $attr['acl_spam_score'] = 1; + $attr['acl_spam_policy'] = 1; + $attr['acl_delimiter_action'] = 1; + $attr['acl_syncjobs'] = 0; + $attr['acl_eas_reset'] = 1; + $attr['acl_sogo_profile_reset'] = 0; + $attr['acl_pushover'] = 1; + $attr['acl_quarantine'] = 1; + $attr['acl_quarantine_attachments'] = 1; + $attr['acl_quarantine_notification'] = 1; + $attr['acl_quarantine_category'] = 1; + $attr['acl_app_passwds'] = 1; + } + + + + // save template + $stmt = $pdo->prepare("INSERT INTO `templates` (`type`, `template`, `attributes`) + VALUES (:type, :template, :attributes)"); + $stmt->execute(array( + ":type" => "mailbox", + ":template" => $_data["template"], + ":attributes" => json_encode($attr) + )); + + // success + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('template_added', $_data["template"]) + ); + return true; + break; + } + break; + case 'edit': + switch ($_type) { + case 'alias_domain': + $alias_domains = (array)$_data['alias_domain']; + foreach ($alias_domains as $alias_domain) { + $alias_domain = idn_to_ascii(strtolower(trim($alias_domain)), 0, INTL_IDNA_VARIANT_UTS46); + $is_now = mailbox('get', 'alias_domain_details', $alias_domain); + if (!empty($is_now)) { + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; + $target_domain = (!empty($_data['target_domain'])) ? idn_to_ascii(strtolower(trim($_data['target_domain'])), 0, INTL_IDNA_VARIANT_UTS46) : $is_now['target_domain']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('alias_domain_invalid', htmlspecialchars($alias_domain)) + ); + continue; + } + if (!is_valid_domain_name($target_domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('target_domain_invalid', htmlspecialchars($target_domain)) + ); + continue; + } + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $target_domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + if (empty(mailbox('get', 'domain_details', $target_domain)) || !empty(mailbox('get', 'alias_domain_details', $target_domain))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('target_domain_invalid', htmlspecialchars($target_domain)) + ); + continue; + } + $stmt = $pdo->prepare("UPDATE `alias_domain` SET + `target_domain` = :target_domain, + `active` = :active + WHERE `alias_domain` = :alias_domain"); + $stmt->execute(array( + ':alias_domain' => $alias_domain, + ':target_domain' => $target_domain, + ':active' => $active + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('aliasd_modified', htmlspecialchars($alias_domain)) + ); + } + break; + case 'tls_policy': + if (!is_array($_data['username'])) { + $usernames = array(); + $usernames[] = $_data['username']; + } + else { + $usernames = $_data['username']; + } + if (!isset($_SESSION['acl']['tls_policy']) || $_SESSION['acl']['tls_policy'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + foreach ($usernames as $username) { + if (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $is_now = mailbox('get', 'tls_policy', $username); + if (!empty($is_now)) { + $tls_enforce_in = (isset($_data['tls_enforce_in'])) ? intval($_data['tls_enforce_in']) : $is_now['tls_enforce_in']; + $tls_enforce_out = (isset($_data['tls_enforce_out'])) ? intval($_data['tls_enforce_out']) : $is_now['tls_enforce_out']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare("UPDATE `mailbox` + SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_out', :tls_out), + `attributes` = JSON_SET(`attributes`, '$.tls_enforce_in', :tls_in) + WHERE `username` = :username"); + $stmt->execute(array( + ':tls_out' => intval($tls_enforce_out), + ':tls_in' => intval($tls_enforce_in), + ':username' => $username + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_modified', $username) + ); + } + break; + case 'quarantine_notification': + if (!is_array($_data['username'])) { + $usernames = array(); + $usernames[] = $_data['username']; + } + else { + $usernames = $_data['username']; + } + if (!isset($_SESSION['acl']['quarantine_notification']) || $_SESSION['acl']['quarantine_notification'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + foreach ($usernames as $username) { + if (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $is_now = mailbox('get', 'quarantine_notification', $username); + if (!empty($is_now)) { + $quarantine_notification = (isset($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : $is_now['quarantine_notification']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + if (!in_array($quarantine_notification, array('never', 'hourly', 'daily', 'weekly'))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare("UPDATE `mailbox` + SET `attributes` = JSON_SET(`attributes`, '$.quarantine_notification', :quarantine_notification) + WHERE `username` = :username"); + $stmt->execute(array( + ':quarantine_notification' => $quarantine_notification, + ':username' => $username + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_modified', $username) + ); + } + break; + case 'quarantine_category': + if (!is_array($_data['username'])) { + $usernames = array(); + $usernames[] = $_data['username']; + } + else { + $usernames = $_data['username']; + } + if (!isset($_SESSION['acl']['quarantine_category']) || $_SESSION['acl']['quarantine_category'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + foreach ($usernames as $username) { + if (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $is_now = mailbox('get', 'quarantine_category', $username); + if (!empty($is_now)) { + $quarantine_category = (isset($_data['quarantine_category'])) ? $_data['quarantine_category'] : $is_now['quarantine_category']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + if (!in_array($quarantine_category, array('add_header', 'reject', 'all'))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare("UPDATE `mailbox` + SET `attributes` = JSON_SET(`attributes`, '$.quarantine_category', :quarantine_category) + WHERE `username` = :username"); + $stmt->execute(array( + ':quarantine_category' => $quarantine_category, + ':username' => $username + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_modified', $username) + ); + } + break; + case 'spam_score': + if (!is_array($_data['username'])) { + $usernames = array(); + $usernames[] = $_data['username']; + } + else { + $usernames = $_data['username']; + } + if (!isset($_SESSION['acl']['spam_score']) || $_SESSION['acl']['spam_score'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + foreach ($usernames as $username) { + if ($_data['spam_score'] == "default") { + $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username + AND (`option` = 'lowspamlevel' OR `option` = 'highspamlevel')"); + $stmt->execute(array( + ':username' => $username + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_modified', $username) + ); + continue; + } + $lowspamlevel = explode(',', $_data['spam_score'])[0]; + $highspamlevel = explode(',', $_data['spam_score'])[1]; + if (!is_numeric($lowspamlevel) || !is_numeric($highspamlevel)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'Invalid spam score, format must be "1,2" where first is low and second is high spam value.' + ); + continue; + } + if ($lowspamlevel == $highspamlevel) { + $highspamlevel = $highspamlevel + 0.1; + } + $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username + AND (`option` = 'lowspamlevel' OR `option` = 'highspamlevel')"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("INSERT INTO `filterconf` (`object`, `option`, `value`) + VALUES (:username, 'highspamlevel', :highspamlevel)"); + $stmt->execute(array( + ':username' => $username, + ':highspamlevel' => $highspamlevel + )); + $stmt = $pdo->prepare("INSERT INTO `filterconf` (`object`, `option`, `value`) + VALUES (:username, 'lowspamlevel', :lowspamlevel)"); + $stmt->execute(array( + ':username' => $username, + ':lowspamlevel' => $lowspamlevel + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_modified', $username) + ); + } + break; + case 'time_limited_alias': + if (!isset($_SESSION['acl']['spam_alias']) || $_SESSION['acl']['spam_alias'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + if (!is_array($_data['address'])) { + $addresses = array(); + $addresses[] = $_data['address']; + } + else { + $addresses = $_data['address']; + } + foreach ($addresses as $address) { + $stmt = $pdo->prepare("SELECT `goto` FROM `spamalias` WHERE `address` = :address"); + $stmt->execute(array(':address' => $address)); + $goto = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $goto)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + if (empty($_data['validity'])) { + continue; + } + $validity = round((int)time() + ($_data['validity'] * 3600)); + $stmt = $pdo->prepare("UPDATE `spamalias` SET `validity` = :validity WHERE + `address` = :address"); + $stmt->execute(array( + ':address' => $address, + ':validity' => $validity + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_modified', htmlspecialchars(implode(', ', (array)$usernames))) + ); + } + break; + case 'delimiter_action': + if (!is_array($_data['username'])) { + $usernames = array(); + $usernames[] = $_data['username']; + } + else { + $usernames = $_data['username']; + } + if (!isset($_SESSION['acl']['delimiter_action']) || $_SESSION['acl']['delimiter_action'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + foreach ($usernames as $username) { + if (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + if (isset($_data['tagged_mail_handler']) && $_data['tagged_mail_handler'] == "subject") { + try { + $redis->hSet('RCPT_WANTS_SUBJECT_TAG', $username, 1); + $redis->hDel('RCPT_WANTS_SUBFOLDER_TAG', $username); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('redis_error', $e) + ); + continue; + } + } + else if (isset($_data['tagged_mail_handler']) && $_data['tagged_mail_handler'] == "subfolder") { + try { + $redis->hSet('RCPT_WANTS_SUBFOLDER_TAG', $username, 1); + $redis->hDel('RCPT_WANTS_SUBJECT_TAG', $username); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('redis_error', $e) + ); + continue; + } + } + else { + try { + $redis->hDel('RCPT_WANTS_SUBJECT_TAG', $username); + $redis->hDel('RCPT_WANTS_SUBFOLDER_TAG', $username); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('redis_error', $e) + ); + continue; + } + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_modified', $username) + ); + } + break; + case 'syncjob': + if (!is_array($_data['id'])) { + $ids = array(); + $ids[] = $_data['id']; + } + else { + $ids = $_data['id']; + } + if (!isset($_SESSION['acl']['syncjobs']) || $_SESSION['acl']['syncjobs'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + foreach ($ids as $id) { + $is_now = mailbox('get', 'syncjob_details', $id, array('with_password')); + if (!empty($is_now)) { + $username = $is_now['user2']; + $user1 = (!empty($_data['user1'])) ? $_data['user1'] : $is_now['user1']; + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; + $last_run = (isset($_data['last_run'])) ? NULL : $is_now['last_run']; + $success = (isset($_data['success'])) ? NULL : $is_now['success']; + $delete2duplicates = (isset($_data['delete2duplicates'])) ? intval($_data['delete2duplicates']) : $is_now['delete2duplicates']; + $subscribeall = (isset($_data['subscribeall'])) ? intval($_data['subscribeall']) : $is_now['subscribeall']; + $delete1 = (isset($_data['delete1'])) ? intval($_data['delete1']) : $is_now['delete1']; + $delete2 = (isset($_data['delete2'])) ? intval($_data['delete2']) : $is_now['delete2']; + $automap = (isset($_data['automap'])) ? intval($_data['automap']) : $is_now['automap']; + $skipcrossduplicates = (isset($_data['skipcrossduplicates'])) ? intval($_data['skipcrossduplicates']) : $is_now['skipcrossduplicates']; + $port1 = (!empty($_data['port1'])) ? $_data['port1'] : $is_now['port1']; + $password1 = (!empty($_data['password1'])) ? $_data['password1'] : $is_now['password1']; + $host1 = (!empty($_data['host1'])) ? $_data['host1'] : $is_now['host1']; + $subfolder2 = (isset($_data['subfolder2'])) ? $_data['subfolder2'] : $is_now['subfolder2']; + $enc1 = (!empty($_data['enc1'])) ? $_data['enc1'] : $is_now['enc1']; + $mins_interval = (!empty($_data['mins_interval'])) ? $_data['mins_interval'] : $is_now['mins_interval']; + $exclude = (isset($_data['exclude'])) ? $_data['exclude'] : $is_now['exclude']; + $custom_params = (isset($_data['custom_params'])) ? $_data['custom_params'] : $is_now['custom_params']; + $maxage = (isset($_data['maxage']) && $_data['maxage'] != "") ? intval($_data['maxage']) : $is_now['maxage']; + $maxbytespersecond = (isset($_data['maxbytespersecond']) && $_data['maxbytespersecond'] != "") ? intval($_data['maxbytespersecond']) : $is_now['maxbytespersecond']; + $timeout1 = (isset($_data['timeout1']) && $_data['timeout1'] != "") ? intval($_data['timeout1']) : $is_now['timeout1']; + $timeout2 = (isset($_data['timeout2']) && $_data['timeout2'] != "") ? intval($_data['timeout2']) : $is_now['timeout2']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + + // validate custom params + foreach (explode('-', $custom_params) as $param){ + if(empty($param)) continue; + + // extract option + if (str_contains($param, '=')) $param = explode('=', $param)[0]; + else $param = rtrim($param, ' '); + // remove first char if first char is - + if ($param[0] == '-') $param = ltrim($param, $param[0]); + + if (str_contains($param, ' ')) { + // bad char + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'bad character SPACE' + ); + return false; + } + + // check if param is whitelisted + if (!in_array(strtolower($param), $GLOBALS["IMAPSYNC_OPTIONS"]["whitelist"])){ + // bad option + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'bad option '. $param + ); + return false; + } + } + if (empty($subfolder2)) { + $subfolder2 = ""; + } + if (!isset($maxage) || !filter_var($maxage, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) { + $maxage = "0"; + } + if (!isset($timeout1) || !filter_var($timeout1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) { + $timeout1 = "600"; + } + if (!isset($timeout2) || !filter_var($timeout2, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) { + $timeout2 = "600"; + } + if (!isset($maxbytespersecond) || !filter_var($maxbytespersecond, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 125000000)))) { + $maxbytespersecond = "0"; + } + if (!filter_var($port1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 65535)))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + if (!filter_var($mins_interval, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 43800)))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + if (!is_valid_domain_name($host1)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + if ($enc1 != "TLS" && $enc1 != "SSL" && $enc1 != "PLAIN") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + if (@preg_match("/" . $exclude . "/", null) === false) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare("UPDATE `imapsync` SET `delete1` = :delete1, + `delete2` = :delete2, + `automap` = :automap, + `skipcrossduplicates` = :skipcrossduplicates, + `maxage` = :maxage, + `maxbytespersecond` = :maxbytespersecond, + `subfolder2` = :subfolder2, + `exclude` = :exclude, + `host1` = :host1, + `last_run` = :last_run, + `success` = :success, + `user1` = :user1, + `password1` = :password1, + `mins_interval` = :mins_interval, + `port1` = :port1, + `enc1` = :enc1, + `delete2duplicates` = :delete2duplicates, + `custom_params` = :custom_params, + `timeout1` = :timeout1, + `timeout2` = :timeout2, + `subscribeall` = :subscribeall, + `active` = :active + WHERE `id` = :id"); + $stmt->execute(array( + ':delete1' => $delete1, + ':delete2' => $delete2, + ':automap' => $automap, + ':skipcrossduplicates' => $skipcrossduplicates, + ':id' => $id, + ':exclude' => $exclude, + ':maxage' => $maxage, + ':maxbytespersecond' => $maxbytespersecond, + ':subfolder2' => $subfolder2, + ':host1' => $host1, + ':user1' => $user1, + ':password1' => $password1, + ':last_run' => $last_run, + ':success' => $success, + ':mins_interval' => $mins_interval, + ':port1' => $port1, + ':enc1' => $enc1, + ':delete2duplicates' => $delete2duplicates, + ':custom_params' => $custom_params, + ':timeout1' => $timeout1, + ':timeout2' => $timeout2, + ':subscribeall' => $subscribeall, + ':active' => $active, + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_modified', $username) + ); + } + break; + case 'filter': + if (!isset($_SESSION['acl']['filters']) || $_SESSION['acl']['filters'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + $sieve = new Sieve\SieveParser(); + if (!is_array($_data['id'])) { + $ids = array(); + $ids[] = $_data['id']; + } + else { + $ids = $_data['id']; + } + foreach ($ids as $id) { + $is_now = mailbox('get', 'filter_details', $id); + if (!empty($is_now)) { + $username = $is_now['username']; + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; + $script_desc = (!empty($_data['script_desc'])) ? $_data['script_desc'] : $is_now['script_desc']; + $script_data = (!empty($_data['script_data'])) ? $_data['script_data'] : $is_now['script_data']; + $filter_type = (!empty($_data['filter_type'])) ? $_data['filter_type'] : $is_now['filter_type']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + try { + $sieve->parse($script_data); + } + catch (Exception $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('sieve_error', $e->getMessage()) + ); + continue; + } + if ($filter_type != 'postfilter' && $filter_type != 'prefilter') { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'filter_type' + ); + continue; + } + if ($active == '1') { + $script_name = 'active'; + $stmt = $pdo->prepare("UPDATE `sieve_filters` + SET `script_name` = 'inactive' + WHERE `username` = :username + AND `filter_type` = :filter_type"); + $stmt->execute(array( + ':username' => $username, + ':filter_type' => $filter_type + )); + } + else { + $script_name = 'inactive'; + } + $stmt = $pdo->prepare("UPDATE `sieve_filters` SET `script_desc` = :script_desc, `script_data` = :script_data, `script_name` = :script_name, `filter_type` = :filter_type + WHERE `id` = :id"); + $stmt->execute(array( + ':script_desc' => $script_desc, + ':script_data' => $script_data, + ':script_name' => $script_name, + ':filter_type' => $filter_type, + ':id' => $id + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_modified', $username) + ); + } + break; + case 'alias': + if (!is_array($_data['id'])) { + $ids = array(); + $ids[] = $_data['id']; + } + else { + $ids = $_data['id']; + } + foreach ($ids as $id) { + $is_now = mailbox('get', 'alias_details', $id); + if (!empty($is_now)) { + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; + $sogo_visible = (isset($_data['sogo_visible'])) ? intval($_data['sogo_visible']) : $is_now['sogo_visible']; + $goto_null = (isset($_data['goto_null'])) ? intval($_data['goto_null']) : 0; + $goto_spam = (isset($_data['goto_spam'])) ? intval($_data['goto_spam']) : 0; + $goto_ham = (isset($_data['goto_ham'])) ? intval($_data['goto_ham']) : 0; + $public_comment = (isset($_data['public_comment'])) ? $_data['public_comment'] : $is_now['public_comment']; + $private_comment = (isset($_data['private_comment'])) ? $_data['private_comment'] : $is_now['private_comment']; + $goto = (!empty($_data['goto'])) ? $_data['goto'] : $is_now['goto']; + $address = (!empty($_data['address'])) ? $_data['address'] : $is_now['address']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('alias_invalid', $address) + ); + continue; + } + if ($_data['expand_alias'] === true || $_data['expand_alias'] == 1) { + $stmt = $pdo->prepare("SELECT `address` FROM `alias` + WHERE `address` = :address + AND `domain` NOT IN ( + SELECT `alias_domain` FROM `alias_domain` + )"); + $stmt->execute(array( + ':address' => $address, + )); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results == 0) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('is_not_primary_alias', htmlspecialchars($address)) + ); + continue; + } + $stmt = $pdo->prepare("SELECT `goto`, GROUP_CONCAT(CONCAT(SUBSTRING(`alias`.`address`, 1, LOCATE('@', `alias`.`address`) - 1), '@', `alias_domain`.`alias_domain`)) AS `missing_alias` + FROM `alias` JOIN `alias_domain` ON `alias_domain`.`target_domain` = `alias`.`domain` + WHERE CONCAT(SUBSTRING(`alias`.`address`, 1, LOCATE('@', `alias`.`address`) - 1), '@', `alias_domain`.`alias_domain`) NOT IN ( + SELECT `address` FROM `alias` WHERE `address` != `goto` + ) + AND `alias`.`address` NOT IN ( + SELECT `address` FROM `alias` WHERE `address` = `goto` + ) + AND `address` = :address ;"); + $stmt->execute(array( + ':address' => $address + )); + $missing_aliases = $stmt->fetch(PDO::FETCH_ASSOC); + if (!empty($missing_aliases['missing_alias'])) { + mailbox('add', 'alias', array( + 'address' => $missing_aliases['missing_alias'], + 'goto' => $missing_aliases['goto'], + 'sogo_visible' => 1, + 'active' => 1 + )); + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('alias_modified', htmlspecialchars($address)) + ); + continue; + } + $domain = idn_to_ascii(substr(strstr($address, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46); + if ($is_now['address'] != $address) { + $local_part = strstr($address, '@', true); + $address = $local_part.'@'.$domain; + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + if ((!filter_var($address, FILTER_VALIDATE_EMAIL) === true) && !empty($local_part)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('alias_invalid', $address) + ); + continue; + } + if (strtolower($is_now['address']) != strtolower($address)) { + $stmt = $pdo->prepare("SELECT `address` FROM `alias` + WHERE `address`= :address OR `address` IN ( + SELECT `username` FROM `mailbox`, `alias_domain` + WHERE ( + `alias_domain`.`alias_domain` = :address_d + AND `mailbox`.`username` = CONCAT(:address_l, '@', alias_domain.target_domain)))"); + $stmt->execute(array( + ':address' => $address, + ':address_l' => $local_part, + ':address_d' => $domain + )); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('is_alias_or_mailbox', htmlspecialchars($address)) + ); + continue; + } + } + $stmt = $pdo->prepare("SELECT `domain` FROM `domain` + WHERE `domain`= :domain1 OR `domain` = (SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain2)"); + $stmt->execute(array(':domain1' => $domain, ':domain2' => $domain)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results == 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('domain_not_found', htmlspecialchars($domain)) + ); + continue; + } + $stmt = $pdo->prepare("SELECT `address` FROM `spamalias` + WHERE `address`= :address"); + $stmt->execute(array(':address' => $address)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('is_spam_alias', htmlspecialchars($address)) + ); + continue; + } + } + if ($goto_null == "1") { + $goto = "null@localhost"; + } + elseif ($goto_spam == "1") { + $goto = "spam@localhost"; + } + elseif ($goto_ham == "1") { + $goto = "ham@localhost"; + } + else { + $gotos = array_map('trim', preg_split( "/( |,|;|\n)/", $goto)); + foreach ($gotos as $i => &$goto) { + if (empty($goto)) { + continue; + } + if (!filter_var($goto, FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('goto_invalid', $goto) + ); + unset($gotos[$i]); + continue; + } + if ($goto == $address) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'alias_goto_identical' + ); + unset($gotos[$i]); + continue; + } + // Delete from sender_acl to prevent duplicates + $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE + `logged_in_as` = :goto AND + `send_as` = :address"); + $stmt->execute(array( + ':goto' => $goto, + ':address' => $address + )); + } + $gotos = array_unique($gotos); + $gotos = array_filter($gotos); + $goto = implode(",", (array)$gotos); + } + if (!empty($goto)) { + $stmt = $pdo->prepare("UPDATE `alias` SET + `address` = :address, + `public_comment` = :public_comment, + `private_comment` = :private_comment, + `domain` = :domain, + `goto` = :goto, + `sogo_visible`= :sogo_visible, + `active`= :active + WHERE `id` = :id"); + $stmt->execute(array( + ':address' => $address, + ':public_comment' => $public_comment, + ':private_comment' => $private_comment, + ':domain' => $domain, + ':goto' => $goto, + ':sogo_visible' => $sogo_visible, + ':active' => $active, + ':id' => $is_now['id'] + )); + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('alias_modified', htmlspecialchars($address)) + ); + } + break; + case 'domain': + if (!is_array($_data['domain'])) { + $domains = array(); + $domains[] = $_data['domain']; + } + else { + $domains = $_data['domain']; + } + foreach ($domains as $domain) { + $domain = idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46); + if (!is_valid_domain_name($domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'domain_invalid' + ); + continue; + } + if ($_SESSION['mailcow_cc_role'] == "domainadmin" && + hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $is_now = mailbox('get', 'domain_details', $domain); + if (!empty($is_now)) { + $gal = (isset($_data['gal'])) ? intval($_data['gal']) : $is_now['gal']; + $description = (!empty($_data['description']) && isset($_SESSION['acl']['domain_desc']) && $_SESSION['acl']['domain_desc'] == "1") ? $_data['description'] : $is_now['description']; + (int)$relayhost = (isset($_data['relayhost']) && isset($_SESSION['acl']['domain_relayhost']) && $_SESSION['acl']['domain_relayhost'] == "1") ? intval($_data['relayhost']) : intval($is_now['relayhost']); + $tags = (is_array($_data['tags']) ? $_data['tags'] : array()); + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'domain_invalid' + ); + continue; + } + + $stmt = $pdo->prepare("UPDATE `domain` SET + `description` = :description, + `gal` = :gal + WHERE `domain` = :domain"); + $stmt->execute(array( + ':description' => $description, + ':gal' => $gal, + ':domain' => $domain + )); + // save tags + foreach($tags as $index => $tag){ + if (empty($tag)) continue; + if ($index > $GLOBALS['TAGGING_LIMIT']) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('tag_limit_exceeded', 'limit '.$GLOBALS['TAGGING_LIMIT']) + ); + break; + } + $stmt = $pdo->prepare("INSERT INTO `tags_domain` (`domain`, `tag_name`) VALUES (:domain, :tag_name)"); + $stmt->execute(array( + ':domain' => $domain, + ':tag_name' => $tag, + )); + } + + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('domain_modified', htmlspecialchars($domain)) + ); + } + elseif ($_SESSION['mailcow_cc_role'] == "admin") { + $is_now = mailbox('get', 'domain_details', $domain); + if (!empty($is_now)) { + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; + $backupmx = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : $is_now['backupmx']; + $gal = (isset($_data['gal'])) ? intval($_data['gal']) : $is_now['gal']; + $relay_all_recipients = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : $is_now['relay_all_recipients']; + $relay_unknown_only = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : $is_now['relay_unknown_only']; + $relayhost = (isset($_data['relayhost'])) ? intval($_data['relayhost']) : $is_now['relayhost']; + $aliases = (!empty($_data['aliases'])) ? $_data['aliases'] : $is_now['max_num_aliases_for_domain']; + $mailboxes = (isset($_data['mailboxes']) && $_data['mailboxes'] != '') ? intval($_data['mailboxes']) : $is_now['max_num_mboxes_for_domain']; + $defquota = (isset($_data['defquota']) && $_data['defquota'] != '') ? intval($_data['defquota']) : ($is_now['def_quota_for_mbox'] / 1048576); + $maxquota = (!empty($_data['maxquota'])) ? $_data['maxquota'] : ($is_now['max_quota_for_mbox'] / 1048576); + $quota = (!empty($_data['quota'])) ? $_data['quota'] : ($is_now['max_quota_for_domain'] / 1048576); + $description = (!empty($_data['description'])) ? $_data['description'] : $is_now['description']; + $tags = (is_array($_data['tags']) ? $_data['tags'] : array()); + if ($relay_all_recipients == '1') { + $backupmx = '1'; + } + if ($relay_unknown_only == '1') { + $backupmx = '1'; + $relay_all_recipients = '1'; + } + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'domain_invalid' + ); + continue; + } + // todo: should be using api here + $stmt = $pdo->prepare("SELECT + COUNT(*) AS count, + MAX(COALESCE(ROUND(`quota`/1048576), 0)) AS `biggest_mailbox`, + COALESCE(ROUND(SUM(`quota`)/1048576), 0) AS `quota_all` + FROM `mailbox` + WHERE (`kind` = '' OR `kind` = NULL) + AND domain = :domain"); + $stmt->execute(array(':domain' => $domain)); + $MailboxData = $stmt->fetch(PDO::FETCH_ASSOC); + // todo: should be using api here + $stmt = $pdo->prepare("SELECT COUNT(*) AS `count` FROM `alias` + WHERE domain = :domain + AND address NOT IN ( + SELECT `username` FROM `mailbox` + )"); + $stmt->execute(array(':domain' => $domain)); + $AliasData = $stmt->fetch(PDO::FETCH_ASSOC); + if ($defquota > $maxquota) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'mailbox_defquota_exceeds_mailbox_maxquota' + ); + continue; + } + if ($defquota == "0" || empty($defquota)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'defquota_empty' + ); + continue; + } + if ($maxquota > $quota) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'mailbox_quota_exceeds_domain_quota' + ); + continue; + } + if ($maxquota == "0" || empty($maxquota)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'maxquota_empty' + ); + continue; + } + if ($MailboxData['biggest_mailbox'] > $maxquota) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('max_quota_in_use', $MailboxData['biggest_mailbox']) + ); + continue; + } + if ($MailboxData['quota_all'] > $quota) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('domain_quota_m_in_use', $MailboxData['quota_all']) + ); + continue; + } + if ($MailboxData['count'] > $mailboxes) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailboxes_in_use', $MailboxData['count']) + ); + continue; + } + if ($AliasData['count'] > $aliases) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('aliases_in_use', $AliasData['count']) + ); + continue; + } + + $stmt = $pdo->prepare("UPDATE `domain` SET + `relay_all_recipients` = :relay_all_recipients, + `relay_unknown_only` = :relay_unknown_only, + `backupmx` = :backupmx, + `gal` = :gal, + `active` = :active, + `quota` = :quota, + `defquota` = :defquota, + `maxquota` = :maxquota, + `relayhost` = :relayhost, + `mailboxes` = :mailboxes, + `aliases` = :aliases, + `description` = :description + WHERE `domain` = :domain"); + $stmt->execute(array( + ':relay_all_recipients' => $relay_all_recipients, + ':relay_unknown_only' => $relay_unknown_only, + ':backupmx' => $backupmx, + ':gal' => $gal, + ':active' => $active, + ':quota' => $quota, + ':defquota' => $defquota, + ':maxquota' => $maxquota, + ':relayhost' => $relayhost, + ':mailboxes' => $mailboxes, + ':aliases' => $aliases, + ':description' => $description, + ':domain' => $domain + )); + // save tags + foreach($tags as $index => $tag){ + if (empty($tag)) continue; + if ($index > $GLOBALS['TAGGING_LIMIT']) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('tag_limit_exceeded', 'limit '.$GLOBALS['TAGGING_LIMIT']) + ); + break; + } + $stmt = $pdo->prepare("INSERT INTO `tags_domain` (`domain`, `tag_name`) VALUES (:domain, :tag_name)"); + $stmt->execute(array( + ':domain' => $domain, + ':tag_name' => $tag, + )); + } + + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('domain_modified', htmlspecialchars($domain)) + ); + } + } + break; + case 'domain_templates': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), + 'msg' => 'access_denied' + ); + return false; + } + if (!is_array($_data['ids'])) { + $ids = array(); + $ids[] = $_data['ids']; + } + else { + $ids = $_data['ids']; + } + foreach ($ids as $id) { + $is_now = mailbox("get", "domain_templates", $id); + if (empty($is_now) || + $is_now["type"] != "domain"){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), + 'msg' => 'template_id_invalid' + ); + continue; + } + + // check name + if ($is_now["template"] == "Default" && $is_now["template"] != $_data["template"]){ + // keep template name of Default template + $_data["template"] = $is_now["template"]; + } + else { + $_data["template"] = (isset($_data["template"])) ? $_data["template"] : $is_now["template"]; + } + // check attributes + $attr = array(); + $attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array(); + $attr['max_num_aliases_for_domain'] = (isset($_data['max_num_aliases_for_domain'])) ? intval($_data['max_num_aliases_for_domain']) : 0; + $attr['max_num_mboxes_for_domain'] = (isset($_data['max_num_mboxes_for_domain'])) ? intval($_data['max_num_mboxes_for_domain']) : 0; + $attr['def_quota_for_mbox'] = (isset($_data['def_quota_for_mbox'])) ? intval($_data['def_quota_for_mbox']) * 1048576 : 0; + $attr['max_quota_for_mbox'] = (isset($_data['max_quota_for_mbox'])) ? intval($_data['max_quota_for_mbox']) * 1048576 : 0; + $attr['max_quota_for_domain'] = (isset($_data['max_quota_for_domain'])) ? intval($_data['max_quota_for_domain']) * 1048576 : 0; + $attr['rl_frame'] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s"; + $attr['rl_value'] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : ""; + $attr['active'] = isset($_data['active']) ? intval($_data['active']) : 1; + $attr['gal'] = (isset($_data['gal'])) ? intval($_data['gal']) : 1; + $attr['backupmx'] = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : 0; + $attr['relay_all_recipients'] = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : 0; + $attr['relay_unknown_only'] = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : 0; + $attr['dkim_selector'] = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : "dkim"; + $attr['key_size'] = isset($_data['key_size']) ? intval($_data['key_size']) : 2048; + + // update template + $stmt = $pdo->prepare("UPDATE `templates` + SET `template` = :template, `attributes` = :attributes + WHERE id = :id"); + $stmt->execute(array( + ":id" => $id , + ":template" => $_data["template"] , + ":attributes" => json_encode($attr) + )); + } + + + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('template_modified', $_data["template"]) + ); + return true; + break; + case 'mailbox': + if (!is_array($_data['username'])) { + $usernames = array(); + $usernames[] = $_data['username']; + } + else { + $usernames = $_data['username']; + } + foreach ($usernames as $username) { + if (!filter_var($username, FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('username_invalid', $username) + ); + continue; + } + $is_now = mailbox('get', 'mailbox_details', $username); + if (isset($_data['protocol_access'])) { + $_data['protocol_access'] = (array)$_data['protocol_access']; + $_data['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : 0; + $_data['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0; + $_data['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0; + $_data['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0; + } + if (!empty($is_now)) { + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; + (int)$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($is_now['attributes']['force_pw_update']); + (int)$sogo_access = (isset($_data['sogo_access']) && isset($_SESSION['acl']['sogo_access']) && $_SESSION['acl']['sogo_access'] == "1") ? intval($_data['sogo_access']) : intval($is_now['attributes']['sogo_access']); + (int)$imap_access = (isset($_data['imap_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['imap_access']) : intval($is_now['attributes']['imap_access']); + (int)$pop3_access = (isset($_data['pop3_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['pop3_access']) : intval($is_now['attributes']['pop3_access']); + (int)$smtp_access = (isset($_data['smtp_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['smtp_access']) : intval($is_now['attributes']['smtp_access']); + (int)$sieve_access = (isset($_data['sieve_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['sieve_access']) : intval($is_now['attributes']['sieve_access']); + (int)$relayhost = (isset($_data['relayhost']) && isset($_SESSION['acl']['mailbox_relayhost']) && $_SESSION['acl']['mailbox_relayhost'] == "1") ? intval($_data['relayhost']) : intval($is_now['attributes']['relayhost']); + (int)$quota_m = (isset_has_content($_data['quota'])) ? intval($_data['quota']) : ($is_now['quota'] / 1048576); + $name = (!empty($_data['name'])) ? ltrim(rtrim($_data['name'], '>'), '<') : $is_now['name']; + $domain = $is_now['domain']; + $quota_b = $quota_m * 1048576; + $password = (!empty($_data['password'])) ? $_data['password'] : null; + $password2 = (!empty($_data['password2'])) ? $_data['password2'] : null; + $tags = (is_array($_data['tags']) ? $_data['tags'] : array()); + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + // if already 0 == ok + if ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['unlimited_quota'] != "1") && ($quota_m == 0 && $is_now['quota'] != 0)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'unlimited_quota_acl' + ); + return false; + } + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $DomainData = mailbox('get', 'domain_details', $domain); + if ($quota_m > ($is_now['max_new_quota'] / 1048576)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_quota_left_exceeded', ($is_now['max_new_quota'] / 1048576)) + ); + continue; + } + if ($quota_m > $DomainData['max_quota_for_mbox']) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_quota_exceeded', $DomainData['max_quota_for_mbox']) + ); + continue; + } + $extra_acls = array(); + if (isset($_data['extended_sender_acl'])) { + if (!isset($_SESSION['acl']['extend_sender_acl']) || $_SESSION['acl']['extend_sender_acl'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'extended_sender_acl_denied' + ); + } + else { + $extra_acls = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['extended_sender_acl'])); + foreach ($extra_acls as $i => &$extra_acl) { + if (empty($extra_acl)) { + continue; + } + if (substr($extra_acl, 0, 1) === "@") { + $extra_acl = ltrim($extra_acl, '@'); + } + if (!filter_var($extra_acl, FILTER_VALIDATE_EMAIL) && !is_valid_domain_name($extra_acl)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('extra_acl_invalid', htmlspecialchars($extra_acl)) + ); + unset($extra_acls[$i]); + continue; + } + $domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains')); + if (filter_var($extra_acl, FILTER_VALIDATE_EMAIL)) { + $extra_acl_domain = idn_to_ascii(substr(strstr($extra_acl, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46); + if (in_array($extra_acl_domain, $domains)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('extra_acl_invalid_domain', $extra_acl_domain) + ); + unset($extra_acls[$i]); + continue; + } + } + else { + if (in_array($extra_acl, $domains)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('extra_acl_invalid_domain', $extra_acl_domain) + ); + unset($extra_acls[$i]); + continue; + } + $extra_acl = '@' . $extra_acl; + } + } + $extra_acls = array_filter($extra_acls); + $extra_acls = array_values($extra_acls); + $extra_acls = array_unique($extra_acls); + $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `external` = 1 AND `logged_in_as` = :username"); + $stmt->execute(array( + ':username' => $username + )); + foreach ($extra_acls as $sender_acl_external) { + $stmt = $pdo->prepare("INSERT INTO `sender_acl` (`send_as`, `logged_in_as`, `external`) + VALUES (:sender_acl, :username, 1)"); + $stmt->execute(array( + ':sender_acl' => $sender_acl_external, + ':username' => $username + )); + } + } + } + if (isset($_data['sender_acl'])) { + // Get sender_acl items set by admin + $sender_acl_admin = array_merge( + mailbox('get', 'sender_acl_handles', $username)['sender_acl_domains']['ro'], + mailbox('get', 'sender_acl_handles', $username)['sender_acl_addresses']['ro'] + ); + // Get sender_acl items from POST array + // Set sender_acl_domain_admin to empty array if sender_acl contains "default" to trigger a reset + // Delete records from sender_acl if sender_acl contains "*" and set to array("*") + $_data['sender_acl'] = (array)$_data['sender_acl']; + if (in_array("*", $_data['sender_acl'])) { + $sender_acl_domain_admin = array('*'); + } + elseif (array("default") === $_data['sender_acl']) { + $sender_acl_domain_admin = array(); + } + else { + if (array_search('default', $_data['sender_acl']) !== false){ + unset($_data['sender_acl'][array_search('default', $_data['sender_acl'])]); + } + $sender_acl_domain_admin = $_data['sender_acl']; + } + if (!empty($sender_acl_domain_admin) || !empty($sender_acl_admin)) { + // Check items in POST array and skip invalid + foreach ($sender_acl_domain_admin as $key => $val) { + // Check for invalid domain or email format or not * + if (!filter_var($val, FILTER_VALIDATE_EMAIL) && !is_valid_domain_name(ltrim($val, '@')) && $val != '*') { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('sender_acl_invalid', $sender_acl_domain_admin[$key]) + ); + unset($sender_acl_domain_admin[$key]); + continue; + } + // Check if user has domain access (if object is domain) + $domain = ltrim($sender_acl_domain_admin[$key], '@'); + if (is_valid_domain_name($domain)) { + // Check for- and skip non-mailcow domains + $domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains')); + if (!empty($domains)) { + if (!in_array($domain, $domains)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('sender_acl_invalid', $sender_acl_domain_admin[$key]) + ); + unset($sender_acl_domain_admin[$key]); + continue; + } + } + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('sender_acl_invalid', $sender_acl_domain_admin[$key]) + ); + unset($sender_acl_domain_admin[$key]); + continue; + } + } + // Wildcard can only be used if role == admin + if ($val == '*' && $_SESSION['mailcow_cc_role'] != 'admin') { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('sender_acl_invalid', $sender_acl_domain_admin[$key]) + ); + unset($sender_acl_domain_admin[$key]); + continue; + } + // Check if user has alias access (if object is email) + if (filter_var($val, FILTER_VALIDATE_EMAIL)) { + if (!hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $val)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('sender_acl_invalid', $sender_acl_domain_admin[$key]) + ); + unset($sender_acl_domain_admin[$key]); + continue; + } + } + } + // Merge both arrays + $sender_acl_merged = array_merge($sender_acl_domain_admin, $sender_acl_admin); + // If merged array still contains "*", set it as only value + !in_array('*', $sender_acl_merged) ?: $sender_acl_merged = array('*'); + $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `external` = 0 AND `logged_in_as` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $fixed_sender_aliases = mailbox('get', 'sender_acl_handles', $username)['fixed_sender_aliases']; + foreach ($sender_acl_merged as $sender_acl) { + $domain = ltrim($sender_acl, '@'); + if (is_valid_domain_name($domain)) { + $sender_acl = '@' . $domain; + } + // Don't add if allowed by alias + if (in_array($sender_acl, $fixed_sender_aliases)) { + continue; + } + $stmt = $pdo->prepare("INSERT INTO `sender_acl` (`send_as`, `logged_in_as`) + VALUES (:sender_acl, :username)"); + $stmt->execute(array( + ':sender_acl' => $sender_acl, + ':username' => $username + )); + } + } + else { + $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `external` = 0 AND `logged_in_as` = :username"); + $stmt->execute(array( + ':username' => $username + )); + } + } + if (!empty($password)) { + if (password_check($password, $password2) !== true) { + continue; + } + $password_hashed = hash_password($password); + $stmt = $pdo->prepare("UPDATE `mailbox` SET + `password` = :password_hashed, + `attributes` = JSON_SET(`attributes`, '$.passwd_update', NOW()) + WHERE `username` = :username"); + $stmt->execute(array( + ':password_hashed' => $password_hashed, + ':username' => $username + )); + } + // We could either set alias = 1 if alias = 2 or tune the Postfix alias table (that's what we did, TODO: do it the other way) + $stmt = $pdo->prepare("UPDATE `alias` SET + `active` = :active + WHERE `address` = :address"); + $stmt->execute(array( + ':address' => $username, + ':active' => $active + )); + $stmt = $pdo->prepare("UPDATE `mailbox` SET + `active` = :active, + `name`= :name, + `quota` = :quota_b, + `attributes` = JSON_SET(`attributes`, '$.force_pw_update', :force_pw_update), + `attributes` = JSON_SET(`attributes`, '$.sogo_access', :sogo_access), + `attributes` = JSON_SET(`attributes`, '$.imap_access', :imap_access), + `attributes` = JSON_SET(`attributes`, '$.sieve_access', :sieve_access), + `attributes` = JSON_SET(`attributes`, '$.pop3_access', :pop3_access), + `attributes` = JSON_SET(`attributes`, '$.relayhost', :relayhost), + `attributes` = JSON_SET(`attributes`, '$.smtp_access', :smtp_access) + WHERE `username` = :username"); + $stmt->execute(array( + ':active' => $active, + ':name' => $name, + ':quota_b' => $quota_b, + ':force_pw_update' => $force_pw_update, + ':sogo_access' => $sogo_access, + ':imap_access' => $imap_access, + ':pop3_access' => $pop3_access, + ':sieve_access' => $sieve_access, + ':smtp_access' => $smtp_access, + ':relayhost' => $relayhost, + ':username' => $username + )); + // save tags + foreach($tags as $index => $tag){ + if (empty($tag)) continue; + if ($index > $GLOBALS['TAGGING_LIMIT']) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('tag_limit_exceeded', 'limit '.$GLOBALS['TAGGING_LIMIT']) + ); + break; + } + $stmt = $pdo->prepare("INSERT INTO `tags_mailbox` (`username`, `tag_name`) VALUES (:username, :tag_name)"); + $stmt->execute(array( + ':username' => $username, + ':tag_name' => $tag, + )); + } + + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_modified', $username) + ); + } + break; + case 'mailbox_templates': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), + 'msg' => 'access_denied' + ); + return false; + } + if (!is_array($_data['ids'])) { + $ids = array(); + $ids[] = $_data['ids']; + } + else { + $ids = $_data['ids']; + } + foreach ($ids as $id) { + $is_now = mailbox("get", "mailbox_templates", $id); + if (empty($is_now) || + $is_now["type"] != "mailbox"){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), + 'msg' => 'template_id_invalid' + ); + continue; + } + + + // check name + if ($is_now["template"] == "Default" && $is_now["template"] != $_data["template"]){ + // keep template name of Default template + $_data["template"] = $is_now["template"]; + } + else { + $_data["template"] = (isset($_data["template"])) ? $_data["template"] : $is_now["template"]; + } + // check attributes + $attr = array(); + $attr["quota"] = isset($_data['quota']) ? intval($_data['quota']) * 1048576 : 0; + $attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : $is_now['tags']; + $attr["quarantine_notification"] = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : $is_now['quarantine_notification']; + $attr["quarantine_category"] = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : $is_now['quarantine_category']; + $attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : $is_now['rl_frame']; + $attr["rl_value"] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : $is_now['rl_value']; + $attr["force_pw_update"] = isset($_data['force_pw_update']) ? intval($_data['force_pw_update']) : $is_now['force_pw_update']; + $attr["sogo_access"] = isset($_data['sogo_access']) ? intval($_data['sogo_access']) : $is_now['sogo_access']; + $attr["active"] = isset($_data['active']) ? intval($_data['active']) : $is_now['active']; + $attr["tls_enforce_in"] = isset($_data['tls_enforce_in']) ? intval($_data['tls_enforce_in']) : $is_now['tls_enforce_in']; + $attr["tls_enforce_out"] = isset($_data['tls_enforce_out']) ? intval($_data['tls_enforce_out']) : $is_now['tls_enforce_out']; + if (isset($_data['protocol_access'])) { + $_data['protocol_access'] = (array)$_data['protocol_access']; + $attr['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : 0; + $attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0; + $attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0; + $attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0; + } + else { + foreach ($is_now as $key => $value){ + $attr[$key] = $is_now[$key]; + } + } + if (isset($_data['acl'])) { + $_data['acl'] = (array)$_data['acl']; + $attr['acl_spam_alias'] = (in_array('spam_alias', $_data['acl'])) ? 1 : 0; + $attr['acl_tls_policy'] = (in_array('tls_policy', $_data['acl'])) ? 1 : 0; + $attr['acl_spam_score'] = (in_array('spam_score', $_data['acl'])) ? 1 : 0; + $attr['acl_spam_policy'] = (in_array('spam_policy', $_data['acl'])) ? 1 : 0; + $attr['acl_delimiter_action'] = (in_array('delimiter_action', $_data['acl'])) ? 1 : 0; + $attr['acl_syncjobs'] = (in_array('syncjobs', $_data['acl'])) ? 1 : 0; + $attr['acl_eas_reset'] = (in_array('eas_reset', $_data['acl'])) ? 1 : 0; + $attr['acl_sogo_profile_reset'] = (in_array('sogo_profile_reset', $_data['acl'])) ? 1 : 0; + $attr['acl_pushover'] = (in_array('pushover', $_data['acl'])) ? 1 : 0; + $attr['acl_quarantine'] = (in_array('quarantine', $_data['acl'])) ? 1 : 0; + $attr['acl_quarantine_attachments'] = (in_array('quarantine_attachments', $_data['acl'])) ? 1 : 0; + $attr['acl_quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0; + $attr['acl_quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0; + $attr['acl_app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0; + } else { + foreach ($is_now as $key => $value){ + $attr[$key] = $is_now[$key]; + } + } + + + // update template + $stmt = $pdo->prepare("UPDATE `templates` + SET `template` = :template, `attributes` = :attributes + WHERE id = :id"); + $stmt->execute(array( + ":id" => $id , + ":template" => $_data["template"] , + ":attributes" => json_encode($attr) + )); + } + + + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('template_modified', $_data["template"]) + ); + return true; + break; + case 'resource': + if (!is_array($_data['name'])) { + $names = array(); + $names[] = $_data['name']; + } + else { + $names = $_data['name']; + } + foreach ($names as $name) { + $is_now = mailbox('get', 'resource_details', $name); + if (!empty($is_now)) { + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; + $multiple_bookings = (isset($_data['multiple_bookings'])) ? intval($_data['multiple_bookings']) : $is_now['multiple_bookings']; + $description = (!empty($_data['description'])) ? $_data['description'] : $is_now['description']; + $kind = (!empty($_data['kind'])) ? $_data['kind'] : $is_now['kind']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('resource_invalid', htmlspecialchars($name)) + ); + continue; + } + if (!filter_var($name, FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('resource_invalid', htmlspecialchars($name)) + ); + continue; + } + if (!isset($multiple_bookings) || $multiple_bookings < -1) { + $multiple_bookings = -1; + } + if (empty($description)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('description_invalid', htmlspecialchars($name)) + ); + continue; + } + if ($kind != 'location' && $kind != 'group' && $kind != 'thing') { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('resource_invalid', htmlspecialchars($name)) + ); + continue; + } + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $name)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare("UPDATE `mailbox` SET + `active` = :active, + `name`= :description, + `kind`= :kind, + `multiple_bookings`= :multiple_bookings + WHERE `username` = :name"); + $stmt->execute(array( + ':active' => $active, + ':description' => $description, + ':multiple_bookings' => $multiple_bookings, + ':kind' => $kind, + ':name' => $name + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('resource_modified', htmlspecialchars($name)) + ); + } + break; + } + break; + case 'get': + switch ($_type) { + case 'sender_acl_handles': + if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") { + return false; + } + $data['sender_acl_domains']['ro'] = array(); + $data['sender_acl_domains']['rw'] = array(); + $data['sender_acl_domains']['selectable'] = array(); + $data['sender_acl_addresses']['ro'] = array(); + $data['sender_acl_addresses']['rw'] = array(); + $data['sender_acl_addresses']['selectable'] = array(); + $data['fixed_sender_aliases'] = array(); + $data['external_sender_aliases'] = array(); + // Fixed addresses + $stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `goto` REGEXP :goto AND `address` NOT LIKE '@%'"); + $stmt->execute(array(':goto' => '(^|,)'.$_data.'($|,)')); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($rows)) { + $data['fixed_sender_aliases'][] = $row['address']; + } + $stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias_domain_alias` FROM `mailbox`, `alias_domain` + WHERE `alias_domain`.`target_domain` = `mailbox`.`domain` + AND `mailbox`.`username` = :username"); + $stmt->execute(array(':username' => $_data)); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($rows)) { + if (!empty($row['alias_domain_alias'])) { + $data['fixed_sender_aliases'][] = $row['alias_domain_alias']; + } + } + // External addresses + $stmt = $pdo->prepare("SELECT `send_as` as `send_as_external` FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as AND `external` = '1'"); + $stmt->execute(array(':logged_in_as' => $_data)); + $exernal_rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($exernal_rows)) { + if (!empty($row['send_as_external'])) { + $data['external_sender_aliases'][] = $row['send_as_external']; + } + } + // Return array $data['sender_acl_domains/addresses']['ro'] with read-only objects + // Return array $data['sender_acl_domains/addresses']['rw'] with read-write objects (can be deleted) + $stmt = $pdo->prepare("SELECT REPLACE(`send_as`, '@', '') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as AND `external` = '0' AND (`send_as` LIKE '@%' OR `send_as` = '*')"); + $stmt->execute(array(':logged_in_as' => $_data)); + $domain_rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($domain_row = array_shift($domain_rows)) { + if (is_valid_domain_name($domain_row['send_as']) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain_row['send_as'])) { + $data['sender_acl_domains']['ro'][] = $domain_row['send_as']; + continue; + } + if (is_valid_domain_name($domain_row['send_as']) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain_row['send_as'])) { + $data['sender_acl_domains']['rw'][] = $domain_row['send_as']; + continue; + } + if ($domain_row['send_as'] == '*' && $_SESSION['mailcow_cc_role'] != 'admin') { + $data['sender_acl_domains']['ro'][] = $domain_row['send_as']; + } + if ($domain_row['send_as'] == '*' && $_SESSION['mailcow_cc_role'] == 'admin') { + $data['sender_acl_domains']['rw'][] = $domain_row['send_as']; + } + } + $stmt = $pdo->prepare("SELECT `send_as` FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as AND `external` = '0' AND (`send_as` NOT LIKE '@%' AND `send_as` != '*')"); + $stmt->execute(array(':logged_in_as' => $_data)); + $address_rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($address_row = array_shift($address_rows)) { + if (filter_var($address_row['send_as'], FILTER_VALIDATE_EMAIL) && !hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $address_row['send_as'])) { + $data['sender_acl_addresses']['ro'][] = $address_row['send_as']; + continue; + } + if (filter_var($address_row['send_as'], FILTER_VALIDATE_EMAIL) && hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $address_row['send_as'])) { + $data['sender_acl_addresses']['rw'][] = $address_row['send_as']; + continue; + } + } + $stmt = $pdo->prepare("SELECT `domain` FROM `domain` + WHERE `domain` NOT IN ( + SELECT REPLACE(`send_as`, '@', '') FROM `sender_acl` + WHERE `logged_in_as` = :logged_in_as1 + AND `external` = '0' + AND `send_as` LIKE '@%') + UNION + SELECT '*' FROM `domain` + WHERE '*' NOT IN ( + SELECT `send_as` FROM `sender_acl` + WHERE `logged_in_as` = :logged_in_as2 + AND `external` = '0' + )"); + $stmt->execute(array( + ':logged_in_as1' => $_data, + ':logged_in_as2' => $_data + )); + $rows_domain = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row_domain = array_shift($rows_domain)) { + if (is_valid_domain_name($row_domain['domain']) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row_domain['domain'])) { + $data['sender_acl_domains']['selectable'][] = $row_domain['domain']; + continue; + } + if ($row_domain['domain'] == '*' && $_SESSION['mailcow_cc_role'] == 'admin') { + $data['sender_acl_domains']['selectable'][] = $row_domain['domain']; + continue; + } + } + $stmt = $pdo->prepare("SELECT `address` FROM `alias` + WHERE `goto` != :goto + AND `address` NOT IN ( + SELECT `send_as` FROM `sender_acl` + WHERE `logged_in_as` = :logged_in_as + AND `external` = '0' + AND `send_as` NOT LIKE '@%')"); + $stmt->execute(array( + ':logged_in_as' => $_data, + ':goto' => $_data + )); + $rows_mbox = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($rows_mbox)) { + // Aliases are not selectable + if (in_array($row['address'], $data['fixed_sender_aliases'])) { + continue; + } + if (filter_var($row['address'], FILTER_VALIDATE_EMAIL) && hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['address'])) { + $data['sender_acl_addresses']['selectable'][] = $row['address']; + } + } + return $data; + break; + case 'mailboxes': + $mailboxes = array(); + if (isset($_extra) && is_array($_extra) && isset($_data)) { + // get by domain and tags + $tags = is_array($_extra) ? $_extra : array(); + + $sql = ""; + foreach ($tags as $key => $tag) { + $sql = $sql."SELECT DISTINCT `username` FROM `tags_mailbox` WHERE `username` LIKE ? AND `tag_name` LIKE ?"; // distinct, avoid duplicates + if ($key === array_key_last($tags)) break; + $sql = $sql.' UNION DISTINCT '; // combine querys with union - distinct, avoid duplicates + } + + // prepend domain to array + $params = array(); + foreach ($tags as $key => $val){ + array_push($params, '%'.$_data.'%'); + array_push($params, '%'.$val.'%'); + } + $stmt = $pdo->prepare($sql); + $stmt->execute($params); + + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], explode('@', $row['username'])[1])) + $mailboxes[] = $row['username']; + } + } + elseif (isset($_data) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + // get by domain + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE (`kind` = '' OR `kind` = NULL) AND `domain` = :domain"); + $stmt->execute(array( + ':domain' => $_data, + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $mailboxes[] = $row['username']; + } + } + else { + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE (`kind` = '' OR `kind` = NULL) AND (`domain` IN (SELECT `domain` FROM `domain_admins` WHERE `active` = '1' AND `username` = :username) OR 'admin' = :role)"); + $stmt->execute(array( + ':username' => $_SESSION['mailcow_cc_username'], + ':role' => $_SESSION['mailcow_cc_role'], + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $mailboxes[] = $row['username']; + } + } + return $mailboxes; + break; + case 'tls_policy': + $attrs = array(); + if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + return false; + } + } + else { + $_data = $_SESSION['mailcow_cc_username']; + } + $stmt = $pdo->prepare("SELECT `attributes` FROM `mailbox` WHERE `username` = :username"); + $stmt->execute(array(':username' => $_data)); + $attrs = $stmt->fetch(PDO::FETCH_ASSOC); + $attrs = json_decode($attrs['attributes'], true); + return array( + 'tls_enforce_in' => $attrs['tls_enforce_in'], + 'tls_enforce_out' => $attrs['tls_enforce_out'] + ); + break; + case 'quarantine_notification': + $attrs = array(); + if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + return false; + } + } + else { + $_data = $_SESSION['mailcow_cc_username']; + } + $stmt = $pdo->prepare("SELECT `attributes` FROM `mailbox` WHERE `username` = :username"); + $stmt->execute(array(':username' => $_data)); + $attrs = $stmt->fetch(PDO::FETCH_ASSOC); + $attrs = json_decode($attrs['attributes'], true); + return $attrs['quarantine_notification']; + break; + case 'quarantine_category': + $attrs = array(); + if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + return false; + } + } + else { + $_data = $_SESSION['mailcow_cc_username']; + } + $stmt = $pdo->prepare("SELECT `attributes` FROM `mailbox` WHERE `username` = :username"); + $stmt->execute(array(':username' => $_data)); + $attrs = $stmt->fetch(PDO::FETCH_ASSOC); + $attrs = json_decode($attrs['attributes'], true); + return $attrs['quarantine_category']; + break; + case 'filters': + $filters = array(); + if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + return false; + } + } + else { + $_data = $_SESSION['mailcow_cc_username']; + } + $stmt = $pdo->prepare("SELECT `id` FROM `sieve_filters` WHERE `username` = :username"); + $stmt->execute(array(':username' => $_data)); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $filters[] = $row['id']; + } + return $filters; + break; + case 'global_filter_details': + $global_filters = array(); + if ($_SESSION['mailcow_cc_role'] != "admin") { + return false; + } + $global_filters['prefilter'] = file_get_contents('/global_sieve/before'); + $global_filters['postfilter'] = file_get_contents('/global_sieve/after'); + return $global_filters; + break; + case 'filter_details': + $filter_details = array(); + if (!is_numeric($_data)) { + return false; + } + $stmt = $pdo->prepare("SELECT CASE `script_name` WHEN 'active' THEN 1 ELSE 0 END AS `active`, + id, + username, + filter_type, + script_data, + script_desc + FROM `sieve_filters` + WHERE `id` = :id"); + $stmt->execute(array(':id' => $_data)); + $filter_details = $stmt->fetch(PDO::FETCH_ASSOC); + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $filter_details['username'])) { + return false; + } + return $filter_details; + break; + case 'active_user_sieve': + $filter_details = array(); + if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + return false; + } + } + else { + $_data = $_SESSION['mailcow_cc_username']; + } + $exec_fields = array( + 'cmd' => 'sieve', + 'task' => 'list', + 'username' => $_data + ); + $filters = docker('post', 'dovecot-mailcow', 'exec', $exec_fields); + $filters = array_filter(preg_split("/(\r\n|\n|\r)/",$filters)); + foreach ($filters as $filter) { + if (preg_match('/.+ ACTIVE/i', $filter)) { + $exec_fields = array( + 'cmd' => 'sieve', + 'task' => 'print', + 'script_name' => substr($filter, 0, -7), + 'username' => $_data + ); + $script = docker('post', 'dovecot-mailcow', 'exec', $exec_fields); + // Remove first line + return preg_replace('/^.+\n/', '', $script); + } + } + return false; + break; + case 'syncjob_details': + $syncjobdetails = array(); + if (!is_numeric($_data)) { + return false; + } + if (isset($_extra) && in_array('no_log', $_extra)) { + $field_query = $pdo->query('SHOW FIELDS FROM `imapsync` WHERE FIELD NOT IN ("returned_text", "password1")'); + $fields = $field_query->fetchAll(PDO::FETCH_ASSOC); + while($field = array_shift($fields)) { + $shown_fields[] = $field['Field']; + } + $stmt = $pdo->prepare("SELECT " . implode(',', (array)$shown_fields) . ", + `active` + FROM `imapsync` WHERE id = :id"); + } + elseif (isset($_extra) && in_array('with_password', $_extra)) { + $stmt = $pdo->prepare("SELECT *, + `active` + FROM `imapsync` WHERE id = :id"); + } + else { + $field_query = $pdo->query('SHOW FIELDS FROM `imapsync` WHERE FIELD NOT IN ("password1")'); + $fields = $field_query->fetchAll(PDO::FETCH_ASSOC); + while($field = array_shift($fields)) { + $shown_fields[] = $field['Field']; + } + $stmt = $pdo->prepare("SELECT " . implode(',', (array)$shown_fields) . ", + `active` + FROM `imapsync` WHERE id = :id"); + } + $stmt->execute(array(':id' => $_data)); + $syncjobdetails = $stmt->fetch(PDO::FETCH_ASSOC); + if (!empty($syncjobdetails['returned_text'])) { + $syncjobdetails['log'] = $syncjobdetails['returned_text']; + } + else { + $syncjobdetails['log'] = ''; + } + unset($syncjobdetails['returned_text']); + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $syncjobdetails['user2'])) { + return false; + } + return $syncjobdetails; + break; + case 'syncjobs': + $syncjobdata = array(); + if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + return false; + } + } + else { + $_data = $_SESSION['mailcow_cc_username']; + } + $stmt = $pdo->prepare("SELECT `id` FROM `imapsync` WHERE `user2` = :username"); + $stmt->execute(array(':username' => $_data)); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $syncjobdata[] = $row['id']; + } + return $syncjobdata; + break; + case 'spam_score': + $curl = curl_init(); + curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); + curl_setopt($curl, CURLOPT_URL,"http://rspamd/actions"); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + $default_actions = curl_exec($curl); + if (!curl_errno($curl)) { + $data_array = json_decode($default_actions, true); + curl_close($curl); + foreach ($data_array as $data) { + if ($data['action'] == 'reject') { + $reject = $data['value']; + continue; + } + elseif ($data['action'] == 'add header') { + $add_header = $data['value']; + continue; + } + } + if (empty($add_header) || empty($reject)) { + // Assume default, set warning + $default = "5, 15"; + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'Could not determine servers default spam score, assuming default' + ); + } + else { + $default = $add_header . ', ' . $reject; + } + } + else { + // Assume default, set warning + $default = "5, 15"; + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'Could not determine servers default spam score, assuming default' + ); + } + curl_close($curl); + $policydata = array(); + if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + return false; + } + } + else { + $_data = $_SESSION['mailcow_cc_username']; + } + $stmt = $pdo->prepare("SELECT `value` FROM `filterconf` WHERE `object` = :username AND + (`option` = 'lowspamlevel' OR `option` = 'highspamlevel')"); + $stmt->execute(array(':username' => $_data)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if (empty($num_results)) { + return $default; + } + else { + $stmt = $pdo->prepare("SELECT `value` FROM `filterconf` WHERE `option` = 'highspamlevel' AND `object` = :username"); + $stmt->execute(array(':username' => $_data)); + $highspamlevel = $stmt->fetch(PDO::FETCH_ASSOC); + $stmt = $pdo->prepare("SELECT `value` FROM `filterconf` WHERE `option` = 'lowspamlevel' AND `object` = :username"); + $stmt->execute(array(':username' => $_data)); + $lowspamlevel = $stmt->fetch(PDO::FETCH_ASSOC); + return $lowspamlevel['value'].', '.$highspamlevel['value']; + } + break; + case 'time_limited_aliases': + $tladata = array(); + if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + return false; + } + } + else { + $_data = $_SESSION['mailcow_cc_username']; + } + $stmt = $pdo->prepare("SELECT `address`, + `goto`, + `validity`, + `created`, + `modified` + FROM `spamalias` + WHERE `goto` = :username + AND `validity` >= :unixnow"); + $stmt->execute(array(':username' => $_data, ':unixnow' => time())); + $tladata = $stmt->fetchAll(PDO::FETCH_ASSOC); + return $tladata; + break; + case 'delimiter_action': + $policydata = array(); + if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + return false; + } + } + else { + $_data = $_SESSION['mailcow_cc_username']; + } + try { + if ($redis->hGet('RCPT_WANTS_SUBJECT_TAG', $_data)) { + return "subject"; + } + elseif ($redis->hGet('RCPT_WANTS_SUBFOLDER_TAG', $_data)) { + return "subfolder"; + } + else { + return "none"; + } + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('redis_error', $e) + ); + return false; + } + break; + case 'resources': + $resources = array(); + if (isset($_data) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + return false; + } + elseif (isset($_data) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` REGEXP 'location|thing|group' AND `domain` = :domain"); + $stmt->execute(array( + ':domain' => $_data, + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $resources[] = $row['username']; + } + } + else { + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` REGEXP 'location|thing|group' AND `domain` IN (SELECT `domain` FROM `domain_admins` WHERE `active` = '1' AND `username` = :username) OR 'admin' = :role"); + $stmt->execute(array( + ':username' => $_SESSION['mailcow_cc_username'], + ':role' => $_SESSION['mailcow_cc_role'], + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $resources[] = $row['username']; + } + } + return $resources; + break; + case 'alias_domains': + $aliasdomains = array(); + if (isset($_data) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + return false; + } + elseif (isset($_data) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` = :domain"); + $stmt->execute(array( + ':domain' => $_data, + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $aliasdomains[] = $row['alias_domain']; + } + } + else { + $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` IN (SELECT `domain` FROM `domain_admins` WHERE `active` = '1' AND `username` = :username) OR 'admin' = :role"); + $stmt->execute(array( + ':username' => $_SESSION['mailcow_cc_username'], + ':role' => $_SESSION['mailcow_cc_role'], + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $aliasdomains[] = $row['alias_domain']; + } + } + return $aliasdomains; + break; + case 'aliases': + $aliases = array(); + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + return false; + } + $stmt = $pdo->prepare("SELECT `id` FROM `alias` WHERE `address` != `goto` AND `domain` = :domain"); + $stmt->execute(array( + ':domain' => $_data, + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $aliases[] = $row['id']; + } + return $aliases; + break; + case 'alias_details': + $aliasdata = array(); + $stmt = $pdo->prepare("SELECT + `id`, + `domain`, + `goto`, + `address`, + `public_comment`, + `private_comment`, + `active`, + `sogo_visible`, + `created`, + `modified` + FROM `alias` + WHERE (`id` = :id OR `address` = :address) AND `address` != `goto`"); + $stmt->execute(array( + ':id' => $_data, + ':address' => $_data, + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain"); + $stmt->execute(array( + ':domain' => $row['domain'], + )); + $row_alias_domain = $stmt->fetch(PDO::FETCH_ASSOC); + if (isset($row_alias_domain['target_domain']) && !empty($row_alias_domain['target_domain'])) { + $aliasdata['in_primary_domain'] = $row_alias_domain['target_domain']; + } + else { + $aliasdata['in_primary_domain'] = ""; + } + $aliasdata['id'] = $row['id']; + $aliasdata['domain'] = $row['domain']; + $aliasdata['public_comment'] = $row['public_comment']; + $aliasdata['private_comment'] = $row['private_comment']; + $aliasdata['domain'] = $row['domain']; + $aliasdata['goto'] = $row['goto']; + $aliasdata['address'] = $row['address']; + (!filter_var($aliasdata['address'], FILTER_VALIDATE_EMAIL)) ? $aliasdata['is_catch_all'] = 1 : $aliasdata['is_catch_all'] = 0; + $aliasdata['active'] = $row['active']; + $aliasdata['active_int'] = $row['active']; + $aliasdata['sogo_visible'] = $row['sogo_visible']; + $aliasdata['sogo_visible_int'] = $row['sogo_visible']; + $aliasdata['created'] = $row['created']; + $aliasdata['modified'] = $row['modified']; + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $aliasdata['domain'])) { + return false; + } + return $aliasdata; + break; + case 'alias_domain_details': + $aliasdomaindata = array(); + $rl = ratelimit('get', 'domain', $_data); + $stmt = $pdo->prepare("SELECT + `alias_domain`, + `target_domain`, + `active`, + `created`, + `modified` + FROM `alias_domain` + WHERE `alias_domain` = :aliasdomain"); + $stmt->execute(array( + ':aliasdomain' => $_data, + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + $stmt = $pdo->prepare("SELECT `backupmx` FROM `domain` WHERE `domain` = :target_domain"); + $stmt->execute(array( + ':target_domain' => $row['target_domain'] + )); + $row_parent = $stmt->fetch(PDO::FETCH_ASSOC); + $aliasdomaindata['alias_domain'] = $row['alias_domain']; + $aliasdomaindata['parent_is_backupmx'] = $row_parent['backupmx']; + $aliasdomaindata['target_domain'] = $row['target_domain']; + $aliasdomaindata['active'] = $row['active']; + $aliasdomaindata['active_int'] = $row['active']; + $aliasdomaindata['rl'] = $rl; + $aliasdomaindata['created'] = $row['created']; + $aliasdomaindata['modified'] = $row['modified']; + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $aliasdomaindata['target_domain'])) { + return false; + } + return $aliasdomaindata; + break; + case 'domains': + $domains = array(); + if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") { + return false; + } + + if (isset($_extra) && is_array($_extra)){ + // get by tags + $tags = is_array($_extra) ? $_extra : array(); + // add % as prefix and suffix to every element for relative searching + $tags = array_map(function($x){ return '%'.$x.'%'; }, $tags); + $sql = ""; + foreach ($tags as $key => $tag) { + $sql = $sql."SELECT DISTINCT `domain` FROM `tags_domain` WHERE `tag_name` LIKE ?"; // distinct, avoid duplicates + if ($key === array_key_last($tags)) break; + $sql = $sql.' UNION DISTINCT '; // combine querys with union - distinct, avoid duplicates + } + $stmt = $pdo->prepare($sql); + $stmt->execute($tags); + + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + if ($_SESSION['mailcow_cc_role'] == "admin") + $domains[] = $row['domain']; + elseif (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['domain'])) + $domains[] = $row['domain']; + } + } else { + // get all + $stmt = $pdo->prepare("SELECT `domain` FROM `domain` + WHERE (`domain` IN ( + SELECT `domain` from `domain_admins` + WHERE (`active`='1' AND `username` = :username)) + ) + OR 'admin'= :role"); + $stmt->execute(array( + ':username' => $_SESSION['mailcow_cc_username'], + ':role' => $_SESSION['mailcow_cc_role'], + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $domains[] = $row['domain']; + } + } + + return $domains; + break; + case 'domain_details': + $domaindata = array(); + $_data = idn_to_ascii(strtolower(trim($_data)), 0, INTL_IDNA_VARIANT_UTS46); + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + return false; + } + $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain"); + $stmt->execute(array( + ':domain' => $_data + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (!empty($row)) { + $_data = $row['target_domain']; + } + $stmt = $pdo->prepare("SELECT + `domain`, + `description`, + `aliases`, + `mailboxes`, + `defquota`, + `maxquota`, + `created`, + `modified`, + `quota`, + `relayhost`, + `relay_all_recipients`, + `relay_unknown_only`, + `backupmx`, + `gal`, + `active` + FROM `domain` WHERE `domain`= :domain"); + $stmt->execute(array( + ':domain' => $_data + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (empty($row)) { + return false; + } + $stmt = $pdo->prepare("SELECT COUNT(`username`) AS `count`, + COALESCE(SUM(`quota`), 0) AS `in_use` + FROM `mailbox` + WHERE (`kind` = '' OR `kind` = NULL) + AND `domain` = :domain"); + $stmt->execute(array(':domain' => $row['domain'])); + $MailboxDataDomain = $stmt->fetch(PDO::FETCH_ASSOC); + $stmt = $pdo->prepare("SELECT SUM(bytes) AS `bytes_total`, SUM(messages) AS `msgs_total` FROM `quota2` + WHERE `username` IN ( + SELECT `username` FROM `mailbox` + WHERE `domain` = :domain + );"); + $stmt->execute(array(':domain' => $row['domain'])); + $SumQuotaInUse = $stmt->fetch(PDO::FETCH_ASSOC); + $rl = ratelimit('get', 'domain', $_data); + $domaindata['max_new_mailbox_quota'] = ($row['quota'] * 1048576) - $MailboxDataDomain['in_use']; + if ($domaindata['max_new_mailbox_quota'] > ($row['maxquota'] * 1048576)) { + $domaindata['max_new_mailbox_quota'] = ($row['maxquota'] * 1048576); + } + $domaindata['def_new_mailbox_quota'] = $domaindata['max_new_mailbox_quota']; + if ($domaindata['def_new_mailbox_quota'] > ($row['defquota'] * 1048576)) { + $domaindata['def_new_mailbox_quota'] = ($row['defquota'] * 1048576); + } + $domaindata['quota_used_in_domain'] = $MailboxDataDomain['in_use']; + if (!empty($SumQuotaInUse['bytes_total'])) { + $domaindata['bytes_total'] = $SumQuotaInUse['bytes_total']; + } + else { + $domaindata['bytes_total'] = 0; + } + if (!empty($SumQuotaInUse['msgs_total'])) { + $domaindata['msgs_total'] = $SumQuotaInUse['msgs_total']; + } + else { + $domaindata['msgs_total'] = 0; + } + $domaindata['mboxes_in_domain'] = $MailboxDataDomain['count']; + $domaindata['mboxes_left'] = $row['mailboxes'] - $MailboxDataDomain['count']; + $domaindata['domain_name'] = $row['domain']; + $domaindata['description'] = $row['description']; + $domaindata['max_num_aliases_for_domain'] = $row['aliases']; + $domaindata['max_num_mboxes_for_domain'] = $row['mailboxes']; + $domaindata['def_quota_for_mbox'] = $row['defquota'] * 1048576; + $domaindata['max_quota_for_mbox'] = $row['maxquota'] * 1048576; + $domaindata['max_quota_for_domain'] = $row['quota'] * 1048576; + $domaindata['relayhost'] = $row['relayhost']; + $domaindata['backupmx'] = $row['backupmx']; + $domaindata['backupmx_int'] = $row['backupmx']; + $domaindata['gal'] = $row['gal']; + $domaindata['gal_int'] = $row['gal']; + $domaindata['rl'] = $rl; + $domaindata['active'] = $row['active']; + $domaindata['active_int'] = $row['active']; + $domaindata['relay_all_recipients'] = $row['relay_all_recipients']; + $domaindata['relay_all_recipients_int'] = $row['relay_all_recipients']; + $domaindata['relay_unknown_only'] = $row['relay_unknown_only']; + $domaindata['relay_unknown_only_int'] = $row['relay_unknown_only']; + $domaindata['created'] = $row['created']; + $domaindata['modified'] = $row['modified']; + $stmt = $pdo->prepare("SELECT COUNT(`address`) AS `alias_count` FROM `alias` + WHERE (`domain`= :domain OR `domain` IN (SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` = :domain2)) + AND `address` NOT IN ( + SELECT `username` FROM `mailbox` + )"); + $stmt->execute(array( + ':domain' => $_data, + ':domain2' => $_data + )); + $AliasDataDomain = $stmt->fetch(PDO::FETCH_ASSOC); + (isset($AliasDataDomain['alias_count'])) ? $domaindata['aliases_in_domain'] = $AliasDataDomain['alias_count'] : $domaindata['aliases_in_domain'] = "0"; + $domaindata['aliases_left'] = $row['aliases'] - $AliasDataDomain['alias_count']; + if ($_SESSION['mailcow_cc_role'] == "admin") + { + $stmt = $pdo->prepare("SELECT GROUP_CONCAT(`username` SEPARATOR ', ') AS domain_admins FROM `domain_admins` WHERE `domain` = :domain"); + $stmt->execute(array( + ':domain' => $_data + )); + $domain_admins = $stmt->fetch(PDO::FETCH_ASSOC); + (isset($domain_admins['domain_admins'])) ? $domaindata['domain_admins'] = $domain_admins['domain_admins'] : $domaindata['domain_admins'] = "-"; + } + $stmt = $pdo->prepare("SELECT `tag_name` + FROM `tags_domain` WHERE `domain`= :domain"); + $stmt->execute(array( + ':domain' => $_data + )); + $tags = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($tag = array_shift($tags)) { + $domaindata['tags'][] = $tag['tag_name']; + } + + return $domaindata; + break; + case 'domain_templates': + if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") { + return false; + } + $_data = (isset($_data)) ? intval($_data) : null; + + if (isset($_data)){ + $stmt = $pdo->prepare("SELECT * FROM `templates` + WHERE `id` = :id AND type = :type"); + $stmt->execute(array( + ":id" => $_data, + ":type" => "domain" + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + if (empty($row)){ + return false; + } + + $row["attributes"] = json_decode($row["attributes"], true); + return $row; + } + else { + $stmt = $pdo->prepare("SELECT * FROM `templates` WHERE `type` = 'domain'"); + $stmt->execute(); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + + if (empty($rows)){ + return false; + } + + foreach($rows as $key => $row){ + $rows[$key]["attributes"] = json_decode($row["attributes"], true); + } + return $rows; + } + break; + case 'mailbox_details': + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + return false; + } + $mailboxdata = array(); + if (preg_match('/y|yes/i', getenv('MASTER'))) { + $stmt = $pdo->prepare("SELECT + `domain`.`backupmx`, + `mailbox`.`username`, + `mailbox`.`name`, + `mailbox`.`active`, + `mailbox`.`domain`, + `mailbox`.`local_part`, + `mailbox`.`quota`, + `mailbox`.`created`, + `mailbox`.`modified`, + `quota2`.`bytes`, + `attributes`, + `quota2`.`messages` + FROM `mailbox`, `quota2`, `domain` + WHERE (`mailbox`.`kind` = '' OR `mailbox`.`kind` = NULL) + AND `mailbox`.`username` = `quota2`.`username` + AND `domain`.`domain` = `mailbox`.`domain` + AND `mailbox`.`username` = :mailbox"); + } + else { + $stmt = $pdo->prepare("SELECT + `domain`.`backupmx`, + `mailbox`.`username`, + `mailbox`.`name`, + `mailbox`.`active`, + `mailbox`.`domain`, + `mailbox`.`local_part`, + `mailbox`.`quota`, + `mailbox`.`created`, + `mailbox`.`modified`, + `quota2replica`.`bytes`, + `attributes`, + `quota2replica`.`messages` + FROM `mailbox`, `quota2replica`, `domain` + WHERE (`mailbox`.`kind` = '' OR `mailbox`.`kind` = NULL) + AND `mailbox`.`username` = `quota2replica`.`username` + AND `domain`.`domain` = `mailbox`.`domain` + AND `mailbox`.`username` = :mailbox"); + } + $stmt->execute(array( + ':mailbox' => $_data, + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + $mailboxdata['username'] = $row['username']; + $mailboxdata['active'] = $row['active']; + $mailboxdata['active_int'] = $row['active']; + $mailboxdata['domain'] = $row['domain']; + $mailboxdata['relayhost'] = $row['relayhost']; + $mailboxdata['name'] = $row['name']; + $mailboxdata['local_part'] = $row['local_part']; + $mailboxdata['quota'] = $row['quota']; + $mailboxdata['messages'] = $row['messages']; + $mailboxdata['attributes'] = json_decode($row['attributes'], true); + $mailboxdata['quota_used'] = intval($row['bytes']); + $mailboxdata['percent_in_use'] = ($row['quota'] == 0) ? '- ' : round((intval($row['bytes']) / intval($row['quota'])) * 100); + $mailboxdata['created'] = $row['created']; + $mailboxdata['modified'] = $row['modified']; + + if ($mailboxdata['percent_in_use'] === '- ') { + $mailboxdata['percent_class'] = "info"; + } + elseif ($mailboxdata['percent_in_use'] >= 90) { + $mailboxdata['percent_class'] = "danger"; + } + elseif ($mailboxdata['percent_in_use'] >= 75) { + $mailboxdata['percent_class'] = "warning"; + } + else { + $mailboxdata['percent_class'] = "success"; + } + + // Determine last logins + $stmt = $pdo->prepare("SELECT MAX(`datetime`) AS `datetime`, `service` FROM `sasl_log` + WHERE `username` = :mailbox + GROUP BY `service` DESC"); + $stmt->execute(array(':mailbox' => $_data)); + $SaslLogsData = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($SaslLogsData as $SaslLogs) { + if ($SaslLogs['service'] == 'imap') { + $last_imap_login = strtotime($SaslLogs['datetime']); + } + else if ($SaslLogs['service'] == 'smtp') { + $last_smtp_login = strtotime($SaslLogs['datetime']); + } + else if ($SaslLogs['service'] == 'pop3') { + $last_pop3_login = strtotime($SaslLogs['datetime']); + } + } + if (!isset($last_imap_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) { + $last_imap_login = 0; + } + if (!isset($last_smtp_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) { + $last_smtp_login = 0; + } + if (!isset($last_pop3_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) { + $last_pop3_login = 0; + } + $mailboxdata['last_imap_login'] = $last_imap_login; + $mailboxdata['last_smtp_login'] = $last_smtp_login; + $mailboxdata['last_pop3_login'] = $last_pop3_login; + + if (!isset($_extra) || $_extra != 'reduced') { + $rl = ratelimit('get', 'mailbox', $_data); + $stmt = $pdo->prepare("SELECT `maxquota`, `quota` FROM `domain` WHERE `domain` = :domain"); + $stmt->execute(array(':domain' => $row['domain'])); + $DomainQuota = $stmt->fetch(PDO::FETCH_ASSOC); + + $stmt = $pdo->prepare("SELECT IFNULL(COUNT(`active`), 0) AS `pushover_active` FROM `pushover` WHERE `username` = :username AND `active` = 1"); + $stmt->execute(array(':username' => $_data)); + $PushoverActive = $stmt->fetch(PDO::FETCH_ASSOC); + $stmt = $pdo->prepare("SELECT COALESCE(SUM(`quota`), 0) as `in_use` FROM `mailbox` WHERE (`kind` = '' OR `kind` = NULL) AND `domain` = :domain AND `username` != :username"); + $stmt->execute(array(':domain' => $row['domain'], ':username' => $_data)); + $MailboxUsage = $stmt->fetch(PDO::FETCH_ASSOC); + $stmt = $pdo->prepare("SELECT IFNULL(COUNT(`address`), 0) AS `sa_count` FROM `spamalias` WHERE `goto` = :address AND `validity` >= :unixnow"); + $stmt->execute(array(':address' => $_data, ':unixnow' => time())); + $SpamaliasUsage = $stmt->fetch(PDO::FETCH_ASSOC); + $mailboxdata['max_new_quota'] = ($DomainQuota['quota'] * 1048576) - $MailboxUsage['in_use']; + $mailboxdata['spam_aliases'] = $SpamaliasUsage['sa_count']; + $mailboxdata['pushover_active'] = ($PushoverActive['pushover_active'] == 1) ? 1 : 0; + if ($mailboxdata['max_new_quota'] > ($DomainQuota['maxquota'] * 1048576)) { + $mailboxdata['max_new_quota'] = ($DomainQuota['maxquota'] * 1048576); + } + if (!empty($rl)) { + $mailboxdata['rl'] = $rl; + $mailboxdata['rl_scope'] = 'mailbox'; + } + else { + $mailboxdata['rl'] = ratelimit('get', 'domain', $row['domain']); + $mailboxdata['rl_scope'] = 'domain'; + } + $mailboxdata['is_relayed'] = $row['backupmx']; + } + $stmt = $pdo->prepare("SELECT `tag_name` + FROM `tags_mailbox` WHERE `username`= :username"); + $stmt->execute(array( + ':username' => $_data + )); + $tags = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($tag = array_shift($tags)) { + $mailboxdata['tags'][] = $tag['tag_name']; + } + + return $mailboxdata; + break; + case 'mailbox_templates': + if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") { + return false; + } + $_data = (isset($_data)) ? intval($_data) : null; + + if (isset($_data)){ + $stmt = $pdo->prepare("SELECT * FROM `templates` + WHERE `id` = :id AND type = :type"); + $stmt->execute(array( + ":id" => $_data, + ":type" => "mailbox" + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + if (empty($row)){ + return false; + } + + $row["attributes"] = json_decode($row["attributes"], true); + return $row; + } + else { + $stmt = $pdo->prepare("SELECT * FROM `templates` WHERE `type` = 'mailbox'"); + $stmt->execute(); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + + if (empty($rows)){ + return false; + } + + foreach($rows as $key => $row){ + $rows[$key]["attributes"] = json_decode($row["attributes"], true); + } + return $rows; + } + break; + case 'resource_details': + $resourcedata = array(); + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + return false; + } + $stmt = $pdo->prepare("SELECT + `username`, + `name`, + `kind`, + `multiple_bookings`, + `local_part`, + `active`, + `domain` + FROM `mailbox` WHERE `kind` REGEXP 'location|thing|group' AND `username` = :resource"); + $stmt->execute(array( + ':resource' => $_data, + )); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + $resourcedata['name'] = $row['username']; + $resourcedata['kind'] = $row['kind']; + $resourcedata['multiple_bookings'] = $row['multiple_bookings']; + $resourcedata['description'] = $row['name']; + $resourcedata['active'] = $row['active']; + $resourcedata['active_int'] = $row['active']; + $resourcedata['domain'] = $row['domain']; + $resourcedata['local_part'] = $row['local_part']; + if (!isset($resourcedata['domain']) || + (isset($resourcedata['domain']) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $resourcedata['domain']))) { + return false; + } + return $resourcedata; + break; + } + break; + case 'delete': + switch ($_type) { + case 'syncjob': + if (!is_array($_data['id'])) { + $ids = array(); + $ids[] = $_data['id']; + } + else { + $ids = $_data['id']; + } + if (!isset($_SESSION['acl']['syncjobs']) || $_SESSION['acl']['syncjobs'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + foreach ($ids as $id) { + if (!is_numeric($id)) { + return false; + } + $stmt = $pdo->prepare("SELECT `user2` FROM `imapsync` WHERE id = :id"); + $stmt->execute(array(':id' => $id)); + $user2 = $stmt->fetch(PDO::FETCH_ASSOC)['user2']; + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $user2)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare("DELETE FROM `imapsync` WHERE `id`= :id"); + $stmt->execute(array(':id' => $id)); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('deleted_syncjob', $id) + ); + } + break; + case 'filter': + if (!is_array($_data['id'])) { + $ids = array(); + $ids[] = $_data['id']; + } + else { + $ids = $_data['id']; + } + if (!isset($_SESSION['acl']['filters']) || $_SESSION['acl']['filters'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + foreach ($ids as $id) { + if (!is_numeric($id)) { + continue; + } + $stmt = $pdo->prepare("SELECT `username` FROM `sieve_filters` WHERE id = :id"); + $stmt->execute(array(':id' => $id)); + $usr = $stmt->fetch(PDO::FETCH_ASSOC)['username']; + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $usr)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare("DELETE FROM `sieve_filters` WHERE `id`= :id"); + $stmt->execute(array(':id' => $id)); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('delete_filter', $id) + ); + } + break; + case 'time_limited_alias': + if (!is_array($_data['address'])) { + $addresses = array(); + $addresses[] = $_data['address']; + } + else { + $addresses = $_data['address']; + } + if (!isset($_SESSION['acl']['spam_alias']) || $_SESSION['acl']['spam_alias'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + foreach ($addresses as $address) { + $stmt = $pdo->prepare("SELECT `goto` FROM `spamalias` WHERE `address` = :address"); + $stmt->execute(array(':address' => $address)); + $goto = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $goto)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare("DELETE FROM `spamalias` WHERE `goto` = :username AND `address` = :item"); + $stmt->execute(array( + ':username' => $goto, + ':item' => $address + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_modified', htmlspecialchars($goto)) + ); + } + break; + case 'eas_cache': + if (!is_array($_data['username'])) { + $usernames = array(); + $usernames[] = $_data['username']; + } + else { + $usernames = $_data['username']; + } + if (!isset($_SESSION['acl']['eas_reset']) || $_SESSION['acl']['eas_reset'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + foreach ($usernames as $username) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare("DELETE FROM `sogo_cache_folder` WHERE `c_uid` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('eas_reset', htmlspecialchars($username)) + ); + } + break; + case 'sogo_profile': + if (!is_array($_data['username'])) { + $usernames = array(); + $usernames[] = $_data['username']; + } + else { + $usernames = $_data['username']; + } + if (!isset($_SESSION['acl']['sogo_profile_reset']) || $_SESSION['acl']['sogo_profile_reset'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + foreach ($usernames as $username) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare("DELETE FROM `sogo_user_profile` WHERE `c_uid` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_cache_folder` WHERE `c_uid` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . $username . "/%' OR `c_uid` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_quick_contact` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_quick_appointment` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_folder_info` WHERE `c_path2` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('sogo_profile_reset', htmlspecialchars($username)) + ); + } + break; + case 'domain': + if (!is_array($_data['domain'])) { + $domains = array(); + $domains[] = $_data['domain']; + } + else { + $domains = $_data['domain']; + } + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + foreach ($domains as $domain) { + if (!is_valid_domain_name($domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'domain_invalid' + ); + continue; + } + $domain = idn_to_ascii(strtolower(trim($domain)), 0, INTL_IDNA_VARIANT_UTS46); + $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` + WHERE `domain` = :domain"); + $stmt->execute(array(':domain' => $domain)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0 || !empty($num_results)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('domain_not_empty', $domain) + ); + continue; + } + $exec_fields = array('cmd' => 'maildir', 'task' => 'cleanup', 'maildir' => $domain); + $maildir_gc = json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true); + if ($maildir_gc['type'] != 'success') { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'Could not move mail storage to garbage collector: ' . $maildir_gc['msg'] + ); + } + $stmt = $pdo->prepare("DELETE FROM `domain` WHERE `domain` = :domain"); + $stmt->execute(array( + ':domain' => $domain, + )); + $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `domain` = :domain"); + $stmt->execute(array( + ':domain' => $domain, + )); + $stmt = $pdo->prepare("DELETE FROM `alias` WHERE `domain` = :domain"); + $stmt->execute(array( + ':domain' => $domain, + )); + $stmt = $pdo->prepare("DELETE FROM `alias_domain` WHERE `target_domain` = :domain"); + $stmt->execute(array( + ':domain' => $domain, + )); + $stmt = $pdo->prepare("DELETE FROM `mailbox` WHERE `domain` = :domain"); + $stmt->execute(array( + ':domain' => $domain, + )); + $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` LIKE :domain"); + $stmt->execute(array( + ':domain' => '%@'.$domain, + )); + $stmt = $pdo->prepare("DELETE FROM `quota2` WHERE `username` LIKE :domain"); + $stmt->execute(array( + ':domain' => '%@'.$domain, + )); + $stmt = $pdo->prepare("DELETE FROM `pushover` WHERE `username` LIKE :domain"); + $stmt->execute(array( + ':domain' => '%@'.$domain, + )); + $stmt = $pdo->prepare("DELETE FROM `quota2replica` WHERE `username` LIKE :domain"); + $stmt->execute(array( + ':domain' => '%@'.$domain, + )); + $stmt = $pdo->prepare("DELETE FROM `spamalias` WHERE `address` LIKE :domain"); + $stmt->execute(array( + ':domain' => '%@'.$domain, + )); + $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :domain"); + $stmt->execute(array( + ':domain' => $domain, + )); + $stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `local_dest` = :domain"); + $stmt->execute(array( + ':domain' => $domain, + )); + $stmt = $pdo->query("DELETE FROM `admin` WHERE `superadmin` = 0 AND `username` NOT IN (SELECT `username`FROM `domain_admins`);"); + $stmt = $pdo->query("DELETE FROM `da_acl` WHERE `username` NOT IN (SELECT `username`FROM `domain_admins`);"); + try { + $redis->hDel('DOMAIN_MAP', $domain); + $redis->hDel('RL_VALUE', $domain); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('redis_error', $e) + ); + continue; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('domain_removed', htmlspecialchars($domain)) + ); + } + break; + case 'domain_templates': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + if (!is_array($_data['ids'])) { + $ids = array(); + $ids[] = $_data['ids']; + } + else { + $ids = $_data['ids']; + } + + + foreach ($ids as $id) { + // delete template + $stmt = $pdo->prepare("DELETE FROM `templates` + WHERE id = :id AND type = :type AND NOT template = :template"); + $stmt->execute(array( + ":id" => $id, + ":type" => "domain", + ":template" => "Default" + )); + + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('template_removed', htmlspecialchars($id)) + ); + return true; + } + break; + case 'alias': + if (!is_array($_data['id'])) { + $ids = array(); + $ids[] = $_data['id']; + } + else { + $ids = $_data['id']; + } + foreach ($ids as $id) { + $alias_data = mailbox('get', 'alias_details', $id); + if (empty($alias_data)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare("DELETE FROM `alias` WHERE `id` = :id"); + $stmt->execute(array( + ':id' => $alias_data['id'] + )); + $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `send_as` = :alias_address"); + $stmt->execute(array( + ':alias_address' => $alias_data['address'] + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('alias_removed', htmlspecialchars($alias_data['address'])) + ); + } + break; + case 'alias_domain': + if (!is_array($_data['alias_domain'])) { + $alias_domains = array(); + $alias_domains[] = $_data['alias_domain']; + } + else { + $alias_domains = $_data['alias_domain']; + } + foreach ($alias_domains as $alias_domain) { + if (!is_valid_domain_name($alias_domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'domain_invalid' + ); + continue; + } + $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` + WHERE `alias_domain`= :alias_domain"); + $stmt->execute(array(':alias_domain' => $alias_domain)); + $DomainData = $stmt->fetch(PDO::FETCH_ASSOC); + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $DomainData['target_domain'])) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare("DELETE FROM `alias_domain` WHERE `alias_domain` = :alias_domain"); + $stmt->execute(array( + ':alias_domain' => $alias_domain, + )); + $stmt = $pdo->prepare("DELETE FROM `alias` WHERE `domain` = :alias_domain"); + $stmt->execute(array( + ':alias_domain' => $alias_domain, + )); + $stmt = $pdo->prepare("DELETE FROM `spamalias` WHERE `address` LIKE :domain"); + $stmt->execute(array( + ':domain' => '%@'.$alias_domain, + )); + $stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `local_dest` = :alias_domain"); + $stmt->execute(array( + ':alias_domain' => $alias_domain, + )); + try { + $redis->hDel('DOMAIN_MAP', $alias_domain); + $redis->hDel('RL_VALUE', $domain); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('redis_error', $e) + ); + continue; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('alias_domain_removed', htmlspecialchars($alias_domain)) + ); + } + break; + case 'mailbox': + if (!is_array($_data['username'])) { + $usernames = array(); + $usernames[] = $_data['username']; + } + else { + $usernames = $_data['username']; + } + foreach ($usernames as $username) { + if (!filter_var($username, FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $mailbox_details = mailbox('get', 'mailbox_details', $username); + if (!empty($mailbox_details['domain']) && !empty($mailbox_details['local_part'])) { + $maildir = $mailbox_details['domain'] . '/' . $mailbox_details['local_part']; + $exec_fields = array('cmd' => 'maildir', 'task' => 'cleanup', 'maildir' => $maildir); + $maildir_gc = json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true); + if ($maildir_gc['type'] != 'success') { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'Could not move maildir to garbage collector: ' . $maildir_gc['msg'] + ); + } + } + else { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'Could not move maildir to garbage collector: variables local_part and/or domain empty' + ); + } + if (strtolower(getenv('SKIP_SOLR')) == 'n') { + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, 'http://solr:8983/solr/dovecot-fts/update?commit=true'); + curl_setopt($curl, CURLOPT_HTTPHEADER,array('Content-Type: text/xml')); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, 'user:' . $username . ''); + curl_setopt($curl, CURLOPT_TIMEOUT, 30); + $response = curl_exec($curl); + if ($response === false) { + $err = curl_error($curl); + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'Could not remove Solr index: ' . print_r($err, true) + ); + } + curl_close($curl); + } + $stmt = $pdo->prepare("DELETE FROM `alias` WHERE `goto` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `pushover` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE `rcpt` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `quota2` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `quota2replica` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `mailbox` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` = :username"); + $stmt->execute(array( + ':username' => $username + )); + // fk, better safe than sorry + $stmt = $pdo->prepare("DELETE FROM `user_acl` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `spamalias` WHERE `goto` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `imapsync` WHERE `user2` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_user_profile` WHERE `c_uid` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_cache_folder` WHERE `c_uid` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . str_replace('%', '\%', $username) . "/%' OR `c_uid` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_quick_contact` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_quick_appointment` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_folder_info` WHERE `c_path2` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `local_dest` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `oauth_access_tokens` WHERE `user_id` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `oauth_refresh_tokens` WHERE `user_id` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `oauth_authorization_codes` WHERE `user_id` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + $stmt = $pdo->prepare("DELETE FROM `fido2` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + $stmt = $pdo->prepare("SELECT `address`, `goto` FROM `alias` + WHERE `goto` REGEXP :username"); + $stmt->execute(array(':username' => '(^|,)'.$username.'($|,)')); + $GotoData = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($GotoData as $gotos) { + $goto_exploded = explode(',', $gotos['goto']); + if (($key = array_search($username, $goto_exploded)) !== false) { + unset($goto_exploded[$key]); + } + $gotos_rebuild = implode(',', (array)$goto_exploded); + $stmt = $pdo->prepare("UPDATE `alias` SET + `goto` = :goto + WHERE `address` = :address"); + $stmt->execute(array( + ':goto' => $gotos_rebuild, + ':address' => $gotos['address'] + )); + } + try { + $redis->hDel('RL_VALUE', $username); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('redis_error', $e) + ); + continue; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_removed', htmlspecialchars($username)) + ); + } + break; + case 'mailbox_templates': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + if (!is_array($_data['ids'])) { + $ids = array(); + $ids[] = $_data['ids']; + } + else { + $ids = $_data['ids']; + } + + + foreach ($ids as $id) { + // delete template + $stmt = $pdo->prepare("DELETE FROM `templates` + WHERE id = :id AND type = :type AND NOT template = :template"); + $stmt->execute(array( + ":id" => $id, + ":type" => "mailbox", + ":template" => "Default" + )); + } + + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'template_removed' + ); + return true; + break; + case 'resource': + if (!is_array($_data['name'])) { + $names = array(); + $names[] = $_data['name']; + } + else { + $names = $_data['name']; + } + foreach ($names as $name) { + if (!filter_var($name, FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $name)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare("DELETE FROM `mailbox` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $name + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_user_profile` WHERE `c_uid` = :username"); + $stmt->execute(array( + ':username' => $name + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_cache_folder` WHERE `c_uid` = :username"); + $stmt->execute(array( + ':username' => $name + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . $name . "/%' OR `c_uid` = :username"); + $stmt->execute(array( + ':username' => $name + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); + $stmt->execute(array( + ':username' => $name + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_quick_contact` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); + $stmt->execute(array( + ':username' => $name + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_quick_appointment` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)"); + $stmt->execute(array( + ':username' => $name + )); + $stmt = $pdo->prepare("DELETE FROM `sogo_folder_info` WHERE `c_path2` = :username"); + $stmt->execute(array( + ':username' => $name + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('resource_removed', htmlspecialchars($name)) + ); + } + break; + case 'tags_domain': + if (!is_array($_data['domain'])) { + $domains = array(); + $domains[] = $_data['domain']; + } + else { + $domains = $_data['domain']; + } + $tags = $_data['tags']; + if (!is_array($tags)) $tags = array(); + + + $wasModified = false; + foreach ($domains as $domain) { + if (!is_valid_domain_name($domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'domain_invalid' + ); + continue; + } + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + + foreach($tags as $tag){ + // delete tag + $wasModified = true; + $stmt = $pdo->prepare("DELETE FROM `tags_domain` WHERE `domain` = :domain AND `tag_name` = :tag_name"); + $stmt->execute(array( + ':domain' => $domain, + ':tag_name' => $tag, + )); + } + } + + if (!$wasModified) return false; + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('domain_modified', $domain) + ); + break; + case 'tags_mailbox': + if (!is_array($_data['username'])) { + $usernames = array(); + $usernames[] = $_data['username']; + } + else { + $usernames = $_data['username']; + } + $tags = $_data['tags']; + if (!is_array($tags)) $tags = array(); + + $wasModified = false; + foreach ($usernames as $username) { + if (!filter_var($username, FILTER_VALIDATE_EMAIL)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'email invalid' + ); + continue; + } + + $is_now = mailbox('get', 'mailbox_details', $username); + $domain = $is_now['domain']; + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + + // delete tags + foreach($tags as $tag){ + $wasModified = true; + + $stmt = $pdo->prepare("DELETE FROM `tags_mailbox` WHERE `username` = :username AND `tag_name` = :tag_name"); + $stmt->execute(array( + ':username' => $username, + ':tag_name' => $tag, + )); + } + } + + if (!$wasModified) return false; + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => array('mailbox_modified', $username) + ); + break; + } + break; + } + if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'mailbox', 'resource')) && getenv('SKIP_SOGO') != "y") { + update_sogo_static_view(); + } +} diff --git a/data/web/inc/functions.mailq.inc.php b/data/web/inc/functions.mailq.inc.php index f42ab7cc..8b00a803 100644 --- a/data/web/inc/functions.mailq.inc.php +++ b/data/web/inc/functions.mailq.inc.php @@ -1,121 +1,121 @@ - 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'access_denied' - ); - return false; - } - function process_mailq_output($returned_output, $_action, $_data) { - if ($returned_output !== NULL) { - if ($_action == 'cat') { - logger(array('return' => array( - array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'queue_cat_success' - ) - ))); - return $returned_output; - } - else { - if (isset($returned_output['type']) && $returned_output['type'] == 'danger') { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'Error: ' . $returned_output['msg'] - ); - } - if (isset($returned_output['type']) && $returned_output['type'] == 'success') { - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'queue_command_success' - ); - } - } - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'unknown' - ); - } - } - if ($_action == 'get') { - $mailq_lines = docker('post', 'postfix-mailcow', 'exec', array('cmd' => 'mailq', 'task' => 'list')); - $lines = 0; - // Hard limit to 10000 items - foreach (preg_split("/((\r?\n)|(\r\n?))/", $mailq_lines) as $mailq_item) if ($lines++ < 10000) { - if (empty($mailq_item) || $mailq_item == '1') { - continue; - } - $mq_line = json_decode($mailq_item, true); - if ($mq_line !== NULL) { - $rcpts = array(); - foreach ($mq_line['recipients'] as $rcpt) { - if (isset($rcpt['delay_reason'])) { - $rcpts[] = $rcpt['address'] . ' (' . $rcpt['delay_reason'] . ')'; - } - else { - $rcpts[] = $rcpt['address']; - } - } - if (!empty($rcpts)) { - $mq_line['recipients'] = $rcpts; - } - $line[] = $mq_line; - } - } - if (!isset($line) || empty($line)) { - return '[]'; - } - else { - return json_encode($line); - } - } - elseif ($_action == 'delete') { - if (!is_array($_data['qid'])) { - $qids = array(); - $qids[] = $_data['qid']; - } - else { - $qids = $_data['qid']; - } - $docker_return = docker('post', 'postfix-mailcow', 'exec', array('cmd' => 'mailq', 'task' => 'delete', 'items' => $qids)); - process_mailq_output(json_decode($docker_return, true), $_action, $_data); - } - elseif ($_action == 'cat') { - if (!is_array($_data['qid'])) { - $qids = array(); - $qids[] = $_data['qid']; - } - else { - $qids = $_data['qid']; - } - $docker_return = docker('post', 'postfix-mailcow', 'exec', array('cmd' => 'mailq', 'task' => 'cat', 'items' => $qids)); - return process_mailq_output($docker_return, $_action, $_data); - } - elseif ($_action == 'edit') { - if (in_array($_data['action'], array('hold', 'unhold', 'deliver'))) { - if (!is_array($_data['qid'])) { - $qids = array(); - $qids[] = $_data['qid']; - } - else { - $qids = $_data['qid']; - } - if (!empty($qids)) { - $docker_return = docker('post', 'postfix-mailcow', 'exec', array('cmd' => 'mailq', 'task' => $_data['action'], 'items' => $qids)); - process_mailq_output(json_decode($docker_return, true), $_action, $_data); - } - } - if (in_array($_data['action'], array('flush', 'super_delete'))) { - $docker_return = docker('post', 'postfix-mailcow', 'exec', array('cmd' => 'mailq', 'task' => $_data['action'])); - process_mailq_output(json_decode($docker_return, true), $_action, $_data); - } - } -} + 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'access_denied' + ); + return false; + } + function process_mailq_output($returned_output, $_action, $_data) { + if ($returned_output !== NULL) { + if ($_action == 'cat') { + logger(array('return' => array( + array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'queue_cat_success' + ) + ))); + return $returned_output; + } + else { + if (isset($returned_output['type']) && $returned_output['type'] == 'danger') { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'Error: ' . $returned_output['msg'] + ); + } + if (isset($returned_output['type']) && $returned_output['type'] == 'success') { + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'queue_command_success' + ); + } + } + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'unknown' + ); + } + } + if ($_action == 'get') { + $mailq_lines = docker('post', 'postfix-mailcow', 'exec', array('cmd' => 'mailq', 'task' => 'list')); + $lines = 0; + // Hard limit to 10000 items + foreach (preg_split("/((\r?\n)|(\r\n?))/", $mailq_lines) as $mailq_item) if ($lines++ < 10000) { + if (empty($mailq_item) || $mailq_item == '1') { + continue; + } + $mq_line = json_decode($mailq_item, true); + if ($mq_line !== NULL) { + $rcpts = array(); + foreach ($mq_line['recipients'] as $rcpt) { + if (isset($rcpt['delay_reason'])) { + $rcpts[] = $rcpt['address'] . ' (' . $rcpt['delay_reason'] . ')'; + } + else { + $rcpts[] = $rcpt['address']; + } + } + if (!empty($rcpts)) { + $mq_line['recipients'] = $rcpts; + } + $line[] = $mq_line; + } + } + if (!isset($line) || empty($line)) { + return '[]'; + } + else { + return json_encode($line); + } + } + elseif ($_action == 'delete') { + if (!is_array($_data['qid'])) { + $qids = array(); + $qids[] = $_data['qid']; + } + else { + $qids = $_data['qid']; + } + $docker_return = docker('post', 'postfix-mailcow', 'exec', array('cmd' => 'mailq', 'task' => 'delete', 'items' => $qids)); + process_mailq_output(json_decode($docker_return, true), $_action, $_data); + } + elseif ($_action == 'cat') { + if (!is_array($_data['qid'])) { + $qids = array(); + $qids[] = $_data['qid']; + } + else { + $qids = $_data['qid']; + } + $docker_return = docker('post', 'postfix-mailcow', 'exec', array('cmd' => 'mailq', 'task' => 'cat', 'items' => $qids)); + return process_mailq_output($docker_return, $_action, $_data); + } + elseif ($_action == 'edit') { + if (in_array($_data['action'], array('hold', 'unhold', 'deliver'))) { + if (!is_array($_data['qid'])) { + $qids = array(); + $qids[] = $_data['qid']; + } + else { + $qids = $_data['qid']; + } + if (!empty($qids)) { + $docker_return = docker('post', 'postfix-mailcow', 'exec', array('cmd' => 'mailq', 'task' => $_data['action'], 'items' => $qids)); + process_mailq_output(json_decode($docker_return, true), $_action, $_data); + } + } + if (in_array($_data['action'], array('flush', 'super_delete'))) { + $docker_return = docker('post', 'postfix-mailcow', 'exec', array('cmd' => 'mailq', 'task' => $_data['action'])); + process_mailq_output(json_decode($docker_return, true), $_action, $_data); + } + } +} diff --git a/data/web/inc/functions.oauth2.inc.php b/data/web/inc/functions.oauth2.inc.php index 7dc56025..f03aef79 100644 --- a/data/web/inc/functions.oauth2.inc.php +++ b/data/web/inc/functions.oauth2.inc.php @@ -1,242 +1,242 @@ - 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data), - 'msg' => 'access_denied' - ); - return false; - } - switch ($_action) { - case 'add': - switch ($_type) { - case 'client': - $client_id = bin2hex(random_bytes(6)); - $client_secret = bin2hex(random_bytes(12)); - $redirect_uri = $_data['redirect_uri']; - $scope = 'profile'; - // For future use - // $grant_type = isset($_data['grant_type']) ? $_data['grant_type'] : 'authorization_code'; - // $scope = isset($_data['scope']) ? $_data['scope'] : 'profile'; - // if ($grant_type != "authorization_code" && $grant_type != "password") { - // $_SESSION['return'][] = array( - // 'type' => 'danger', - // 'log' => array(__FUNCTION__, $_action, $_type, $_data), - // 'msg' => 'access_denied' - // ); - // return false; - // } - if ($scope != "profile") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data), - 'msg' => 'Invalid scope' - ); - return false; - } - $stmt = $pdo->prepare("SELECT 'client' FROM `oauth_clients` - WHERE `client_id` = :client_id"); - $stmt->execute(array(':client_id' => $client_id)); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data), - 'msg' => 'Client ID exists' - ); - return false; - } - $stmt = $pdo->prepare("INSERT INTO `oauth_clients` (`client_id`, `client_secret`, `redirect_uri`, `scope`) - VALUES (:client_id, :client_secret, :redirect_uri, :scope)"); - $stmt->execute(array( - ':client_id' => $client_id, - ':client_secret' => $client_secret, - ':redirect_uri' => $redirect_uri, - ':scope' => $scope - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data), - 'msg' => 'Added client access' - ); - break; - } - break; - case 'edit': - switch ($_type) { - case 'client': - $ids = (array)$_data['id']; - foreach ($ids as $id) { - $is_now = oauth2('details', 'client', $id); - if (!empty($is_now)) { - $redirect_uri = (!empty($_data['redirect_uri'])) ? $_data['redirect_uri'] : $is_now['redirect_uri']; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data), - 'msg' => 'access_denied' - ); - return false; - } - if (isset($_data['revoke_tokens'])) { - $stmt = $pdo->prepare("DELETE FROM `oauth_access_tokens` - WHERE `client_id` IN ( - SELECT `client_id` FROM `oauth_clients` WHERE `id` = :id - )"); - $stmt->execute(array( - ':id' => $id - )); - $stmt = $pdo->prepare("DELETE FROM `oauth_refresh_tokens` - WHERE `client_id` IN ( - SELECT `client_id` FROM `oauth_clients` WHERE `id` = :id - )"); - $stmt->execute(array( - ':id' => $id - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data), - 'msg' => array('object_modified', htmlspecialchars($id)) - ); - continue; - } - if (isset($_data['renew_secret'])) { - $client_secret = bin2hex(random_bytes(12)); - $stmt = $pdo->prepare("UPDATE `oauth_clients` SET `client_secret` = :client_secret WHERE `id` = :id"); - $stmt->execute(array( - ':client_secret' => $client_secret, - ':id' => $id - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data), - 'msg' => array('object_modified', htmlspecialchars($id)) - ); - continue; - } - if (empty($redirect_uri)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data), - 'msg' => 'Redirect/Callback URL cannot be empty' - ); - continue; - } - $stmt = $pdo->prepare("UPDATE `oauth_clients` SET - `redirect_uri` = :redirect_uri - WHERE `id` = :id"); - $stmt->execute(array( - ':id' => $id, - ':redirect_uri' => $redirect_uri - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data), - 'msg' => array('object_modified', htmlspecialchars($id)) - ); - } - break; - } - break; - case 'delete': - switch ($_type) { - case 'client': - (array)$ids = $_data['id']; - foreach ($ids as $id) { - if (!is_numeric($id)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data), - 'msg' => 'access_denied' - ); - continue; - } - $stmt = $pdo->prepare("DELETE FROM `oauth_clients` - WHERE `id` = :id"); - $stmt->execute(array( - ':id' => $id - )); - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data), - 'msg' => array('items_deleted', htmlspecialchars($id)) - ); - break; - case 'access_token': - (array)$access_tokens = $_data['access_token']; - foreach ($access_tokens as $access_token) { - if (!ctype_alnum($access_token)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data), - 'msg' => 'access_denied' - ); - return false; - } - $stmt = $pdo->prepare("DELETE FROM `oauth_access_tokens` - WHERE `access_token` = :access_token"); - $stmt->execute(array( - ':access_token' => $access_token - )); - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data), - 'msg' => sprintf($lang['success']['items_deleted'], implode(', ', (array)$access_tokens)) - ); - break; - case 'refresh_token': - (array)$refresh_tokens = $_data['refresh_token']; - foreach ($refresh_tokens as $refresh_token) { - if (!ctype_alnum($refresh_token)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data), - 'msg' => 'access_denied' - ); - return false; - } - $stmt = $pdo->prepare("DELETE FROM `oauth_refresh_tokens` WHERE `refresh_token` = :refresh_token"); - $stmt->execute(array( - ':refresh_token' => $refresh_token - )); - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data), - 'msg' => sprintf($lang['success']['items_deleted'], implode(', ', (array)$refresh_tokens)) - ); - break; - } - break; - case 'get': - switch ($_type) { - case 'clients': - $stmt = $pdo->query("SELECT `id` FROM `oauth_clients`"); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($row = array_shift($rows)) { - $oauth_clients[] = $row['id']; - } - return $oauth_clients; - break; - } - break; - case 'details': - switch ($_type) { - case 'client': - $stmt = $pdo->prepare("SELECT * FROM `oauth_clients` - WHERE `id` = :id"); - $stmt->execute(array(':id' => $_data)); - $oauth_client_details = $stmt->fetch(PDO::FETCH_ASSOC); - return $oauth_client_details; - break; - } - break; - } -} + 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => 'access_denied' + ); + return false; + } + switch ($_action) { + case 'add': + switch ($_type) { + case 'client': + $client_id = bin2hex(random_bytes(6)); + $client_secret = bin2hex(random_bytes(12)); + $redirect_uri = $_data['redirect_uri']; + $scope = 'profile'; + // For future use + // $grant_type = isset($_data['grant_type']) ? $_data['grant_type'] : 'authorization_code'; + // $scope = isset($_data['scope']) ? $_data['scope'] : 'profile'; + // if ($grant_type != "authorization_code" && $grant_type != "password") { + // $_SESSION['return'][] = array( + // 'type' => 'danger', + // 'log' => array(__FUNCTION__, $_action, $_type, $_data), + // 'msg' => 'access_denied' + // ); + // return false; + // } + if ($scope != "profile") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => 'Invalid scope' + ); + return false; + } + $stmt = $pdo->prepare("SELECT 'client' FROM `oauth_clients` + WHERE `client_id` = :client_id"); + $stmt->execute(array(':client_id' => $client_id)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => 'Client ID exists' + ); + return false; + } + $stmt = $pdo->prepare("INSERT INTO `oauth_clients` (`client_id`, `client_secret`, `redirect_uri`, `scope`) + VALUES (:client_id, :client_secret, :redirect_uri, :scope)"); + $stmt->execute(array( + ':client_id' => $client_id, + ':client_secret' => $client_secret, + ':redirect_uri' => $redirect_uri, + ':scope' => $scope + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => 'Added client access' + ); + break; + } + break; + case 'edit': + switch ($_type) { + case 'client': + $ids = (array)$_data['id']; + foreach ($ids as $id) { + $is_now = oauth2('details', 'client', $id); + if (!empty($is_now)) { + $redirect_uri = (!empty($_data['redirect_uri'])) ? $_data['redirect_uri'] : $is_now['redirect_uri']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => 'access_denied' + ); + return false; + } + if (isset($_data['revoke_tokens'])) { + $stmt = $pdo->prepare("DELETE FROM `oauth_access_tokens` + WHERE `client_id` IN ( + SELECT `client_id` FROM `oauth_clients` WHERE `id` = :id + )"); + $stmt->execute(array( + ':id' => $id + )); + $stmt = $pdo->prepare("DELETE FROM `oauth_refresh_tokens` + WHERE `client_id` IN ( + SELECT `client_id` FROM `oauth_clients` WHERE `id` = :id + )"); + $stmt->execute(array( + ':id' => $id + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => array('object_modified', htmlspecialchars($id)) + ); + continue; + } + if (isset($_data['renew_secret'])) { + $client_secret = bin2hex(random_bytes(12)); + $stmt = $pdo->prepare("UPDATE `oauth_clients` SET `client_secret` = :client_secret WHERE `id` = :id"); + $stmt->execute(array( + ':client_secret' => $client_secret, + ':id' => $id + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => array('object_modified', htmlspecialchars($id)) + ); + continue; + } + if (empty($redirect_uri)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => 'Redirect/Callback URL cannot be empty' + ); + continue; + } + $stmt = $pdo->prepare("UPDATE `oauth_clients` SET + `redirect_uri` = :redirect_uri + WHERE `id` = :id"); + $stmt->execute(array( + ':id' => $id, + ':redirect_uri' => $redirect_uri + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => array('object_modified', htmlspecialchars($id)) + ); + } + break; + } + break; + case 'delete': + switch ($_type) { + case 'client': + (array)$ids = $_data['id']; + foreach ($ids as $id) { + if (!is_numeric($id)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare("DELETE FROM `oauth_clients` + WHERE `id` = :id"); + $stmt->execute(array( + ':id' => $id + )); + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => array('items_deleted', htmlspecialchars($id)) + ); + break; + case 'access_token': + (array)$access_tokens = $_data['access_token']; + foreach ($access_tokens as $access_token) { + if (!ctype_alnum($access_token)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => 'access_denied' + ); + return false; + } + $stmt = $pdo->prepare("DELETE FROM `oauth_access_tokens` + WHERE `access_token` = :access_token"); + $stmt->execute(array( + ':access_token' => $access_token + )); + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => sprintf($lang['success']['items_deleted'], implode(', ', (array)$access_tokens)) + ); + break; + case 'refresh_token': + (array)$refresh_tokens = $_data['refresh_token']; + foreach ($refresh_tokens as $refresh_token) { + if (!ctype_alnum($refresh_token)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => 'access_denied' + ); + return false; + } + $stmt = $pdo->prepare("DELETE FROM `oauth_refresh_tokens` WHERE `refresh_token` = :refresh_token"); + $stmt->execute(array( + ':refresh_token' => $refresh_token + )); + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_type, $_data), + 'msg' => sprintf($lang['success']['items_deleted'], implode(', ', (array)$refresh_tokens)) + ); + break; + } + break; + case 'get': + switch ($_type) { + case 'clients': + $stmt = $pdo->query("SELECT `id` FROM `oauth_clients`"); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($rows)) { + $oauth_clients[] = $row['id']; + } + return $oauth_clients; + break; + } + break; + case 'details': + switch ($_type) { + case 'client': + $stmt = $pdo->prepare("SELECT * FROM `oauth_clients` + WHERE `id` = :id"); + $stmt->execute(array(':id' => $_data)); + $oauth_client_details = $stmt->fetch(PDO::FETCH_ASSOC); + return $oauth_client_details; + break; + } + break; + } +} diff --git a/data/web/inc/functions.policy.inc.php b/data/web/inc/functions.policy.inc.php index 498f991f..9d45f9cd 100644 --- a/data/web/inc/functions.policy.inc.php +++ b/data/web/inc/functions.policy.inc.php @@ -1,320 +1,320 @@ - 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - switch ($_scope) { - case 'domain': - $object = $_data['domain']; - if (is_valid_domain_name($object)) { - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $object = idn_to_ascii(strtolower(trim($object)), 0, INTL_IDNA_VARIANT_UTS46); - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - if ($_data['object_list'] == "bl") { - $object_list = "blacklist_from"; - } - elseif ($_data['object_list'] == "wl") { - $object_list = "whitelist_from"; - } - $object_from = trim(strtolower($_data['object_from'])); - if (!ctype_alnum(str_replace(array('@', '_', '.', '-', '*'), '', $object_from))) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'policy_list_from_invalid' - ); - return false; - } - if ($object_list != "blacklist_from" && $object_list != "whitelist_from") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $stmt = $pdo->prepare("SELECT `object` FROM `filterconf` - WHERE (`option` = 'whitelist_from' OR `option` = 'blacklist_from') - AND `object` = :object - AND `value` = :object_from"); - $stmt->execute(array(':object' => $object, ':object_from' => $object_from)); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'policy_list_from_exists' - ); - return false; - } - - $stmt = $pdo->prepare("INSERT INTO `filterconf` (`object`, `option` ,`value`) - VALUES (:object, :object_list, :object_from)"); - $stmt->execute(array( - ':object' => $object, - ':object_list' => $object_list, - ':object_from' => $object_from - )); - - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => array('domain_modified', $object) - ); - break; - case 'mailbox': - $object = $_data['username']; - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - if ($_data['object_list'] == "bl") { - $object_list = "blacklist_from"; - } - elseif ($_data['object_list'] == "wl") { - $object_list = "whitelist_from"; - } - $object_from = trim(strtolower($_data['object_from'])); - if (!ctype_alnum(str_replace(array('@', '_', '.', '-', '*'), '', $object_from))) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'policy_list_from_invalid' - ); - return false; - } - if ($object_list != "blacklist_from" && $object_list != "whitelist_from") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $stmt = $pdo->prepare("SELECT `object` FROM `filterconf` - WHERE (`option` = 'whitelist_from' OR `option` = 'blacklist_from') - AND `object` = :object - AND `value` = :object_from"); - $stmt->execute(array(':object' => $object, ':object_from' => $object_from)); - $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); - if ($num_results != 0) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'policy_list_from_exists' - ); - return false; - } - $stmt = $pdo->prepare("INSERT INTO `filterconf` (`object`, `option` ,`value`) - VALUES (:object, :object_list, :object_from)"); - $stmt->execute(array( - ':object' => $object, - ':object_list' => $object_list, - ':object_from' => $object_from - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => array('mailbox_modified', $object) - ); - break; - } - break; - case 'delete': - if (!isset($_SESSION['acl']['spam_policy']) || $_SESSION['acl']['spam_policy'] != "1" ) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - switch ($_scope) { - case 'domain': - (array)$prefids = $_data['prefid']; - foreach ($prefids as $prefid) { - if (!is_numeric($prefid)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'access_denied' - ); - continue; - } - $stmt = $pdo->prepare("SELECT `object` FROM `filterconf` WHERE `prefid` = :prefid"); - $stmt->execute(array(':prefid' => $prefid)); - $object = $stmt->fetch(PDO::FETCH_ASSOC)['object']; - if (is_valid_domain_name($object)) { - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'access_denied' - ); - continue; - } - $object = idn_to_ascii(strtolower(trim($object)), 0, INTL_IDNA_VARIANT_UTS46); - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'access_denied' - ); - continue; - } - try { - $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :object AND `prefid` = :prefid"); - $stmt->execute(array( - ':object' => $object, - ':prefid' => $prefid - )); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => array('mysql_error', $e) - ); - continue; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => array('item_deleted',$prefid) - ); - } - break; - case 'mailbox': - if (!is_array($_data['prefid'])) { - $prefids = array(); - $prefids[] = $_data['prefid']; - } - else { - $prefids = $_data['prefid']; - } - foreach ($prefids as $prefid) { - if (!is_numeric($prefid)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'access_denied' - ); - continue; - } - $stmt = $pdo->prepare("SELECT `object` FROM `filterconf` WHERE `prefid` = :prefid"); - $stmt->execute(array(':prefid' => $prefid)); - $object = $stmt->fetch(PDO::FETCH_ASSOC)['object']; - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'access_denied' - ); - continue; - } - try { - $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :object AND `prefid` = :prefid"); - $stmt->execute(array( - ':object' => $object, - ':prefid' => $prefid - )); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => array('mysql_error', $e) - ); - continue; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => array('items_deleted', implode(', ', (array)$prefids)) - ); - } - break; - } - break; - case 'get': - switch ($_scope) { - case 'domain': - if (!is_valid_domain_name($_data)) { - return false; - } - else { - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - return false; - } - $_data = idn_to_ascii(strtolower(trim($_data)), 0, INTL_IDNA_VARIANT_UTS46); - } - - // WHITELIST - $stmt = $pdo->prepare("SELECT `object`, `value`, `prefid` FROM `filterconf` WHERE `option`='whitelist_from' AND (`object` LIKE :object_mail OR `object` = :object_domain)"); - $stmt->execute(array(':object_mail' => '%@' . $_data, ':object_domain' => $_data)); - $rows['whitelist'] = $stmt->fetchAll(PDO::FETCH_ASSOC); - // BLACKLIST - $stmt = $pdo->prepare("SELECT `object`, `value`, `prefid` FROM `filterconf` WHERE `option`='blacklist_from' AND (`object` LIKE :object_mail OR `object` = :object_domain)"); - $stmt->execute(array(':object_mail' => '%@' . $_data, ':object_domain' => $_data)); - $rows['blacklist'] = $stmt->fetchAll(PDO::FETCH_ASSOC); - - return $rows; - break; - case 'mailbox': - if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - return false; - } - } - else { - $_data = $_SESSION['mailcow_cc_username']; - } - $domain = mailbox('get', 'mailbox_details', $_data)['domain']; - if (empty($domain)) { - return false; - } - // WHITELIST - $stmt = $pdo->prepare("SELECT `object`, `value`, `prefid` FROM `filterconf` WHERE `option`='whitelist_from' AND (`object` = :username OR `object` = :domain)"); - $stmt->execute(array(':username' => $_data, ':domain' => $domain)); - $rows['whitelist'] = $stmt->fetchAll(PDO::FETCH_ASSOC); - // BLACKLIST - $stmt = $pdo->prepare("SELECT `object`, `value`, `prefid` FROM `filterconf` WHERE `option`='blacklist_from' AND (`object` = :username OR `object` = :domain)"); - $stmt->execute(array(':username' => $_data, ':domain' => $domain)); - $rows['blacklist'] = $stmt->fetchAll(PDO::FETCH_ASSOC); - return $rows; - break; - } - break; - } -} + 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + switch ($_scope) { + case 'domain': + $object = $_data['domain']; + if (is_valid_domain_name($object)) { + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $object = idn_to_ascii(strtolower(trim($object)), 0, INTL_IDNA_VARIANT_UTS46); + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + if ($_data['object_list'] == "bl") { + $object_list = "blacklist_from"; + } + elseif ($_data['object_list'] == "wl") { + $object_list = "whitelist_from"; + } + $object_from = trim(strtolower($_data['object_from'])); + if (!ctype_alnum(str_replace(array('@', '_', '.', '-', '*'), '', $object_from))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'policy_list_from_invalid' + ); + return false; + } + if ($object_list != "blacklist_from" && $object_list != "whitelist_from") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $stmt = $pdo->prepare("SELECT `object` FROM `filterconf` + WHERE (`option` = 'whitelist_from' OR `option` = 'blacklist_from') + AND `object` = :object + AND `value` = :object_from"); + $stmt->execute(array(':object' => $object, ':object_from' => $object_from)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'policy_list_from_exists' + ); + return false; + } + + $stmt = $pdo->prepare("INSERT INTO `filterconf` (`object`, `option` ,`value`) + VALUES (:object, :object_list, :object_from)"); + $stmt->execute(array( + ':object' => $object, + ':object_list' => $object_list, + ':object_from' => $object_from + )); + + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('domain_modified', $object) + ); + break; + case 'mailbox': + $object = $_data['username']; + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + if ($_data['object_list'] == "bl") { + $object_list = "blacklist_from"; + } + elseif ($_data['object_list'] == "wl") { + $object_list = "whitelist_from"; + } + $object_from = trim(strtolower($_data['object_from'])); + if (!ctype_alnum(str_replace(array('@', '_', '.', '-', '*'), '', $object_from))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'policy_list_from_invalid' + ); + return false; + } + if ($object_list != "blacklist_from" && $object_list != "whitelist_from") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $stmt = $pdo->prepare("SELECT `object` FROM `filterconf` + WHERE (`option` = 'whitelist_from' OR `option` = 'blacklist_from') + AND `object` = :object + AND `value` = :object_from"); + $stmt->execute(array(':object' => $object, ':object_from' => $object_from)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'policy_list_from_exists' + ); + return false; + } + $stmt = $pdo->prepare("INSERT INTO `filterconf` (`object`, `option` ,`value`) + VALUES (:object, :object_list, :object_from)"); + $stmt->execute(array( + ':object' => $object, + ':object_list' => $object_list, + ':object_from' => $object_from + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('mailbox_modified', $object) + ); + break; + } + break; + case 'delete': + if (!isset($_SESSION['acl']['spam_policy']) || $_SESSION['acl']['spam_policy'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + switch ($_scope) { + case 'domain': + (array)$prefids = $_data['prefid']; + foreach ($prefids as $prefid) { + if (!is_numeric($prefid)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare("SELECT `object` FROM `filterconf` WHERE `prefid` = :prefid"); + $stmt->execute(array(':prefid' => $prefid)); + $object = $stmt->fetch(PDO::FETCH_ASSOC)['object']; + if (is_valid_domain_name($object)) { + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'access_denied' + ); + continue; + } + $object = idn_to_ascii(strtolower(trim($object)), 0, INTL_IDNA_VARIANT_UTS46); + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'access_denied' + ); + continue; + } + try { + $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :object AND `prefid` = :prefid"); + $stmt->execute(array( + ':object' => $object, + ':prefid' => $prefid + )); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('mysql_error', $e) + ); + continue; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('item_deleted',$prefid) + ); + } + break; + case 'mailbox': + if (!is_array($_data['prefid'])) { + $prefids = array(); + $prefids[] = $_data['prefid']; + } + else { + $prefids = $_data['prefid']; + } + foreach ($prefids as $prefid) { + if (!is_numeric($prefid)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare("SELECT `object` FROM `filterconf` WHERE `prefid` = :prefid"); + $stmt->execute(array(':prefid' => $prefid)); + $object = $stmt->fetch(PDO::FETCH_ASSOC)['object']; + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'access_denied' + ); + continue; + } + try { + $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :object AND `prefid` = :prefid"); + $stmt->execute(array( + ':object' => $object, + ':prefid' => $prefid + )); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('mysql_error', $e) + ); + continue; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('items_deleted', implode(', ', (array)$prefids)) + ); + } + break; + } + break; + case 'get': + switch ($_scope) { + case 'domain': + if (!is_valid_domain_name($_data)) { + return false; + } + else { + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + return false; + } + $_data = idn_to_ascii(strtolower(trim($_data)), 0, INTL_IDNA_VARIANT_UTS46); + } + + // WHITELIST + $stmt = $pdo->prepare("SELECT `object`, `value`, `prefid` FROM `filterconf` WHERE `option`='whitelist_from' AND (`object` LIKE :object_mail OR `object` = :object_domain)"); + $stmt->execute(array(':object_mail' => '%@' . $_data, ':object_domain' => $_data)); + $rows['whitelist'] = $stmt->fetchAll(PDO::FETCH_ASSOC); + // BLACKLIST + $stmt = $pdo->prepare("SELECT `object`, `value`, `prefid` FROM `filterconf` WHERE `option`='blacklist_from' AND (`object` LIKE :object_mail OR `object` = :object_domain)"); + $stmt->execute(array(':object_mail' => '%@' . $_data, ':object_domain' => $_data)); + $rows['blacklist'] = $stmt->fetchAll(PDO::FETCH_ASSOC); + + return $rows; + break; + case 'mailbox': + if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + return false; + } + } + else { + $_data = $_SESSION['mailcow_cc_username']; + } + $domain = mailbox('get', 'mailbox_details', $_data)['domain']; + if (empty($domain)) { + return false; + } + // WHITELIST + $stmt = $pdo->prepare("SELECT `object`, `value`, `prefid` FROM `filterconf` WHERE `option`='whitelist_from' AND (`object` = :username OR `object` = :domain)"); + $stmt->execute(array(':username' => $_data, ':domain' => $domain)); + $rows['whitelist'] = $stmt->fetchAll(PDO::FETCH_ASSOC); + // BLACKLIST + $stmt = $pdo->prepare("SELECT `object`, `value`, `prefid` FROM `filterconf` WHERE `option`='blacklist_from' AND (`object` = :username OR `object` = :domain)"); + $stmt->execute(array(':username' => $_data, ':domain' => $domain)); + $rows['blacklist'] = $stmt->fetchAll(PDO::FETCH_ASSOC); + return $rows; + break; + } + break; + } +} diff --git a/data/web/inc/functions.pushover.inc.php b/data/web/inc/functions.pushover.inc.php index 5393c0d5..bb765679 100644 --- a/data/web/inc/functions.pushover.inc.php +++ b/data/web/inc/functions.pushover.inc.php @@ -1,223 +1,223 @@ - 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'access_denied' - ); - return false; - } - if (!is_array($_data['username'])) { - $usernames = array(); - $usernames[] = $_data['username']; - } - else { - $usernames = $_data['username']; - } - foreach ($usernames as $username) { - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'access_denied' - ); - continue; - } - $delete = $_data['delete']; - if ($delete == "true") { - $stmt = $pdo->prepare("DELETE FROM `pushover` WHERE `username` = :username"); - $stmt->execute(array( - ':username' => $username - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'pushover_settings_edited' - ); - continue; - } - $is_now = pushover('get', $username); - if (!empty($is_now)) { - $key = (!empty($_data['key'])) ? $_data['key'] : $is_now['key']; - $token = (!empty($_data['token'])) ? $_data['token'] : $is_now['token']; - $senders = (isset($_data['senders'])) ? $_data['senders'] : $is_now['senders']; - $senders_regex = (isset($_data['senders_regex'])) ? $_data['senders_regex'] : $is_now['senders_regex']; - $title = (!empty($_data['title'])) ? $_data['title'] : $is_now['title']; - $text = (!empty($_data['text'])) ? $_data['text'] : $is_now['text']; - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; - $evaluate_x_prio = (isset($_data['evaluate_x_prio'])) ? intval($_data['evaluate_x_prio']) : $is_now['evaluate_x_prio']; - $only_x_prio = (isset($_data['only_x_prio'])) ? intval($_data['only_x_prio']) : $is_now['only_x_prio']; - $sound = (isset($_data['sound'])) ? $_data['sound'] : $is_now['sound']; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'access_denied' - ); - continue; - } - if (!empty($senders_regex) && !is_valid_regex($senders_regex)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'Invalid regex' - ); - continue; - } - $senders = array_map('trim', preg_split( "/( |,|;|\n)/", $senders)); - foreach ($senders as $i => &$sender) { - if (empty($sender)) { - continue; - } - if (!filter_var($sender, FILTER_VALIDATE_EMAIL) === true) { - unset($senders[$i]); - continue; - } - $senders[$i] = preg_replace('/\.(?=.*?@gmail\.com$)/', '$1', $sender); - } - $senders = array_filter($senders); - if (empty($senders)) { $senders = ''; } - $senders = implode(",", (array)$senders); - if (!ctype_alnum($key) || strlen($key) != 30) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_data), - 'msg' => 'pushover_key' - ); - continue; - } - if (!ctype_alnum($token) || strlen($token) != 30) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_data), - 'msg' => 'pushover_token' - ); - continue; - } - $po_attributes = json_encode( - array( - 'evaluate_x_prio' => strval(intval($evaluate_x_prio)), - 'only_x_prio' => strval(intval($only_x_prio)), - 'sound' => strval($sound) - ) - ); - $stmt = $pdo->prepare("REPLACE INTO `pushover` (`username`, `key`, `attributes`, `senders_regex`, `senders`, `token`, `title`, `text`, `active`) - VALUES (:username, :key, :po_attributes, :senders_regex, :senders, :token, :title, :text, :active)"); - $stmt->execute(array( - ':username' => $username, - ':key' => $key, - ':po_attributes' => $po_attributes, - ':senders_regex' => $senders_regex, - ':senders' => $senders, - ':token' => $token, - ':title' => $title, - ':text' => $text, - ':active' => $active - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'pushover_settings_edited' - ); - } - break; - case 'get': - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'access_denied' - ); - return false; - } - $stmt = $pdo->prepare("SELECT * FROM `pushover` WHERE `username` = :username"); - $stmt->execute(array( - ':username' => $_data - )); - $data = $stmt->fetch(PDO::FETCH_ASSOC); - $data['attributes'] = json_decode($data['attributes'], true); - if (empty($data)) { - return false; - } - else { - return $data; - } - break; - case 'test': - if (!isset($_SESSION['acl']['pushover']) || $_SESSION['acl']['pushover'] != "1" ) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'access_denied' - ); - return false; - } - if (!is_array($_data['username'])) { - $usernames = array(); - $usernames[] = $_data['username']; - } - else { - $usernames = $_data['username']; - } - foreach ($usernames as $username) { - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'access_denied' - ); - continue; - } - $stmt = $pdo->prepare("SELECT * FROM `pushover` - WHERE `username` = :username"); - $stmt->execute(array( - ':username' => $username - )); - $api_data = $stmt->fetch(PDO::FETCH_ASSOC); - if (!empty($api_data)) { - $title = (!empty($api_data['title'])) ? $api_data['title'] : 'Mail'; - $text = (!empty($api_data['text'])) ? $api_data['text'] : 'You\'ve got mail 📧'; - curl_setopt_array($ch = curl_init(), array( - CURLOPT_URL => "https://api.pushover.net/1/users/validate.json", - CURLOPT_POSTFIELDS => array( - "token" => $api_data['token'], - "user" => $api_data['key'] - ), - CURLOPT_SAFE_UPLOAD => true, - CURLOPT_RETURNTRANSFER => true, - )); - $result = curl_exec($ch); - $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - curl_close($ch); - if ($httpcode == 200) { - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => sprintf('Pushover API OK (%d): %s', $httpcode, $result) - ); - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => sprintf('Pushover API ERR (%d): %s', $httpcode, $result) - ); - } - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'pushover_credentials_missing' - ); - return false; - } - } - break; - } -} + 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'access_denied' + ); + return false; + } + if (!is_array($_data['username'])) { + $usernames = array(); + $usernames[] = $_data['username']; + } + else { + $usernames = $_data['username']; + } + foreach ($usernames as $username) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'access_denied' + ); + continue; + } + $delete = $_data['delete']; + if ($delete == "true") { + $stmt = $pdo->prepare("DELETE FROM `pushover` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'pushover_settings_edited' + ); + continue; + } + $is_now = pushover('get', $username); + if (!empty($is_now)) { + $key = (!empty($_data['key'])) ? $_data['key'] : $is_now['key']; + $token = (!empty($_data['token'])) ? $_data['token'] : $is_now['token']; + $senders = (isset($_data['senders'])) ? $_data['senders'] : $is_now['senders']; + $senders_regex = (isset($_data['senders_regex'])) ? $_data['senders_regex'] : $is_now['senders_regex']; + $title = (!empty($_data['title'])) ? $_data['title'] : $is_now['title']; + $text = (!empty($_data['text'])) ? $_data['text'] : $is_now['text']; + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; + $evaluate_x_prio = (isset($_data['evaluate_x_prio'])) ? intval($_data['evaluate_x_prio']) : $is_now['evaluate_x_prio']; + $only_x_prio = (isset($_data['only_x_prio'])) ? intval($_data['only_x_prio']) : $is_now['only_x_prio']; + $sound = (isset($_data['sound'])) ? $_data['sound'] : $is_now['sound']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'access_denied' + ); + continue; + } + if (!empty($senders_regex) && !is_valid_regex($senders_regex)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'Invalid regex' + ); + continue; + } + $senders = array_map('trim', preg_split( "/( |,|;|\n)/", $senders)); + foreach ($senders as $i => &$sender) { + if (empty($sender)) { + continue; + } + if (!filter_var($sender, FILTER_VALIDATE_EMAIL) === true) { + unset($senders[$i]); + continue; + } + $senders[$i] = preg_replace('/\.(?=.*?@gmail\.com$)/', '$1', $sender); + } + $senders = array_filter($senders); + if (empty($senders)) { $senders = ''; } + $senders = implode(",", (array)$senders); + if (!ctype_alnum($key) || strlen($key) != 30) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_data), + 'msg' => 'pushover_key' + ); + continue; + } + if (!ctype_alnum($token) || strlen($token) != 30) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_data), + 'msg' => 'pushover_token' + ); + continue; + } + $po_attributes = json_encode( + array( + 'evaluate_x_prio' => strval(intval($evaluate_x_prio)), + 'only_x_prio' => strval(intval($only_x_prio)), + 'sound' => strval($sound) + ) + ); + $stmt = $pdo->prepare("REPLACE INTO `pushover` (`username`, `key`, `attributes`, `senders_regex`, `senders`, `token`, `title`, `text`, `active`) + VALUES (:username, :key, :po_attributes, :senders_regex, :senders, :token, :title, :text, :active)"); + $stmt->execute(array( + ':username' => $username, + ':key' => $key, + ':po_attributes' => $po_attributes, + ':senders_regex' => $senders_regex, + ':senders' => $senders, + ':token' => $token, + ':title' => $title, + ':text' => $text, + ':active' => $active + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'pushover_settings_edited' + ); + } + break; + case 'get': + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'access_denied' + ); + return false; + } + $stmt = $pdo->prepare("SELECT * FROM `pushover` WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $_data + )); + $data = $stmt->fetch(PDO::FETCH_ASSOC); + $data['attributes'] = json_decode($data['attributes'], true); + if (empty($data)) { + return false; + } + else { + return $data; + } + break; + case 'test': + if (!isset($_SESSION['acl']['pushover']) || $_SESSION['acl']['pushover'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'access_denied' + ); + return false; + } + if (!is_array($_data['username'])) { + $usernames = array(); + $usernames[] = $_data['username']; + } + else { + $usernames = $_data['username']; + } + foreach ($usernames as $username) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare("SELECT * FROM `pushover` + WHERE `username` = :username"); + $stmt->execute(array( + ':username' => $username + )); + $api_data = $stmt->fetch(PDO::FETCH_ASSOC); + if (!empty($api_data)) { + $title = (!empty($api_data['title'])) ? $api_data['title'] : 'Mail'; + $text = (!empty($api_data['text'])) ? $api_data['text'] : 'You\'ve got mail 📧'; + curl_setopt_array($ch = curl_init(), array( + CURLOPT_URL => "https://api.pushover.net/1/users/validate.json", + CURLOPT_POSTFIELDS => array( + "token" => $api_data['token'], + "user" => $api_data['key'] + ), + CURLOPT_SAFE_UPLOAD => true, + CURLOPT_RETURNTRANSFER => true, + )); + $result = curl_exec($ch); + $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + if ($httpcode == 200) { + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => sprintf('Pushover API OK (%d): %s', $httpcode, $result) + ); + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => sprintf('Pushover API ERR (%d): %s', $httpcode, $result) + ); + } + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'pushover_credentials_missing' + ); + return false; + } + } + break; + } +} diff --git a/data/web/inc/functions.quarantine.inc.php b/data/web/inc/functions.quarantine.inc.php index f4f49de4..7cb9cdfe 100644 --- a/data/web/inc/functions.quarantine.inc.php +++ b/data/web/inc/functions.quarantine.inc.php @@ -1,841 +1,841 @@ - array( - array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ) - ))); - return false; - } - $stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt` - WHERE SHA2(CONCAT(`id`, `qid`), 256) = :hash - AND user_acl.quarantine = 1 - AND rcpt IN (SELECT username FROM mailbox)'); - $stmt->execute(array(':hash' => $hash)); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (empty($row['id']) || !is_numeric($row['id'])) { - logger(array('return' => array( - array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ) - ))); - return false; - } - else { - $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE id = :id"); - $stmt->execute(array( - ':id' => $row['id'] - )); - } - logger(array('return' => array( - array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('item_deleted', $row['id']) - ) - ))); - break; - case 'quick_release': - // Dont return results, just log - $hash = trim($_data); - if (preg_match("/^([a-f0-9]{64})$/", $hash) === false) { - logger(array('return' => array( - array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ) - ))); - return false; - } - $stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt` - WHERE SHA2(CONCAT(`id`, `qid`), 256) = :hash - AND `user_acl`.`quarantine` = 1 - AND `username` IN (SELECT `username` FROM `mailbox`)'); - $stmt->execute(array(':hash' => $hash)); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (empty($row['id']) || !is_numeric($row['id'])) { - logger(array('return' => array( - array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ) - ))); - return false; - } - else { - $stmt = $pdo->prepare('SELECT `msg`, `qid`, `sender`, `rcpt` FROM `quarantine` WHERE `id` = :id'); - $stmt->execute(array(':id' => $row['id'])); - $detail_row = $stmt->fetch(PDO::FETCH_ASSOC); - $sender = !empty($detail_row['sender']) ? $detail_row['sender'] : 'sender-unknown@rspamd'; - if (!empty(gethostbynamel('postfix-mailcow'))) { - $postfix = 'postfix-mailcow'; - } - if (!empty(gethostbynamel('postfix'))) { - $postfix = 'postfix'; - } - else { - logger(array('return' => array( - array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('release_send_failed', 'Cannot determine Postfix host') - ) - ))); - return false; - } - try { - $release_format = $redis->Get('Q_RELEASE_FORMAT'); - } - catch (RedisException $e) { - logger(array('return' => array( - array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('redis_error', $e) - ) - ))); - return false; - } - if ($release_format == 'attachment') { - try { - $mail = new PHPMailer(true); - $mail->isSMTP(); - $mail->SMTPDebug = 0; - $mail->SMTPOptions = array( - 'ssl' => array( - 'verify_peer' => false, - 'verify_peer_name' => false, - 'allow_self_signed' => true - ) - ); - if (!empty(gethostbynamel('postfix-mailcow'))) { - $postfix = 'postfix-mailcow'; - } - if (!empty(gethostbynamel('postfix'))) { - $postfix = 'postfix'; - } - else { - logger(array('return' => array( - array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('release_send_failed', 'Cannot determine Postfix host') - ) - ))); - return false; - } - $mail->Host = $postfix; - $mail->Port = 590; - $mail->setFrom($sender); - $mail->CharSet = 'UTF-8'; - $mail->Subject = sprintf($lang['quarantine']['release_subject'], $detail_row['qid']); - $mail->addAddress($detail_row['rcpt']); - $mail->IsHTML(false); - $msg_tmpf = tempnam("/tmp", $detail_row['qid']); - file_put_contents($msg_tmpf, $detail_row['msg']); - $mail->addAttachment($msg_tmpf, $detail_row['qid'] . '.eml'); - $mail->Body = sprintf($lang['quarantine']['release_body']); - $mail->send(); - unlink($msg_tmpf); - } - catch (phpmailerException $e) { - unlink($msg_tmpf); - logger(array('return' => array( - array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('release_send_failed', $e->errorMessage()) - ) - ))); - return false; - } - } - elseif ($release_format == 'raw') { - $detail_row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/', 'X-Pre-Release-Spam-Flag $1', $detail_row['msg']); - $postfix_talk = array( - array('220', 'HELO quarantine' . chr(10)), - array('250', 'MAIL FROM: ' . $sender . chr(10)), - array('250', 'RCPT TO: ' . $detail_row['rcpt'] . chr(10)), - array('250', 'DATA' . chr(10)), - array('354', $detail_row['msg'] . chr(10) . '.' . chr(10)), - array('250', 'QUIT' . chr(10)), - array('221', '') - ); - // Thanks to https://stackoverflow.com/questions/6632399/given-an-email-as-raw-text-how-can-i-send-it-using-php - $smtp_connection = fsockopen($postfix, 590, $errno, $errstr, 1); - if (!$smtp_connection) { - logger(array('return' => array( - array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'Cannot connect to Postfix' - ) - ))); - return false; - } - for ($i=0; $i < count($postfix_talk); $i++) { - $smtp_resource = fgets($smtp_connection, 256); - if (substr($smtp_resource, 0, 3) !== $postfix_talk[$i][0]) { - $ret = substr($smtp_resource, 0, 3); - $ret = (empty($ret)) ? '-' : $ret; - logger(array('return' => array( - array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'Postfix returned SMTP code ' . $smtp_resource . ', expected ' . $postfix_talk[$i][0] - ) - ))); - return false; - } - if ($postfix_talk[$i][1] !== '') { - fputs($smtp_connection, $postfix_talk[$i][1]); - } - } - fclose($smtp_connection); - } - $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE id = :id"); - $stmt->execute(array( - ':id' => $row['id'] - )); - } - logger(array('return' => array( - array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('item_released', $hash) - ) - ))); - break; - case 'delete': - if (!is_array($_data['id'])) { - $ids = array(); - $ids[] = $_data['id']; - } - else { - $ids = $_data['id']; - } - if (!isset($_SESSION['acl']['quarantine']) || $_SESSION['acl']['quarantine'] != "1" ) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - foreach ($ids as $id) { - if (!is_numeric($id)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - continue; - } - $stmt = $pdo->prepare('SELECT `rcpt` FROM `quarantine` WHERE `id` = :id'); - $stmt->execute(array(':id' => $id)); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt']) && $_SESSION['mailcow_cc_role'] != 'admin') { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - continue; - } - else { - $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE `id` = :id"); - $stmt->execute(array( - ':id' => $id - )); - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('item_deleted', $id) - ); - } - break; - case 'edit': - if (!isset($_SESSION['acl']['quarantine']) || $_SESSION['acl']['quarantine'] != "1" ) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - // Edit settings - if ($_data['action'] == 'settings') { - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $retention_size = $_data['retention_size']; - if ($_data['release_format'] == 'attachment' || $_data['release_format'] == 'raw') { - $release_format = $_data['release_format']; - } - else { - $release_format = 'raw'; - } - $max_size = $_data['max_size']; - if ($_data['max_score'] == "") { - $max_score = ''; - } - else { - $max_score = floatval($_data['max_score']); - } - $max_age = intval($_data['max_age']); - $subject = $_data['subject']; - if (!filter_var($_data['bcc'], FILTER_VALIDATE_EMAIL)) { - $bcc = ''; - } - else { - $bcc = $_data['bcc']; - } - if (!filter_var($_data['redirect'], FILTER_VALIDATE_EMAIL)) { - $redirect = ''; - } - else { - $redirect = $_data['redirect']; - } - if (!filter_var($_data['sender'], FILTER_VALIDATE_EMAIL)) { - $sender = ''; - } - else { - $sender = $_data['sender']; - } - $html = $_data['html_tmpl']; - if ($max_age <= 0) { - $max_age = 365; - } - $exclude_domains = (array)$_data['exclude_domains']; - try { - $redis->Set('Q_RETENTION_SIZE', intval($retention_size)); - $redis->Set('Q_MAX_SIZE', intval($max_size)); - $redis->Set('Q_MAX_SCORE', $max_score); - $redis->Set('Q_MAX_AGE', $max_age); - $redis->Set('Q_EXCLUDE_DOMAINS', json_encode($exclude_domains)); - $redis->Set('Q_RELEASE_FORMAT', $release_format); - $redis->Set('Q_SENDER', $sender); - $redis->Set('Q_BCC', $bcc); - $redis->Set('Q_REDIRECT', $redirect); - $redis->Set('Q_SUBJ', $subject); - $redis->Set('Q_HTML', $html); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('redis_error', $e) - ); - return false; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'saved_settings' - ); - } - // Release item - elseif ($_data['action'] == 'release' || $_data['action'] == 'learnham') { - if (!is_array($_data['id'])) { - $ids = array(); - $ids[] = $_data['id']; - } - else { - $ids = $_data['id']; - } - foreach ($ids as $id) { - if (!is_numeric($id)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - continue; - } - $stmt = $pdo->prepare('SELECT `msg`, `action`, `qid`, `sender`, `rcpt` FROM `quarantine` WHERE `id` = :id'); - $stmt->execute(array(':id' => $id)); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt']) && $_SESSION['mailcow_cc_role'] != 'admin' || empty($row['rcpt'])) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - continue; - } - $sender = !empty($row['sender']) ? $row['sender'] : 'sender-unknown@rspamd'; - if (!empty(gethostbynamel('postfix-mailcow'))) { - $postfix = 'postfix-mailcow'; - } - if (!empty(gethostbynamel('postfix'))) { - $postfix = 'postfix'; - } - else { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('release_send_failed', 'Cannot determine Postfix host') - ); - continue; - } - try { - $release_format = $redis->Get('Q_RELEASE_FORMAT'); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('redis_error', $e) - ); - return false; - } - if ($release_format == 'attachment') { - try { - $mail = new PHPMailer(true); - $mail->isSMTP(); - $mail->SMTPDebug = 0; - $mail->SMTPOptions = array( - 'ssl' => array( - 'verify_peer' => false, - 'verify_peer_name' => false, - 'allow_self_signed' => true - ) - ); - if (!empty(gethostbynamel('postfix-mailcow'))) { - $postfix = 'postfix-mailcow'; - } - if (!empty(gethostbynamel('postfix'))) { - $postfix = 'postfix'; - } - else { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('release_send_failed', 'Cannot determine Postfix host') - ); - continue; - } - $mail->Host = $postfix; - $mail->Port = 590; - $mail->setFrom($sender); - $mail->CharSet = 'UTF-8'; - $mail->Subject = sprintf($lang['quarantine']['release_subject'], $row['qid']); - $mail->addAddress($row['rcpt']); - $mail->IsHTML(false); - $msg_tmpf = tempnam("/tmp", $row['qid']); - file_put_contents($msg_tmpf, $row['msg']); - $mail->addAttachment($msg_tmpf, $row['qid'] . '.eml'); - $mail->Body = sprintf($lang['quarantine']['release_body']); - $mail->send(); - unlink($msg_tmpf); - } - catch (phpmailerException $e) { - unlink($msg_tmpf); - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('release_send_failed', $e->errorMessage()) - ); - continue; - } - } - elseif ($release_format == 'raw') { - $row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/', 'X-Pre-Release-Spam-Flag $1', $row['msg']); - $postfix_talk = array( - array('220', 'HELO quarantine' . chr(10)), - array('250', 'MAIL FROM: ' . $sender . chr(10)), - array('250', 'RCPT TO: ' . $row['rcpt'] . chr(10)), - array('250', 'DATA' . chr(10)), - array('354', str_replace("\n.", '', $row['msg']) . chr(10) . '.' . chr(10)), - array('250', 'QUIT' . chr(10)), - array('221', '') - ); - // Thanks to https://stackoverflow.com/questions/6632399/given-an-email-as-raw-text-how-can-i-send-it-using-php - $smtp_connection = fsockopen($postfix, 590, $errno, $errstr, 1); - if (!$smtp_connection) { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'Cannot connect to Postfix' - ); - return false; - } - for ($i=0; $i < count($postfix_talk); $i++) { - $smtp_resource = fgets($smtp_connection, 256); - if (substr($smtp_resource, 0, 3) !== $postfix_talk[$i][0]) { - $ret = substr($smtp_resource, 0, 3); - $ret = (empty($ret)) ? '-' : $ret; - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'Postfix returned SMTP code ' . $smtp_resource . ', expected ' . $postfix_talk[$i][0] - ); - return false; - } - if ($postfix_talk[$i][1] !== '') { - fputs($smtp_connection, $postfix_talk[$i][1]); - } - } - fclose($smtp_connection); - } - $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE `id` = :id"); - $stmt->execute(array( - ':id' => $id - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('item_released', $id) - ); - // Item was released and deleted from quarantine, now learning ham - $curl = curl_init(); - curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_TIMEOUT, 30); - curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain')); - curl_setopt($curl, CURLOPT_URL,"http://rspamd/learnham"); - curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']); - $response = curl_exec($curl); - if (!curl_errno($curl)) { - $response = json_decode($response, true); - if (isset($response['error'])) { - if (stripos($response['error'], 'already learned') === false) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('ham_learn_error', $response['error']) - ); - continue; - } - } - curl_close($curl); - $curl = curl_init(); - curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_TIMEOUT, 30); - curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain', 'Flag: 11')); - curl_setopt($curl, CURLOPT_URL,"http://rspamd/fuzzydel"); - curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']); - // It is most likely not a spam hash, so we ignore any error/warning response - // $response = curl_exec($curl); - if (!curl_errno($curl)) { - curl_close($curl); - $curl = curl_init(); - curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_TIMEOUT, 30); - curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain', 'Flag: 13')); - curl_setopt($curl, CURLOPT_URL,"http://rspamd/fuzzyadd"); - curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']); - $response = curl_exec($curl); - curl_exec($curl); - if (!curl_errno($curl)) { - $response = json_decode($response, true); - if (isset($response['error'])) { - if (stripos($response['error'], 'No content to generate fuzzy') === false) { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('fuzzy_learn_error', $response['error']) - ); - } - } - } - curl_close($curl); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('learned_ham', $id) - ); - continue; - } - else { - curl_close($curl); - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('ham_learn_error', 'Curl: ' . curl_strerror(curl_errno($curl))) - ); - continue; - } - curl_close($curl); - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('ham_learn_error', 'unknown') - ); - continue; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('ham_learn_error', 'Curl: ' . curl_strerror(curl_errno($curl))) - ); - curl_close($curl); - continue; - } - curl_close($curl); - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('ham_learn_error', 'unknown') - ); - continue; - } - } - elseif ($_data['action'] == 'learnspam') { - if (!is_array($_data['id'])) { - $ids = array(); - $ids[] = $_data['id']; - } - else { - $ids = $_data['id']; - } - foreach ($ids as $id) { - if (!is_numeric($id)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - continue; - } - $stmt = $pdo->prepare('SELECT `msg`, `rcpt`, `action` FROM `quarantine` WHERE `id` = :id'); - $stmt->execute(array(':id' => $id)); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt']) && $_SESSION['mailcow_cc_role'] != 'admin' || empty($row['rcpt'])) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - continue; - } - $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE `id` = :id"); - $stmt->execute(array( - ':id' => $id - )); - $curl = curl_init(); - curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_TIMEOUT, 30); - curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain')); - curl_setopt($curl, CURLOPT_URL,"http://rspamd/learnspam"); - curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']); - $response = curl_exec($curl); - if (!curl_errno($curl)) { - $response = json_decode($response, true); - if (isset($response['error'])) { - if (stripos($response['error'], 'already learned') === false) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('spam_learn_error', $response['error']) - ); - continue; - } - } - curl_close($curl); - $curl = curl_init(); - curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_TIMEOUT, 30); - curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain', 'Flag: 13')); - curl_setopt($curl, CURLOPT_URL,"http://rspamd/fuzzydel"); - curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']); - // It is most likely not a spam hash, so we ignore any error/warning response - // $response = curl_exec($curl); - if (!curl_errno($curl)) { - curl_close($curl); - $curl = curl_init(); - curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_TIMEOUT, 30); - curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain', 'Flag: 11')); - curl_setopt($curl, CURLOPT_URL,"http://rspamd/fuzzyadd"); - curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']); - $response = curl_exec($curl); - curl_exec($curl); - if (!curl_errno($curl)) { - $response = json_decode($response, true); - if (isset($response['error'])) { - if (stripos($response['error'], 'No content to generate fuzzy') === false) { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('fuzzy_learn_error', $response['error']) - ); - } - } - } - curl_close($curl); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('qlearn_spam', $id) - ); - continue; - } - else { - curl_close($curl); - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('spam_learn_error', 'Curl: ' . curl_strerror(curl_errno($curl))) - ); - continue; - } - curl_close($curl); - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('spam_learn_error', 'unknown') - ); - continue; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('spam_learn_error', 'Curl: ' . curl_strerror(curl_errno($curl))) - ); - curl_close($curl); - continue; - } - curl_close($curl); - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('spam_learn_error', 'unknown') - ); - continue; - } - } - return true; - break; - case 'get': - if ($_SESSION['mailcow_cc_role'] == "user") { - $stmt = $pdo->prepare('SELECT `id`, `qid`, `subject`, LOCATE("VIRUS_FOUND", `symbols`) AS `virus_flag`, `score`, `rcpt`, `sender`, `action`, UNIX_TIMESTAMP(`created`) AS `created`, `notified` FROM `quarantine` WHERE `rcpt` = :mbox'); - $stmt->execute(array(':mbox' => $_SESSION['mailcow_cc_username'])); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - $q_meta[] = $row; - } - } - elseif ($_SESSION['mailcow_cc_role'] == "admin") { - $stmt = $pdo->query('SELECT `id`, `qid`, `subject`, LOCATE("VIRUS_FOUND", `symbols`) AS `virus_flag`, `score`, `rcpt`, `sender`, `action`, UNIX_TIMESTAMP(`created`) AS `created`, `notified` FROM `quarantine`'); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - $q_meta[] = $row; - } - } - else { - $domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains')); - foreach ($domains as $domain) { - $stmt = $pdo->prepare('SELECT `id`, `qid`, `subject`, LOCATE("VIRUS_FOUND", `symbols`) AS `virus_flag`, `score`, `rcpt`, `sender`, `action`, UNIX_TIMESTAMP(`created`) AS `created`, `notified` FROM `quarantine` WHERE `rcpt` REGEXP :domain'); - $stmt->execute(array(':domain' => '@' . $domain . '$')); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - while($row = array_shift($rows)) { - $q_meta[] = $row; - } - } - } - return $q_meta; - break; - case 'settings': - try { - if ($_SESSION['mailcow_cc_role'] == "admin") { - $settings['exclude_domains'] = json_decode($redis->Get('Q_EXCLUDE_DOMAINS'), true); - } - $settings['max_size'] = $redis->Get('Q_MAX_SIZE'); - $settings['max_score'] = $redis->Get('Q_MAX_SCORE'); - $settings['max_age'] = $redis->Get('Q_MAX_AGE'); - $settings['retention_size'] = $redis->Get('Q_RETENTION_SIZE'); - $settings['release_format'] = $redis->Get('Q_RELEASE_FORMAT'); - $settings['subject'] = $redis->Get('Q_SUBJ'); - $settings['sender'] = $redis->Get('Q_SENDER'); - $settings['bcc'] = $redis->Get('Q_BCC'); - $settings['redirect'] = $redis->Get('Q_REDIRECT'); - $settings['html_tmpl'] = htmlspecialchars($redis->Get('Q_HTML')); - if (empty($settings['html_tmpl'])) { - $settings['html_tmpl'] = htmlspecialchars(file_get_contents("/tpls/quarantine.tpl")); - } - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('redis_error', $e) - ); - return false; - } - return $settings; - break; - case 'details': - if (!is_numeric($_data) || empty($_data)) { - return false; - } - $stmt = $pdo->prepare('SELECT * FROM `quarantine` WHERE `id`= :id'); - $stmt->execute(array(':id' => $_data)); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt']) || $_SESSION['mailcow_cc_role'] == 'admin') { - return $row; - } - logger(array('return' => array( - array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ) - ))); - return false; - break; - case 'hash_details': - $hash = trim($_data); - if (preg_match("/^([a-f0-9]{64})$/", $hash) === false) { - logger(array('return' => array( - array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ) - ))); - return false; - } - $stmt = $pdo->prepare('SELECT * FROM `quarantine` WHERE SHA2(CONCAT(`id`, `qid`), 256) = :hash'); - $stmt->execute(array(':hash' => $hash)); - return $stmt->fetch(PDO::FETCH_ASSOC); - break; - } -} + array( + array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ) + ))); + return false; + } + $stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt` + WHERE SHA2(CONCAT(`id`, `qid`), 256) = :hash + AND user_acl.quarantine = 1 + AND rcpt IN (SELECT username FROM mailbox)'); + $stmt->execute(array(':hash' => $hash)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (empty($row['id']) || !is_numeric($row['id'])) { + logger(array('return' => array( + array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ) + ))); + return false; + } + else { + $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE id = :id"); + $stmt->execute(array( + ':id' => $row['id'] + )); + } + logger(array('return' => array( + array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('item_deleted', $row['id']) + ) + ))); + break; + case 'quick_release': + // Dont return results, just log + $hash = trim($_data); + if (preg_match("/^([a-f0-9]{64})$/", $hash) === false) { + logger(array('return' => array( + array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ) + ))); + return false; + } + $stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt` + WHERE SHA2(CONCAT(`id`, `qid`), 256) = :hash + AND `user_acl`.`quarantine` = 1 + AND `username` IN (SELECT `username` FROM `mailbox`)'); + $stmt->execute(array(':hash' => $hash)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (empty($row['id']) || !is_numeric($row['id'])) { + logger(array('return' => array( + array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ) + ))); + return false; + } + else { + $stmt = $pdo->prepare('SELECT `msg`, `qid`, `sender`, `rcpt` FROM `quarantine` WHERE `id` = :id'); + $stmt->execute(array(':id' => $row['id'])); + $detail_row = $stmt->fetch(PDO::FETCH_ASSOC); + $sender = !empty($detail_row['sender']) ? $detail_row['sender'] : 'sender-unknown@rspamd'; + if (!empty(gethostbynamel('postfix-mailcow'))) { + $postfix = 'postfix-mailcow'; + } + if (!empty(gethostbynamel('postfix'))) { + $postfix = 'postfix'; + } + else { + logger(array('return' => array( + array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('release_send_failed', 'Cannot determine Postfix host') + ) + ))); + return false; + } + try { + $release_format = $redis->Get('Q_RELEASE_FORMAT'); + } + catch (RedisException $e) { + logger(array('return' => array( + array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ) + ))); + return false; + } + if ($release_format == 'attachment') { + try { + $mail = new PHPMailer(true); + $mail->isSMTP(); + $mail->SMTPDebug = 0; + $mail->SMTPOptions = array( + 'ssl' => array( + 'verify_peer' => false, + 'verify_peer_name' => false, + 'allow_self_signed' => true + ) + ); + if (!empty(gethostbynamel('postfix-mailcow'))) { + $postfix = 'postfix-mailcow'; + } + if (!empty(gethostbynamel('postfix'))) { + $postfix = 'postfix'; + } + else { + logger(array('return' => array( + array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('release_send_failed', 'Cannot determine Postfix host') + ) + ))); + return false; + } + $mail->Host = $postfix; + $mail->Port = 590; + $mail->setFrom($sender); + $mail->CharSet = 'UTF-8'; + $mail->Subject = sprintf($lang['quarantine']['release_subject'], $detail_row['qid']); + $mail->addAddress($detail_row['rcpt']); + $mail->IsHTML(false); + $msg_tmpf = tempnam("/tmp", $detail_row['qid']); + file_put_contents($msg_tmpf, $detail_row['msg']); + $mail->addAttachment($msg_tmpf, $detail_row['qid'] . '.eml'); + $mail->Body = sprintf($lang['quarantine']['release_body']); + $mail->send(); + unlink($msg_tmpf); + } + catch (phpmailerException $e) { + unlink($msg_tmpf); + logger(array('return' => array( + array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('release_send_failed', $e->errorMessage()) + ) + ))); + return false; + } + } + elseif ($release_format == 'raw') { + $detail_row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/', 'X-Pre-Release-Spam-Flag $1', $detail_row['msg']); + $postfix_talk = array( + array('220', 'HELO quarantine' . chr(10)), + array('250', 'MAIL FROM: ' . $sender . chr(10)), + array('250', 'RCPT TO: ' . $detail_row['rcpt'] . chr(10)), + array('250', 'DATA' . chr(10)), + array('354', $detail_row['msg'] . chr(10) . '.' . chr(10)), + array('250', 'QUIT' . chr(10)), + array('221', '') + ); + // Thanks to https://stackoverflow.com/questions/6632399/given-an-email-as-raw-text-how-can-i-send-it-using-php + $smtp_connection = fsockopen($postfix, 590, $errno, $errstr, 1); + if (!$smtp_connection) { + logger(array('return' => array( + array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'Cannot connect to Postfix' + ) + ))); + return false; + } + for ($i=0; $i < count($postfix_talk); $i++) { + $smtp_resource = fgets($smtp_connection, 256); + if (substr($smtp_resource, 0, 3) !== $postfix_talk[$i][0]) { + $ret = substr($smtp_resource, 0, 3); + $ret = (empty($ret)) ? '-' : $ret; + logger(array('return' => array( + array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'Postfix returned SMTP code ' . $smtp_resource . ', expected ' . $postfix_talk[$i][0] + ) + ))); + return false; + } + if ($postfix_talk[$i][1] !== '') { + fputs($smtp_connection, $postfix_talk[$i][1]); + } + } + fclose($smtp_connection); + } + $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE id = :id"); + $stmt->execute(array( + ':id' => $row['id'] + )); + } + logger(array('return' => array( + array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('item_released', $hash) + ) + ))); + break; + case 'delete': + if (!is_array($_data['id'])) { + $ids = array(); + $ids[] = $_data['id']; + } + else { + $ids = $_data['id']; + } + if (!isset($_SESSION['acl']['quarantine']) || $_SESSION['acl']['quarantine'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + foreach ($ids as $id) { + if (!is_numeric($id)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare('SELECT `rcpt` FROM `quarantine` WHERE `id` = :id'); + $stmt->execute(array(':id' => $id)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt']) && $_SESSION['mailcow_cc_role'] != 'admin') { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + continue; + } + else { + $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE `id` = :id"); + $stmt->execute(array( + ':id' => $id + )); + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('item_deleted', $id) + ); + } + break; + case 'edit': + if (!isset($_SESSION['acl']['quarantine']) || $_SESSION['acl']['quarantine'] != "1" ) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + // Edit settings + if ($_data['action'] == 'settings') { + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $retention_size = $_data['retention_size']; + if ($_data['release_format'] == 'attachment' || $_data['release_format'] == 'raw') { + $release_format = $_data['release_format']; + } + else { + $release_format = 'raw'; + } + $max_size = $_data['max_size']; + if ($_data['max_score'] == "") { + $max_score = ''; + } + else { + $max_score = floatval($_data['max_score']); + } + $max_age = intval($_data['max_age']); + $subject = $_data['subject']; + if (!filter_var($_data['bcc'], FILTER_VALIDATE_EMAIL)) { + $bcc = ''; + } + else { + $bcc = $_data['bcc']; + } + if (!filter_var($_data['redirect'], FILTER_VALIDATE_EMAIL)) { + $redirect = ''; + } + else { + $redirect = $_data['redirect']; + } + if (!filter_var($_data['sender'], FILTER_VALIDATE_EMAIL)) { + $sender = ''; + } + else { + $sender = $_data['sender']; + } + $html = $_data['html_tmpl']; + if ($max_age <= 0) { + $max_age = 365; + } + $exclude_domains = (array)$_data['exclude_domains']; + try { + $redis->Set('Q_RETENTION_SIZE', intval($retention_size)); + $redis->Set('Q_MAX_SIZE', intval($max_size)); + $redis->Set('Q_MAX_SCORE', $max_score); + $redis->Set('Q_MAX_AGE', $max_age); + $redis->Set('Q_EXCLUDE_DOMAINS', json_encode($exclude_domains)); + $redis->Set('Q_RELEASE_FORMAT', $release_format); + $redis->Set('Q_SENDER', $sender); + $redis->Set('Q_BCC', $bcc); + $redis->Set('Q_REDIRECT', $redirect); + $redis->Set('Q_SUBJ', $subject); + $redis->Set('Q_HTML', $html); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'saved_settings' + ); + } + // Release item + elseif ($_data['action'] == 'release' || $_data['action'] == 'learnham') { + if (!is_array($_data['id'])) { + $ids = array(); + $ids[] = $_data['id']; + } + else { + $ids = $_data['id']; + } + foreach ($ids as $id) { + if (!is_numeric($id)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare('SELECT `msg`, `action`, `qid`, `sender`, `rcpt` FROM `quarantine` WHERE `id` = :id'); + $stmt->execute(array(':id' => $id)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt']) && $_SESSION['mailcow_cc_role'] != 'admin' || empty($row['rcpt'])) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + continue; + } + $sender = !empty($row['sender']) ? $row['sender'] : 'sender-unknown@rspamd'; + if (!empty(gethostbynamel('postfix-mailcow'))) { + $postfix = 'postfix-mailcow'; + } + if (!empty(gethostbynamel('postfix'))) { + $postfix = 'postfix'; + } + else { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('release_send_failed', 'Cannot determine Postfix host') + ); + continue; + } + try { + $release_format = $redis->Get('Q_RELEASE_FORMAT'); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + if ($release_format == 'attachment') { + try { + $mail = new PHPMailer(true); + $mail->isSMTP(); + $mail->SMTPDebug = 0; + $mail->SMTPOptions = array( + 'ssl' => array( + 'verify_peer' => false, + 'verify_peer_name' => false, + 'allow_self_signed' => true + ) + ); + if (!empty(gethostbynamel('postfix-mailcow'))) { + $postfix = 'postfix-mailcow'; + } + if (!empty(gethostbynamel('postfix'))) { + $postfix = 'postfix'; + } + else { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('release_send_failed', 'Cannot determine Postfix host') + ); + continue; + } + $mail->Host = $postfix; + $mail->Port = 590; + $mail->setFrom($sender); + $mail->CharSet = 'UTF-8'; + $mail->Subject = sprintf($lang['quarantine']['release_subject'], $row['qid']); + $mail->addAddress($row['rcpt']); + $mail->IsHTML(false); + $msg_tmpf = tempnam("/tmp", $row['qid']); + file_put_contents($msg_tmpf, $row['msg']); + $mail->addAttachment($msg_tmpf, $row['qid'] . '.eml'); + $mail->Body = sprintf($lang['quarantine']['release_body']); + $mail->send(); + unlink($msg_tmpf); + } + catch (phpmailerException $e) { + unlink($msg_tmpf); + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('release_send_failed', $e->errorMessage()) + ); + continue; + } + } + elseif ($release_format == 'raw') { + $row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/', 'X-Pre-Release-Spam-Flag $1', $row['msg']); + $postfix_talk = array( + array('220', 'HELO quarantine' . chr(10)), + array('250', 'MAIL FROM: ' . $sender . chr(10)), + array('250', 'RCPT TO: ' . $row['rcpt'] . chr(10)), + array('250', 'DATA' . chr(10)), + array('354', str_replace("\n.", '', $row['msg']) . chr(10) . '.' . chr(10)), + array('250', 'QUIT' . chr(10)), + array('221', '') + ); + // Thanks to https://stackoverflow.com/questions/6632399/given-an-email-as-raw-text-how-can-i-send-it-using-php + $smtp_connection = fsockopen($postfix, 590, $errno, $errstr, 1); + if (!$smtp_connection) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'Cannot connect to Postfix' + ); + return false; + } + for ($i=0; $i < count($postfix_talk); $i++) { + $smtp_resource = fgets($smtp_connection, 256); + if (substr($smtp_resource, 0, 3) !== $postfix_talk[$i][0]) { + $ret = substr($smtp_resource, 0, 3); + $ret = (empty($ret)) ? '-' : $ret; + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'Postfix returned SMTP code ' . $smtp_resource . ', expected ' . $postfix_talk[$i][0] + ); + return false; + } + if ($postfix_talk[$i][1] !== '') { + fputs($smtp_connection, $postfix_talk[$i][1]); + } + } + fclose($smtp_connection); + } + $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE `id` = :id"); + $stmt->execute(array( + ':id' => $id + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('item_released', $id) + ); + // Item was released and deleted from quarantine, now learning ham + $curl = curl_init(); + curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_TIMEOUT, 30); + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain')); + curl_setopt($curl, CURLOPT_URL,"http://rspamd/learnham"); + curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']); + $response = curl_exec($curl); + if (!curl_errno($curl)) { + $response = json_decode($response, true); + if (isset($response['error'])) { + if (stripos($response['error'], 'already learned') === false) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('ham_learn_error', $response['error']) + ); + continue; + } + } + curl_close($curl); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_TIMEOUT, 30); + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain', 'Flag: 11')); + curl_setopt($curl, CURLOPT_URL,"http://rspamd/fuzzydel"); + curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']); + // It is most likely not a spam hash, so we ignore any error/warning response + // $response = curl_exec($curl); + if (!curl_errno($curl)) { + curl_close($curl); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_TIMEOUT, 30); + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain', 'Flag: 13')); + curl_setopt($curl, CURLOPT_URL,"http://rspamd/fuzzyadd"); + curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']); + $response = curl_exec($curl); + curl_exec($curl); + if (!curl_errno($curl)) { + $response = json_decode($response, true); + if (isset($response['error'])) { + if (stripos($response['error'], 'No content to generate fuzzy') === false) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('fuzzy_learn_error', $response['error']) + ); + } + } + } + curl_close($curl); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('learned_ham', $id) + ); + continue; + } + else { + curl_close($curl); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('ham_learn_error', 'Curl: ' . curl_strerror(curl_errno($curl))) + ); + continue; + } + curl_close($curl); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('ham_learn_error', 'unknown') + ); + continue; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('ham_learn_error', 'Curl: ' . curl_strerror(curl_errno($curl))) + ); + curl_close($curl); + continue; + } + curl_close($curl); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('ham_learn_error', 'unknown') + ); + continue; + } + } + elseif ($_data['action'] == 'learnspam') { + if (!is_array($_data['id'])) { + $ids = array(); + $ids[] = $_data['id']; + } + else { + $ids = $_data['id']; + } + foreach ($ids as $id) { + if (!is_numeric($id)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare('SELECT `msg`, `rcpt`, `action` FROM `quarantine` WHERE `id` = :id'); + $stmt->execute(array(':id' => $id)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt']) && $_SESSION['mailcow_cc_role'] != 'admin' || empty($row['rcpt'])) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + continue; + } + $stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE `id` = :id"); + $stmt->execute(array( + ':id' => $id + )); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_TIMEOUT, 30); + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain')); + curl_setopt($curl, CURLOPT_URL,"http://rspamd/learnspam"); + curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']); + $response = curl_exec($curl); + if (!curl_errno($curl)) { + $response = json_decode($response, true); + if (isset($response['error'])) { + if (stripos($response['error'], 'already learned') === false) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('spam_learn_error', $response['error']) + ); + continue; + } + } + curl_close($curl); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_TIMEOUT, 30); + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain', 'Flag: 13')); + curl_setopt($curl, CURLOPT_URL,"http://rspamd/fuzzydel"); + curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']); + // It is most likely not a spam hash, so we ignore any error/warning response + // $response = curl_exec($curl); + if (!curl_errno($curl)) { + curl_close($curl); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_TIMEOUT, 30); + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/plain', 'Flag: 11')); + curl_setopt($curl, CURLOPT_URL,"http://rspamd/fuzzyadd"); + curl_setopt($curl, CURLOPT_POSTFIELDS, $row['msg']); + $response = curl_exec($curl); + curl_exec($curl); + if (!curl_errno($curl)) { + $response = json_decode($response, true); + if (isset($response['error'])) { + if (stripos($response['error'], 'No content to generate fuzzy') === false) { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('fuzzy_learn_error', $response['error']) + ); + } + } + } + curl_close($curl); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('qlearn_spam', $id) + ); + continue; + } + else { + curl_close($curl); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('spam_learn_error', 'Curl: ' . curl_strerror(curl_errno($curl))) + ); + continue; + } + curl_close($curl); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('spam_learn_error', 'unknown') + ); + continue; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('spam_learn_error', 'Curl: ' . curl_strerror(curl_errno($curl))) + ); + curl_close($curl); + continue; + } + curl_close($curl); + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('spam_learn_error', 'unknown') + ); + continue; + } + } + return true; + break; + case 'get': + if ($_SESSION['mailcow_cc_role'] == "user") { + $stmt = $pdo->prepare('SELECT `id`, `qid`, `subject`, LOCATE("VIRUS_FOUND", `symbols`) AS `virus_flag`, `score`, `rcpt`, `sender`, `action`, UNIX_TIMESTAMP(`created`) AS `created`, `notified` FROM `quarantine` WHERE `rcpt` = :mbox'); + $stmt->execute(array(':mbox' => $_SESSION['mailcow_cc_username'])); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $q_meta[] = $row; + } + } + elseif ($_SESSION['mailcow_cc_role'] == "admin") { + $stmt = $pdo->query('SELECT `id`, `qid`, `subject`, LOCATE("VIRUS_FOUND", `symbols`) AS `virus_flag`, `score`, `rcpt`, `sender`, `action`, UNIX_TIMESTAMP(`created`) AS `created`, `notified` FROM `quarantine`'); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $q_meta[] = $row; + } + } + else { + $domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains')); + foreach ($domains as $domain) { + $stmt = $pdo->prepare('SELECT `id`, `qid`, `subject`, LOCATE("VIRUS_FOUND", `symbols`) AS `virus_flag`, `score`, `rcpt`, `sender`, `action`, UNIX_TIMESTAMP(`created`) AS `created`, `notified` FROM `quarantine` WHERE `rcpt` REGEXP :domain'); + $stmt->execute(array(':domain' => '@' . $domain . '$')); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $q_meta[] = $row; + } + } + } + return $q_meta; + break; + case 'settings': + try { + if ($_SESSION['mailcow_cc_role'] == "admin") { + $settings['exclude_domains'] = json_decode($redis->Get('Q_EXCLUDE_DOMAINS'), true); + } + $settings['max_size'] = $redis->Get('Q_MAX_SIZE'); + $settings['max_score'] = $redis->Get('Q_MAX_SCORE'); + $settings['max_age'] = $redis->Get('Q_MAX_AGE'); + $settings['retention_size'] = $redis->Get('Q_RETENTION_SIZE'); + $settings['release_format'] = $redis->Get('Q_RELEASE_FORMAT'); + $settings['subject'] = $redis->Get('Q_SUBJ'); + $settings['sender'] = $redis->Get('Q_SENDER'); + $settings['bcc'] = $redis->Get('Q_BCC'); + $settings['redirect'] = $redis->Get('Q_REDIRECT'); + $settings['html_tmpl'] = htmlspecialchars($redis->Get('Q_HTML')); + if (empty($settings['html_tmpl'])) { + $settings['html_tmpl'] = htmlspecialchars(file_get_contents("/tpls/quarantine.tpl")); + } + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + return $settings; + break; + case 'details': + if (!is_numeric($_data) || empty($_data)) { + return false; + } + $stmt = $pdo->prepare('SELECT * FROM `quarantine` WHERE `id`= :id'); + $stmt->execute(array(':id' => $_data)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if (hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['rcpt']) || $_SESSION['mailcow_cc_role'] == 'admin') { + return $row; + } + logger(array('return' => array( + array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ) + ))); + return false; + break; + case 'hash_details': + $hash = trim($_data); + if (preg_match("/^([a-f0-9]{64})$/", $hash) === false) { + logger(array('return' => array( + array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ) + ))); + return false; + } + $stmt = $pdo->prepare('SELECT * FROM `quarantine` WHERE SHA2(CONCAT(`id`, `qid`), 256) = :hash'); + $stmt->execute(array(':hash' => $hash)); + return $stmt->fetch(PDO::FETCH_ASSOC); + break; + } +} diff --git a/data/web/inc/functions.quota_notification.inc.php b/data/web/inc/functions.quota_notification.inc.php index 9f58bfb4..91ff5bca 100644 --- a/data/web/inc/functions.quota_notification.inc.php +++ b/data/web/inc/functions.quota_notification.inc.php @@ -1,150 +1,150 @@ - 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - switch ($_action) { - case 'edit': - $retention_size = $_data['retention_size']; - if ($_data['release_format'] == 'attachment' || $_data['release_format'] == 'raw') { - $release_format = $_data['release_format']; - } - else { - $release_format = 'raw'; - } - $subject = $_data['subject']; - $sender = preg_replace('/[\x00-\x1F\x80-\xFF]/', '', $_data['sender']); - if (filter_var($sender, FILTER_VALIDATE_EMAIL) === false) { - $sender = ''; - } - $html = $_data['html_tmpl']; - try { - $redis->Set('QW_SENDER', $sender); - $redis->Set('QW_SUBJ', $subject); - $redis->Set('QW_HTML', $html); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('redis_error', $e) - ); - return false; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'saved_settings' - ); - break; - case 'get': - try { - $settings['subject'] = $redis->Get('QW_SUBJ'); - $settings['sender'] = $redis->Get('QW_SENDER'); - $settings['html_tmpl'] = htmlspecialchars($redis->Get('QW_HTML')); - if (empty($settings['html_tmpl'])) { - $settings['html_tmpl'] = htmlspecialchars(file_get_contents("/tpls/quota.tpl")); - } - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('redis_error', $e) - ); - return false; - } - return $settings; - break; - } -} -function quota_notification_bcc($_action, $_data = null) { - global $redis; - $_data_log = $_data; - if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - switch ($_action) { - case 'edit': - $domain = $_data['domain']; - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $active = intval($_data['active']); - $bcc_rcpts = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['bcc_rcpt'])); - foreach ($bcc_rcpts as $i => &$rcpt) { - $rcpt = preg_replace('/[\x00-\x1F\x80-\xFF]/', '', $rcpt); - if (!empty($rcpt) && filter_var($rcpt, FILTER_VALIDATE_EMAIL) === false) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('goto_invalid', htmlspecialchars($rcpt)) - ); - unset($bcc_rcpts[$i]); - continue; - } - } - $bcc_rcpts = array_unique($bcc_rcpts); - $bcc_rcpts = array_filter($bcc_rcpts); - if (empty($bcc_rcpts)) { - $active = 0; - - } - try { - $redis->hSet('QW_BCC', $domain, json_encode(array('bcc_rcpts' => $bcc_rcpts, 'active' => $active))); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('redis_error', $e) - ); - return false; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'saved_settings' - ); - break; - case 'get': - $domain = $_data; - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - try { - return json_decode($redis->hGet('QW_BCC', $domain), true); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('redis_error', $e) - ); - return false; - } - break; - } -} + 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + switch ($_action) { + case 'edit': + $retention_size = $_data['retention_size']; + if ($_data['release_format'] == 'attachment' || $_data['release_format'] == 'raw') { + $release_format = $_data['release_format']; + } + else { + $release_format = 'raw'; + } + $subject = $_data['subject']; + $sender = preg_replace('/[\x00-\x1F\x80-\xFF]/', '', $_data['sender']); + if (filter_var($sender, FILTER_VALIDATE_EMAIL) === false) { + $sender = ''; + } + $html = $_data['html_tmpl']; + try { + $redis->Set('QW_SENDER', $sender); + $redis->Set('QW_SUBJ', $subject); + $redis->Set('QW_HTML', $html); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'saved_settings' + ); + break; + case 'get': + try { + $settings['subject'] = $redis->Get('QW_SUBJ'); + $settings['sender'] = $redis->Get('QW_SENDER'); + $settings['html_tmpl'] = htmlspecialchars($redis->Get('QW_HTML')); + if (empty($settings['html_tmpl'])) { + $settings['html_tmpl'] = htmlspecialchars(file_get_contents("/tpls/quota.tpl")); + } + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + return $settings; + break; + } +} +function quota_notification_bcc($_action, $_data = null) { + global $redis; + $_data_log = $_data; + if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + switch ($_action) { + case 'edit': + $domain = $_data['domain']; + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $active = intval($_data['active']); + $bcc_rcpts = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['bcc_rcpt'])); + foreach ($bcc_rcpts as $i => &$rcpt) { + $rcpt = preg_replace('/[\x00-\x1F\x80-\xFF]/', '', $rcpt); + if (!empty($rcpt) && filter_var($rcpt, FILTER_VALIDATE_EMAIL) === false) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('goto_invalid', htmlspecialchars($rcpt)) + ); + unset($bcc_rcpts[$i]); + continue; + } + } + $bcc_rcpts = array_unique($bcc_rcpts); + $bcc_rcpts = array_filter($bcc_rcpts); + if (empty($bcc_rcpts)) { + $active = 0; + + } + try { + $redis->hSet('QW_BCC', $domain, json_encode(array('bcc_rcpts' => $bcc_rcpts, 'active' => $active))); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'saved_settings' + ); + break; + case 'get': + $domain = $_data; + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + try { + return json_decode($redis->hGet('QW_BCC', $domain), true); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + break; + } +} diff --git a/data/web/inc/functions.ratelimit.inc.php b/data/web/inc/functions.ratelimit.inc.php index f9e7a71a..d7ef6e0c 100644 --- a/data/web/inc/functions.ratelimit.inc.php +++ b/data/web/inc/functions.ratelimit.inc.php @@ -1,242 +1,242 @@ - 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - switch ($_scope) { - case 'domain': - if (!is_array($_data['object'])) { - $objects = array(); - $objects[] = $_data['object']; - } - else { - $objects = $_data['object']; - } - foreach ($objects as $object) { - $rl_value = intval($_data['rl_value']); - $rl_frame = $_data['rl_frame']; - if (!in_array($rl_frame, array('s', 'm', 'h', 'd'))) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'rl_timeframe' - ); - continue; - } - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'access_denied' - ); - continue; - } - if (empty($rl_value)) { - try { - $redis->hDel('RL_VALUE', $object); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => array('redis_error', $e) - ); - continue; - } - } - else { - try { - $redis->hSet('RL_VALUE', $object, $rl_value . ' / 1' . $rl_frame); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => array('redis_error', $e) - ); - continue; - } - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => array('rl_saved', $object) - ); - } - break; - case 'mailbox': - if (!is_array($_data['object'])) { - $objects = array(); - $objects[] = $_data['object']; - } - else { - $objects = $_data['object']; - } - foreach ($objects as $object) { - $rl_value = intval($_data['rl_value']); - $rl_frame = $_data['rl_frame']; - if (!in_array($rl_frame, array('s', 'm', 'h', 'd'))) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'rl_timeframe' - ); - continue; - } - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object) - || ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin')) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'access_denied' - ); - continue; - } - if (empty($rl_value)) { - try { - $redis->hDel('RL_VALUE', $object); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => array('redis_error', $e) - ); - continue; - } - } - else { - try { - $redis->hSet('RL_VALUE', $object, $rl_value . ' / 1' . $rl_frame); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => array('redis_error', $e) - ); - continue; - } - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => array('rl_saved', $object) - ); - } - break; - } - break; - case 'get': - switch ($_scope) { - case 'domain': - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - return false; - } - try { - if ($rl_value = $redis->hGet('RL_VALUE', $_data)) { - $rl = explode(' / 1', $rl_value); - $data['value'] = $rl[0]; - $data['frame'] = $rl[1]; - return $data; - } - else { - return false; - } - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => array('redis_error', $e) - ); - return false; - } - return false; - break; - case 'mailbox': - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data) - || ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin')) { - return false; - } - try { - if ($rl_value = $redis->hGet('RL_VALUE', $_data)) { - $rl = explode(' / 1', $rl_value); - $data['value'] = $rl[0]; - $data['frame'] = $rl[1]; - return $data; - } - else { - return false; - } - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => array('redis_error', $e) - ); - return false; - } - return false; - break; - } - break; - case 'delete': - $data['hash'] = $_data; - if ($_SESSION['mailcow_cc_role'] != 'admin' || !preg_match('/^RL[0-9A-Za-z=]+$/i', trim($data['hash']))) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - try { - $data_rllog = $redis->lRange('RL_LOG', 0, -1); - if ($data_rllog) { - foreach ($data_rllog as $json_line) { - if (preg_match('/' . $data['hash'] . '/i', $json_line)) { - $redis->lRem('RL_LOG', $json_line, 0); - } - } - } - if ($redis->type($data['hash']) == Redis::REDIS_HASH) { - $redis->delete($data['hash']); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'hash_deleted' - ); - return true; - } - else { - $_SESSION['return'][] = array( - 'type' => 'warning', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => 'hash_not_found' - ); - return false; - } - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), - 'msg' => array('redis_error', $e) - ); - return false; - } - return false; - break; - } + 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + switch ($_scope) { + case 'domain': + if (!is_array($_data['object'])) { + $objects = array(); + $objects[] = $_data['object']; + } + else { + $objects = $_data['object']; + } + foreach ($objects as $object) { + $rl_value = intval($_data['rl_value']); + $rl_frame = $_data['rl_frame']; + if (!in_array($rl_frame, array('s', 'm', 'h', 'd'))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'rl_timeframe' + ); + continue; + } + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'access_denied' + ); + continue; + } + if (empty($rl_value)) { + try { + $redis->hDel('RL_VALUE', $object); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('redis_error', $e) + ); + continue; + } + } + else { + try { + $redis->hSet('RL_VALUE', $object, $rl_value . ' / 1' . $rl_frame); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('redis_error', $e) + ); + continue; + } + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('rl_saved', $object) + ); + } + break; + case 'mailbox': + if (!is_array($_data['object'])) { + $objects = array(); + $objects[] = $_data['object']; + } + else { + $objects = $_data['object']; + } + foreach ($objects as $object) { + $rl_value = intval($_data['rl_value']); + $rl_frame = $_data['rl_frame']; + if (!in_array($rl_frame, array('s', 'm', 'h', 'd'))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'rl_timeframe' + ); + continue; + } + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object) + || ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin')) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'access_denied' + ); + continue; + } + if (empty($rl_value)) { + try { + $redis->hDel('RL_VALUE', $object); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('redis_error', $e) + ); + continue; + } + } + else { + try { + $redis->hSet('RL_VALUE', $object, $rl_value . ' / 1' . $rl_frame); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('redis_error', $e) + ); + continue; + } + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('rl_saved', $object) + ); + } + break; + } + break; + case 'get': + switch ($_scope) { + case 'domain': + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + return false; + } + try { + if ($rl_value = $redis->hGet('RL_VALUE', $_data)) { + $rl = explode(' / 1', $rl_value); + $data['value'] = $rl[0]; + $data['frame'] = $rl[1]; + return $data; + } + else { + return false; + } + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + return false; + break; + case 'mailbox': + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data) + || ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin')) { + return false; + } + try { + if ($rl_value = $redis->hGet('RL_VALUE', $_data)) { + $rl = explode(' / 1', $rl_value); + $data['value'] = $rl[0]; + $data['frame'] = $rl[1]; + return $data; + } + else { + return false; + } + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + return false; + break; + } + break; + case 'delete': + $data['hash'] = $_data; + if ($_SESSION['mailcow_cc_role'] != 'admin' || !preg_match('/^RL[0-9A-Za-z=]+$/i', trim($data['hash']))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + return false; + } + try { + $data_rllog = $redis->lRange('RL_LOG', 0, -1); + if ($data_rllog) { + foreach ($data_rllog as $json_line) { + if (preg_match('/' . $data['hash'] . '/i', $json_line)) { + $redis->lRem('RL_LOG', $json_line, 0); + } + } + } + if ($redis->type($data['hash']) == Redis::REDIS_HASH) { + $redis->delete($data['hash']); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'hash_deleted' + ); + return true; + } + else { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'hash_not_found' + ); + return false; + } + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + return false; + break; + } } \ No newline at end of file diff --git a/data/web/inc/functions.rspamd.inc.php b/data/web/inc/functions.rspamd.inc.php index fd1c5bd6..4a29fade 100644 --- a/data/web/inc/functions.rspamd.inc.php +++ b/data/web/inc/functions.rspamd.inc.php @@ -1,211 +1,211 @@ - 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $content = $_data['content']; - $desc = $_data['desc']; - $active = intval($_data['active']); - if (empty($content)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'map_content_empty' - ); - return false; - } - $stmt = $pdo->prepare("INSERT INTO `settingsmap` (`content`, `desc`, `active`) - VALUES (:content, :desc, :active)"); - $stmt->execute(array( - ':content' => $content, - ':desc' => $desc, - ':active' => $active - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'settings_map_added' - ); - break; - case 'edit': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $ids = (array)$_data['id']; - foreach ($ids as $id) { - $is_now = rsettings('details', $id); - if (!empty($is_now)) { - $content = (!empty($_data['content'])) ? $_data['content'] : $is_now['content']; - $desc = (!empty($_data['desc'])) ? $_data['desc'] : $is_now['desc']; - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('settings_map_invalid', $id) - ); - continue; - } - $content = trim($content); - $stmt = $pdo->prepare("UPDATE `settingsmap` SET - `content` = :content, - `desc` = :desc, - `active` = :active - WHERE `id` = :id"); - $stmt->execute(array( - ':content' => $content, - ':desc' => $desc, - ':active' => $active, - ':id' => $id - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('object_modified', htmlspecialchars(implode(',', $ids))) - ); - } - break; - case 'delete': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $ids = (array)$_data['id']; - foreach ($ids as $id) { - $stmt = $pdo->prepare("DELETE FROM `settingsmap` WHERE `id`= :id"); - $stmt->execute(array(':id' => $id)); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('settings_map_removed', htmlspecialchars($id)) - ); - } - break; - case 'get': - if ($_SESSION['mailcow_cc_role'] != "admin") { - return false; - } - $settingsmaps = array(); - $stmt = $pdo->query("SELECT `id`, `desc`, `active` FROM `settingsmap`"); - $settingsmaps = $stmt->fetchAll(PDO::FETCH_ASSOC); - return $settingsmaps; - break; - case 'details': - if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) { - return false; - } - $settingsmapdata = array(); - $stmt = $pdo->prepare("SELECT `id`, - `desc`, - `content`, - `active` - FROM `settingsmap` - WHERE `id` = :id"); - $stmt->execute(array(':id' => $_data)); - $settingsmapdata = $stmt->fetch(PDO::FETCH_ASSOC); - return $settingsmapdata; - break; - } -} -function rspamd_maps($_action, $_data = null) { - global $pdo; - global $lang; - global $RSPAMD_MAPS; - $_data_log = $_data; - switch ($_action) { - case 'edit': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, '-'), - 'msg' => 'access_denied' - ); - return false; - } - $maps = (array)$_data['map']; - foreach ($maps as $map) { - foreach ($RSPAMD_MAPS as $rspamd_map_type) { - if (!in_array($map, $rspamd_map_type)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, '-'), - 'msg' => array('global_map_invalid', $map) - ); - continue; - } - } - try { - if (file_exists('/rspamd_custom_maps/' . $map)) { - $map_content = trim($_data['rspamd_map_data']); - $map_handle = fopen('/rspamd_custom_maps/' . $map, 'w'); - if (!$map_handle) { - throw new Exception($lang['danger']['file_open_error']); - } - fwrite($map_handle, $map_content . PHP_EOL); - fclose($map_handle); - sleep(1.5); - touch('/rspamd_custom_maps/' . $map); - } - } - catch (Exception $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, '-'), - 'msg' => array('global_map_write_error', htmlspecialchars($map), htmlspecialchars($e->getMessage())) - ); - continue; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, '-'), - 'msg' => array('object_modified', htmlspecialchars($map)) - ); - } - break; - } -} -function rspamd_actions() { - if (isset($_SESSION["mailcow_cc_role"]) && $_SESSION["mailcow_cc_role"] == "admin") { - $curl = curl_init(); - curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); - curl_setopt($curl, CURLOPT_URL,"http://rspamd/stat"); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); - $data = curl_exec($curl); - if ($data) { - $return = array(); - $stats_array = json_decode($data, true)['actions']; - $stats_array['soft reject'] = $stats_array['soft reject'] + $stats_array['greylist']; - unset($stats_array['greylist']); - foreach ($stats_array as $action => $count) { - $return[] = array($action, $count); - } - return $return; - } - else { - return false; - } - } - else { - return false; - } -} + 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $content = $_data['content']; + $desc = $_data['desc']; + $active = intval($_data['active']); + if (empty($content)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'map_content_empty' + ); + return false; + } + $stmt = $pdo->prepare("INSERT INTO `settingsmap` (`content`, `desc`, `active`) + VALUES (:content, :desc, :active)"); + $stmt->execute(array( + ':content' => $content, + ':desc' => $desc, + ':active' => $active + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'settings_map_added' + ); + break; + case 'edit': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $ids = (array)$_data['id']; + foreach ($ids as $id) { + $is_now = rsettings('details', $id); + if (!empty($is_now)) { + $content = (!empty($_data['content'])) ? $_data['content'] : $is_now['content']; + $desc = (!empty($_data['desc'])) ? $_data['desc'] : $is_now['desc']; + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('settings_map_invalid', $id) + ); + continue; + } + $content = trim($content); + $stmt = $pdo->prepare("UPDATE `settingsmap` SET + `content` = :content, + `desc` = :desc, + `active` = :active + WHERE `id` = :id"); + $stmt->execute(array( + ':content' => $content, + ':desc' => $desc, + ':active' => $active, + ':id' => $id + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('object_modified', htmlspecialchars(implode(',', $ids))) + ); + } + break; + case 'delete': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $ids = (array)$_data['id']; + foreach ($ids as $id) { + $stmt = $pdo->prepare("DELETE FROM `settingsmap` WHERE `id`= :id"); + $stmt->execute(array(':id' => $id)); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('settings_map_removed', htmlspecialchars($id)) + ); + } + break; + case 'get': + if ($_SESSION['mailcow_cc_role'] != "admin") { + return false; + } + $settingsmaps = array(); + $stmt = $pdo->query("SELECT `id`, `desc`, `active` FROM `settingsmap`"); + $settingsmaps = $stmt->fetchAll(PDO::FETCH_ASSOC); + return $settingsmaps; + break; + case 'details': + if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) { + return false; + } + $settingsmapdata = array(); + $stmt = $pdo->prepare("SELECT `id`, + `desc`, + `content`, + `active` + FROM `settingsmap` + WHERE `id` = :id"); + $stmt->execute(array(':id' => $_data)); + $settingsmapdata = $stmt->fetch(PDO::FETCH_ASSOC); + return $settingsmapdata; + break; + } +} +function rspamd_maps($_action, $_data = null) { + global $pdo; + global $lang; + global $RSPAMD_MAPS; + $_data_log = $_data; + switch ($_action) { + case 'edit': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, '-'), + 'msg' => 'access_denied' + ); + return false; + } + $maps = (array)$_data['map']; + foreach ($maps as $map) { + foreach ($RSPAMD_MAPS as $rspamd_map_type) { + if (!in_array($map, $rspamd_map_type)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, '-'), + 'msg' => array('global_map_invalid', $map) + ); + continue; + } + } + try { + if (file_exists('/rspamd_custom_maps/' . $map)) { + $map_content = trim($_data['rspamd_map_data']); + $map_handle = fopen('/rspamd_custom_maps/' . $map, 'w'); + if (!$map_handle) { + throw new Exception($lang['danger']['file_open_error']); + } + fwrite($map_handle, $map_content . PHP_EOL); + fclose($map_handle); + sleep(1.5); + touch('/rspamd_custom_maps/' . $map); + } + } + catch (Exception $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, '-'), + 'msg' => array('global_map_write_error', htmlspecialchars($map), htmlspecialchars($e->getMessage())) + ); + continue; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, '-'), + 'msg' => array('object_modified', htmlspecialchars($map)) + ); + } + break; + } +} +function rspamd_actions() { + if (isset($_SESSION["mailcow_cc_role"]) && $_SESSION["mailcow_cc_role"] == "admin") { + $curl = curl_init(); + curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock'); + curl_setopt($curl, CURLOPT_URL,"http://rspamd/stat"); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + $data = curl_exec($curl); + if ($data) { + $return = array(); + $stats_array = json_decode($data, true)['actions']; + $stats_array['soft reject'] = $stats_array['soft reject'] + $stats_array['greylist']; + unset($stats_array['greylist']); + foreach ($stats_array as $action => $count) { + $return[] = array($action, $count); + } + return $return; + } + else { + return false; + } + } + else { + return false; + } +} diff --git a/data/web/inc/functions.tls_policy_maps.inc.php b/data/web/inc/functions.tls_policy_maps.inc.php index 9d42b2c8..8b472cde 100644 --- a/data/web/inc/functions.tls_policy_maps.inc.php +++ b/data/web/inc/functions.tls_policy_maps.inc.php @@ -1,172 +1,172 @@ - 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => 'tls_policy_map_dest_invalid' - ); - return false; - } - if (!empty($parameters)) { - foreach (explode(' ', $parameters) as $parameter) { - if (!preg_match('/(.+)\=(.+)/i', $parameter)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => 'tls_policy_map_parameter_invalid' - ); - return false; - } - } - } - $active = intval($_data['active']); - $tls_policy_maps = tls_policy_maps('get'); - foreach ($tls_policy_maps as $tls_policy_map) { - if (tls_policy_maps('details', $tls_policy_map)['dest'] == $dest) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('tls_policy_map_entry_exists', htmlspecialchars($dest)) - ); - return false; - } - } - $stmt = $pdo->prepare("INSERT INTO `tls_policy_override` (`dest`, `policy`, `parameters`, `active`) VALUES - (:dest, :policy, :parameters, :active)"); - $stmt->execute(array( - ':dest' => $dest, - ':policy' => $policy, - ':parameters' => $parameters, - ':active' => $active - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('tls_policy_map_entry_saved', htmlspecialchars($dest)) - ); - break; - case 'edit': - $ids = (array)$_data['id']; - foreach ($ids as $id) { - $is_now = tls_policy_maps('details', $id); - if (!empty($is_now)) { - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; - $dest = (!empty($_data['dest'])) ? $_data['dest'] : $is_now['dest']; - $policy = (!empty($_data['policy'])) ? $_data['policy'] : $is_now['policy']; - $parameters = (isset($_data['parameters'])) ? $_data['parameters'] : $is_now['parameters']; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - if (empty($dest) || in_array($dest, array('.', '*', '@'))) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => 'tls_policy_map_dest_invalid' - ); - return false; - } - if (!empty($parameters)) { - foreach (explode(' ', $parameters) as $parameter) { - if (!preg_match('/(.+)\=(.+)/i', $parameter)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => 'tls_policy_map_parameter_invalid' - ); - return false; - } - } - } - $tls_policy_maps = tls_policy_maps('get'); - foreach ($tls_policy_maps as $tls_policy_map) { - if ($tls_policy_map == $id) { continue; } - if (tls_policy_maps('details', $tls_policy_map)['dest'] == $dest) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('recipient_map_entry_exists', htmlspecialchars($dest)) - ); - return false; - } - } - $stmt = $pdo->prepare("UPDATE `tls_policy_override` SET - `dest` = :dest, - `policy` = :policy, - `parameters` = :parameters, - `active` = :active - WHERE `id`= :id"); - $stmt->execute(array( - ':dest' => $dest, - ':policy' => $policy, - ':parameters' => $parameters, - ':active' => $active, - ':id' => $id - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('tls_policy_map_entry_saved', htmlspecialchars($dest)) - ); - } - break; - case 'details': - $mapdata = array(); - $id = intval($_data); - $stmt = $pdo->prepare("SELECT `id`, - `dest`, - `policy`, - `parameters`, - `active` AS `active`, - `created`, - `modified` FROM `tls_policy_override` - WHERE `id` = :id"); - $stmt->execute(array(':id' => $id)); - $mapdata = $stmt->fetch(PDO::FETCH_ASSOC); - return $mapdata; - break; - case 'get': - $mapdata = array(); - $all_items = array(); - $id = intval($_data); - $stmt = $pdo->query("SELECT `id` FROM `tls_policy_override`"); - $all_items = $stmt->fetchAll(PDO::FETCH_ASSOC); - foreach ($all_items as $i) { - $mapdata[] = $i['id']; - } - $all_items = null; - return $mapdata; - break; - case 'delete': - $ids = (array)$_data['id']; - foreach ($ids as $id) { - if (!is_numeric($id)) { - return false; - } - $stmt = $pdo->prepare("DELETE FROM `tls_policy_override` WHERE `id`= :id"); - $stmt->execute(array(':id' => $id)); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data, $_attr), - 'msg' => array('tls_policy_map_entry_deleted', htmlspecialchars($id)) - ); - } - break; - } -} + 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => 'tls_policy_map_dest_invalid' + ); + return false; + } + if (!empty($parameters)) { + foreach (explode(' ', $parameters) as $parameter) { + if (!preg_match('/(.+)\=(.+)/i', $parameter)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => 'tls_policy_map_parameter_invalid' + ); + return false; + } + } + } + $active = intval($_data['active']); + $tls_policy_maps = tls_policy_maps('get'); + foreach ($tls_policy_maps as $tls_policy_map) { + if (tls_policy_maps('details', $tls_policy_map)['dest'] == $dest) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('tls_policy_map_entry_exists', htmlspecialchars($dest)) + ); + return false; + } + } + $stmt = $pdo->prepare("INSERT INTO `tls_policy_override` (`dest`, `policy`, `parameters`, `active`) VALUES + (:dest, :policy, :parameters, :active)"); + $stmt->execute(array( + ':dest' => $dest, + ':policy' => $policy, + ':parameters' => $parameters, + ':active' => $active + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('tls_policy_map_entry_saved', htmlspecialchars($dest)) + ); + break; + case 'edit': + $ids = (array)$_data['id']; + foreach ($ids as $id) { + $is_now = tls_policy_maps('details', $id); + if (!empty($is_now)) { + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; + $dest = (!empty($_data['dest'])) ? $_data['dest'] : $is_now['dest']; + $policy = (!empty($_data['policy'])) ? $_data['policy'] : $is_now['policy']; + $parameters = (isset($_data['parameters'])) ? $_data['parameters'] : $is_now['parameters']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => 'access_denied' + ); + continue; + } + if (empty($dest) || in_array($dest, array('.', '*', '@'))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => 'tls_policy_map_dest_invalid' + ); + return false; + } + if (!empty($parameters)) { + foreach (explode(' ', $parameters) as $parameter) { + if (!preg_match('/(.+)\=(.+)/i', $parameter)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => 'tls_policy_map_parameter_invalid' + ); + return false; + } + } + } + $tls_policy_maps = tls_policy_maps('get'); + foreach ($tls_policy_maps as $tls_policy_map) { + if ($tls_policy_map == $id) { continue; } + if (tls_policy_maps('details', $tls_policy_map)['dest'] == $dest) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('recipient_map_entry_exists', htmlspecialchars($dest)) + ); + return false; + } + } + $stmt = $pdo->prepare("UPDATE `tls_policy_override` SET + `dest` = :dest, + `policy` = :policy, + `parameters` = :parameters, + `active` = :active + WHERE `id`= :id"); + $stmt->execute(array( + ':dest' => $dest, + ':policy' => $policy, + ':parameters' => $parameters, + ':active' => $active, + ':id' => $id + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('tls_policy_map_entry_saved', htmlspecialchars($dest)) + ); + } + break; + case 'details': + $mapdata = array(); + $id = intval($_data); + $stmt = $pdo->prepare("SELECT `id`, + `dest`, + `policy`, + `parameters`, + `active` AS `active`, + `created`, + `modified` FROM `tls_policy_override` + WHERE `id` = :id"); + $stmt->execute(array(':id' => $id)); + $mapdata = $stmt->fetch(PDO::FETCH_ASSOC); + return $mapdata; + break; + case 'get': + $mapdata = array(); + $all_items = array(); + $id = intval($_data); + $stmt = $pdo->query("SELECT `id` FROM `tls_policy_override`"); + $all_items = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($all_items as $i) { + $mapdata[] = $i['id']; + } + $all_items = null; + return $mapdata; + break; + case 'delete': + $ids = (array)$_data['id']; + foreach ($ids as $id) { + if (!is_numeric($id)) { + return false; + } + $stmt = $pdo->prepare("DELETE FROM `tls_policy_override` WHERE `id`= :id"); + $stmt->execute(array(':id' => $id)); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data, $_attr), + 'msg' => array('tls_policy_map_entry_deleted', htmlspecialchars($id)) + ); + } + break; + } +} diff --git a/data/web/inc/functions.transports.inc.php b/data/web/inc/functions.transports.inc.php index 05ad25d8..c92368b0 100644 --- a/data/web/inc/functions.transports.inc.php +++ b/data/web/inc/functions.transports.inc.php @@ -1,508 +1,508 @@ - 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $hostname = trim($_data['hostname']); - $username = str_replace(':', '\:', trim($_data['username'])); - $password = str_replace(':', '\:', trim($_data['password'])); - if (empty($hostname)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('invalid_host', htmlspecialchars($host)) - ); - return false; - } - try { - $stmt = $pdo->prepare("INSERT INTO `relayhosts` (`hostname`, `username` ,`password`, `active`) - VALUES (:hostname, :username, :password, :active)"); - $stmt->execute(array( - ':hostname' => $hostname, - ':username' => $username, - ':password' => str_replace(':', '\:', $password), - ':active' => '1' - )); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('mysql_error', $e) - ); - return false; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('relayhost_added', htmlspecialchars(implode(', ', (array)$hosts))) - ); - break; - case 'edit': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $ids = (array)$_data['id']; - foreach ($ids as $id) { - $is_now = relayhost('details', $id); - if (!empty($is_now)) { - $hostname = (!empty($_data['hostname'])) ? trim($_data['hostname']) : $is_now['hostname']; - $username = (isset($_data['username'])) ? trim($_data['username']) : $is_now['username']; - $password = (isset($_data['password'])) ? trim($_data['password']) : $is_now['password']; - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('relayhost_invalid', $id) - ); - continue; - } - try { - $stmt = $pdo->prepare("UPDATE `relayhosts` SET - `hostname` = :hostname, - `username` = :username, - `password` = :password, - `active` = :active - WHERE `id` = :id"); - $stmt->execute(array( - ':id' => $id, - ':hostname' => $hostname, - ':username' => $username, - ':password' => $password, - ':active' => $active - )); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('mysql_error', $e) - ); - continue; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('object_modified', htmlspecialchars(implode(', ', (array)$hostnames))) - ); - } - break; - case 'delete': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $ids = (array)$_data['id']; - foreach ($ids as $id) { - try { - $stmt = $pdo->prepare("DELETE FROM `relayhosts` WHERE `id`= :id"); - $stmt->execute(array(':id' => $id)); - $stmt = $pdo->prepare("UPDATE `domain` SET `relayhost` = '0' WHERE `relayhost`= :id"); - $stmt->execute(array(':id' => $id)); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('mysql_error', $e) - ); - continue; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('relayhost_removed', htmlspecialchars($id)) - ); - } - break; - case 'get': - if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") { - return false; - } - $relayhosts = array(); - $stmt = $pdo->query("SELECT `id`, `hostname`, `username`, `active` FROM `relayhosts`"); - $relayhosts = $stmt->fetchAll(PDO::FETCH_ASSOC); - return $relayhosts; - break; - case 'details': - if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) { - return false; - } - $relayhostdata = array(); - $stmt = $pdo->prepare("SELECT `id`, - `hostname`, - `username`, - `password`, - `active`, - CONCAT(LEFT(`password`, 3), '...') AS `password_short` - FROM `relayhosts` - WHERE `id` = :id"); - $stmt->execute(array(':id' => $_data)); - $relayhostdata = $stmt->fetch(PDO::FETCH_ASSOC); - if (!empty($relayhostdata)) { - $stmt = $pdo->prepare("SELECT GROUP_CONCAT(`domain` SEPARATOR ', ') AS `used_by_domains` FROM `domain` WHERE `relayhost` = :id"); - $stmt->execute(array(':id' => $_data)); - $used_by_domains = $stmt->fetch(PDO::FETCH_ASSOC)['used_by_domains']; - $used_by_domains = (empty($used_by_domains)) ? '' : $used_by_domains; - $relayhostdata['used_by_domains'] = $used_by_domains; - $stmt = $pdo->prepare("SELECT GROUP_CONCAT(`username` SEPARATOR ', ') AS `used_by_mailboxes` FROM `mailbox` WHERE JSON_VALUE(`attributes`, '$.relayhost') = :id"); - $stmt->execute(array(':id' => $_data)); - $used_by_mailboxes = $stmt->fetch(PDO::FETCH_ASSOC)['used_by_mailboxes']; - $used_by_mailboxes = (empty($used_by_mailboxes)) ? '' : $used_by_mailboxes; - $relayhostdata['used_by_mailboxes'] = $used_by_mailboxes; - } - return $relayhostdata; - break; - } -} -function transport($_action, $_data = null) { - global $pdo; - global $lang; - $_data_log = $_data; - switch ($_action) { - case 'add': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $destinations = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['destination'])); - $active = intval($_data['active']); - $is_mx_based = intval($_data['is_mx_based']); - $nexthop = trim($_data['nexthop']); - if (filter_var($nexthop, FILTER_VALIDATE_IP)) { - $nexthop = '[' . $nexthop . ']'; - } - preg_match('/\[(.+)\].*/', $nexthop, $next_hop_matches); - $next_hop_clean = (isset($next_hop_matches[1])) ? $next_hop_matches[1] : $nexthop; - $username = str_replace(':', '\:', trim($_data['username'])); - $password = str_replace(':', '\:', trim($_data['password'])); - if (empty($nexthop)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('invalid_nexthop') - ); - return false; - } - $transports = transport('get'); - if (!empty($transports)) { - foreach ($transports as $transport) { - $transport_data = transport('details', $transport['id']); - $existing_nh[] = $transport_data['nexthop']; - preg_match('/\[(.+)\].*/', $transport_data['nexthop'], $existing_clean_nh[]); - if (($transport_data['nexthop'] == $nexthop || $transport_data['nexthop'] == $next_hop_clean) && $transport_data['username'] != $username) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'invalid_nexthop_authenticated' - ); - return false; - } - foreach ($destinations as $d_ix => &$dest) { - if (empty($dest)) { - unset($destinations[$d_ix]); - continue; - } - if ($transport_data['destination'] == $dest) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('transport_dest_exists', $dest) - ); - unset($destinations[$d_ix]); - continue; - } - // ".domain" is a valid destination, "..domain" is not - if ($is_mx_based == 0 && (empty($dest) || (is_valid_domain_name(preg_replace('/^' . preg_quote('.', '/') . '/', '', $dest)) === false && $dest != '*' && filter_var($dest, FILTER_VALIDATE_EMAIL) === false))) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('invalid_destination', $dest) - ); - unset($destinations[$d_ix]); - continue; - } - if ($is_mx_based == 1 && (empty($dest) || @preg_match('/' . $dest . '/', null) === false)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('invalid_destination', $dest) - ); - unset($destinations[$d_ix]); - continue; - } - } - } - } - $destinations = array_filter(array_values(array_unique($destinations))); - if (empty($destinations)) { return false; } - if (isset($next_hop_matches[1])) { - if ($existing_nh !== null && in_array($next_hop_clean, $existing_nh)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('next_hop_interferes', $next_hop_clean, $nexthop) - ); - return false; - } - } - else { - foreach ($existing_clean_nh as $existing_clean_nh_each) { - if ($existing_clean_nh_each[1] == $nexthop) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('next_hop_interferes_any', $nexthop) - ); - return false; - } - } - } - foreach ($destinations as $insert_dest) { - $stmt = $pdo->prepare("INSERT INTO `transports` (`nexthop`, `destination`, `is_mx_based`, `username` , `password`, `active`) - VALUES (:nexthop, :destination, :is_mx_based, :username, :password, :active)"); - $stmt->execute(array( - ':nexthop' => $nexthop, - ':destination' => $insert_dest, - ':is_mx_based' => $is_mx_based, - ':username' => $username, - ':password' => str_replace(':', '\:', $password), - ':active' => $active - )); - } - $stmt = $pdo->prepare("UPDATE `transports` SET - `username` = :username, - `password` = :password - WHERE `nexthop` = :nexthop"); - $stmt->execute(array( - ':nexthop' => $nexthop, - ':username' => $username, - ':password' => $password - )); - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('relayhost_added', htmlspecialchars(implode(', ', (array)$hosts))) - ); - break; - case 'edit': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $ids = (array)$_data['id']; - foreach ($ids as $id) { - $is_now = transport('details', $id); - if (!empty($is_now)) { - $destination = (!empty($_data['destination'])) ? trim($_data['destination']) : $is_now['destination']; - $nexthop = (!empty($_data['nexthop'])) ? trim($_data['nexthop']) : $is_now['nexthop']; - $username = (isset($_data['username'])) ? trim($_data['username']) : $is_now['username']; - $password = (isset($_data['password'])) ? trim($_data['password']) : $is_now['password']; - $is_mx_based = (isset($_data['is_mx_based']) && $_data['is_mx_based'] != '') ? intval($_data['is_mx_based']) : $is_now['is_mx_based']; - $active = (isset($_data['active']) && $_data['active'] != '') ? intval($_data['active']) : $is_now['active']; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('relayhost_invalid', $id) - ); - continue; - } - preg_match('/\[(.+)\].*/', $nexthop, $next_hop_matches); - if (filter_var($nexthop, FILTER_VALIDATE_IP)) { - $nexthop = '[' . $nexthop . ']'; - } - $next_hop_clean = (isset($next_hop_matches[1])) ? $next_hop_matches[1] : $nexthop; - $transports = transport('get'); - if (!empty($transports)) { - foreach ($transports as $transport) { - $transport_data = transport('details', $transport['id']); - if ($transport['id'] == $id) { - continue; - } - $existing_nh[] = $transport_data['nexthop']; - preg_match('/\[(.+)\].*/', $transport_data['nexthop'], $existing_clean_nh[]); - if ($transport_data['destination'] == $destination) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'transport_dest_exists' - ); - return false; - } - } - } - if ($is_mx_based == 0 && (empty($destination) || (is_valid_domain_name(preg_replace('/^' . preg_quote('.', '/') . '/', '', $destination)) === false && $destination != '*' && filter_var($destination, FILTER_VALIDATE_EMAIL) === false))) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('invalid_destination', $destination) - ); - return false; - } - if ($is_mx_based == 1 && (empty($destination) || @preg_match('/' . $destination . '/', null) === false)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('invalid_destination', $destination) - ); - return false; - } - if (isset($next_hop_matches[1])) { - if ($existing_nh !== null && in_array($next_hop_clean, $existing_nh)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('next_hop_interferes', $next_hop_clean, $nexthop) - ); - return false; - } - } - else { - foreach ($existing_clean_nh as $existing_clean_nh_each) { - if ($existing_clean_nh_each[1] == $nexthop) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('next_hop_interferes_any', $nexthop) - ); - return false; - } - } - } - if (empty($username)) { - $password = ''; - } - try { - $stmt = $pdo->prepare("UPDATE `transports` SET - `destination` = :destination, - `is_mx_based` = :is_mx_based, - `nexthop` = :nexthop, - `username` = :username, - `password` = :password, - `active` = :active - WHERE `id` = :id"); - $stmt->execute(array( - ':id' => $id, - ':destination' => $destination, - ':is_mx_based' => $is_mx_based, - ':nexthop' => $nexthop, - ':username' => $username, - ':password' => $password, - ':active' => $active - )); - $stmt = $pdo->prepare("UPDATE `transports` SET - `username` = :username, - `password` = :password - WHERE `nexthop` = :nexthop"); - $stmt->execute(array( - ':nexthop' => $nexthop, - ':username' => $username, - ':password' => $password - )); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('mysql_error', $e) - ); - continue; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('object_modified', htmlspecialchars(implode(', ', (array)$hostnames))) - ); - } - break; - case 'delete': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $ids = (array)$_data['id']; - foreach ($ids as $id) { - try { - $stmt = $pdo->prepare("DELETE FROM `transports` WHERE `id`= :id"); - $stmt->execute(array(':id' => $id)); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('mysql_error', $e) - ); - continue; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('relayhost_removed', htmlspecialchars($id)) - ); - } - break; - case 'get': - if ($_SESSION['mailcow_cc_role'] != "admin") { - return false; - } - $transports = array(); - $stmt = $pdo->query("SELECT `id`, `is_mx_based`, `destination`, `nexthop`, `username` FROM `transports`"); - $transports = $stmt->fetchAll(PDO::FETCH_ASSOC); - return $transports; - break; - case 'details': - if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) { - return false; - } - $transportdata = array(); - $stmt = $pdo->prepare("SELECT `id`, - `is_mx_based`, - `destination`, - `nexthop`, - `username`, - `password`, - `active`, - CONCAT(LEFT(`password`, 3), '...') AS `password_short` - FROM `transports` - WHERE `id` = :id"); - $stmt->execute(array(':id' => $_data)); - $transportdata = $stmt->fetch(PDO::FETCH_ASSOC); - return $transportdata; - break; - } -} + 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $hostname = trim($_data['hostname']); + $username = str_replace(':', '\:', trim($_data['username'])); + $password = str_replace(':', '\:', trim($_data['password'])); + if (empty($hostname)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('invalid_host', htmlspecialchars($host)) + ); + return false; + } + try { + $stmt = $pdo->prepare("INSERT INTO `relayhosts` (`hostname`, `username` ,`password`, `active`) + VALUES (:hostname, :username, :password, :active)"); + $stmt->execute(array( + ':hostname' => $hostname, + ':username' => $username, + ':password' => str_replace(':', '\:', $password), + ':active' => '1' + )); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('mysql_error', $e) + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('relayhost_added', htmlspecialchars(implode(', ', (array)$hosts))) + ); + break; + case 'edit': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $ids = (array)$_data['id']; + foreach ($ids as $id) { + $is_now = relayhost('details', $id); + if (!empty($is_now)) { + $hostname = (!empty($_data['hostname'])) ? trim($_data['hostname']) : $is_now['hostname']; + $username = (isset($_data['username'])) ? trim($_data['username']) : $is_now['username']; + $password = (isset($_data['password'])) ? trim($_data['password']) : $is_now['password']; + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('relayhost_invalid', $id) + ); + continue; + } + try { + $stmt = $pdo->prepare("UPDATE `relayhosts` SET + `hostname` = :hostname, + `username` = :username, + `password` = :password, + `active` = :active + WHERE `id` = :id"); + $stmt->execute(array( + ':id' => $id, + ':hostname' => $hostname, + ':username' => $username, + ':password' => $password, + ':active' => $active + )); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('mysql_error', $e) + ); + continue; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('object_modified', htmlspecialchars(implode(', ', (array)$hostnames))) + ); + } + break; + case 'delete': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $ids = (array)$_data['id']; + foreach ($ids as $id) { + try { + $stmt = $pdo->prepare("DELETE FROM `relayhosts` WHERE `id`= :id"); + $stmt->execute(array(':id' => $id)); + $stmt = $pdo->prepare("UPDATE `domain` SET `relayhost` = '0' WHERE `relayhost`= :id"); + $stmt->execute(array(':id' => $id)); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('mysql_error', $e) + ); + continue; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('relayhost_removed', htmlspecialchars($id)) + ); + } + break; + case 'get': + if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") { + return false; + } + $relayhosts = array(); + $stmt = $pdo->query("SELECT `id`, `hostname`, `username`, `active` FROM `relayhosts`"); + $relayhosts = $stmt->fetchAll(PDO::FETCH_ASSOC); + return $relayhosts; + break; + case 'details': + if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) { + return false; + } + $relayhostdata = array(); + $stmt = $pdo->prepare("SELECT `id`, + `hostname`, + `username`, + `password`, + `active`, + CONCAT(LEFT(`password`, 3), '...') AS `password_short` + FROM `relayhosts` + WHERE `id` = :id"); + $stmt->execute(array(':id' => $_data)); + $relayhostdata = $stmt->fetch(PDO::FETCH_ASSOC); + if (!empty($relayhostdata)) { + $stmt = $pdo->prepare("SELECT GROUP_CONCAT(`domain` SEPARATOR ', ') AS `used_by_domains` FROM `domain` WHERE `relayhost` = :id"); + $stmt->execute(array(':id' => $_data)); + $used_by_domains = $stmt->fetch(PDO::FETCH_ASSOC)['used_by_domains']; + $used_by_domains = (empty($used_by_domains)) ? '' : $used_by_domains; + $relayhostdata['used_by_domains'] = $used_by_domains; + $stmt = $pdo->prepare("SELECT GROUP_CONCAT(`username` SEPARATOR ', ') AS `used_by_mailboxes` FROM `mailbox` WHERE JSON_VALUE(`attributes`, '$.relayhost') = :id"); + $stmt->execute(array(':id' => $_data)); + $used_by_mailboxes = $stmt->fetch(PDO::FETCH_ASSOC)['used_by_mailboxes']; + $used_by_mailboxes = (empty($used_by_mailboxes)) ? '' : $used_by_mailboxes; + $relayhostdata['used_by_mailboxes'] = $used_by_mailboxes; + } + return $relayhostdata; + break; + } +} +function transport($_action, $_data = null) { + global $pdo; + global $lang; + $_data_log = $_data; + switch ($_action) { + case 'add': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $destinations = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['destination'])); + $active = intval($_data['active']); + $is_mx_based = intval($_data['is_mx_based']); + $nexthop = trim($_data['nexthop']); + if (filter_var($nexthop, FILTER_VALIDATE_IP)) { + $nexthop = '[' . $nexthop . ']'; + } + preg_match('/\[(.+)\].*/', $nexthop, $next_hop_matches); + $next_hop_clean = (isset($next_hop_matches[1])) ? $next_hop_matches[1] : $nexthop; + $username = str_replace(':', '\:', trim($_data['username'])); + $password = str_replace(':', '\:', trim($_data['password'])); + if (empty($nexthop)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('invalid_nexthop') + ); + return false; + } + $transports = transport('get'); + if (!empty($transports)) { + foreach ($transports as $transport) { + $transport_data = transport('details', $transport['id']); + $existing_nh[] = $transport_data['nexthop']; + preg_match('/\[(.+)\].*/', $transport_data['nexthop'], $existing_clean_nh[]); + if (($transport_data['nexthop'] == $nexthop || $transport_data['nexthop'] == $next_hop_clean) && $transport_data['username'] != $username) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'invalid_nexthop_authenticated' + ); + return false; + } + foreach ($destinations as $d_ix => &$dest) { + if (empty($dest)) { + unset($destinations[$d_ix]); + continue; + } + if ($transport_data['destination'] == $dest) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('transport_dest_exists', $dest) + ); + unset($destinations[$d_ix]); + continue; + } + // ".domain" is a valid destination, "..domain" is not + if ($is_mx_based == 0 && (empty($dest) || (is_valid_domain_name(preg_replace('/^' . preg_quote('.', '/') . '/', '', $dest)) === false && $dest != '*' && filter_var($dest, FILTER_VALIDATE_EMAIL) === false))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('invalid_destination', $dest) + ); + unset($destinations[$d_ix]); + continue; + } + if ($is_mx_based == 1 && (empty($dest) || @preg_match('/' . $dest . '/', null) === false)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('invalid_destination', $dest) + ); + unset($destinations[$d_ix]); + continue; + } + } + } + } + $destinations = array_filter(array_values(array_unique($destinations))); + if (empty($destinations)) { return false; } + if (isset($next_hop_matches[1])) { + if ($existing_nh !== null && in_array($next_hop_clean, $existing_nh)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('next_hop_interferes', $next_hop_clean, $nexthop) + ); + return false; + } + } + else { + foreach ($existing_clean_nh as $existing_clean_nh_each) { + if ($existing_clean_nh_each[1] == $nexthop) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('next_hop_interferes_any', $nexthop) + ); + return false; + } + } + } + foreach ($destinations as $insert_dest) { + $stmt = $pdo->prepare("INSERT INTO `transports` (`nexthop`, `destination`, `is_mx_based`, `username` , `password`, `active`) + VALUES (:nexthop, :destination, :is_mx_based, :username, :password, :active)"); + $stmt->execute(array( + ':nexthop' => $nexthop, + ':destination' => $insert_dest, + ':is_mx_based' => $is_mx_based, + ':username' => $username, + ':password' => str_replace(':', '\:', $password), + ':active' => $active + )); + } + $stmt = $pdo->prepare("UPDATE `transports` SET + `username` = :username, + `password` = :password + WHERE `nexthop` = :nexthop"); + $stmt->execute(array( + ':nexthop' => $nexthop, + ':username' => $username, + ':password' => $password + )); + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('relayhost_added', htmlspecialchars(implode(', ', (array)$hosts))) + ); + break; + case 'edit': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $ids = (array)$_data['id']; + foreach ($ids as $id) { + $is_now = transport('details', $id); + if (!empty($is_now)) { + $destination = (!empty($_data['destination'])) ? trim($_data['destination']) : $is_now['destination']; + $nexthop = (!empty($_data['nexthop'])) ? trim($_data['nexthop']) : $is_now['nexthop']; + $username = (isset($_data['username'])) ? trim($_data['username']) : $is_now['username']; + $password = (isset($_data['password'])) ? trim($_data['password']) : $is_now['password']; + $is_mx_based = (isset($_data['is_mx_based']) && $_data['is_mx_based'] != '') ? intval($_data['is_mx_based']) : $is_now['is_mx_based']; + $active = (isset($_data['active']) && $_data['active'] != '') ? intval($_data['active']) : $is_now['active']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('relayhost_invalid', $id) + ); + continue; + } + preg_match('/\[(.+)\].*/', $nexthop, $next_hop_matches); + if (filter_var($nexthop, FILTER_VALIDATE_IP)) { + $nexthop = '[' . $nexthop . ']'; + } + $next_hop_clean = (isset($next_hop_matches[1])) ? $next_hop_matches[1] : $nexthop; + $transports = transport('get'); + if (!empty($transports)) { + foreach ($transports as $transport) { + $transport_data = transport('details', $transport['id']); + if ($transport['id'] == $id) { + continue; + } + $existing_nh[] = $transport_data['nexthop']; + preg_match('/\[(.+)\].*/', $transport_data['nexthop'], $existing_clean_nh[]); + if ($transport_data['destination'] == $destination) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'transport_dest_exists' + ); + return false; + } + } + } + if ($is_mx_based == 0 && (empty($destination) || (is_valid_domain_name(preg_replace('/^' . preg_quote('.', '/') . '/', '', $destination)) === false && $destination != '*' && filter_var($destination, FILTER_VALIDATE_EMAIL) === false))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('invalid_destination', $destination) + ); + return false; + } + if ($is_mx_based == 1 && (empty($destination) || @preg_match('/' . $destination . '/', null) === false)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('invalid_destination', $destination) + ); + return false; + } + if (isset($next_hop_matches[1])) { + if ($existing_nh !== null && in_array($next_hop_clean, $existing_nh)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('next_hop_interferes', $next_hop_clean, $nexthop) + ); + return false; + } + } + else { + foreach ($existing_clean_nh as $existing_clean_nh_each) { + if ($existing_clean_nh_each[1] == $nexthop) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('next_hop_interferes_any', $nexthop) + ); + return false; + } + } + } + if (empty($username)) { + $password = ''; + } + try { + $stmt = $pdo->prepare("UPDATE `transports` SET + `destination` = :destination, + `is_mx_based` = :is_mx_based, + `nexthop` = :nexthop, + `username` = :username, + `password` = :password, + `active` = :active + WHERE `id` = :id"); + $stmt->execute(array( + ':id' => $id, + ':destination' => $destination, + ':is_mx_based' => $is_mx_based, + ':nexthop' => $nexthop, + ':username' => $username, + ':password' => $password, + ':active' => $active + )); + $stmt = $pdo->prepare("UPDATE `transports` SET + `username` = :username, + `password` = :password + WHERE `nexthop` = :nexthop"); + $stmt->execute(array( + ':nexthop' => $nexthop, + ':username' => $username, + ':password' => $password + )); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('mysql_error', $e) + ); + continue; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('object_modified', htmlspecialchars(implode(', ', (array)$hostnames))) + ); + } + break; + case 'delete': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $ids = (array)$_data['id']; + foreach ($ids as $id) { + try { + $stmt = $pdo->prepare("DELETE FROM `transports` WHERE `id`= :id"); + $stmt->execute(array(':id' => $id)); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('mysql_error', $e) + ); + continue; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('relayhost_removed', htmlspecialchars($id)) + ); + } + break; + case 'get': + if ($_SESSION['mailcow_cc_role'] != "admin") { + return false; + } + $transports = array(); + $stmt = $pdo->query("SELECT `id`, `is_mx_based`, `destination`, `nexthop`, `username` FROM `transports`"); + $transports = $stmt->fetchAll(PDO::FETCH_ASSOC); + return $transports; + break; + case 'details': + if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) { + return false; + } + $transportdata = array(); + $stmt = $pdo->prepare("SELECT `id`, + `is_mx_based`, + `destination`, + `nexthop`, + `username`, + `password`, + `active`, + CONCAT(LEFT(`password`, 3), '...') AS `password_short` + FROM `transports` + WHERE `id` = :id"); + $stmt->execute(array(':id' => $_data)); + $transportdata = $stmt->fetch(PDO::FETCH_ASSOC); + return $transportdata; + break; + } +} diff --git a/data/web/inc/lib/WebAuthn/rootCertificates/huawei.pem b/data/web/inc/lib/WebAuthn/rootCertificates/huawei.pem index 8c954139..f7324d55 100644 --- a/data/web/inc/lib/WebAuthn/rootCertificates/huawei.pem +++ b/data/web/inc/lib/WebAuthn/rootCertificates/huawei.pem @@ -1,31 +1,31 @@ ------BEGIN CERTIFICATE----- -MIIFZDCCA0ygAwIBAgIIYsLLTehAXpYwDQYJKoZIhvcNAQELBQAwUDELMAkGA1UE -BhMCQ04xDzANBgNVBAoMBkh1YXdlaTETMBEGA1UECwwKSHVhd2VpIENCRzEbMBkG -A1UEAwwSSHVhd2VpIENCRyBSb290IENBMB4XDTE3MDgyMTEwNTYyN1oXDTQyMDgx -NTEwNTYyN1owUDELMAkGA1UEBhMCQ04xDzANBgNVBAoMBkh1YXdlaTETMBEGA1UE -CwwKSHVhd2VpIENCRzEbMBkGA1UEAwwSSHVhd2VpIENCRyBSb290IENBMIICIjAN -BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1OyKm3Ig/6eibB7Uz2o93UqGk2M7 -84WdfF8mvffvu218d61G5M3Px54E3kefUTk5Ky1ywHvw7Rp9KDuYv7ktaHkk+yr5 -9Ihseu3a7iM/C6SnMSGt+LfB/Bcob9Abw95EigXQ4yQddX9hbNrin3AwZw8wMjEI -SYYDo5GuYDL0NbAiYg2Y5GpfYIqRzoi6GqDz+evLrsl20kJeCEPgJZN4Jg00Iq9k -++EKOZ5Jc/Zx22ZUgKpdwKABkvzshEgG6WWUPB+gosOiLv++inu/9blDpEzQZhjZ -9WVHpURHDK1YlCvubVAMhDpnbqNHZ0AxlPletdoyugrH/OLKl5inhMXNj3Re7Hl8 -WsBWLUKp6sXFf0dvSFzqnr2jkhicS+K2IYZnjghC9cOBRO8fnkonh0EBt0evjUIK -r5ClbCKioBX8JU+d4ldtWOpp2FlxeFTLreDJ5ZBU4//bQpTwYMt7gwMK+MO5Wtok -Ux3UF98Z6GdUgbl6nBjBe82c7oIQXhHGHPnURQO7DDPgyVnNOnTPIkmiHJh/e3vk -VhiZNHFCCLTip6GoJVrLxwb9i4q+d0thw4doxVJ5NB9OfDMV64/ybJgpf7m3Ld2y -E0gsf1prrRlDFDXjlYyqqpf1l9Y0u3ctXo7UpXMgbyDEpUQhq3a7txZQO/17luTD -oA6Tz1ADavvBwHkCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF -MAMBAf8wHQYDVR0OBBYEFKrE03lH6G4ja+/wqWwicz16GWmhMA0GCSqGSIb3DQEB -CwUAA4ICAQC1d3TMB+VHZdGrWJbfaBShFNiCTN/MceSHOpzBn6JumQP4N7mxCOwd -RSsGKQxV2NPH7LTXWNhUvUw5Sek96FWx/+Oa7jsj3WNAVtmS3zKpCQ5iGb08WIRO -cFnx3oUQ5rcO8r/lUk7Q2cN0E+rF4xsdQrH9k2cd3kAXZXBjfxfKPJTdPy1XnZR/ -h8H5EwEK5DWjSzK1wKd3G/Fxdm3E23pcr4FZgdYdOlFSiqW2TJ3Qe6lF4GOKOOyd -WHkpu54ieTsqoYcuMKnKMjT2SLNNgv9Gu5ipaG8Olz6g9C7Htp943lmK/1Vtnhgg -pL3rDTsFX/+ehk7OtxuNzRMD9lXUtEfok7f8XB0dcL4ZjnEhDmp5QZqC1kMubHQt -QnTauEiv0YkSGOwJAUZpK1PIff5GgxXYfaHfBC6Op4q02ppl5Q3URl7XIjYLjvs9 -t4S9xPe8tb6416V2fe1dZ62vOXMMKHkZjVihh+IceYpJYHuyfKoYJyahLOQXZykG -K5iPAEEtq3HPfMVF43RKHOwfhrAH5KwelUA/0EkcR4Gzth1MKEqojdnYNemkkSy7 -aNPPT4LEm5R7sV6vG1CjwbgvQrWCgc4nMb8ngdfnVF7Ydqjqi9SAqUzIk4+Uf0ZY -+6RY5IcHdCaiPaWIE1xURQ8B0DRUURsQwXdjZhgLN/DKJpCl5aCCxg== ------END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFZDCCA0ygAwIBAgIIYsLLTehAXpYwDQYJKoZIhvcNAQELBQAwUDELMAkGA1UE +BhMCQ04xDzANBgNVBAoMBkh1YXdlaTETMBEGA1UECwwKSHVhd2VpIENCRzEbMBkG +A1UEAwwSSHVhd2VpIENCRyBSb290IENBMB4XDTE3MDgyMTEwNTYyN1oXDTQyMDgx +NTEwNTYyN1owUDELMAkGA1UEBhMCQ04xDzANBgNVBAoMBkh1YXdlaTETMBEGA1UE +CwwKSHVhd2VpIENCRzEbMBkGA1UEAwwSSHVhd2VpIENCRyBSb290IENBMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1OyKm3Ig/6eibB7Uz2o93UqGk2M7 +84WdfF8mvffvu218d61G5M3Px54E3kefUTk5Ky1ywHvw7Rp9KDuYv7ktaHkk+yr5 +9Ihseu3a7iM/C6SnMSGt+LfB/Bcob9Abw95EigXQ4yQddX9hbNrin3AwZw8wMjEI +SYYDo5GuYDL0NbAiYg2Y5GpfYIqRzoi6GqDz+evLrsl20kJeCEPgJZN4Jg00Iq9k +++EKOZ5Jc/Zx22ZUgKpdwKABkvzshEgG6WWUPB+gosOiLv++inu/9blDpEzQZhjZ +9WVHpURHDK1YlCvubVAMhDpnbqNHZ0AxlPletdoyugrH/OLKl5inhMXNj3Re7Hl8 +WsBWLUKp6sXFf0dvSFzqnr2jkhicS+K2IYZnjghC9cOBRO8fnkonh0EBt0evjUIK +r5ClbCKioBX8JU+d4ldtWOpp2FlxeFTLreDJ5ZBU4//bQpTwYMt7gwMK+MO5Wtok +Ux3UF98Z6GdUgbl6nBjBe82c7oIQXhHGHPnURQO7DDPgyVnNOnTPIkmiHJh/e3vk +VhiZNHFCCLTip6GoJVrLxwb9i4q+d0thw4doxVJ5NB9OfDMV64/ybJgpf7m3Ld2y +E0gsf1prrRlDFDXjlYyqqpf1l9Y0u3ctXo7UpXMgbyDEpUQhq3a7txZQO/17luTD +oA6Tz1ADavvBwHkCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFKrE03lH6G4ja+/wqWwicz16GWmhMA0GCSqGSIb3DQEB +CwUAA4ICAQC1d3TMB+VHZdGrWJbfaBShFNiCTN/MceSHOpzBn6JumQP4N7mxCOwd +RSsGKQxV2NPH7LTXWNhUvUw5Sek96FWx/+Oa7jsj3WNAVtmS3zKpCQ5iGb08WIRO +cFnx3oUQ5rcO8r/lUk7Q2cN0E+rF4xsdQrH9k2cd3kAXZXBjfxfKPJTdPy1XnZR/ +h8H5EwEK5DWjSzK1wKd3G/Fxdm3E23pcr4FZgdYdOlFSiqW2TJ3Qe6lF4GOKOOyd +WHkpu54ieTsqoYcuMKnKMjT2SLNNgv9Gu5ipaG8Olz6g9C7Htp943lmK/1Vtnhgg +pL3rDTsFX/+ehk7OtxuNzRMD9lXUtEfok7f8XB0dcL4ZjnEhDmp5QZqC1kMubHQt +QnTauEiv0YkSGOwJAUZpK1PIff5GgxXYfaHfBC6Op4q02ppl5Q3URl7XIjYLjvs9 +t4S9xPe8tb6416V2fe1dZ62vOXMMKHkZjVihh+IceYpJYHuyfKoYJyahLOQXZykG +K5iPAEEtq3HPfMVF43RKHOwfhrAH5KwelUA/0EkcR4Gzth1MKEqojdnYNemkkSy7 +aNPPT4LEm5R7sV6vG1CjwbgvQrWCgc4nMb8ngdfnVF7Ydqjqi9SAqUzIk4+Uf0ZY ++6RY5IcHdCaiPaWIE1xURQ8B0DRUURsQwXdjZhgLN/DKJpCl5aCCxg== +-----END CERTIFICATE----- diff --git a/data/web/inc/lib/vendor/soundasleep/html2text/tests/anchors.html b/data/web/inc/lib/vendor/soundasleep/html2text/tests/anchors.html index 2413c527..61dffa0d 100644 --- a/data/web/inc/lib/vendor/soundasleep/html2text/tests/anchors.html +++ b/data/web/inc/lib/vendor/soundasleep/html2text/tests/anchors.html @@ -1,12 +1,12 @@ -A document without any HTML open/closing tags. - -
- -We try and use the representation given by common browsers of the -HTML document, so that it looks similar when converted to plain text. - -visit foo.com - or http://www.foo.com - -link - -

An anchor which will not appear

+A document without any HTML open/closing tags. + +
+ +We try and use the representation given by common browsers of the +HTML document, so that it looks similar when converted to plain text. + +visit foo.com - or http://www.foo.com + +link + +

An anchor which will not appear

diff --git a/data/web/inc/lib/vendor/soundasleep/html2text/tests/basic.html b/data/web/inc/lib/vendor/soundasleep/html2text/tests/basic.html index df466931..3bf68d24 100644 --- a/data/web/inc/lib/vendor/soundasleep/html2text/tests/basic.html +++ b/data/web/inc/lib/vendor/soundasleep/html2text/tests/basic.html @@ -1,21 +1,21 @@ - -Ignored Title - -

Hello, World!

- -

This is some e-mail content. - Even though it has whitespace and newlines, the e-mail converter - will handle it correctly. - -

Even mismatched tags.

- -
A div
-
Another div
-
A div
within a div
- -

Another line
Yet another line

- - A link - - + +Ignored Title + +

Hello, World!

+ +

This is some e-mail content. + Even though it has whitespace and newlines, the e-mail converter + will handle it correctly. + +

Even mismatched tags.

+ +
A div
+
Another div
+
A div
within a div
+ +

Another line
Yet another line

+ + A link + + \ No newline at end of file diff --git a/data/web/inc/lib/vendor/soundasleep/html2text/tests/table.html b/data/web/inc/lib/vendor/soundasleep/html2text/tests/table.html index b4b3cf09..e887375c 100644 --- a/data/web/inc/lib/vendor/soundasleep/html2text/tests/table.html +++ b/data/web/inc/lib/vendor/soundasleep/html2text/tests/table.html @@ -1,53 +1,53 @@ - -Ignored Title - -

Hello, World!

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Col ACol B
- Data A1 - - Data B1 -
- Data A2 - - Data B2 -
- Data A3 - - Data B4 -
- Total A - - Total B -
- - + +Ignored Title + +

Hello, World!

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Col ACol B
+ Data A1 + + Data B1 +
+ Data A2 + + Data B2 +
+ Data A3 + + Data B4 +
+ Total A + + Total B +
+ + \ No newline at end of file diff --git a/data/web/inc/lib/vendor/soundasleep/html2text/tests/test3.txt b/data/web/inc/lib/vendor/soundasleep/html2text/tests/test3.txt index 4104e030..e549fb4c 100644 --- a/data/web/inc/lib/vendor/soundasleep/html2text/tests/test3.txt +++ b/data/web/inc/lib/vendor/soundasleep/html2text/tests/test3.txt @@ -1,2 +1,2 @@ -test one +test one test two \ No newline at end of file diff --git a/data/web/inc/lib/vendor/soundasleep/html2text/tests/test4.txt b/data/web/inc/lib/vendor/soundasleep/html2text/tests/test4.txt index 644e527c..61244077 100644 --- a/data/web/inc/lib/vendor/soundasleep/html2text/tests/test4.txt +++ b/data/web/inc/lib/vendor/soundasleep/html2text/tests/test4.txt @@ -1,5 +1,5 @@ -1 -2 -3 -4 +1 +2 +3 +4 5 6 \ No newline at end of file diff --git a/data/web/js/build/006-formcache.min.js b/data/web/js/build/006-formcache.min.js index 10a02884..42f93056 100644 --- a/data/web/js/build/006-formcache.min.js +++ b/data/web/js/build/006-formcache.min.js @@ -1,10 +1,10 @@ -/*! - * Form Cache v@VERSION - * https://github.com/fengyuanchen/formcache - * - * Copyright 2014 Fengyuan Chen - * Released under the MIT license - * - * Date: @DATE - */ +/*! + * Form Cache v@VERSION + * https://github.com/fengyuanchen/formcache + * + * Copyright 2014 Fengyuan Chen + * Released under the MIT license + * + * Date: @DATE + */ !function(t){"function"==typeof define&&define.amd?define("formcache",["jquery"],t):t(jQuery)}(function(t){"use strict";var e=t(window),i=window.sessionStorage,s=window.localStorage,n="undefined",o=".formcache",a=/[\.\*\+\^\$\:\!\[\]#>~]+/g,c="change"+o,h="beforeunload"+o,r=function(t){return"checkbox"===t.type||"radio"===t.type},f=function(t){return parseInt(t,10)},u=function(e,i){this.form=e,this.$form=t(e),this.defaults=t.extend({},u.DEFAULTS,t.isPlainObject(i)?i:{}),this.init()};u.prototype={constructor:u,init:function(){var e=this.defaults;e.maxAge=Math.abs(e.maxAge||e.maxage),e.autoStore=Boolean(e.autoStore||e.autostore),this.initKey(),this.initStorage(),this.caches=this.storage.caches,this.index=0,this.activeIndex=0,this.storing=null,t.isArray(e.controls)||(e.controls=[]),this.$controls=this.$form.find(e.controls.join()).not(":file"),this.addListeners(),this.outputCache()},initKey:function(){var e=this.$form,i=this.defaults.key||e.data("key");i||(t("form").each(function(e){t(this).data("key",e)}),i=e.data("key")),this.key=location.pathname+"#formcache-"+i},initStorage:function(){var e,n=this.defaults,o=this.key,a=new Date,c={date:a,maxAge:n.maxAge,caches:[]};i&&(e=i.getItem(o)),!e&&s&&(e=s.getItem(o)),e="string"==typeof e?JSON.parse(e):null,t.isPlainObject(e)?"number"==typeof e.maxAge&&(a-new Date(e.date))/1e3>e.maxAge&&(e=c):e=c,this.storage=e},addListeners:function(){this.defaults.autoStore&&(this.$controls.on(c,t.proxy(this.change,this)),e.on(h,t.proxy(this.beforeunload,this)))},removeListeners:function(){this.defaults.autoStore&&(this.$controls.off(c,this.change),e.off(h,this.beforeunload))},change:function(e){var i,s,n=e.target,o=t(n),c=o.attr("name"),h=[];c&&(i=c.replace(a,""),this.$controls.filter('[name*="'+i+'"]').each(function(){r(n)?h.push(this.checked):(s=t(this).val(),s&&h.push(s))}),h.length&&(this.update(c,h),clearTimeout(this.storing),this.storing=setTimeout(t.proxy(this.store,this),1e3)))},beforeunload:function(){this.update(),this.store()},update:function(t,e){var i=this.activeIndex||this.index,s=this.getCache(i);"string"==typeof t?s[t]=e:s=this.serialize(),this.setCache(i,s)},serialize:function(){var e={};return this.$controls.each(function(){var i,s,n=t(this),o=n.attr("name");o&&(i=e[o],i=t.isArray(i)?i:[],r(this)?i.push(this.checked):(s=n.val(),s&&i.push(s)),i.length&&(e[o]=i))}),e},getCache:function(t){return this.caches[f(t)||this.index]||{}},getCaches:function(){return this.caches},setCache:function(e,i){typeof i===n&&(i=e,e=NaN),t.isPlainObject(i)&&(e=f(e)||this.index,this.caches[e]=i,this.store())},setCaches:function(e){t.isArray(e)&&(this.caches=e,this.store())},removeCache:function(t){this.caches.splice(f(t)||this.index,1),this.store()},removeCaches:function(){this.caches=[],this.store()},outputCache:function(e){var i=this.getCache(e);t.isPlainObject(i)&&(this.activeIndex=f(e)||this.index,i=t.extend(!0,{},i),this.$controls.each(function(){var e,s,n=t(this),o=n.attr("name");o&&(e=i[o],t.isArray(e)&&e.length&&(s=e.shift(),r(this)?this.checked=s:n.val(s)))}))},store:function(){var t=this.storage,e=this.key,n=this.defaults;t.date=new Date,t.maxAge=n.maxAge,t=JSON.stringify(t),n.session&&i&&i.setItem(e,t),n.local&&s&&s.setItem(e,t)},clear:function(){var t=this.key,e=this.defaults;e.session&&i&&i.removeItem(t),e.local&&s&&s.removeItem(t)},destroy:function(){this.removeListeners(),this.$form.removeData("formcache")}},u.DEFAULTS={key:"",local:!0,session:0,autoStore:!0,maxAge:void 0,controls:["select","textarea","input"]},u.setDefaults=function(e){t.extend(u.DEFAULTS,e)},u.other=t.fn.formcache,t.fn.formcache=function(e){var i,s=[].slice.call(arguments,1);return this.each(function(){var n,o=t(this),a=o.data("formcache");a||o.data("formcache",a=new u(this,e)),"string"==typeof e&&t.isFunction(n=a[e])&&(i=n.apply(a,s))}),typeof i!==n?i:this},t.fn.formcache.Constructor=u,t.fn.formcache.setDefaults=u.setDefaults,t.fn.formcache.noConflict=function(){return t.fn.formcache=u.other,this},t(function(){t('form[data-toggle="formcache"]').formcache()})}); \ No newline at end of file diff --git a/data/web/js/build/009-numberedtextarea.min.js b/data/web/js/build/009-numberedtextarea.min.js index c99329f7..48daa32b 100644 --- a/data/web/js/build/009-numberedtextarea.min.js +++ b/data/web/js/build/009-numberedtextarea.min.js @@ -1 +1 @@ -!function(e){function t(t,n){var a=e('
').insertAfter(t);e(t).detach().appendTo(a)}function n(t,n){(t=e(t)).parents(".numberedtextarea-wrapper");var i=parseFloat(t.css("padding-left")),o=parseFloat(t.css("padding-top")),s=(parseFloat(t.css("padding-bottom")),e('
').insertAfter(t));t.css({paddingLeft:i+s.width()+"px"}).on("input propertychange change keyup paste",function(){a(t,n)}).on("scroll",function(){r(t,n)}),s.css({paddingLeft:i+"px",paddingTop:o+"px",lineHeight:t.css("line-height"),fontFamily:t.css("font-family"),width:s.width()-i+"px"}),t.trigger("change")}function a(t,n){var a=(t=e(t)).parent().find(".numberedtextarea-line-numbers"),r=t.val().split("\n").length,o=parseFloat(t.css("padding-bottom"));for(a.find(".numberedtextarea-number").remove(),i=1;i<=r;i++){var s=e('
'+i+"
").appendTo(a);i===r&&s.css("margin-bottom",o+"px")}}function r(t,n){(t=e(t)).parent().find(".numberedtextarea-line-numbers").scrollTop(t.scrollTop())}function o(e,t){if(e.focus(),"number"==typeof e.selectionStart){var n=e.value,a=e.selectionStart;e.value=n.slice(0,a)+t+n.slice(e.selectionEnd),e.selectionEnd=e.selectionStart=a+t.length}else if(void 0!==document.selection){var i=document.selection.createRange();i.text=t,i.collapse(!1),i.select()}}function s(t){e(t).keydown(function(e){if(9==e.which)return o(this,"\t"),!1}),e(t).keypress(function(e){if(9==e.which)return!1})}e.fn.numberedtextarea=function(a){var i=e.extend({color:null,borderColor:null,class:null,allowTabChar:!1},a);return this.each(function(){if("textarea"!==this.nodeName.toLowerCase())return console.log("This is not a