You've already forked Mailu
mirror of
https://github.com/Mailu/Mailu.git
synced 2025-07-13 01:20:28 +02:00
First fetchmail implementation
This commit is contained in:
@ -26,4 +26,5 @@ from freeposte.admin.views import \
|
|||||||
base, \
|
base, \
|
||||||
aliases, \
|
aliases, \
|
||||||
users, \
|
users, \
|
||||||
domains
|
domains, \
|
||||||
|
fetches
|
||||||
|
@ -71,3 +71,15 @@ class AdminForm(Form):
|
|||||||
class ManagerForm(Form):
|
class ManagerForm(Form):
|
||||||
manager = fields.StringField('Manager address', [validators.Email()])
|
manager = fields.StringField('Manager address', [validators.Email()])
|
||||||
submit = fields.SubmitField('Submit')
|
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
|
# Many-to-many association table for domain managers
|
||||||
managers = db.Table('manager',
|
managers = db.Table('manager',
|
||||||
db.Column('domain_name', db.String(80), db.ForeignKey('domain.name')),
|
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')
|
domain = db.relationship(Domain, backref='aliases')
|
||||||
destination = db.Column(db.String(), nullable=False)
|
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)
|
||||||
|
9
admin/freeposte/admin/templates/fetch/create.html
Normal file
9
admin/freeposte/admin/templates/fetch/create.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{% extends "form.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Add a fetched account
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block subtitle %}
|
||||||
|
{{ user }}
|
||||||
|
{% endblock %}
|
9
admin/freeposte/admin/templates/fetch/edit.html
Normal file
9
admin/freeposte/admin/templates/fetch/edit.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{% extends "form.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Update a fetched account
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block subtitle %}
|
||||||
|
{{ user }}
|
||||||
|
{% endblock %}
|
44
admin/freeposte/admin/templates/fetch/list.html
Normal file
44
admin/freeposte/admin/templates/fetch/list.html
Normal file
@ -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>
|
||||||
|
<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>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ url_for('.user_fetchmail') }}">
|
<a href="{{ url_for('.fetch_list') }}">
|
||||||
<i class="fa fa-download"></i> <span>Fetched accounts</span>
|
<i class="fa fa-download"></i> <span>Fetched accounts</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -1 +0,0 @@
|
|||||||
{% extends "working.html" %}
|
|
@ -40,3 +40,13 @@ def get_alias(alias):
|
|||||||
if not alias.domain in flask_login.current_user.get_managed_domains():
|
if not alias.domain in flask_login.current_user.get_managed_domains():
|
||||||
return 403
|
return 403
|
||||||
return alias
|
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
|
||||||
|
68
admin/freeposte/admin/views/fetches.py
Normal file
68
admin/freeposte/admin/views/fetches.py
Normal file
@ -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(
|
return flask.redirect(
|
||||||
flask.url_for('.user_list', domain_name=user.domain.name))
|
flask.url_for('.user_list', domain_name=user.domain.name))
|
||||||
return flask.render_template('user/reply.html', form=form, user=user)
|
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
|
env_file: freeposte.env
|
||||||
volumes:
|
volumes:
|
||||||
- /data/webmail:/data
|
- /data/webmail:/data
|
||||||
|
|
||||||
|
fetchmail:
|
||||||
|
build: fetchmail
|
||||||
|
image: freeposte/fetchmail
|
||||||
|
env_file: freeposte.env
|
||||||
|
volumes:
|
||||||
|
- /data/freeposte:/data
|
||||||
|
13
fetchmail/Dockerfile
Normal file
13
fetchmail/Dockerfile
Normal file
@ -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"]
|
54
fetchmail/fetchmail.py
Executable file
54
fetchmail/fetchmail.py
Executable file
@ -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()
|
Reference in New Issue
Block a user