diff --git a/actors/.gitignore b/actors/.gitignore
index bbe31bbe..bd0cb678 100644
--- a/actors/.gitignore
+++ b/actors/.gitignore
@@ -1,2 +1,5 @@
bin
*_enterprise*
+udsactor*.deb
+udsactor*.build
+udsactor*.changes
diff --git a/actors/linux/Makefile b/actors/linux/Makefile
new file mode 100644
index 00000000..8221cd31
--- /dev/null
+++ b/actors/linux/Makefile
@@ -0,0 +1,24 @@
+SRCDIR = ../src
+LIBDIR = $(DESTDIR)/usr/share/pyshared/UDSActor
+BINDIR = $(DESTDIR)/usr/bin
+CFGDIR = $(DESTDIR)/etc/udsactor
+clean:
+ rm -rf $(DESTDIR)
+install:
+ mkdir -p $(LIBDIR)
+ mkdir -p $(BINDIR)
+ mkdir -p $(CFGDIR)
+
+ mkdir -p $(LIBDIR)/
+ cp -r $(SRCDIR)/udsactor $(LIBDIR)/udsactor
+ rm -rf $(LIBDIR)/*.py[co]
+
+ cp $(SRCDIR)/UDSActorConfig.py $(LIBDIR)
+ cp $(SRCDIR)/UDSActorUser.py $(LIBDIR)
+ cp $(SRCDIR)/setup_dialog_ui.py $(LIBDIR)
+
+ # chmod 0755 $(BINDIR)/udsactor
+uninstall:
+ rm -rf $(LIBDIR)
+ # rm -f $(BINDIR)/udsactor
+ rm -rf $(CFGDIR)
\ No newline at end of file
diff --git a/actors/linux/UDS_Actor_Configuration.desktop b/actors/linux/UDS_Actor_Configuration.desktop
new file mode 100644
index 00000000..1e4f44f1
--- /dev/null
+++ b/actors/linux/UDS_Actor_Configuration.desktop
@@ -0,0 +1,11 @@
+[Desktop Entry]
+Name=UDS Actor Configuration
+Version=1.0
+Exec=/usr/bin/UDSActorConfig
+Comment=UDS Actor Configuration Application. (Must be executed as root)
+Icon=/usr/share/pyshared/UDSActor/img/uds.png
+Type=Application
+Terminal=false
+StartupNotify=true
+Encoding=UTF-8
+Categories=Settings;System;
\ No newline at end of file
diff --git a/actors/linux/debian/changelog b/actors/linux/debian/changelog
new file mode 100644
index 00000000..ff6d5eef
--- /dev/null
+++ b/actors/linux/debian/changelog
@@ -0,0 +1,5 @@
+udsactor (1.7.0) UNRELEASED; urgency=medium
+
+ * Initial release.
+
+ -- Adolfo Gómez García Click on this button to test the server host and master key parameters. A window will be displayed with results after the test is executed. This button will only be active if all parameters are filled. Enter the Master Key (found on UDS Configuration section) of the UDS Broker to allow communication of the Actor with Broker
Select the security for communications with UDS Broker.
The recommended method of communication is Use SSL, but selection needs to be acording to your broker configuration.
", None)) + self.useSSl.setItemText(0, _translate("UdsActorSetupDialog", "Do not use SSL", None)) + self.useSSl.setItemText(1, _translate("UdsActorSetupDialog", "Use SSL", None)) + self.logLevelLabel.setText(_translate("UdsActorSetupDialog", "Log Level", None)) + diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/REST.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/REST.py new file mode 100644 index 00000000..e006d8fd --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/REST.py @@ -0,0 +1,220 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 201 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 +''' + +# pylint: disable-msg=E1101,W0703 + +from __future__ import unicode_literals + +import requests +import logging +import json +import uuid +import six +import codecs + +from .utils import exceptionToMessage + +VERIFY_CERT = False + + +class RESTError(Exception): + ERRCODE = 0 + + +class ConnectionError(RESTError): + ERRCODE = -1 + + +# Errors ""raised"" from broker +class InvalidKeyError(RESTError): + ERRCODE = 1 + + +class UnmanagedHostError(RESTError): + ERRCODE = 2 + + +class UserServiceNotFoundError(RESTError): + ERRCODE = 3 + + +class OsManagerError(RESTError): + ERRCODE = 4 + + +# Disable warnings log messages +try: + import urllib3 # @UnusedImport +except Exception: + from requests.packages import urllib3 # @Reimport + +try: + urllib3.disable_warnings() # @UndefinedVariable +except Exception: + pass # In fact, isn't too important, but wil log warns to logging file + + +def ensureResultIsOk(result): + if 'error' not in result: + return + + for i in (InvalidKeyError, UnmanagedHostError, UserServiceNotFoundError, OsManagerError): + if result['error'] == i.ERRCODE: + raise i(result['result']) + + err = RESTError(result['result']) + err.ERRCODE = result['error'] + raise err + + +def unscramble(value): + if value is None or value == '': + return value + + value = bytearray(codecs.decode(value, 'hex')) + + n = 0x32 + result = [] + for ch in value: + c = ch ^ n + n = (n + c) & 0xFF + result.append(six.int2byte(c)) + + return b''.join(result)[::-1].decode('utf8') + + +class Api(object): + def __init__(self, host, masterKey, ssl, scrambledResponses=False): + self.host = host + self.masterKey = masterKey + self.useSSL = ssl + self.scrambledResponses = scrambledResponses + self.uuid = None + self.url = "{}://{}/rest/actor/".format(('http', 'https')[ssl], self.host) + self.secretKey = six.text_type(uuid.uuid4()) + self.newerRequestLib = 'verify' in requests.sessions.Session.__attrs__ + # Disable logging requests messages except for errors, ... + logging.getLogger("requests").setLevel(logging.ERROR) + + def _getUrl(self, method, key=None, ids=None): + url = self.url + method + params = [] + if key is not None: + params.append('key=' + key) + if ids is not None: + params.append('id=' + ids) + + if len(params) > 0: + url += '?' + '&'.join(params) + + return url + + def _request(self, url, data=None): + try: + if data is None: + # Old requests version does not support verify, but they do not checks ssl certificate + if self.newerRequestLib: + r = requests.get(url, verify=VERIFY_CERT) + else: + r = requests.get(url) # Always ignore certs?? + else: + if self.newerRequestLib: + r = requests.post(url, data=data, headers={'content-type': 'application/json'}, verify=VERIFY_CERT) + else: + r = requests.post(url, data=data, headers={'content-type': 'application/json'}) + + r = json.loads(r.content) # Using instead of r.json() to make compatible with oooold rquests lib versions + except requests.exceptions.RequestException as e: + raise ConnectionError(e) + except Exception as e: + raise ConnectionError(exceptionToMessage(e)) + + ensureResultIsOk(r) + + if self.scrambledResponses is True: + # test && init are not scrambled, even if rest of messages are + try: + r['result'] = unscramble(r['result']) + except Exception as e: # Can't unscramble, return result "as is" + r['warning'] = True + + return r + + @property + def isConnected(self): + return self.uuid is not None + + def test(self): + url = self._getUrl('test', self.masterKey) + return self._request(url)['result'] + + def init(self, ids): + ''' + Ids is a comma separated values indicating MAC=ip + ''' + url = self._getUrl('init', key=self.masterKey, ids=ids) + self.uuid, self.mac = self._request(url)['result'] + return self.uuid + + def postMessage(self, msg, data, processData=True): + if self.uuid is None: + raise ConnectionError('REST api has not been initialized') + + if processData: + data = json.dumps({'data': data}) + url = self._getUrl('/'.join([self.uuid, msg])) + return self._request(url, data)['result'] + + def notifyComm(self, url): + return self.postMessage('notifyComms', url) + + def login(self, username): + return self.postMessage('login', username) + + def logout(self, username): + return self.postMessage('logout', username) + + def information(self): + return self.postMessage('information', '') + + def setReady(self, ipsInfo): + data = ','.join(['{}={}'.format(v[0], v[1]) for v in ipsInfo]) + return self.postMessage('ready', data) + + def notifyIpChanges(self, ipsInfo): + data = ','.join(['{}={}'.format(v[0], v[1]) for v in ipsInfo]) + return self.postMessage('ip', data) + + def log(self, logLevel, message): + data = json.dumps({'message': message, 'level': logLevel}) + return self.postMessage('log', data, processData=False) + diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/__init__.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/__init__.py @@ -0,0 +1 @@ + diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/certs.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/certs.py new file mode 100644 index 00000000..d98dc8eb --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/certs.py @@ -0,0 +1,103 @@ +# -*- 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 tempfile import gettempdir +from os.path import exists, join + +CERTFILE = 'UDSActor.pem' + +def createSelfSignedCert(force=False): + + certFile = join(gettempdir(), CERTFILE) + + if exists(certFile) and not force: + return certFile + + certData = '''-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCb50K3mIznNklz +yVAD7xSQOSJQ6+NPXj7U9/4zLZ+TvmbQ7RqUUsxbfxHbeRnoYTWV2nKk4+tHqmvz +ujLSS/loFhTSMqtrLn7rowSYJoQhKOUkAiQlWkqCfItWgL5pJopDpNHFul9Rn3ds +PMWQTiGeUNR4Y3RnBhr1Q1BsqAzf4m6zFUmgLPPmVLdF4uJ3Tuz8TSy2gWLs5aSr +5do4WamwUfYjRSVMJECmwjUM4rQ8SQgg0sHBeBuDUGNBvBQFac1G7qUcMReeu8Zr +DUtMsXma/l4rA8NB5CRmTrQbTBF4l+jb2BDFebDqDUK1Oqs9X35yOQfDOAFYHiix +PX0IsXOZAgMBAAECggEBAJi3000RrIUZUp6Ph0gzPMuCjDEEwWiQA7CPNX1gpb8O +dp0WhkDhUroWIaICYPSXtOwUTtVjRqivMoxPy1Thg3EIoGC/rdeSdlXRHMEGicwJ +yVyalFnatr5Xzg5wkxVh4XMd0zeDt7e3JD7s0QLo5lm1CEzd77qz6lhzFic5/1KX +bzdULtTlq60dazg2hEbcS4OmM1UMCtRVDAsOIUIZPL0M9j1C1d1iEdYnh2xshKeG +/GOfo95xsgdMlGjtv3hUT5ryKVoEsu+36rGb4VfhPfUvvoVbRx5QZpW+QvxaYh5E +Fi0JEROozFwG31Y++8El7J3yQko8cFBa1lYYUwwpNAECgYEAykT+GiM2YxJ4uVF1 +OoKiE9BD53i0IG5j87lGPnWqzEwYBwnqjEKDTou+uzMGz3MDV56UEFNho7wUWh28 +LpEkjJB9QgbsugjxIBr4JoL/rYk036e/6+U8I95lvYWrzb+rBMIkRDYI7kbQD/mQ +piYUpuCkTymNAu2RisK6bBzJslkCgYEAxVE23OQvkCeOV8hJNPZGpJ1mDS+TiOow +oOScMZmZpail181eYbAfMsCr7ri812lSj98NvA2GNVLpddil6LtS1cQ5p36lFBtV +xQUMZiFz4qVbEak+izL+vPaev/mXXsOcibAIQ+qI/0txFpNhJjpaaSy6vRCBYFmc +8pgSoBnBI0ECgYAUKCn2atnpp5aWSTLYgNosBU4vDA1PShD14dnJMaqyr0aZtPhF +v/8b3btFJoGgPMLxgWEZ+2U4ju6sSFhPf7FXvLJu2QfQRkHZRDbEh7t5DLpTK4Fp +va9vl6Ml7uM/HsGpOLuqfIQJUs87OFCc7iCSvMJDDU37I7ekT2GKkpfbCQKBgBrE +0NeY0WcSJrp7/oqD2sOcYurpCG/rrZs2SIZmGzUhMxaa0vIXzbO59dlWELB8pmnE +Tf20K//x9qA5OxDe0PcVPukdQlH+/1zSOYNliG44FqnHtyd1TJ/gKVtMBiAiE4uO +aSClod5Yosf4SJbCFd/s5Iyfv52NqsAyp1w3Aj/BAoGAVCnEiGUfyHlIR+UH4zZW +GXJMeqdZLfcEIszMxLePkml4gUQhoq9oIs/Kw+L1DDxUwzkXN4BNTlFbOSu9gzK1 +dhuIUGfS6RPL88U+ivC3A0y2jT43oUMqe3hiRt360UQ1GXzp2dMnR9odSRB1wHoO +IOjEBZ8341/c9ZHc5PCGAG8= +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIJAIrEIthCfxUCMA0GCSqGSIb3DQEBCwUAMIGNMQswCQYD +VQQGEwJFUzEPMA0GA1UECAwGTWFkcmlkMREwDwYDVQQHDAhBbGNvcmNvbjEMMAoG +A1UECgwDVURTMQ4wDAYDVQQLDAVBY3RvcjESMBAGA1UEAwwJVURTIEFjdG9yMSgw +JgYJKoZIhvcNAQkBFhlzdXBwb3J0QHVkc2VudGVycHJpc2UuY29tMB4XDTE0MTAy +NjIzNDEyNFoXDTI0MTAyMzIzNDEyNFowgY0xCzAJBgNVBAYTAkVTMQ8wDQYDVQQI +DAZNYWRyaWQxETAPBgNVBAcMCEFsY29yY29uMQwwCgYDVQQKDANVRFMxDjAMBgNV +BAsMBUFjdG9yMRIwEAYDVQQDDAlVRFMgQWN0b3IxKDAmBgkqhkiG9w0BCQEWGXN1 +cHBvcnRAdWRzZW50ZXJwcmlzZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQCb50K3mIznNklzyVAD7xSQOSJQ6+NPXj7U9/4zLZ+TvmbQ7RqUUsxb +fxHbeRnoYTWV2nKk4+tHqmvzujLSS/loFhTSMqtrLn7rowSYJoQhKOUkAiQlWkqC +fItWgL5pJopDpNHFul9Rn3dsPMWQTiGeUNR4Y3RnBhr1Q1BsqAzf4m6zFUmgLPPm +VLdF4uJ3Tuz8TSy2gWLs5aSr5do4WamwUfYjRSVMJECmwjUM4rQ8SQgg0sHBeBuD +UGNBvBQFac1G7qUcMReeu8ZrDUtMsXma/l4rA8NB5CRmTrQbTBF4l+jb2BDFebDq +DUK1Oqs9X35yOQfDOAFYHiixPX0IsXOZAgMBAAGjUDBOMB0GA1UdDgQWBBRShS90 +5lJTNvYPIEqP3GxWwG5iiDAfBgNVHSMEGDAWgBRShS905lJTNvYPIEqP3GxWwG5i +iDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAU0Sp4gXhQmRVzq+7+ +vRFUkQuPj4Ga/d9r5Wrbg3hck3+5pwe9/7APoq0P/M0DBhQpiJKjrD6ydUevC+Y/ +43ZOJPhMlNw0o6TdQxOkX6FDwQanLLs7sfvJvqtVzYn3nuRFKT3dvl7Zg44QMw2M +ay42q59fAcpB4LaDx/i7gOYSS5eca3lYW7j7YSr/+ozXK2KlgUkuCUHN95lOq+dF +trmV9mjzM4CNPZqKSE7kpHRywgrXGPCO000NvEGSYf82AtgRSFKiU8NWLQSEPdcB +k//2dsQZw2cRZ8DrC2B6Tb3M+3+CA6wVyqfqZh1SZva3LfGvq/C+u+ItguzPqNpI +xtvM +-----END CERTIFICATE-----''' + with open(certFile, "wt") as f: + f.write(certData) + + return certFile + +# At beginning, force certificate creation +createSelfSignedCert(force=True) diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/httpserver.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/httpserver.py new file mode 100644 index 00000000..d514289e --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/httpserver.py @@ -0,0 +1,196 @@ +# -*- 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 +''' + + +import threading +import uuid +import json +import six +from six.moves import socketserver # @UnresolvedImport, pylint: disable=import-error +from six.moves import BaseHTTPServer # @UnresolvedImport, pylint: disable=import-error +import time + +from udsactor.log import logger +from udsactor import utils +from udsactor.certs import createSelfSignedCert +import ssl + +startTime = time.time() + + +class HTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): + uuid = None + ipc = None + lock = threading.Lock() + + def sendJsonError(self, code, message): + self.send_response(code) + 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('/') + try: + params = dict((v.split('=') for v in self.path.split('?')[1].split('&'))) + except: + params = {} + + if path[0] != HTTPServerHandler.uuid: + self.sendJsonError(403, 'Forbidden') + return + + if len(path) != 2: + self.send_response(200) + 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))) + return + + try: + operation = getattr(self, 'get_' + path[1]) + result = operation(params) # Protect not POST methods + except AttributeError: + self.sendJsonError(404, 'Method not found') + return + except Exception as e: + logger.error('Got exception executing GET {}: {}'.format(path[1], utils.toUnicode(e.message))) + self.sendJsonError(500, str(e)) + return + + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.end_headers() + # Send the html message + self.wfile.write(json.dumps(result)) + + def do_POST(self): + path = self.path.split('?')[0][1:].split('/') + if path[0] != HTTPServerHandler.uuid: + self.sendJsonError(403, 'Forbidden') + return + + if len(path) != 2: + self.sendJsonError(400, 'Invalid request') + return + + try: + HTTPServerHandler.lock.acquire() + length = int(self.headers.getheader('content-length')) + content = self.rfile.read(length) + print(length, ">>", content, '<<') + params = json.loads(content) + + operation = getattr(self, 'post_' + path[1]) + result = operation(params) # Protect not POST methods + except AttributeError: + self.sendJsonError(404, 'Method not found') + return + except Exception as e: + logger.error('Got exception executing POST {}: {}'.format(path[1], utils.toUnicode(e.message))) + self.sendJsonError(500, str(e)) + return + finally: + HTTPServerHandler.lock.release() + + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.end_headers() + # Send the html message + self.wfile.write(json.dumps(result)) + + def post_logoff(self, params): + logger.debug('Sending LOGOFF to clients') + HTTPServerHandler.ipc.sendLoggofMessage() + return 'ok' + + # Alias + post_logout = post_logoff + + def post_message(self, params): + logger.debug('Sending MESSAGE to clients') + if 'message' not in params: + raise Exception('Invalid message parameters') + HTTPServerHandler.ipc.sendMessageMessage(params['message']) + return 'ok' + + def post_script(self, params): + if 'script' not in params: + raise Exception('Invalid script parameters') + if 'user' in params: + logger.debug('Sending SCRIPT to clients') + HTTPServerHandler.ipc.sendScriptMessage(params['script']) + else: + # Execute script at server space, that is, here + # as a secondary thread + script = params['script'] + + def executor(): + logger.debug('Executing script: {}'.format(script)) + try: + six.exec_(script, None, None) + except Exception as e: + logger.error('Error executing script: {}'.format(e)) + th = threading.Thread(target=executor) + th.start() + return 'ok' + + def get_information(self, params): + # TODO: Return something useful? :) + return 'Information' + + +class HTTPServerThread(threading.Thread): + def __init__(self, address, ipc): + super(self.__class__, self).__init__() + + if HTTPServerHandler.uuid is None: + HTTPServerHandler.uuid = uuid.uuid4().get_hex() + + HTTPServerHandler.ipc = ipc + + self.certFile = createSelfSignedCert() + self.server = socketserver.TCPServer(address, HTTPServerHandler) + self.server.socket = ssl.wrap_socket(self.server.socket, certfile=self.certFile, server_side=True) + + def getServerUrl(self): + return 'https://{}:{}/{}'.format(self.server.server_address[0], self.server.server_address[1], HTTPServerHandler.uuid) + + def stop(self): + logger.debug('Stopping REST Service') + self.server.shutdown() + + def run(self): + self.server.serve_forever() diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/ipc.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/ipc.py new file mode 100644 index 00000000..32d1b16d --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/ipc.py @@ -0,0 +1,389 @@ +# -*- 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 threading +import sys +import six +import traceback +import pickle + +from udsactor.utils import toUnicode +from udsactor.log import logger + +# The IPC Server will wait for connections from clients +# Clients will open socket, and wait for data from server +# The messages sent (from server) will be the following (subject to future changes): +# Message_id Data Action +# ------------ -------- -------------------------- +# MSG_LOGOFF None Logout user from session +# MSG_MESSAGE message,level Display a message with level (INFO, WARN, ERROR, FATAL) # TODO: Include level, right now only has message +# MSG_SCRIPT python script Execute an specific python script INSIDE CLIENT environment (this messages is not sent right now) +# The messages received (sent from client) will be the following: +# Message_id Data Action +# ------------ -------- -------------------------- +# REQ_LOGOUT Logout user from session +# REQ_INFORMATION None Request information from ipc server (maybe configuration parameters in a near future) +# REQ_LOGIN python script Execute an specific python script INSIDE CLIENT environment (this messages is not sent right now) +# +# All messages are in the form: +# BYTE +# 0 1-2 3 4 ... +# MSG_ID DATA_LENGTH (little endian) Data (can be 0 length) +# With a previos "MAGIC" header in fron of each message + +MSG_LOGOFF = 0xA1 +MSG_MESSAGE = 0xB2 +MSG_SCRIPT = 0xC3 +MSG_INFORMATION = 0xD4 + +# Request messages +REQ_INFORMATION = MSG_INFORMATION +REQ_LOGIN = 0xE5 +REQ_LOGOUT = MSG_LOGOFF + +VALID_MESSAGES = (MSG_LOGOFF, MSG_MESSAGE, MSG_SCRIPT, MSG_INFORMATION) + +REQ_INFORMATION = 0xAA + +MAGIC = b'\x55\x44\x53\x00' # UDS in hexa with a padded 0 to the right + + +# Allows notifying login/logout from client for linux platform +ALLOW_LOG_METHODS = sys.platform != 'win32' + + +# States for client processor +ST_SECOND_BYTE = 0x01 +ST_RECEIVING = 0x02 +ST_PROCESS_MESSAGE = 0x02 + + +class ClientProcessor(threading.Thread): + def __init__(self, parent, clientSocket): + super(self.__class__, self).__init__() + self.parent = parent + self.clientSocket = clientSocket + self.running = False + self.messages = six.moves.queue.Queue(32) # @UndefinedVariable + + def stop(self): + logger.debug('Stoping client processor') + self.running = False + + def processRequest(self, msg, data): + print('Got message {}, with data {}'.format(msg, data)) + + def run(self): + self.running = True + self.clientSocket.setblocking(0) + + state = None + recv_msg = None + recv_data = None + while self.running: + try: + counter = 1024 + while counter > 0: # So we process at least the incoming queue every XX bytes readed + counter -= 1 + b = self.clientSocket.recv(1) + if b == b'': + break + buf = six.byte2int(b) # Empty buffer, this is set as non-blocking + if state is None: + if buf in (REQ_INFORMATION, REQ_LOGIN, REQ_LOGOUT): + print('State set to {}'.format(buf)) + state = buf + recv_msg = buf + continue # Get next byte + else: + logger.debug('Got unexpected data {}'.format(buf)) + elif state in (REQ_INFORMATION, REQ_LOGIN, REQ_LOGOUT): + print('First length byte is {}'.format(buf)) + msg_len = buf + state = ST_SECOND_BYTE + continue + elif state == ST_SECOND_BYTE: + msg_len += buf << 8 + print('Second length byte is {}, len is {}'.format(buf, msg_len)) + if msg_len == 0: + self.processRequest(recv_msg, None) + state = None + break + state = ST_RECEIVING + recv_data = b'' + continue + elif state == ST_RECEIVING: + recv_data += six.int2byte(buf) + msg_len -= 1 + if msg_len == 0: + self.processRequest(recv_msg, recv_data) + recv_data = None + state = None + break + else: + logger.debug('Got invalid message from request: {}, state: {}'.format(buf, state)) + except socket.error as e: + # If no data is present, no problem at all, pass to check messages + pass + + try: + msg = self.messages.get(block=False) + except six.moves.queue.Empty: # No message got in time @UndefinedVariable + continue + + logger.debug('Got message {}'.format(msg)) + + try: + m = msg[1] if msg[1] is not None else b'' + l = len(m) + data = MAGIC + six.int2byte(msg[0]) + six.int2byte(l & 0xFF) + six.int2byte(l >> 8) + m + try: + self.clientSocket.sendall(data) + except socket.error as e: + # Send data error + logger.debug('Socket connection is no more available: {}'.format(e.args)) + self.running = False + except Exception as e: + logger.error('Invalid message in queue: {}'.format(e)) + + try: + self.clientSocket.close() + except Exception: + pass # If can't close, nothing happens, just end thread + + +class ServerIPC(threading.Thread): + + def __init__(self, listenPort, infoParams=None): + super(self.__class__, self).__init__() + self.port = listenPort + self.running = False + self.serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.threads = [] + self.infoParams = infoParams + + def stop(self): + logger.debug('Stopping Server IPC') + self.running = False + for t in self.threads: + t.stop() + socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect(('localhost', self.port)) + self.serverSocket.close() + + for t in self.threads: + t.join() + + def sendMessage(self, msgId, msgData): + ''' + Notify message to all listening threads + ''' + logger.debug('Sending message {},{} to all clients'.format(msgId, msgData)) + + # Convert to bytes so length is correctly calculated + if isinstance(msgData, six.text_type): + msgData = msgData.encode('utf8') + + for t in self.threads: + if t.isAlive(): + logger.debug('Sending to {}'.format(t)) + t.messages.put((msgId, msgData)) + + def sendLoggofMessage(self): + self.sendMessage(MSG_LOGOFF, '') + + def sendMessageMessage(self, message): + self.sendMessage(MSG_MESSAGE, message) + + def sendScriptMessage(self, script): + self.sendMessage(MSG_SCRIPT, script) + + def cleanupFinishedThreads(self): + ''' + Cleans up current threads list + ''' + aliveThreads = [] + for t in self.threads: + if t.isAlive(): + logger.debug('Thread {} is alive'.format(t)) + aliveThreads.append(t) + self.threads[:] = aliveThreads + + def run(self): + self.running = True + + self.serverSocket.bind(('localhost', self.port)) + self.serverSocket.setblocking(1) + self.serverSocket.listen(4) + + while True: + try: + (clientSocket, address) = self.serverSocket.accept() + # Stop processiong if thread is mean to stop + if self.running is False: + break + logger.debug('Got connection from {}'.format(address)) + + self.cleanupFinishedThreads() # House keeping + + t = ClientProcessor(self, clientSocket) + self.threads.append(t) + t.start() + except Exception as e: + logger.error('Got an exception on Server ipc thread: {}'.format(e)) + + +class ClientIPC(threading.Thread): + def __init__(self, listenPort): + super(ClientIPC, self).__init__() + self.port = listenPort + self.running = False + self.clientSocket = None + self.messages = six.moves.queue.Queue(32) # @UndefinedVariable + + self.connect() + + def stop(self): + self.running = False + + def getMessage(self): + while self.running: + try: + return self.messages.get(timeout=1) + except six.moves.queue.Empty: # @UndefinedVariable + continue + + return None + + def sendRequestMessage(self, msg, data=None): + if data is None: + data = b'' + + if isinstance(data, six.text_type): # Convert to bytes if necessary + data = data.encode('utf-8') + + l = len(data) + msg = six.int2byte(msg) + six.int2byte(l & 0xFF) + six.int2byte(l >> 8) + data + self.clientSocket.sendall(msg) + + def requestInformation(self): + self.sendRequestMessage(REQ_INFORMATION) + + def sendLogin(self, username): + self.sendRequestMessage(REQ_LOGIN, username) + + def sendLogout(self, username): + self.sendRequestMessage(REQ_LOGOUT, username) + + def messageReceived(self): + ''' + Override this method to automatically get notified on new message + received. Message is at self.messages queue + ''' + pass # Messa + + def receiveBytes(self, number): + msg = b'' + while self.running and len(msg) < number: + try: + buf = self.clientSocket.recv(number - len(msg)) + if buf == b'': + self.running = False + break + msg += buf + except socket.timeout: + pass + + if self.running is False: + return None + return msg + + def connect(self): + self.clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.clientSocket.connect(('localhost', self.port)) + self.clientSocket.settimeout(2) # 2 seconds timeout + + def run(self): + self.running = True + + while self.running: + try: + msg = b'' + # We look for magic message header + while self.running: # Wait for MAGIC + try: + buf = self.clientSocket.recv(len(MAGIC) - len(msg)) + if buf == b'': + self.running = False + break + msg += buf + if len(msg) != len(MAGIC): + continue # Do not have message + if msg != MAGIC: # Skip first byte an continue searchong + msg = msg[1:] + continue + break + except socket.timeout: # Timeout is here so we can get stop thread + continue + + # Now we get message basic data (msg + datalen) + msg = bytearray(self.receiveBytes(3)) + + # We have the magic header, here comes the message itself + if msg is None: + continue + + msgId = msg[0] + dataLen = msg[1] + (msg[2] << 8) + if msgId not in VALID_MESSAGES: + raise Exception('Invalid message id: {}'.format(msgId)) + + data = self.receiveBytes(dataLen) + if data is None: + continue + + self.messages.put((msgId, data)) + self.messageReceived() + + except socket.error as e: + logger.error('Communication with server got an error: {}'.format(toUnicode(e.strerror))) + self.running = False + return + except Exception as e: + logger.error('Error: {}'.format(e.args)) + + try: + self.clientSocket.close() + except Exception: + pass # If can't close, nothing happens, just end thread + diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/UDSActorService.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/UDSActorService.py new file mode 100644 index 00000000..4bca8688 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/UDSActorService.py @@ -0,0 +1,154 @@ +# -*- 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 import operations + +from udsactor.service import CommonService +from udsactor.service import initCfg +from udsactor.service import IPC_PORT +from udsactor import ipc + +from udsactor.log import logger + +from udsactor.linux.daemon import Daemon +from udsactor.linux import renamer + +import sys + + +class UDSActorSvc(Daemon, CommonService): + def __init__(self, args=None): + Daemon.__init__(self, '/var/run/udsa.pid') + CommonService.__init__(self) + + def rename(self, name, user=None, oldPassword=None, newPassword=None): + ''' + Renames the computer, and optionally sets a password for an user + before this + ''' + + # Check for password change request for an user + if user is not None: + logger.info('Setting password for user {}'.format(user)) + try: + operations.changeUserPassword(user, oldPassword, newPassword) + except Exception as e: + # We stop here without even renaming computer, because the + # process has failed + raise Exception( + 'Could not change password for user {} (maybe invalid current password is configured at broker): {} '.format(user, unicode(e))) + + renamer.rename(name) + self.setReady() + + def joinDomain(self, name, domain, ou, account, password): + logger.fatal('Join domain is not supported on linux platforms right now') + + def run(self): + initCfg() + + logger.debug('Running Daemon') + + # Linux daemon will continue running unless something is requested to + if self.interactWithBroker() is False: + logger.debug('Interact with broker returned false, stopping service after a while') + return + + if self.isAlive is False: + logger.debug('The service is not alive after broker interaction, stopping it') + return + + if self.rebootRequested is True: + logger.debug('Reboot has been requested, stopping service') + return + + self.initIPC() + + # ********************* + # * Main Service loop * + # ********************* + # Counter used to check ip changes only once every 10 seconds, for + # example + counter = 0 + while self.isAlive: + counter += 1 + if counter % 10 == 0: + self.checkIpsChanged() + # In milliseconds, will break + self.doWait(1000) + + self.endIPC() + self.endAPI() + + self.notifyStop() + + +def usage(): + sys.stderr.write("usage: {} start|stop|restart|login 'username'|logout 'username'".format(sys.argv[0])) + sys.exit(2) + +if __name__ == '__main__': + initCfg() + + if len(sys.argv) == 3: + try: + client = ipc.ClientIPC(IPC_PORT) + client.start() + if 'login' == sys.argv[1]: + client.sendLogin(sys.argv[2]) + sys.exit(0) + elif 'logout' == sys.argv[1]: + client.sendLogout(sys.argv[2]) + sys.exit(0) + else: + usage() + except Exception as e: + logger.error(e) + finally: + client.stop() + usage() + + logger.debug('Executing actor') + daemon = UDSActorSvc() + if len(sys.argv) == 2: + if 'start' == sys.argv[1]: + daemon.start() + elif 'stop' == sys.argv[1]: + daemon.stop() + elif 'restart' == sys.argv[1]: + daemon.restart() + else: + usage() + sys.exit(0) + else: + usage() diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/__init__.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/__init__.py new file mode 100644 index 00000000..3a98c780 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/__init__.py @@ -0,0 +1,32 @@ +# -*- 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 diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/daemon.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/daemon.py new file mode 100644 index 00000000..9c62b1b8 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/daemon.py @@ -0,0 +1,166 @@ +# -*- 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: : http://www.jejik.com/authors/sander_marechal/ +@see: : http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/ +''' + +from __future__ import unicode_literals +import sys +import os +import time +import atexit + + +from signal import SIGTERM + + +class Daemon: + """ + A generic daemon class. + + Usage: subclass the Daemon class and override the run() method + """ + def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): + self.stdin = stdin + self.stdout = stdout + self.stderr = stderr + self.pidfile = pidfile + + def daemonize(self): + """ + do the UNIX double-fork magic, see Stevens' "Advanced + Programming in the UNIX Environment" for details (ISBN 0201563177) + http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 + """ + try: + pid = os.fork() + if pid > 0: + # exit first parent + sys.exit(0) + except OSError as e: + sys.stderr.write("fork #1 failed: {} ({})\n".format(e.errno, e.strerror)) + sys.exit(1) + + # decouple from parent environment + os.chdir("/") + os.setsid() + os.umask(0) + + # do second fork + try: + pid = os.fork() + if pid > 0: + # exit from second parent + sys.exit(0) + except OSError as e: + sys.stderr.write("fork #2 failed: {} ({})\n".format(e.errno, e.strerror)) + sys.exit(1) + + # redirect standard file descriptors + sys.stdout.flush() + sys.stderr.flush() + si = open(self.stdin, 'r') + so = open(self.stdout, 'a+') + se = open(self.stderr, 'a+', 0) + os.dup2(si.fileno(), sys.stdin.fileno()) + os.dup2(so.fileno(), sys.stdout.fileno()) + os.dup2(se.fileno(), sys.stderr.fileno()) + + # write pidfile + atexit.register(self.delpid) + pid = str(os.getpid()) + with open(self.pidfile, 'w+') as f: + f.write("{}\n".format(pid)) + + def delpid(self): + os.remove(self.pidfile) + + def start(self): + """ + Start the daemon + """ + # Check for a pidfile to see if the daemon already runs + try: + pf = file(self.pidfile, 'r') + pid = int(pf.read().strip()) + pf.close() + except IOError: + pid = None + + if pid: + message = "pidfile %s already exist. Daemon already running?\n" + sys.stderr.write(message % self.pidfile) + sys.exit(1) + + # Start the daemon + self.daemonize() + self.run() + + def stop(self): + """ + Stop the daemon + """ + # Get the pid from the pidfile + try: + pf = open(self.pidfile, 'r') + pid = int(pf.read().strip()) + pf.close() + except IOError: + pid = None + + if pid is None: + sys.stderr.write("pidfile {} does not exist. Daemon not running?\n".format(self.pidfile)) + return # not an error in a restart + + # Try killing the daemon process + try: + while True: + os.kill(pid, SIGTERM) + time.sleep(1) + except OSError as err: + if err.errno == 3: # No such process + if os.path.exists(self.pidfile): + os.remove(self.pidfile) + else: + sys.stderr.write(err) + sys.exit(1) + + def restart(self): + """ + Restart the daemon + """ + self.stop() + self.start() + + # Overridables + def run(self): + """ + You should override this method when you subclass Daemon. It will be called after the process has been + daemonized by start() or restart(). + """ diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/log.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/log.py new file mode 100644 index 00000000..542ef9bf --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/log.py @@ -0,0 +1,80 @@ +# -*- 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 logging +import os +import tempfile +import six + +# Valid logging levels, from UDS Broker (uds.core.utils.log) +OTHER, DEBUG, INFO, WARN, ERROR, FATAL = (10000 * (x + 1) for x in six.moves.xrange(6)) # @UndefinedVariable + + +class LocalLogger(object): + def __init__(self): + # tempdir is different for "user application" and "service" + # service wil get c:\windows\temp, while user will get c:\users\XXX\temp + # Try to open logger at /var/log path + # If it fails (access denied normally), will try to open one at user's home folder, and if + # agaim it fails, open it at the tmpPath + + for logDir in ('/var/log', os.path.expanduser('~'), tempfile.gettempdir()): + try: + fname = os.path.join(logDir, 'udsactor.log') + logging.basicConfig( + filename=fname, + filemode='a', + format='%(levelname)s %(asctime)s %(message)s', + level=logging.DEBUG + ) + self.logger = logging.getLogger('udsactor') + os.chmod(fname, 0o0600) + return + except Exception: + pass + + # Logger can't be set + self.logger = None + + def log(self, level, message): + # Debug messages are logged to a file + # our loglevels are 10000 (other), 20000 (debug), .... + # logging levels are 10 (debug), 20 (info) + # OTHER = logging.NOTSET + self.logger.log(int(level / 1000) - 10, message) + + def isWindows(self): + return False + + def isLinux(self): + return True diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/operations.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/operations.py new file mode 100644 index 00000000..2a967f09 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/operations.py @@ -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 diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/renamer/__init__.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/renamer/__init__.py new file mode 100644 index 00000000..5d221177 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/renamer/__init__.py @@ -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() \ No newline at end of file diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/renamer/debian.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/renamer/debian.py new file mode 100644 index 00000000..c1761656 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/renamer/debian.py @@ -0,0 +1,68 @@ +# -*- 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): + ''' + Debian renamer + Expects new host name on newName + Host does not needs to be rebooted after renaming + ''' + logger.debug('using Debian renamer') + + with open('/etc/hostname', 'w') as hostname: + hostname.write(newName) + + # Force system new name + os.system('/bin/hostname %s' % newName) + + # add name to "hosts" + with open('/etc/hosts', 'r') as hosts: + lines = hosts.readlines() + with open('/etc/hosts', 'w') as hosts: + hosts.write("127.0.1.1\t%s\n" % newName) + for l in lines: + if l[:9] == '127.0.1.1': # Skips existing 127.0.1.1. if it already exists + continue + hosts.write(l) + + return True + +# All names in lower case +renamers['debian'] = rename +renamers['ubuntu'] = rename diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/store.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/store.py new file mode 100644 index 00000000..ca2d5d17 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/linux/store.py @@ -0,0 +1,80 @@ +# -*- 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 +''' + + +import six +import os + +DEBUG = False + +CONFIGFILE = '/etc/udsactor/udsactor.cfg' if DEBUG is False else '/tmp/udsactor.cfg' + + +def checkPermissions(): + return True if DEBUG else os.getuid() == 0 + + +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 + + return res + + +def writeConfig(data): + cfg = six.moves.configparser.SafeConfigParser() # @UndefinedVariable + cfg.optionxform = six.text_type + cfg.add_section('uds') + for key, val in data.items(): + cfg.set('uds', key, str(val)) + + # Ensures exists destination folder + dirname = os.path.dirname(CONFIGFILE) + if not os.path.exists(dirname): + os.mkdir(dirname, mode=0o700) # Will create only if route to path already exists, for example, /etc (that must... :-)) + + with open(CONFIGFILE, 'w') as f: + cfg.write(f) + + os.chmod(CONFIGFILE, 0o0600) diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/log.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/log.py new file mode 100644 index 00000000..3f2cd3c8 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/log.py @@ -0,0 +1,91 @@ +# -*- 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 sys +import six + +if sys.platform == 'win32': + from udsactor.windows.log import LocalLogger # @UnusedImport +else: + from udsactor.linux.log import LocalLogger # @Reimport + +# Valid logging levels, from UDS Broker (uds.core.utils.log) +OTHER, DEBUG, INFO, WARN, ERROR, FATAL = (10000 * (x + 1) for x in six.moves.xrange(6)) # @UndefinedVariable + + +class Logger(object): + def __init__(self): + self.logLevel = OTHER + self.logger = LocalLogger() + self.remoteLogger = None + + def setLevel(self, level): + self.logLevel = level + self.logger.log(INFO, 'Setting LogLevel to {}'.format(level)) + + def setRemoteLogger(self, remoteLogger): + self.remoteLogger = remoteLogger + + def log(self, level, message): + if level < self.logLevel: # Skip not wanted messages + return + + # If remote logger is available, notify message to it + try: + if self.remoteLogger is not None and self.remoteLogger.isConnected: + self.remoteLogger.log(level, message) + except Exception as e: + self.logger.log(FATAL, 'Error notifying log to broker: {}'.format(e.message)) + + self.logger.log(level, message) + + def debug(self, message): + self.log(DEBUG, message) + + def warn(self, message): + self.log(WARN, message) + + def info(self, message): + self.log(WARN, message) + + def error(self, message): + self.log(ERROR, message) + + def fatal(self, message): + self.log(FATAL, message) + + def flush(self): + pass + + +logger = Logger() diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/operations.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/operations.py new file mode 100644 index 00000000..fc241ab4 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/operations.py @@ -0,0 +1,40 @@ +# -*- 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 +''' +# pylint: disable=unused-wildcard-import,wildcard-import + +from __future__ import unicode_literals + +import sys +if sys.platform == 'win32': + from .windows.operations import * # @UnusedWildImport +else: + from .linux.operations import * # @UnusedWildImport diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/service.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/service.py new file mode 100644 index 00000000..73290f2a --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/service.py @@ -0,0 +1,270 @@ +# -*- 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.log import logger + +from . import operations +from . import store +from . import REST +from . import ipc +from . import httpserver +from .utils import exceptionToMessage + +import socket +import time +import random + +IPC_PORT = 39188 + +cfg = None + + +def initCfg(): + global cfg # pylint: disable=global-statement + cfg = store.readConfig() + + if logger.logger.isWindows(): + # Logs will also go to windows event log for services + logger.logger.serviceLogger = True + + if cfg is not None: + logger.setLevel(cfg.get('logLevel', 20000)) + else: + logger.setLevel(20000) + + +class CommonService(object): + def __init__(self): + self.isAlive = True + self.api = None + self.ipc = None + self.httpServer = None + self.rebootRequested = False + self.knownIps = [] + socket.setdefaulttimeout(20) + + def reboot(self): + self.rebootRequested = True + + def setReady(self): + self.api.setReady([(v.mac, v.ip) for v in operations.getNetworkInfo()]) + + def interactWithBroker(self, scrambledResponses=False): + ''' + Returns True to continue to main loop, false to stop & exit service + ''' + # If no configuration is found, stop service + if cfg is None: + logger.fatal('No configuration found, stopping service') + return False + + self.api = REST.Api(cfg['host'], cfg['masterKey'], cfg['ssl'], scrambledResponses=scrambledResponses) + + # Wait for Broker to be ready + counter = 0 + while self.isAlive: + try: + # getNetworkInfo is a generator function + netInfo = tuple(operations.getNetworkInfo()) + self.knownIps = dict(((i.mac, i.ip) for i in netInfo)) + ids = ','.join([i.mac for i in netInfo]) + if ids == '': + # Wait for any network interface to be ready + logger.debug('No network interfaces found, retrying in a while...') + raise Exception() + logger.debug('Ids: {}'.format(ids)) + self.api.init(ids) + # Set remote logger to notify log info to broker + logger.setRemoteLogger(self.api) + + break + except REST.InvalidKeyError: + logger.fatal('Can\'t sync with broker: Invalid broker Master Key') + return False + except REST.UnmanagedHostError: + # Maybe interface that is registered with broker is not enabled already? + # Right now, we thing that the interface connected to broker is + # the interface that broker will know, let's see how this works + logger.fatal('This host is not managed by UDS Broker (ids: {})'.format(ids)) + return False + except Exception as e: + logger.debug('Exception caugh: {}, retrying'.format(exceptionToMessage(e))) + # Any other error is expectable and recoverable, so let's wait a bit and retry again + # but, if too many errors, will log it (one every minute, for + # example) + counter += 1 + if counter % 60 == 0: # Every 5 minutes, raise a log + logger.info('Trying to inititialize connection with broker (last error: {})'.format(exceptionToMessage(e))) + # Wait a bit before next check + self.doWait(5000) + + # Broker connection is initialized, now get information about what to + # do + counter = 0 + while self.isAlive: + try: + logger.debug('Requesting information of what to do now') + info = self.api.information() + data = info.split('\r') + if len(data) != 2: + logger.error('The format of the information message is not correct (got {})'.format(info)) + raise Exception + params = data[1].split('\t') + if data[0] == 'rename': + try: + if len(params) == 1: # Simple rename + self.rename(params[0]) + # Rename with change password for an user + elif len(params) == 4: + self.rename(params[0], params[1], params[2], params[3]) + else: + logger.error('Got invalid parameter for rename operation: {}'.format(params)) + return False + break + except Exception as e: + logger.error('Error at computer renaming stage: {}'.format(e.message)) + return False + elif data[0] == 'domain': + if len(params) != 5: + logger.error('Got invalid parameters for domain message: {}'.format(params)) + return False + self.joinDomain(params[0], params[1], params[2], params[3], params[4]) + break + else: + logger.error('Unrecognized action sent from broker: {}'.format(data[0])) + return False # Stop running service + except REST.UserServiceNotFoundError: + logger.error('The host has lost the sync state with broker! (host uuid changed?)') + return False + except Exception: + counter += 1 + if counter % 60 == 0: + logger.warn('Too many retries in progress, though still trying (last error: {})'.format(e.message.decode('windows-1250', 'ignore'))) + # Any other error is expectable and recoverable, so let's wait + # a bit and retry again + # Wait a bit before next check + self.doWait(5000) + + if self.rebootRequested: + try: + operations.reboot() + except Exception as e: + logger.error('Exception on reboot: {}'.format(e.message)) + return False # Stops service + + return True + + def checkIpsChanged(self): + netInfo = tuple(operations.getNetworkInfo()) + for i in netInfo: + # If at least one ip has changed + if i.mac in self.knownIps and self.knownIps[i.mac] != i.ip: + logger.info('Notifying ip change to broker (mac {}, from {} to {})'.format(i.mac, self.knownIps[i.mac], i.ip)) + try: + # Notifies all interfaces IPs + self.api.notifyIpChanges(((v.mac, v.ip) for v in netInfo)) + # Regenerates Known ips + self.knownIps = dict(((i.mac, i.ip) for i in netInfo)) + except Exception as e: + logger.warn('Got an error notifiying IPs to broker: {} (will retry in a bit)'.format(e.message.decode('windows-1250', 'ignore'))) + + def initIPC(self): + # ****************************************** + # * Initialize listener IPC & REST threads * + # ****************************************** + logger.debug('Starting IPC listener at {}'.format(IPC_PORT)) + self.ipc = ipc.ServerIPC(IPC_PORT) + self.ipc.start() + + if self.api.mac in self.knownIps: + address = (self.knownIps[self.api.mac], random.randrange(32000, 64000)) + logger.debug('Starting REST listener at {}'.format(address)) + self.httpServer = httpserver.HTTPServerThread(address, self.ipc) + self.httpServer.start() + # And notify it to broker + self.api.notifyComm(self.httpServer.getServerUrl()) + + def endIPC(self): + # Remove IPC threads + if self.ipc is not None: + try: + self.ipc.stop() + except: + logger.error('Couln\'t stop ipc server') + if self.httpServer is not None: + try: + self.httpServer.stop() + except: + logger.error('Couln\'t stop REST server') + + def endAPI(self): + if self.api is not None: + try: + self.api.notifyComm(None) + except: + logger.error('Couln\'t remove comms url from broker') + + self.notifyStop() + + # *************************************************** + # Methods that ARE overriden by linux & windows Actor + # *************************************************** + def rename(self, name, user=None, oldPassword=None, newPassword=None): + ''' + Invoked when broker requests a rename action + MUST BE OVERRIDEN + ''' + raise NotImplementedError('Method renamed has not been implemented!') + + def joinDomain(self, name, domain, ou, account, password): + ''' + Invoked when broker requests a "domain" action + MUST BE OVERRIDEN + ''' + raise NotImplementedError('Method renamed has not been implemented!') + + # **************************************** + # Methods that CAN BE overriden by actors + # **************************************** + def doWait(self, miliseconds): + ''' + Invoked to wait a bit + CAN be OVERRIDEN + ''' + time.sleep(float(miliseconds) / 1000) + + def notifyStop(self): + ''' + Overriden to log stop + ''' + logger.info('Service is being stopped') diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/store.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/store.py new file mode 100644 index 00000000..6281eaa8 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/store.py @@ -0,0 +1,39 @@ +# -*- 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 +''' +# pylint: disable=unused-wildcard-import, wildcard-import +from __future__ import unicode_literals + +import sys +if sys.platform == 'win32': + from udsactor.windows.store import * # @UnusedWildImport +else: + from udsactor.linux.store import * # @UnusedWildImport diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/utils.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/utils.py new file mode 100644 index 00000000..9480a6ab --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/utils.py @@ -0,0 +1,72 @@ +# -*- 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 sys +import six + +if sys.platform == 'win32': + _fromEncoding = 'windows-1250' +else: + _fromEncoding = 'utf-8' + + +def toUnicode(msg): + try: + if not isinstance(msg, six.text_type): + if isinstance(msg, six.binary_type): + return msg.decode(_fromEncoding, 'ignore') + return six.text_type(msg) + else: + return msg + except Exception: + try: + return six.text_type(msg) + except Exception: + return '' + + +def exceptionToMessage(e): + msg = '' + for arg in e.args: + if isinstance(arg, Exception): + msg = msg + exceptionToMessage(arg) + else: + msg = msg + toUnicode(arg) + '. ' + return msg + + +class Bunch(dict): + def __init__(self, **kw): + dict.__init__(self, kw) + self.__dict__ = self + diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/SENS.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/SENS.py new file mode 100644 index 00000000..232706c3 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/SENS.py @@ -0,0 +1,139 @@ +# -*- 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 +''' +# _*_ coding: iso-8859-1 _*_ + +from __future__ import unicode_literals + +import win32com.client +import win32com.server.policy +import os + +from udsactor.log import logger + +# based on python SENS example from +# http://timgolden.me.uk/python/win32_how_do_i/track-session-events.html + +# from Sens.h +SENSGUID_PUBLISHER = "{5fee1bd6-5b9b-11d1-8dd2-00aa004abd5e}" +SENSGUID_EVENTCLASS_LOGON = "{d5978630-5b9f-11d1-8dd2-00aa004abd5e}" + +# from EventSys.h +PROGID_EventSystem = "EventSystem.EventSystem" +PROGID_EventSubscription = "EventSystem.EventSubscription" + +IID_ISensLogon = "{d597bab3-5b9f-11d1-8dd2-00aa004abd5e}" + + +class SensLogon(win32com.server.policy.DesignatedWrapPolicy): + _com_interfaces_ = [IID_ISensLogon] + _public_methods_ = [ + 'Logon', + 'Logoff', + 'StartShell', + 'DisplayLock', + 'DisplayUnlock', + 'StartScreenSaver', + 'StopScreenSaver' + ] + + def __init__(self, api): + self._wrap_(self) + self.api = api + + def Logon(self, *args): + logger.debug('Logon event: {}'.format(args)) + if self.api is not None and self.api.isConnected: + try: + data = self.api.login(args[0]) + logger.debug('Data received for login: {}'.format(data)) + data = data.split('\t') + if len(data) == 2: + logger.debug('Data is valid: {}'.format(data)) + windir = os.environ['windir'] + with open(os.path.join(windir, 'remoteip.txt'), 'w') as f: + f.write(data[0]) + with open(os.path.join(windir, 'remoteh.txt'), 'w') as f: + f.write(data[1]) + except Exception as e: + logger.fatal('Error notifying logon to server: {}'.format(e)) + + def Logoff(self, *args): + logger.debug('Logoff event: {}'.format(args)) + if self.api is not None and self.api.isConnected: + try: + self.api.logout(args[0]) + except Exception as e: + logger.fatal('Error notifying logon to server: {}'.format(e)) + + def StartShell(self, *args): + logevent('StartShell : %s' % [args]) + + def DisplayLock(self, *args): + logevent('DisplayLock : %s' % [args]) + + def DisplayUnlock(self, *args): + logevent('DisplayUnlock : %s' % [args]) + + def StartScreenSaver(self, *args): + # When finished basic actor, we will use this to provide a new parameter: logout on screensaver + # This will allow to easily close sessions of idle users + logevent('StartScreenSaver : %s' % [args]) + + def StopScreenSaver(self, *args): + logevent('StopScreenSaver : %s' % [args]) + + +def logevent(msg): + logger.info(msg) + +# def register(): + # call the CoInitialize to allow the registration to run in an other + # thread + # pythoncom.CoInitialize() + + #logevent('Registring ISensLogon') + + # sl=SensLogon() + # subscription_interface=pythoncom.WrapObject(sl) + + # event_system=win32com.client.Dispatch(PROGID_EventSystem) + + # event_subscription=win32com.client.Dispatch(PROGID_EventSubscription) + # event_subscription.EventClassID=SENSGUID_EVENTCLASS_LOGON + # event_subscription.PublisherID=SENSGUID_PUBLISHER + #event_subscription.SubscriptionName='Python subscription' + # event_subscription.SubscriberInterface=subscription_interface + + #event_system.Store(PROGID_EventSubscription, event_subscription) + + # pythoncom.PumpMessages() + ##logevent('ISensLogon stopped') diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/UDSActorService.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/UDSActorService.py new file mode 100644 index 00000000..aac3c3e9 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/UDSActorService.py @@ -0,0 +1,254 @@ +# -*- 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 +# pylint: disable=unused-wildcard-import, wildcard-import + +import win32serviceutil # @UnresolvedImport, pylint: disable=import-error +import win32service # @UnresolvedImport, pylint: disable=import-error +import win32event # @UnresolvedImport, pylint: disable=import-error +import win32com.client # @UnresolvedImport, @UnusedImport, pylint: disable=import-error +import pythoncom # @UnresolvedImport, pylint: disable=import-error +import servicemanager # @UnresolvedImport, pylint: disable=import-error + + +from udsactor import operations +from udsactor.service import CommonService +from udsactor.service import initCfg + +from udsactor.log import logger + +from .SENS import SensLogon +from .SENS import logevent +from .SENS import SENSGUID_EVENTCLASS_LOGON +from .SENS import SENSGUID_PUBLISHER +from .SENS import PROGID_EventSubscription +from .SENS import PROGID_EventSystem + + +class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService): + ''' + This class represents a Windows Service for managing actor interactions + with UDS Broker and Machine + ''' + _svc_name_ = "UDSActor" + _svc_display_name_ = "UDS Actor Service" + _svc_description_ = "UDS Actor for machines managed by UDS Broker" + # 'System Event Notification' is the SENS service + _svc_deps_ = ['EventLog', 'SENS'] + + def __init__(self, args): + win32serviceutil.ServiceFramework.__init__(self, args) + CommonService.__init__(self) + self.hWaitStop = win32event.CreateEvent(None, 1, 0, None) + + def SvcStop(self): + self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) + self.isAlive = False + win32event.SetEvent(self.hWaitStop) + + SvcShutdown = SvcStop + + def notifyStop(self): + servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, + servicemanager.PYS_SERVICE_STOPPED, + (self._svc_name_, '')) + + def doWait(self, miliseconds): + win32event.WaitForSingleObject(self.hWaitStop, miliseconds) + + def rename(self, name, user=None, oldPassword=None, newPassword=None): + ''' + Renames the computer, and optionally sets a password for an user + before this + ''' + hostName = operations.getComputerName() + + if hostName.lower() == name.lower(): + logger.info('Computer name is now {}'.format(hostName)) + self.setReady() + return + + # Check for password change request for an user + if user is not None: + logger.info('Setting password for user {}'.format(user)) + try: + operations.changeUserPassword(user, oldPassword, newPassword) + except Exception as e: + # We stop here without even renaming computer, because the + # process has failed + raise Exception( + 'Could not change password for user {} (maybe invalid current password is configured at broker): {} '.format(user, unicode(e))) + + operations.renameComputer(name) + # Reboot just after renaming + logger.info('Rebooting computer got activate new name {}'.format(name)) + self.reboot() + + def oneStepJoin(self, name, domain, ou, account, password): + ''' + Ejecutes the join domain in exactly one step + ''' + currName = operations.getComputerName() + # If name is desired, simply execute multiStepJoin, because computer + # name will not change + if currName.lower() == name.lower(): + self.multiStepJoin(name, domain, ou, account, password) + else: + operations.renameComputer(name) + logger.debug('Computer renamed to {} without reboot'.format(name)) + operations.joinDomain( + domain, ou, account, password, executeInOneStep=True) + logger.debug( + 'Requested join domain {} without errors'.format(domain)) + self.reboot() + + def multiStepJoin(self, name, domain, ou, account, password): + currName = operations.getComputerName() + if currName.lower() == name.lower(): + currDomain = operations.getDomainName() + if currDomain is not None and currDomain.lower() == domain.lower(): + logger.info( + 'Machine {} is part of domain {}'.format(name, domain)) + self.setReady() + else: + operations.joinDomain( + domain, ou, account, password, executeInOneStep=False) + else: + operations.renameComputer(name) + logger.info( + 'Rebooting computer got activate new name {}'.format(name)) + self.reboot() + + def joinDomain(self, name, domain, ou, account, password): + ver = operations.getWindowsVersion() + ver = ver[0] * 10 + ver[1] + logger.info('Starting joining domain {} with name {} (detected operating version: {})'.format( + domain, name, ver)) + # Accepts one step joinDomain, also remember XP is no more supported by + # microsoft, but this also must works with it because will do a "multi + # step" join + if ver >= 60: + self.oneStepJoin(name, domain, ou, account, password) + else: + self.multiStepJoin(name, domain, ou, account, password) + + def SvcDoRun(self): + ''' + Main service loop + ''' + initCfg() + + logger.debug('running SvcDoRun') + servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, + servicemanager.PYS_SERVICE_STARTED, + (self._svc_name_, '')) + + # call the CoInitialize to allow the registration to run in an other + # thread + logger.debug('Initializing com...') + pythoncom.CoInitialize() + + # ******************************************************** + # * Ask brokers what to do before proceding to main loop * + # ******************************************************** + if self.interactWithBroker(scrambledResponses=True) is False: + logger.debug('Interact with broker returned false, stopping service after a while') + self.notifyStop() + win32event.WaitForSingleObject(self.hWaitStop, 5000) + return + + if self.isAlive is False: + logger.debug('The service is not alive after broker interaction, stopping it') + self.notifyStop() + return + + if self.rebootRequested is True: + logger.debug('Reboot has been requested, stopping service') + self.notifyStop() + return + + self.initIPC() + + # ******************************** + # * Registers SENS subscriptions * + # ******************************** + logevent('Registering ISensLogon') + subscription_guid = '{41099152-498E-11E4-8FD3-10FEED05884B}' + sl = SensLogon(self.api) + subscription_interface = pythoncom.WrapObject(sl) + + event_system = win32com.client.Dispatch(PROGID_EventSystem) + + event_subscription = win32com.client.Dispatch(PROGID_EventSubscription) + event_subscription.EventClassID = SENSGUID_EVENTCLASS_LOGON + event_subscription.PublisherID = SENSGUID_PUBLISHER + event_subscription.SubscriptionName = 'UDS Actor subscription' + event_subscription.SubscriptionID = subscription_guid + event_subscription.SubscriberInterface = subscription_interface + + event_system.Store(PROGID_EventSubscription, event_subscription) + + logger.debug('Registered SENS, running main loop') + + # ********************* + # * Main Service loop * + # ********************* + # Counter used to check ip changes only once every 10 seconds, for + # example + counter = 0 + while self.isAlive: + counter += 1 + # Process SENS messages, This will be a bit asyncronous (1 second + # delay) + pythoncom.PumpWaitingMessages() + if counter % 10 == 0: + self.checkIpsChanged() + # In milliseconds, will break + win32event.WaitForSingleObject(self.hWaitStop, 1000) + + logger.debug('Exited main loop, deregistering SENS') + + # ******************************************* + # * Remove SENS subscription before exiting * + # ******************************************* + event_system.Remove( + PROGID_EventSubscription, "SubscriptionID == " + subscription_guid) + + self.endIPC() # Ends IPC servers + self.endAPI() # And deinitializes REST api if needed + + self.notifyStop() + + +if __name__ == '__main__': + initCfg() + win32serviceutil.HandleCommandLine(UDSActorSvc) diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/__init__.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/__init__.py new file mode 100644 index 00000000..3a98c780 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/__init__.py @@ -0,0 +1,32 @@ +# -*- 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 diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/log.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/log.py new file mode 100644 index 00000000..97d57887 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/log.py @@ -0,0 +1,77 @@ +# -*- 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 servicemanager # @UnresolvedImport, pylint: disable=import-error +import logging +import os +import tempfile + +# Valid logging levels, from UDS Broker (uds.core.utils.log) +OTHER, DEBUG, INFO, WARN, ERROR, FATAL = (10000 * (x + 1) for x in xrange(6)) + + +class LocalLogger(object): + def __init__(self): + # tempdir is different for "user application" and "service" + # service wil get c:\windows\temp, while user will get c:\users\XXX\temp + logging.basicConfig( + filename=os.path.join(tempfile.gettempdir(), 'udsactor.log'), + filemode='a', + format='%(levelname)s %(asctime)s %(message)s', + level=logging.DEBUG + ) + self.logger = logging.getLogger('udsactor') + self.serviceLogger = False + + def log(self, level, message): + # Debug messages are logged to a file + # our loglevels are 10000 (other), 20000 (debug), .... + # logging levels are 10 (debug), 20 (info) + # OTHER = logging.NOTSET + self.logger.log(level / 1000 - 10, message) + + if level < INFO or self.serviceLogger is False: # Only information and above will be on event log + return + + if level < WARN: # Info + servicemanager.LogInfoMsg(message) + elif level < ERROR: # WARN + servicemanager.LogWarningMsg(message) + else: # Error & Fatal + servicemanager.LogErrorMsg(message) + + def isWindows(self): + return True + + def isLinux(self): + return False diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/operations.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/operations.py new file mode 100644 index 00000000..d7929c31 --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/operations.py @@ -0,0 +1,194 @@ +# -*- 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 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") + adapters = wmobj.ExecQuery("Select * from Win32_NetworkAdapterConfiguration where IpEnabled=True") + try: + for obj in adapters: + for ip in obj.IPAddress: + if ':' in ip: # Is IPV6, skip this + continue + if ip == '' or ip is None: + continue + yield utils.Bunch(name=obj.Caption, mac=obj.MACAddress, ip=ip) + except Exception: + return + + +def getDomainName(): + ''' + Will return the domain name if we belong a domain, else None + (if part of a network group, will also return None) + ''' + # Status: + # 0 = Unknown + # 1 = Unjoined + # 2 = Workgroup + # 3 = Domain + domain, status = win32net.NetGetJoinInformation() + if status != 3: + domain = None + + return domain + + +def getWindowsVersion(): + return win32api.GetVersionEx() + +EWX_LOGOFF = 0x00000000 +EWX_SHUTDOWN = 0x00000001 +EWX_REBOOT = 0x00000002 +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) + privs = ((win32security.LookupPrivilegeValue(None, win32security.SE_SHUTDOWN_NAME), win32security.SE_PRIVILEGE_ENABLED),) + win32security.AdjustTokenPrivileges(htok, 0, privs) + win32api.ExitWindowsEx(flags, 0) + + +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: + # win32api.FormatMessage -> returns error string + # win32api.GetLastError -> returns error code + # (just put this comment here to remember to log this when logger is available) + error = getErrorMessage() + computerName = win32api.GetComputerNameEx(win32con.ComputerNamePhysicalDnsHostname) + raise Exception('Error renaming computer from {} to {}: {}'.format(computerName, newName, error)) + +NETSETUP_JOIN_DOMAIN = 0x00000001 +NETSETUP_ACCT_CREATE = 0x00000002 +NETSETUP_ACCT_DELETE = 0x00000004 +NETSETUP_WIN9X_UPGRADE = 0x00000010 +NETSETUP_DOMAIN_JOIN_IF_JOINED = 0x00000020 +NETSETUP_JOIN_UNSECURE = 0x00000040 +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: + if '.' in domain: + account = account + '@' + domain + else: + account = domain + '\\' + account + + + # Do log + flags = NETSETUP_ACCT_CREATE | NETSETUP_DOMAIN_JOIN_IF_JOINED | NETSETUP_JOIN_DOMAIN + + if executeInOneStep: + flags |= NETSETUP_JOIN_WITH_NEW_NAME + + flags = DWORD(flags) + + domain = LPCWSTR(domain) + + # Must be in format "ou=.., ..., dc=...," + ou = LPCWSTR(ou) if ou is not None and ou != '' else None + account = LPCWSTR(account) + password = LPCWSTR(password) + + res = ctypes.windll.netapi32.NetJoinDomain(None, domain, ou, account, password, flags) + # Machine found in another ou, use it and warn this on log + if res == 2224: + flags = DWORD(NETSETUP_DOMAIN_JOIN_IF_JOINED | NETSETUP_JOIN_DOMAIN) + res = ctypes.windll.netapi32.NetJoinDomain(None, domain, None, account, password, flags) + if res != 0: + # Log the error + error = getErrorMessage(res) + 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) + oldPassword = LPCWSTR(oldPassword) + newPassword = LPCWSTR(newPassword) + + res = ctypes.windll.netapi32.NetUserChangePassword(computerName, user, oldPassword, newPassword) + + if res != 0: + # Log the error, and raise exception to parent + 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) + ctypes.windll.user32.GetLastInputInfo(ctypes.byref(lastInputInfo)) + millis = ctypes.windll.kernel32.GetTickCount() - lastInputInfo.dwTime + return millis / 1000.0 diff --git a/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/store.py b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/store.py new file mode 100644 index 00000000..60eb015f --- /dev/null +++ b/actors/linux/debian/udsactor/usr/share/pyshared/UDSActor/udsactor/windows/store.py @@ -0,0 +1,94 @@ +# -*- 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 win32com.shell import shell # @UnresolvedImport, pylint: disable=import-error +import _winreg as wreg # @UnresolvedImport, pylint: disable=import-error +import win32security # @UnresolvedImport, pylint: disable=import-error +import cPickle + + +# Can be changed to whatever we want, but registry key is protected by permissions +def encoder(data): + return data.encode('bz2') + + +def decoder(data): + return data.decode('bz2') + +DEBUG = True + +path = 'Software\\UDSActor' +baseKey = wreg.HKEY_CURRENT_USER if DEBUG is True else wreg.HKEY_LOCAL_MACHINE # @UndefinedVariable + + +def checkPermissions(): + return True if DEBUG else shell.IsUserAnAdmin() + + +def fixRegistryPermissions(handle): + if DEBUG: + return + # Fix permissions so users can't read this key + v = win32security.GetSecurityInfo(handle, win32security.SE_REGISTRY_KEY, win32security.DACL_SECURITY_INFORMATION) + dacl = v.GetSecurityDescriptorDacl() + n = 0 + # Remove all normal users access permissions to the registry key + while n < dacl.GetAceCount(): + if unicode(dacl.GetAce(n)[2]) == u'PySID:S-1-5-32-545': # Whell known Users SID + dacl.DeleteAce(n) + else: + n += 1 + win32security.SetSecurityInfo(handle, win32security.SE_REGISTRY_KEY, + win32security.DACL_SECURITY_INFORMATION | win32security.PROTECTED_DACL_SECURITY_INFORMATION, + None, None, dacl, None) + + +def readConfig(): + try: + key = wreg.OpenKey(baseKey, path, 0, wreg.KEY_QUERY_VALUE) # @UndefinedVariable + data, _ = wreg.QueryValueEx(key, '') # @UndefinedVariable + wreg.CloseKey(key) # @UndefinedVariable + return cPickle.loads(decoder(data)) + except Exception: + return None + + +def writeConfig(data): + try: + key = wreg.OpenKey(baseKey, path, 0, wreg.KEY_ALL_ACCESS) # @UndefinedVariable + except Exception: + key = wreg.CreateKeyEx(baseKey, path, 0, wreg.KEY_ALL_ACCESS) # @UndefinedVariable + fixRegistryPermissions(key.handle) + + wreg.SetValueEx(key, "", 0, wreg.REG_BINARY, encoder(cPickle.dumps(data))) # @UndefinedVariable + wreg.CloseKey(key) # @UndefinedVariable diff --git a/actors/linux/readme.txt b/actors/linux/readme.txt new file mode 100644 index 00000000..64c14f77 --- /dev/null +++ b/actors/linux/readme.txt @@ -0,0 +1 @@ +This package provides the actor needed for using with UDS infrastructure. diff --git a/actors/src/UDSActorConfig.py b/actors/src/UDSActorConfig.py old mode 100644 new mode 100755 diff --git a/actors/src/UDSActorUser.py b/actors/src/UDSActorUser.py old mode 100644 new mode 100755 index 30d54b40..13c94359 --- a/actors/src/UDSActorUser.py +++ b/actors/src/UDSActorUser.py @@ -38,6 +38,7 @@ import cPickle from udsactor import ipc from udsactor import utils from udsactor.log import logger +from udsactor.service import IPC_PORT class MessagesProcessor(QtCore.QThread): @@ -51,7 +52,7 @@ class MessagesProcessor(QtCore.QThread): def __init__(self): super(self.__class__, self).__init__() try: - self.ipc = ipc.ClientIPC(39188) + self.ipc = ipc.ClientIPC(IPC_PORT) self.ipc.start() except Exception: self.ipc = None @@ -91,7 +92,7 @@ class MessagesProcessor(QtCore.QThread): try: logger.error('Got error on IPC thread {}'.format(utils.exceptionToMessage(e))) except: - logger.error('Got error on IPC thread (and unicode error??)') + logger.error('Got error on IPC thread (an unicode error??)') if self.ipc.running is False and self.running is True: logger.warn('Lost connection with Service, closing program') @@ -99,9 +100,13 @@ class MessagesProcessor(QtCore.QThread): self.exit.emit() -class SystemTrayIconApp(QtGui.QSystemTrayIcon): - def __init__(self, icon, app, parent=None): - self.app = app +class UDSSystemTray(QtGui.QSystemTrayIcon): + def __init__(self, app_, parent=None): + self.app = app_ + + style = app.style() + icon = QtGui.QIcon(style.standardPixmap(QtGui.QStyle.SP_DesktopIcon)) + QtGui.QSystemTrayIcon.__init__(self, icon, parent) self.menu = QtGui.QMenu(parent) exitAction = self.menu.addAction("Exit") @@ -149,9 +154,7 @@ if __name__ == '__main__': QtGui.QMessageBox.critical(None, "Systray", "I couldn't detect any system tray on this system.") sys.exit(1) - style = app.style() - icon = QtGui.QIcon(style.standardPixmap(QtGui.QStyle.SP_DesktopIcon)) - trayIcon = SystemTrayIconApp(icon, app) + trayIcon = UDSSystemTray(app) trayIcon.show() sys.exit(app.exec_()) diff --git a/actors/src/udsactor/REST.py b/actors/src/udsactor/REST.py index 8e485fe4..e006d8fd 100644 --- a/actors/src/udsactor/REST.py +++ b/actors/src/udsactor/REST.py @@ -38,7 +38,6 @@ import requests import logging import json import uuid -import urllib3 import six import codecs @@ -72,6 +71,18 @@ class OsManagerError(RESTError): ERRCODE = 4 +# Disable warnings log messages +try: + import urllib3 # @UnusedImport +except Exception: + from requests.packages import urllib3 # @Reimport + +try: + urllib3.disable_warnings() # @UndefinedVariable +except Exception: + pass # In fact, isn't too important, but wil log warns to logging file + + def ensureResultIsOk(result): if 'error' not in result: return @@ -110,8 +121,8 @@ class Api(object): self.uuid = None self.url = "{}://{}/rest/actor/".format(('http', 'https')[ssl], self.host) self.secretKey = six.text_type(uuid.uuid4()) + self.newerRequestLib = 'verify' in requests.sessions.Session.__attrs__ # Disable logging requests messages except for errors, ... - urllib3.disable_warnings() logging.getLogger("requests").setLevel(logging.ERROR) def _getUrl(self, method, key=None, ids=None): @@ -130,11 +141,18 @@ class Api(object): def _request(self, url, data=None): try: if data is None: - r = requests.get(url, verify=VERIFY_CERT) + # Old requests version does not support verify, but they do not checks ssl certificate + if self.newerRequestLib: + r = requests.get(url, verify=VERIFY_CERT) + else: + r = requests.get(url) # Always ignore certs?? else: - r = requests.post(url, data=data, headers={'content-type': 'application/json'}, verify=VERIFY_CERT) + if self.newerRequestLib: + r = requests.post(url, data=data, headers={'content-type': 'application/json'}, verify=VERIFY_CERT) + else: + r = requests.post(url, data=data, headers={'content-type': 'application/json'}) - r = r.json() + r = json.loads(r.content) # Using instead of r.json() to make compatible with oooold rquests lib versions except requests.exceptions.RequestException as e: raise ConnectionError(e) except Exception as e: @@ -148,7 +166,6 @@ class Api(object): r['result'] = unscramble(r['result']) except Exception as e: # Can't unscramble, return result "as is" r['warning'] = True - pass return r diff --git a/actors/src/udsactor/linux/UDSActorService.py b/actors/src/udsactor/linux/UDSActorService.py index 6688ad5c..4bca8688 100644 --- a/actors/src/udsactor/linux/UDSActorService.py +++ b/actors/src/udsactor/linux/UDSActorService.py @@ -31,27 +31,23 @@ ''' from __future__ import unicode_literals -from udsactor import store -from udsactor import REST from udsactor import operations -from udsactor import httpserver -from udsactor import ipc -from udsactor import operations from udsactor.service import CommonService from udsactor.service import initCfg +from udsactor.service import IPC_PORT +from udsactor import ipc from udsactor.log import logger -from .daemon import Daemon -from . import renamer +from udsactor.linux.daemon import Daemon +from udsactor.linux import renamer -import time -import random +import sys class UDSActorSvc(Daemon, CommonService): - def __init__(self, args): + def __init__(self, args=None): Daemon.__init__(self, '/var/run/udsa.pid') CommonService.__init__(self) @@ -83,6 +79,7 @@ class UDSActorSvc(Daemon, CommonService): logger.debug('Running Daemon') + # Linux daemon will continue running unless something is requested to if self.interactWithBroker() is False: logger.debug('Interact with broker returned false, stopping service after a while') return @@ -96,3 +93,62 @@ class UDSActorSvc(Daemon, CommonService): return self.initIPC() + + # ********************* + # * Main Service loop * + # ********************* + # Counter used to check ip changes only once every 10 seconds, for + # example + counter = 0 + while self.isAlive: + counter += 1 + if counter % 10 == 0: + self.checkIpsChanged() + # In milliseconds, will break + self.doWait(1000) + + self.endIPC() + self.endAPI() + + self.notifyStop() + + +def usage(): + sys.stderr.write("usage: {} start|stop|restart|login 'username'|logout 'username'".format(sys.argv[0])) + sys.exit(2) + +if __name__ == '__main__': + initCfg() + + if len(sys.argv) == 3: + try: + client = ipc.ClientIPC(IPC_PORT) + client.start() + if 'login' == sys.argv[1]: + client.sendLogin(sys.argv[2]) + sys.exit(0) + elif 'logout' == sys.argv[1]: + client.sendLogout(sys.argv[2]) + sys.exit(0) + else: + usage() + except Exception as e: + logger.error(e) + finally: + client.stop() + usage() + + logger.debug('Executing actor') + daemon = UDSActorSvc() + if len(sys.argv) == 2: + if 'start' == sys.argv[1]: + daemon.start() + elif 'stop' == sys.argv[1]: + daemon.stop() + elif 'restart' == sys.argv[1]: + daemon.restart() + else: + usage() + sys.exit(0) + else: + usage() diff --git a/actors/src/udsactor/linux/daemon.py b/actors/src/udsactor/linux/daemon.py index 5c95ddff..9c62b1b8 100644 --- a/actors/src/udsactor/linux/daemon.py +++ b/actors/src/udsactor/linux/daemon.py @@ -158,6 +158,7 @@ class Daemon: self.stop() self.start() + # Overridables def run(self): """ You should override this method when you subclass Daemon. It will be called after the process has been diff --git a/actors/src/udsactor/linux/log.py b/actors/src/udsactor/linux/log.py index 8a1d2fda..542ef9bf 100644 --- a/actors/src/udsactor/linux/log.py +++ b/actors/src/udsactor/linux/log.py @@ -44,13 +44,27 @@ class LocalLogger(object): def __init__(self): # tempdir is different for "user application" and "service" # service wil get c:\windows\temp, while user will get c:\users\XXX\temp - logging.basicConfig( - filename=os.path.join(tempfile.gettempdir(), 'udsactor.log'), - filemode='a', - format='%(levelname)s %(asctime)s %(message)s', - level=logging.DEBUG - ) - self.logger = logging.getLogger('udsactor') + # Try to open logger at /var/log path + # If it fails (access denied normally), will try to open one at user's home folder, and if + # agaim it fails, open it at the tmpPath + + for logDir in ('/var/log', os.path.expanduser('~'), tempfile.gettempdir()): + try: + fname = os.path.join(logDir, 'udsactor.log') + logging.basicConfig( + filename=fname, + filemode='a', + format='%(levelname)s %(asctime)s %(message)s', + level=logging.DEBUG + ) + self.logger = logging.getLogger('udsactor') + os.chmod(fname, 0o0600) + return + except Exception: + pass + + # Logger can't be set + self.logger = None def log(self, level, message): # Debug messages are logged to a file diff --git a/actors/src/udsactor/windows/UDSActorService.py b/actors/src/udsactor/windows/UDSActorService.py index c173d782..aac3c3e9 100644 --- a/actors/src/udsactor/windows/UDSActorService.py +++ b/actors/src/udsactor/windows/UDSActorService.py @@ -230,8 +230,8 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService): # Process SENS messages, This will be a bit asyncronous (1 second # delay) pythoncom.PumpWaitingMessages() - # if counter % 10 == 0: - # self.checkIpsChanged() + if counter % 10 == 0: + self.checkIpsChanged() # In milliseconds, will break win32event.WaitForSingleObject(self.hWaitStop, 1000)