[DockerAPI] WIP: change of structure, add some more commands to control mail queue

master
André 2018-10-23 21:12:37 +02:00
parent 5347009fbb
commit fbf1c7b7c1
1 changed files with 209 additions and 106 deletions

View File

@ -1,6 +1,7 @@
from flask import Flask from flask import Flask
from flask_restful import Resource, Api from flask_restful import Resource, Api
from flask import jsonify from flask import jsonify
from flask import Response
from flask import request from flask import request
from threading import Thread from threading import Thread
from OpenSSL import crypto from OpenSSL import crypto
@ -71,112 +72,214 @@ class container_post(Resource):
if not request.json or not 'cmd' in request.json: if not request.json or not 'cmd' in request.json:
return jsonify(type='danger', msg='cmd is missing') return jsonify(type='danger', msg='cmd is missing')
if request.json['cmd'] == 'df' and request.json['dir']: if request.json['cmd'] == 'mailq':
try: if 'items' in request.json:
for container in docker_client.containers.list(filters={"id": container_id}): # Check if queue id is valid
# Should be changed to be able to validate a path r = re.compile("^[0-9a-fA-F]+$")
directory = re.sub('[^0-9a-zA-Z/]+', '', request.json['dir']) filtered_qids = filter(r.match, request.json['items'])
df_return = container.exec_run(["/bin/bash", "-c", "/bin/df -H " + directory + " | /usr/bin/tail -n1 | /usr/bin/tr -s [:blank:] | /usr/bin/tr ' ' ','"], user='nobody') if filtered_qids:
if df_return.exit_code == 0: if request.json['task'] == 'delete':
return df_return.output.rstrip() flagged_qids = ['-d %s' % i for i in filtered_qids]
else: sanitized_string = str(' '.join(flagged_qids));
return "0,0,0,0,0,0" try:
except Exception as e: for container in docker_client.containers.list(filters={"id": container_id}):
return jsonify(type='danger', msg=str(e)) postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
elif request.json['cmd'] == 'sieve_list' and request.json['username']: if postsuper_r.exit_code == 0:
try: return jsonify(type='success', msg='command completed successfully')
for container in docker_client.containers.list(filters={"id": container_id}): else:
sieve_return = container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm sieve list -u '" + request.json['username'].replace("'", "'\\''") + "'"]) return jsonify(type='danger', msg=str(postsuper_r.output))
return sieve_return.output except Exception as e:
except Exception as e: return jsonify(type='danger', msg=str(e))
return jsonify(type='danger', msg=str(e)) if request.json['task'] == 'hold':
elif request.json['cmd'] == 'sieve_print' and request.json['script_name'] and request.json['username']: flagged_qids = ['-h %s' % i for i in filtered_qids]
try: sanitized_string = str(' '.join(flagged_qids));
for container in docker_client.containers.list(filters={"id": container_id}): try:
sieve_return = container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm sieve get -u '" + request.json['username'].replace("'", "'\\''") + "' '" + request.json['script_name'].replace("'", "'\\''") + "'"]) for container in docker_client.containers.list(filters={"id": container_id}):
return sieve_return.output postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
except Exception as e: if postsuper_r.exit_code == 0:
return jsonify(type='danger', msg=str(e)) return jsonify(type='success', msg='command completed successfully')
# not in use... else:
elif request.json['cmd'] == 'mail_crypt_generate' and request.json['username'] and request.json['old_password'] and request.json['new_password']: return jsonify(type='danger', msg=str(postsuper_r.output))
try: except Exception as e:
for container in docker_client.containers.list(filters={"id": container_id}): return jsonify(type='danger', msg=str(e))
# create if missing if request.json['task'] == 'unhold':
crypto_generate = container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm mailbox cryptokey generate -u '" + request.json['username'].replace("'", "'\\''") + "' -URf"], user='vmail') flagged_qids = ['-H %s' % i for i in filtered_qids]
if crypto_generate.exit_code == 0: sanitized_string = str(' '.join(flagged_qids));
# open a shell, bind stdin and return socket try:
cryptokey_shell = container.exec_run(["/bin/bash"], stdin=True, socket=True, user='vmail') for container in docker_client.containers.list(filters={"id": container_id}):
# command to be piped to shell postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
cryptokey_cmd = "/usr/local/bin/doveadm mailbox cryptokey password -u '" + request.json['username'].replace("'", "'\\''") + "' -n '" + request.json['new_password'].replace("'", "'\\''") + "' -o '" + request.json['old_password'].replace("'", "'\\''") + "'\n" if postsuper_r.exit_code == 0:
# socket is .output return jsonify(type='success', msg='command completed successfully')
cryptokey_socket = cryptokey_shell.output; else:
try : return jsonify(type='danger', msg=str(postsuper_r.output))
# send command utf-8 encoded except Exception as e:
cryptokey_socket.sendall(cryptokey_cmd.encode('utf-8')) return jsonify(type='danger', msg=str(e))
# we won't send more data than this if request.json['task'] == 'deliver':
cryptokey_socket.shutdown(socket.SHUT_WR) flagged_qids = ['-i %s' % i for i in filtered_qids]
except socket.error: try:
# exit on socket error for container in docker_client.containers.list(filters={"id": container_id}):
return jsonify(type='danger', msg=str('socket error')) for i in flagged_qids:
# read response postqueue_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix')
cryptokey_response = recv_socket_data(cryptokey_socket) # todo: check each exit code
crypto_error = re.search('dcrypt_key_load_private.+failed.+error', cryptokey_response) return jsonify(type='success', msg=str("Scheduled immediate delivery"))
if crypto_error is not None: except Exception as e:
return jsonify(type='danger', msg=str("dcrypt_key_load_private error")) return jsonify(type='danger', msg=str(e))
return jsonify(type='success', msg=str("key pair generated")) elif request.json['task'] == 'list':
else: try:
return jsonify(type='danger', msg=str(crypto_generate.output)) for container in docker_client.containers.list(filters={"id": container_id}):
except Exception as e: mailq_return = container.exec_run(["/usr/sbin/postqueue", "-j"], user='postfix')
return jsonify(type='danger', msg=str(e)) if mailq_return.exit_code == 0:
elif request.json['cmd'] == 'maildir_cleanup' and request.json['maildir']: # We want plain text content from Postfix
try: r = Response(response=mailq_return.output, status=200, mimetype="text/plain")
for container in docker_client.containers.list(filters={"id": container_id}): r.headers["Content-Type"] = "text/plain; charset=utf-8"
sane_name = re.sub(r'\W+', '', request.json['maildir']) return r
maildir_cleanup = container.exec_run(["/bin/bash", "-c", "if [[ -d '/var/vmail/" + request.json['maildir'].replace("'", "'\\''") + "' ]]; then /bin/mv '/var/vmail/" + request.json['maildir'].replace("'", "'\\''") + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "'; fi"], user='vmail') except Exception as e:
if maildir_cleanup.exit_code == 0: return jsonify(type='danger', msg=str(e))
return jsonify(type='success', msg=str("moved to garbage")) elif request.json['task'] == 'flush':
else: try:
return jsonify(type='danger', msg=str(maildir_cleanup.output)) for container in docker_client.containers.list(filters={"id": container_id}):
except Exception as e: postqueue_r = container.exec_run(["/usr/sbin/postqueue", "-f"], user='postfix')
return jsonify(type='danger', msg=str(e)) if postqueue_r.exit_code == 0:
elif request.json['cmd'] == 'worker_password' and request.json['raw']: return jsonify(type='success', msg='command completed successfully')
try: else:
for container in docker_client.containers.list(filters={"id": container_id}): return jsonify(type='danger', msg=str(postqueue_r.output))
worker_shell = container.exec_run(["/bin/bash"], stdin=True, socket=True, user='_rspamd') except Exception as e:
worker_cmd = "/usr/bin/rspamadm pw -e -p '" + request.json['raw'].replace("'", "'\\''") + "' 2> /dev/null\n" return jsonify(type='danger', msg=str(e))
worker_socket = worker_shell.output; elif request.json['task'] == 'super_delete':
try : try:
worker_socket.sendall(worker_cmd.encode('utf-8')) for container in docker_client.containers.list(filters={"id": container_id}):
worker_socket.shutdown(socket.SHUT_WR) postsuper_r = container.exec_run(["/usr/sbin/postsuper", "-d", "ALL"])
except socket.error: if postsuper_r.exit_code == 0:
return jsonify(type='danger', msg=str('socket error')) return jsonify(type='success', msg='command completed successfully')
worker_response = recv_socket_data(worker_socket) else:
matched = False return jsonify(type='danger', msg=str(postsuper_r.output))
for line in worker_response.split("\n"): except Exception as e:
if '$2$' in line: return jsonify(type='danger', msg=str(e))
matched = True
hash = line.strip() elif request.json['cmd'] == 'system':
hash_out = re.search('\$2\$.+$', hash).group(0) if request.json['task'] == 'df':
f = open("/access.inc", "w") if 'dir' in request.json:
f.write('enable_password = "' + re.sub('[^0-9a-zA-Z\$]+', '', hash_out.rstrip()) + '";\n') try:
f.close() for container in docker_client.containers.list(filters={"id": container_id}):
container.restart() # Should be changed to be able to validate a path
if matched: directory = re.sub('[^0-9a-zA-Z/]+', '', request.json['dir'])
return jsonify(type='success', msg='command completed successfully') df_return = container.exec_run(["/bin/bash", "-c", "/bin/df -H " + directory + " | /usr/bin/tail -n1 | /usr/bin/tr -s [:blank:] | /usr/bin/tr ' ' ','"], user='nobody')
else: if df_return.exit_code == 0:
return jsonify(type='danger', msg='command did not complete') return df_return.output.rstrip()
except Exception as e: else:
return jsonify(type='danger', msg=str(e)) return "0,0,0,0,0,0"
elif request.json['cmd'] == 'mailman_password' and request.json['email'] and request.json['passwd']: except Exception as e:
try: return jsonify(type='danger', msg=str(e))
for container in docker_client.containers.list(filters={"id": container_id}):
add_su = container.exec_run(["/bin/bash", "-c", "/opt/mm_web/add_su.py '" + request.json['passwd'].replace("'", "'\\''") + "' '" + request.json['email'].replace("'", "'\\''") + "'"], user='mailman') elif request.json['cmd'] == 'sieve':
if add_su.exit_code == 0: if request.json['task'] == 'list':
return jsonify(type='success', msg='command completed successfully') if 'username' in request.json:
else: try:
return jsonify(type='danger', msg='command did not complete, exit code was ' + int(add_su.exit_code)) for container in docker_client.containers.list(filters={"id": container_id}):
except Exception as e: sieve_return = container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm sieve list -u '" + request.json['username'].replace("'", "'\\''") + "'"])
return jsonify(type='danger', msg=str(e)) r = Response(response=sieve_return.output, status=200, mimetype="text/plain")
r.headers["Content-Type"] = "text/plain; charset=utf-8"
return r
except Exception as e:
return jsonify(type='danger', msg=str(e))
elif request.json['task'] == 'print':
if 'username' in request.json and 'script_name' in request.json:
try:
for container in docker_client.containers.list(filters={"id": container_id}):
sieve_return = container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm sieve get -u '" + request.json['username'].replace("'", "'\\''") + "' '" + request.json['script_name'].replace("'", "'\\''") + "'"])
r = Response(response=sieve_return.output, status=200, mimetype="text/plain")
r.headers["Content-Type"] = "text/plain; charset=utf-8"
return r
except Exception as e:
return jsonify(type='danger', msg=str(e))
# elif request.json['cmd'] == 'mail_crypt_generate' and request.json['username'] and request.json['old_password'] and request.json['new_password']:
# try:
# for container in docker_client.containers.list(filters={"id": container_id}):
# # create if missing
# crypto_generate = container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm mailbox cryptokey generate -u '" + request.json['username'].replace("'", "'\\''") + "' -URf"], user='vmail')
# if crypto_generate.exit_code == 0:
# # open a shell, bind stdin and return socket
# cryptokey_shell = container.exec_run(["/bin/bash"], stdin=True, socket=True, user='vmail')
# # command to be piped to shell
# cryptokey_cmd = "/usr/local/bin/doveadm mailbox cryptokey password -u '" + request.json['username'].replace("'", "'\\''") + "' -n '" + request.json['new_password'].replace("'", "'\\''") + "' -o '" + request.json['old_password'].replace("'", "'\\''") + "'\n"
# # socket is .output
# cryptokey_socket = cryptokey_shell.output;
# try :
# # send command utf-8 encoded
# cryptokey_socket.sendall(cryptokey_cmd.encode('utf-8'))
# # we won't send more data than this
# cryptokey_socket.shutdown(socket.SHUT_WR)
# except socket.error:
# # exit on socket error
# return jsonify(type='danger', msg=str('socket error'))
# # read response
# cryptokey_response = recv_socket_data(cryptokey_socket)
# crypto_error = re.search('dcrypt_key_load_private.+failed.+error', cryptokey_response)
# if crypto_error is not None:
# return jsonify(type='danger', msg=str("dcrypt_key_load_private error"))
# return jsonify(type='success', msg=str("key pair generated"))
# else:
# return jsonify(type='danger', msg=str(crypto_generate.output))
# except Exception as e:
# return jsonify(type='danger', msg=str(e))
elif request.json['cmd'] == 'maildir':
if request.json['task'] == 'cleanup':
if 'maildir' in request.json:
try:
for container in docker_client.containers.list(filters={"id": container_id}):
sane_name = re.sub(r'\W+', '', request.json['maildir'])
maildir_cleanup = container.exec_run(["/bin/bash", "-c", "if [[ -d '/var/vmail/" + request.json['maildir'].replace("'", "'\\''") + "' ]]; then /bin/mv '/var/vmail/" + request.json['maildir'].replace("'", "'\\''") + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "'; fi"], user='vmail')
if maildir_cleanup.exit_code == 0:
return jsonify(type='success', msg=str("moved to garbage"))
else:
return jsonify(type='danger', msg=str(maildir_cleanup.output))
except Exception as e:
return jsonify(type='danger', msg=str(e))
elif request.json['cmd'] == 'rspamd':
if request.json['task'] == 'worker_password':
if 'raw' in request.json:
try:
for container in docker_client.containers.list(filters={"id": container_id}):
worker_shell = container.exec_run(["/bin/bash"], stdin=True, socket=True, user='_rspamd')
worker_cmd = "/usr/bin/rspamadm pw -e -p '" + request.json['raw'].replace("'", "'\\''") + "' 2> /dev/null\n"
worker_socket = worker_shell.output;
try :
worker_socket.sendall(worker_cmd.encode('utf-8'))
worker_socket.shutdown(socket.SHUT_WR)
except socket.error:
return jsonify(type='danger', msg=str('socket error'))
worker_response = recv_socket_data(worker_socket)
matched = False
for line in worker_response.split("\n"):
if '$2$' in line:
matched = True
hash = line.strip()
hash_out = re.search('\$2\$.+$', hash).group(0)
f = open("/access.inc", "w")
f.write('enable_password = "' + re.sub('[^0-9a-zA-Z\$]+', '', hash_out.rstrip()) + '";\n')
f.close()
container.restart()
if matched:
return jsonify(type='success', msg='command completed successfully')
else:
return jsonify(type='danger', msg='command did not complete')
except Exception as e:
return jsonify(type='danger', msg=str(e))
# elif request.json['cmd'] == 'mailman':
# if request.json['task'] == 'password':
# if request.json['email'] and request.json['passwd']:
# try:
# for container in docker_client.containers.list(filters={"id": container_id}):
# add_su = container.exec_run(["/bin/bash", "-c", "/opt/mm_web/add_su.py '" + request.json['passwd'].replace("'", "'\\''") + "' '" + request.json['email'].replace("'", "'\\''") + "'"], user='mailman')
# if add_su.exit_code == 0:
# return jsonify(type='success', msg='command completed successfully')
# else:
# return jsonify(type='danger', msg='command did not complete, exit code was ' + int(add_su.exit_code))
# except Exception as e:
# return jsonify(type='danger', msg=str(e))
else: else:
return jsonify(type='danger', msg='Unknown command') return jsonify(type='danger', msg='Unknown command')