You've already forked Mailu
							
							
				mirror of
				https://github.com/Mailu/Mailu.git
				synced 2025-10-30 23:37:43 +02:00 
			
		
		
		
	First fetchmail implementation
This commit is contained in:
		| @@ -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) | ||||
|   | ||||
							
								
								
									
										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> | ||||
|     </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> | ||||
|   | ||||
| @@ -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(): | ||||
|         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 | ||||
|   | ||||
							
								
								
									
										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( | ||||
|                 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 | ||||
|   | ||||
							
								
								
									
										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