...
 
Commits (6)
......@@ -64,3 +64,6 @@ windows/
android/
linux/
django/
# Vagrant stuff
.vagrant
DOCKER=docker
VAGRANT=vagrant
.PHONY: linux
linux: DOCKER_EXTRA=$(shell [ -L build ] && P=$$(readlink build) && echo -v $$P/:$$P )
......@@ -7,7 +8,7 @@ linux:
-t boundery-client-linux -f ./docker/Dockerfile.linux ./docker
$(DOCKER) run -it --rm --user $$(id -u):$$(id -g) \
-v `pwd`/:/home/build/src $(DOCKER_EXTRA) boundery-client-linux \
python3 setup.py linux
python3 setup.py linux --build
#.PHONY: android
#android:
......@@ -17,10 +18,27 @@ linux:
# -v `pwd`/:/home/build/src $(DOCKER_EXTRA) boundery-client-android \
# /bin/sh -c "python3 setup.py android && python3 setup.py android --build"
.PHONY: windows
windows:
rm -rf windows
VAGRANT_VAGRANTFILE=Vagrantfile.windows $(VAGRANT) up
VAGRANT_VAGRANTFILE=Vagrantfile.windows $(VAGRANT) provision --provision-with build
while [ ! -f "windows/builddone" ]; do sleep 1; done
VAGRANT_VAGRANTFILE=Vagrantfile.windows $(VAGRANT) halt
.PHONY: windows-gui
windows-gui:
VAGRANT_VAGRANTFILE=Vagrantfile.windows vagrant rdp -- /cert-ignore
.PHONY: windows-halt
windows-halt:
VAGRANT_VAGRANTFILE=Vagrantfile.windows vagrant halt
.PHONY: windows-destroy
windows-destroy:
VAGRANT_VAGRANTFILE=Vagrantfile.windows vagrant destroy -f
.PHONY: dev
dev:
@python3 boundery/app.py --debug --local
.PHONY: check
check:
pyflakes3 `find boundery -name '*.py' | grep -v osal/__init__.py`
pyflakes3 `find boundery -name '*.py' | grep -v osal/__init__[.]py | grep -v osal/win32wifi[.]py`
raise "Run: vagrant plugin install winrm" unless Vagrant.has_plugin?('winrm')
raise "Run: vagrant plugin install winrm-elevated" unless Vagrant.has_plugin?('winrm-elevated')
Vagrant.configure("2") do |config|
config.vm.box = ""
config.vm.define "win", autostart: false do |win|
#2.2.0 or later: win.vagrant.plugins = ["winrm", "winrm-elevated"]
win.vm.provider "virtualbox" do |vb|
#vb.gui = true
vb.memory = "2048"
end
win.vm.guest = :windows
win.vm.communicator = "winrm"
win.winrm.username = 'vagrant'
win.winrm.password = 'vagrant'
win.vm.box = "opentable/win-2012r2-standard-amd64-nocm"
win.vm.network "forwarded_port", host: 33389, guest: 3389
win.vm.provision "prep", type: "shell", inline: <<-SHELL
echo " ****** Installing chocolatey ******"
if (!(Test-Path "$env:SystemDrive\\ProgramData\\Chocolatey\\bin")) {
iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
}
#Need to pull in the chocolatey profile so refreshenv works.
$env:ChocolateyInstall = Convert-Path "$((Get-Command choco).path)\\..\\.."
Import-Module "$env:ChocolateyInstall\\helpers\\chocolateyProfile.psm1"
refreshenv
echo " ****** Installing .Net 3.5 ******"
Install-WindowsFeature Net-Framework-Core
#dism.exe /online /enable-feature /featurename:NetFX3 /all
echo " ****** Installing needed choco packages ******"
choco install --forcex86 -y python
choco install -y wixtoolset
choco install -y git
refreshenv
echo " ****** Setting up python venv ******"
cd \\Users\\vagrant
python -m venv venv
. venv\\Scripts\\activate.ps1
echo " ****** Installing briefcase ******"
$env:PIP_DISABLE_PIP_VERSION_CHECK=1
pip install briefcase
SHELL
win.vm.provision "build", type: "shell", run: "never", inline: <<-SHELL
echo " ****** Activating python venv ******"
cd \\Users\\vagrant
. venv\\Scripts\\activate.ps1
$env:PIP_DISABLE_PIP_VERSION_CHECK=1
echo " ****** Copying source files ******"
rm -R -Fo client
mkdir client
cp -Ex .* \\vagrant\\* client
cp -R -Fo \\vagrant\\boundery client
cd client
echo " ****** Starting installer build ******"
$env:PYTHONIOENCODING="utf-8" #Workaround briefcase bug 179.
python setup.py windows --build
echo " ****** Copying build artifacts ******"
mkdir -Fo \\vagrant\\windows
cp -Fo windows\\Boundery* \\vagrant\\windows
echo " ****** Build done *******"
echo "" > \\vagrant\\windows\\builddone
SHELL
#Package up into an exe (also with zerotier's .msi?):
#https://www.firegiant.com/wix/tutorial/net-and-net/bootstrapping/
end
config.vm.define "mac", autostart: false do |mac|
mac.vm.box = "ashiq/osx-10.14"
mac.vm.provider "virtualbox" do |vb|
#vb.gui = true
vb.memory = "2048"
end
mac.vm.provider "virtualbox" do |vb|
vb.customize ['modifyvm', :id, '--ostype', 'MacOS_64']
vb.customize ['modifyvm', :id, '--paravirtprovider', 'default']
# Adjust CPU settings according to
# https://github.com/geerlingguy/macos-virtualbox-vm
vb.customize ['modifyvm', :id, '--cpuidset',
'00000001', '000306a9', '00020800', '80000201', '178bfbff']
# Disable USB variant requiring Virtualbox proprietary extension pack
vb.customize ["modifyvm", :id, '--usbehci', 'off', '--usbxhci', 'off']
end
mac.vm.provision "prep", type: "shell", inline: <<-SHELL
pwd
SHELL
mac.vm.provision "build", type: "shell", run: "never", inline: <<-SHELL
echo "build"
SHELL
end
end
raise "Run: vagrant plugin install winrm" unless Vagrant.has_plugin?('winrm')
raise "Run: vagrant plugin install winrm-elevated" unless Vagrant.has_plugin?('winrm-elevated')
Vagrant.configure("2") do |config|
#2.2.0 or later: config.vagrant.plugins = ["winrm", "winrm-elevated"]
config.vm.box = "opentable/win-2012r2-standard-amd64-nocm"
config.vm.network "forwarded_port", host: 33389, guest: 3389
config.vm.guest = :windows
config.vm.communicator = "winrm"
config.winrm.username = 'vagrant'
config.winrm.password = 'vagrant'
config.vm.provider "virtualbox" do |vb|
#vb.gui = true
vb.memory = "2048"
vb.name = "windows"
end
config.vm.provision "prep", type: "shell", inline: <<-SHELL
echo " ****** Installing chocolatey ******"
if (!(Test-Path "$env:SystemDrive\\ProgramData\\Chocolatey\\bin")) {
iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
}
#Need to pull in the chocolatey profile so refreshenv works.
$env:ChocolateyInstall = Convert-Path "$((Get-Command choco).path)\\..\\.."
Import-Module "$env:ChocolateyInstall\\helpers\\chocolateyProfile.psm1"
refreshenv
echo " ****** Installing .Net 3.5 ******"
Install-WindowsFeature Net-Framework-Core
#dism.exe /online /enable-feature /featurename:NetFX3 /all
echo " ****** Installing needed choco packages ******"
choco install --forcex86 -y python
choco install -y wixtoolset
choco install -y git
refreshenv
echo " ****** Setting up python venv ******"
cd \\Users\\vagrant
python -m venv venv
. venv\\Scripts\\activate.ps1
echo " ****** Installing briefcase ******"
$env:PIP_DISABLE_PIP_VERSION_CHECK=1
pip install briefcase
SHELL
config.vm.provision "build", type: "shell", run: "never", inline: <<-SHELL
echo " ****** Activating python venv ******"
cd \\Users\\vagrant
. venv\\Scripts\\activate.ps1
$env:PIP_DISABLE_PIP_VERSION_CHECK=1
echo " ****** Copying source files ******"
rm -R -Fo client
mkdir client
cp -Ex .* \\vagrant\\* client
cp -R -Fo \\vagrant\\boundery client
cd client
echo " ****** Starting installer build ******"
$env:PYTHONIOENCODING="utf-8" #Workaround briefcase bug 179.
python setup.py windows --build
echo " ****** Copying build artifacts ******"
mkdir -Fo \\vagrant\\windows
cp -Fo windows\\Boundery* \\vagrant\\windows
echo " ****** Build done *******"
echo "" > \\vagrant\\windows\\builddone
SHELL
#Package up into an exe (also with zerotier's .msi?):
#https://www.firegiant.com/wix/tutorial/net-and-net/bootstrapping/
end
import sys, time, os
#Something bottle does in run() blows up if sys.std* are None when bottle is imported.
if '--privsub' not in sys.argv:
if sys.executable.upper().endswith("\\PYTHONW.EXE"):
sys.stdout = open(os.devnull, 'w')
sys.stderr = open(os.devnull, 'w')
sys.stdin = open(os.devnull, 'r')
import requests
import webbrowser
import appdirs
from bottle import route, run, static_file, TEMPLATE_PATH
from collections import namedtuple
from threading import Thread
from boundery import settings
#XXX Add some APIKEY between browser and this server.
#XXX Add JS to make it quit the deamon when all browser tabs/windows are closed.
#XXX Add JS to make it quit the deamon when all browser tabs/windows are closed. Also, timeout.
POLL_INTERVAL = 0.5
......@@ -46,9 +56,17 @@ def main():
if "--privsub" in sys.argv:
enroll.privsub_run()
else:
if sys.executable.upper().endswith("\\PYTHONW.EXE"):
sys.stdout = open(os.path.join(appdirs.user_data_dir("boundery"), "out.log"), 'w')
sys.stderr = open(os.path.join(appdirs.user_data_dir("boundery"), "err.log"), 'w')
poller = Thread(target=poll_ready, name="poll_ready", daemon=True)
poller.start()
if DEBUG:
run(host="localhost", port=settings.PORT, debug=True)
else:
run(host="localhost", port=settings.PORT, debug=False, server='waitress')
#briefcase expects main to return an object w/ a function on win32.
return namedtuple('Dummy', 'main_loop')(main_loop = lambda: None)
......@@ -30,14 +30,14 @@ def do_priv(cmd):
privsub = osal.sudo(sys.executable, os.path.abspath(__main__.__file__), "--privsub")
line = privsub.stdout.readline().strip() #XXX timeout?
if line != "ok":
raise Exception("privsub startup failed: " + line)
raise Exception("privsub startup failed: '%s'" % line)
privsub.stdin.write(cmd + '\n')
privsub.stdin.flush()
line = privsub.stdout.readline().strip()
if line.startswith("ERROR "):
raise Exception("privsub cmd %s failed: %s" % (cmd, line))
raise Exception("privsub cmd %s failed: '%s'" % (cmd, line))
return line
def finish_priv():
......@@ -95,6 +95,7 @@ def step1():
@get('/step1_api1')
def step1_api1():
#XXX Emit a useful message if there are no mounts!
return template("step1_api1", { "mounts": osal.get_mounts() })
step1_thread = None
......
......@@ -6,10 +6,9 @@ if os.name == "posix":
else:
from .osal_linux import *
else:
#XXX Windows UAC (runas) doesn't hook up stdin/out. Named pipes?
assert(False)
from .osal_windows import *
#netsh wlan show networks mode=bssid
#https://stackoverflow.com/questions/130763/request-uac-elevation-from-within-a-python-script
#XXX Get wifi password. Pypi has "ng" which claims to do so.
#https://stackoverflow.com/questions/5747007/get-root-dialog-in-python-on-mac-os-x-windows/31984663#31984663
#/Library/Application Support/ZeroTier/One/authtoken.secret
import sys, os, subprocess, ctypes
#briefcase's start.py adds app_packages to sys.path, which skips pywin32's .pth file. sitedirs don't.
import site
site.addsitedir(next(filter(lambda i: i.endswith('app_packages'), sys.path), ''))
from . import win32wifi
import pywintypes, win32pipe, win32file, msvcrt
from win32api import GetLogicalDriveStrings
from win32file import GetDriveType
#XXX Would be nice to get human-readable description as well...
def get_mounts():
drives = GetLogicalDriveStrings()
drives = [i for i in drives.split("\x00") if i]
return [i[:2] for i in drives if GetDriveType(i) == win32file.DRIVE_REMOVABLE]
def _pipe_handle(pipe):
try:
win32pipe.ConnectNamedPipe(pipe, None)
while True:
result, resp = win32file.ReadFile(pipe, 64*1024)
print("message: %s" % resp)
print("Client connected")
except pywintypes.error as e:
if e.args[0] == 109:
print("broken pipe, bye bye")
else:
print("other? %s" % e)
finally:
win32file.CloseHandle(pipe)
class NPWrapper:
def __init__(self, pipe):
win32pipe.ConnectNamedPipe(pipe, None)
self._pipe = pipe
self.stdin = open(msvcrt.open_osfhandle(pipe, os.O_WRONLY), 'w')
self.stdout = open(msvcrt.open_osfhandle(pipe, os.O_RDONLY), 'r')
self.stderr = open(msvcrt.open_osfhandle(pipe, os.O_RDONLY), 'r')
def wait(self, timeout = -1):
#XXX Need to actually wait here?
win32file.CloseHandle(self._pipe)
#Windows UAC (runas) doesn't hook up stdin/out. So we use a named pipe and a helper process.
def sudo(cmd, *args):
pipe = win32pipe.CreateNamedPipe(r'\\.\pipe\BounderySudo',
win32pipe.PIPE_ACCESS_DUPLEX,
win32pipe.PIPE_TYPE_BYTE | win32pipe.PIPE_READMODE_BYTE |
win32pipe.PIPE_WAIT, 1, 65536, 65536, 0, None)
fullargs = [ os.path.join(os.path.dirname(__file__),
'osal_windows_elevate.py'), cmd ]
fullargs.extend(args)
fullargs.append('--')
fullargs.extend(sys.path)
python = sys.executable
if python.upper().endswith('\\PYTHON.EXE'):
python = python[:-len('python.exe')] + 'pythonw.exe'
if fullargs[1].upper().endswith('\\PYTHON.EXE'):
fullargs[1] = fullargs[1][:-len('python.exe')] + 'pythonw.exe'
ctypes.windll.shell32.ShellExecuteW(None, "runas",
python, subprocess.list2cmdline(fullargs), None, 1)
return NPWrapper(pipe)
def get_zerotier_token_path():
return "C:\\ProgramData\\ZeroTier\\One\\authtoken.secret"
def get_ssids():
#XXX Figure out which SSID client is currently connected to.
return [ (False, min(max(2 * (x[1] + 100), 0), 100), x[0]) for x in win32wifi.get_BSSI().values() ]
import sys
sys.path = sys.argv[sys.argv.index('--')+1:]
cmdargs = sys.argv[1:sys.argv.index('--')]
import time, os, subprocess
import appdirs
#briefcase's start.py adds app_packages to sys.path, which skips pywin32's .pth file. sitedirs don't.
import site
site.addsitedir(next(filter(lambda i: i.endswith('app_packages'), sys.path), ''))
import pywintypes, win32file, msvcrt
out = open(os.path.join(appdirs.user_data_dir("boundery"), "elevate.log"), 'w')
def pipe_client():
quit = False
while not quit:
try:
print("Connecting to named pipe", file=out, flush=True)
pipe = win32file.CreateFile(r'\\.\pipe\BounderySudo',
win32file.GENERIC_READ | win32file.GENERIC_WRITE,
0, None, win32file.OPEN_EXISTING, 0, None)
print("Starting: '%s' -- '%s'" % (cmdargs, sys.path), file=out, flush=True)
stdin = open(msvcrt.open_osfhandle(pipe, os.O_RDONLY), 'r')
stdout = open(msvcrt.open_osfhandle(pipe, os.O_WRONLY), 'w')
p = subprocess.Popen(cmdargs, stdin=stdin, stdout=stdout, stderr=subprocess.STDOUT)
quit=True
except pywintypes.error as e:
if e.args[0] == 2:
print("no pipe, trying again in a sec", file=out, flush=True)
time.sleep(1)
elif e.args[0] == 109:
print("broken pipe, bye bye", file=out, flush=True)
quit = True
else:
print("other? %s" % e, file=out, flush=True)
quit = True
win32file.CloseHandle(pipe)
print("Done", file=out, flush=True)
if __name__ == '__main__':
try:
pipe_client()
except BaseException as e:
print("Exception: %s" % e, file=out, flush=True)
out.close()
This diff is collapsed.
......@@ -65,7 +65,9 @@
}
</style>
<p>Choose Wifi settings, then choose the device to use to create the boot card.</p>
<h1>Choose Wifi settings, then choose the device to use to create the boot card.</h1>
<p>If you are installing to a raspberry pi (or any other computer that doesn't support 5Ghz wifi), make sure to pick a 2.4Ghz Wifi network.</p>
<form action="/step1" method="post">
<div>
......@@ -81,7 +83,7 @@
<div>
<input type="radio" name="ssid" value="other">Other <input class="hideshow" type="text" name="other_ssid" value=""><br>
</div>
<input type="radio" name="ssid" value="">None. I'm using wired ethernet.
<input type="radio" name="ssid" value="" required>None. I'm using wired ethernet.
</div>
<div>
<label for="wifi_pw">Wifi Password (Blank for none):</label>
......
% rebase('base.tpl', title='Boundery.me')
<p>Next we'll create the private networks for you to securely communicate with your home server. This requires elevated privileges, which means you will need to type in your password and click OK on a popup window. To proceed <a href="/step4">click here.</a></p>
<p>Next we'll create the private networks for you to securely communicate with your home server. This requires elevated privileges, which means you will need to click OK and/or type in your password on a popup window. To proceed <a href="/step4">click here.</a></p>
......@@ -38,7 +38,7 @@ setup(
'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
],
install_requires=[
'hkdf', 'PyNaCl', 'bottle', 'waitress', 'appdirs'
'requests', 'hkdf', 'PyNaCl', 'bottle', 'waitress', 'appdirs'
],
options={
'app': {
......