first dockerized approach

This commit is contained in:
Dominik Chilla 2018-11-14 23:29:46 +01:00
parent 544dabe94c
commit 943e7bee04
17 changed files with 664 additions and 130 deletions

1
BASEOS Normal file
View File

@ -0,0 +1 @@
debian

View File

@ -1,2 +1,53 @@
# gulag
Gulag quarantine
config.json:
`{
"daemon":{
"listen_host": "127.0.0.1",
"listen_port": 5001
},
"trusted_proxies": {
"rprx01":[
"172.16.100.5", "fd00:100::5"
],
"rprx02":[
"172.16.100.6", "fd00:100::6"
]
},
"api_keys": {
"HIGHLY_SECURE_API_KEY": {
"user": "GULAG APP"
}
},
"uri_prefixes": {
"root": "https://<fqdn>/api/v1/",
"quarmails": "https://<fqdn>/api/v1/quarmails/",
"attachments": "https://<fqdn>/api/v1/attachments/"
},
"db":{
"server": "127.0.0.1",
"user": "root",
"password": "",
"name": "Gulag"
},
"cleaner":{
"retention_period": "12 hour",
"interval": 10
},
"importer":{
"interval": 10
}
}`
uwsgi.ini:
`[uwsgi]
processes = 4
cheaper = 1
cheaper-initial = 1
cheaper-step = 1
python-path = /app
wsgi-file = /app/uwsgi.py
pyargv = --config /config/config.json
socket = /socket/gulag_uwsgi.sock`

1
VERSION Normal file
View File

@ -0,0 +1 @@
18.11

BIN
app/.config.json.swp Normal file

Binary file not shown.

117
app/Gulag.py Normal file
View File

@ -0,0 +1,117 @@
import json,sys
import email,email.header
from GulagDB import GulagDB,GulagDBException
from GulagMailbox import IMAPmailbox,IMAPmailboxException
class GulagException(Exception):
message = None
def __init__(self,message):
self.message = message
class Gulag:
config = None
db = None
def __init__(self, path_to_config_file):
try:
with open(path_to_config_file, 'r') as f:
self.config = json.load(f)
f.close()
except:
raise GulagException("CONFIG-FILE-Exception: " + str(sys.exc_info()))
try:
self.db = GulagDB(
self.config['db']['server'],
self.config['db']['user'],
self.config['db']['password'],
self.config['db']['name']
)
except GulagDBException as e:
raise GulagException(e.message) from e
def import_quarmails(self):
# Alle Mailboxes durchiterieren und die Meta-Infos aller neuen (unseen)
# Nachrichten in die Datenbank importieren
for mailbox in self.db.get_mailboxes():
imap_mb = None
try:
imap_mb = IMAPmailbox(
mailbox['imap_server'],
mailbox['imap_user'],
mailbox['imap_pass'],
mailbox['imap_mailbox']
)
except IMAPmailboxException as e:
print(e.message)
continue
quarmail_ids = []
attachments = []
for unseen in imap_mb.get_unseen_messages():
uid = unseen['imap_uid']
msg = unseen['msg']
msg_size = len(str(msg))
r5321_from = email.header.decode_header(msg['Return-Path'])[0][0]
r5321_rcpts = email.header.decode_header(msg['X-Envelope-To-Blocked'])[0][0]
r5322_from = email.header.decode_header(msg['From'])[0][0]
subject = email.header.decode_header(msg['Subject'])[0][0]
msg_id = None
try:
msg_id = email.header.decode_header(msg['Message-ID'])[0][0]
except:
pass
date = None
try:
date = email.header.decode_header(msg['Date'])[0][0]
except:
pass
x_spam_status = email.header.decode_header(msg['X-Spam-Status'])[0][0]
r5321_rcpts = str(r5321_rcpts).lower()
r5321_rcpts = r5321_rcpts.replace(" ", "")
# Pro Envelope-RCPT einen Eintrag in die DB schreiben.
# Die E-Mail im IMAP-Backend existiert jedoch nur ein Mal und wird
# über die mailbox_id sowie die imap_uid mehrfach referenziert.
for r5321_rcpt in r5321_rcpts.split(","):
quarmail_id = self.db.add_quarmail({
'mx_queue_id': 'queue_id', 'env_from': r5321_from, 'env_rcpt': r5321_rcpt,
'hdr_cf': x_spam_status, 'hdr_from': r5322_from, 'hdr_subject': subject,
'hdr_msgid': msg_id, 'hdr_date': date, 'cf_meta': 'cf_meta',
'mailbox_id': 'quarantine@zwackl.de', 'imap_uid': uid, 'msg_size': msg_size
})
quarmail_ids.append(quarmail_id)
# Ende for rcpts
# Alle MIME-Parts durchiterieren und Attachments
# (MIME-Parts mit name/filename Attribut) extrahieren
for part in msg.walk():
if part.get_filename():
# ist ein Attachment
filename = email.header.decode_header(part.get_filename())
if filename[0][1]:
# Encoded
filename = filename[0][0].decode(filename[0][1])
else:
# Nicht encoded
filename = filename[0][0]
attach_id = self.db.add_attachment({
'filename': filename,
'content_type': part.get_content_type()
})
attachments.append(attach_id)
# Ende if part.get_filename()
# Ende for msg.walk()
# Ende for(unseen)
imap_mb.close()
# QuarMails und Attachments verknüpfen
if(len(attachments) > 0):
for quarmail_id in quarmail_ids:
for attachment_id in attachments:
self.db.quarmail2attachment(str(quarmail_id), str(attachment_id))
# Ende for get_mailboxes
def cleanup_quarmails(self):
print("Mails to expunge: " +
str(len(
self.db.get_deprecated_mails(self.config['cleaner']['retention_period'])
))
)

