Commit 6bdaacab by Szeberényi Imre

Initial version, Python 2.7

parents
*.swp
*.swo
.ropeproject
*.pyc
version.txt
*,cover
.coverage
*.log
build
dist
*.exe
*.spec
NOTES - aka, mivel lehet szivni...
__INSTALL__
* adott egy bootstrap script, ami minden szukseges elokeszuletet megcsinal, ami az agens mukodesehez kell:
+ alap csomagokat telepit fel
+ konyvtarakat hoz letre
+ usereket hoz letre es allit be
+ letolti az agent forrasat
+ felpatcheli az rc-t
+ telepiti az agenst
__RC.D__
* minden egyes circle_agent altal eszkozolt beallitas a /etc/rc.conf.d/-ben talalhato egy kulon file alatt
* a FreeBSD-s rc.subr az agent telepitesekor modositasra kerul, mivel alapbol a partial config file-ok kezelese agyhalal es nem logikus
* az interface beallitasok a /etc/rc.conf.d/ifconfig_IFNAME alatt talalhatok
* az agent daemon-kent fut
__SMBFS__
* mindenhol case insensitive, kiveve a /etc/nsmb.conf-ban es a ~/.nsmbrc-ben, itt a userneveket szigoruan upper-case kell irni
* az /etc/nsmb.conf-ban vagy a ~/.nsmbrc-ben meg kell ismetelni a [SERVER] es a [SERVER:USER] reszeknel az add-t es a password-ot
__IPV6__
* semmi extra, prefixlen kell prefix helyett
__ROUTING__
* {ipv6_,}defaultrouter-eket kell beallitani
__USER__
* a cloud es a root felhasznalo ugyan azt a jelszot birtokolja minden idoben
* a passwd update mindket felhasznalora hatassal van
__SERIAL__
* virtios soros port elsodlegesen, utana hw soros
* \r sorveg eseten hibas csomagok, FreeBSD-n \n az elvart
import logging
from logging.handlers import NTEventLogHandler
import os
import servicemanager
import socket
import sys
import win32event
import win32service
import win32serviceutil
from agent import main as agent_main, reactor
logger = logging.getLogger()
fh = NTEventLogHandler(
"CIRCLE Agent", dllname=os.path.dirname(__file__))
formatter = logging.Formatter(
"%(asctime)s - %(name)s [%(levelname)s] %(message)s")
fh.setFormatter(formatter)
logger.addHandler(fh)
level = os.environ.get('LOGLEVEL', 'INFO')
logger.setLevel(level)
logger.info("%s loaded", __file__)
class AppServerSvc (win32serviceutil.ServiceFramework):
_svc_name_ = "circle-agent"
_svc_display_name_ = "CIRCLE Agent"
def __init__(self, args):
win32serviceutil.ServiceFramework.__init__(self, args)
self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
socket.setdefaulttimeout(60)
def SvcStop(self):
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
win32event.SetEvent(self.hWaitStop)
reactor.stop()
logger.info("%s stopped", __file__)
def SvcDoRun(self):
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
servicemanager.PYS_SERVICE_STARTED,
(self._svc_name_, ''))
logger.info("%s starting", __file__)
agent_main()
def main():
if len(sys.argv) == 1:
# service must be starting...
# for the sake of debugging etc, we use win32traceutil to see
# any unhandled exceptions and print statements.
import win32traceutil # noqa
logger.info("service is starting...")
servicemanager.Initialize()
servicemanager.PrepareToHostSingle(AppServerSvc)
# Now ask the service manager to fire things up for us...
servicemanager.StartServiceCtrlDispatcher()
logger.info("service done!")
else:
win32serviceutil.HandleCommandLine(AppServerSvc)
if __name__ == '__main__':
try:
main()
except (SystemExit, KeyboardInterrupt):
raise
except Exception:
logger.exception("Exception:")
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from os import environ, chdir
import platform
import subprocess
import sys
system = platform.system() # noqa
if system == "Linux" or system == "FreeBSD": # noqa
try: # noqa
chdir(sys.path[0]) # noqa
subprocess.call(('pip', 'install', '-r', 'requirements.txt')) # noqa
except Exception: # noqa
pass # hope it works # noqa
from twisted.internet import reactor, defer
from twisted.internet.task import LoopingCall
import uptime
import logging
from inspect import getargspec, isfunction
from utils import SerialLineReceiverBase
# Note: Import everything because later we need to use the BaseContext
# (relative import error.
from context import BaseContext, get_context, get_serial # noqa
Context = get_context()
logging.basicConfig()
logger = logging.getLogger()
level = environ.get('LOGLEVEL', 'INFO')
logger.setLevel(level)
class SerialLineReceiver(SerialLineReceiverBase):
def connectionMade(self):
self.transport.write('\r\n')
self.send_command(
command='agent_started',
args={'version': Context.get_agent_version(),
'system': system})
def shutdown():
self.connectionLost2('shutdown')
d = defer.Deferred()
reactor.callLater(0.3, d.callback, "1")
return d
reactor.addSystemEventTrigger("before", "shutdown", shutdown)
def connectionLost(self, reason):
reactor.stop()
def connectionLost2(self, reason):
self.send_command(command='agent_stopped',
args={})
def tick(self):
logger.debug("Sending tick")
try:
self.send_status()
except Exception:
logger.exception("Twisted hide exception")
def __init__(self):
super(SerialLineReceiver, self).__init__()
self.lc = LoopingCall(self.tick)
self.lc.start(5, now=False)
def send_status(self):
import psutil
disk_usage = dict((disk.device.replace('/', '_'),
psutil.disk_usage(disk.mountpoint).percent)
for disk in psutil.disk_partitions())
args = {"cpu": dict(psutil.cpu_times()._asdict()),
"ram": dict(psutil.virtual_memory()._asdict()),
"swap": dict(psutil.swap_memory()._asdict()),
"uptime": {"seconds": uptime.uptime()},
"disk": disk_usage,
"user": {"count": len(psutil.users())}}
self.send_response(response='status', args=args)
logger.debug("send_status finished")
def _check_args(self, func, args):
if not isinstance(args, dict):
raise TypeError("Arguments should be all keyword-arguments in a "
"dict for command %s instead of %s." %
(self._pretty_fun(func), type(args).__name__))
# check for unexpected keyword arguments
argspec = getargspec(func)
if argspec.keywords is None: # _operation doesn't take ** args
unexpected_kwargs = set(args) - set(argspec.args)
if unexpected_kwargs:
raise TypeError(
"Command %s got unexpected keyword arguments: %s" % (
self._pretty_fun(func), ", ".join(unexpected_kwargs)))
mandatory_args = argspec.args
if argspec.defaults: # remove those with default value
mandatory_args = mandatory_args[0:-len(argspec.defaults)]
missing_kwargs = set(mandatory_args) - set(args)
if missing_kwargs:
raise TypeError("Command %s missing arguments: %s" % (
self._pretty_fun(func), ", ".join(missing_kwargs)))
def _get_command(self, command, args):
if not isinstance(command, basestring) or command.startswith('_'):
raise AttributeError(u'Invalid command: %s' % command)
try:
func = getattr(Context, command)
except AttributeError as e:
raise AttributeError(u'Command not found: %s (%s)' % (command, e))
if not isfunction(func):
raise AttributeError("Command refers to non-static method %s." %
self._pretty_fun(func))
self._check_args(func, args)
logger.debug("_get_command finished")
return func
@staticmethod
def _pretty_fun(fun):
try:
argspec = getargspec(fun)
args = argspec.args
if argspec.varargs:
args.append("*" + argspec.varargs)
if argspec.keywords:
args.append("**" + argspec.keywords)
return "%s(%s)" % (fun.__name__, ",".join(args))
except Exception:
return "<%s>" % type(fun).__name__
def handle_command(self, command, args):
func = self._get_command(command, args)
retval = func(**args)
self.send_response(
response=func.__name__,
args={'retval': retval, 'uuid': args.get('uuid', None)})
def handle_response(self, response, args):
pass
def main():
# Get proper serial class and port name
(serial, port) = get_serial()
logger.info("Opening port %s", port)
# Open serial connection
serial(SerialLineReceiver(), port, reactor)
try:
from notify import register_publisher
register_publisher(reactor)
except Exception:
logger.exception("Could not register notify publisher")
logger.debug("Starting reactor.")
reactor.run()
logger.debug("Reactor finished.")
if __name__ == '__main__':
main()
#!/bin/sh
#export LOGLEVEL=DEBUG
pkg install -y `cat bootstrap/freebsd/pkg-requirements.txt`
grep "^cloud:" /etc/passwd > /dev/null
ret=$?
if [ $ret -ne 0 ]
then
# create the required backdoor user
pw user add cloud -m
pw group mod wheel -m cloud
fi
sed -i '.orig' -e 's/# \(%wheel ALL=(ALL) ALL\)/\1/g' /usr/local/etc/sudoers
if [ ! -d /usr/ports ]
then
git clone https://github.com/HardenedBSD/freebsd-ports.git /usr/ports
fi
if [ ! -d /store ]
then
mkdir -p /store
fi
if [ ! -d /root/agent ]
then
cd /root
git clone https://github.com/opntr/bme-cloud-circle-agent.git agent
fi
cp -r bootstrap/freebsd/rc.conf.d /etc
cd /root/agent
if [ -d /usr/local/etc/rc.d ]
then
cp bootstrap/freebsd/rc.d/agent /usr/local/etc/rc.d
fi
if [ ! -f /etc/rc.conf.d/agent ]
then
echo 'agent_enable="YES"' > /etc/rc.conf.d/agent
fi
pip install -r requirements.txt
service agent start
for netconf in /etc/rc.conf.d/ifconfig_*
do
load_rc_config $(basename "$netconf")
done
#!/bin/sh
#
# $FreeBSD$
#
# PROVIDE: agent
# REQUIRE: DAEMON NETWORKING
# KEYWORD: shutdown
. /etc/rc.subr
name="agent"
rcvar="agent_enable"
pidfile="/var/run/${name}.pid"
start_cmd="do_start"
stop_cmd="do_stop"
do_start()
{
PATH=${PATH}:/usr/local/bin/
printf "start ${name}: "
/usr/sbin/daemon -P ${pidfile} -r /usr/local/bin/python /root/agent/agent.py
ret=$?
if [ "${ret}" = "0" ]
then
echo "done."
else
echo "failed..."
fi
}
do_stop()
{
printf "stop ${name}: "
if [ -f ${pidfile} ]
then
kill `cat ${pidfile}`
echo "done."
else
echo "not running..."
return 1
fi
}
load_rc_config $name
run_rc_command "$1"
# Open urls in default web browser provided by circle agent
# Part of CIRCLE project http://circlecloud.org/
# Should be in autostart and run by the user logged in
import logging
logger = logging.getLogger()
fh = logging.FileHandler("agent-client.log")
formatter = logging.Formatter(
"%(asctime)s - %(name)s [%(levelname)s] %(message)s")
fh.setFormatter(formatter)
logger.addHandler(fh)
from notify import run_client
if __name__ == '__main__':
run_client()
""" This is the defautl context file. It replaces the Context class
to the platform specific one.
"""
import platform
def _get_virtio_device():
path = None
GUID = '{6FDE7521-1B65-48ae-B628-80BE62016026}'
from infi.devicemanager import DeviceManager
dm = DeviceManager()
dm.root.rescan()
# Search Virtio-Serial by name TODO: search by class_guid
for i in dm.all_devices:
if i.has_property("description"):
if "virtio-serial".upper() in i.description.upper():
path = ("\\\\?\\" +
i.children[0].instance_id.lower().replace('\\', '#') +
"#" + GUID.lower()
)
return path
def get_context():
system = platform.system()
if system == "Windows":
from windows._win32context import Context
elif system == "Linux":
from linux._linuxcontext import Context
elif system == "FreeBSD":
from freebsd._freebsdcontext import Context
else:
raise NotImplementedError("Platform %s is not supported.", system)
return Context
def get_serial():
system = platform.system()
port = None
if system == 'Windows':
port = _get_virtio_device()
import pythoncom
pythoncom.CoInitialize()
if port:
from windows.win32virtio import SerialPort
else:
from twisted.internet.serialport import SerialPort
port = r'\\.\COM1'
elif system == "Linux":
port = "/dev/virtio-ports/agent"
try:
open(port, 'rw').close()
except (OSError, IOError):
from twisted.internet.serialport import SerialPort
port = '/dev/ttyS0'
else:
from linux.posixvirtio import SerialPort
elif system == "FreeBSD":
port = "/dev/ttyV0.1"
try:
open(port, 'rw').close()
except (OSError, IOError):
from twisted.internet.serialport import SerialPort
port = '/dev/ttyu0'
else:
from freebsd.posixvirtio import SerialPort
else:
raise NotImplementedError("Platform %s is not supported.", system)
return (SerialPort, port)
class BaseContext(object):
@staticmethod
def change_password(password):
pass
@staticmethod
def restart_networking():
pass
@staticmethod
def change_ip(interfaces, dns):
pass
@staticmethod
def set_time(time):
pass
@staticmethod
def set_hostname(hostname):
pass
@staticmethod
def mount_store(host, username, password):
pass
@staticmethod
def get_keys():
pass
@staticmethod
def add_keys(keys):
pass
@staticmethod
def del_keys(keys):
pass
@staticmethod
def cleanup():
pass
@staticmethod
def start_access_server():
pass
@staticmethod
def append(data, filename, chunk_number, uuid):
pass
@staticmethod
def update(filename, executable, checksum, uuid):
pass
@staticmethod
def ipaddresses():
pass
@staticmethod
def get_agent_version():
try:
with open('version.txt') as f:
return f.readline()
except IOError:
return None
@staticmethod
def send_expiration(url):
import notify
notify.notify(url)
serial: ok
ipv4: ok
ipv6: ok
hostname: ok
time: ?
smb: ok
passwd update: ok
get-ssh-keys: ?
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from os import mkdir, environ, chdir, chmod
import platform
from shutil import copy, rmtree, move
import subprocess
import sys
system = platform.system() # noqa
working_directory = sys.path[0] # noqa
try: # noqa
# load virtio console driver, the device is /dev/ttyV0.1 # noqa
subprocess.call(('/sbin/kldload', '-n', 'virtio_console')) # noqa
subprocess.call(('/sbin/kldload', '-n', 'smbfs')) # noqa
chdir(working_directory) # noqa
subprocess.call( # noqa
('/usr/local/bin/pip', 'install', '-r', 'requirements.txt')) # noqa
copy("/root/agent/misc/vm_renewal", "/usr/local/bin/") # noqa
except Exception: # noqa
pass # hope it works # noqa
import logging
import fileinput
import tarfile
from os.path import expanduser, join, exists
from glob import glob
from StringIO import StringIO
from base64 import decodestring
from hashlib import md5
from ssh import PubKey
from .network import change_ip_freebsd
from context import BaseContext
from twisted.internet import reactor
logging.basicConfig()
logger = logging.getLogger()
level = environ.get('LOGLEVEL', 'INFO')
logger.setLevel(level)
SSH_DIR = expanduser('~cloud/.ssh')
AUTHORIZED_KEYS = join(SSH_DIR, 'authorized_keys')
STORE_DIR = '/store'
mount_template_freebsd = (
'//CLOUD/%(username)s %(dir)s smbfs '
'rw,-N,-ucloud,-U%(username)s 0 0\n')
NSMBRC = '/etc/nsmb.conf'
nsmbrc_template_freebsd = (
'[CLOUD:%(USERNAME)s]\n'
'addr=%(host)s\n'
'password=%(password)s\n'
'\n'
'[CLOUD]\n'
'addr=%(host)s\n'
'password=%(password)s\n')
class Context(BaseContext):
# http://stackoverflow.com/questions/12081310/
# python-module-to-change-system-date-and-time
@staticmethod
def _freebsd_set_time(time):
import ctypes
import ctypes.util
CLOCK_REALTIME = 0
class timespec(ctypes.Structure):
_fields_ = [("tv_sec", ctypes.c_long),
("tv_nsec", ctypes.c_long)]
librt = ctypes.CDLL(ctypes.util.find_library("rt"))
ts = timespec()
ts.tv_sec = int(time)
ts.tv_nsec = 0
librt.clock_settime(CLOCK_REALTIME, ctypes.byref(ts))
@staticmethod
def change_password(password):
proc0 = subprocess.Popen(
['/usr/sbin/pw', 'user', 'mod', 'cloud', '-h', '0'],
stdin=subprocess.PIPE)
proc0.communicate('%s\n' % password)
proc1 = subprocess.Popen(
['/usr/sbin/pw', 'user', 'mod', 'root', '-h', '0'],
stdin=subprocess.PIPE)
proc1.communicate('%s\n' % password)
@staticmethod
def restart_networking():
logger.debug("restart_networking")
@staticmethod
def change_ip(interfaces, dns):
nameservers = dns.replace(' ', '').split(',')
change_ip_freebsd(interfaces, nameservers)
@staticmethod
def set_time(time):
Context._freebsd_set_time(float(time))
try:
subprocess.call(['/usr/sbin/service' 'ntpd', 'onerestart'])
except Exception:
pass
@staticmethod
def set_hostname(hostname):
with open('/etc/rc.conf.d/hostname', 'w') as f:
f.write("hostname=\""+hostname+"\"")
with open('/etc/hosts', 'w') as f:
f.write("127.0.0.1 localhost\n"
"127.0.1.1 %s\n" % hostname)
subprocess.call(['/usr/sbin/service', 'hostname', 'restart'])
@staticmethod
def mount_store(host, username, password):
data = {'host': host, 'username': username, 'password': password,
'USERNAME': username.upper()}
data['dir'] = STORE_DIR
if not exists(STORE_DIR):
mkdir(STORE_DIR)
# TODO
for line in fileinput.input('/etc/fstab', inplace=True):
if not (line.startswith('//') and ' smbfs ' in line):
print line.rstrip()
with open(NSMBRC, 'w') as f:
chmod(NSMBRC, 0600)
f.write(nsmbrc_template_freebsd % data)
with open('/etc/fstab', 'a') as f:
f.write(mount_template_freebsd % data)
subprocess.call('/sbin/mount -a', shell=True)
@staticmethod
def get_keys():
retval = []
try:
with open(AUTHORIZED_KEYS, 'r') as f:
for line in f.readlines():
try:
retval.append(PubKey.from_str(line))
except Exception:
logger.exception(u'Invalid ssh key: ')
except IOError:
pass
return retval
@staticmethod
def _save_keys(keys):
print keys
try:
mkdir(SSH_DIR)
except OSError:
pass
with open(AUTHORIZED_KEYS, 'w') as f:
for key in keys:
f.write(unicode(key) + '\n')
@staticmethod
def add_keys(keys):
new_keys = Context.get_keys()
for key in keys:
try:
p = PubKey.from_str(key)
if p not in new_keys:
new_keys.append(p)
except Exception:
logger.exception(u'Invalid ssh key: ')
Context._save_keys(new_keys)
@staticmethod
def del_keys(keys):
new_keys = Context.get_keys()
for key in keys:
try:
p = PubKey.from_str(key)
try:
new_keys.remove(p)
except ValueError:
pass
except Exception:
logger.exception(u'Invalid ssh key: ')
Context._save_keys(new_keys)
@staticmethod
def cleanup():
filelist = ([
'/root/.bash_history',
'/home/cloud/.bash_history',
'/root/.ssh',
'/home/cloud/.ssh',
'/root/.lesshst',
'/home/cloud/.lesshst',
'/root/.history',
'/home/cloud/.history',
'/root/.viminfo',
'/home/cloud/.viminfo',
'/etc/nsmb.conf',
'/root/.nsmbrc.conf',
'/home/cloud/.nsmbrc.conf',
]
+ glob('/etc/ssh/ssh_host_*'))
for f in filelist:
rmtree(f, ignore_errors=True)
subprocess.call(('/usr/bin/ssh-keygen', '-A'))
@staticmethod
def start_access_server():
subprocess.call(('/usr/sbin/service', 'ssh', 'start'))
@staticmethod
def append(data, filename, chunk_number, uuid):
if chunk_number == 0:
flag = "w"
else:
flag = "a"
with open(filename, flag) as myfile:
myfile.write(data)
@staticmethod
def update(filename, executable, checksum, uuid):
new_dir = working_directory + '.new'
old_dir = working_directory + '.old.%s' % uuid
with open(filename, "r") as f:
data = f.read()
local_checksum = md5(data).hexdigest()
if local_checksum != checksum:
raise Exception("Checksum missmatch the file is damaged.")
decoded = StringIO(decodestring(data))
try:
tar = tarfile.TarFile.open("dummy", fileobj=decoded, mode='r|gz')
tar.extractall(new_dir)
except tarfile.ReadError as e:
logger.error(e)
move(working_directory, old_dir)
move(new_dir, working_directory)
logger.info("Transfer completed!")
reactor.stop()
@staticmethod
def ipaddresses():
import netifaces
args = {}
interfaces = netifaces.interfaces()
for i in interfaces:
if i == 'lo0':
continue
args[i] = []
addresses = netifaces.ifaddresses(i)
args[i] = ([x['addr']
for x in addresses.get(netifaces.AF_INET, [])] +
[x['addr']
for x in addresses.get(netifaces.AF_INET6, [])
if '%' not in x['addr']])
return args
@staticmethod
def get_agent_version():
try:
with open('version.txt') as f:
return f.readline()
except IOError:
return None
@staticmethod
def send_expiration(url):
import notify
notify.notify(url)
import netifaces
from netaddr import IPNetwork
import logging
import subprocess
import os
import os.path
logger = logging.getLogger()
rcconf_dir = '/etc/rc.conf.d/'
def get_interfaces_freebsd(interfaces):
for ifname in netifaces.interfaces():
if ifname == 'lo0':
continue # XXXOP: ?
logger.debug("get_interfaces: " + ifname)
mac = netifaces.ifaddresses(ifname)[18][0]['addr']
logger.debug("get_interfaces: " + mac)
conf = interfaces.get(mac.upper())
if conf:
yield ifname, conf
def remove_interfaces_freebsd(devices):
for device in devices:
if_file = "%s/ifconfig_%s" % (rcconf_dir, device)
if os.path.isfile(if_file):
logger.debug("remove interface configuration: " + if_file)
os.unlink(if_file)
else:
logger.debug(
"unable to remove interface configuration: " + if_file)
def change_ip_freebsd(interfaces, nameservers):
data = list(get_interfaces_freebsd(interfaces))
for ifname, conf in data:
subprocess.call(('/usr/sbin/service', 'netif', 'stop', ifname))
remove_interfaces_freebsd(dict(data).keys())
for device, conf in data:
if_file = rcconf_dir + "ifconfig_" + device
with open(if_file, 'w') as f:
ipv4_alias_counter = ipv6_alias_counter = 0
for i in conf['addresses']:
alias = ""
ip_with_prefix = IPNetwork(i)
ip = ip_with_prefix.ip
prefixlen = ip_with_prefix.prefixlen
if ip.version == 6:
alias = "_ipv6"
if ipv6_alias_counter > 0:
alias = '_alias%d' % (ipv6_alias_counter-1)
ipv6_alias_counter += 1
f.write("ifconfig_" + device + alias + "=" +
"\"inet6 %(ip)s prefixlen %(pref)s\"\n"
% {'ip': ip, 'pref': prefixlen})
f.write("ipv6_defaultrouter=\""+str(conf['gw6'])+"\"\n")
else:
if ipv4_alias_counter > 0:
# az aliasok szamanak folytonosnak kell lennie
ipv4_alias_counter = ipv6_alias_counter + 1
alias = '_alias%d' % (ipv4_alias_counter)
ipv4_alias_counter += 1
f.write("ifconfig_" + device + alias + "=" +
"\"inet %(ip)s/%(pref)s\"\n"
% {'ip': ip, 'pref': prefixlen})
f.write("defaultrouter=\""+str(conf['gw4'])+"\"\n")
with open("/etc/resolv.conf", "w") as f:
for ns in nameservers:
f.write("nameserver %s\n" % ns)
for ifname, conf in data:
subprocess.call(('/usr/sbin/service', 'netif', 'start', ifname))
subprocess.call(('/usr/sbin/service', 'routing', 'start'))
# example:
# change_ip_ubuntu({
# u'02:00:00:02:A3:E8': {
# u'gw4': u'10.1.0.254', 'gw6': '2001::ffff',
# u'addresses': [u'10.1.0.84/24', '10.1.0.1/24', '2001::1/48']},
# u'02:00:00:02:A3:E9': {
# u'gw4': u'10.255.255.1', u'addresses': [u'10.255.255.9']}},
# '8.8.8.8')
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Virtio-Serial Port Protocol
"""
# system imports
import os
# dependent on pyserial ( http://pyserial.sf.net/ )
# only tested w/ 1.18 (5 Dec 2002)
# twisted imports
from twisted.internet import abstract, fdesc
class SerialPort(abstract.FileDescriptor):
"""
A select()able serial device, acting as a transport.
"""
connected = 1
def __init__(self, protocol, deviceNameOrPortNumber, reactor):
abstract.FileDescriptor.__init__(self, reactor)
self.port = deviceNameOrPortNumber
self._serial = os.open(
self.port, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK)
self.reactor = reactor
self.protocol = protocol
self.protocol.makeConnection(self)
self.startReading()
def fileno(self):
return self._serial
def writeSomeData(self, data):
"""
Write some data to the serial device.
"""
return fdesc.writeToFD(self.fileno(), data)
def doRead(self):
"""
Some data's readable from serial device.
"""
return fdesc.readFromFD(self.fileno(), self.protocol.dataReceived)
def connectionLost(self, reason):
"""
Called when the serial port disconnects.
Will call C{connectionLost} on the protocol that is handling the
serial data.
"""
abstract.FileDescriptor.connectionLost(self, reason)
os.close(self._serial)
self.protocol.connectionLost(reason)
from base64 import decodestring
from struct import unpack
import binascii
import unittest
class InvalidKeyType(Exception):
pass
class InvalidKey(Exception):
pass
class PubKey(object):
key_types = ('ssh-rsa', 'ssh-dsa', 'ssh-ecdsa')
# http://stackoverflow.com/questions/2494450/ssh-rsa-public-key-
# validation-using-a-regular-expression
@classmethod
def validate_key(cls, key_type, key):
try:
data = decodestring(key)
except binascii.Error:
raise InvalidKey()
int_len = 4
str_len = unpack('>I', data[:int_len])[0]
if data[int_len:int_len + str_len] != key_type:
raise InvalidKey()
def __init__(self, key_type, key, comment):
if key_type not in self.key_types:
raise InvalidKeyType()
self.key_type = key_type
PubKey.validate_key(key_type, key)
self.key = key
self.comment = unicode(comment)
def __hash__(self):
return hash(frozenset(self.__dict__.items()))
def __eq__(self, other):
return self.__dict__ == other.__dict__
@classmethod
def from_str(cls, line):
key_type, key, comment = line.split()
return PubKey(key_type, key, comment)
def __unicode__(self):
return u' '.join((self.key_type, self.key, self.comment))
def __repr__(self):
return u'<PubKey: %s>' % unicode(self)
# Unit tests
class SshTestCase(unittest.TestCase):
def setUp(self):
self.p1 = PubKey.from_str('ssh-rsa AAAAB3NzaC1yc2EA comment')
self.p2 = PubKey.from_str('ssh-rsa AAAAB3NzaC1yc2EA comment')
self.p3 = PubKey.from_str('ssh-rsa AAAAB3NzaC1yc2EC comment')
def test_invalid_key_type(self):
self.assertRaises(InvalidKeyType, PubKey, 'ssh-inv', 'x', 'comment')
def test_valid_key(self):
PubKey('ssh-rsa', 'AAAAB3NzaC1yc2EA', 'comment')
def test_invalid_key(self):
self.assertRaises(InvalidKey, PubKey, 'ssh-rsa', 'x', 'comment')
def test_invalid_key2(self):
self.assertRaises(InvalidKey, PubKey, 'ssh-rsa',
'AAAAB3MzaC1yc2EA', 'comment')
def test_repr(self):
p = PubKey('ssh-rsa', 'AAAAB3NzaC1yc2EA', 'comment')
self.assertEqual(
repr(p), '<PubKey: ssh-rsa AAAAB3NzaC1yc2EA comment>')
def test_unicode(self):
p = PubKey('ssh-rsa', 'AAAAB3NzaC1yc2EA', 'comment')
self.assertEqual(unicode(p), 'ssh-rsa AAAAB3NzaC1yc2EA comment')
def test_from_str(self):
p = PubKey.from_str('ssh-rsa AAAAB3NzaC1yc2EA comment')
self.assertEqual(unicode(p), 'ssh-rsa AAAAB3NzaC1yc2EA comment')
def test_eq(self):
self.assertEqual(self.p1, self.p2)
self.assertNotEqual(self.p1, self.p3)
def test_hash(self):
s = set()
s.add(self.p1)
s.add(self.p2)
s.add(self.p3)
self.assertEqual(len(s), 2)
if __name__ == '__main__':
unittest.main()
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from os import mkdir, remove, chown
from pwd import getpwnam
import platform
from shutil import rmtree, move
import subprocess
import sys
working_directory = sys.path[0] # noqa
import logging
import fileinput
import tarfile
from os.path import expanduser, join, exists
from glob import glob
from StringIO import StringIO
from base64 import decodestring
from hashlib import md5
from ssh import PubKey
from .network import change_ip_ubuntu, change_ip_rhel
from context import BaseContext
from twisted.internet import reactor
logger = logging.getLogger()
SSH_DIR = expanduser('~cloud/.ssh')
AUTHORIZED_KEYS = join(SSH_DIR, 'authorized_keys')
STORE_DIR = '/store'
mount_template_linux = (
'//%(host)s/%(username)s %(dir)s cifs username=%(username)s'
',password=%(password)s,iocharset=utf8,uid=cloud 0 0\n')
distros = {'Scientific Linux': 'rhel',
'Oracle Linux Server': 'rhel',
'CentOS': 'rhel',
'CentOS Linux': 'rhel',
'Debian': 'debian',
'Ubuntu': 'debian'}
distro = distros[platform.linux_distribution()[0]]
class Context(BaseContext):
# http://stackoverflow.com/questions/12081310/
# python-module-to-change-system-date-and-time
@staticmethod
def _linux_set_time(time):
import ctypes
import ctypes.util
CLOCK_REALTIME = 0
class timespec(ctypes.Structure):
_fields_ = [("tv_sec", ctypes.c_long),
("tv_nsec", ctypes.c_long)]
librt = ctypes.CDLL(ctypes.util.find_library("rt"))
ts = timespec()
ts.tv_sec = int(time)
ts.tv_nsec = 0
librt.clock_settime(CLOCK_REALTIME, ctypes.byref(ts))
@staticmethod
def change_password(password):
proc = subprocess.Popen(['/usr/sbin/chpasswd'],
stdin=subprocess.PIPE)
proc.communicate('cloud:%s\n' % password)
@staticmethod
def restart_networking():
if distro == 'debian':
subprocess.call(['/etc/init.d/networking', 'restart'])
elif distro == 'rhel':
subprocess.call(['/bin/systemctl', 'restart', 'network'])
pass
@staticmethod
def change_ip(interfaces, dns):
nameservers = dns.replace(' ', '').split(',')
if distro == 'debian':
change_ip_ubuntu(interfaces, nameservers)
elif distro == 'rhel':
change_ip_rhel(interfaces, nameservers)
@staticmethod
def set_time(time):
Context._linux_set_time(float(time))
try:
subprocess.call(['/etc/init.d/ntp', 'restart'])
except Exception:
pass
@staticmethod
def set_hostname(hostname):
with open('/etc/hostname', 'w') as f:
f.write(hostname)
with open('/etc/hosts', 'w') as f:
f.write("127.0.0.1 localhost\n"
"127.0.1.1 %s\n" % hostname)
subprocess.call(['/bin/hostname', hostname])
@staticmethod
def mount_store(host, username, password):
data = {'host': host, 'username': username, 'password': password}
data['dir'] = STORE_DIR
if not exists(STORE_DIR):
mkdir(STORE_DIR)
# TODO
for line in fileinput.input('/etc/fstab', inplace=True):
if not (line.startswith('//') and ' cifs ' in line):
print line.rstrip()
with open('/etc/fstab', 'a') as f:
f.write(mount_template_linux % data)
subprocess.call('mount -a', shell=True)
@staticmethod
def get_keys():
retval = []
try:
with open(AUTHORIZED_KEYS, 'r') as f:
for line in f.readlines():
try:
retval.append(PubKey.from_str(line))
except Exception:
logger.exception(u'Invalid ssh key: ')
except IOError:
pass
return retval
@staticmethod
def _save_keys(keys):
try:
mkdir(SSH_DIR)
except OSError:
pass
with open(AUTHORIZED_KEYS, 'w') as f:
for key in keys:
f.write(unicode(key) + '\n')
uid = getpwnam("cloud").pw_uid
chown(SSH_DIR, uid, -1)
chown(AUTHORIZED_KEYS, uid, -1)
@staticmethod
def add_keys(keys):
new_keys = Context.get_keys()
for key in keys:
try:
p = PubKey.from_str(key)
if p not in new_keys:
new_keys.append(p)
except Exception:
logger.exception(u'Invalid ssh key: ')
Context._save_keys(new_keys)
@staticmethod
def del_keys(keys):
new_keys = Context.get_keys()
for key in keys:
try:
p = PubKey.from_str(key)
try:
new_keys.remove(p)
except ValueError:
pass
except Exception:
logger.exception(u'Invalid ssh key: ')
Context._save_keys(new_keys)
@staticmethod
def cleanup():
filelist = ([
'/root/.bash_history', '/home/cloud/.bash_history',
] + glob('/etc/ssh/ssh_host_*'))
dirlist = ('/root/.ssh', '/home/cloud/.ssh')
for d in dirlist:
rmtree(d, ignore_errors=True)
for f in filelist:
if exists(f):
remove(f)
subprocess.call(('/usr/bin/ssh-keygen', '-A'))
@staticmethod
def start_access_server():
try:
subprocess.call(('/sbin/start', 'ssh'))
except OSError:
subprocess.call(('/bin/systemctl', 'start', 'sshd.service'))
@staticmethod
def append(data, filename, chunk_number, uuid):
if chunk_number == 0:
flag = "w"
else:
flag = "a"
with open(filename, flag) as myfile:
myfile.write(data)
@staticmethod
def update(filename, executable, checksum, uuid):
new_dir = working_directory + '.new'
old_dir = working_directory + '.old.%s' % uuid
with open(filename, "r") as f:
data = f.read()
local_checksum = md5(data).hexdigest()
if local_checksum != checksum:
raise Exception("Checksum missmatch the file is damaged.")
decoded = StringIO(decodestring(data))
try:
tar = tarfile.TarFile.open("dummy", fileobj=decoded, mode='r|gz')
tar.extractall(new_dir)
except tarfile.ReadError as e:
logger.error(e)
move(working_directory, old_dir)
move(new_dir, working_directory)
logger.info("Transfer completed!")
reactor.stop()
@staticmethod
def ipaddresses():
import netifaces
args = {}
interfaces = netifaces.interfaces()
for i in interfaces:
if i == 'lo':
continue
args[i] = []
addresses = netifaces.ifaddresses(i)
args[i] = ([x['addr']
for x in addresses.get(netifaces.AF_INET, [])] +
[x['addr']
for x in addresses.get(netifaces.AF_INET6, [])
if '%' not in x['addr']])
return args
@staticmethod
def get_agent_version():
try:
with open('version.txt') as f:
return f.readline()
except IOError:
return None
@staticmethod
def send_expiration(url):
import notify
notify.notify(url)
import netifaces
from netaddr import IPNetwork
import fileinput
import logging
import subprocess
logger = logging.getLogger()
interfaces_file = '/etc/network/interfaces'
ifcfg_template = '/etc/sysconfig/network-scripts/ifcfg-%s'
def get_interfaces_linux(interfaces):
for ifname in netifaces.interfaces():
mac = netifaces.ifaddresses(ifname)[17][0]['addr']
conf = interfaces.get(mac.upper())
if conf:
yield ifname, conf
def remove_interfaces_ubuntu(devices):
delete_device = False
for line in fileinput.input(interfaces_file, inplace=True):
line = line.rstrip()
words = line.split()
if line.startswith('#') or line == '' or line.isspace() or not words:
# keep line
print line
continue
if (words[0] in ('auto', 'allow-hotplug') and
words[1].split(':')[0] in devices):
# remove line
continue
if words[0] == 'iface':
ifname = words[1].split(':')[0]
if ifname in devices:
# remove line
delete_device = True
continue
else:
delete_device = False
if line[0] in (' ', '\t') and delete_device:
# remove line
continue
# keep line
print line
def change_ip_ubuntu(interfaces, nameservers):
data = list(get_interfaces_linux(interfaces))
for ifname, conf in data:
subprocess.call(('/sbin/ifdown', ifname))
subprocess.call(('/sbin/ip', 'addr', 'flush', 'dev', ifname))
subprocess.call(('/sbin/ip', 'link', 'set', 'dev', ifname,
'down'))
remove_interfaces_ubuntu(dict(data).keys())
with open(interfaces_file, 'a') as f:
for ifname, conf in data:
ipv4_alias_counter = ipv6_alias_counter = 0
f.write('auto %s\n' % ifname)
for i in conf['addresses']:
ip_with_prefix = IPNetwork(i)
prefixlen = ip_with_prefix.prefixlen
ip = ip_with_prefix.ip
alias = ifname
if ip.version == 6:
if ipv6_alias_counter > 0:
alias = '%s:%d' % (ifname, ipv6_alias_counter)
ipv6_alias_counter += 1
else:
if ipv4_alias_counter > 0:
alias = '%s:%d' % (ifname, ipv4_alias_counter)
ipv4_alias_counter += 1
f.write(
'iface %(ifname)s %(proto)s static\n'
' address %(ip)s\n'
' netmask %(prefixlen)d\n'
' gateway %(gw)s\n'
' dns-nameservers %(dns)s\n' % {
'ifname': alias,
'proto': 'inet6' if ip.version == 6 else 'inet',
'ip': ip,
'prefixlen': prefixlen,
'gw': conf['gw6' if ip.version == 6 else 'gw4'],
'dns': ' '.join(nameservers)})
for ifname, conf in data:
subprocess.call(('/sbin/ifup', ifname))
# example:
# change_ip_ubuntu({
# u'02:00:00:02:A3:E8': {
# u'gw4': u'10.1.0.254', 'gw6': '2001::ffff',
# u'addresses': [u'10.1.0.84/24', '10.1.0.1/24', '2001::1/48']},
# u'02:00:00:02:A3:E9': {
# u'gw4': u'10.255.255.1', u'addresses': [u'10.255.255.9']}},
# '8.8.8.8')
def change_ip_rhel(interfaces, nameservers):
for ifname, conf in get_interfaces_linux(interfaces):
subprocess.call(('/sbin/ifdown', ifname))
subprocess.call(('/sbin/ip', 'addr', 'flush', 'dev', ifname))
subprocess.call(('/sbin/ip', 'link', 'set', 'dev', ifname, 'down'))
with open(ifcfg_template % ifname, 'w') as f:
f.write('DEVICE=%s\n'
'BOOTPROTO=none\n'
'NM_CONTROLLED=no\n'
'USERCTL=no\n'
'ONBOOT=yes\n' % ifname)
if len(nameservers) == 0:
f.write('PEERDNS=no')
if len(nameservers) >= 1:
f.write('DNS1=%s\n' % nameservers[0])
if len(nameservers) >= 2:
f.write('DNS2=%s\n' % nameservers[1])
for i in conf['addresses']:
ip_with_prefix = IPNetwork(i)
ip = ip_with_prefix.ip
if ip.version == 6:
f.write('IPV6INIT=yes\n'
'IPV6ADDR=%(ip)s/%(prefixlen)d\n'
'IPV6_DEFAULTGW=%(gw)s\n' % {
'ip': ip,
'prefixlen': ip_with_prefix.prefixlen,
'gw': conf['gw6']})
else:
f.write('NETMASK=%(netmask)s\n'
'IPADDR=%(ip)s\n'
'GATEWAY=%(gw)s\n' % {
'ip': ip,
'netmask': str(ip_with_prefix.netmask),
'gw': conf['gw4']})
subprocess.call(('/sbin/ifup', ifname))
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Virtio-Serial Port Protocol
"""
# system imports
import logging
import os
from time import sleep
# dependent on pyserial ( http://pyserial.sf.net/ )
# only tested w/ 1.18 (5 Dec 2002)
# twisted imports
from twisted.internet import abstract, fdesc
logger = logging.getLogger()
class SerialPort(abstract.FileDescriptor):
"""
A select()able serial device, acting as a transport.
"""
connected = 1
def __init__(self, protocol, deviceNameOrPortNumber, reactor):
abstract.FileDescriptor.__init__(self, reactor)
self.port = deviceNameOrPortNumber
self._serial = os.open(
self.port, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK)
self.reactor = reactor
self.protocol = protocol
self.protocol.makeConnection(self)
self.startReading()
def fileno(self):
return self._serial
def writeSomeData(self, data):
"""
Write some data to the serial device.
"""
return fdesc.writeToFD(self.fileno(), data)
def doRead(self):
"""
Some data's readable from serial device.
"""
return fdesc.readFromFD(self.fileno(), self.protocol.dataReceived)
def connectionLost(self, reason):
"""
Called when the serial port disconnects.
Will call C{connectionLost} on the protocol that is handling the
serial data.
"""
abstract.FileDescriptor.connectionLost(self, reason)
os.close(self._serial)
sleep(2)
self._serial = os.open(
self.port, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK)
self.startReading()
logger.info("Reconnecting")
from base64 import decodestring
from struct import unpack
import binascii
import unittest
class InvalidKeyType(Exception):
pass
class InvalidKey(Exception):
pass
class PubKey(object):
key_types = ('ssh-rsa', 'ssh-dsa', 'ssh-ecdsa')
# http://stackoverflow.com/questions/2494450/ssh-rsa-public-key-
# validation-using-a-regular-expression
@classmethod
def validate_key(cls, key_type, key):
try:
data = decodestring(key)
except binascii.Error:
raise InvalidKey()
int_len = 4
str_len = unpack('>I', data[:int_len])[0]
if data[int_len:int_len + str_len] != key_type:
raise InvalidKey()
def __init__(self, key_type, key, comment):
if key_type not in self.key_types:
raise InvalidKeyType()
self.key_type = key_type
PubKey.validate_key(key_type, key)
self.key = key
self.comment = unicode(comment)
def __hash__(self):
return hash(frozenset(self.__dict__.items()))
def __eq__(self, other):
return self.__dict__ == other.__dict__
@classmethod
def from_str(cls, line):
key_type, key, comment = line.split()
return PubKey(key_type, key, comment)
def __unicode__(self):
return u' '.join((self.key_type, self.key, self.comment))
def __repr__(self):
return u'<PubKey: %s>' % unicode(self)
# Unit tests
class SshTestCase(unittest.TestCase):
def setUp(self):
self.p1 = PubKey.from_str('ssh-rsa AAAAB3NzaC1yc2EA comment')
self.p2 = PubKey.from_str('ssh-rsa AAAAB3NzaC1yc2EA comment')
self.p3 = PubKey.from_str('ssh-rsa AAAAB3NzaC1yc2EC comment')
def test_invalid_key_type(self):
self.assertRaises(InvalidKeyType, PubKey, 'ssh-inv', 'x', 'comment')
def test_valid_key(self):
PubKey('ssh-rsa', 'AAAAB3NzaC1yc2EA', 'comment')
def test_invalid_key(self):
self.assertRaises(InvalidKey, PubKey, 'ssh-rsa', 'x', 'comment')
def test_invalid_key2(self):
self.assertRaises(InvalidKey, PubKey, 'ssh-rsa',
'AAAAB3MzaC1yc2EA', 'comment')
def test_repr(self):
p = PubKey('ssh-rsa', 'AAAAB3NzaC1yc2EA', 'comment')
self.assertEqual(
repr(p), '<PubKey: ssh-rsa AAAAB3NzaC1yc2EA comment>')
def test_unicode(self):
p = PubKey('ssh-rsa', 'AAAAB3NzaC1yc2EA', 'comment')
self.assertEqual(unicode(p), 'ssh-rsa AAAAB3NzaC1yc2EA comment')
def test_from_str(self):
p = PubKey.from_str('ssh-rsa AAAAB3NzaC1yc2EA comment')
self.assertEqual(unicode(p), 'ssh-rsa AAAAB3NzaC1yc2EA comment')
def test_eq(self):
self.assertEqual(self.p1, self.p2)
self.assertNotEqual(self.p1, self.p3)
def test_hash(self):
s = set()
s.add(self.p1)
s.add(self.p2)
s.add(self.p3)
self.assertEqual(len(s), 2)
if __name__ == '__main__':
unittest.main()
description "CIRCLE agent"
start on runlevel [2345]
stop on runlevel [!2345]
respawn
respawn limit 30 30
script
cd /root/agent
. /root/.virtualenvs/agent/bin/activate
python agent.py
end script
# /etc/systemd/system/agent.service
[Unit]
Description=CIRCLE agent
Before=network.target
[Service]
ExecStart=/bin/bash -c "cd /root/agent; . /root/.virtualenvs/agent/bin/activate; python agent.py"
Restart=always
[Install]
WantedBy=multi-user.target
#!/bin/bash
sudo bash -c "source /root/.virtualenvs/agent/bin/activate; source /root/.virtualenvs/agent/bin/postactivate 2>/dev/null; cd /root/agent; ./vm_renewal"
#!/usr/bin/env python
# -*- coding: utf-8 -*-
##
# Notify user about vm expiring
##
import cookielib
import errno
import json
import logging
import multiprocessing
import os
import platform
import subprocess
import urllib2
from urlparse import urlsplit
logger = logging.getLogger()
logger.debug("notify imported")
file_name = "vm_renewal.json"
win = platform.system() == "Windows"
def parse_arguments():
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-u", "--url", type=str, required=True)
args = parser.parse_args()
return args
def get_temp_dir():
if os.getenv("TMPDIR"):
temp_dir = os.getenv("TMPDIR")
elif os.getenv("TMP"):
temp_dir = os.getenv("TMP")
elif os.path.exists("/tmp"):
temp_dir = "/tmp"
elif os.path.exists("/var/tmp"):
temp_dir = "/var/tmp"
return temp_dir
def wall(text):
if win:
return
if text is None:
logger.error("Incorrect function call")
else:
process = subprocess.Popen("wall", stdin=subprocess.PIPE, shell=True)
process.communicate(input=text)[0]
def accept():
import datetime
from tzlocal import get_localzone
from pytz import UTC
file_path = os.path.join(get_temp_dir(), file_name)
if not os.path.isfile(file_path):
print "There is no recent notification to accept."
return False
# Load the saved url
url = json.load(open(file_path, "r"))
cj = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
try:
opener.open(url) # GET to collect cookies
cookies = cj._cookies_for_request(urllib2.Request(url))
token = [c for c in cookies if c.name == "csrftoken"][0].value
req = urllib2.Request(url, "", {
"accept": "application/json", "referer": url,
"x-csrftoken": token})
rsp = opener.open(req)
data = json.load(rsp)
newtime = data["new_suspend_time"]
# Parse time from JSON (Create UTC Localized Datetime objec)
parsed_time = datetime.datetime.strptime(
newtime[:-6], "%Y-%m-%d %H:%M:%S.%f").replace(tzinfo=UTC)
# Convert to the machine localization
new_local_time = parsed_time.astimezone(
get_localzone()).strftime("%Y-%m-%d %H:%M:%S")
except ValueError as e:
print "Parsing time failed: %s" % e
except Exception as e:
print e
print "Renewal failed. Please try it manually at %s" % url
logger.exception("renew failed")
return False
else:
print("Renew succeeded. The machine will be "
"suspended at %s." % new_local_time)
os.remove(file_path)
return True
def notify(url):
try:
logger.debug("notify(%s) called", url)
if win:
logger.info("notifying %d clients", len(clients))
for c in clients:
logger.debug("sending url %s to client %s", url, unicode(c))
c.sendLine(url.encode())
else:
file_path = os.path.join(get_temp_dir(), file_name)
if file_already_exists(file_path):
os.remove(file_path)
if file_already_exists(file_path):
raise Exception(
"Couldn't create file %s as new" %
file_path)
with open(file_path, "w") as f:
json.dump(url, f)
wall("This virtual machine is going to expire! Please type \n"
" vm_renewal\n"
"command to keep it running.")
logger.debug("wall sent, trying to start browser")
p = multiprocessing.Process(target=open_in_browser, args=(url, ))
p.start()
except Exception:
logger.exception("Couldn't notify about %s" % url)
def open_in_browser(url):
if not win:
display = search_display()
if display:
display, uid, gid = display
os.setgid(gid)
os.setuid(uid)
os.environ['DISPLAY'] = display
logger.debug("DISPLAY=%s", display)
else:
display = True
if display:
import webbrowser
webbrowser.open(url, new=2, autoraise=True)
def mount_smb(url):
data = urlsplit(url)
share = data.path.lstrip('/')
subprocess.call(('net', 'use', 'Z:', '/delete'))
try:
p = subprocess.Popen((
'net', 'use', 'Z:', r'\\%s\%s' % (data.hostname, share),
data.password, '/user:%s' % data.username, '/PERSISTENT:YES'),
stderr=subprocess.PIPE, stdout=subprocess.PIPE,
stdin=subprocess.PIPE)
logger.info('mount_smb(): %s', p.communicate())
except Exception:
logger.exception('Unhandled exception: ')
def file_already_exists(name, mode=0o644):
"""Return whether file already exists, create it if not.
Other errors are silently ignored as the file will be reopened anyways.
Creating it is needed to avoid race condition.
"""
try:
fd = os.open(name, os.O_CREAT | os.O_EXCL, mode)
except OSError as e:
if e.errno == errno.EEXIST:
return True
else:
os.close(fd)
return False
def search_display():
"""Search a valid DISPLAY env var in processes
"""
env = os.getenv("DISPLAY")
if env:
return env
for pid in os.listdir("/proc"):
if not pid.isdigit():
continue
env = os.path.join("/proc", pid, "environ")
try:
with open(env, "r") as f:
envs = dict(line.split("=", 1)
for line in f.read().split("\0") if "=" in line)
if "DISPLAY" in envs and ":" in envs["DISPLAY"]:
p = os.stat(os.path.join("/proc", pid))
return envs["DISPLAY"], p.st_uid, p.st_gid
except Exception:
continue
return None
if win:
from twisted.internet import protocol
from twisted.protocols import basic
clients = set()
port = 25683
class PubProtocol(basic.LineReceiver):
def __init__(self, factory):
self.factory = factory
def connectionMade(self):
logger.info("client connected: %s", unicode(self))
clients.add(self)
def connectionLost(self, reason):
logger.info("client disconnected: %s", unicode(self))
clients.remove(self)
class PubFactory(protocol.Factory):
def __init__(self):
clients.clear()
def buildProtocol(self, addr):
return PubProtocol(self)
def register_publisher(reactor):
reactor.listenTCP(port, PubFactory(), interface='localhost')
class SubProtocol(basic.LineReceiver):
def lineReceived(self, line):
print "received", line
if line.startswith('cifs://'):
mount_smb(line)
else:
open_in_browser(line)
class SubFactory(protocol.ReconnectingClientFactory):
def buildProtocol(self, addr):
return SubProtocol()
def run_client():
from twisted.internet import reactor
print "connect to localhost:%d" % port
reactor.connectTCP("localhost", port, SubFactory())
reactor.run()
else:
def register_publisher(reactor):
pass
def main():
args = parse_arguments()
notify(args.url)
if __name__ == '__main__':
main()
from twisted.protocols.basic import LineReceiver
import json
import logging
import platform
logger = logging.getLogger()
system = platform.system()
class SerialLineReceiverBase(LineReceiver, object):
MAX_LENGTH = 1024*1024*128
def __init__(self, *args, **kwargs):
if system == "FreeBSD":
self.delimiter = '\n'
else:
self.delimiter = '\r'
super(SerialLineReceiverBase, self).__init__(*args, **kwargs)
def send_response(self, response, args):
self.transport.write(json.dumps({'response': response,
'args': args}) + '\r\n')
def send_command(self, command, args):
self.transport.write(json.dumps({'command': command,
'args': args}) + '\r\n')
def handle_command(self, command, args):
raise NotImplementedError("Subclass must implement abstract method")
def handle_response(self, response, args):
raise NotImplementedError("Subclass must implement abstract method")
def lineReceived(self, data):
try:
data = json.loads(data)
args = data.get('args', {})
if not isinstance(args, dict):
args = {}
command = data.get('command', None)
response = data.get('response', None)
logger.debug('[serial] valid json: %s' % (data, ))
except (ValueError, KeyError) as e:
logger.error('[serial] invalid json: %s (%s)' % (data, e))
return
if command is not None and isinstance(command, unicode):
logger.debug('received command: %s (%s)' % (command, args))
try:
self.handle_command(command, args)
except Exception as e:
logger.exception(u'Unhandled exception: ')
elif response is not None and isinstance(response, unicode):
logger.debug('received reply: %s (%s)' % (response, args))
self.handle_response(response, args)
#!/usr/bin/env python
import notify
if __name__ == '__main__':
try:
notify.accept()
except:
print ("There was an unknown error while trying to "
"renew this vm, please do it manually!")
import logging
from logging.handlers import NTEventLogHandler
from time import sleep
import os
import servicemanager
import socket
import sys
import win32event
import win32service
import win32serviceutil
logger = logging.getLogger()
fh = NTEventLogHandler(
"CIRCLE Watchdog", dllname=os.path.dirname(__file__))
formatter = logging.Formatter(
"%(asctime)s - %(name)s [%(levelname)s] %(message)s")
fh.setFormatter(formatter)
logger.addHandler(fh)
level = os.environ.get('LOGLEVEL', 'INFO')
logger.setLevel(level)
logger.info("%s loaded", __file__)
service_name = "circle-agent"
stopped = False
def watch():
def check_service(service_name):
return win32serviceutil.QueryServiceStatus(service_name)[1] == 4
def start_service():
win32serviceutil.StartService(service_name)
while True:
if not check_service(service_name):
logger.info("Service %s is not running.", service_name)
start_service()
if stopped:
return
sleep(10)
class AppServerSvc (win32serviceutil.ServiceFramework):
_svc_name_ = "circle-watchdog"
_svc_display_name_ = "CIRCLE Watchdog"
def __init__(self, args):
win32serviceutil.ServiceFramework.__init__(self, args)
self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
socket.setdefaulttimeout(60)
def SvcStop(self):
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
win32event.SetEvent(self.hWaitStop)
global stopped
stopped = True
logger.info("%s stopped", __file__)
def SvcDoRun(self):
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
servicemanager.PYS_SERVICE_STARTED,
(self._svc_name_, ''))
logger.info("%s starting", __file__)
watch()
def main():
if len(sys.argv) == 1:
# service must be starting...
# for the sake of debugging etc, we use win32traceutil to see
# any unhandled exceptions and print statements.
import win32traceutil # noqa
logger.info("service is starting...")
servicemanager.Initialize()
servicemanager.PrepareToHostSingle(AppServerSvc)
# Now ask the service manager to fire things up for us...
servicemanager.StartServiceCtrlDispatcher()
logger.info("service done!")
else:
win32serviceutil.HandleCommandLine(AppServerSvc)
if __name__ == '__main__':
try:
main()
except (SystemExit, KeyboardInterrupt):
raise
except Exception:
logger.exception("Exception:")
#!/usr/bin/env python
# -*- coding: utf-8 -*-
working_directory = r"C:\circle" # noqa
from os.path import join
import logging
import tarfile
from StringIO import StringIO
from base64 import decodestring
from hashlib import md5
from datetime import datetime
import win32api
import wmi
import netifaces
from twisted.internet import reactor
from .network import change_ip_windows
from context import BaseContext
logger = logging.getLogger()
class Context(BaseContext):
@staticmethod
def change_password(password):
from win32com import adsi
ads_obj = adsi.ADsGetObject('WinNT://localhost/%s,user' % 'cloud')
ads_obj.Getinfo()
ads_obj.SetPassword(password)
@staticmethod
def restart_networking():
pass
@staticmethod
def change_ip(interfaces, dns):
nameservers = dns.replace(' ', '').split(',')
change_ip_windows(interfaces, nameservers)
@staticmethod
def set_time(time):
t = datetime.utcfromtimestamp(float(time))
win32api.SetSystemTime(t.year, t.month, 0, t.day, t.hour,
t.minute, t.second, 0)
@staticmethod
def set_hostname(hostname):
wmi.WMI().Win32_ComputerSystem()[0].Rename(hostname)
@staticmethod
def mount_store(host, username, password):
import notify
url = 'cifs://%s:%s@%s/%s' % (username, password, host, username)
for c in notify.clients:
logger.debug("sending url %s to client %s", url, unicode(c))
c.sendLine(url.encode())
@staticmethod
def get_keys():
pass
@staticmethod
def add_keys(keys):
pass
@staticmethod
def del_keys(keys):
pass
@staticmethod
def cleanup():
# TODO
pass
@staticmethod
def start_access_server():
# TODO
pass
@staticmethod
def append(data, filename, chunk_number, uuid):
if chunk_number == 0:
flag = "w"
else:
flag = "a"
with open(filename, flag) as myfile:
myfile.write(data)
@staticmethod
def _update_registry(dir, executable):
# HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\circle-agent
from _winreg import (OpenKeyEx, SetValueEx, QueryValueEx,
HKEY_LOCAL_MACHINE, KEY_ALL_ACCESS)
with OpenKeyEx(HKEY_LOCAL_MACHINE,
r'SYSTEM\CurrentControlSet\services\circle-agent',
0,
KEY_ALL_ACCESS) as key:
(old_executable, reg_type) = QueryValueEx(key, "ImagePath")
SetValueEx(key, "ImagePath", None, 2, join(dir, executable))
return old_executable
@staticmethod
def update(filename, executable, checksum, uuid):
with open(filename, "r") as f:
data = f.read()
local_checksum = md5(data).hexdigest()
if local_checksum != checksum:
raise Exception("Checksum missmatch the file is damaged.")
decoded = StringIO(decodestring(data))
try:
tar = tarfile.TarFile.open("dummy", fileobj=decoded, mode='r|gz')
tar.extractall(working_directory)
except tarfile.ReadError as e:
logger.error(e)
logger.info("Transfer completed!")
Context._update_registry(working_directory, executable)
logger.info('Updated')
reactor.stop()
@staticmethod
def ipaddresses():
args = {}
interfaces = netifaces.interfaces()
for i in interfaces:
if i == 'lo':
continue
args[i] = []
addresses = netifaces.ifaddresses(i)
args[i] = ([x['addr']
for x in addresses.get(netifaces.AF_INET, [])] +
[x['addr']
for x in addresses.get(netifaces.AF_INET6, [])
if '%' not in x['addr']])
return args
@staticmethod
def get_agent_version():
try:
with open(join(working_directory, 'version.txt')) as f:
return f.readline()
except IOError:
return None
from netaddr import IPNetwork, IPAddress
import logging
from subprocess import PIPE, Popen
logger = logging.getLogger()
interfaces_file = '/etc/network/interfaces'
ifcfg_template = '/etc/sysconfig/network-scripts/ifcfg-%s'
# example:
# change_ip_ubuntu({
# u'02:00:00:02:A3:E8': {
# u'gw4': u'10.1.0.254', 'gw6': '2001::ffff',
# u'addresses': [u'10.1.0.84/24', '10.1.0.1/24', '2001::1/48']},
# u'02:00:00:02:A3:E9': {
# u'gw4': u'10.255.255.1', u'addresses': [u'10.255.255.9']}},
# '8.8.8.8')
class IPAddress2(IPNetwork):
def key(self):
return self._module.version, self._value, self._prefixlen
def check_output2(cmd, shell=False):
try:
p = Popen(cmd, shell=shell,
stderr=PIPE, stdout=PIPE, stdin=PIPE)
stdout, stderr = p.communicate()
logger.info('%s: %s, %s', cmd, stdout, stderr)
return stdout
except Exception:
logger.exception(
'Unhandled exception in %s: ', cmd)
def get_interfaces_windows(interfaces):
import wmi
nics = wmi.WMI().Win32_NetworkAdapterConfiguration(IPEnabled=True)
for nic in nics:
conf = interfaces.get(nic.MACAddress)
if conf:
yield nic, conf
def change_ip_windows(interfaces, nameservers):
for nic, conf in get_interfaces_windows(interfaces):
link_local = IPNetwork('fe80::/16')
new_addrs = set([IPAddress2(ip) for ip in conf['addresses']])
old_addrs = set([IPAddress2('%s/%s' % (ip, nic.IPSubnet[i]))
for i, ip in enumerate(nic.IPAddress)
if IPAddress(ip) not in link_local])
addrs_add = new_addrs - old_addrs
addrs_del = old_addrs - new_addrs
changed = (addrs_add or addrs_del or
set(nic.DefaultIPGateway) != set(
[conf.get('gw4'), conf.get('gw6')]))
if changed:
logger.info('new config for <%s(%s)>: %s', nic.Description,
nic.MACAddress, ', '.join(conf['addresses']))
for ip in addrs_add:
logger.info('add %s (%s)', ip, nic.Description)
if ip.version == 6:
cmd = (
'netsh interface ipv6 add address '
'interface=%s address=%s'
% (nic.InterfaceIndex, ip))
else:
cmd = (
'netsh interface ipv4 add address '
'%s %s %s'
% (nic.InterfaceIndex, ip.ip, ip.netmask))
check_output2(cmd, shell=True)
for ip in addrs_del:
proto = 'ipv6' if ip.version == 6 else 'ipv4'
logger.info('del %s (%s)', ip, nic.Description)
check_output2(
'netsh interface %s delete address '
'%s %s'
% (proto, nic.InterfaceIndex, ip.ip), shell=True)
# default gw4
if conf.get('gw4'):
check_output2(
'netsh interface ip del route 0.0.0.0/0 interface=%s'
% nic.InterfaceIndex, shell=True)
check_output2(
'netsh interface ip add route 0.0.0.0/0 interface=%s %s'
% (nic.InterfaceIndex, conf.get('gw4')), shell=True)
# default gw6
if conf.get('gw6'):
check_output2(
'netsh interface ipv6 del route ::/0 interface=%s'
% nic.InterfaceIndex, shell=True)
check_output2(
'netsh interface ipv6 add route ::/0 interface=%s %s'
% (nic.InterfaceIndex, conf.get('gw6')), shell=True)
# DNS
index = 1
for ns in nameservers:
check_output2('netsh interface ipv4 add dnsserver %s '
'address=%s index=%i'
% (nic.InterfaceIndex, ns, index), shell=True)
index += 1
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Serial port support for Windows.
Requires PySerial and pywin32.
"""
# system imports
import win32file
import win32event
import win32con
# twisted imports
from twisted.internet import abstract
# sibling imports
import logging
logger = logging.getLogger()
class SerialPort(abstract.FileDescriptor):
"""A serial device, acting as a transport, that uses a win32 event."""
connected = 1
def __init__(self, protocol, deviceNameOrPortNumber, reactor):
self.hComPort = win32file.CreateFile(
deviceNameOrPortNumber,
win32con.GENERIC_READ | win32con.GENERIC_WRITE,
0, # exclusive access
None, # no security
win32con.OPEN_EXISTING,
win32con.FILE_ATTRIBUTE_NORMAL | win32con.FILE_FLAG_OVERLAPPED,
0)
self.reactor = reactor
self.protocol = protocol
self.outQueue = []
self.closed = 0
self.closedNotifies = 0
self.writeInProgress = 0
self.protocol = protocol
self._overlappedRead = win32file.OVERLAPPED()
self._overlappedRead.hEvent = win32event.CreateEvent(None, 1, 0, None)
self._overlappedWrite = win32file.OVERLAPPED()
self._overlappedWrite.hEvent = win32event.CreateEvent(None, 0, 0, None)
self.reactor.addEvent(
self._overlappedRead.hEvent,
self,
'serialReadEvent')
self.reactor.addEvent(
self._overlappedWrite.hEvent,
self,
'serialWriteEvent')
self.protocol.makeConnection(self)
self._finishPortSetup()
def _finishPortSetup(self):
"""
Finish setting up the serial port.
This is a separate method to facilitate testing.
"""
rc, self.read_buf = win32file.ReadFile(self.hComPort,
win32file.AllocateReadBuffer(1),
self._overlappedRead)
def serialReadEvent(self):
# get that character we set up
try:
n = win32file.GetOverlappedResult(
self.hComPort,
self._overlappedRead,
0)
except Exception:
import time
time.sleep(10)
n = 0
if n:
first = str(self.read_buf[:n])
# now we should get everything that is already in the buffer (max
# 4096)
win32event.ResetEvent(self._overlappedRead.hEvent)
rc, buf = win32file.ReadFile(self.hComPort,
win32file.AllocateReadBuffer(4096),
self._overlappedRead)
n = win32file.GetOverlappedResult(
self.hComPort,
self._overlappedRead,
1)
# handle all the received data:
self.protocol.dataReceived(first + str(buf[:n]))
# set up next one
win32event.ResetEvent(self._overlappedRead.hEvent)
rc, self.read_buf = win32file.ReadFile(self.hComPort,
win32file.AllocateReadBuffer(1),
self._overlappedRead)
def write(self, data):
if data:
if self.writeInProgress:
self.outQueue.append(data)
logger.debug("added to queue")
else:
self.writeInProgress = 1
win32file.WriteFile(self.hComPort, data, self._overlappedWrite)
logger.debug("Writed to file")
def serialWriteEvent(self):
try:
dataToWrite = self.outQueue.pop(0)
except IndexError:
self.writeInProgress = 0
return
else:
win32file.WriteFile(
self.hComPort,
dataToWrite,
self._overlappedWrite)
def connectionLost(self, reason):
"""
Called when the serial port disconnects.
Will call C{connectionLost} on the protocol that is handling the
serial data.
"""
self.reactor.removeEvent(self._overlappedRead.hEvent)
self.reactor.removeEvent(self._overlappedWrite.hEvent)
abstract.FileDescriptor.connectionLost(self, reason)
win32file.CloseHandle(self.hComPort)
self.protocol.connectionLost(reason)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment