First fetchmail implementation

master
Pierre Jaury 8 years ago
parent c56a51f7b7
commit 30ecbf81cd

@ -26,4 +26,5 @@ from freeposte.admin.views import \
base, \
aliases, \
users, \
domains
domains, \
fetches

@ -71,3 +71,15 @@ class AdminForm(Form):
class ManagerForm(Form):
manager = fields.StringField('Manager address', [validators.Email()])
submit = fields.SubmitField('Submit')
class FetchForm(Form):
protocol = fields.SelectField('Protocol', choices=[
('imap', 'IMAP'), ('pop3', 'POP3')
])
host = fields.StringField('Hostname or IP')
port = fields.IntegerField('TCP port')
tls = fields.BooleanField('Enable TLS')
username = fields.StringField('Username')
password = fields.StringField('Password')
submit = fields.SubmitField('Submit')

@ -10,7 +10,7 @@ import re
# Many-to-many association table for domain managers
managers = db.Table('manager',
db.Column('domain_name', db.String(80), db.ForeignKey('domain.name')),
db.Column('user_address', db.String(80), db.ForeignKey('user.address'))
db.Column('user_address', db.String(255), db.ForeignKey('user.address'))
)
@ -143,3 +143,19 @@ class Alias(Address):
"""
domain = db.relationship(Domain, backref='aliases')
destination = db.Column(db.String(), nullable=False)
class Fetch(Base):
""" A fetched account is a repote POP/IMAP account fetched into a local
account.
"""
id = db.Column(db.Integer(), primary_key=True)
user_address = db.Column(db.String(255), db.ForeignKey(User.address),
nullable=False)
user = db.relationship(User, backref='fetches')
protocol = db.Column(db.Enum('imap', 'pop3'), nullable=False)
host = db.Column(db.String(255), nullable=False)
port = db.Column(db.Integer(), nullable=False)
tls = db.Column(db.Boolean(), nullable=False)
username = db.Column(db.String(255), nullable=False)
password = db.Column(db.String(255), nullable=False)

@ -0,0 +1,9 @@
{% extends "form.html" %}
{% block title %}
Add a fetched account
{% endblock %}
{% block subtitle %}
{{ user }}
{% endblock %}

@ -0,0 +1,9 @@
{% extends "form.html" %}
{% block title %}
Update a fetched account
{% endblock %}
{% block subtitle %}
{{ user }}
{% endblock %}

@ -0,0 +1,44 @@
{% extends "base.html" %}
{% block title %}
Fetched accounts
{% endblock %}
{% block subtitle %}
{{ user }}
{% endblock %}
{% block main_action %}
<a class="btn btn-primary" href="{{ url_for('.fetch_create', user_address=user.address) }}">Add an account</a>
{% endblock %}
{% block box %}
<table class="table table-bordered">
<tbody>
<tr>
<th>Actions</th>
<th>Protocol</th>
<th>Hostname</th>
<th>Port</th>
<th>TLS</th>
<th>Username</th>
<th>Created</th>
<th>Last edit</th>
</tr>
{% for fetch in user.fetches %}
<tr>
<td>
<a href="{{ url_for('.fetch_edit', fetch_id=fetch.id) }}" title="Edit"><i class="fa fa-pencil"></i></a>&nbsp;
<a href="{{ url_for('.fetch_delete', fetch_id=fetch.id) }}" onclick="return confirm('Are you sure?')" title="Delete"><i class="fa fa-trash"></i></a>
</td>
<td>{{ fetch.protocol }}</td>
<td>{{ fetch.host }}</td>
<td>{{ fetch.port }}</td>
<td>{{ fetch.tls }}</td>
<td>{{ fetch.created_at }}</td>
<td>{{ fetch.updated_at or '' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

@ -24,7 +24,7 @@
</a>
</li>
<li>
<a href="{{ url_for('.user_fetchmail') }}">
<a href="{{ url_for('.fetch_list') }}">
<i class="fa fa-download"></i> <span>Fetched accounts</span>
</a>
</li>

@ -40,3 +40,13 @@ def get_alias(alias):
if not alias.domain in flask_login.current_user.get_managed_domains():
return 403
return alias
def get_fetch(fetch_id):
fetch = models.Fetch.query.filter_by(id=fetch_id).first()
if not fetch:
flask.abort(404)
if not fetch.user.domain in flask_login.current_user.get_managed_domains():
if not fetch.user == flask_login.current_user:
flask.abort(403)
return fetch

@ -0,0 +1,68 @@
from freeposte.admin import app, db, models, forms, utils
from flask.ext import login as flask_login
import os
import flask
import wtforms_components
@app.route('/fetch/list', methods=['GET', 'POST'], defaults={'user_address': None})
@app.route('/fetch/list/<user_address>', methods=['GET'])
@flask_login.login_required
def fetch_list(user_address):
user = utils.get_user(user_address, True)
return flask.render_template('fetch/list.html', user=user)
@app.route('/fetch/list', methods=['GET', 'POST'], defaults={'user_address': None})
@app.route('/fetch/create/<user_address>', methods=['GET', 'POST'])
@flask_login.login_required
def fetch_create(user_address):
user = utils.get_user(user_address)
form = forms.FetchForm()
if form.validate_on_submit():
fetch = models.Fetch(user=user)
fetch.protocol = form.protocol.data
fetch.host = form.host.data
fetch.port = form.port.data
fetch.tls = form.tls.data
fetch.username = form.username.data
fetch.password = form.password.data
db.session.add(fetch)
db.session.commit()
flask.flash('Fetch configuration created')
return flask.redirect(
flask.url_for('.fetch_create', user_address=user.address))
return flask.render_template('fetch/create.html', form=form)
@app.route('/fetch/edit/<fetch_id>', methods=['GET', 'POST'])
@flask_login.login_required
def fetch_edit(fetch_id):
fetch = utils.get_fetch(fetch_id)
form = forms.FetchForm(obj=fetch)
if form.validate_on_submit():
fetch.protocol = form.protocol.data
fetch.host = form.host.data
fetch.port = form.port.data
fetch.tls = form.tls.data
fetch.username = form.username.data
fetch.password = form.password.data
db.session.add(fetch)
db.session.commit()
flask.flash('Fetch configuration updated')
return flask.redirect(
flask.url_for('.fetch_list', user_address=fetch.user.address))
return flask.render_template('fetch/edit.html',
form=form, fetch=fetch)
@app.route('/fetch/delete/<fetch_id>', methods=['GET'])
@flask_login.login_required
def fetch_delete(fetch_id):
fetch = utils.get_fetch(fetch_id)
db.session.delete(fetch)
db.session.commit()
flask.flash('Fetch configuration delete')
return flask.redirect(
flask.url_for('.fetch_list', user_address=fetch.user.address))

@ -149,10 +149,3 @@ def user_reply(user_email):
return flask.redirect(
flask.url_for('.user_list', domain_name=user.domain.name))
return flask.render_template('user/reply.html', form=form, user=user)
@app.route('/user/fetchmail', methods=['GET', 'POST'], defaults={'user_email': None})
@app.route('/user/fetchmail/<user_email>', methods=['GET', 'POST'])
@flask_login.login_required
def user_fetchmail(user_email):
return flask.render_template('user/fetchmail.html')

@ -60,3 +60,10 @@ services:
env_file: freeposte.env
volumes:
- /data/webmail:/data
fetchmail:
build: fetchmail
image: freeposte/fetchmail
env_file: freeposte.env
volumes:
- /data/freeposte:/data

@ -0,0 +1,13 @@
FROM python:alpine
RUN apk add --update \
fetchmail \
&& rm -rf /var/cache/apk/*
COPY fetchmail.py /fetchmail.py
RUN mkdir /var/spool/mail \
&& chown mail: /var/spool/mail
USER mail
CMD ["/fetchmail.py"]

@ -0,0 +1,54 @@
#!/usr/bin/env python
import sqlite3
import time
import os
import tempfile
RC_LINE = """
poll {host} proto {protocol} port {port}
user "{username}" password "{password}"
smtphost "smtp"
smtpname {user_address}
{options}
"""
def fetchmail(fetchmailrc):
print(fetchmailrc)
with tempfile.NamedTemporaryFile() as handler:
handler.write(fetchmailrc.encode("utf8"))
handler.flush()
os.system("fetchmail -N -f '{}'".format(handler.name))
def run(cursor):
cursor.execute("""
SELECT user_address, protocol, host, port, tls, username, password
FROM fetch
""")
fetchmailrc = ""
for line in cursor.fetchall():
user_address, protocol, host, port, tls, username, password = line
options = "options ssl" if tls else ""
fetchmailrc += RC_LINE.format(
user_address=user_address,
protocol=protocol,
host=host,
port=port,
username=username,
password=password,
options=options
)
fetchmail(fetchmailrc)
if __name__ == "__main__":
db_path = os.environ.get("DB_PATH", "/data/freeposte.db")
connection = sqlite3.connect(db_path)
while True:
time.sleep(int(os.environ.get("FETCHMAIL_DELAY", 10)))
cursor = connection.cursor()
run(cursor)
cursor.close()
Loading…
Cancel
Save