134
app/GulagDB.py Normal file
View File

@ -0,0 +1,134 @@
import mysql.connector as mariadb
class GulagDBException(Exception):
message = None
def __init__(self,message):
self.message = str(message)
class GulagDB:
conn = None
def __init__(self, server, user, password, name):
try:
self.conn = mariadb.connect(
host=server,
user=user,
password=password,
database=name
)
except mariadb.Error as e:
raise GulagDBException(e) from e
def close(self):
self.conn.close()
def get_mailboxes(self):
try:
cursor = self.conn.cursor()
cursor.execute("select * from Mailboxes;")
results = []
data = cursor.fetchall()
if data == None:
return results
desc = cursor.description
for tuple in data:
dict = {}
for (name, value) in zip(desc, tuple):
dict[name[0]] = value
results.append(dict)
return results
except mariadb.Error as e:
raise GulagDBException(e) from e
def add_quarmail(self, quarmail):
try:
cursor = self.conn.cursor()
cursor.execute("insert into QuarMails " +
"(mx_queue_id,env_from,env_rcpt,"+
"hdr_cf,hdr_from,hdr_subject,"+
"hdr_msgid,hdr_date,cf_meta,"+
"mailbox_id,imap_uid,msg_size) " +
"values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
(quarmail['mx_queue_id'],quarmail['env_from'],quarmail['env_rcpt'],
quarmail['hdr_cf'],quarmail['hdr_from'],quarmail['hdr_subject'],
quarmail['hdr_msgid'],quarmail['hdr_date'],quarmail['cf_meta'],
quarmail['mailbox_id'],quarmail['imap_uid'],quarmail['msg_size']
)
)
self.conn.commit()
return cursor.lastrowid
except mariadb.Error as e:
raise GulagDBException(e) from e
def del_quarmail(self, id):
try:
cursor = self.conn.cursor()
cursor.execute("delete from QuarMails where id=%s;", (id))
self.conn.commit()
return cursor.lastrowid
except mariadb.Error as e:
raise GulagDBException(e) from e
def get_quarmails(self, mailbox_id):
try:
cursor = self.conn.cursor()
cursor.execute(
"select * from QuarMails where mailbox_id='%s';",
(mailbox_id)
)
results = []
data = cursor.fetchall()
if data == None:
return results
desc = cursor.description
for tuple in data:
dict = {}
for (name, value) in zip(desc, tuple):
dict[name[0]] = value
results.append(dict)
return results
except mariadb.Error as e:
raise GulagDBException(e) from e
def get_deprecated_mails(self,retention_period):
try:
cursor = self.conn.cursor()
query = "select ctime,mailbox_id,imap_uid from QuarMails where ctime < date_sub(NOW(), INTERVAL "+ retention_period +");"
cursor.execute(query)
results = []
data = cursor.fetchall()
if data == None:
return results
desc = cursor.description
for tuple in data:
dict = {}
for (name, value) in zip(desc, tuple):
dict[name[0]] = value
results.append(dict)
return results
except mariadb.Error as e:
raise GulagDBException(e) from e
def add_attachment(self, attach):
try:
cursor = self.conn.cursor()
cursor.execute("insert into Attachments " +
"(filename, content_type) values (%s,%s)",
(attach['filename'], attach['content_type'])
)
self.conn.commit()
return cursor.lastrowid
except mariadb.Error as e:
raise GulagDBException(e) from e
def quarmail2attachment(self,quarmail_id,attachment_id):
try:
cursor = self.conn.cursor()
cursor.execute("insert into QuarMail2Attachment " +
"(quarmail_id, attachment_id) values (%s,%s)",
(quarmail_id, attachment_id)
)
self.conn.commit()
except mariadb.Error as e:
raise GulagDBException(e) from e

