* Adding actors support for linux (based on old actor)

This commit is contained in:
Adolfo Gómez García 2014-11-11 07:09:53 +01:00
parent 85a32cf667
commit 418fac20e5
9 changed files with 341 additions and 32 deletions

View File

@ -33,6 +33,7 @@ from __future__ import unicode_literals
import sys
from PyQt4 import QtCore, QtGui
import six
from udsactor import store
from udsactor import REST
@ -48,7 +49,7 @@ class MyForm(QtGui.QDialog):
self.ui = Ui_UdsActorSetupDialog()
self.ui.setupUi(self)
if data is not None:
logger.debug('Setting configuration parameters in form')
logger.debug('Setting configuration parameters in form: {}'.format(data))
self.ui.host.setText(data.get('host', ''))
self.ui.masterKey.setText(data.get('masterKey', ''))
self.ui.useSSl.setCurrentIndex(1 if data.get('ssl', False) is True else 0)
@ -56,8 +57,8 @@ class MyForm(QtGui.QDialog):
def _getCfg(self):
return {
'host': unicode(self.ui.host.text()),
'masterKey': unicode(self.ui.masterKey.text()),
'host': six.text_type(self.ui.host.text()),
'masterKey': six.text_type(self.ui.masterKey.text()),
'ssl': self.ui.useSSl.currentIndex() == 1,
'logLevel': (self.ui.logLevelComboBox.currentIndex() + 1) * 10000
}
@ -104,7 +105,7 @@ if __name__ == "__main__":
cfg = store.readConfig()
if cfg is not None:
logger.setLevel(cfg.get('logLevel', 20000))
logger.setLevel(int(cfg.get('logLevel', 20000)))
else:
logger.setLevel(20000)

View File

@ -209,8 +209,8 @@ def testRemote():
if __name__ == '__main__':
# ipcServer()
ipcTest()
# testRest()
# ipcTest()
testRest()
# testIdle()
# testServer()
# testRemote()

View File

