diff --git a/migrations/versions/0068d7a0fac0_.py b/migrations/versions/0068d7a0fac0_.py
new file mode 100644
index 0000000000000000000000000000000000000000..d539392599b3cf5ea8b8d644c2d92cddff0ef565
--- /dev/null
+++ b/migrations/versions/0068d7a0fac0_.py
@@ -0,0 +1,30 @@
+"""empty message
+
+Revision ID: 0068d7a0fac0
+Revises: 4bdc217932c3
+Create Date: 2017-03-04 02:27:14.915941
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = '0068d7a0fac0'
+down_revision = '4bdc217932c3'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.add_column('protocoltypes', sa.Column('allowed_networks', sa.String(), nullable=True))
+    op.add_column('protocoltypes', sa.Column('restrict_networks', sa.Boolean(), nullable=True))
+    # ### end Alembic commands ###
+
+
+def downgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.drop_column('protocoltypes', 'restrict_networks')
+    op.drop_column('protocoltypes', 'allowed_networks')
+    # ### end Alembic commands ###
diff --git a/models/database.py b/models/database.py
index 102a63d6bedba47e5cbfe546fc1b30eb567eb774..0788d1ec112b93cf6c3cd0651e9128713e478c8f 100644
--- a/models/database.py
+++ b/models/database.py
@@ -6,7 +6,7 @@ from io import StringIO, BytesIO
 from enum import Enum
 
 from shared import db, date_filter, date_filter_short, escape_tex, DATE_KEY, START_TIME_KEY, END_TIME_KEY
-from utils import random_string, url_manager, get_etherpad_url, split_terms
+from utils import random_string, url_manager, get_etherpad_url, split_terms, check_ip_in_networks
 from models.errors import DateNotMatchingException
 
 import os
@@ -36,6 +36,8 @@ class ProtocolType(db.Model):
     wiki_only_public = db.Column(db.Boolean)
     printer = db.Column(db.String)
     calendar = db.Column(db.String)
+    restrict_networks = db.Column(db.Boolean)
+    allowed_networks = db.Column(db.String)
 
     protocols = relationship("Protocol", backref=backref("protocoltype"), cascade="all, delete-orphan", order_by="Protocol.id")
     default_tops = relationship("DefaultTOP", backref=backref("protocoltype"), cascade="all, delete-orphan", order_by="DefaultTOP.number")
@@ -45,8 +47,9 @@ class ProtocolType(db.Model):
 
     def __init__(self, name, short_name, organization, usual_time,
             is_public, modify_group, private_group, public_group,
-            private_mail, public_mail,
-            use_wiki, wiki_category, wiki_only_public, printer, calendar):
+            private_mail, public_mail, use_wiki, wiki_category,
+            wiki_only_public, printer, calendar,
+            restrict_networks, allowed_networks):
         self.name = name
         self.short_name = short_name
         self.organization = organization
@@ -62,18 +65,22 @@ class ProtocolType(db.Model):
         self.wiki_only_public = wiki_only_public
         self.printer = printer
         self.calendar = calendar
+        self.restrict_networks = restrict_networks
+        self.allowed_networks = allowed_networks
 
     def __repr__(self):
         return ("<ProtocolType(id={}, short_name={}, name={}, "
                 "organization={}, is_public={}, modify_group={}, "
                 "private_group={}, public_group={}, use_wiki={}, "
                 "wiki_category='{}', wiki_only_public={}, printer={}, "
-                "usual_time={}, calendar='{}')>".format(
+                "usual_time={}, calendar='{}', restrict_networks={}, "
+                "allowed_networks='{}')>".format(
             self.id, self.short_name, self.name,
             self.organization, self.is_public, self.modify_group,
             self.private_group, self.public_group, self.use_wiki,
             self.wiki_category, self.wiki_only_public, self.printer,
-            self.usual_time, self.calendar))
+            self.usual_time, self.calendar, self.restrict_networks,
+            self.allowed_networks))
 
     def get_latest_protocol(self):
         candidates = sorted([protocol for protocol in self.protocols if protocol.is_done()], key=lambda p: p.date, reverse=True)
@@ -81,12 +88,18 @@ class ProtocolType(db.Model):
             return None
         return candidates[0]
 
-    @hybrid_method
     def has_public_view_right(self, user):
+        return (self.has_public_anonymous_view_right()
+            or (user is not None and self.has_public_authenticated_view_right(user)))
+
+    def has_public_anonymous_view_right(self):
         return (self.is_public
-            or (user is not None and 
-                ((self.public_group != "" and self.public_group in user.groups)
-                or (self.private_group != "" and self.private_group in user.groups))))
+            and (not self.restrict_networks 
+                or check_ip_in_networks(self.allowed_networks)))
+
+    def has_public_authenticated_view_right(self, user):
+        return ((self.public_group != "" and self.public_group in user.groups)
+            or (self.private_group != "" and self.private_group in user.groups))
 
     def has_private_view_right(self, user):
         return (user is not None and self.private_group != "" and self.private_group in user.groups)
diff --git a/requirements.txt b/requirements.txt
index 6cd3d29ea3a7eb551c7058bf79611ba5409c0206..28192423c36fbaa9fb8fa36998e84a8bbd692b6f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -29,7 +29,6 @@ MarkupSafe==0.23
 nose==1.3.7
 packaging==16.8
 pathtools==0.1.2
-pkg-resources==0.0.0
 psycopg2==2.6.2
 Pygments==2.2.0
 pyldap==2.4.28
diff --git a/server.py b/server.py
index 52c8ef156068c515b584e68e5e3f9c547eebe1a3..76722cc93bbd7156fc650809c67e3090ad0544e7 100755
--- a/server.py
+++ b/server.py
@@ -9,9 +9,9 @@ from flask_migrate import Migrate, MigrateCommand
 #from flask_socketio import SocketIO
 from celery import Celery
 from sqlalchemy import or_, and_
-#from apscheduler.schedulers.background import BackgroundScheduler
-#from apscheduler.triggers.cron import CronTrigger
-#from apscheduler.triggers.interval import IntervalTrigger
+from apscheduler.schedulers.background import BackgroundScheduler
+from apscheduler.triggers.cron import CronTrigger
+from apscheduler.triggers.interval import IntervalTrigger
 import atexit
 from io import StringIO, BytesIO
 import os
@@ -45,17 +45,6 @@ celery = make_celery(app, config)
 #    return socketio
 #socketio = make_socketio(app, config)
 
-#def make_scheduler(app, config, function):
-#    scheduler = BackgroundScheduler()
-#    scheduler.start()
-#    scheduler.add_job(
-#        func=function,
-#        trigger=CronTrigger(hour='*', minute=23),
-#        id="scheduler",
-#        name="Do an action regularly",
-#        replace_existing=True)
-#    atexit.register(scheduler.shutdown)
-
 app.jinja_env.trim_blocks = True
 app.jinja_env.lstrip_blocks = True
 app.jinja_env.filters["datify"] = date_filter
@@ -1227,11 +1216,25 @@ def logout():
     return redirect(url_for(".index"))
 
 def make_scheduler():
-    from uwsgidecorators import timer as uwsgitimer, signal as uwsgisignal, cron as uwsgicron
-    @uwsgicron(30, -1, -1, -1, -1, target="mule")
-    def uwsgi_timer(signum):
-        if signum == 0:
-            check_and_send_reminders()
+    try:
+        from uwsgidecorators import timer as uwsgitimer, signal as uwsgisignal, cron as uwsgicron
+        print("using uwsgi for cron-like tasks")
+        @uwsgicron(30, -1, -1, -1, -1, target="mule")
+        def uwsgi_timer(signum):
+            if signum == 0:
+                check_and_send_reminders()
+    except ModuleNotFoundError:
+        print("uwsgi not found, falling back to apscheduler for cron-like tasks")
+        def make_scheduler(app, config, function):
+            scheduler = BackgroundScheduler()
+            scheduler.start()
+            scheduler.add_job(
+                func=function,
+                trigger=CronTrigger(hour='*', minute=30),
+                id="scheduler",
+                name="Do an action regularly",
+                replace_existing=True)
+            atexit.register(scheduler.shutdown)
 
     def check_and_send_reminders():
         print("check and send reminders")
diff --git a/utils.py b/utils.py
index 80ce03fa438078a932bdfdbfc98d53334c80a9df..2457d376e202950f25f42ac5e628bc05b58e0282 100644
--- a/utils.py
+++ b/utils.py
@@ -11,6 +11,7 @@ from email.mime.application import MIMEApplication
 from datetime import datetime, date, timedelta
 import requests
 from io import BytesIO
+import ipaddress
 
 import config
 
@@ -170,3 +171,17 @@ def add_line_numbers(text):
             line
         ))
     return "\n".join(lines)
+
+def check_ip_in_networks(networks_string):
+    address = ipaddress.ip_address(request.remote_addr)
+    if address == ipaddress.ip_address("127.0.0.1") and "X-Real-Ip" in request.headers:
+        address = ipaddress.ip_address(request.headers["X-Real-Ip"])
+    print(address)
+    try:
+        for network_string in networks_string.split(","):
+            network = ipaddress.ip_network(network_string.strip())
+            if address in network:
+                return True
+        return False
+    except ValueError:
+        return False
diff --git a/views/forms.py b/views/forms.py
index 3049f5c4768b41ce496bb6d0670c229ccbfd18d9..27698ed7ab9cf4189e216b710451831e74bf2037 100644
--- a/views/forms.py
+++ b/views/forms.py
@@ -1,7 +1,9 @@
 from flask_wtf import FlaskForm
-from wtforms import StringField, PasswordField, BooleanField, DateField, HiddenField, IntegerField, SelectField, FileField, DateTimeField, TextAreaField
+from wtforms import StringField, PasswordField, BooleanField, DateField, HiddenField, IntegerField, SelectField, FileField, DateTimeField, TextAreaField, Field, widgets
 from wtforms.validators import InputRequired, Optional
 
+import ipaddress
+
 from models.database import TodoState
 from validators import CheckTodoDateByState
 from calendarpush import Client as CalendarClient
@@ -59,6 +61,36 @@ def coerce_todostate(key):
         key = TodoState[key_part]
     return key
 
+class IPNetworkField(Field):
+    widget = widgets.TextInput()
+
+    def __init__(self, label=None, validators=None, **kwargs):
+        super().__init__(label, validators, **kwargs)
+
+    def _value(self):
+        if self.raw_data:
+            print("raw_data in _value", self.raw_data)
+            return " ".join(self.raw_data)
+        else:
+            return self.data and str(self.data) or ""
+
+    def process_formdata(self, valuelist):
+        print("valuelist in process_formdata", valuelist)
+        if valuelist:
+            data_str = valuelist[0]
+            result_parts = []
+            try:
+                for part in data_str.split(","):
+                    part = part.strip()
+                    if len(part) > 0:
+                        network = ipaddress.ip_network(part)
+                        result_parts.append(network)
+            except ValueError as exc:
+                print(exc)
+                self.data = None
+                raise ValueError(self.gettext("Not a valid IP Network: {}".format(str(exc))))
+            self.data = ",".join(map(str, result_parts))
+
 class LoginForm(FlaskForm):
     username = StringField("Benutzer", validators=[InputRequired("Bitte gib deinen Benutzernamen ein.")])
     password = PasswordField("Passwort", validators=[InputRequired("Bitte gib dein Passwort ein.")])
@@ -79,6 +111,8 @@ class ProtocolTypeForm(FlaskForm):
     wiki_only_public = BooleanField("Wiki ist öffentlich")
     printer = SelectField("Drucker", choices=[])
     calendar = SelectField("Kalender", choices=[])
+    restrict_networks = BooleanField("Netzwerke einschränken")
+    allowed_networks = IPNetworkField("Erlaubte Netzwerke")
 
     def __init__(self, **kwargs):
         super().__init__(**kwargs)
diff --git a/views/tables.py b/views/tables.py
index f60995ecf2bb82765765ce033672a43b70875cbd..2922b03f0e051c7cf8b98559caa0753a90e5a97f 100644
--- a/views/tables.py
+++ b/views/tables.py
@@ -125,9 +125,10 @@ class ProtocolTypeTable(SingleValueTable):
         calendar_headers = ["Kalender"]
         if not config.CALENDAR_ACTIVE:
             calendar_headers = []
+        network_headers = ["Netzwerke einschränken", "Erlaubte Netzwerke"]
         action_headers = ["Aktion"]
         return (general_headers + mail_headers + printing_headers
-           + wiki_headers + calendar_headers + action_headers)
+           + wiki_headers + calendar_headers + network_headers + action_headers)
 
     def row(self):
         general_part = [
@@ -159,8 +160,13 @@ class ProtocolTypeTable(SingleValueTable):
         calendar_part = [self.value.calendar if self.value.calendar is not None else ""]
         if not config.CALENDAR_ACTIVE:
             calendar_part = []
+        network_part = [
+            Table.bool(self.value.restrict_networks),
+            self.value.allowed_networks
+        ]
         action_part = [Table.link(url_for("delete_type", type_id=self.value.id), "Löschen", confirm="Bist du dir sicher, dass du den Protokolltype {} löschen möchtest?".format(self.value.name))]
-        return general_part + mail_part + printing_part + wiki_part + calendar_part + action_part
+        return (general_part + mail_part + printing_part + wiki_part + 
+            calendar_part + network_part + action_part)
 
 class DefaultTOPsTable(Table):
     def __init__(self, tops, protocoltype=None):