89
app/GulagMailbox.py Normal file
View File

@ -0,0 +1,89 @@
import imaplib
import email
import email.header
class IMAPmailboxException(Exception):
message = None
def __init__(self,message):
self.message = str(message)
class IMAPmailbox:
imap_server = None
imap_user = None
imap_pass = None
imap_mailbox = None
mailbox = None
def __init__(self, imap_server, imap_user, imap_pass, imap_mailbox):
self.imap_server = imap_server
self.imap_user = imap_user
self.imap_pass = imap_pass
self.imap_mailbox = imap_mailbox
try:
self.mailbox = imaplib.IMAP4(self.imap_server)
rv, data = self.mailbox.login(self.imap_user, self.imap_pass)
except imaplib.IMAP4.error as e:
raise IMAPmailboxException(
"LOGIN FAILED FOR " + self.imap_user + '@' + self.imap_server
) from e
except ConnectionRefusedError as e:
raise IMAPmailboxException(
self.imap_user + ": IMAP server " + self.imap_server + " refused connection"
) from e
rv, data = self.mailbox.select(self.imap_mailbox)
if rv != 'OK':
raise IMAPmailboxException(
"ERROR: Unable to select mailbox: " + self.imap_mailbox
)
def close(self):
self.mailbox.close()
self.mailbox.logout()
def get_unseen_messages(self):
results = []
rv, data = self.mailbox.uid('SEARCH', 'UNSEEN')
if rv != 'OK':
return
for uid in data[0].split():
rv, data = self.mailbox.uid('FETCH', uid, '(RFC822)')
if rv != 'OK':
print("ERROR getting message", str(uid))
continue
results.append({
'imap_uid': uid,
'msg': email.message_from_bytes(data[0][1])
})
return results
def get_message(self,imap_uid):
rv, data = self.mailbox.uid('FETCH', imap_uid, '(RFC822)')
if rv != 'OK':
raise IMAPmailboxException("ERROR getting message: %s", str(imap_uid))
return email.message_from_bytes(data[0][1])
def get_attachments(self,imap_uid):
results = []
rv, data = self.mailbox.uid('FETCH', imap_uid, '(RFC822)')
if rv != 'OK':
raise IMAPmailboxException("ERROR getting message: %s", str(imap_uid))
msg = email.message_from_bytes(data[0][1])
for part in msg.walk():
if part.get_filename():
# ist ein Attachment
filename = email.header.decode_header(part.get_filename())
if filename[0][1]:
# Encoded
filename = filename[0][0].decode(filename[0][1])
else:
# Nicht encoded
filename = filename[0][0]
results.append({
'filename': filename,
'content-type': part.get_content_type(), # Ist das wirklich wahr?
'content': part.get_payload(decode=True)
})
# Ende if part.get_filename()
return results

37
app/Resources.py Normal file
View File

