from django.shortcuts import render, get_object_or_404, redirect from django.http import HttpResponse from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm from django.contrib.auth.forms import AdminPasswordChangeForm from django.db.models import Q from django.views.decorators.csrf import csrf_exempt from django.utils import timezone from django.core.files import File from django.db.models.fields.files import FieldFile from django.contrib.auth.models import User from django.contrib.auth.decorators import login_required, user_passes_test from django.db.models.signals import post_save from django.dispatch import receiver from django.conf import settings from django.contrib.auth.forms import PasswordResetForm from django.urls import reverse from .models import Device, Organization, Network, Model, Wifi, UserStatus from .signals import * from .forms import * from .device import makewificonfig, heartbeathandler from .tasks import mkfirmware from distutils.dir_util import copy_tree from celery.exceptions import TimeoutError from subprocess import call as execute import glob import sys import subprocess import os import socket import tempfile import crypt import tarfile from datetime import datetime import time import uuid def is_superuser(user): return user.is_superuser def is_staff(user): return user.is_staff @login_required def index(request): return redirect(reverse("devices")) @csrf_exempt def heartbeat(request): device = get_object_or_404(Device, secret=request.POST.get("secret", "")) ip = request.POST.get("ip", "") return HttpResponse(heartbeathandler(device, ip)) @csrf_exempt def wireless(request): device = get_object_or_404(Device, secret=request.POST.get("secret", "")) device.wireless = timezone.now() device.save() return HttpResponse(makewificonfig(device)) @csrf_exempt def hosts(request): device = get_object_or_404(Device, secret=request.POST.get("secret", "")) sigRebootDevice(device.serial, None, False) device.reboot = False device.save() return render(request, "manager/hosts", {"device": device}) @csrf_exempt def update(request): FWDIR = "/opt/vpnmanager/images/" device = get_object_or_404(Device, secret=request.POST.get("secret", "")) try: process = mkfirmware.apply_async((device, FWDIR), serializer='pickle') if not process.get(timeout=300): return HttpResponse(status=503) except TimeoutError: return HttpResponse(status=503) sigUpdateDevice(device.serial, None, False) device.update = False device.save() with open("%s/%s.bin" % (FWDIR, device.id), "rb") as download: response = HttpResponse( download.read(), content_type="application/octet-stream" ) response['Content-Disposition'] = ( 'inline; ' 'filename=%s.bin' % device.serial ) return response def ping(request, device_id): if request.user.is_authenticated: try: device = Device.objects.get( id=device_id, organization__in=request.user.organization_set.all() ) except Exception: device = None ajax = '{\n "status": ' if not device: status = "-1" else: tz = timezone.get_current_timezone() if device.lasttime: naive = timezone.make_naive(device.lasttime, tz) tt = naive.timetuple() lasttime = str(int(time.mktime(tt)) * 1000) else: lasttime = None if device.lastbeat: naive = timezone.make_naive(device.lastbeat, tz) tt = naive.timetuple() lastbeat = str(int(time.mktime(tt)) * 1000) else: lastbeat = None status = None try: if device.curip: try: socket.inet_aton(device.curip) except Exception: status = -3 else: if device.lasttime: diff = timezone.now() - device.lasttime secs = diff.total_seconds() if secs < 300: cmd = "ping -c1 -w1 %s >/dev/null 2>&1" status = os.system(cmd % device.curip) if not os.WEXITSTATUS(status): status = 1 if not status: if (not device.lasttime) or secs > 120: diff = timezone.now() - device.lastbeat secs = diff.total_seconds() if secs < 60: status = 2 if not status: status = 0 except Exception as e: status = str(e) status += "-4" finally: ajax += str(status) ajax += ',\n "serial": "%s"' % device.serial ajax += ',\n "name": "%s"' % (device.name or "") ajax += ',\n "ip": "%s"' % (device.curip or "") ajax += ',\n "time": "%s"' % lasttime ajax += ',\n "lastbeat": "%s"' % lastbeat ajax += ',\n "reboot": %i' % (1 if device.reboot else 0) ajax += ',\n "update": %i' % (1 if device.update else 0) ajax += ',\n "network": {' ajax += '\n "intip": "%s"' % device.network.intip ajax += ',\n "extip": "%s"' % device.network.extip ajax += ',\n "name": "%s"' % (device.network.name or "") ajax += '\n }' else: ajax += "-2" ajax += "\n}" return HttpResponse(ajax, content_type="application/json") @login_required def devices(request, code=200, exception=None): return render( request, "manager/index.html", { "title": "Device Administration", "exception": exception }, status=code ) @login_required def editdevice(request, device_id): post = request.method == "POST" device = get_object_or_404( Device, id=device_id, organization__in=request.user.organization_set.all() ) subnets = Network.objects.filter( organization=device.organization ) wifis = Wifi.objects.filter( organization=device.organization ) form = DeviceForm( request.POST if post else None, instance=device ) form.fields["mac"].disabled = True form.fields["serial"].disabled = True form.fields["secret"].disabled = True form.fields["model"].disabled = True form.fields["password"].disabled = True if post and form.is_valid(): form.save() return redirect(reverse("devices")) return render( request, "manager/form.html", { "title": "Edit Device", "form": form } ) @user_passes_test(is_superuser) def makeuser(request): if request.POST.get("username", ""): user = User.objects.create_user( username=request.POST.get("username", ""), password=str(uuid.uuid4().hex), first_name=request.POST.get("firstname", ""), last_name=request.POST.get("lastname", ""), is_staff=request.POST.get("staff", "0") == "True", is_superuser=request.POST.get("superuser", "0") == "True", email=request.POST.get("email", "") ) user.organization_set.set(set(request.POST.getlist("orga", []))) form = PasswordResetForm({"email": user.email}) if form.is_valid(): form.save( request=request, use_https=True, email_template_name='registration/add_user.html') return redirect(reverse("users")) else: return render(request, "manager/adduser.html", {"title": "Add User"}) @login_required def edituser(request, user_id): if request.user.is_staff or request.user.id == user_id: orgas = request.user.organization_set.all() user = User.objects.distinct().get(id=user_id, organization__in=orgas) if not user: return redirect(reverse("users")) if request.POST.get("form", ""): newfirst = request.POST.get("firstname", "") newlast = request.POST.get("lastname", "") if newlast != user.last_name or newfirst != user.first_name: sigRenameUser( user.username, request.user.username, "%s %s" % (user.first_name, user.last_name), "%s %s" % (newfirst, newlast) ) user.first_name = request.POST.get("firstname", "") user.last_name = request.POST.get("lastname", "") if request.user.is_staff or request.user.is_superuser: newstaff = request.POST.get("staff", "0") == "True" if newstaff != user.is_staff: sigStaffUser( user.username, request.user.username, newstaff ) user.is_staff = newstaff if request.user.is_superuser: newsuper = request.POST.get("superuser", "0") == "True" if newsuper != user.is_superuser: sigSuperUser( user.username, request.user.username, newsuper) user.is_superuser = newsuper neworgas = set(request.POST.getlist("orga", [])) oldorgas = set(user.organization_set.all()) if neworgas != oldorgas: sigOrgaUser( user.username, request.user.username, oldorgas, neworgas ) user.organization_set.set(neworgas) newmail = request.POST.get("email", "") if newmail != user.email: sigMailUser( user.username, request.user.username, user.email, newmail ) user.email = newmail user.save() return redirect(reverse("users")) return render( request, "manager/edituser.html", { "title": "Edit User", "auser": user } ) return redirect( reverse( "two_factor:login", kwargs={ 'next': request.path } ) ) @login_required def editwifi(request, wifi_id): userorgas = request.user.organization_set.all() wifi = get_object_or_404( Wifi, id=wifi_id, organization__in=userorgas ) form = WifiForm( request.POST if request.method == "POST" else None, instance=wifi ) form.fields["organization"].queryset = userorgas if request.method == "POST" and form.is_valid(): form.save() return redirect(reverse("wifi")) return render( request, "manager/form.html", { "title": "Edit WiFi", "form": form } ) @user_passes_test(is_superuser) def getconfig(request, device_id): FWDIR = "/opt/vpnmanager/images/" device = get_object_or_404(Device, id=device_id) files = glob.glob("%s/%s.bin" % (FWDIR, device_id)) if not files: mtime = os.path.getmtime(files[0]) tz = timezone.get_current_timezone() if not datetime.fromtimestamp(mtime, tz) > device.model.firmware: work = mkfirmware.apply_async((device, FWDIR), serializer='pickle') if not work.get(timeout=300): return HttpResponse(status=503) sigUpdateDevice(device.serial, None, False) device.update = False device.save() with open("%s/%s.bin" % (FWDIR, device.id), "rb") as download: response = HttpResponse( download.read(), content_type="application/octet-stream" ) response['Content-Disposition'] = ( 'inline; ' 'filename=%s.bin' % device.serial ) return response @login_required def rebootdevice(request, device_id): device = get_object_or_404( Device, id=device_id, organization__in=request.user.organization_set.all() ) if not device.reboot: sigRebootDevice(device.serial, request.user.username, True) device.reboot = True device.save() return redirect(reverse("devices")) @user_passes_test(is_staff) def updatedevice(request, device_id): device = get_object_or_404( Device, id=device_id, organization__in=request.user.organization_set.all() ) if not device.update: sigUpdateDevice(device.serial, request.user.username, True) device.update = True device.save() return redirect(reverse("devices")) @user_passes_test(is_superuser) def deletedevice(request, device_id): CADIR = "/etc/openvpn/ca/" BEFORE = os.getcwd() device = get_object_or_404(Device, id=device_id) os.chdir(CADIR) execute(CADIR + "/revoke " + device.serial, shell=True) os.system("rm " + CADIR + "/keys/" + device.serial + ".{crt,csr,key}") os.chdir(BEFORE) device.delete() return redirect(reverse("devices")) @user_passes_test(is_staff) def deletewifi(request, wifi_id): wifi = get_object_or_404( Wifi, id=wifi_id, organization__in=request.user.organization_set.all() ) wifi.delete() return redirect(reverse("wifi")) @user_passes_test(is_superuser) def deleteuser(request, user_id): user = get_object_or_404(User, id=user_id) user.delete() return redirect(reverse("users")) @user_passes_test(is_superuser) def deletenetwork(request, network_id): network = get_object_or_404(Network, id=network_id) network.delete() return redirect(reverse("networks")) @user_passes_test(is_superuser) def deleteorga(request, orga_id): orga = get_object_or_404(Organization, id=orga_id) orga.delete() return redirect(reverse("organizations")) @login_required def makewifi(request): post = request.method == "POST" postdata = request.POST if post else None organization = request.user.userstatus.orga initial = {"organization": organization} if not post else None form = WifiForm(postdata, initial=initial) form.fields["organization"].queryset = request.user.organization_set.all() if request.method == "POST" and form.is_valid(): form.save() return redirect(reverse("wifi")) return render( request, "manager/form.html", { "title": "Add WiFi", "form": form } ) @user_passes_test(is_superuser) def makenetwork(request): if request.method == "POST": form = NetworkForm(request.POST) if form.is_valid(): form.save() return redirect(reverse("networks")) else: form = NetworkForm() return render( request, "manager/form.html", { "title": "Add Network", "form": form } ) @user_passes_test(is_superuser) def editnetwork(request, network_id): net = get_object_or_404(Network, id=network_id) postdata = request.POST if request.method == "POST" else None form = NetworkForm(postdata, instance=net) form.fields["intip"].disabled = True form.fields["extip"].disabled = True if request.method == "POST" and form.is_valid(): form.save() return redirect(reverse("networks")) return render( request, "manager/form.html", { "title": "Edit Network", "form": form } ) @login_required def setactiveorga(request, orga_id): request.user.userstatus.orga = get_object_or_404( Organization, id=orga_id, users=request.user ) request.user.userstatus.save() return HttpResponse("") @user_passes_test(is_superuser) def makeorganization(request): if request.method == "POST": form = OrgaForm(request.POST) if form.is_valid(): orga = form.save() request.user.organization_set.add(orga) Network.objects.get(intip="No VPN").organization.add(orga) return redirect(reverse("organizations")) else: form = OrgaForm() return render( request, "manager/form.html", { "title": "Add Organization", "form": form } ) @user_passes_test(is_superuser) def editorganization(request, orga_id): orga = get_object_or_404(Organization, id=orga_id) if request.method == "POST": form = OrgaForm(request.POST, instance=orga) if form.is_valid(): form.save() return redirect(reverse("organizations")) else: form = OrgaForm(instance=orga) return render( request, "manager/form.html", { "title": "Change Organization", "form": form } ) @user_passes_test(is_superuser) def makedevice(request): CADIR = "/etc/openvpn/ca/" CONFIGDIR = "/etc/openvpn/client-configs/" BEFORE = os.getcwd() device_serial = request.POST.get("serial", "").replace(" ", "_") device_name = request.POST.get("name", "") device_organization = request.POST.get("organization", "") device_model = request.POST.get("model", "") device_ssid = request.POST.get("ssid", device_serial) device_key = request.POST.get("key", "") device_mac = request.POST.get("mac", "") if not device_serial: orga = Organization.objects.all() models = Model.objects.all() return render(request, "manager/add.html", { "title": "Add Device", "organizations": orga, "models": models } ) if glob.glob(CADIR + "/keys/" + device_serial + "*"): return HttpResponse("Key already exists.") os.chdir(CADIR) if execute(CADIR + "/generate-key " + device_serial, shell=True): os.chdir(BEFORE) return HttpResponse("Error generating key.") if glob.glob(CONFIGDIR + "/files/" + device_serial + "*"): os.chdir(BEFORE) return HttpResponse("Configuration file already exists.") os.chdir(CONFIGDIR) if execute(CONFIGDIR + "/make_config " + device_serial, shell=True): os.chdir(BEFORE) return HttpResponse("Error generating config file.") os.chdir(BEFORE) with open(CONFIGDIR + "/files/" + device_serial + ".ovpn") as vpnfile: vpnconfig = vpnfile.read() organization = Organization.objects.get(id=device_organization) device = Device.objects.create( serial=device_serial, name=device_name, ssid=device_ssid, key=device_key, model=Model.objects.filter(id=device_model)[0], network=Network.objects.filter(intip="No VPN")[0], organization=organization, vpnconfig=vpnconfig, mac=device_mac or None, changed=timezone.now() ) return redirect(reverse("devices")) @receiver(post_save, sender=settings.AUTH_USER_MODEL) def createUserStatus(sender, instance, created, **kwargs): if created: UserStatus.objects.create(user=instance) @receiver(post_save, sender=settings.AUTH_USER_MODEL) def saveUserStatus(sender, instance, **kwargs): try: instance.userstatus.save() except Exception: UserStatus.objects.create(user=instance) def errorhandler(request, code, exception): if code == 403: exception.human = ("You have no permission " "to access the requested page.") elif code == 400: exception.human = ("Something seems to be wrong with " "the request you sent. Please try again.") elif code == 404: exception.human = ("Sorry, we were unable to find the page you " "requested. Please check the address and try again." ) elif code == 500: exception.human = ("Sorry, something seems to be wrong on our end. " "Please try again later or contact support if " "the problem persists.") return devices(request, code=code, exception=exception) def handler400(request, exception): return errorhandler(request, 400, exception) def handler403(request, exception): return errorhandler(request, 403, exception) def handler404(request, exception): return errorhandler( request, 404, exception ) def handler500(request): return errorhandler( request, 500, Exception("Please check server logs for additional information.") )