@ -29,14 +29,14 @@
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
import SimpleHTTPServer
import SocketServer
import threading
import uuid
import json
from base64 import decodestring
import six
from six.moves import socketserver # @UnresolvedImport, pylint: disable=import-error
from six.moves import SimpleHTTPServer # @UnresolvedImport, pylint: disable=import-error
import time
from udsactor.log import logger
@ -54,12 +54,11 @@ class HTTPServerHandler(SimpleHTTPServer.BaseHTTPServer.BaseHTTPRequestHandler):
def sendJsonError(self, code, message):
self.send_response(code)
self.send_header('Content-type','application/json')
self.send_header('Content-type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps({'error': message}))
return
def do_GET(self):
# Very simple path & params splitter
path = self.path.split('?')[0][1:].split('/')
@ -74,7 +73,7 @@ class HTTPServerHandler(SimpleHTTPServer.BaseHTTPServer.BaseHTTPRequestHandler):
if len(path) != 2:
self.send_response(200)
self.send_header('Content-type','application/json')
self.send_header('Content-type', 'application/json')
self.end_headers()
# Send the html message
self.wfile.write(json.dumps("UDS Actor has been running for {} seconds".format(time.time() - startTime)))
@ -88,11 +87,11 @@ class HTTPServerHandler(SimpleHTTPServer.BaseHTTPServer.BaseHTTPRequestHandler):
return
except Exception as e:
logger.error('Got exception executing GET {}: {}'.format(path[1], utils.toUnicode(e.message)))
self.sendJsonError(500, unicode(e))
self.sendJsonError(500, str(e))
return
self.send_response(200)
self.send_header('Content-type','application/json')
self.send_header('Content-type', 'application/json')
self.end_headers()
# Send the html message
self.wfile.write(json.dumps(result))
@ -111,7 +110,7 @@ class HTTPServerHandler(SimpleHTTPServer.BaseHTTPServer.BaseHTTPRequestHandler):
HTTPServerHandler.lock.acquire()
length = int(self.headers.getheader('content-length'))
content = self.rfile.read(length)
print length, ">>", content, '<<'
print(length, ">>", content, '<<')
params = json.loads(content)
operation = getattr(self, 'post_' + path[1])
@ -121,14 +120,13 @@ class HTTPServerHandler(SimpleHTTPServer.BaseHTTPServer.BaseHTTPRequestHandler):
return
except Exception as e:
logger.error('Got exception executing POST {}: {}'.format(path[1], utils.toUnicode(e.message)))
self.sendJsonError(500, unicode(e))
self.sendJsonError(500, str(e))
return
finally:
HTTPServerHandler.lock.release()
self.send_response(200)
self.send_header('Content-type','application/json')
self.send_header('Content-type', 'application/json')
self.end_headers()
# Send the html message
self.wfile.write(json.dumps(result))
@ -158,10 +156,11 @@ class HTTPServerHandler(SimpleHTTPServer.BaseHTTPServer.BaseHTTPRequestHandler):
# Execute script at server space, that is, here
# as a secondary thread
script = params['script']
def executor():
logger.debug('Executing script: {}'.format(script))
try:
exec script in None, None
exec(script, None, None)
except Exception as e:
logger.error('Error executing script: {}'.format(e))
th = threading.Thread(target=executor)
@ -183,7 +182,7 @@ class HTTPServerThread(threading.Thread):
HTTPServerHandler.ipc = ipc
self.certFile = createSelfSignedCert()
self.server = SocketServer.TCPServer(address, HTTPServerHandler)
self.server = socketserver.TCPServer(address, HTTPServerHandler)
self.server.socket = ssl.wrap_socket(self.server.socket, certfile=self.certFile, server_side=True)
def getServerUrl(self):

View File

@ -1 +0,0 @@

View File

@ -0,0 +1,160 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
import socket
import platform
import fcntl
import os
import struct
import array
import six
from udsactor import utils
def _getMacAddr(ifname):
'''
Returns the mac address of an interface
Mac is returned as unicode utf-8 encoded
'''
if isinstance(ifname, list):
return dict([(name, _getMacAddr(name)) for name in ifname])
if isinstance(ifname, six.text_type):
ifname = ifname.encode('utf-8') # If unicode, convert to bytes (or str in python 2.7)
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
info = bytearray(fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', ifname[:15])))
return six.text_type(''.join(['%02x:' % char for char in info[18:24]])[:-1])
except Exception:
return None
def _getIpAddr(ifname):
'''
Returns the ip address of an interface
Ip is returned as unicode utf-8 encoded
'''
if isinstance(ifname, list):
return dict([(name, _getIpAddr(name)) for name in ifname])
if isinstance(ifname, six.text_type):
ifname = ifname.encode('utf-8') # If unicode, convert to bytes (or str in python 2.7)
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
return six.text_type(socket.inet_ntoa(fcntl.ioctl(
s.fileno(),
0x8915, # SIOCGIFADDR
struct.pack('256s', ifname[:15])
)[20:24]))
except Exception:
return None
def _getInterfaces():
'''
Returns a list of interfaces names coded in utf-8
'''
max_possible = 128 # arbitrary. raise if needed.
space = max_possible * 16
if platform.architecture()[0] == '32bit':
offset, length = 32, 32
elif platform.architecture()[0] == '64bit':
offset, length = 16, 40
else:
raise OSError('Unknown arquitecture {0}'.format(platform.architecture()[0]))
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
names = array.array(str('B'), b'\0' * space)
outbytes = struct.unpack('iL', fcntl.ioctl(
s.fileno(),
0x8912, # SIOCGIFCONF
struct.pack('iL', space, names.buffer_info()[0])
))[0]
namestr = names.tostring()
# return namestr, outbytes
return [namestr[i:i + offset].split(b'\0', 1)[0].decode('utf-8') for i in range(0, outbytes, length)]
def _getIpAndMac(ifname):
ip, mac = _getIpAddr(ifname), _getMacAddr(ifname)
return (ip, mac)
def getComputerName():
'''
Returns computer name, with no domain
'''
return socket.gethostname().split('.')[0]
def getNetworkInfo():
for ifname in _getInterfaces():
ip, mac = _getIpAndMac(ifname)
yield utils.Bunch(name=ifname, mac=mac, ip=ip)
def getDomainName():
return ''
def getLinuxVersion():
lv = platform.linux_distribution()
return lv[0] + ', ' + lv[1]
def reboot(flags=0):
'''
Simple reboot using os command
'''
os.system('/sbin/shutdown now -r')
def loggoff():
pass
def renameComputer(newName):
pass
def joinDomain(domain, ou, account, password, executeInOneStep=False):
pass
def changeUserPassword(user, oldPassword, newPassword):
'''
Simple password change for user using command line
'''
os.system('echo "{1}\n{1}" | /usr/bin/passwd {0} 2> /dev/null'.format(user, newPassword))
def getIdleDuration():
return 0

View File

@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
import platform
import os
import sys
import pkgutil
from udsactor.log import logger
renamers = {}
def rename(newName):
distribution = platform.linux_distribution()[0].lower()
if distribution in renamers:
return renamers[distribution](newName)
logger.error('Renamer for platform "{0}" not found'.format(distribution))
return False
# Do load of packages
def _init():
pkgpath = os.path.dirname(sys.modules[__name__].__file__)
for _, name, _ in pkgutil.iter_modules([pkgpath]):
__import__(__name__ + '.' + name, globals(), locals())
_init()

View File

@ -0,0 +1,73 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
from udsactor.linux.renamer import renamers
from udsactor.log import logger
import os
def rename(newName):
# If new name has "'\t'
if '\t' in newName:
newName, account, password = newName.split('\t')
else:
account = password = None
logger.debug('Debian renamer')
if account is not None:
os.system('echo "{1}\n{1}" | /usr/bin/passwd {0} 2> /dev/null'.format(account, password))
f = open('/etc/hostname', 'w')
f.write(newName)
f.close()
os.system('/bin/hostname %s' % newName)
# add name to "hosts"
f = open('/etc/hosts', 'r')
lines = f.readlines()
f.close()
f = open('/etc/hosts', 'w')
f.write("127.0.1.1\t%s\n" % newName)
for l in lines:
if l[:9] == '127.0.1.1':
continue
f.write(l)
f.close()
return True
# All names in lower case
renamers['debian'] = rename
renamers['ubuntu'] = rename

View File

@ -29,7 +29,7 @@
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
import six
import os
@ -47,10 +47,15 @@ def readConfig():
res = {}
try:
cfg = six.moves.configparser.SafeConfigParser() # @UndefinedVariable
cfg.optionxform = six.text_type
cfg.read(CONFIGFILE)
# Just reads 'uds' section
for key in cfg.options('uds'):
res[key] = cfg.get('uds', key)
if res[key].lower() in ('true', 'yes', 'si'):
res[key] = True
elif res[key].lower() in ('false', 'no'):
res[key] = False
except Exception:
pass
@ -59,10 +64,12 @@ def readConfig():
def writeConfig(data):
cfg = six.moves.configparser.SafeConfigParser() # @UndefinedVariable
cfg.optionxform = six.text_type
cfg.add_section('uds')
for key, val in data.iteritems():
cfg.set('uds', key, unicode(val))
with file(CONFIGFILE, 'w') as f:
for key, val in data.items():
cfg.set('uds', key, str(val))
with open(CONFIGFILE, 'w') as f:
cfg.write(f)
os.chmod(CONFIGFILE, 0600)
os.chmod(CONFIGFILE, 0o0600)

View File

@ -31,24 +31,27 @@
'''
from __future__ import unicode_literals
import win32com.client
import win32net
import win32security
import win32api
import win32con
import win32com.client # @UnresolvedImport, pylint: disable=import-error
import win32net # @UnresolvedImport, pylint: disable=import-error
import win32security # @UnresolvedImport, pylint: disable=import-error
import win32api # @UnresolvedImport, pylint: disable=import-error
import win32con # @UnresolvedImport, pylint: disable=import-error
import ctypes
from ctypes.wintypes import DWORD, LPCWSTR
from udsactor import utils
from udsactor.log import logger
def getErrorMessage(res=0):
msg = win32api.FormatMessage(res)
return msg.decode('windows-1250', 'ignore')
def getComputerName():
return win32api.GetComputerNameEx(win32con.ComputerNamePhysicalDnsHostname)
def getNetworkInfo():
obj = win32com.client.Dispatch("WbemScripting.SWbemLocator")
wmobj = obj.ConnectServer("localhost", "root\cimv2")
@ -64,6 +67,7 @@ def getNetworkInfo():
except Exception:
return
def getDomainName():
'''
Will return the domain name if we belong a domain, else None
@ -80,6 +84,7 @@ def getDomainName():
return domain
def getWindowsVersion():
return win32api.GetVersionEx()
@ -90,6 +95,7 @@ EWX_FORCE = 0x00000004
EWX_POWEROFF = 0x00000008
EWX_FORCEIFHUNG = 0x00000010
def reboot(flags=EWX_FORCEIFHUNG | EWX_REBOOT):
hproc = win32api.GetCurrentProcess()
htok = win32security.OpenProcessToken(hproc, win32security.TOKEN_ADJUST_PRIVILEGES | win32security.TOKEN_QUERY)
@ -101,6 +107,7 @@ def reboot(flags=EWX_FORCEIFHUNG | EWX_REBOOT):
def loggoff():
win32api.ExitWindowsEx(EWX_LOGOFF)
def renameComputer(newName):
# Needs admin privileges to work
if ctypes.windll.kernel32.SetComputerNameExW(DWORD(win32con.ComputerNamePhysicalDnsHostname), LPCWSTR(newName)) == 0:
@ -121,6 +128,7 @@ NETSETUP_MACHINE_PWD_PASSED = 0x00000080
NETSETUP_JOIN_WITH_NEW_NAME = 0x00000400
NETSETUP_DEFER_SPN_SET = 0x1000000
def joinDomain(domain, ou, account, password, executeInOneStep=False):
# If account do not have domain, include it
if '@' not in account and '\\' not in account:
@ -156,6 +164,7 @@ def joinDomain(domain, ou, account, password, executeInOneStep=False):
print res, error
raise Exception('Error joining domain {}, with credentials {}/*****{}: {}, {}'.format(domain.value, account.value, ', under OU {}'.format(ou.value) if ou.value != None else '', res, error))
def changeUserPassword(user, oldPassword, newPassword):
computerName = LPCWSTR(getComputerName())
user = LPCWSTR(user)
@ -169,12 +178,14 @@ def changeUserPassword(user, oldPassword, newPassword):
error = getErrorMessage()
raise Exception('Error changing password for user {}: {}'.format(user.value, error))
class LASTINPUTINFO(ctypes.Structure):
_fields_ = [
('cbSize', ctypes.c_uint),
('dwTime', ctypes.c_uint),
]
def getIdleDuration():
lastInputInfo = LASTINPUTINFO()
lastInputInfo.cbSize = ctypes.sizeof(lastInputInfo)