@ -0,0 +1,37 @@
from flask import request
from flask_restful import Resource, Api, abort
import json
class GulagResource(Resource):
gulag = None
def __init__(self,gulag_object):
self.gulag = gulag_object
#XXX self.check_trusted_proxy()
#XXX self.check_auth()
def check_trusted_proxy(self):
remote_ip = request.remote_addr
if 'trusted_proxies' not in self.gulag.config:
# Trusted-proxies not configured
return True
for proxy in self.gulag.config['trusted_proxies']:
for trusted_proxy_ip in self.gulag.config['trusted_proxies'][proxy]:
if(remote_ip == trusted_proxy_ip):
return True
abort(403, message="Untrusted client IP-address!")
def check_auth(self):
if not 'API-KEY' in request.headers:
abort(400, message="API-KEY header missing!")
api_key = request.headers['API-KEY']
if api_key not in self.gulag.config['api_keys']:
abort(401, message="NOT AUTHORIZED!")
class ResRoot(GulagResource):
def get(self):
return {"resource": "root :)"}
class ResQuarMails(GulagResource):
def get(self):
return {"abc": "1234"}
# return self.gulag.get_quarmails()

77
app/gulag_server.py Executable file
View File

@ -0,0 +1,77 @@
#!/usr/bin/env python3
import argparse,sys,os,time,signal
from flask import Flask
from flask_restful import Api
from Gulag import Gulag,GulagException
from Resources import ResRoot,ResQuarMails
parser = argparse.ArgumentParser()
parser.add_argument('--config', required=True, help="Path to config file")
args = parser.parse_args()
#child_pids = []
#importer_pid = os.fork()
#if(importer_pid == 0):
# # Child process: importer
# try:
# gulag = Gulag(args.config)
# except GulagException as e:
# print(e.message)
# sys.exit(1)
# while True:
# try:
# gulag.import_quarmails()
# except GulagException as e:
# print("Importer-Exception: " + e.message)
# time.sleep(gulag.config['importer']['interval'])
#
#cleaner_pid = os.fork()
#if(cleaner_pid == 0):
# # Child process: cleaner
# try:
# gulag = Gulag(args.config)
# except GulagException as e:
# print(e.message)
# sys.exit(1)
# while True:
# try:
# gulag.cleanup_quarmails()
# except GulagException as e:
# print("Cleaner-Exception: " + e.message)
# time.sleep(gulag.config['cleaner']['interval'])
# Parent
#child_pids.append(importer_pid)
#child_pids.append(cleaner_pid)
try:
try:
gulag = Gulag(args.config)
except GulagException as e:
raise Exception(e.message) from e
app = Flask(__name__)
api = Api(app, catch_all_404s=True)
api.add_resource(ResRoot,
'/api/v1/',
resource_class_kwargs={'gulag_object': gulag}
)
api.add_resource(ResQuarMails,
'/api/v1/quarmails/',
resource_class_kwargs={'gulag_object': gulag}
)
if __name__ == '__main__':
app.run(debug=False,
# will be overriden by uwsgi.ini
host=gulag.config['daemon']['listen_host'],
port=gulag.config['daemon']['listen_port']
)
gulag.db.close()
sys.exit(0)
except:
print("MAIN-EXCEPTION: " + str(sys.exc_info()))
# # Destroy childs
# for child_pid in child_pids:
# print("Killing child pid: %s", child_pid)
# os.kill(child_pid, signal.SIGTERM)

5
app/uwsgi.py Executable file
View File

@ -0,0 +1,5 @@
from gulag_server import app as application
if __name__ == "__main__":
application.run()

37
config/config.json Normal file
View File

@ -0,0 +1,37 @@
{
"daemon":{
"listen_host": "127.0.0.1",
"listen_port": 5001
},
"trusted_proxies": {
"rprx01":[
"172.16.100.5", "fd00:100::5"
],
"rprx02":[
"172.16.100.6", "fd00:100::6"
]
},
"api_keys": {
"HIGHLY_SECURE_API_KEY": {
"user": "GULAG APP"
}
},
"uri_prefixes": {
"root": "https://<fqdn>/api/v1/",
"quarmails": "https://<fqdn>/api/v1/quarmails/",
"attachments": "https://<fqdn>/api/v1/attachments/"
},
"db":{
"server": "127.0.0.1",
"user": "root",
"password": "",
"name": "Gulag"
},
"cleaner":{
"retention_period": "12 hour",
"interval": 10
},
"importer":{
"interval": 10
}
}

9
config/uwsgi.ini Normal file
View File

@ -0,0 +1,9 @@
[uwsgi]
processes = 4
cheaper = 1
cheaper-initial = 1
cheaper-step = 1
python-path = /app
wsgi-file = /app/uwsgi.py
pyargv = --config /config/config.json
socket = /socket/gulag_uwsgi.sock

53
db/gulag.sql Normal file
View File

@ -0,0 +1,53 @@
create database Gulag;
create table Mailboxes(
email_address varchar(767) not null primary key collate 'ascii_general_ci',
name varchar(256) not null,
imap_server varchar(256) not null default '127.0.0.1',
imap_security varchar(32) not null default 'plain',
imap_user varchar(256) not null,
imap_pass varchar(256) not null,
imap_mailbox varchar(256) not null default 'INBOX',
imap_mailbox_fp varchar(256) not null default 'false-positives',
imap_separator varchar(4) not null default '/'
comment varchar(256) default null
)ENGINE = InnoDB;
insert into Mailboxes (email_address,name,imap_user,imap_pass,)
values('quarantine-in@example.org','E-Mail inbound quarantine','quarantine-in','quarantine-in_secure_password');
insert into Mailboxes (email_address,name,imap_user,imap_pass)
values('quarantine-out@example.org','E-Mail outbound quarantine','quarantine-out','quarantine-out_secure_password');
insert into Mailboxes (email_address,name,imap_user,imap_pass)
values('quarantine-sandbox@example.org','E-Mail sandbox quarantine','quarantine-sb','quarantine-sb_secure_password');
create table QuarMails (
id int unsigned auto_increment primary key,
ctime TIMESTAMP,
mx_queue_id varchar(64) not null,
env_from varchar(256) not null,
env_rcpt varchar(256) not null,
hdr_cf TEXT,
hdr_from varchar(256) default null,
hdr_subject varchar(1024) default null,
hdr_msgid varchar(512) default null,
hdr_date varchar(128) default null,
cf_meta TEXT default null,
mailbox_id varchar(256) not null collate 'ascii_general_ci',
foreign key (mailbox_id) references Mailboxes (email_address) on update cascade on delete cascade,
imap_uid int unsigned not null,
msg_size int unsigned not null
)ENGINE = InnoDB;
create table Attachments (
id int unsigned auto_increment primary key,
filename varchar(256) not null,
content_type varchar(256) not null,
comment varchar(256)
)ENGINE = InnoDB;
create table QuarMail2Attachment (
quarmail_id int unsigned,
attachment_id int unsigned,
foreign key (quarmail_id) references QuarMails (id) on delete cascade on update cascade,
foreign key (attachment_id) references Attachments (id) on delete cascade on update cascade
)ENGINE = InnoDB;

33
docker-build.sh Executable file
View File

@ -0,0 +1,33 @@
#!/bin/sh
BRANCH="$(/usr/bin/git branch|/bin/grep \*|/usr/bin/awk {'print $2'})"
VERSION="$(/bin/cat VERSION)"
BASEOS="$(/bin/cat BASEOS)"
#REGISTRY="some-registry.invalid"
GO=""
while getopts g opt
do
case $opt in
g) GO="go";;
esac
done
if [ -z "${GO}" ] ; then
echo "Building GULAG@docker on '${BASEOS}' for version '${VERSION}' in branch '${BRANCH}'!"
echo "GO serious with '-g'!"
exit 1
fi
IMAGES="gulag-server gulag-db"
for IMAGE in ${IMAGES}; do
/usr/bin/docker build -t "${IMAGE}/${BASEOS}:${VERSION}_${BRANCH}" -f "docker/${IMAGE}/${BASEOS}/Dockerfile" .
# /usr/bin/docker tag "${IMAGE}/${BASEOS}:${VERSION}_${BRANCH}" "${REGISTRY}/${IMAGE}/${BASEOS}:${VERSION}_${BRANCH}"
done
#/bin/echo "Push images to registry:"
#for IMAGE in ${IMAGES}; do
# /bin/echo "/usr/bin/docker push ${REGISTRY}/${IMAGE}/${BASEOS}:${VERSION}_${BRANCH}"
#done

View File

@ -0,0 +1,5 @@
FROM debian
RUN apt update && \
apt -yq --no-install-recommends install procps net-tools mariadb-server mariadb-client
COPY db/gulag.sql /.
CMD ["/usr/bin/mysqld_safe"]

View File

@ -0,0 +1,15 @@
FROM debian
LABEL maintainer="Dominik Chilla"
ENV DEBIAN_FRONTEND=noninteractive \
TZ=Europe/Berlin
RUN set -ex ; \
apt-get -qq update \
&& apt-get -qq --no-install-recommends install \
uwsgi-plugin-python3 python3-setuptools python3-flask \
python3-flask-restful python3-mysql.connector \
uwsgi uwsgi-plugin-python3
RUN /bin/mkdir /config /socket /app
COPY app/*.py /app/

View File

@ -1,130 +0,0 @@
#!/usr/bin/env python3
import sys
import imaplib
import email
import email.header
import json
import fcntl
import uuid
import argparse
DB_FILE = "quar_db.json"
def process_mailbox(MAILBOX):
db = []
try:
f = open(DB_FILE, 'r')
fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
db = json.load(f)
fcntl.flock(f, fcntl.LOCK_UN)
f.close()
except FileNotFoundError:
pass
except:
print("DB-file-open-exception: " + str(sys.exc_info()))
sys.exit(1)
rv, data = MAILBOX.uid('SEARCH', 'UNSEEN')
if rv != 'OK':
return
# Alle "neuen" Nachrichten durchiterieren und
# Meta-Infos in die Datenbank importieren
for uid in data[0].split():
uid = uid.decode()
rv, data = MAILBOX.uid('FETCH', uid, '(RFC822)')
if rv != 'OK':
print("ERROR getting message", uid)
sys.exit(1)
msg = email.message_from_bytes(data[0][1])
msg_size = len(str(msg))
attachments = []
# Alle MIME-Parts durchiterieren und Attachments
# (MIME-Parts mit name/filename Attribut) extrahieren
for part in msg.walk():
if part.get_filename():
# ist ein Attachment
filename = email.header.decode_header(part.get_filename())
if filename[0][1]:
# Encoded
filename = filename[0][0].decode(filename[0][1])
else:
# Nicht encoded
filename = filename[0][0]
attachments.append({
'filename': filename,
'content-type': part.get_content_type() # Ist das wirklich wahr?
})
# Ende if part.get_filename()
# Ende for ... msg.walk()
r5321_from = email.header.decode_header(msg['Return-Path'])[0][0]
r5321_rcpts = email.header.decode_header(msg['X-Envelope-To-Blocked'])[0][0]
r5322_from = email.header.decode_header(msg['From'])[0][0]
subject = email.header.decode_header(msg['Subject'])[0][0]
quar_id = str(uuid.uuid4())
msg_id = None
try:
msg_id = email.header.decode_header(msg['Message-ID'])[0][0]
except:
pass
x_spam_status = email.header.decode_header(msg['X-Spam-Status'])[0][0]
r5321_rcpts = str(r5321_rcpts).lower()
r5321_rcpts = r5321_rcpts.replace(" ", "")
db.append({
'quarantine_id': quar_id, # AUTO_INCREMENT
'envelope_sender': r5321_from,
'envelope_recipients': r5321_rcpts,
'from_header': r5322_from,
'subject': subject,
'message_id': msg_id,
'imap_uid': uid,
'spam_status': x_spam_status,
'msg_size': msg_size,
'attachments': attachments
})
# Ende for(unseen)
try:
f = open(DB_FILE, 'w')
fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
json.dump(db, f)
fcntl.flock(f, fcntl.LOCK_UN)
f.close()
except:
print("DB-file-open-exception: " + str(sys.exc_info()))
sys.exit(1)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--config', required=True, help="Path to config file")
args = parser.parse_args()
config = {}
try:
with open(args.config, 'r') as f:
config = json.load(f)
f.close()
except:
print("CONFIG-FILE-Exception: " + str(sys.exc_info()))
sys.exit(1)
# INBOX = imaplib.IMAP4('mbox02.zwackl.local')
INBOX = imaplib.IMAP4(config['imap']['server'])
try:
# rv, data = INBOX.login(EMAIL_ACCOUNT, EMAIL_PASS)
rv, data = INBOX.login(
config['imap']['user'], config['imap']['password']
)
except imaplib.IMAP4.error:
print ("LOGIN FAILED!!! ")
sys.exit(1)
# rv, data = INBOX.select(EMAIL_FOLDER)
rv, data = INBOX.select(config['imap']['folder'])
if rv == 'OK':
process_mailbox(INBOX)
INBOX.close()
else:
print("ERROR: Unable to open mailbox ", rv)
INBOX.logout()
sys.exit(0)