From 628feff05c41bc39d48c39ba67dff5ba43cfb15a Mon Sep 17 00:00:00 2001 From: lgromero Date: Thu, 15 Feb 2024 11:42:04 +0100 Subject: [PATCH] refs #198 adds ogAdmServer and ogAdmAgent code --- native/Sources/Clients/README.es.txt | 9 + native/Sources/Clients/ogAdmClient/Makefile | 31 + .../Clients/ogAdmClient/ogAdmClient.cfg | 5 + .../Clients/ogAdmClient/sources/ogAdmClient.c | 2331 ++++++++ .../Clients/ogAdmClient/sources/ogAdmClient.h | 183 + .../Clients/ogAdmClient/sources/ogAdmClient.o | Bin 0 -> 108604 bytes .../ogagent-oglive_3.0.0-20190520_all.deb | Bin 0 -> 33624 bytes ...gent-oglive_3.0.0-20190520_amd64.buildinfo | 182 + ...gagent-oglive_3.0.0-20190520_amd64.changes | 26 + .../Clients/ogagent/oglive/build-stamp | 0 .../Clients/ogagent/oglive/configure-stamp | 0 .../installed-by-dh_installdocs | 1 + .../Clients/ogagent/oglive/debian/files | 2 + .../debian/ogagent-oglive.debhelper.log | 14 + .../debian/ogagent-oglive/DEBIAN/control | 11 + .../debian/ogagent-oglive/DEBIAN/md5sums | 44 + .../debian/ogagent-oglive/DEBIAN/postinst | 21 + .../debian/ogagent-oglive/DEBIAN/postrm | 10 + .../ogagent-oglive/etc/ogagent/ogagent.cfg | 1 + .../ogagent-oglive/etc/ogagent/ogclient.cfg | 1 + .../debian/ogagent-oglive/usr/bin/ogagent | 6 + .../usr/share/OGAgent/cfg/ogagent.cfg | 27 + .../usr/share/OGAgent/cfg/ogclient.cfg | 11 + .../usr/share/OGAgent/opengnsys/RESTApi.py | 196 + .../usr/share/OGAgent/opengnsys/__init__.py | 60 + .../usr/share/OGAgent/opengnsys/certs.py | 101 + .../usr/share/OGAgent/opengnsys/config.py | 58 + .../usr/share/OGAgent/opengnsys/httpserver.py | 150 + .../usr/share/OGAgent/opengnsys/ipc.py | 423 ++ .../OGAgent/opengnsys/linux/OGAgentService.py | 147 + .../share/OGAgent/opengnsys/linux/__init__.py | 32 + .../share/OGAgent/opengnsys/linux/daemon.py | 182 + .../usr/share/OGAgent/opengnsys/linux/log.py | 80 + .../OGAgent/opengnsys/linux/operations.py | 286 + .../opengnsys/linux/renamer/__init__.py | 61 + .../OGAgent/opengnsys/linux/renamer/debian.py | 68 + .../opengnsys/linux/renamer/opensuse.py | 66 + .../OGAgent/opengnsys/linux/renamer/redhat.py | 74 + .../usr/share/OGAgent/opengnsys/loader.py | 111 + .../usr/share/OGAgent/opengnsys/log.py | 103 + .../share/OGAgent/opengnsys/macos/__init__.py | 32 + .../OGAgent/opengnsys/macos/operations.py | 263 + .../OGAgent/opengnsys/modules/__init__.py | 0 .../modules/client/OpenGnSys/__init__.py | 63 + .../opengnsys/modules/client/__init__.py | 0 .../modules/server/OpenGnSys/__init__.py | 558 ++ .../opengnsys/modules/server/__init__.py | 0 .../OGAgent/opengnsys/oglive/__init__.py | 32 + .../share/OGAgent/opengnsys/oglive/daemon.py | 182 + .../OGAgent/opengnsys/oglive/operations.py | 258 + .../usr/share/OGAgent/opengnsys/operations.py | 57 + .../share/OGAgent/opengnsys/scriptThread.py | 51 + .../usr/share/OGAgent/opengnsys/service.py | 249 + .../usr/share/OGAgent/opengnsys/utils.py | 72 + .../opengnsys/windows/OGAgentService.py | 124 + .../OGAgent/opengnsys/windows/__init__.py | 39 + .../share/OGAgent/opengnsys/windows/log.py | 77 + .../OGAgent/opengnsys/windows/operations.py | 269 + .../OGAgent/opengnsys/workers/__init__.py | 2 + .../opengnsys/workers/client_worker.py | 114 + .../opengnsys/workers/server_worker.py | 186 + .../doc/ogagent-oglive/changelog.Debian.gz | Bin 0 -> 214 bytes .../usr/share/doc/ogagent-oglive/copyright | 26 + .../usr/share/doc/ogagent-oglive/readme.txt | 3 + native/Sources/Includes/Database.cpp | 203 + native/Sources/Includes/Database.h | 61 + native/Sources/Includes/Database.o | Bin 0 -> 33312 bytes native/Sources/Includes/ogAdmLib.c | 1074 ++++ native/Sources/Includes/ogAdmLib.h | 319 ++ native/Sources/Services/ogAdmAgent/Makefile | 42 + .../Services/ogAdmAgent/ogAdmAgent.cfg | 7 + .../ogAdmAgent/sources/ogAdmAgent.cpp | 914 +++ .../Services/ogAdmAgent/sources/ogAdmAgent.h | 81 + .../Services/ogAdmAgent/sources/ogAdmAgent.o | Bin 0 -> 127632 bytes native/Sources/Services/ogAdmRepoAux | 285 + native/Sources/Services/ogAdmServer/Makefile | 38 + .../ogAdmServer/Sources-Services-.txt | 1 + .../Services/ogAdmServer/ogAdmServer.cfg | 8 + .../Services/ogAdmServer/sources/dbi.c | 37 + .../Services/ogAdmServer/sources/dbi.h | 22 + .../Services/ogAdmServer/sources/dbi.o | Bin 0 -> 6728 bytes .../ogAdmServer/sources/ogAdmServer.c | 4961 +++++++++++++++++ .../ogAdmServer/sources/ogAdmServer.h | 60 + .../ogAdmServer/sources/ogAdmServer.o | Bin 0 -> 249504 bytes .../Services/ogAdmServer/tests/clients.json | 1 + .../ogAdmServer/tests/config/ogAdmServer.cfg | 8 + .../ogAdmServer/tests/create_basic_image.json | 1 + .../ogAdmServer/tests/create_image.json | 1 + .../tests/create_incremental_image.json | 1 + .../ogAdmServer/tests/post_clients.json | 1 + .../ogAdmServer/tests/post_shell_output.json | 1 + .../ogAdmServer/tests/post_shell_run.json | 1 + .../Services/ogAdmServer/tests/poweroff.json | 1 + .../Services/ogAdmServer/tests/reboot.json | 1 + .../tests/restore_basic_image.json | 1 + .../ogAdmServer/tests/restore_image.json | 1 + .../tests/restore_incremental_image.json | 1 + .../Services/ogAdmServer/tests/run-tests.py | 41 + .../Services/ogAdmServer/tests/run-tests.sh | 22 + .../ogAdmServer/tests/run_schedule.json | 1 + .../Services/ogAdmServer/tests/session.json | 1 + .../ogAdmServer/tests/setup_image.json | 1 + .../Services/ogAdmServer/tests/stop.json | 1 + .../tests/units/test_0001_get_clients.py | 19 + .../tests/units/test_0002_post_clients.py | 24 + .../tests/units/test_0003_post_wol.py | 34 + .../tests/units/test_0004_post_shell_run.py | 35 + .../units/test_0005_post_shell_output.py | 28 + .../tests/units/test_0006_post_session.py | 34 + .../tests/units/test_0007_post_poweroff.py | 28 + .../tests/units/test_0008_post_reboot.py | 28 + .../tests/units/test_0009_post_stop.py | 28 + .../tests/units/test_0010_post_refresh.py | 28 + .../tests/units/test_0011_post_hardware.py | 28 + .../tests/units/test_0012_post_software.py | 35 + .../tests/units/test_0013_nonexistent.py | 30 + .../tests/units/test_0014_big_request.py | 19 + .../tests/units/test_0015_wrong_headers.py | 29 + .../units/test_0016_post_image_create.py | 39 + .../units/test_0017_post_image_restore.py | 40 + .../tests/units/test_0018_post_setup.py | 56 + .../test_0019_post_image_create_basic.py | 47 + ...test_0020_post_image_create_incremental.py | 49 + .../test_0021_post_image_restore_basic.py | 50 + ...est_0022_post_image_restore_incremental.py | 52 + .../units/test_0023_post_run_schedule.py | 28 + .../Services/ogAdmServer/tests/wol.json | 1 + native/Sources/Services/ogAdmServerAux | 47 + native/Sources/Services/opengnsys.default | 15 + native/Sources/Services/opengnsys.init | 224 + 130 files changed, 17046 insertions(+) create mode 100644 native/Sources/Clients/README.es.txt create mode 100644 native/Sources/Clients/ogAdmClient/Makefile create mode 100644 native/Sources/Clients/ogAdmClient/ogAdmClient.cfg create mode 100644 native/Sources/Clients/ogAdmClient/sources/ogAdmClient.c create mode 100644 native/Sources/Clients/ogAdmClient/sources/ogAdmClient.h create mode 100644 native/Sources/Clients/ogAdmClient/sources/ogAdmClient.o create mode 100644 native/Sources/Clients/ogagent/ogagent-oglive_3.0.0-20190520_all.deb create mode 100644 native/Sources/Clients/ogagent/ogagent-oglive_3.0.0-20190520_amd64.buildinfo create mode 100644 native/Sources/Clients/ogagent/ogagent-oglive_3.0.0-20190520_amd64.changes create mode 100644 native/Sources/Clients/ogagent/oglive/build-stamp create mode 100644 native/Sources/Clients/ogagent/oglive/configure-stamp create mode 100644 native/Sources/Clients/ogagent/oglive/debian/.debhelper/generated/ogagent-oglive/installed-by-dh_installdocs create mode 100644 native/Sources/Clients/ogagent/oglive/debian/files create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive.debhelper.log create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/DEBIAN/control create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/DEBIAN/md5sums create mode 100755 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/DEBIAN/postinst create mode 100755 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/DEBIAN/postrm create mode 120000 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/etc/ogagent/ogagent.cfg create mode 120000 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/etc/ogagent/ogclient.cfg create mode 100755 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/bin/ogagent create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/cfg/ogagent.cfg create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/cfg/ogclient.cfg create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/RESTApi.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/__init__.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/certs.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/config.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/httpserver.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/ipc.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/OGAgentService.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/__init__.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/daemon.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/log.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/operations.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/renamer/__init__.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/renamer/debian.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/renamer/opensuse.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/renamer/redhat.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/loader.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/log.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/macos/__init__.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/macos/operations.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/modules/__init__.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/modules/client/OpenGnSys/__init__.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/modules/client/__init__.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/modules/server/OpenGnSys/__init__.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/modules/server/__init__.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/oglive/__init__.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/oglive/daemon.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/oglive/operations.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/operations.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/scriptThread.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/service.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/utils.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/windows/OGAgentService.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/windows/__init__.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/windows/log.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/windows/operations.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/workers/__init__.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/workers/client_worker.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/workers/server_worker.py create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/doc/ogagent-oglive/changelog.Debian.gz create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/doc/ogagent-oglive/copyright create mode 100644 native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/doc/ogagent-oglive/readme.txt create mode 100644 native/Sources/Includes/Database.cpp create mode 100644 native/Sources/Includes/Database.h create mode 100644 native/Sources/Includes/Database.o create mode 100644 native/Sources/Includes/ogAdmLib.c create mode 100644 native/Sources/Includes/ogAdmLib.h create mode 100644 native/Sources/Services/ogAdmAgent/Makefile create mode 100644 native/Sources/Services/ogAdmAgent/ogAdmAgent.cfg create mode 100644 native/Sources/Services/ogAdmAgent/sources/ogAdmAgent.cpp create mode 100644 native/Sources/Services/ogAdmAgent/sources/ogAdmAgent.h create mode 100644 native/Sources/Services/ogAdmAgent/sources/ogAdmAgent.o create mode 100755 native/Sources/Services/ogAdmRepoAux create mode 100644 native/Sources/Services/ogAdmServer/Makefile create mode 100644 native/Sources/Services/ogAdmServer/Sources-Services-.txt create mode 100644 native/Sources/Services/ogAdmServer/ogAdmServer.cfg create mode 100644 native/Sources/Services/ogAdmServer/sources/dbi.c create mode 100644 native/Sources/Services/ogAdmServer/sources/dbi.h create mode 100644 native/Sources/Services/ogAdmServer/sources/dbi.o create mode 100644 native/Sources/Services/ogAdmServer/sources/ogAdmServer.c create mode 100644 native/Sources/Services/ogAdmServer/sources/ogAdmServer.h create mode 100644 native/Sources/Services/ogAdmServer/sources/ogAdmServer.o create mode 100644 native/Sources/Services/ogAdmServer/tests/clients.json create mode 100644 native/Sources/Services/ogAdmServer/tests/config/ogAdmServer.cfg create mode 100644 native/Sources/Services/ogAdmServer/tests/create_basic_image.json create mode 100644 native/Sources/Services/ogAdmServer/tests/create_image.json create mode 100644 native/Sources/Services/ogAdmServer/tests/create_incremental_image.json create mode 100644 native/Sources/Services/ogAdmServer/tests/post_clients.json create mode 100644 native/Sources/Services/ogAdmServer/tests/post_shell_output.json create mode 100644 native/Sources/Services/ogAdmServer/tests/post_shell_run.json create mode 100644 native/Sources/Services/ogAdmServer/tests/poweroff.json create mode 100644 native/Sources/Services/ogAdmServer/tests/reboot.json create mode 100644 native/Sources/Services/ogAdmServer/tests/restore_basic_image.json create mode 100644 native/Sources/Services/ogAdmServer/tests/restore_image.json create mode 100644 native/Sources/Services/ogAdmServer/tests/restore_incremental_image.json create mode 100755 native/Sources/Services/ogAdmServer/tests/run-tests.py create mode 100755 native/Sources/Services/ogAdmServer/tests/run-tests.sh create mode 100644 native/Sources/Services/ogAdmServer/tests/run_schedule.json create mode 100644 native/Sources/Services/ogAdmServer/tests/session.json create mode 100644 native/Sources/Services/ogAdmServer/tests/setup_image.json create mode 100644 native/Sources/Services/ogAdmServer/tests/stop.json create mode 100644 native/Sources/Services/ogAdmServer/tests/units/test_0001_get_clients.py create mode 100644 native/Sources/Services/ogAdmServer/tests/units/test_0002_post_clients.py create mode 100644 native/Sources/Services/ogAdmServer/tests/units/test_0003_post_wol.py create mode 100644 native/Sources/Services/ogAdmServer/tests/units/test_0004_post_shell_run.py create mode 100644 native/Sources/Services/ogAdmServer/tests/units/test_0005_post_shell_output.py create mode 100644 native/Sources/Services/ogAdmServer/tests/units/test_0006_post_session.py create mode 100644 native/Sources/Services/ogAdmServer/tests/units/test_0007_post_poweroff.py create mode 100644 native/Sources/Services/ogAdmServer/tests/units/test_0008_post_reboot.py create mode 100644 native/Sources/Services/ogAdmServer/tests/units/test_0009_post_stop.py create mode 100644 native/Sources/Services/ogAdmServer/tests/units/test_0010_post_refresh.py create mode 100644 native/Sources/Services/ogAdmServer/tests/units/test_0011_post_hardware.py create mode 100644 native/Sources/Services/ogAdmServer/tests/units/test_0012_post_software.py create mode 100644 native/Sources/Services/ogAdmServer/tests/units/test_0013_nonexistent.py create mode 100644 native/Sources/Services/ogAdmServer/tests/units/test_0014_big_request.py create mode 100644 native/Sources/Services/ogAdmServer/tests/units/test_0015_wrong_headers.py create mode 100644 native/Sources/Services/ogAdmServer/tests/units/test_0016_post_image_create.py create mode 100644 native/Sources/Services/ogAdmServer/tests/units/test_0017_post_image_restore.py create mode 100644 native/Sources/Services/ogAdmServer/tests/units/test_0018_post_setup.py create mode 100644 native/Sources/Services/ogAdmServer/tests/units/test_0019_post_image_create_basic.py create mode 100644 native/Sources/Services/ogAdmServer/tests/units/test_0020_post_image_create_incremental.py create mode 100644 native/Sources/Services/ogAdmServer/tests/units/test_0021_post_image_restore_basic.py create mode 100644 native/Sources/Services/ogAdmServer/tests/units/test_0022_post_image_restore_incremental.py create mode 100644 native/Sources/Services/ogAdmServer/tests/units/test_0023_post_run_schedule.py create mode 100644 native/Sources/Services/ogAdmServer/tests/wol.json create mode 100755 native/Sources/Services/ogAdmServerAux create mode 100644 native/Sources/Services/opengnsys.default create mode 100755 native/Sources/Services/opengnsys.init diff --git a/native/Sources/Clients/README.es.txt b/native/Sources/Clients/README.es.txt new file mode 100644 index 0000000..56900d8 --- /dev/null +++ b/native/Sources/Clients/README.es.txt @@ -0,0 +1,9 @@ +OpenGnsys Services for Clients README +======================================= + + +Este directorio contiene el código fuente de los servicios OpenGnsys específicos para clientes. + +- ogAdmClient servicio para cliente ogLive que atiende peticiones de OpenGnsys Server +- ogagent OGAgent: agente modular para sistemas operativos con API REST + diff --git a/native/Sources/Clients/ogAdmClient/Makefile b/native/Sources/Clients/ogAdmClient/Makefile new file mode 100644 index 0000000..d3628c9 --- /dev/null +++ b/native/Sources/Clients/ogAdmClient/Makefile @@ -0,0 +1,31 @@ +# makefile + +# Nombre del proyecto +PROYECTO := ogAdmClient + +# Directorios y librerias +DIRS := +LIBS := -static + +# Opciones de compilacion +OPCS := -m32 -O0 -g -Wall # Depuracion +#OPCS := -m32 -O3 -Wall # Optimizacion + +# Ficheros objetos +OBJS := sources/ogAdmClient.o + +all: $(PROYECTO) + +$(PROYECTO): $(OBJS) + gcc $(OPCS) $(DIRS) $(LIBS) $(OBJS) -o $(PROYECTO) +# strip $(PROYECTO) # Optimizacion + +clean: + rm -f $(PROYECTO) $(OBJS) + +sources/%.o: sources/%.c + gcc $(OPCS) -I ../../Includes -c -o"$@" "$<" + + + + diff --git a/native/Sources/Clients/ogAdmClient/ogAdmClient.cfg b/native/Sources/Clients/ogAdmClient/ogAdmClient.cfg new file mode 100644 index 0000000..b15e740 --- /dev/null +++ b/native/Sources/Clients/ogAdmClient/ogAdmClient.cfg @@ -0,0 +1,5 @@ +ServidorAdm=SERVERIP +PUERTO=2008 +PATHINTERFACE=/opt/opengnsys/interfaceAdm +UrlMenu=OPENGNSYSURL/varios/menubrowser.php +UrlMsg=http://localhost/cgi-bin/httpd-log.sh \ No newline at end of file diff --git a/native/Sources/Clients/ogAdmClient/sources/ogAdmClient.c b/native/Sources/Clients/ogAdmClient/sources/ogAdmClient.c new file mode 100644 index 0000000..6f87cc4 --- /dev/null +++ b/native/Sources/Clients/ogAdmClient/sources/ogAdmClient.c @@ -0,0 +1,2331 @@ +// ******************************************************************************************************** +// Cliernte: ogAdmClient +// Autor: José Manuel Alonso (E.T.S.I.I.) Universidad de Sevilla +// Fecha Creación: Marzo-2010 +// Fecha Última modificación: Abril-2010 +// Nombre del fichero: ogAdmClient.c +// Descripción :Este fichero implementa el cliente general del sistema +// ******************************************************************************************************** + +#include "ogAdmClient.h" +#include "ogAdmLib.c" +//________________________________________________________________________________________________________ +// Función: tomaConfiguracion +// +// Descripción: +// Lee el fichero de configuración del servicio +// Parámetros: +// filecfg : Ruta completa al fichero de configuración +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//________________________________________________________________________________________________________ +BOOLEAN tomaConfiguracion(char* filecfg) +{ + char modulo[] = "tomaConfiguracion()"; + + if (filecfg == NULL || strlen(filecfg) == 0) { + errorLog(modulo, 1, FALSE); // Fichero de configuración del cliente vacío + return (FALSE); + } + FILE *fcfg; + int lSize; + char * buffer, *lineas[MAXPRM], *dualparametro[2]; + int i, numlin, resul; + + fcfg = fopen(filecfg, "rt"); + if (fcfg == NULL) { + errorLog(modulo, 2, FALSE); // No existe fichero de configuración del cliente + return (FALSE); + } + + fseek(fcfg, 0, SEEK_END); + lSize = ftell(fcfg); // Obtiene tamaño del fichero. + rewind(fcfg); + buffer = (char*) reservaMemoria(lSize+1); // Toma memoria para el buffer de lectura. + if (buffer == NULL) { // No hay memoria suficiente para el buffer + errorLog(modulo, 3, FALSE); + return (FALSE); + } + lSize=fread(buffer, 1, lSize, fcfg); // Lee contenido del fichero + buffer[lSize]=CHARNULL; + fclose(fcfg); + + /* Inicializar variables globales */ + servidoradm[0]=CHARNULL; + puerto[0] = CHARNULL; + pathinterface[0]=CHARNULL; + urlmenu[0]=CHARNULL; + urlmsg[0]=CHARNULL; + + numlin = splitCadena(lineas, buffer, '\n'); + for (i = 0; i < numlin; i++) { + splitCadena(dualparametro, lineas[i], '='); + + resul = strcmp(StrToUpper(dualparametro[0]), "SERVIDORADM"); + if (resul == 0) + strcpy(servidoradm, dualparametro[1]); + + resul = strcmp(StrToUpper(dualparametro[0]), "PUERTO"); + if (resul == 0) + strcpy(puerto, dualparametro[1]); + + resul = strcmp(StrToUpper(dualparametro[0]), "PATHINTERFACE"); + if (resul == 0) + strcpy(pathinterface, dualparametro[1]); + + resul = strcmp(StrToUpper(dualparametro[0]), "URLMENU"); + if (resul == 0) + strcpy(urlmenu, dualparametro[1]); + + resul = strcmp(StrToUpper(dualparametro[0]), "URLMSG"); + if (resul == 0) + strcpy(urlmsg, dualparametro[1]); + } + + if (servidoradm[0] == CHARNULL) { + liberaMemoria(buffer); + errorLog(modulo,4, FALSE); // Falta parámetro SERVIDORADM + return (FALSE); + } + + if (puerto[0] == CHARNULL) { + liberaMemoria(buffer); + errorLog(modulo,5, FALSE); // Falta parámetro PUERTO + return (FALSE); + } + if (pathinterface[0] == CHARNULL) { + liberaMemoria(buffer); + errorLog(modulo,56, FALSE); // Falta parámetro PATHINTERFACE + return (FALSE); + } + + if (urlmenu[0] == CHARNULL) { + liberaMemoria(buffer); + errorLog(modulo,89, FALSE); // Falta parámetro URLMENU + return (FALSE); + } + if (urlmsg[0] == CHARNULL) { + liberaMemoria(buffer); + errorLog(modulo,90, FALSE); // Falta parámetro URLMSG + return (FALSE); + } + liberaMemoria(buffer); + return (TRUE); +} +//______________________________________________________________________________________________________ +// Función: FinterfaceAdmin +// +// Descripción: +// Esta función es la puerta de comunicación entre el módulo de administración y el motor de clonación. +// La Aplicación de administración utiliza una interface para ejecutar funciones del motor de clonación; +// esta interface llamará a la API del motor con lo que cambiando el comportamiento de esta interface +// podremos hacer llamadas a otras API de clonación y de esta manera probar distintos motores. +// +// Parámetros: +// - script: Nombre del módulo,función o script de la interface +// - parametros: Parámetros que se le pasarán a la interface +// - salida: Recoge la salida que genera la llamada a la interface + +// Devuelve: +// Código de error de la ejecución al módulo , función o script de la interface +// +// Especificaciones: +// El parámetro salida recoge la salida desde un fichero que se genera en la ejecución del script siempre que +// sea distinto de NULL, esto es, si al llamar a la función este parámetro es NULL no se recogerá dicha salida. +// Este fichero tiene una ubicación fija: /tmp/_retinterface +//______________________________________________________________________________________________________ + +int FinterfaceAdmin( char *script,char* parametros,char* salida) +{ + FILE *f; + int lSize,nargs,i,resul; + char msglog[LONSTD],*argumentos[MAXARGS]; + char modulo[] = "FinterfaceAdmin()"; + + + if (ndebug>= DEBUG_MEDIO) { + sprintf(msglog, "%s:%s", tbMensajes[8], script); + infoDebug(msglog); + } + + /* Crea matriz de los argumentos */ + nargs=splitCadena(argumentos,parametros,32); + for(i=nargs;i= DEBUG_ALTO) { + sprintf(msglog, "%s: #%d-%s", tbMensajes[9],i+1,argumentos[i]); + infoDebug(msglog); + } + } + /* Elimina fichero de retorno */ + if(salida!=(char*)NULL){ + f = fopen("/tmp/_retinterface_","w" ); + if (f==NULL){ // Error de eliminación + scriptLog(modulo,10); + resul=8; + scriptLog(modulo,resul); + return(resul); + } + fclose(f); + } + /* Compone linea de comando */ + if(parametros){ + strcat(script," "); + strcat(script,parametros); + } + /* LLamada función interface */ + resul=system(script); + if(resul){ + scriptLog(modulo,10); + scriptLog(modulo,resul); + return(resul); + } + /* Lee fichero de retorno */ + if(salida!=(char*)NULL){ + f = fopen("/tmp/_retinterface_","rb" ); + if (f==NULL){ // Error de apertura + scriptLog(modulo,10); + resul=9; + scriptLog(modulo,resul); + return(resul); + } + else{ + fseek (f ,0,SEEK_END); // Obtiene tamaño del fichero. + lSize = ftell (f); + rewind (f); + if(lSize>LONGITUD_SCRIPTSALIDA){ + scriptLog(modulo,10); + resul=11; + scriptLog(modulo,resul); + return(resul); + } + fread (salida,1,lSize,f); // Lee contenido del fichero + rTrim(salida); + fclose(f); + } + } + /* Muestra información de retorno */ + if(salida!=(char*)NULL){ + if(ndebug>2){ + sprintf(msglog,"Información devuelta %s",salida); + infoDebug(msglog); + } + } + return(resul); +} +//______________________________________________________________________________________________________ +// Función: interfaceAdmin +// +// Descripción: +// Esta función es la puerta de comunicación entre el módulo de administración y el motor de clonación. +// La Aplicación de administración utiliza una interface para ejecutar funciones del motor de clonación; +// esta interface llamará a la API del motor con lo que cambiando el comportamiento de esta interface +// podremos hacer llamadas a otras API de clonación y de esta manera probar distintos motores. +// +// Parámetros: +// - script: Nombre del módulo,función o script de la interface +// - parametros: Parámetros que se le pasarán a la interface +// - salida: Recoge la salida que genera la llamada a la interface + +// Devuelve: +// Código de error de la ejecución al módulo , función o script de la interface +// +// Especificaciones: +// El parámetro salida recoge la salida desde el procedimiento hijo que se genera en la ejecución de éste +// siempre que sea distinto de NULL, esto es, si al llamar a la función este parámetro es NULL no se +// recogerá dicha salida. +//______________________________________________________________________________________________________ + +int interfaceAdmin( char *script,char* parametros,char* salida) +{ + int descr[2]; /* Descriptores de E y S de la turbería */ + int bytesleidos; /* Bytes leidos en el mensaje */ + int estado; + pid_t pid; + char buffer[LONBLK]; // Buffer de lectura de fichero + pipe (descr); + int i,nargs,resul; + int lon; // Longitud de cadena + char msglog[LONSUC]; // Mensaje de registro de sucesos + char *argumentos[MAXARGS]; + char modulo[] = "interfaceAdmin()"; + if (ndebug>= DEBUG_MEDIO) { + sprintf(msglog, "%s:%s", tbMensajes[8], script); + infoDebug(msglog); + } + + /* Crea matriz de los argumentos */ + nargs=splitCadena(argumentos,parametros,32); + for(i=nargs;i= DEBUG_ALTO) { + // Truncar la cadena si es mayor que el tamaño de la línea de log. + sprintf(msglog, "%s: #%d-", tbMensajes[9], i+1); + lon = strlen (msglog); + if (lon + strlen (argumentos[i]) < LONSUC) { + strcat (msglog, argumentos[i]); + } + else + { + strncat (msglog, argumentos[i], LONSUC - lon - 4); + strcat (msglog, "..."); + } + infoDebug(msglog); + } + } + + if((pid=fork())==0) + { + //_______________________________________________________________ + + /* Proceso hijo que ejecuta la función de interface */ + + close (descr[LEER]); + dup2 (descr[ESCRIBIR], 1); + close (descr[ESCRIBIR]); + resul=execv(script,argumentos); + //resul=execlp (script, script, argumentos[0],argumentos[1],NULL); + exit(resul); + + /* Fin de proceso hijo */ + //_______________________________________________________________ + } + else + { + //_______________________________________________________________ + + /* Proceso padre que espera la ejecución del hijo */ + + if (pid ==-1){ // Error en la creación del proceso hijo + scriptLog(modulo,10); + resul=13; + scriptLog(modulo,resul); + return(resul); + } + close (descr[ESCRIBIR]); + bytesleidos = read (descr[LEER], buffer, LONBLK-1); + while(bytesleidos>0){ + if(salida!=(char*)NULL){ // Si se solicita retorno de información... + buffer[bytesleidos]='\0'; + // Error si se supera el tamaño máximo de cadena de salida. + if(strlen(buffer)+strlen(salida)>LONGITUD_SCRIPTSALIDA){ + scriptLog(modulo,10); + resul=11; + scriptLog(modulo,resul); + return(resul); + } + rTrim(buffer); + strcat(salida,buffer); + } + bytesleidos = read (descr[LEER], buffer, LONBLK-1); + } + close (descr[LEER]); + //kill(pid,SIGQUIT); + waitpid(pid,&estado,0); + resul=WEXITSTATUS(estado); + if(resul){ + scriptLog(modulo,10); + scriptLog(modulo,resul); + return(resul); + } + /* Fin de proceso padre */ + //_______________________________________________________________ + } + + /* Muestra información de retorno */ + if(salida!=(char*)NULL){ + if(ndebug>2){ + // Truncar la cadena si es mayor que el tamaño de la línea de log. + strcpy(msglog,"Informacion devuelta "); + lon = strlen (msglog); + if (lon + strlen (salida) < LONSUC) { + strcat (msglog, salida); + } + else + { + strncat (msglog, salida, LONSUC-lon-4); + strcat (msglog, "..."); + } + infoDebug(msglog); + } + } + return(resul); +} +//______________________________________________________________________________________________________ +// Función: scriptLog +// +// Descripción: +// Registra los sucesos de errores de scripts en el fichero de log +// Parametros: +// - modulo: Módulo donde se produjo el error +// - coderr : Código del mensaje de error del script +//______________________________________________________________________________________________________ +void scriptLog(const char *modulo,int coderr) +{ + char msglog[LONSUC]; + + if(coderr>>>>>>>>>>>>>>>>>>>>>>>>> + char msglog[LONSTD]; + char modulo[] = "cuestionCache()"; + + sprintf(interface,"%s/%s",pathinterface,"procesaCache"); + sprintf(parametros,"%s %s","procesaCache",tam); + + herror=interfaceAdmin(interface,parametros,NULL); + if(herror){ + sprintf(msglog,"%s",tbErrores[88]); + errorInfo(modulo,msglog); + return(FALSE); + } + + return(TRUE); +} +//______________________________________________________________________________________________________ +// Función: cargaPaginaWeb +// +// Descripción: +// Muestra una pégina web usando el browser +// Parámetros: +// urp: Dirección url de la página +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +int cargaPaginaWeb(char *url) +{ + pid_t pidbrowser; // Identificador del proceso que se crea para mostrar una página web con el browser + int resul=0; + char* argumentos[4]; + char modulo[] = "cargaPaginaWeb()"; + + // Destruye los procesos del Browser y lanza uno nuevo. + system("pkill -9 browser"); + + sprintf(interface,"/opt/opengnsys/bin/browser"); + sprintf(parametros,"browser -qws %s",url); + + splitCadena(argumentos,parametros,' '); // Crea matriz de los argumentos del scripts + argumentos[3]=NULL; + if((pidbrowser=fork())==0){ + /* Proceso hijo que ejecuta el script */ + resul=execv(interface,argumentos); + exit(resul); + } + else { + if (pidbrowser ==-1){ + scriptLog(modulo,10); + resul=13; + scriptLog(modulo,resul); + return(resul); + } + } + return(resul); +} +//________________________________________________________________________________________________________ +// Función: muestraMenu +// +// Descripción: +// Muestra el menu inicial del cliente +// Parámetros: +// Ninguno +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//________________________________________________________________________________________________________ +void muestraMenu() +{ + cargaPaginaWeb(urlmenu); +} +//______________________________________________________________________________________________________ +// Función: muestraMensaje +// +// Descripción: +// Muestra un mensaje en pantalla +// Parámetros: +// - idx: Indice del mensaje +// - msg: Descripción Mensaje +// ________________________________________________________________________________________________________ +void muestraMensaje(int idx,char*msg) +{ + char *msgpan,url[250]; + + if(msg){ + msgpan=URLEncode(msg); + sprintf(url,"%s?msg=%s",urlmsg,msgpan); // Url de la página de mensajes + liberaMemoria(msgpan); + } + else + sprintf(url,"%s?idx=%d",urlmsg,idx); // Url de la página de mensajes + cargaPaginaWeb(url); +} +//______________________________________________________________________________________________________ +// Función: InclusionCliente +// Descripción: +// Abre una sesión en el servidor de administración y registra al cliente en el sistema +// Parámetros: +// Ninguno +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//______________________________________________________________________________________________________ +BOOLEAN inclusionCliente(TRAMA* ptrTrama) +{ + int lon; // Longitud de cadena + char msglog[LONSUC]; // Mensaje de registro de sucesos + char *cfg; // Datos de configuración + SOCKET socket_c; + char modulo[] = "inclusionCliente()"; + + cfg=LeeConfiguracion(); + + if(!cfg){ // No se puede recuperar la configuración del cliente + errorLog(modulo,36,FALSE); + errorLog(modulo,37,FALSE); + return(FALSE); + } + if (ndebug>= DEBUG_ALTO) { + // Truncar la cadena si es mayor que el tamaño de la línea de log. + sprintf(msglog, "%s", tbMensajes[14]); + lon = strlen (msglog); + if (lon + strlen (cfg) < LONSUC) { + strcat (msglog, cfg); + } + else + { + strncat (msglog, cfg, LONSUC - lon - 4); + strcat (msglog, "..."); + } + infoDebug(msglog); + } + initParametros(ptrTrama,0); + lon=sprintf(ptrTrama->parametros,"nfn=InclusionCliente\r"); // Nombre de la función a ejecutar en el servidor + lon+=sprintf(ptrTrama->parametros+lon,"cfg=%s\r",cfg); // Configuración de los Sistemas Operativos del cliente + liberaMemoria(cfg); + + if(!enviaMensajeServidor(&socket_c,ptrTrama,MSG_PETICION)){ + errorLog(modulo,37,FALSE); + return(FALSE); + } + ptrTrama=recibeMensaje(&socket_c); + if(!ptrTrama){ + errorLog(modulo,45,FALSE); + return(FALSE); + } + + close(socket_c); + + if(!gestionaTrama(ptrTrama)){ // Análisis de la trama + errorLog(modulo,39,FALSE); + return(FALSE); + } + + return(TRUE); +} +//______________________________________________________________________________________________________ +// Función: RESPUESTA_InclusionCliente +// +// Descripción: +// Respuesta del servidor de administración a la petición de inicio +// enviando los datos identificativos del cliente y otras configuraciones +// Parámetros: +// - ptrTrama: Trama recibida por el servidor con el contenido y los parámetros +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//______________________________________________________________________________________________________ +BOOLEAN RESPUESTA_InclusionCliente(TRAMA* ptrTrama) +{ + char* res; + char modulo[] = "RESPUESTA_InclusionCliente()"; + + res=copiaParametro("res",ptrTrama); // Resultado del proceso de inclusión + if(atoi(res)==0){ // Error en el proceso de inclusión + liberaMemoria(res); + errorLog(modulo,41,FALSE); + return (FALSE); + } + liberaMemoria(res); + + idordenador=copiaParametro("ido",ptrTrama); // Identificador del ordenador + nombreordenador=copiaParametro("npc",ptrTrama); // Nombre del ordenador + cache=copiaParametro("che",ptrTrama); // Tamaño de la caché reservada al cliente + idproautoexec=copiaParametro("exe",ptrTrama); // Procedimento de inicio (Autoexec) + idcentro=copiaParametro("idc",ptrTrama); // Identificador de la Unidad Organizativa + idaula=copiaParametro("ida",ptrTrama); // Identificador del aula + + if(idordenador==NULL || nombreordenador==NULL){ + errorLog(modulo,40,FALSE); + return (FALSE); + } + return(TRUE); +} +//______________________________________________________________________________________________________ +// +// Función: LeeConfiguracion +// Descripción: +// Abre una sesión en el servidor de administración y registra al cliente en el sistema +// Parámetros: +// Ninguno +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//______________________________________________________________________________________________________ + +char* LeeConfiguracion() +{ + char* parametroscfg; + char modulo[] = "LeeConfiguracion()"; + int herrorcfg; + + // Reservar memoria para los datos de cofiguración. + parametroscfg=(char*)reservaMemoria(LONGITUD_SCRIPTSALIDA); + if(!parametroscfg){ + errorLog(modulo,3,FALSE); + return(NULL); + } + // Ejecutar script y obtener datos. + sprintf(interface,"%s/%s",pathinterface,"getConfiguration"); + herrorcfg=interfaceAdmin(interface,NULL,parametroscfg); + + if(herrorcfg){ // No se puede recuperar la configuración del cliente + liberaMemoria(parametroscfg); + errorLog(modulo,36,FALSE); + return(NULL); + } + return(parametroscfg); +} +//________________________________________________________________________________________________________ +// Función: autoexecCliente +// +// Descripción: +// Solicita procedimiento de autoexec para el cliebnte +// Parámetros: +// Ninguno +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//________________________________________________________________________________________________________ +BOOLEAN autoexecCliente(TRAMA* ptrTrama) +{ + SOCKET socket_c; + char modulo[] = "autoexecCliente()"; + + initParametros(ptrTrama,0); + sprintf(ptrTrama->parametros,"nfn=AutoexecCliente\rexe=%s\r",idproautoexec); + + if(!enviaMensajeServidor(&socket_c,ptrTrama,MSG_PETICION)){ + errorLog(modulo,42,FALSE); + return(FALSE); + } + ptrTrama=recibeMensaje(&socket_c); + if(!ptrTrama){ + errorLog(modulo,45,FALSE); + return(FALSE); + } + + close(socket_c); + + if(!gestionaTrama(ptrTrama)){ // Análisis de la trama + errorLog(modulo,39,FALSE); + return(FALSE); + } + + return(TRUE); +} +//________________________________________________________________________________________________________ +// Función: autoexecCliente +// +// Descripción: +// Ejecuta un script de autoexec personalizado en todos los inicios para el cliente +// Parámetros: +// Ninguno +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//________________________________________________________________________________________________________ +BOOLEAN RESPUESTA_AutoexecCliente(TRAMA* ptrTrama) +{ + SOCKET socket_c; + char *res,*nfl; + char modulo[] = "RESPUESTA_AutoexecCliente()"; + + res=copiaParametro("res",ptrTrama); + if(atoi(res)==0){ // Error en el proceso de autoexec + liberaMemoria(res); + return (FALSE); + } + liberaMemoria(res); + + nfl=copiaParametro("nfl",ptrTrama); + initParametros(ptrTrama,0); + sprintf(ptrTrama->parametros,"nfn=enviaArchivo\rnfl=%s\r",nfl); + liberaMemoria(nfl); + + /* Envía petición */ + if(!enviaMensajeServidor(&socket_c,ptrTrama,MSG_PETICION)){ + errorLog(modulo,42,FALSE); + return(FALSE); + } + /* Nombre del archivo destino (local)*/ + char fileautoexec[LONPRM]; + sprintf(fileautoexec,"/tmp/_autoexec_%s",IPlocal); + + /* Recibe archivo */ + if(!recArchivo(&socket_c,fileautoexec)){ + errorLog(modulo,58, FALSE); + close(socket_c); + return(FALSE); + } + + close(socket_c); + + /* Ejecuta archivo */ + ejecutaArchivo(fileautoexec,ptrTrama); + return(TRUE); +} +//______________________________________________________________________________________________________ +// Función: comandosPendientes +// +// Descripción: +// Búsqueda de acciones pendientes en el servidor de administración +// Parámetros: +// Ninguno +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//______________________________________________________________________________________________________ +BOOLEAN comandosPendientes(TRAMA* ptrTrama) +{ + SOCKET socket_c; + char modulo[] = "comandosPendientes()"; + + CMDPTES=TRUE; + initParametros(ptrTrama,0); + + while(CMDPTES){ + sprintf(ptrTrama->parametros,"nfn=ComandosPendientes\r"); + if(!enviaMensajeServidor(&socket_c,ptrTrama,MSG_PETICION)){ + errorLog(modulo,42,FALSE); + return(FALSE); + } + ptrTrama=recibeMensaje(&socket_c); + if(!ptrTrama){ + errorLog(modulo,45,FALSE); + return(FALSE); + } + + close(socket_c); + + if(!gestionaTrama(ptrTrama)){ // Análisis de la trama + errorLog(modulo,39,FALSE); + return(FALSE); + } + } + return(TRUE); +} +//______________________________________________________________________________________________________ +// Función: NoComandosPtes +// +// Descripción: +// Conmuta el switch de los comandos pendientes y lo pone a false +// Parámetros: +// - ptrTrama: contenido del mensaje +// Devuelve: +// TRUE siempre +// Especificaciones: +// Cuando se ejecuta esta función se sale del bucle que recupera los comandos pendientes en el +// servidor y el cliente pasa a a estar disponible para recibir comandos desde el éste. +//______________________________________________________________________________________________________ +BOOLEAN NoComandosPtes(TRAMA* ptrTrama) +{ + CMDPTES=FALSE; // Corta el bucle de comandos pendientes + return(TRUE); +} +//______________________________________________________________________________________________________ +// Función: ProcesaComandos +// +// Descripción: +// Espera comando desde el Servidor de Administración para ejecutarlos +// Parámetros: +// Ninguno +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +void procesaComandos(TRAMA* ptrTrama) +{ + int lon; + SOCKET socket_c; + char modulo[] = "procesaComandos()"; + + initParametros(ptrTrama,0); + while(TRUE){ + lon=sprintf(ptrTrama->parametros,"nfn=DisponibilidadComandos\r"); + lon+=sprintf(ptrTrama->parametros+lon,"tpc=%s\r",CLIENTE_OPENGNSYS); // Activar disponibilidad + if(!enviaMensajeServidor(&socket_c,ptrTrama,MSG_INFORMACION)){ + errorLog(modulo,43,FALSE); + return; + } + infoLog(19); // Disponibilidad de cliente activada + ptrTrama=recibeMensaje(&socket_c); + if(!ptrTrama){ + errorLog(modulo,46,FALSE); + return; + } + + close(socket_c); + + if(!gestionaTrama(ptrTrama)){ // Análisis de la trama + errorLog(modulo,39,FALSE); + return; + } + if(!comandosPendientes(ptrTrama)){ + errorLog(modulo,42,FALSE); + } + } +} +//______________________________________________________________________________________________________ +// Función: Actualizar +// +// Descripción: +// Actualiza los datos de un ordenador como si volviera a solicitar la entrada +// en el sistema al servidor de administración +// Parámetros: +// ptrTrama: contenido del mensajede +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//______________________________________________________________________________________________________ +BOOLEAN Actualizar(TRAMA* ptrTrama) +{ + char msglog[LONSTD]; // Mensaje de log + char *cfg; // Cadena de datos de configuración + int lon; // Longitud de cadena + char modulo[] = "Actualizar()"; + + if (ndebug>=DEBUG_MAXIMO) { + sprintf(msglog, "%s:%s",tbMensajes[21],modulo); + infoDebug(msglog); + } + muestraMensaje(1,NULL); + if(!comandosPendientes(ptrTrama)){ + errorLog(modulo,84,FALSE); + return(FALSE); + } + + cfg=LeeConfiguracion(); + herror=0; + if(!cfg){ // No se puede recuperar la configuración del cliente + errorLog(modulo,36,FALSE); + herror=3; + } + // Envia Configuracion al servidor + initParametros(ptrTrama,0); + lon=sprintf(ptrTrama->parametros,"nfn=%s\r","RESPUESTA_Configurar"); + lon+=sprintf(ptrTrama->parametros+lon,"cfg=%s\r",cfg); // Configuración de los Sistemas Operativos del cliente + respuestaEjecucionComando(ptrTrama,herror,0); + + muestraMenu(); + + return(TRUE); +} +//______________________________________________________________________________________________________ +// Función: Purgar +// +// Descripción: +// Detiene la ejecución del browser +// Parámetros: +// ptrTrama: contenido del mensajede +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//______________________________________________________________________________________________________ +int Purgar(TRAMA* ptrTrama) +{ + + exit(EXIT_SUCCESS); +} +//______________________________________________________________________________________________________ +// Función: Sondeo +// +// Descripción: +// Envía al servidor una confirmación de que está dentro del sistema +// Parámetros: +// ptrTrama: contenido del mensajede +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//______________________________________________________________________________________________________ +BOOLEAN Sondeo(TRAMA* ptrTrama) +{ + return(TRUE); +} +//______________________________________________________________________________________________________ +// Función: ConsolaRemota +// +// Descripción: +// Ejecuta un comando de la Shell y envia el eco al servidor (Consola remota) +// Parámetros: +// ptrTrama: contenido del mensaje +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//______________________________________________________________________________________________________ +BOOLEAN ConsolaRemota(TRAMA* ptrTrama) +{ + SOCKET socket_c; + char *nfn,*scp,*aux,ecosrc[LONPRM],ecodst[LONPRM],msglog[LONSTD];; + char modulo[] = "ConsolaRemota()"; + + /* Nombre del archivo de script */ + char filescript[LONPRM]; + sprintf(filescript,"/tmp/_script_%s",IPlocal); + + aux=copiaParametro("scp",ptrTrama); + scp=URLDecode(aux); + escribeArchivo(filescript,scp); + liberaMemoria(aux); + liberaMemoria(scp); + + nfn=copiaParametro("nfn",ptrTrama); + sprintf(interface,"%s/%s",pathinterface,nfn); + sprintf(ecosrc,"/tmp/_econsola_%s",IPlocal); + sprintf(parametros,"%s %s %s",nfn,filescript,ecosrc); + herror=interfaceAdmin(interface,parametros,NULL); + if(herror){ + sprintf(msglog,"%s:%s",tbErrores[86],nfn); + errorInfo(modulo,msglog); + } + else{ + /* Envía fichero de inventario al servidor */ + sprintf(ecodst,"/tmp/_Seconsola_%s",IPlocal); // Nombre que tendra el archivo en el Servidor + initParametros(ptrTrama,0); + sprintf(ptrTrama->parametros,"nfn=recibeArchivo\rnfl=%s\r",ecodst); + if(!enviaMensajeServidor(&socket_c,ptrTrama,MSG_COMANDO)){ + errorLog(modulo,42,FALSE); + return(FALSE); + } + /* Espera señal para comenzar el envío */ + liberaMemoria(ptrTrama); + recibeFlag(&socket_c,ptrTrama); + /* Envía archivo */ + if(!sendArchivo(&socket_c,ecosrc)){ + errorLog(modulo,57, FALSE); + herror=12; // Error de envío de fichero por la red + } + close(socket_c); + } + liberaMemoria(nfn); + return(TRUE); +} +//_____________________________________________________________________________________________________ +// Función: Comando +// +// Descripción: +// COmando personalizado enviado desde el servidor +// Parámetros: +// ptrTrama: contenido del mensaje +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//_____________________________________________________________________________________________________ +BOOLEAN Comando(TRAMA* ptrTrama) +{ + char *ids,*nfn,msglog[LONSTD]; + char modulo[] = "Comando()"; + + if (ndebug>=DEBUG_MAXIMO) { + sprintf(msglog, "%s:%s",tbMensajes[21],modulo); + infoDebug(msglog); + } + nfn=copiaParametro("nfn",ptrTrama); + ids=copiaParametro("ids",ptrTrama); + + sprintf(interface,"%s/%s",pathinterface,nfn); + herror=interfaceAdmin(interface,NULL,NULL); + if(herror){ + sprintf(msglog,"%s:%s",tbErrores[86],nfn); + errorInfo(modulo,msglog); + } + /* Envia respuesta de ejecucución del comando */ + initParametros(ptrTrama,0); + sprintf(ptrTrama->parametros,"nfn=RESPUESTA_%s\r",nfn); + respuestaEjecucionComando(ptrTrama,herror,ids); + liberaMemoria(nfn); + liberaMemoria(ids); + return(TRUE); +} +//_____________________________________________________________________________________________________ +// Función: Arrancar +// +// Descripción: +// Responde a un comando de encendido por la red +// Parámetros: +// ptrTrama: contenido del mensaje +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//_____________________________________________________________________________________________________ +BOOLEAN Arrancar(TRAMA* ptrTrama) +{ + int lon; + char *ids,msglog[LONSTD]; + char modulo[] = "Arrancar()"; + + if (ndebug>=DEBUG_MAXIMO) { + sprintf(msglog, "%s:%s",tbMensajes[21],modulo); + infoDebug(msglog); + } + + ids=copiaParametro("ids",ptrTrama); + + /* Envia respuesta de ejecucución del script */ + initParametros(ptrTrama,0); + lon=sprintf(ptrTrama->parametros,"nfn=%s\r","RESPUESTA_Arrancar"); + lon+=sprintf(ptrTrama->parametros+lon,"tpc=%s\r",CLIENTE_OPENGNSYS); + respuestaEjecucionComando(ptrTrama,0,ids); + liberaMemoria(ids); + return(TRUE); +} +//_____________________________________________________________________________________________________ +// Función: Apagar +// +// Descripción: +// Apaga el cliente +// Parámetros: +// ptrTrama: contenido del mensaje +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//_____________________________________________________________________________________________________ +BOOLEAN Apagar(TRAMA* ptrTrama) +{ + char *ids,*nfn,msglog[LONSTD]; + char modulo[] = "Apagar()"; + + if (ndebug>=DEBUG_MAXIMO) { + sprintf(msglog, "%s:%s",tbMensajes[21],modulo); + infoDebug(msglog); + } + nfn=copiaParametro("nfn",ptrTrama); + ids=copiaParametro("ids",ptrTrama); + + initParametros(ptrTrama,0); + sprintf(ptrTrama->parametros,"nfn=%s\r","RESPUESTA_Apagar"); + respuestaEjecucionComando(ptrTrama,0,ids); + + sprintf(interface,"%s/%s",pathinterface,nfn); + herror=interfaceAdmin(interface,NULL,NULL); + if(herror){ + sprintf(msglog,"%s:%s",tbErrores[86],nfn); + liberaMemoria(nfn); + liberaMemoria(ids); + errorInfo(modulo,msglog); + return(FALSE); + } + liberaMemoria(nfn); + liberaMemoria(ids); + return(TRUE); +} +//_____________________________________________________________________________________________________ +// Función: Reiniciar +// +// Descripción: +// Apaga el cliente +// Parámetros: +// ptrTrama: contenido del mensaje +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//_____________________________________________________________________________________________________ +BOOLEAN Reiniciar(TRAMA* ptrTrama) +{ + char *nfn,*ids,msglog[LONSTD]; + char modulo[] = "Reiniciar()"; + + if (ndebug>=DEBUG_MAXIMO) { + sprintf(msglog, "%s:%s",tbMensajes[21],modulo); + infoDebug(msglog); + } + nfn=copiaParametro("nfn",ptrTrama); + ids=copiaParametro("ids",ptrTrama); + + initParametros(ptrTrama,0); + sprintf(ptrTrama->parametros,"nfn=%s\r","RESPUESTA_Reiniciar"); + respuestaEjecucionComando(ptrTrama,0,ids); + + sprintf(interface,"%s/%s",pathinterface,nfn); + herror=interfaceAdmin(interface,NULL,NULL); + if(herror){ + sprintf(msglog,"%s:%s",tbErrores[86],nfn); + liberaMemoria(nfn); + liberaMemoria(ids); + errorInfo(modulo,msglog); + return(FALSE); + } + liberaMemoria(nfn); + liberaMemoria(ids); + return(TRUE); +} +//_____________________________________________________________________________________________________ +// Función: IniciarSesion +// +// Descripción: +// Inicia sesión en el Sistema Operativo de una de las particiones +// Parámetros: +// ptrTrama: contenido del mensaje +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//_____________________________________________________________________________________________________ +BOOLEAN IniciarSesion(TRAMA* ptrTrama) +{ + char *nfn,*ids,*disk,*par,msglog[LONSTD]; + char modulo[] = "IniciarSesion()"; + + if (ndebug>=DEBUG_MAXIMO) { + sprintf(msglog, "%s:%s",tbMensajes[21],modulo); + infoDebug(msglog); + } + nfn=copiaParametro("nfn",ptrTrama); + ids=copiaParametro("ids",ptrTrama); + disk=copiaParametro("dsk",ptrTrama); + par=copiaParametro("par",ptrTrama); + + initParametros(ptrTrama,0); + sprintf(ptrTrama->parametros,"nfn=%s\r","RESPUESTA_IniciarSesion"); + respuestaEjecucionComando(ptrTrama,0,ids); + liberaMemoria(ids); + + sprintf(interface,"%s/%s",pathinterface,nfn); + sprintf(parametros,"%s %s %s",nfn,disk,par); + liberaMemoria(par); + + herror=interfaceAdmin(interface,parametros,NULL); + + if(herror){ + sprintf(msglog,"%s:%s",tbErrores[86],nfn); + liberaMemoria(nfn); + errorInfo(modulo,msglog); + return(FALSE); + } + liberaMemoria(nfn); + return(TRUE); +} +//______________________________________________________________________________________________________ +// Función: CrearImagen +// +// Descripción: +// Crea una imagen de una partición +// Parámetros: +// ptrTrama: contenido del mensaje +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//______________________________________________________________________________________________________ +BOOLEAN CrearImagen(TRAMA* ptrTrama) +{ + int lon; + char *nfn,*dsk,*par,*cpt,*idi,*ipr,*nci,*ids,msglog[LONSTD]; + char modulo[] = "CrearImagen()"; + + if (ndebug>=DEBUG_MAXIMO) { + sprintf(msglog, "%s:%s",tbMensajes[21],modulo); + infoDebug(msglog); + } + + dsk=copiaParametro("dsk",ptrTrama); // Disco + par=copiaParametro("par",ptrTrama); // Número de partición + cpt=copiaParametro("cpt",ptrTrama); // Código de la partición + idi=copiaParametro("idi",ptrTrama); // Identificador de la imagen + nci=copiaParametro("nci",ptrTrama); // Nombre canónico de la imagen + ipr=copiaParametro("ipr",ptrTrama); // Ip del repositorio + + nfn=copiaParametro("nfn",ptrTrama); + ids=copiaParametro("ids",ptrTrama); + muestraMensaje(7,NULL); + + if(InventariandoSoftware(ptrTrama,FALSE,"InventarioSoftware")){ // Crea inventario Software previamente + muestraMensaje(2,NULL); + sprintf(interface,"%s/%s",pathinterface,nfn); + sprintf(parametros,"%s %s %s %s %s",nfn,dsk,par,nci,ipr); + herror=interfaceAdmin(interface,parametros,NULL); + if(herror){ + sprintf(msglog,"%s:%s",tbErrores[86],nfn); + errorInfo(modulo,msglog); + muestraMensaje(10,NULL); + } + else + muestraMensaje(9,NULL); + } + else{ + sprintf(msglog,"%s:%s",tbErrores[86],nfn); + errorInfo(modulo,msglog); + } + + /* Envia respuesta de ejecución de la función de interface */ + initParametros(ptrTrama,0); + lon=sprintf(ptrTrama->parametros,"nfn=%s\r","RESPUESTA_CrearImagen"); + lon+=sprintf(ptrTrama->parametros+lon,"idi=%s\r",idi); // Identificador de la imagen + lon+=sprintf(ptrTrama->parametros+lon,"dsk=%s\r",dsk); // Número de disco + lon+=sprintf(ptrTrama->parametros+lon,"par=%s\r",par); // Número de partición de donde se creó + lon+=sprintf(ptrTrama->parametros+lon,"cpt=%s\r",cpt); // Tipo o código de partición + lon+=sprintf(ptrTrama->parametros+lon,"ipr=%s\r",ipr); // Ip del repositorio donde se alojó + respuestaEjecucionComando(ptrTrama,herror,ids); + + liberaMemoria(dsk); + liberaMemoria(par); + liberaMemoria(cpt); + liberaMemoria(idi); + liberaMemoria(nci); + liberaMemoria(ipr); + liberaMemoria(nfn); + liberaMemoria(ids); + + muestraMenu(); + + return(TRUE); +} +//______________________________________________________________________________________________________ +// Función: CrearImagenBasica +// +// Descripción: +// Crea una imagen básica a travers dela sincronización +// Parámetros: +// ptrTrama: contenido del mensaje +// +// FDevuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//______________________________________________________________________________________________________ +BOOLEAN CrearImagenBasica(TRAMA* ptrTrama) +{ + int lon; + char *nfn,*dsk,*par,*cpt,*idi,*nci,*rti,*ipr,*msy,*whl,*eli,*cmp,*bpi,*cpc,*bpc,*nba,*ids,msglog[LONSTD]; + char modulo[] = "CrearImagenBasica()"; + + if (ndebug>=DEBUG_MAXIMO) { + sprintf(msglog, "%s:%s",tbMensajes[21],modulo); + infoDebug(msglog); + } + nfn=copiaParametro("nfn",ptrTrama); + dsk=copiaParametro("dsk",ptrTrama); // Disco + par=copiaParametro("par",ptrTrama); // Número de partición + cpt=copiaParametro("cpt",ptrTrama); // Tipo de partición + idi=copiaParametro("idi",ptrTrama); // Identificador de la imagen + nci=copiaParametro("nci",ptrTrama); // Nombre canónico de la imagen + rti=copiaParametro("rti",ptrTrama); // Ruta de origen de la imagen + ipr=copiaParametro("ipr",ptrTrama); // Ip del repositorio + + msy=copiaParametro("msy",ptrTrama); // Método de sincronización + + whl=copiaParametro("whl",ptrTrama); // Envío del fichero completo si hay diferencias + eli=copiaParametro("eli",ptrTrama); // Elimiar archivos en destino que no estén en origen + cmp=copiaParametro("cmp",ptrTrama); // Comprimir antes de enviar + + bpi=copiaParametro("bpi",ptrTrama); // Borrar la imagen antes de crearla + cpc=copiaParametro("cpc",ptrTrama); // Copiar también imagen a la cache + bpc=copiaParametro("bpc",ptrTrama); // Borrarla de la cache antes de copiarla en ella + nba=copiaParametro("nba",ptrTrama); // No borrar archivos en destino + + muestraMensaje(7,NULL); // Creando Inventario Software + if(InventariandoSoftware(ptrTrama,FALSE,"InventarioSoftware")){ // Crea inventario Software previamente + muestraMensaje(30,NULL);// Creando Imagen Básica, por favor espere... + sprintf(interface,"%s/%s",pathinterface,nfn); + sprintf(parametros,"%s %s %s %s %s %s%s%s %s%s%s%s %s %s",nfn,dsk,par,nci,ipr,whl,eli,cmp,bpi,cpc,bpc,nba,msy,rti); + herror=interfaceAdmin(interface,parametros,NULL); + if(herror){ + sprintf(msglog,"%s:%s",tbErrores[86],nfn); + errorInfo(modulo,msglog); + muestraMensaje(29,NULL);// Ha habido algún error en el proceso de creación de imagen básica + } + else + muestraMensaje(28,NULL);// El proceso de creación de imagen básica ha terminado correctamente + } + else{ + sprintf(msglog,"%s:%s",tbErrores[86],nfn); + errorInfo(modulo,msglog); + } + + ids=copiaParametro("ids",ptrTrama); // Identificador de la sesión + + /* Envia respuesta de ejecución de la función de interface */ + initParametros(ptrTrama,0); + lon=sprintf(ptrTrama->parametros,"nfn=%s\r","RESPUESTA_CrearImagenBasica"); + lon+=sprintf(ptrTrama->parametros+lon,"idi=%s\r",idi); // Identificador de la imagen + lon+=sprintf(ptrTrama->parametros+lon,"dsk=%s\r",dsk); // Número de disco + lon+=sprintf(ptrTrama->parametros+lon,"par=%s\r",par); // Número de partición de donde se creó + lon+=sprintf(ptrTrama->parametros+lon,"cpt=%s\r",cpt); // Tipo o código de partición + lon+=sprintf(ptrTrama->parametros+lon,"ipr=%s\r",ipr); // Ip del repositorio donde se alojó + respuestaEjecucionComando(ptrTrama,herror,ids); + + liberaMemoria(nfn); + liberaMemoria(dsk); + liberaMemoria(par); + liberaMemoria(cpt); + liberaMemoria(idi); + liberaMemoria(nci); + liberaMemoria(rti); + liberaMemoria(ipr); + + liberaMemoria(msy); + + liberaMemoria(whl); + liberaMemoria(eli); + liberaMemoria(cmp); + + liberaMemoria(bpi); + liberaMemoria(cpc); + liberaMemoria(bpc); + liberaMemoria(nba); + liberaMemoria(ids); + + muestraMenu(); + + return(TRUE); +} +//______________________________________________________________________________________________________ +// Función: CrearSoftIncremental +// +// Descripción: +// Crea una software incremental comparando una partición con una imagen básica +// Parámetros: +// ptrTrama: contenido del mensaje +// +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//______________________________________________________________________________________________________ +BOOLEAN CrearSoftIncremental(TRAMA* ptrTrama) +{ + int lon; + char *nfn,*dsk,*par,*idi,*idf,*ipr,*nci,*rti,*ncf,*msy,*whl,*eli,*cmp,*bpi,*cpc,*bpc,*nba,*ids,msglog[LONSTD]; + char modulo[] = "CrearSoftIncremental()"; + + if (ndebug>=DEBUG_MAXIMO) { + sprintf(msglog, "%s:%s",tbMensajes[21],modulo); + infoDebug(msglog); + } + nfn=copiaParametro("nfn",ptrTrama); + + dsk=copiaParametro("dsk",ptrTrama); // Disco + par=copiaParametro("par",ptrTrama); // Número de partición + idi=copiaParametro("idi",ptrTrama); // Identificador de la imagen + nci=copiaParametro("nci",ptrTrama); // Nombre canónico de la imagen + rti=copiaParametro("rti",ptrTrama); // Ruta de origen de la imagen + ipr=copiaParametro("ipr",ptrTrama); // Ip del repositorio + idf=copiaParametro("idf",ptrTrama); // Identificador de la imagen diferencial + ncf=copiaParametro("ncf",ptrTrama); // Nombre canónico de la imagen diferencial + + msy=copiaParametro("msy",ptrTrama); // Método de sincronización + + whl=copiaParametro("whl",ptrTrama); // Envío del fichero completo si hay diferencias + eli=copiaParametro("eli",ptrTrama); // Elimiar archivos en destino que no estén en origen + cmp=copiaParametro("cmp",ptrTrama); // Comprimir antes de enviar + + bpi=copiaParametro("bpi",ptrTrama); // Borrar la imagen antes de crearla + cpc=copiaParametro("cpc",ptrTrama); // Copiar también imagen a la cache + bpc=copiaParametro("bpc",ptrTrama); // Borrarla de la cache antes de copiarla en ella + nba=copiaParametro("nba",ptrTrama); // No borrar archivos en destino + + muestraMensaje(7,NULL); // Creando Inventario Software + if(InventariandoSoftware(ptrTrama,FALSE,"InventarioSoftware")){ // Crea inventario Software previamente + muestraMensaje(25,NULL);// Creando Imagen Incremental, por favor espere... + sprintf(interface,"%s/%s",pathinterface,nfn); + sprintf(parametros,"%s %s %s %s %s %s %s%s%s %s%s%s%s %s %s",nfn,dsk,par,nci,ipr,ncf,whl,eli,cmp,bpi,cpc,bpc,nba,msy,rti); + + herror=interfaceAdmin(interface,parametros,NULL); + if(herror){ + sprintf(msglog,"%s:%s",tbErrores[86],nfn); + errorInfo(modulo,msglog); + muestraMensaje(27,NULL);// Ha habido algún error en el proceso de creación de imagen básica + } + else + muestraMensaje(26,NULL);// El proceso de creación de imagen incremental ha terminado correctamente + } + else{ + sprintf(msglog,"%s:%s",tbErrores[86],nfn); + errorInfo(modulo,msglog); + } + + ids=copiaParametro("ids",ptrTrama); // Identificador de la sesión + + /* Envia respuesta de ejecución de la función de interface */ + initParametros(ptrTrama,0); + lon=sprintf(ptrTrama->parametros,"nfn=%s\r","RESPUESTA_CrearSoftIncremental"); + lon+=sprintf(ptrTrama->parametros+lon,"idf=%s\r",idf); // Identificador de la imagen incremental + lon+=sprintf(ptrTrama->parametros+lon,"dsk=%s\r",dsk); // Número de disco + lon+=sprintf(ptrTrama->parametros+lon,"par=%s\r",par); // Número de partición + respuestaEjecucionComando(ptrTrama,herror,ids); + + liberaMemoria(nfn); + liberaMemoria(dsk); + liberaMemoria(par); + liberaMemoria(idi); + liberaMemoria(nci); + liberaMemoria(rti); + liberaMemoria(ipr); + liberaMemoria(idf); + liberaMemoria(ncf); + liberaMemoria(msy); + liberaMemoria(whl); + liberaMemoria(eli); + liberaMemoria(cmp); + liberaMemoria(bpi); + liberaMemoria(cpc); + liberaMemoria(bpc); + liberaMemoria(nba); + liberaMemoria(ids); + + muestraMenu(); + + return(TRUE); +} +//______________________________________________________________________________________________________ +// Función: RestaurarImagen +// +// Descripción: +// Restaura una imagen en una partición +// Parámetros: +// ptrTrama: contenido del mensaje +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En bpccaso de ocurrir algún error +//______________________________________________________________________________________________________ +BOOLEAN RestaurarImagen(TRAMA* ptrTrama) +{ + int lon; + char *nfn,*dsk,*par,*idi,*ipr,*ifs,*cfg,*nci,*ids,*ptc,msglog[LONSTD]; + char modulo[] = "RestaurarImagen()"; + + if (ndebug>=DEBUG_MAXIMO) { + sprintf(msglog, "%s:%s",tbMensajes[21],modulo); + infoDebug(msglog); + } + + dsk=copiaParametro("dsk",ptrTrama); + par=copiaParametro("par",ptrTrama); + idi=copiaParametro("idi",ptrTrama); + ipr=copiaParametro("ipr",ptrTrama); + nci=copiaParametro("nci",ptrTrama); + ifs=copiaParametro("ifs",ptrTrama); + ptc=copiaParametro("ptc",ptrTrama); + + nfn=copiaParametro("nfn",ptrTrama); + ids=copiaParametro("ids",ptrTrama); + muestraMensaje(3,NULL); + sprintf(interface,"%s/%s",pathinterface,nfn); + sprintf(parametros,"%s %s %s %s %s %s",nfn,dsk,par,nci,ipr,ptc); + herror=interfaceAdmin(interface,parametros,NULL); + if(herror){ + sprintf(msglog,"%s:%s",tbErrores[86],nfn); + errorInfo(modulo,msglog); + muestraMensaje(12,NULL); + } + else + muestraMensaje(11,NULL); + + /* Obtener nueva configuración */ + cfg=LeeConfiguracion(); + if(!cfg){ // No se puede recuperar la configuración del cliente + errorLog(modulo,36,FALSE); + } + + /* Envia respuesta de ejecución de la función de interface */ + initParametros(ptrTrama,0); + lon=sprintf(ptrTrama->parametros,"nfn=%s\r","RESPUESTA_RestaurarImagen"); + lon+=sprintf(ptrTrama->parametros+lon,"idi=%s\r",idi); // Identificador de la imagen + lon+=sprintf(ptrTrama->parametros+lon,"dsk=%s\r",dsk); // Número de disco + lon+=sprintf(ptrTrama->parametros+lon,"par=%s\r",par); // Número de partición + lon+=sprintf(ptrTrama->parametros+lon,"ifs=%s\r",ifs); // Identificador del perfil software + lon+=sprintf(ptrTrama->parametros+lon,"cfg=%s\r",cfg); // Configuración de discos + respuestaEjecucionComando(ptrTrama,herror,ids); + + liberaMemoria(nfn); + liberaMemoria(dsk); + liberaMemoria(par); + liberaMemoria(idi); + liberaMemoria(nci); + liberaMemoria(ipr); + liberaMemoria(ifs); + liberaMemoria(cfg); + liberaMemoria(ptc); + liberaMemoria(ids); + + muestraMenu(); + + return(TRUE); +} +//______________________________________________________________________________________________________ +// Función: RestaurarImagenBasica +// +// Descripción: +// Restaura una imagen básica en una partición +// Parámetros: +// ptrTrama: contenido del mensaje +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//______________________________________________________________________________________________________ +BOOLEAN RestaurarImagenBasica(TRAMA* ptrTrama) +{ + int lon; + char *nfn,*dsk,*par,*idi,*ipr,*met,*nci,*rti,*ifs,*cfg,*msy,*whl,*eli,*cmp,*tpt,*bpi,*cpc,*bpc,*nba,*ids,msglog[LONSTD]; + char modulo[] = "RestaurarImagenBasica()"; + + if (ndebug>=DEBUG_MAXIMO) { + sprintf(msglog, "%s:%s",tbMensajes[21],modulo); + infoDebug(msglog); + } + dsk=copiaParametro("dsk",ptrTrama); + par=copiaParametro("par",ptrTrama); + idi=copiaParametro("idi",ptrTrama); + ipr=copiaParametro("ipr",ptrTrama); + met=copiaParametro("met",ptrTrama); // Método de clonación 0= desde caché 1= desde repositorio + nci=copiaParametro("nci",ptrTrama); + rti=copiaParametro("rti",ptrTrama); // Ruta de origen de la imagen + ifs=copiaParametro("ifs",ptrTrama); + + tpt=copiaParametro("tpt",ptrTrama); // Tipo de trasnmisión unicast o multicast + msy=copiaParametro("msy",ptrTrama); // Metodo de sincronizacion + + whl=copiaParametro("whl",ptrTrama); // Envío del fichero completo si hay diferencias + eli=copiaParametro("eli",ptrTrama); // Elimiar archivos en destino que no estén en origen + cmp=copiaParametro("cmp",ptrTrama); // Comprimir antes de enviar + + bpi=copiaParametro("bpi",ptrTrama); // Borrar la imagen antes de crearla + cpc=copiaParametro("cpc",ptrTrama); // Copiar también imagen a la cache + bpc=copiaParametro("bpc",ptrTrama); // Borrarla de la cache antes de copiarla en ella + nba=copiaParametro("nba",ptrTrama); // No borrar archivos en destino + + nfn=copiaParametro("nfn",ptrTrama); + ids=copiaParametro("ids",ptrTrama); + muestraMensaje(31,NULL); + sprintf(interface,"%s/%s",pathinterface,nfn); + sprintf(parametros,"%s %s %s %s %s %s %s%s%s %s%s%s%s %s %s %s",nfn,dsk,par,nci,ipr,tpt,whl,eli,cmp,bpi,cpc,bpc,nba,met,msy,rti); + herror=interfaceAdmin(interface,parametros,NULL); + if(herror){ + sprintf(msglog,"%s:%s",tbErrores[86],nfn); + errorInfo(modulo,msglog); + muestraMensaje(33,NULL); + } + else + muestraMensaje(32,NULL); + + /* Obtener nueva configuración */ + cfg=LeeConfiguracion(); + if(!cfg){ // No se puede recuperar la configuración del cliente + errorLog(modulo,36,FALSE); + } + + /* Envia respuesta de ejecución de la función de interface */ + initParametros(ptrTrama,0); + lon=sprintf(ptrTrama->parametros,"nfn=%s\r","RESPUESTA_RestaurarImagenBasica"); + lon+=sprintf(ptrTrama->parametros+lon,"idi=%s\r",idi); // Identificador de la imagen + lon+=sprintf(ptrTrama->parametros+lon,"dsk=%s\r",dsk); // Número de disco + lon+=sprintf(ptrTrama->parametros+lon,"par=%s\r",par); // Número de partición + lon+=sprintf(ptrTrama->parametros+lon,"ifs=%s\r",ifs); // Identificador del perfil software + lon+=sprintf(ptrTrama->parametros+lon,"cfg=%s\r",cfg); // Configuración de discos + respuestaEjecucionComando(ptrTrama,herror,ids); + + liberaMemoria(nfn); + liberaMemoria(dsk); + liberaMemoria(par); + liberaMemoria(idi); + liberaMemoria(nci); + liberaMemoria(rti); + liberaMemoria(ifs); + liberaMemoria(cfg); + liberaMemoria(ipr); + liberaMemoria(met); + + liberaMemoria(tpt); + liberaMemoria(msy); + + liberaMemoria(whl); + liberaMemoria(eli); + liberaMemoria(cmp); + + liberaMemoria(bpi); + liberaMemoria(cpc); + liberaMemoria(bpc); + liberaMemoria(nba); + liberaMemoria(ids); + + muestraMenu(); + + return(TRUE); +} +//______________________________________________________________________________________________________ +// Función: RestaurarSoftIncremental +// +// Descripción: +// Restaura software incremental en una partición +// Parámetros: +// ptrTrama: contenido del mensaje +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//______________________________________________________________________________________________________ +BOOLEAN RestaurarSoftIncremental(TRAMA* ptrTrama) +{ + int lon; + char *nfn,*dsk,*par,*idi,*ipr,*met,*ifs,*nci,*rti,*idf,*ncf,*msy,*whl,*eli,*cmp,*tpt,*bpi,*cpc,*bpc,*nba,*ids,msglog[LONSTD]; + char modulo[] = "RestaurarSoftIncremental()"; + + if (ndebug>=DEBUG_MAXIMO) { + sprintf(msglog, "%s:%s",tbMensajes[21],modulo); + infoDebug(msglog); + } + dsk=copiaParametro("dsk",ptrTrama); + par=copiaParametro("par",ptrTrama); + idi=copiaParametro("idi",ptrTrama); + idf=copiaParametro("idf",ptrTrama); + ipr=copiaParametro("ipr",ptrTrama); + met=copiaParametro("met",ptrTrama); // Método de clonación 0= desde caché 1= desde repositorio + ifs=copiaParametro("ifs",ptrTrama); + nci=copiaParametro("nci",ptrTrama); + rti=copiaParametro("rti",ptrTrama); // Ruta de origen de la imagen + ncf=copiaParametro("ncf",ptrTrama); + + tpt=copiaParametro("tpt",ptrTrama); // Tipo de trasnmisión unicast o multicast + msy=copiaParametro("msy",ptrTrama); // Metodo de sincronizacion + + whl=copiaParametro("whl",ptrTrama); // Envío del fichero completo si hay diferencias + eli=copiaParametro("eli",ptrTrama); // Elimiar archivos en destino que no estén en origen + cmp=copiaParametro("cmp",ptrTrama); // Comprimir antes de enviar + + bpi=copiaParametro("bpi",ptrTrama); // Borrar la imagen antes de crearla + cpc=copiaParametro("cpc",ptrTrama); // Copiar también imagen a la cache + bpc=copiaParametro("bpc",ptrTrama); // Borrarla de la cache antes de copiarla en ella + nba=copiaParametro("nba",ptrTrama); // No borrar archivos en destino + + nfn=copiaParametro("nfn",ptrTrama); + ids=copiaParametro("ids",ptrTrama); + muestraMensaje(31,NULL); + sprintf(interface,"%s/%s",pathinterface,nfn); + sprintf(parametros,"%s %s %s %s %s %s %s %s%s%s %s%s%s%s %s %s %s",nfn,dsk,par,nci,ipr,ncf,tpt,whl,eli,cmp,bpi,cpc,bpc,nba,met,msy,rti); + herror=interfaceAdmin(interface,parametros,NULL); + if(herror){ + sprintf(msglog,"%s:%s",tbErrores[86],nfn); + errorInfo(modulo,msglog); + muestraMensaje(35,NULL); + } + else + muestraMensaje(34,NULL); + + /* Envia respuesta de ejecución de la función de interface */ + initParametros(ptrTrama,0); + lon=sprintf(ptrTrama->parametros,"nfn=%s\r","RESPUESTA_RestaurarSoftIncremental"); + lon+=sprintf(ptrTrama->parametros+lon,"idi=%s\r",idf); // Identificador de la imagen incremental (Forzada a idi) + lon+=sprintf(ptrTrama->parametros+lon,"dsk=%s\r",dsk); // Número de disco + lon+=sprintf(ptrTrama->parametros+lon,"par=%s\r",par); // Número de partición + lon+=sprintf(ptrTrama->parametros+lon,"ifs=%s\r",ifs); // Identificador del perfil software + + respuestaEjecucionComando(ptrTrama,herror,ids); + + liberaMemoria(nfn); + liberaMemoria(dsk); + liberaMemoria(par); + liberaMemoria(idi); + liberaMemoria(idf); + liberaMemoria(nci); + liberaMemoria(rti); + liberaMemoria(ncf); + liberaMemoria(ifs); + liberaMemoria(ipr); + liberaMemoria(met); + + liberaMemoria(tpt); + liberaMemoria(msy); + + liberaMemoria(whl); + liberaMemoria(eli); + liberaMemoria(cmp); + + liberaMemoria(bpi); + liberaMemoria(cpc); + liberaMemoria(bpc); + liberaMemoria(nba); + liberaMemoria(ids); + + muestraMenu(); + + return(TRUE); +} +//______________________________________________________________________________________________________ +// Función: Configurar +// +// Descripción: +// Configura la tabla de particiones y formatea +// Parámetros: +// ptrTrama: contenido del mensaje +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//______________________________________________________________________________________________________ +BOOLEAN Configurar(TRAMA* ptrTrama) +{ + int lon; + char *nfn,*dsk,*cfg,*ids,msglog[LONSTD]; + char modulo[] = "Configurar()"; + + if (ndebug>=DEBUG_MAXIMO) { + sprintf(msglog, "%s:%s",tbMensajes[21],modulo); + infoDebug(msglog); + } + + dsk=copiaParametro("dsk",ptrTrama); + cfg=copiaParametro("cfg",ptrTrama); + /* Sustituir caracteres */ + sustituir(cfg,'\n','$'); + sustituir(cfg,'\t','#'); + + nfn=copiaParametro("nfn",ptrTrama); + ids=copiaParametro("ids",ptrTrama); + muestraMensaje(4,NULL); + sprintf(interface,"%s/%s",pathinterface,nfn); + sprintf(parametros,"%s %s %s",nfn,dsk,cfg); + + herror=interfaceAdmin(interface,parametros,NULL); + if(herror){ + sprintf(msglog,"%s:%s",tbErrores[86],nfn); + errorInfo(modulo,msglog); + muestraMensaje(13,NULL); + } + else + muestraMensaje(14,NULL); + + cfg=LeeConfiguracion(); + if(!cfg){ // No se puede recuperar la configuración del cliente + errorLog(modulo,36,FALSE); + return(FALSE); + } + + /* Envia respuesta de ejecución del comando*/ + initParametros(ptrTrama,0); + lon=sprintf(ptrTrama->parametros,"nfn=%s\r","RESPUESTA_Configurar"); + lon+=sprintf(ptrTrama->parametros+lon,"cfg=%s\r",cfg); // Configuración de los Sistemas Operativos del cliente + respuestaEjecucionComando(ptrTrama,herror,ids); + + liberaMemoria(dsk); + liberaMemoria(cfg); + liberaMemoria(nfn); + liberaMemoria(ids); + + muestraMenu(); + + return(TRUE); +} +// ________________________________________________________________________________________________________ +// Función: InventarioHardware +// +// Descripción: +// Envia al servidor el nombre del archivo de inventario de su hardware para posteriormente +// esperar que éste lo solicite y enviarlo por la red. +// Parámetros: +// ptrTrama: contenido del mensaje +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//______________________________________________________________________________________________________ +BOOLEAN InventarioHardware(TRAMA* ptrTrama) +{ + int lon; + SOCKET socket_c; + char *nfn,*ids,msglog[LONSTD],hrdsrc[LONPRM],hrddst[LONPRM]; + char modulo[] = "InventarioHardware()"; + + if (ndebug>=DEBUG_MAXIMO) { + sprintf(msglog, "%s:%s",tbMensajes[21],modulo); + infoDebug(msglog); + } + + nfn=copiaParametro("nfn",ptrTrama); + ids=copiaParametro("ids",ptrTrama); + muestraMensaje(6,NULL); + + sprintf(interface,"%s/%s",pathinterface,nfn); + sprintf(hrdsrc,"/tmp/Chrd-%s",IPlocal); // Nombre que tendra el archivo de inventario + sprintf(parametros,"%s %s",nfn,hrdsrc); + herror=interfaceAdmin(interface,parametros,NULL); + if(herror){ + sprintf(msglog,"%s:%s",tbErrores[86],nfn); + errorInfo(modulo,msglog); + muestraMensaje(18,NULL); + } + else{ + /* Envía fichero de inventario al servidor */ + sprintf(hrddst,"/tmp/Shrd-%s",IPlocal); // Nombre que tendra el archivo en el Servidor + initParametros(ptrTrama,0); + sprintf(ptrTrama->parametros,"nfn=recibeArchivo\rnfl=%s\r",hrddst); + if(!enviaMensajeServidor(&socket_c,ptrTrama,MSG_COMANDO)){ + liberaMemoria(nfn); + liberaMemoria(ids); + errorLog(modulo,42,FALSE); + return(FALSE); + } + /* Espera señal para comenzar el envío */ + liberaMemoria(ptrTrama); + recibeFlag(&socket_c,ptrTrama); + /* Envía archivo */ + if(!sendArchivo(&socket_c,hrdsrc)){ + errorLog(modulo,57, FALSE); + herror=12; // Error de envío de fichero por la red + } + close(socket_c); + muestraMensaje(17,NULL); + } + + /* Envia respuesta de ejecución de la función de interface */ + initParametros(ptrTrama,0); + lon=sprintf(ptrTrama->parametros,"nfn=%s\r","RESPUESTA_InventarioHardware"); + lon+=sprintf(ptrTrama->parametros+lon,"hrd=%s\r",hrddst); + respuestaEjecucionComando(ptrTrama,herror,ids); + liberaMemoria(nfn); + liberaMemoria(ids); + + muestraMenu(); + + return(TRUE); +} +// ________________________________________________________________________________________________________ +// Función: InventarioSoftware +// +// Descripción: +// Crea el inventario software de un sistema operativo instalado en una partición. +// Parámetros: +// ptrTrama: contenido del mensaje +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//______________________________________________________________________________________________________ +BOOLEAN InventarioSoftware(TRAMA* ptrTrama) +{ + char *nfn,*ids,msglog[LONSTD]; + char modulo[] = "InventarioSoftware()"; + + if (ndebug>=DEBUG_MAXIMO) { + sprintf(msglog, "%s:%s",tbMensajes[21],modulo); + infoDebug(msglog); + } + nfn=copiaParametro("nfn",ptrTrama); + ids=copiaParametro("ids",ptrTrama); + muestraMensaje(7,NULL); + InventariandoSoftware(ptrTrama,TRUE,nfn); + respuestaEjecucionComando(ptrTrama,herror,ids); + liberaMemoria(nfn); + liberaMemoria(ids); + muestraMenu(); + return(TRUE); +} +// ________________________________________________________________________________________________________ +// +// Función: InventariandoSoftware +// +// Descripción: +// Envia al servidor el nombre del archivo de inventario de su software para posteriormente +// esperar que éste lo solicite y enviarlo por la red. +// Parámetros: +// ptrTrama: contenido del mensaje +// sw: switch que indica si la función es llamada por el comando InventarioSoftware(true) o CrearImagen(false) +// nfn: Nombre de la función del Interface que implementa el comando +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//______________________________________________________________________________________________________ +BOOLEAN InventariandoSoftware(TRAMA* ptrTrama,BOOLEAN sw,char *nfn) +{ + int lon; + SOCKET socket_c; + char *dsk,*par,msglog[LONSTD],sftsrc[LONPRM],sftdst[LONPRM]; + char modulo[] = "InventariandoSoftware()"; + + dsk=copiaParametro("dsk",ptrTrama); // Disco + par=copiaParametro("par",ptrTrama); + + sprintf(interface,"%s/%s",pathinterface,nfn); + sprintf(sftsrc,"/tmp/CSft-%s-%s",IPlocal,par); // Nombre que tendra el archivo de inventario + sprintf(parametros,"%s %s %s %s",nfn,dsk,par,sftsrc); + + herror=interfaceAdmin(interface,parametros,NULL); + herror=0; + if(herror){ + sprintf(msglog,"%s:%s",tbErrores[86],nfn); + errorInfo(modulo,msglog); + muestraMensaje(20,NULL); + } + else{ + /* Envía fichero de inventario al servidor */ + sprintf(sftdst,"/tmp/Ssft-%s-%s",IPlocal,par); // Nombre que tendra el archivo en el Servidor + initParametros(ptrTrama,0); + + sprintf(ptrTrama->parametros,"nfn=recibeArchivo\rnfl=%s\r",sftdst); + if(!enviaMensajeServidor(&socket_c,ptrTrama,MSG_COMANDO)){ + errorLog(modulo,42,FALSE); + liberaMemoria(dsk); + liberaMemoria(par); + return(FALSE); + } + /* Espera señal para comenzar el envío */ + liberaMemoria(ptrTrama); + if(!recibeFlag(&socket_c,ptrTrama)){ + errorLog(modulo,17,FALSE); + } + /* Envía archivo */ + if(!sendArchivo(&socket_c,sftsrc)){ + errorLog(modulo,57, FALSE); + herror=12; // Error de envío de fichero por la red + } + close(socket_c); + muestraMensaje(19,NULL); + } + initParametros(ptrTrama,0); + lon=sprintf(ptrTrama->parametros,"nfn=%s\r","RESPUESTA_InventarioSoftware"); + lon+=sprintf(ptrTrama->parametros+lon,"par=%s\r",par); + lon+=sprintf(ptrTrama->parametros+lon,"sft=%s\r",sftdst); + if(!sw) + respuestaEjecucionComando(ptrTrama,herror,"0"); + + liberaMemoria(dsk); + liberaMemoria(par); + return(TRUE); +} +// ________________________________________________________________________________________________________ +// Función: EjecutarScript +// +// Descripción: +// Ejecuta código de script +// Parámetros: +// ptrTrama: contenido del mensaje +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//______________________________________________________________________________________________________ +BOOLEAN EjecutarScript(TRAMA* ptrTrama) +{ + int lon; + char *nfn,*aux,*ids,*scp,*cfg,msglog[LONSTD]; + char modulo[] = "EjecutarScript()"; + + if (ndebug>=DEBUG_MAXIMO) { + sprintf(msglog, "%s:%s",tbMensajes[21],modulo); + infoDebug(msglog); + } + aux=copiaParametro("scp",ptrTrama); + scp=URLDecode(aux); + + + muestraMensaje(8,NULL); + /* Nombre del archivo de script */ + char filescript[LONPRM]; + sprintf(filescript,"/tmp/_script_%s",IPlocal); + escribeArchivo(filescript,scp); + nfn=copiaParametro("nfn",ptrTrama); + sprintf(interface,"%s/%s",pathinterface,nfn); + sprintf(parametros,"%s %s",nfn,filescript); + herror=interfaceAdmin(interface,parametros,NULL); + if(herror){ + sprintf(msglog,"%s:%s",tbErrores[86],nfn); + errorInfo(modulo,msglog); + muestraMensaje(21,NULL); + } + else + muestraMensaje(22,NULL); + + // Toma configuración de particiones + cfg=LeeConfiguracion(); + if(!cfg){ // No se puede recuperar la configuración del cliente + errorLog(modulo,36,FALSE); + herror=36; + } + + ids=copiaParametro("ids",ptrTrama); + + //herror=ejecutarCodigoBash(scp); + initParametros(ptrTrama,0); + lon=sprintf(ptrTrama->parametros,"nfn=%s\r","RESPUESTA_EjecutarScript"); + lon+=sprintf(ptrTrama->parametros+lon,"cfg=%s\r",cfg); // Configuración de los Sistemas Operativos del cliente + respuestaEjecucionComando(ptrTrama,herror,ids); + + liberaMemoria(nfn); + liberaMemoria(ids); + liberaMemoria(aux); + liberaMemoria(scp); + liberaMemoria(cfg); + + muestraMenu(); + + return(TRUE); +} +//______________________________________________________________________________________________________ +// Función: respuestaEjecucionComando +// +// Descripción: +// Envia una respuesta a una ejecucion de comando al servidor de Administración +// Parámetros: +// - ptrTrama: contenido del mensaje +// - res: Resultado de la ejecución (Código de error devuelto por el script ejecutado) +// - ids: Identificador de la sesion (En caso de no haber seguimiento es NULO) +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +BOOLEAN respuestaEjecucionComando(TRAMA* ptrTrama,int res,char *ids) +{ + int lon; + SOCKET socket_c; + char modulo[] = "respuestaEjecucionComando()"; + + lon=strlen(ptrTrama->parametros); + if(ids){ // Existe seguimiento + lon+=sprintf(ptrTrama->parametros+lon,"ids=%s\r",ids); // Añade identificador de la sesión + } + if (res==0){ // Resultado satisfactorio + lon+=sprintf(ptrTrama->parametros+lon,"res=%s\r","1"); + lon+=sprintf(ptrTrama->parametros+lon,"der=%s\r",""); + } + else{ // Algún error + lon+=sprintf(ptrTrama->parametros+lon,"res=%s\r","2"); + if(res>MAXERRORSCRIPT) + lon+=sprintf(ptrTrama->parametros+lon,"der=%s (Error de script:%d)\r",tbErroresScripts[0],res);// Descripción del error + else + lon+=sprintf(ptrTrama->parametros+lon,"der=%s\r",tbErroresScripts[res]);// Descripción del error + } + if(!(enviaMensajeServidor(&socket_c,ptrTrama,MSG_NOTIFICACION))){ + errorLog(modulo,44,FALSE); + return(FALSE); + } + + close(socket_c); + return(TRUE); +} +// ________________________________________________________________________________________________________ +// Función: gestionaTrama +// +// Descripción: +// Procesa las tramas recibidas. +// Parametros: +// ptrTrama: contenido del mensaje +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +BOOLEAN gestionaTrama(TRAMA *ptrTrama) +{ + int i, res; + char *nfn; + char modulo[] = "gestionaTrama()"; + + INTROaFINCAD(ptrTrama); + nfn = copiaParametro("nfn", ptrTrama); // Toma nombre de función + for (i = 0; i < MAXIMAS_FUNCIONES; i++) { // Recorre funciones que procesan las tramas + res = strcmp(tbfuncionesClient[i].nf, nfn); + if (res == 0) { // Encontrada la función que procesa el mensaje + liberaMemoria(nfn); + return(tbfuncionesClient[i].fptr(ptrTrama)); // Invoca la función + } + } + + liberaMemoria(nfn); + + /* Sólo puede ser un comando personalizado + if (ptrTrama->tipo==MSG_COMANDO) + return(Comando(ptrTrama)); + */ + errorLog(modulo, 18, FALSE); + return (FALSE); +} +//________________________________________________________________________________________________________ +// Función: ejecutaArchivo +// +// Descripción: +// Ejecuta los comando contenido en un archivo (cada comando y sus parametros separados por un +// salto de linea. +// Parámetros: +// filecmd: Nombre del archivo de comandos +// ptrTrama: Puntero a una estructura TRAMA usada en las comunicaciones por red (No debe ser NULL) +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//________________________________________________________________________________________________________ +BOOLEAN ejecutaArchivo(char* filecmd,TRAMA *ptrTrama) +{ + char* buffer,*lineas[MAXIMAS_LINEAS]; + int i,numlin; + char modulo[] = "ejecutaArchivo()"; + + buffer=leeArchivo(filecmd); + if(buffer){ + numlin = splitCadena(lineas, buffer, '@'); + initParametros(ptrTrama,0); + for (i = 0; i < numlin; i++) { + if(strlen(lineas[i])>0){ + strcpy(ptrTrama->parametros,lineas[i]); + //strcat(ptrTrama->parametros,"\rMCDJ@"); // Fin de trama + if(!gestionaTrama(ptrTrama)){ // Análisis de la trama + errorLog(modulo,39,FALSE); + //return(FALSE); + } + } + } + } + liberaMemoria(buffer); + return(TRUE); +} + +BOOLEAN EjecutaComandosPendientes(TRAMA* ptrTrama) +{ + return(TRUE); +} + +//______________________________________________________________________________________________________ +// Función: enviaMensajeServidor +// +// Descripción: +// Envia un mensaje al servidor de Administración +// Parámetros: +// - socket_c: (Salida) Socket utilizado para el envío +// - ptrTrama: contenido del mensaje +// - tipo: Tipo de mensaje +// C=Comando, N=Respuesta a un comando, P=Peticion,R=Respuesta a una petición, I=Informacion +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +BOOLEAN enviaMensajeServidor(SOCKET *socket_c,TRAMA *ptrTrama,char tipo) +{ + int lon; + char modulo[] = "enviaMensajeServidor()"; + + *socket_c=abreConexion(); + if(*socket_c==INVALID_SOCKET){ + errorLog(modulo,38,FALSE); // Error de conexión con el servidor + return(FALSE); + } + ptrTrama->arroba='@'; // Cabecera de la trama + strncpy(ptrTrama->identificador,"JMMLCAMDJ_MCDJ",14); // identificador de la trama + ptrTrama->tipo=tipo; // Tipo de mensaje + lon=strlen(ptrTrama->parametros); // Compone la trama + lon+=sprintf(ptrTrama->parametros+lon,"iph=%s\r",IPlocal); // Ip del ordenador + lon+=sprintf(ptrTrama->parametros+lon,"ido=%s\r",idordenador); // Identificador del ordenador + lon+=sprintf(ptrTrama->parametros+lon,"npc=%s\r",nombreordenador); // Nombre del ordenador + lon+=sprintf(ptrTrama->parametros+lon,"idc=%s\r",idcentro); // Identificador del centro + lon+=sprintf(ptrTrama->parametros+lon,"ida=%s\r",idaula); // Identificador del aula + + if (!mandaTrama(socket_c,ptrTrama)) { + errorLog(modulo,26,FALSE); + return (FALSE); + } + return(TRUE); +} +// ******************************************************************************************************** +// PROGRAMA PRINCIPAL (CLIENTE) +// ******************************************************************************************************** +int main(int argc, char *argv[]) +{ + TRAMA *ptrTrama; + char modulo[] = "main()"; + + ptrTrama=(TRAMA *)reservaMemoria(sizeof(TRAMA)); + if (ptrTrama == NULL) { // No hay memoria suficiente para el bufer de las tramas + errorLog(modulo, 3, FALSE); + exit(EXIT_FAILURE); + } + /*-------------------------------------------------------------------------------------------------------- + Validación de parámetros de ejecución y fichero de configuración + ---------------------------------------------------------------------------------------------------------*/ + if (!validacionParametros(argc, argv,3)) // Valida parámetros de ejecución + exit(EXIT_FAILURE); + + if (!tomaConfiguracion(szPathFileCfg)) // Toma parametros de configuración + exit(EXIT_FAILURE); + /*-------------------------------------------------------------------------------------------------------- + Carga catálogo de funciones que procesan las tramas + ---------------------------------------------------------------------------------------------------------*/ + int cf = 0; + + strcpy(tbfuncionesClient[cf].nf, "RESPUESTA_AutoexecCliente"); + tbfuncionesClient[cf++].fptr = &RESPUESTA_AutoexecCliente; + + strcpy(tbfuncionesClient[cf].nf, "RESPUESTA_InclusionCliente"); + tbfuncionesClient[cf++].fptr = &RESPUESTA_InclusionCliente; + + strcpy(tbfuncionesClient[cf].nf, "NoComandosPtes"); + tbfuncionesClient[cf++].fptr = &NoComandosPtes; + + strcpy(tbfuncionesClient[cf].nf, "Actualizar"); + tbfuncionesClient[cf++].fptr = &Actualizar; + + strcpy(tbfuncionesClient[cf].nf, "Purgar"); + tbfuncionesClient[cf++].fptr = &Purgar; + + strcpy(tbfuncionesClient[cf].nf, "ConsolaRemota"); + tbfuncionesClient[cf++].fptr = &ConsolaRemota; + + strcpy(tbfuncionesClient[cf].nf, "Sondeo"); + tbfuncionesClient[cf++].fptr = &Sondeo; + + strcpy(tbfuncionesClient[cf].nf, "Arrancar"); + tbfuncionesClient[cf++].fptr = &Arrancar; + + strcpy(tbfuncionesClient[cf].nf, "Apagar"); + tbfuncionesClient[cf++].fptr = &Apagar; + + strcpy(tbfuncionesClient[cf].nf, "Reiniciar"); + tbfuncionesClient[cf++].fptr = &Reiniciar; + + strcpy(tbfuncionesClient[cf].nf, "IniciarSesion"); + tbfuncionesClient[cf++].fptr = &IniciarSesion; + + strcpy(tbfuncionesClient[cf].nf, "CrearImagen"); + tbfuncionesClient[cf++].fptr = &CrearImagen; + + strcpy(tbfuncionesClient[cf].nf, "CrearImagenBasica"); + tbfuncionesClient[cf++].fptr = &CrearImagenBasica; + + strcpy(tbfuncionesClient[cf].nf, "CrearSoftIncremental"); + tbfuncionesClient[cf++].fptr = &CrearSoftIncremental; + + strcpy(tbfuncionesClient[cf].nf, "RestaurarImagen"); + tbfuncionesClient[cf++].fptr = &RestaurarImagen; + + strcpy(tbfuncionesClient[cf].nf, "RestaurarImagenBasica"); + tbfuncionesClient[cf++].fptr = &RestaurarImagenBasica; + + strcpy(tbfuncionesClient[cf].nf, "RestaurarSoftIncremental"); + tbfuncionesClient[cf++].fptr = &RestaurarSoftIncremental; + + + strcpy(tbfuncionesClient[cf].nf, "Configurar"); + tbfuncionesClient[cf++].fptr = &Configurar; + + strcpy(tbfuncionesClient[cf].nf, "EjecutarScript"); + tbfuncionesClient[cf++].fptr = &EjecutarScript; + + strcpy(tbfuncionesClient[cf].nf, "InventarioHardware"); + tbfuncionesClient[cf++].fptr = &InventarioHardware; + + strcpy(tbfuncionesClient[cf].nf, "InventarioSoftware"); + tbfuncionesClient[cf++].fptr = &InventarioSoftware; + + strcpy(tbfuncionesClient[cf].nf, "EjecutaComandosPendientes"); + tbfuncionesClient[cf++].fptr = &EjecutaComandosPendientes; + + /*-------------------------------------------------------------------------------------------------------- + Toma dirección IP del cliente + ---------------------------------------------------------------------------------------------------------*/ + if(!tomaIPlocal()){ // Error al recuperar la IP local + errorLog(modulo,0,FALSE); + exit(EXIT_FAILURE); + } + /*-------------------------------------------------------------------------------------------------------- + Inicio de sesión + ---------------------------------------------------------------------------------------------------------*/ + infoLog(1); // Inicio de sesión + infoLog(3); // Abriendo sesión en el servidor de Administración; + /*-------------------------------------------------------------------------------------------------------- + Inclusión del cliente en el sistema + ---------------------------------------------------------------------------------------------------------*/ + if(!inclusionCliente(ptrTrama)){ // Ha habido algún problema al abrir sesión + errorLog(modulo,0,FALSE); + exit(EXIT_FAILURE); + } + infoLog(4); // Cliente iniciado + + /*-------------------------------------------------------------------------------------------------------- + Procesamiento de la cache + ---------------------------------------------------------------------------------------------------------*/ + infoLog(23); // Abriendo sesión en el servidor de Administración; + if(!cuestionCache(cache)){ + errorLog(modulo,0,FALSE); + exit(EXIT_FAILURE); + } + /*-------------------------------------------------------------------------------------------------------- + Ejecución del autoexec + ---------------------------------------------------------------------------------------------------------*/ + if(atoi(idproautoexec)>0){ // Ejecución de procedimiento Autoexec + infoLog(5); + if(!autoexecCliente(ptrTrama)){ // Ejecución fichero autoexec + errorLog(modulo,0,FALSE); + exit(EXIT_FAILURE); + } + } + /*-------------------------------------------------------------------------------------------------------- + Comandos pendientes + ---------------------------------------------------------------------------------------------------------*/ + infoLog(6); // Procesa comandos pendientes + if(!comandosPendientes(ptrTrama)){ // Ejecución de acciones pendientes + errorLog(modulo,0,FALSE); + exit(EXIT_FAILURE); + } + infoLog(7); // Acciones pendientes procesadas + /*-------------------------------------------------------------------------------------------------------- + Bucle de recepción de comandos + ---------------------------------------------------------------------------------------------------------*/ + muestraMenu(); + procesaComandos(ptrTrama); // Bucle para procesar comandos interactivos + /*-------------------------------------------------------------------------------------------------------- + Fin de la sesión + ---------------------------------------------------------------------------------------------------------*/ + exit(EXIT_SUCCESS); +} diff --git a/native/Sources/Clients/ogAdmClient/sources/ogAdmClient.h b/native/Sources/Clients/ogAdmClient/sources/ogAdmClient.h new file mode 100644 index 0000000..da0a5c3 --- /dev/null +++ b/native/Sources/Clients/ogAdmClient/sources/ogAdmClient.h @@ -0,0 +1,183 @@ +// ******************************************************************************************************** +// Cliernte: ogAdmClient +// Autor: José Manuel Alonso (E.T.S.I.I.) Universidad de Sevilla +// Fecha Creación: Marzo-2010 +// Fecha Última modificación: Marzo-2010 +// Nombre del fichero: ogAdmClient.h +// Descripción :Este fichero implementa el cliente general del sistema +// ******************************************************************************************************** +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ogAdmLib.h" +// ________________________________________________________________________________________________________ +// Variables globales +// ________________________________________________________________________________________________________ +char *idordenador; // Identificador del ordenador +char *nombreordenador; // Nombre del ordenador +char *cache; // Tamaño de la caché +char *idproautoexec; // Identificador del procedimiento de autoexec +char *idcentro; // Identificador de la Unidad Organizativa +char *idaula; // Identificador del aula +char IPlocal[LONIP]; // Ip local + +char servidoradm[LONPRM]; // Dirección IP del servidor de administración +char puerto[LONPRM]; // Puerto de comunicación +char pathinterface[LONPRM]; // Path donde está la interface entre la administración y el módulo de clonación + +char interface[LONFUN]; // Nombre del módulo,función o script de la interface con el módulo de administración +char parametros[LONSTD]; // Parámetros para la llamada +int herror; + +BOOLEAN CMDPTES; // Para bucle de comandos pendientes + + +char urlmenu[MAXLONURL]; // Url de la pagina de menu para el browser +char urlmsg[MAXLONURL]; // Url de la página de mensajed para el browser + + +typedef struct{ // Estructura usada para referenciar las funciones que procesan las tramas + char nf[LONFUN]; // Nombre de la función + BOOLEAN (*fptr)(TRAMA*); // Puntero a la función que procesa la trama +}MSGFUN; +MSGFUN tbfuncionesClient[MAXIMAS_FUNCIONES]; +// ________________________________________________________________________________________________________ +// Tabla de errores de la ejecución de los scripts +// ________________________________________________________________________________________________________ +char* tbErroresScripts[]={"Se han generado errores desconocidos. No se puede continuar la ejecución de este módulo",\ + "001-Formato de ejecución incorrecto.",\ + "002-Fichero o dispositivo no encontrado",\ + "003-Error en partición de disco",\ + "004-Partición o fichero bloqueado",\ + "005-Error al crear o restaurar una imagen",\ + "006-Sin sistema operativo",\ + "007-Programa o función BOOLEAN no ejecutable",\ + "008-Error en la creación del archivo de eco para consola remota",\ + "009-Error en la lectura del archivo temporal de intercambio",\ + "010-Error al ejecutar la llamada a la interface de administración",\ + "011-La información retornada por la interface de administración excede de la longitud permitida",\ + "012-Error en el envío de fichero por la red",\ + "013-Error en la creación del proceso hijo",\ + "014-Error de escritura en destino",\ + "015-Sin Cache en el Cliente",\ + "016-No hay espacio en la cache para almacenar fichero-imagen",\ + "017-Error al Reducir el Sistema Archivos",\ + "018-Error al Expandir el Sistema Archivos",\ + "019-Valor fuera de rango o no válido.",\ + "020-Sistema de archivos desconocido o no se puede montar",\ + "021-Error en partición de caché local",\ + "022-El disco indicado no contiene una particion GPT",\ + "023-Error no definido",\ + "024-Error no definido",\ + "025-Error no definido",\ + "026-Error no definido",\ + "027-Error no definido",\ + "028-Error no definido",\ + "029-Error no definido",\ + "030-Error al restaurar imagen - Imagen mas grande que particion",\ + "031-Error al realizar el comando updateCache",\ + "032-Error al formatear",\ + "033-Archivo de imagen corrupto o de otra versión de partclone",\ + "034-Error no definido",\ + "035-Error no definido",\ + "036-Error no definido",\ + "037-Error no definido",\ + "038-Error no definido",\ + "039-Error no definido",\ + "040-Error imprevisto no definido",\ + "041-Error no definido",\ + "042-Error no definido",\ + "043-Error no definido",\ + "044-Error no definido",\ + "045-Error no definido",\ + "046-Error no definido",\ + "047-Error no definido",\ + "048-Error no definido",\ + "049-Error no definido",\ + "050-Error en la generación de sintaxis de transferenica unicast",\ + "051-Error en envio UNICAST de una particion",\ + "052-Error en envio UNICAST de un fichero",\ + "053-Error en la recepcion UNICAST de una particion",\ + "054-Error en la recepcion UNICAST de un fichero",\ + "055-Error en la generacion de sintaxis de transferenica Multicast",\ + "056-Error en envio MULTICAST de un fichero",\ + "057-Error en la recepcion MULTICAST de un fichero",\ + "058-Error en envio MULTICAST de una particion",\ + "059-Error en la recepcion MULTICAST de una particion",\ + "060-Error en la conexion de una sesion UNICAST|MULTICAST con el MASTER",\ + "061-Error no definido",\ + "062-Error no definido",\ + "063-Error no definido",\ + "064-Error no definido",\ + "065-Error no definido",\ + "066-Error no definido",\ + "067-Error no definido",\ + "068-Error no definido",\ + "069-Error no definido",\ + "070-Error al montar una imagen sincronizada.",\ + "071-Imagen no sincronizable (es monolitica).",\ + "072-Error al desmontar la imagen.",\ + "073-No se detectan diferencias entre la imagen basica y la particion.",\ + "074-Error al sincronizar, puede afectar la creacion/restauracion de la imagen.",\ + "Error desconocido " + }; + #define MAXERRORSCRIPT 74 // Error máximo cometido +// ________________________________________________________________________________________________________ +// Prototipo de funciones +// ________________________________________________________________________________________________________ +BOOLEAN autoexecCliente(TRAMA*); +BOOLEAN RESPUESTA_AutoexecCliente(TRAMA*); +void procesaComandos(TRAMA*); + +BOOLEAN tomaConfiguracion(char*); +BOOLEAN tomaIPlocal(void); +void scriptLog(const char *,int ); + +BOOLEAN gestionaTrama(TRAMA *); +BOOLEAN inclusionCliente(); +char* LeeConfiguracion(); +BOOLEAN RESPUESTA_InclusionCliente(TRAMA *); + +BOOLEAN comandosPendientes(TRAMA*); +BOOLEAN NoComandosPtes(TRAMA *); + +BOOLEAN respuestaEjecucionComando(TRAMA *,int,char*); +BOOLEAN Sondeo(TRAMA *); +BOOLEAN Actualizar(TRAMA *); +int Purgar(TRAMA* ); + +BOOLEAN ConsolaRemota(TRAMA*); + +BOOLEAN Arrancar(TRAMA *); +BOOLEAN Apagar(TRAMA *); +BOOLEAN Reiniciar(TRAMA *); +BOOLEAN IniciarSesion(TRAMA *); +BOOLEAN CrearImagen(TRAMA *); +BOOLEAN CrearImagenBasica(TRAMA *); +BOOLEAN CrearSoftIncremental(TRAMA*); + +BOOLEAN InventarioHardware(TRAMA *); +BOOLEAN InventariandoSoftware(TRAMA *,BOOLEAN,char*); +BOOLEAN EjecutarScript(TRAMA *); +BOOLEAN ejecutaArchivo(char*,TRAMA*); + +BOOLEAN cuestionCache(char*); +int cargaPaginaWeb(char *); +void muestraMenu(void); +void muestraMensaje(int idx,char*); + +BOOLEAN enviaMensajeServidor(SOCKET *,TRAMA *,char); + + + + diff --git a/native/Sources/Clients/ogAdmClient/sources/ogAdmClient.o b/native/Sources/Clients/ogAdmClient/sources/ogAdmClient.o new file mode 100644 index 0000000000000000000000000000000000000000..e8bec648fc91775dbf0b298384526615646aa621 GIT binary patch literal 108604 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 DNc6y) literal 0 HcmV?d00001 diff --git a/native/Sources/Clients/ogagent/ogagent-oglive_3.0.0-20190520_amd64.buildinfo b/native/Sources/Clients/ogagent/ogagent-oglive_3.0.0-20190520_amd64.buildinfo new file mode 100644 index 0000000..d2b2392 --- /dev/null +++ b/native/Sources/Clients/ogagent/ogagent-oglive_3.0.0-20190520_amd64.buildinfo @@ -0,0 +1,182 @@ +Format: 1.0 +Source: ogagent-oglive +Binary: ogagent-oglive +Architecture: all +Version: 3.0.0-20190520 +Checksums-Md5: + 7252ce182f6ea1d1c6d569046770cbad 33624 ogagent-oglive_3.0.0-20190520_all.deb +Checksums-Sha1: + 5c1a1984bc7598e33a4271d11954352a77adda50 33624 ogagent-oglive_3.0.0-20190520_all.deb +Checksums-Sha256: + 94624c2751b6178a9476ee57a5f59c98f516cf5795f849383418a4182644dd0d 33624 ogagent-oglive_3.0.0-20190520_all.deb +Build-Origin: Ubuntu +Build-Architecture: amd64 +Build-Date: Thu, 14 Dec 2023 08:28:43 +0100 +Build-Tainted-By: + merged-usr-via-aliased-dirs + usr-local-has-configs + usr-local-has-libraries + usr-local-has-programs +Installed-Build-Depends: + autoconf (= 2.71-2), + automake (= 1:1.16.5-1.3), + autopoint (= 0.21-4ubuntu4), + autotools-dev (= 20220109.1), + base-files (= 12ubuntu4.4), + base-passwd (= 3.5.52build1), + bash (= 5.1-6ubuntu1), + binutils (= 2.38-4ubuntu2.4), + binutils-common (= 2.38-4ubuntu2.4), + binutils-x86-64-linux-gnu (= 2.38-4ubuntu2.4), + bsdextrautils (= 2.37.2-4ubuntu3), + bsdutils (= 1:2.37.2-4ubuntu3), + build-essential (= 12.9ubuntu3), + bzip2 (= 1.0.8-5build1), + coreutils (= 8.32-4.1ubuntu1), + cpp (= 4:11.2.0-1ubuntu1), + cpp-11 (= 11.4.0-1ubuntu1~22.04), + dash (= 0.5.11+git20210903+057cd650a4ed-3build1), + debconf (= 1.5.79ubuntu1), + debhelper (= 13.6ubuntu1), + debianutils (= 5.5-1ubuntu2), + debugedit (= 1:5.0-4build1), + dh-autoreconf (= 20), + dh-strip-nondeterminism (= 1.13.0-1), + diffutils (= 1:3.8-0ubuntu2), + dpkg (= 1.21.1ubuntu2.2), + dpkg-dev (= 1.21.1ubuntu2.2), + dwz (= 0.14-1build2), + file (= 1:5.41-3ubuntu0.1), + findutils (= 4.8.0-1ubuntu3), + g++ (= 4:11.2.0-1ubuntu1), + g++-11 (= 11.4.0-1ubuntu1~22.04), + gawk (= 1:5.1.0-1ubuntu0.1), + gcc (= 4:11.2.0-1ubuntu1), + gcc-11 (= 11.4.0-1ubuntu1~22.04), + gcc-11-base (= 11.4.0-1ubuntu1~22.04), + gcc-12-base (= 12.3.0-1ubuntu1~22.04), + gettext (= 0.21-4ubuntu4), + gettext-base (= 0.21-4ubuntu4), + grep (= 3.7-1build1), + groff-base (= 1.22.4-8build1), + gzip (= 1.10-4ubuntu4.1), + hostname (= 3.23ubuntu2), + init-system-helpers (= 1.62), + install-info (= 6.8-4build1), + intltool-debian (= 0.35.0+20060710.5), + libacl1 (= 2.3.1-1), + libarchive-zip-perl (= 1.68-1), + libasan6 (= 11.4.0-1ubuntu1~22.04), + libatomic1 (= 12.3.0-1ubuntu1~22.04), + libattr1 (= 1:2.5.1-1build1), + libaudit-common (= 1:3.0.7-1build1), + libaudit1 (= 1:3.0.7-1build1), + libbinutils (= 2.38-4ubuntu2.4), + libblkid1 (= 2.37.2-4ubuntu3), + libbz2-1.0 (= 1.0.8-5build1), + libc-bin (= 2.35-0ubuntu3.5), + libc-dev-bin (= 2.35-0ubuntu3.5), + libc6 (= 2.35-0ubuntu3.5), + libc6-dev (= 2.35-0ubuntu3.5), + libcap-ng0 (= 0.7.9-2.2build3), + libcap2 (= 1:2.44-1ubuntu0.22.04.1), + libcc1-0 (= 12.3.0-1ubuntu1~22.04), + libcom-err2 (= 1.46.5-2ubuntu1.1), + libcrypt-dev (= 1:4.4.27-1), + libcrypt1 (= 1:4.4.27-1), + libctf-nobfd0 (= 2.38-4ubuntu2.4), + libctf0 (= 2.38-4ubuntu2.4), + libdb5.3 (= 5.3.28+dfsg1-0.8ubuntu3), + libdebconfclient0 (= 0.261ubuntu1), + libdebhelper-perl (= 13.6ubuntu1), + libdpkg-perl (= 1.21.1ubuntu2.2), + libdw1 (= 0.186-1build1), + libelf1 (= 0.186-1build1), + libfile-stripnondeterminism-perl (= 1.13.0-1), + libgcc-11-dev (= 11.4.0-1ubuntu1~22.04), + libgcc-s1 (= 12.3.0-1ubuntu1~22.04), + libgcrypt20 (= 1.9.4-3ubuntu3), + libgdbm-compat4 (= 1.23-1), + libgdbm6 (= 1.23-1), + libgmp10 (= 2:6.2.1+dfsg-3ubuntu1), + libgomp1 (= 12.3.0-1ubuntu1~22.04), + libgpg-error0 (= 1.43-3), + libgssapi-krb5-2 (= 1.19.2-2ubuntu0.3), + libicu70 (= 70.1-2), + libisl23 (= 0.24-2build1), + libitm1 (= 12.3.0-1ubuntu1~22.04), + libk5crypto3 (= 1.19.2-2ubuntu0.3), + libkeyutils1 (= 1.6.1-2ubuntu3), + libkrb5-3 (= 1.19.2-2ubuntu0.3), + libkrb5support0 (= 1.19.2-2ubuntu0.3), + liblsan0 (= 12.3.0-1ubuntu1~22.04), + liblz4-1 (= 1.9.3-2build2), + liblzma5 (= 5.2.5-2ubuntu1), + libmagic-mgc (= 1:5.41-3ubuntu0.1), + libmagic1 (= 1:5.41-3ubuntu0.1), + libmount1 (= 2.37.2-4ubuntu3), + libmpc3 (= 1.2.1-2build1), + libmpfr6 (= 4.1.0-3build3), + libnsl-dev (= 1.3.0-2build2), + libnsl2 (= 1.3.0-2build2), + libpam-modules (= 1.4.0-11ubuntu2.3), + libpam-modules-bin (= 1.4.0-11ubuntu2.3), + libpam-runtime (= 1.4.0-11ubuntu2.3), + libpam0g (= 1.4.0-11ubuntu2.3), + libpcre2-8-0 (= 10.40-1+ubuntu22.04.1+deb.sury.org+1), + libpcre3 (= 2:8.45-1+ubuntu22.04.1+deb.sury.org+1), + libperl5.34 (= 5.34.0-3ubuntu1.3), + libpipeline1 (= 1.5.5-1), + libquadmath0 (= 12.3.0-1ubuntu1~22.04), + libreadline8 (= 8.1.2-1), + libseccomp2 (= 2.5.3-2ubuntu2), + libselinux1 (= 3.3-1build2), + libsigsegv2 (= 2.13-1ubuntu3), + libsmartcols1 (= 2.37.2-4ubuntu3), + libssl3 (= 3.0.2-0ubuntu1.12), + libstdc++-11-dev (= 11.4.0-1ubuntu1~22.04), + libstdc++6 (= 12.3.0-1ubuntu1~22.04), + libsub-override-perl (= 0.09-2), + libsystemd0 (= 249.11-0ubuntu3.11), + libtinfo6 (= 6.3-2ubuntu0.1), + libtirpc-common (= 1.3.2-2ubuntu0.1), + libtirpc-dev (= 1.3.2-2ubuntu0.1), + libtirpc3 (= 1.3.2-2ubuntu0.1), + libtool (= 2.4.6-15build2), + libtsan0 (= 11.4.0-1ubuntu1~22.04), + libubsan1 (= 12.3.0-1ubuntu1~22.04), + libuchardet0 (= 0.0.7-1build2), + libudev1 (= 249.11-0ubuntu3.11), + libunistring2 (= 1.0-1), + libuuid1 (= 2.37.2-4ubuntu3), + libxml2 (= 2.9.14+dfsg-0.1+ubuntu22.04.1+deb.sury.org+1), + libzstd1 (= 1.4.8+dfsg-3build1), + linux-libc-dev (= 5.15.0-91.101), + login (= 1:4.8.1-2ubuntu2.1), + lsb-base (= 11.1.0ubuntu4), + lto-disabled-list (= 24), + m4 (= 1.4.18-5ubuntu2), + make (= 4.3-4.1build1), + man-db (= 2.10.2-1), + mawk (= 1.3.4.20200120-3), + ncurses-base (= 6.3-2ubuntu0.1), + ncurses-bin (= 6.3-2ubuntu0.1), + patch (= 2.7.6-7build2), + perl (= 5.34.0-3ubuntu1.3), + perl-base (= 5.34.0-3ubuntu1.3), + perl-modules-5.34 (= 5.34.0-3ubuntu1.3), + po-debconf (= 1.0.21+nmu1), + readline-common (= 8.1.2-1), + rpcsvc-proto (= 1.4.2-0ubuntu6), + sed (= 4.8-1ubuntu2), + sensible-utils (= 0.0.17), + sysvinit-utils (= 3.01-1ubuntu1), + tar (= 1.34+dfsg-1ubuntu0.1.22.04.2), + util-linux (= 2.37.2-4ubuntu3), + xz-utils (= 5.2.5-2ubuntu1), + zlib1g (= 1:1.2.11.dfsg-2ubuntu9.2) +Environment: + DEB_BUILD_OPTIONS="parallel=8" + DEB_BUILD_PROFILES="noudeb" + LANG="C" + SOURCE_DATE_EPOCH="1529319600" diff --git a/native/Sources/Clients/ogagent/ogagent-oglive_3.0.0-20190520_amd64.changes b/native/Sources/Clients/ogagent/ogagent-oglive_3.0.0-20190520_amd64.changes new file mode 100644 index 0000000..9ab26d9 --- /dev/null +++ b/native/Sources/Clients/ogagent/ogagent-oglive_3.0.0-20190520_amd64.changes @@ -0,0 +1,26 @@ +Format: 1.8 +Date: Mon, 18 Jun 2018 13:00:00 +0200 +Source: ogagent-oglive +Binary: ogagent-oglive +Built-For-Profiles: noudeb +Architecture: all +Version: 3.0.0-20190520 +Distribution: unstable +Urgency: medium +Maintainer: Ramón M. Gómez +Changed-By: Ramón M. Gómez +Description: + ogagent-oglive - OpenGnsys Agent for ogLive client +Changes: + ogagent-oglive (3.0.0-20190520) unstable; urgency=medium + . + * OGAgent for ogLive compatible with OpenGnsys 3 web API +Checksums-Sha1: + 5c1a1984bc7598e33a4271d11954352a77adda50 33624 ogagent-oglive_3.0.0-20190520_all.deb + ec6e0da3760059612e014f25297cb73e887697cd 5954 ogagent-oglive_3.0.0-20190520_amd64.buildinfo +Checksums-Sha256: + 94624c2751b6178a9476ee57a5f59c98f516cf5795f849383418a4182644dd0d 33624 ogagent-oglive_3.0.0-20190520_all.deb + 7f2a8b5e25860c53c72b1a438010be022b3bf1c0a05f3562b570f9e940bfd450 5954 ogagent-oglive_3.0.0-20190520_amd64.buildinfo +Files: + 7252ce182f6ea1d1c6d569046770cbad 33624 admin optional ogagent-oglive_3.0.0-20190520_all.deb + 9f2806400070d5df4bb4c54a010dde70 5954 admin optional ogagent-oglive_3.0.0-20190520_amd64.buildinfo diff --git a/native/Sources/Clients/ogagent/oglive/build-stamp b/native/Sources/Clients/ogagent/oglive/build-stamp new file mode 100644 index 0000000..e69de29 diff --git a/native/Sources/Clients/ogagent/oglive/configure-stamp b/native/Sources/Clients/ogagent/oglive/configure-stamp new file mode 100644 index 0000000..e69de29 diff --git a/native/Sources/Clients/ogagent/oglive/debian/.debhelper/generated/ogagent-oglive/installed-by-dh_installdocs b/native/Sources/Clients/ogagent/oglive/debian/.debhelper/generated/ogagent-oglive/installed-by-dh_installdocs new file mode 100644 index 0000000..e4fdbda --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/.debhelper/generated/ogagent-oglive/installed-by-dh_installdocs @@ -0,0 +1 @@ +./readme.txt diff --git a/native/Sources/Clients/ogagent/oglive/debian/files b/native/Sources/Clients/ogagent/oglive/debian/files new file mode 100644 index 0000000..a00cd9d --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/files @@ -0,0 +1,2 @@ +ogagent-oglive_3.0.0-20190520_all.deb admin optional +ogagent-oglive_3.0.0-20190520_amd64.buildinfo admin optional diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive.debhelper.log b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive.debhelper.log new file mode 100644 index 0000000..908d06e --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive.debhelper.log @@ -0,0 +1,14 @@ +dh_prep +dh_installdirs +dh_installchangelogs +dh_installdocs +dh_installdebconf +dh_installinit +dh_compress +dh_link +dh_fixperms +dh_installdeb +dh_shlibdeps +dh_gencontrol +dh_md5sums +dh_builddeb diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/DEBIAN/control b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/DEBIAN/control new file mode 100644 index 0000000..719a066 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/DEBIAN/control @@ -0,0 +1,11 @@ +Package: ogagent-oglive +Version: 3.0.0-20190520 +Architecture: all +Maintainer: Ramón M. Gómez +Installed-Size: 235 +Depends: python-requests (>= 0.8.2), python-six (>= 1.1), python-prctl (>= 1.1.1), python (>= 2.7), libxss1 +Section: admin +Priority: optional +Homepage: https://opengnsys.es +Description: OpenGnsys Agent for ogLive client + This package provides the required components to allow this machine to work on an environment managed by OpenGnsys. diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/DEBIAN/md5sums b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/DEBIAN/md5sums new file mode 100644 index 0000000..319e0b8 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/DEBIAN/md5sums @@ -0,0 +1,44 @@ +a645b097e936b35a0f9d667320b97c7a usr/bin/ogagent +f1af20ebcb0f9ca24050f5440b280971 usr/share/OGAgent/cfg/ogagent.cfg +1a151168042f9f91b0ac9de8d81b1cc9 usr/share/OGAgent/cfg/ogclient.cfg +e9b55cf5a2893cb205ece2914ad2cec6 usr/share/OGAgent/opengnsys/RESTApi.py +b2145636e0e9f3733a07dc3c864ffba6 usr/share/OGAgent/opengnsys/__init__.py +a53a553fabe11b358872eaa6f15e51c7 usr/share/OGAgent/opengnsys/certs.py +b6b6d13dffe43f62019489701bd3dceb usr/share/OGAgent/opengnsys/config.py +5ddd04ea082b0030b56dbac442817ba2 usr/share/OGAgent/opengnsys/httpserver.py +a7ceb4791ea9a1e5074f37c27aaef65b usr/share/OGAgent/opengnsys/ipc.py +08005cd7b24449275157d9c560f729c0 usr/share/OGAgent/opengnsys/linux/OGAgentService.py +3ef55d64ebda86651c3d882c5c649389 usr/share/OGAgent/opengnsys/linux/__init__.py +e5c7da55543002f86d591d243383563a usr/share/OGAgent/opengnsys/linux/daemon.py +86e7674bddc118fc1ceb9cd1de7d5912 usr/share/OGAgent/opengnsys/linux/log.py +18c89c3f7a9d457f99c1e1bb9e3297dd usr/share/OGAgent/opengnsys/linux/operations.py +f287da160c9d182e8a11d86bce27fd69 usr/share/OGAgent/opengnsys/linux/renamer/__init__.py +dee0a162a35e6bf8b18e39c4b8c4922c usr/share/OGAgent/opengnsys/linux/renamer/debian.py +39cc52a365eac6caec10dbad16d3dceb usr/share/OGAgent/opengnsys/linux/renamer/opensuse.py +b97470f7d68208d837659fcd6b910c9f usr/share/OGAgent/opengnsys/linux/renamer/redhat.py +f2255a73b7801f9596a9929358d75629 usr/share/OGAgent/opengnsys/loader.py +8d648c5a0cbb79e69c3d0eeee8247226 usr/share/OGAgent/opengnsys/log.py +cc21354530aecfd1376d0a9efcedc0f5 usr/share/OGAgent/opengnsys/macos/__init__.py +5ed4af5272fe5f5e4bd1f5ad591edd3a usr/share/OGAgent/opengnsys/macos/operations.py +d41d8cd98f00b204e9800998ecf8427e usr/share/OGAgent/opengnsys/modules/__init__.py +5be7cd44bb941107cbe67345982cce22 usr/share/OGAgent/opengnsys/modules/client/OpenGnSys/__init__.py +d41d8cd98f00b204e9800998ecf8427e usr/share/OGAgent/opengnsys/modules/client/__init__.py +a7903db8bd76249a4e0ee72147542ecd usr/share/OGAgent/opengnsys/modules/server/OpenGnSys/__init__.py +d41d8cd98f00b204e9800998ecf8427e usr/share/OGAgent/opengnsys/modules/server/__init__.py +3ef55d64ebda86651c3d882c5c649389 usr/share/OGAgent/opengnsys/oglive/__init__.py +e5c7da55543002f86d591d243383563a usr/share/OGAgent/opengnsys/oglive/daemon.py +a316e2cd56e19eb453215d441f9589aa usr/share/OGAgent/opengnsys/oglive/operations.py +7b20385f798d849dd546eb1c25149d76 usr/share/OGAgent/opengnsys/operations.py +e5c055043531b106f345f63ffbfb3b44 usr/share/OGAgent/opengnsys/scriptThread.py +296542d5654398781ec99c1ec2c0db7f usr/share/OGAgent/opengnsys/service.py +68b513e35d67d3e783947ec35b60cfa7 usr/share/OGAgent/opengnsys/utils.py +99858b8c43e60816ce3d50888f6bdf21 usr/share/OGAgent/opengnsys/windows/OGAgentService.py +5c4822b3e4cef2d5ec2225ad8adcfeed usr/share/OGAgent/opengnsys/windows/__init__.py +737d49175b1b4045adfd3bd8a66b66b7 usr/share/OGAgent/opengnsys/windows/log.py +f6e9945941b22caf066617abc25b47e2 usr/share/OGAgent/opengnsys/windows/operations.py +4eeead8170a612cedd5ac16f043695f8 usr/share/OGAgent/opengnsys/workers/__init__.py +7e0279e56a6730c74c4b525916a06f57 usr/share/OGAgent/opengnsys/workers/client_worker.py +6e6ff6fc0e398882d8696ae00cf48bb9 usr/share/OGAgent/opengnsys/workers/server_worker.py +c25929437b6c40d24ff965ca588dd089 usr/share/doc/ogagent-oglive/changelog.Debian.gz +172f344346f66d22a8bf1888b1811c06 usr/share/doc/ogagent-oglive/copyright +a7d8603a8cfc9a45e52129a5e5e8664b usr/share/doc/ogagent-oglive/readme.txt diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/DEBIAN/postinst b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/DEBIAN/postinst new file mode 100755 index 0000000..8fc04b5 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/DEBIAN/postinst @@ -0,0 +1,21 @@ +#!/bin/sh + +. /usr/share/debconf/confmodule + +set -e +case "$1" in + configure) + chmod 600 /usr/share/OGAgent/cfg/ogagent.cfg + ;; + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + + + +exit 0 diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/DEBIAN/postrm b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/DEBIAN/postrm new file mode 100755 index 0000000..a46fa48 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/DEBIAN/postrm @@ -0,0 +1,10 @@ +#!/bin/sh -e + +. /usr/share/debconf/confmodule + +set -e + +if [ "$1" = "purge" ] ; then + rm -rf /usr/share/OGAgent || true > /dev/null 2>&1 +fi + diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/etc/ogagent/ogagent.cfg b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/etc/ogagent/ogagent.cfg new file mode 120000 index 0000000..8a21b68 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/etc/ogagent/ogagent.cfg @@ -0,0 +1 @@ +/usr/share/OGAgent/cfg/ogagent.cfg \ No newline at end of file diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/etc/ogagent/ogclient.cfg b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/etc/ogagent/ogclient.cfg new file mode 120000 index 0000000..c7dfd07 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/etc/ogagent/ogclient.cfg @@ -0,0 +1 @@ +/usr/share/OGAgent/cfg/ogclient.cfg \ No newline at end of file diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/bin/ogagent b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/bin/ogagent new file mode 100755 index 0000000..1bcc29b --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/bin/ogagent @@ -0,0 +1,6 @@ +#!/bin/sh + +FOLDER=/usr/share/OGAgent + +cd $FOLDER +python -m opengnsys.linux.OGAgentService $@ diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/cfg/ogagent.cfg b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/cfg/ogagent.cfg new file mode 100644 index 0000000..2c84cb1 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/cfg/ogagent.cfg @@ -0,0 +1,27 @@ +[opengnsys] +# Listen address & port of REST +address=0.0.0.0 +port=8000 + +# This is a comma separated list of paths where to look for modules to load +path=test_modules/server + +# Remote OpenGnsys Service (please change IP address) +remote=https://192.168.2.10/opengnsys/rest/v3 +# Alternate OpenGnsys Service (comment out to enable this option) +#altremote=https://10.0.2.2/opengnsys/rest + +# Security tokens (please change) +client=xxxxx +secret=yyyyy + +# Log Level, if ommited, will be set to INFO +log=DEBUG + +# Module specific +# The sections must match the module name +# This section will be passes on activation to module +#[Sample1] +#value1=Mariete +#value2=Yo +#remote=https://172.27.0.1:9999/rest diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/cfg/ogclient.cfg b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/cfg/ogclient.cfg new file mode 100644 index 0000000..d39fc96 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/cfg/ogclient.cfg @@ -0,0 +1,11 @@ +[opengnsys] +# Log Level, if ommited, will be set to INFO +log=DEBUG + +# Module specific +# The sections must match the module name +# This section will be passes on activation to module +#[Sample1] +#value1=Mariete +#value2=Yo +#remote=https://172.27.0.1:9999/rest diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/RESTApi.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/RESTApi.py new file mode 100644 index 0000000..39c23a9 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/RESTApi.py @@ -0,0 +1,196 @@ +# -*- 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 warnings + +from .log import logger + +from .utils import exceptionToMessage + +VERIFY_CERT = False # Do not check server certificate +TIMEOUT = 5 # Connection timout, in seconds + + +class RESTError(Exception): + ERRCODE = 0 + + +class ConnectionError(RESTError): + ERRCODE = -1 + + +# Disable warnings log messages +try: + import urllib3 # @UnusedImport +except Exception: + from requests.packages import urllib3 # @Reimport + +try: + urllib3.disable_warnings() # @UndefinedVariable + warnings.simplefilter("ignore") +except Exception: + pass # In fact, isn't too important, but wil log warns to logging file + + +class REST(object): + """ + Simple interface to remote REST apis. + The constructor expects the "base url" as parameter, that is, the url that will be common on all REST requests + Remember that this is a helper for "easy of use". You can provide your owns using requests lib for example. + Examples: + v = REST('https://example.com/rest/v1/') (Can omit trailing / if desired) + v.sendMessage('hello?param1=1¶m2=2') + This will generate a GET message to https://example.com/rest/v1/hello?param1=1¶m2=2, and return the + deserialized JSON result or an exception + v.sendMessage('hello?param1=1¶m2=2', {'name': 'mario' }) + This will generate a POST message to https://example.com/rest/v1/hello?param1=1¶m2=2, with json encoded + body {'name': 'mario' }, and also returns + the deserialized JSON result or raises an exception in case of error + """ + access_token = None + refresh_token = None + + def __init__(self, url): + """ + Initializes the REST helper + url is the full url of the REST API Base, as for example "https://example.com/rest/v1". + @param url The url of the REST API Base. The trailing '/' can be included or omitted, as desired. + """ + self.endpoint = url + + if self.endpoint[-1] != '/': + self.endpoint += '/' + + # Some OSs ships very old python requests lib implementations, workaround them... + try: + self.newerRequestLib = requests.__version__.split('.')[0] >= '1' + except Exception: + self.newerRequestLib = False # I no version, guess this must be an old requests + + # Disable logging requests messages except for errors, ... + logging.getLogger("requests").setLevel(logging.CRITICAL) + # Tries to disable all warnings + try: + warnings.simplefilter("ignore") # Disables all warnings + except Exception: + pass + + def _getUrl(self, method): + """ + Internal method + Composes the URL based on "method" + @param method: Method to append to base url for composition + """ + url = self.endpoint + method + + return url + + def _request(self, url, data=None, patch=False): + """ + Launches the request + @param url: The url to obtain + @param data: if None, the request will be sent as a GET request. If != None, the request will be sent as a POST, + with data serialized as JSON in the body. + """ + try: + # Prepare the header content + headers = {'content-type': 'application/json'} + if self.access_token is not None: + headers['Authorization'] = 'Bearer ' + self.access_token + + if data is None: + logger.debug('REST - Requesting using GET (no data provided) {}'.format(url)) + # Old requests version does not support verify, but it do not checks ssl certificate by default + if self.newerRequestLib: + r = requests.get(url, verify=VERIFY_CERT, timeout=TIMEOUT) + else: + r = requests.get(url) + else: # POST / PATCH + if patch is False: + logger.debug('REST - Requesting using POST {}, data: {}'.format(url, data)) + if self.newerRequestLib: + r = requests.post(url, data=data, headers=headers, + verify=VERIFY_CERT, timeout=TIMEOUT) + else: + r = requests.post(url, data=data, headers=headers) + else: + logger.debug('REST - Requesting using PATCH {}, data: {}'.format(url, data)) + if self.newerRequestLib: + r = requests.patch(url, data=data, headers=headers, + verify=VERIFY_CERT, timeout=TIMEOUT) + else: + r = requests.patch(url, data=data, headers=headers) + + # la respuesta puede venir sin contenido, para ello miramos el codigo devuelto + if r.status_code == 204: + r = json.loads('{"response": "OK"}') + else: + r = json.loads(r.content) # Using instead of r.json() to make compatible with old requests lib versions + logger.debug("REST - response - " + json.dumps(r)) + except requests.exceptions.RequestException as e: + raise ConnectionError(e) + except Exception as e: + raise ConnectionError(exceptionToMessage(e)) + + return r + + def sendMessage(self, msg, data=None, processData=True, patch=False): + """ + Sends a message to remote REST server + @param data: if None or omitted, message will be a GET, else it will send a POST + @param processData: if True, data will be serialized to json before sending, else, data will be sent as "raw" + """ + logger.debug('Invoking post message {} with data {}'.format(msg, data)) + + if processData and data is not None: + data = json.dumps(data) + + url = self._getUrl(msg) + logger.debug('Requesting {}'.format(url)) + + return self._request(url, data, patch) + + def set_authorization_headers(self, access_token = None, refresh_token = None): + """ + Set access token and refresh token for use in REST Api with authorization headers + @param access_token: if None, no authorization headers will be sent in each request, else, and authorization header will be sent + @param refresh_token: if not None, when access_token expires, it will be used to obtain new access token + """ + self.access_token = access_token + self.refresh_token = refresh_token \ No newline at end of file diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/__init__.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/__init__.py new file mode 100644 index 0000000..02e7c34 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/__init__.py @@ -0,0 +1,60 @@ +# -*- 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 + +# On centos, old six release does not includes byte2int, nor six.PY2 +import six + +import modules +from RESTApi import REST, RESTError + +try: + with open('VERSION', 'r') as v: + VERSION = v.read() +except IOError: + VERSION = '1.1.0' + +__title__ = 'OpenGnsys Agent' +__version__ = VERSION +__build__ = 0x010750 +__author__ = 'Adolfo Gómez' +__license__ = "BSD 3-clause" +__copyright__ = "Copyright VirtualCable S.L.U." + +if not hasattr(six, 'byte2int'): + if six.PY3: + import operator + six.byte2int = operator.itemgetter(0) + else: + def _byte2int(bs): + return ord(bs[0]) + six.byte2int = _byte2int diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/certs.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/certs.py new file mode 100644 index 0000000..e4c070e --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/certs.py @@ -0,0 +1,101 @@ +# -*- 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 = 'OGAgent.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 diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/config.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/config.py new file mode 100644 index 0000000..d1f3ede --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/config.py @@ -0,0 +1,58 @@ +# -*- 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 + +from ConfigParser import SafeConfigParser + +config = None + +def readConfig(client=False): + ''' + Reads configuration file + If client is False, will read ogagent.cfg as configuration + If client is True, will read ogclient.cfg as configuration + + This is this way so we can protect ogagent.cfg against reading for non admin users on all platforms. + ''' + cfg = SafeConfigParser() + if client is True: + fname = 'ogclient.cfg' + else: + fname = 'ogagent.cfg' + + if len(cfg.read('cfg/{}'.format(fname))) == 0: + # No configuration found + return None + + return cfg + \ No newline at end of file diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/httpserver.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/httpserver.py new file mode 100644 index 0000000..a06ae6b --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/httpserver.py @@ -0,0 +1,150 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2015 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, print_function + +# Pydev can't parse "six.moves.xxxx" because it is loaded lazy +import six +from six.moves.socketserver import ThreadingMixIn # @UnresolvedImport +from six.moves.BaseHTTPServer import BaseHTTPRequestHandler # @UnresolvedImport +from six.moves.BaseHTTPServer import HTTPServer # @UnresolvedImport +from six.moves.urllib.parse import unquote # @UnresolvedImport + +import json +import threading +import ssl + +from .utils import exceptionToMessage +from .certs import createSelfSignedCert +from .log import logger + +class HTTPServerHandler(BaseHTTPRequestHandler): + service = None + protocol_version = 'HTTP/1.0' + server_version = 'OpenGnsys Agent Server' + sys_version = '' + + 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 sendJsonResponse(self, data): + self.send_response(200) + data = json.dumps(data) + self.send_header('Content-type', 'application/json') + self.send_header('Content-Length', len(data)) + self.end_headers() + # Send the html message + self.wfile.write(data) + + + # parseURL + def parseUrl(self): + # Very simple path & params splitter + path = self.path.split('?')[0][1:].split('/') + + try: + params = dict((v[0], unquote(v[1])) for v in (v.split('=') for v in self.path.split('?')[1].split('&'))) + except Exception: + params = {} + + for v in self.service.modules: + if v.name == path[0]: # Case Sensitive!!!! + return (v, path[1:], params) + + return (None, path, params) + + def notifyMessage(self, module, path, getParams, postParams): + ''' + Locates witch module will process the message based on path (first folder on url path) + ''' + try: + data = module.processServerMessage(path, getParams, postParams, self) + self.sendJsonResponse(data) + except Exception as e: + logger.exception() + self.sendJsonError(500, exceptionToMessage(e)) + + def do_GET(self): + module, path, params = self.parseUrl() + + self.notifyMessage(module, path, params, None) + + def do_POST(self): + module, path, getParams = self.parseUrl() + + # Tries to get JSON content (UTF-8 encoded) + try: + length = int(self.headers.getheader('content-length')) + content = self.rfile.read(length).decode('utf-8') + logger.debug('length: {}, content >>{}<<'.format(length, content)) + postParams = json.loads(content) + except Exception as e: + self.sendJsonError(500, exceptionToMessage(e)) + + self.notifyMessage(module, path, getParams, postParams) + + + def log_error(self, fmt, *args): + logger.error('HTTP ' + fmt % args) + + def log_message(self, fmt, *args): + logger.info('HTTP ' + fmt % args) + + +class HTTPThreadingServer(ThreadingMixIn, HTTPServer): + pass + +class HTTPServerThread(threading.Thread): + def __init__(self, address, service): + super(self.__class__, self).__init__() + + HTTPServerHandler.service = service # Keep tracking of service so we can intercact with it + + self.certFile = createSelfSignedCert() + self.server = HTTPThreadingServer(address, HTTPServerHandler) + self.server.socket = ssl.wrap_socket(self.server.socket, certfile=self.certFile, server_side=True) + + logger.debug('Initialized HTTPS Server thread on {}'.format(address)) + + def getServerUrl(self): + return 'https://{}:{}/'.format(self.server.server_address[0], self.server.server_address[1]) + + def stop(self): + self.server.shutdown() + + def run(self): + self.server.serve_forever() + + diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/ipc.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/ipc.py new file mode 100644 index 0000000..dd3663d --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/ipc.py @@ -0,0 +1,423 @@ +# -*- 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 six +import traceback +import json + +from opengnsys.utils import toUnicode +from opengnsys.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_POPUP title,message Display a popup box with a title +# 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 + +# Client messages +MSG_LOGOFF = 0xA1 # Request log off from an user +MSG_MESSAGE = 0xB2 +MSG_POPUP = 0xB3 +MSG_SCRIPT = 0xC3 + +# Request messages +REQ_MESSAGE = 0xD4 +REQ_POPUP = 0xD5 +REQ_LOGIN = 0xE5 +REQ_LOGOUT = 0xF6 + +# Reverse msgs dict for debugging +REV_DICT = { + MSG_LOGOFF: 'MSG_LOGOFF', + MSG_MESSAGE: 'MSG_MESSAGE', + MSG_POPUP: 'MSG_POPUP', + MSG_SCRIPT: 'MSG_SCRIPT', + REQ_LOGIN: 'REQ_LOGIN', + REQ_LOGOUT: 'REQ_LOGOUT', + REQ_MESSAGE: 'REQ_MESSAGE' +} + +MAGIC = b'\x4F\x47\x41\x00' # OGA in hexa with a padded 0 to the right + + +# 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): + logger.debug('Got Client message {}={}'.format(msg, REV_DICT.get(msg))) + if self.parent.clientMessageProcessor is not None: + self.parent.clientMessageProcessor(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'': + # Client disconnected + self.running = False + break + buf = six.byte2int(b) # Empty buffer, this is set as non-blocking + if state is None: + if buf in (REQ_MESSAGE, REQ_LOGIN, REQ_LOGOUT): + logger.debug('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_MESSAGE, REQ_LOGIN, REQ_LOGOUT): + logger.debug('First length byte is {}'.format(buf)) + msg_len = buf + state = ST_SECOND_BYTE + continue + elif state == ST_SECOND_BYTE: + msg_len += buf << 8 + logger.debug('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 + except Exception as e: + tb = traceback.format_exc() + logger.error('Error: {}, trace: {}'.format(e, tb)) + + if self.running is False: + break + + try: + msg = self.messages.get(block=True, timeout=1) + except six.moves.queue.Empty: # No message got in time @UndefinedVariable + continue + + logger.debug('Got message {}={}'.format(msg, REV_DICT.get(msg[0]))) + + 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)) + + logger.debug('Client processor stopped') + try: + self.clientSocket.close() + except Exception: + pass # If can't close, nothing happens, just end thread + + +class ServerIPC(threading.Thread): + + def __init__(self, listenPort, clientMessageProcessor=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.clientMessageProcessor = clientMessageProcessor + + 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, REV_DICT.get(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 sendPopupMessage(self, title, message): + self.sendMessage(MSG_POPUP, {'title':title, '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 processing if thread is mean to stop + if self.running is False: + break + logger.debug('Got connection from {}'.format(address)) + + self.cleanupFinishedThreads() # House keeping + + logger.debug('Starting new thread, current: {}'.format(self.threads)) + 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): + logger.debug('Sending request for msg: {}({}), {}'.format(msg, REV_DICT.get(msg), data)) + 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 sendLogin(self, username, language): + self.sendRequestMessage(REQ_LOGIN, username+','+language) + + def sendLogout(self, username): + self.sendRequestMessage(REQ_LOGOUT, username) + + def sendMessage(self, module, message, data=None): + ''' + Sends a message "message" with data (data will be encoded as json, so ensure that it is serializable) + @param module: Module that will receive this message + @param message: Message to send. This message is "customized", and understand by modules + @param data: Data to be send as message companion + ''' + msg = '\0'.join((module, message, json.dumps(data))) + self.sendRequestMessage(REQ_MESSAGE, msg) + + def messageReceived(self): + ''' + Override this method to automatically get notified on new message + received. Message is at self.messages queue + ''' + pass + + def receiveBytes(self, number): + msg = b'' + while self.running and len(msg) < number: + try: + buf = self.clientSocket.recv(number - len(msg)) + if buf == b'': + logger.debug('Buf {}, msg {}({})'.format(buf, msg, REV_DICT.get(msg))) + self.running = False + break + msg += buf + except socket.timeout: + pass + + if self.running is False: + logger.debug('Not running, returning None') + 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) # Static, custom socket timeout of 2 seconds for local connection (no network) + + 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 + + if self.running is False: + break + + # 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 (MSG_LOGOFF, MSG_MESSAGE, MSG_SCRIPT): + 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: + tb = traceback.format_exc() + logger.error('Error: {}, trace: {}'.format(e, tb)) + + try: + self.clientSocket.close() + except Exception: + pass # If can't close, nothing happens, just end thread + diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/OGAgentService.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/OGAgentService.py new file mode 100644 index 0000000..29a238d --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/OGAgentService.py @@ -0,0 +1,147 @@ +# -*- 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 opengnsys.service import CommonService +from opengnsys.service import IPC_PORT +from opengnsys import ipc + +from opengnsys.log import logger + +from opengnsys.linux.daemon import Daemon + +import sys +import signal +import json + +try: + from prctl import set_proctitle # @UnresolvedImport +except Exception: # Platform may not include prctl, so in case it's not available, we let the "name" as is + def set_proctitle(_): + pass + + +class OGAgentSvc(Daemon, CommonService): + def __init__(self, args=None): + Daemon.__init__(self, '/var/run/opengnsys-agent.pid') + CommonService.__init__(self) + + def run(self): + logger.debug('** Running Daemon **') + set_proctitle('OGAgent') + + self.initialize() + + # Call modules initialization + # They are called in sequence, no threading is done at this point, so ensure modules onActivate always returns + + + # ********************* + # * Main Service loop * + # ********************* + # Counter used to check ip changes only once every 10 seconds, for + # example + try: + while self.isAlive: + # In milliseconds, will break + self.doWait(1000) + except (KeyboardInterrupt, SystemExit) as e: + logger.error('Requested exit of main loop') + except Exception as e: + logger.exception() + logger.error('Caught exception on main loop: {}'.format(e)) + + self.terminate() + + self.notifyStop() + + def signal_handler(self, signal, frame): + self.isAlive = False + sys.stderr.write("signal handler: {}".format(signal)) + + +def usage(): + sys.stderr.write("usage: {} start|stop|restart|fg|login 'username'|logout 'username'|message 'module' 'message' 'json'\n".format(sys.argv[0])) + sys.exit(2) + +if __name__ == '__main__': + logger.setLevel('INFO') + + if len(sys.argv) == 5 and sys.argv[1] == 'message': + logger.debug('Running client opengnsys') + client = None + try: + client = ipc.ClientIPC(IPC_PORT) + client.sendMessage(sys.argv[2], sys.argv[3], json.loads(sys.argv[4])) + sys.exit(0) + except Exception as e: + logger.error(e) + + + if len(sys.argv) == 3 and sys.argv[1] in ('login', 'logout'): + logger.debug('Running client opengnsys') + client = None + try: + client = ipc.ClientIPC(IPC_PORT) + 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) + elif len(sys.argv) != 2: + usage() + + logger.debug('Executing actor') + daemon = OGAgentSvc() + + signal.signal(signal.SIGTERM, daemon.signal_handler) + signal.signal(signal.SIGINT, daemon.signal_handler) + + 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() + elif 'fg' == sys.argv[1]: + daemon.run() + else: + usage() + sys.exit(0) + else: + usage() diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/__init__.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/__init__.py new file mode 100644 index 0000000..3a98c78 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/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/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/daemon.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/daemon.py new file mode 100644 index 0000000..3753808 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/daemon.py @@ -0,0 +1,182 @@ +# -*- 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 opengnsys.log import logger + +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: + logger.error("fork #1 error: {}".format(e)) + sys.stderr.write("fork #1 failed: {}\n".format(e)) + 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: + logger.error("fork #2 error: {}".format(e)) + sys.stderr.write("fork #2 failed: {}\n".format(e)) + 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): + try: + os.remove(self.pidfile) + except Exception: + # Not found/not permissions or whatever... + pass + + def start(self): + """ + Start the daemon + """ + logger.debug('Starting daemon') + # Check for a pidfile to see if the daemon already runs + try: + pf = open(self.pidfile, 'r') + pid = int(pf.read().strip()) + pf.close() + except IOError: + pid = None + + if pid: + message = "pidfile {} already exist. Daemon already running?\n".format(pid) + logger.error(message) + sys.stderr.write(message) + sys.exit(1) + + # Start the daemon + self.daemonize() + try: + self.run() + except Exception as e: + logger.error('Exception running process: {}'.format(e)) + + if os.path.exists(self.pidfile): + os.remove(self.pidfile) + + 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: + message = "pidfile {} does not exist. Daemon not running?\n".format(self.pidfile) + logger.info(message) + # sys.stderr.write(message) + return # not an error in a restart + + # Try killing the daemon process + try: + for i in range(10): + 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/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/log.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/log.py new file mode 100644 index 0000000..dc54e19 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/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, 'opengnsys.log') + logging.basicConfig( + filename=fname, + filemode='a', + format='%(levelname)s %(asctime)s %(message)s', + level=logging.DEBUG + ) + self.logger = logging.getLogger('opengnsys') + 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/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/operations.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/operations.py new file mode 100644 index 0000000..db6ea18 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/operations.py @@ -0,0 +1,286 @@ +# -*- 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 locale +import ctypes # @UnusedImport +import ctypes.util +import subprocess +import struct +import array +import six +from opengnsys import utils +from .renamer import rename + + +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(str('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(str('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(str('iL'), fcntl.ioctl( + s.fileno(), + 0x8912, # SIOCGIFCONF + struct.pack(str('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(): + ''' + Obtains a list of network interfaces + @return: A "generator" of elements, that are dict-as-object, with this elements: + name: Name of the interface + mac: mac of the interface + ip: ip of the interface + ''' + for ifname in _getInterfaces(): + ip, mac = _getIpAndMac(ifname) + if mac != '00:00:00:00:00:00': # Skips local interfaces + 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 + ''' + # Workaround for dummy thread + if six.PY3 is False: + import threading + threading._DummyThread._Thread__stop = lambda x: 42 + + subprocess.call(['/sbin/reboot']) + + +def poweroff(flags=0): + ''' + Simple poweroff using os command + ''' + # Workaround for dummy thread + if six.PY3 is False: + import threading + threading._DummyThread._Thread__stop = lambda x: 42 + + subprocess.call(['/sbin/poweroff']) + + +def logoff(): + ''' + Kills all curent user processes, which must send a logogof + caveat: If the user has other sessions, will also disconnect from them + ''' + # Workaround for dummy thread + if six.PY3 is False: + import threading + threading._DummyThread._Thread__stop = lambda x: 42 + + subprocess.call(['/usr/bin/pkill', '-u', os.environ['USER']]) + + +def renameComputer(newName): + rename(newName) + + +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)) + + +class XScreenSaverInfo(ctypes.Structure): + _fields_ = [('window', ctypes.c_long), + ('state', ctypes.c_int), + ('kind', ctypes.c_int), + ('til_or_since', ctypes.c_ulong), + ('idle', ctypes.c_ulong), + ('eventMask', ctypes.c_ulong)] + +# Initialize xlib & xss +try: + xlibPath = ctypes.util.find_library('X11') + xssPath = ctypes.util.find_library('Xss') + xlib = ctypes.cdll.LoadLibrary(xlibPath) + xss = ctypes.cdll.LoadLibrary(xssPath) + + # Fix result type to XScreenSaverInfo Structure + xss.XScreenSaverQueryExtension.restype = ctypes.c_int + xss.XScreenSaverAllocInfo.restype = ctypes.POINTER(XScreenSaverInfo) # Result in a XScreenSaverInfo structure +except Exception: # Libraries not accesible, not found or whatever.. + xlib = xss = None + + +def initIdleDuration(atLeastSeconds): + ''' + On linux we set the screensaver to at least required seconds, or we never will get "idle" + ''' + # Workaround for dummy thread + if six.PY3 is False: + import threading + threading._DummyThread._Thread__stop = lambda x: 42 + + subprocess.call(['/usr/bin/xset', 's', '{}'.format(atLeastSeconds + 30)]) + # And now reset it + subprocess.call(['/usr/bin/xset', 's', 'reset']) + + +def getIdleDuration(): + ''' + Returns idle duration, in seconds + ''' + if xlib is None or xss is None: + return 0 # Libraries not available + + # production code might want to not hardcode the offset 16... + display = xlib.XOpenDisplay(None) + + event_base = ctypes.c_int() + error_base = ctypes.c_int() + + available = xss.XScreenSaverQueryExtension(display, ctypes.byref(event_base), ctypes.byref(error_base)) + if available != 1: + return 0 # No screen saver is available, no way of getting idle + + info = xss.XScreenSaverAllocInfo() + xss.XScreenSaverQueryInfo(display, xlib.XDefaultRootWindow(display), info) + + if info.contents.state != 0: + return 3600 * 100 * 1000 # If screen saver is active, return a high enough value + + return info.contents.idle / 1000.0 + + +def getCurrentUser(): + ''' + Returns current logged in user + ''' + return os.environ['USER'] + + +def getSessionLanguage(): + ''' + Returns the user's session language + ''' + return locale.getdefaultlocale()[0] + + +def showPopup(title, message): + ''' + Displays a message box on user's session (during 1 min). + ''' + return subprocess.call('zenity --info --timeout 60 --title "{}" --text "{}"'.format(title, message), shell=True) + + +def get_etc_path(): + """ + :return: + Returns etc directory path. + """ + return os.sep + 'etc' diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/renamer/__init__.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/renamer/__init__.py new file mode 100644 index 0000000..27198dc --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/renamer/__init__.py @@ -0,0 +1,61 @@ +# -*- 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 opengnsys.log import logger + +renamers = {} + + +# Renamers now are for IPv4 only addresses +def rename(newName): + distribution = platform.linux_distribution()[0].lower().strip() + if distribution in renamers: + return renamers[distribution](newName) + + # Try Debian renamer, simplest one + logger.info('Renamer for platform "{0}" not found, tryin debian renamer'.format(distribution)) + return renamers['debian'](newName) + + +# 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/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/renamer/debian.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/renamer/debian.py new file mode 100644 index 0000000..be55e00 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/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 opengnsys.linux.renamer import renamers +from opengnsys.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/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/renamer/opensuse.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/renamer/opensuse.py new file mode 100644 index 0000000..a2d29a5 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/renamer/opensuse.py @@ -0,0 +1,66 @@ +# -*- 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 opengnsys.linux.renamer import renamers +from opengnsys.log import logger + +import os + + +def rename(newName): + ''' + RH, Centos, Fedora Renamer + Expects new host name on newName + Host does not needs to be rebooted after renaming + ''' + logger.debug('using SUSE 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{}\n".format(newName)) + for l in lines: + if l[:9] != '127.0.1.1': # Skips existing 127.0.1.1. if it already exists + hosts.write(l) + + return True + +# All names in lower case +renamers['opensuse'] = rename +renamers['suse'] = rename diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/renamer/redhat.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/renamer/redhat.py new file mode 100644 index 0000000..8821a81 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/linux/renamer/redhat.py @@ -0,0 +1,74 @@ +# -*- 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 opengnsys.linux.renamer import renamers +from opengnsys.log import logger + +import os + + +def rename(newName): + ''' + RH, Centos, Fedora Renamer + Expects new host name on newName + Host does not needs to be rebooted after renaming + ''' + logger.debug('using RH 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{}\n".format(newName)) + for l in lines: + if l[:9] != '127.0.1.1': # Skips existing 127.0.1.1. if it already exists + hosts.write(l) + + with open('/etc/sysconfig/network', 'r') as net: + lines = net.readlines() + with open('/etc/sysconfig/network', 'w') as net: + net.write('HOSTNAME={}\n'.format(newName)) + for l in lines: + if l[:8] != 'HOSTNAME': + net.write(l) + + return True + +# All names in lower case +renamers['centos linux'] = rename +renamers['fedora'] = rename diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/loader.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/loader.py new file mode 100644 index 0000000..23988fc --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/loader.py @@ -0,0 +1,111 @@ +# -*- 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 + +# This is a simple module loader, so we can add "external opengnsys" modules as addons +# Modules under "opengsnsys/modules" are always autoloaded +from __future__ import unicode_literals + +import pkgutil +import os.path + +from opengnsys.workers import ServerWorker +from opengnsys.workers import ClientWorker +from .log import logger + + +def loadModules(controller, client=False): + ''' + Load own provided modules plus the modules that are in the configuration path. + The loading order is not defined (they are loaded as found, because modules MUST be "standalone" modules + @param service: The service that: + * Holds the configuration + * Will be used to initialize modules. + ''' + + ogModules = [] + + if client is False: + from opengnsys.modules.server import OpenGnSys # @UnusedImport + from .modules import server # @UnusedImport, just used to ensure opengnsys modules are initialized + modPath = 'opengnsys.modules.server' + modType = ServerWorker + else: + from opengnsys.modules.client import OpenGnSys # @UnusedImport @Reimport + from .modules import client # @UnusedImport, just used to ensure opengnsys modules are initialized + modPath = 'opengnsys.modules.client' + modType = ClientWorker + + def addCls(cls): + logger.debug('Found module class {}'.format(cls)) + try: + if cls.name is None: + # Error, cls has no name + # Log the issue and + logger.error('Class {} has no name attribute'.format(cls)) + return + ogModules.append(cls(controller)) + except Exception as e: + logger.error('Error loading module {}'.format(e)) + + def recursiveAdd(p): + subcls = p.__subclasses__() + + if len(subcls) == 0: + addCls(p) + else: + for c in subcls: + recursiveAdd(c) + + def doLoad(paths): + for (module_loader, name, ispkg) in pkgutil.iter_modules(paths, modPath + '.'): + if ispkg: + logger.debug('Found module package {}'.format(name)) + module_loader.find_module(name).load_module(name) + + + if controller.config.has_option('opengnsys', 'path') is True: + paths = tuple(os.path.abspath(v) for v in controller.config.get('opengnsys', 'path').split(',')) + else: + paths = () + + # paths += (os.path.dirname(sys.modules[modPath].__file__),) + + logger.debug('Loading modules from {}'.format(paths)) + + # Load modules + doLoad(paths) + + # Add to list of available modules + recursiveAdd(modType) + + return ogModules diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/log.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/log.py new file mode 100644 index 0000000..e34c087 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/log.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 __future__ import unicode_literals + +import traceback +import sys +import six + +if sys.platform == 'win32': + from opengnsys.windows.log import LocalLogger # @UnusedImport +else: + from opengnsys.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 + +_levelName = { + 'OTHER': OTHER, + 'DEBUG': DEBUG, + 'INFO': INFO, + 'WARN': WARN, + 'ERROR': ERROR, + 'FATAL': FATAL +} + +class Logger(object): + def __init__(self): + self.logLevel = INFO + self.logger = LocalLogger() + + def setLevel(self, level): + ''' + Sets log level filter (minimum level required for a log message to be processed) + :param level: Any message with a level below this will be filtered out + ''' + if isinstance(level, six.string_types): + level = _levelName.get(level, INFO) + + self.logLevel = level # Ensures level is an integer or fails + + def log(self, level, message): + if level < self.logLevel: # Skip not wanted messages + return + + 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(INFO, message) + + def error(self, message): + self.log(ERROR, message) + + def fatal(self, message): + self.log(FATAL, message) + + def exception(self): + try: + tb = traceback.format_exc() + except Exception: + tb = '(could not get traceback!)' + + self.log(DEBUG, tb) + + def flush(self): + pass + + +logger = Logger() diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/macos/__init__.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/macos/__init__.py new file mode 100644 index 0000000..ee5ba4c --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/macos/__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: Ramón M. Gómez, ramongomez at us dot es +''' +from __future__ import unicode_literals diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/macos/operations.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/macos/operations.py new file mode 100644 index 0000000..286b6d8 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/macos/operations.py @@ -0,0 +1,263 @@ +# -*- 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 locale +import ctypes # @UnusedImport +import ctypes.util +import subprocess +import struct +import array +import six +from opengnsys import utils +import netifaces + + +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: + return netifaces.ifaddresses(ifname)[18][0]['addr'] + 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: + return netifaces.ifaddresses(ifname)[2][0]['addr'] + except Exception: + return None + + +def _getInterfaces(): + ''' + Returns a list of interfaces names + ''' + return netifaces.interfaces() + + +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(): + ''' + Obtains a list of network interfaces + @return: A "generator" of elements, that are dict-as-object, with this elements: + name: Name of the interface + mac: mac of the interface + ip: ip of the interface + ''' + for ifname in _getInterfaces(): + ip, mac = _getIpAndMac(ifname) + if mac != None and ip != None: # Skips local interfaces + yield utils.Bunch(name=ifname, mac=mac, ip=ip) + + +def getDomainName(): + return '' + + +def getMacosVersion(): + return 'macOS {}'.format(platform.mac_ver()[0]) + + +def reboot(flags=0): + ''' + Simple reboot command + ''' + # Workaround for dummy thread + if six.PY3 is False: + import threading + threading._DummyThread._Thread__stop = lambda x: 42 + + # Exec reboot command + subprocess.call('/sbin/shutdown -r now', shell=True) + + +def poweroff(flags=0): + ''' + Simple poweroff command + ''' + # Workaround for dummy thread + if six.PY3 is False: + import threading + threading._DummyThread._Thread__stop = lambda x: 42 + + # Exec shutdown command + subprocess.call('/sbin/shutdown -h now', shell=True) + + +def logoff(): + ''' + Simple logout using AppleScript + ''' + # Workaround for dummy thread + if six.PY3 is False: + import threading + threading._DummyThread._Thread__stop = lambda x: 42 + + # Exec logout using AppleSctipt + subprocess.call('/usr/bin/osascript -e \'tell app "System Events" to «event aevtrlgo»\'', shell=True) + + +def renameComputer(newName): + rename(newName) + + +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)) + + +class XScreenSaverInfo(ctypes.Structure): + _fields_ = [('window', ctypes.c_long), + ('state', ctypes.c_int), + ('kind', ctypes.c_int), + ('til_or_since', ctypes.c_ulong), + ('idle', ctypes.c_ulong), + ('eventMask', ctypes.c_ulong)] + +# Initialize xlib & xss +try: + xlibPath = ctypes.util.find_library('X11') + xssPath = ctypes.util.find_library('Xss') + xlib = ctypes.cdll.LoadLibrary(xlibPath) + xss = ctypes.cdll.LoadLibrary(xssPath) + + # Fix result type to XScreenSaverInfo Structure + xss.XScreenSaverQueryExtension.restype = ctypes.c_int + xss.XScreenSaverAllocInfo.restype = ctypes.POINTER(XScreenSaverInfo) # Result in a XScreenSaverInfo structure +except Exception: # Libraries not accesible, not found or whatever.. + xlib = xss = None + + +def initIdleDuration(atLeastSeconds): + ''' + On linux we set the screensaver to at least required seconds, or we never will get "idle" + ''' + # Workaround for dummy thread + if six.PY3 is False: + import threading + threading._DummyThread._Thread__stop = lambda x: 42 + + subprocess.call(['/usr/bin/xset', 's', '{}'.format(atLeastSeconds + 30)]) + # And now reset it + subprocess.call(['/usr/bin/xset', 's', 'reset']) + + +def getIdleDuration(): + ''' + Returns idle duration, in seconds + ''' + if xlib is None or xss is None: + return 0 # Libraries not available + + # production code might want to not hardcode the offset 16... + display = xlib.XOpenDisplay(None) + + event_base = ctypes.c_int() + error_base = ctypes.c_int() + + available = xss.XScreenSaverQueryExtension(display, ctypes.byref(event_base), ctypes.byref(error_base)) + if available != 1: + return 0 # No screen saver is available, no way of getting idle + + info = xss.XScreenSaverAllocInfo() + xss.XScreenSaverQueryInfo(display, xlib.XDefaultRootWindow(display), info) + + if info.contents.state != 0: + return 3600 * 100 * 1000 # If screen saver is active, return a high enough value + + return info.contents.idle / 1000.0 + + +def getCurrentUser(): + ''' + Returns current logged in user + ''' + return os.environ['USER'] + + +def getSessionLanguage(): + ''' + Returns the user's session language + ''' + return locale.getdefaultlocale()[0] + + +def showPopup(title, message): + ''' + Displays a message box on user's session (during 1 min). + ''' + # Show a dialog using AppleSctipt + return subprocess.call('/usr/bin/osascript -e \'display notification "{}" with title "{}"\''.format(message, title), shell=True) + + +def get_etc_path(): + """ + :return: + Returns etc directory path. + """ + return os.sep + 'etc' diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/modules/__init__.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/modules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/modules/client/OpenGnSys/__init__.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/modules/client/OpenGnSys/__init__.py new file mode 100644 index 0000000..b840d92 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/modules/client/OpenGnSys/__init__.py @@ -0,0 +1,63 @@ +# -*- 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: Ramón M. Gómez, ramongomez at us dot es +''' +from __future__ import unicode_literals + +from opengnsys.workers import ClientWorker + +from opengnsys import operations +from opengnsys.log import logger +from opengnsys.scriptThread import ScriptExecutorThread + +class OpenGnSysWorker(ClientWorker): + name = 'opengnsys' + + def onActivation(self): + logger.debug('Activate invoked') + + def onDeactivation(self): + logger.debug('Deactivate invoked') + + # Processes script execution + def process_script(self, jsonParams): + logger.debug('Processed message: script({})'.format(jsonParams)) + thr = ScriptExecutorThread(jsonParams['code']) + thr.start() + #self.sendServerMessage('script', {'op', 'launched'}) + + def process_logoff(self, jsonParams): + logger.debug('Processed message: logoff({})'.format(jsonParams)) + operations.logoff() + + def process_popup(self, jsonParams): + logger.debug('Processed message: popup({})'.format(jsonParams)) + ret = operations.showPopup(jsonParams['title'], jsonParams['message']) + #self.sendServerMessage('popup', {'op', ret}) + diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/modules/client/__init__.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/modules/client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/modules/server/OpenGnSys/__init__.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/modules/server/OpenGnSys/__init__.py new file mode 100644 index 0000000..223875a --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/modules/server/OpenGnSys/__init__.py @@ -0,0 +1,558 @@ +# -*- 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: Ramón M. Gómez, ramongomez at us dot es +""" +from __future__ import unicode_literals + +import os +import random +import shutil +import string +import subprocess +import threading +import time +import urllib +import signal + +from opengnsys import REST +from opengnsys import operations +from opengnsys.log import logger +from opengnsys.workers import ServerWorker +from six.moves.urllib import parse + + +# Check authorization header decorator +def check_secret(fnc): + """ + Decorator to check for received secret key and raise exception if it isn't valid. + """ + def wrapper(*args, **kwargs): + try: + this, path, get_params, post_params, server = args # @UnusedVariable + if this.random == server.headers['Authorization']: + fnc(*args, **kwargs) + else: + raise Exception('Unauthorized operation') + except Exception as e: + logger.error(e) + raise Exception(e) + + return wrapper + + +# Error handler decorator. +def catch_background_error(fnc): + def wrapper(*args, **kwargs): + this = args[0] + try: + fnc(*args, **kwargs) + except Exception as e: + this.REST.sendMessage('error?id={}'.format(kwargs.get('requestId', 'error')), {'error': '{}'.format(e)}) + + return wrapper + + +def check_locked_partition(sync=False): + """ + Decorator to check if a partition is locked + """ + + def outer(fnc): + def wrapper(*args, **kwargs): + part_id = 'None' + try: + this, path, get_params, post_params, server = args # @UnusedVariable + part_id = post_params['disk'] + post_params['part'] + if this.locked.get(part_id, False): + this.locked[part_id] = True + fnc(*args, **kwargs) + else: + return 'partition locked' + except Exception as e: + this.locked[part_id] = False + return 'error {}'.format(e) + finally: + if sync is True: + this.locked[part_id] = False + logger.debug('Lock status: {} {}'.format(fnc, this.locked)) + + return wrapper + + return outer + + +class OpenGnSysWorker(ServerWorker): + name = 'opengnsys' + interface = None # Bound interface for OpenGnsys + REST = None # REST object + logged_in = False # User session flag + browser = {} # Browser info + commands = [] # Running commands + random = None # Random string for secure connections + length = 32 # Random string length + access_token = refresh_token = None # Server authorization tokens + grant_type = 'http://opengnsys.es/grants/og_client' + + def _launch_browser(self, url): + """ + Launchs the Browser with specified URL + :param url: URL to show + """ + logger.debug('Launching browser with URL: {}'.format(url)) + # Trying to kill an old browser + try: + os.kill(self.browser['process'].pid, signal.SIGKILL) + except OSError: + logger.warn('Cannot kill the old browser process') + except KeyError: + # There is no previous browser + pass + self.browser['url'] = url + self.browser['process'] = subprocess.Popen(['browser', '-qws', url]) + + def _task_command(self, route, code, op_id, send_config=False): + """ + Task to execute a command and return results to a server URI + :param route: server callback REST route to return results + :param code: code to execute + :param op_id: operation id. + """ + menu_url = '' + # Show execution tacking log, if OGAgent runs on ogLive + os_type = operations.os_type.lower() + if os_type == 'oglive': + menu_url = self.browser['url'] + self._launch_browser('http://localhost/cgi-bin/httpd-log.sh') + # Execute the code + (stat, out, err) = operations.exec_command(code) + # Remove command from the list + for c in self.commands: + if c.getName() == op_id: + self.commands.remove(c) + # Remove the REST API prefix, if needed + if route.startswith(self.REST.endpoint): + route = route[len(self.REST.endpoint):] + # Send back exit status and outputs (base64-encoded) + self.REST.sendMessage(route, {'mac': self.interface.mac, 'ip': self.interface.ip, 'trace': op_id, + 'status': stat, 'output': out.encode('base64'), 'error': err.encode('base64')}) + # Show latest menu, if OGAgent runs on ogLive + if os_type == 'oglive': + # Send configuration data, if needed + if send_config: + self.REST.sendMessage('clients/configs', {'mac': self.interface.mac, 'ip': self.interface.ip, + 'config': operations.get_configuration()}) + self._launch_browser(menu_url) + + def onActivation(self): + """ + Sends OGAgent activation notification to OpenGnsys server + """ + t = 0 + # Generate random secret to send on activation + self.random = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(self.length)) + # Ensure cfg has required configuration variables or an exception will be thrown + url = self.service.config.get('opengnsys', 'remote') + server_client = self.service.config.get('opengnsys', 'client') + server_secret = self.service.config.get('opengnsys', 'secret') + if operations.os_type == 'ogLive' and 'oglive' in os.environ: + # Replacing server IP if its running on ogLive client + logger.debug('Activating on ogLive client, new server is {}'.format(os.environ['oglive'])) + url = parse.urlsplit(url)._replace(netloc=os.environ['oglive']).geturl() + if not url.endswith(os.path.sep): + url += os.path.sep + self.REST = REST(url) + # Get network interfaces until they are active or timeout (5 minutes) + for t in range(0, 300): + try: + self.interface = list(operations.getNetworkInfo())[0] # Get first network interface + except Exception as e: + # Wait 1 sec. and retry + time.sleep(1) + finally: + # Exit loop if interface is active + if self.interface: + if t > 0: + logger.debug("Fetch connection data after {} tries".format(t)) + break + # Raise error after timeout + if not self.interface: + raise e + # Compose login route + login_route = 'oauth/v2/token?client_id=' + server_client + '&client_secret=' + server_secret + \ + '&grant_type=' + self.grant_type + '&ip=' + self.interface.ip + '&mac=' + self.interface.mac + \ + '&token=' + self.random + # Send initialization login message + response = None + # Loop to send initialization message + for t in range(0, 100): + try: + try: + # New web compatibility. + self.REST = REST(self.service.config.get('opengnsys', 'remote')) + response = self.REST.sendMessage(login_route) + break + except: + # Trying to initialize on alternative server, if defined + # (used in "exam mode" from the University of Seville) + self.REST = REST(self.service.config.get('opengnsys', 'altremote')) + response = self.REST.sendMessage(login_route) + break + except: + time.sleep(3) + # Raise error after timeout or authentication failure + if 0 < t < 100: + logger.debug('Successful connection after {} tries'.format(t)) + elif t == 100: + raise Exception('Initialization error: Cannot connect to remote server') + if response['access_token'] is None: + raise Exception('Initialization error: Cannot obtain access token') + # Read access tokens + self.access_token = response['access_token'] + self.refresh_token = response['refresh_token'] + # Once authenticated with the server, change the API URL for private request + self.REST = REST(url + 'api/private') + # Set authorization tokens in the REST object, so in each request this token will be used + self.REST.set_authorization_headers(self.access_token, self.refresh_token) + # Completing OGAgent initialization process + os_type = operations.os_type.lower() + if os_type == 'oglive': + # Create HTML file (TEMPORARY) + message = """ + + + + +

+ OpenGnsys 3 +
+

+ + +""" + f = open('/tmp/init.html', 'w') + f.write(message) + f.close() + # Launching Browser + self._launch_browser('/tmp/init.html') + self.REST.sendMessage('clients/statuses', {'mac': self.interface.mac, 'ip': self.interface.ip, + 'status': 'initializing'}) + # Send configuration message + self.REST.sendMessage('clients/configs', {'mac': self.interface.mac, 'ip': self.interface.ip, + 'config': operations.get_configuration()}) + # Launching new Browser with client's menu + # menu_url = self.REST.sendMessage('menus?mac' + self.interface.mac + '&ip=' + self.interface.ip) + menu_url = '/opt/opengnsys/log/' + self.interface.ip + '.info.html' # TEMPORARY menu + self._launch_browser(menu_url) + else: + # Delete marking files + for f in ['ogboot.me', 'ogboot.firstboot', 'ogboot.secondboot']: + try: + os.remove(os.sep + f) + except OSError: + pass + # Copy file "HostsFile.FirstOctetOfIPAddress" to "HostsFile", if it exists + # (used in "exam mode" from the University of Seville) + hosts_file = os.path.join(operations.get_etc_path(), 'hosts') + new_file = hosts_file + '.' + self.interface.ip.split('.')[0] + if os.path.isfile(new_file): + shutil.copy2(new_file, hosts_file) + # Return status message + self.REST.sendMessage('clients/statuses', {'mac': self.interface.mac, 'ip': self.interface.ip, + 'status': os_type}) + + def onDeactivation(self): + """ + Sends OGAgent stopping notification to OpenGnsys server + """ + logger.debug('onDeactivation') + self.REST.sendMessage('clients/statuses', {'mac': self.interface.mac, 'ip': self.interface.ip, + 'ostype': operations.os_type, 'osversion': operations.os_version, + 'status': 'off'}) + + def processClientMessage(self, message, data): + logger.debug('Got OpenGnsys message from client: {}, data {}'.format(message, data)) + + def onLogin(self, data): + """ + Sends session login notification to OpenGnsys server + """ + user, sep, language = data.partition(',') + logger.debug('Received login for {} with language {}'.format(user, language)) + self.logged_in = True + self.REST.sendMessage('ogagent/loggedin', {'ip': self.interface.ip, 'user': user, 'language': language, + 'ostype': operations.os_type, 'osversion': operations.os_version}) + + def onLogout(self, user): + """ + Sends session logout notification to OpenGnsys server + """ + logger.debug('Received logout for {}'.format(user)) + self.logged_in = False + self.REST.sendMessage('ogagent/loggedout', {'ip': self.interface.ip, 'user': user}) + + def process_ogclient(self, path, get_params, post_params, server): + """ + This method can be overridden to provide your own message processor, or better you can + implement a method that is called exactly as "process_" + path[0] (module name has been removed from path + array) and this default processMessage will invoke it + * Example: + Imagine this invocation url (no matter if GET or POST): http://example.com:9999/Sample/mazinger/Z + The HTTP Server will remove "Sample" from path, parse arguments and invoke this method as this: + module.processMessage(["mazinger","Z"], get_params, post_params) + + This method will process "mazinger", and look for a "self" method that is called "process_mazinger", + and invoke it this way: + return self.process_mazinger(["Z"], get_params, post_params) + + In the case path is empty (that is, the path is composed only by the module name, like in + "http://example.com/Sample", the "process" method will be invoked directly + + The methods must return data that can be serialized to json (i.e. Objects are not serializable to json, + basic type are) + """ + if not path: + return "ok" + try: + operation = getattr(self, 'ogclient_' + path[0]) + except Exception: + raise Exception('Message processor for "{}" not found'.format(path[0])) + return operation(path[1:], get_params, post_params) + + def process_status(self, path, get_params, post_params, server): + """ + Returns client status (OS type or execution status) and login status + :param path: + :param get_params: + :param post_params: + :param server: + :return: JSON object {"status": "status_code", "loggedin": boolean} + """ + res = {'loggedin': self.logged_in} + try: + res['status'] = operations.os_type.lower() + except KeyError: + res['status'] = '' + # Check if OpenGnsys Client is busy + if res['status'] == 'oglive' and len(self.commands) > 0: + res['status'] = 'busy' + return res + + @check_secret + def process_reboot(self, path, get_params, post_params, server): + """ + Launches a system reboot operation + :param path: + :param get_params: + :param post_params: + :param server: authorization header + :return: JSON object {"op": "launched"} + """ + logger.debug('Received reboot operation') + + # Rebooting thread + def rebt(): + operations.reboot() + + threading.Thread(target=rebt).start() + return {'op': 'launched'} + + @check_secret + def process_poweroff(self, path, get_params, post_params, server): + """ + Launches a system power off operation + :param path: + :param get_params: + :param post_params: + :param server: authorization header + :return: JSON object {"op": "launched"} + """ + logger.debug('Received poweroff operation') + + # Powering off thread + def pwoff(): + time.sleep(2) + operations.poweroff() + + threading.Thread(target=pwoff).start() + return {'op': 'launched'} + + @check_secret + def process_script(self, path, get_params, post_params, server): + """ + Processes an script execution (script should be encoded in base64) + :param path: + :param get_params: + :param post_params: JSON object {"redirect_uri, "uri", "script": "commands", "id": trace_id} + :param server: authorization header + :return: JSON object {"op": "launched"} or {"error": "message"} + """ + logger.debug('Processing script request') + # Processing data + try: + script = urllib.unquote(post_params.get('script').decode('base64')).decode('utf8') + op_id = post_params.get('id') + route = post_params.get('redirectUri') + send_config = (post_params.get('sendConfig', 'false') == 'true') + # Check if the thread id. exists + for c in self.commands: + if c.getName() == str(op_id): + raise Exception('Task id. already exists: {}'.format(op_id)) + if post_params.get('client', 'false') == 'false': + # Launching a new thread + thr = threading.Thread(name=op_id, target=self._task_command, args=(route, script, op_id, send_config)) + thr.start() + self.commands.append(thr) + else: + # Executing as normal user + self.sendClientMessage('script', {'code': script}) + except Exception as e: + logger.error('Got exception {}'.format(e)) + return {'error': e} + return {'op': 'launched'} + + @check_secret + def process_logoff(self, path, get_params, post_params, server): + """ + Closes user session + """ + logger.debug('Received logoff operation') + # Send log off message to OGAgent client + self.sendClientMessage('logoff', {}) + return {'op': 'sent to client'} + + @check_secret + def process_popup(self, path, get_params, post_params, server): + """ + Shows a message popup on the user's session + """ + logger.debug('Received message operation') + # Send popup message to OGAgent client + self.sendClientMessage('popup', post_params) + return {'op': 'launched'} + + def process_client_popup(self, params): + self.REST.sendMessage('popup_done', params) + + @check_secret + def process_config(self, path, get_params, post_params, server): + """ + Returns client configuration + :param path: + :param get_params: + :param post_params: + :param server: authorization header + :return: JSON object + """ + serialno = '' # Serial number + storage = [] # Storage configuration + warnings = 0 # Number of warnings + logger.debug('Received getconfig operation') + # Processing data + for row in operations.get_configuration().split(';'): + cols = row.split(':') + if len(cols) == 1: + if cols[0] != '': + # Serial number + serialno = cols[0] + else: + # Skip blank rows + pass + elif len(cols) == 7: + disk, npart, tpart, fs, opsys, size, usage = cols + try: + if int(npart) == 0: + # Disk information + storage.append({'disk': int(disk), 'parttable': int(tpart), 'size': int(size)}) + else: + # Partition information + storage.append({'disk': int(disk), 'partition': int(npart), 'parttype': tpart, + 'filesystem': fs, 'operatingsystem': opsys, 'size': int(size), + 'usage': int(usage)}) + except ValueError: + logger.warn('Configuration parameter error: {}'.format(cols)) + warnings += 1 + else: + # Log warnings + logger.warn('Configuration data error: {}'.format(cols)) + warnings += 1 + # Return configuration data and count of warnings + return {'serialno': serialno, 'storage': storage, 'warnings': warnings} + + @check_secret + def process_execinfo(self, path, get_params, post_params, server): + """ + Returns running commands information + :param path: + :param get_params: + :param post_params: + :param server: authorization header + :return: JSON array: [["callback_url", "commands", trace_id], ...] + """ + data = [] + logger.debug('Received execinfo operation') + # Return the arguments of all running threads + for c in self.commands: + if c.is_alive(): + data.append(c.__dict__['_Thread__args']) + return data + + @check_secret + def process_stopcmd(self, path, get_params, post_params, server): + """ + Stops a running process identified by its trace id. + :param path: + :param get_params: + :param post_params: JSON object {"trace": trace_id} + :param server: authorization header + :return: JSON object: {"stopped": trace_id} + """ + logger.debug('Received stopcmd operation with params {}:'.format(post_params)) + # Find operation id. and stop the thread + op_id = post_params.get('trace') + for c in self.commands: + if c.is_alive() and c.getName() == str(op_id): + c._Thread__stop() + return {"stopped": op_id} + return {} diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/modules/server/__init__.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/modules/server/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/oglive/__init__.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/oglive/__init__.py new file mode 100644 index 0000000..3a98c78 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/oglive/__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/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/oglive/daemon.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/oglive/daemon.py new file mode 100644 index 0000000..3753808 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/oglive/daemon.py @@ -0,0 +1,182 @@ +# -*- 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 opengnsys.log import logger + +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: + logger.error("fork #1 error: {}".format(e)) + sys.stderr.write("fork #1 failed: {}\n".format(e)) + 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: + logger.error("fork #2 error: {}".format(e)) + sys.stderr.write("fork #2 failed: {}\n".format(e)) + 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): + try: + os.remove(self.pidfile) + except Exception: + # Not found/not permissions or whatever... + pass + + def start(self): + """ + Start the daemon + """ + logger.debug('Starting daemon') + # Check for a pidfile to see if the daemon already runs + try: + pf = open(self.pidfile, 'r') + pid = int(pf.read().strip()) + pf.close() + except IOError: + pid = None + + if pid: + message = "pidfile {} already exist. Daemon already running?\n".format(pid) + logger.error(message) + sys.stderr.write(message) + sys.exit(1) + + # Start the daemon + self.daemonize() + try: + self.run() + except Exception as e: + logger.error('Exception running process: {}'.format(e)) + + if os.path.exists(self.pidfile): + os.remove(self.pidfile) + + 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: + message = "pidfile {} does not exist. Daemon not running?\n".format(self.pidfile) + logger.info(message) + # sys.stderr.write(message) + return # not an error in a restart + + # Try killing the daemon process + try: + for i in range(10): + 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/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/oglive/operations.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/oglive/operations.py new file mode 100644 index 0000000..8c74f74 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/oglive/operations.py @@ -0,0 +1,258 @@ +# -*- 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: Ramón M. Gómez, ramongomez at us dot es +""" +from __future__ import unicode_literals + +import socket +import platform +import os +import fcntl +import subprocess +import struct +import array +import six +import chardet +from opengnsys import utils +from opengnsys.log import logger + + +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(str('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(str('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(str('iL'), fcntl.ioctl( + s.fileno(), + 0x8912, # SIOCGIFCONF + struct.pack(str('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 _exec_ogcommand(ogcmd): + """ + Loads OpenGnsys environment variables, executes the command and returns the result + """ + ret = subprocess.check_output(ogcmd, shell=True) + return ret + + +def getComputerName(): + """ + Returns computer name, with no domain + """ + return socket.gethostname().split('.')[0] + + +def getNetworkInfo(): + """ + Obtains a list of network interfaces + :return: A "generator" of elements, that are dict-as-object, with this elements: + name: Name of the interface + mac: mac of the interface + ip: ip of the interface + """ + for ifname in _getInterfaces(): + ip, mac = _getIpAndMac(ifname) + if mac != '00:00:00:00:00:00': # Skips local interfaces + yield utils.Bunch(name=ifname, mac=mac, ip=ip) + + +def getDomainName(): + return '' + + +def get_oglive_version(): + """ + Returns ogLive Kernel version and architecture + :return: kernel version + """ + kv = platform.os.uname() + return kv[2] + ', ' + kv[4] + + +def reboot(): + """ + Simple reboot using OpenGnsys script + """ + # Workaround for dummy thread + if six.PY3 is False: + import threading + threading._DummyThread._Thread__stop = lambda x: 42 + + _exec_ogcommand('/opt/opengnsys/scripts/reboot') + + +def poweroff(): + """ + Simple power off using OpenGnsys script + """ + # Workaround for dummy thread + if six.PY3 is False: + import threading + threading._DummyThread._Thread__stop = lambda x: 42 + + _exec_ogcommand('/opt/opengnsys/scripts/poweroff') + + +def get_etc_path(): + """ + Returns etc directory path. + """ + return os.sep + 'etc' + + +def get_configuration(): + """ + Returns client's configuration + Warning: this operation may take some time + :return: + """ + try: + _exec_ogcommand('/opt/opengnsys/interfaceAdm/getConfiguration') + # Returns content of configuration file + cfgdata = open('/tmp/getconfig', 'r').read().strip() + except IOError: + cfgdata = '' + return cfgdata + + +def exec_command(cmd): + """ + Executing a shell command + :param cmd: + :return: object with components: + output: standard output + error: error output + exit: exit code + """ + proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (out, err) = proc.communicate() + try: + if out is not None: + encoding = chardet.detect(out)["encoding"] + if encoding is not None: + out = out.decode(encoding).encode("utf8") + if err is not None: + encoding = chardet.detect(err)["encoding"] + if encoding is not None: + err = err.decode(encoding).encode("utf8") + except Exception as e: + logger.debug("ERROR EXEC COMMAND: {}".format(str(e))) + + stat = proc.returncode + return stat, out, err + + +def get_hardware(): + """ + Returns client's hardware list + :return: + """ + try: + filepath = _exec_ogcommand('/opt/opengnsys/scripts/listHardwareInfo').strip() + # Returns content of configuration file, skipping the header line and newline characters + with open(filepath, 'r') as f: + harddata = map(str.strip, f.readlines()[1:]) + except IOError: + harddata = '' + return harddata + + +def get_software(disk, part): + """ + Returns software list installed on an operating system + :param disk: + :param part: + :return: + """ + try: + filepath = _exec_ogcommand('/opt/opengnsys/scripts/listSoftwareInfo {} {}'.format(disk, part)).strip() + # Returns content of configuration file, skipping the header line and newline characters + with open(filepath, 'r') as f: + softdata = map(str.strip, f.readlines()) + except IOError: + softdata = '' + return softdata diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/operations.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/operations.py new file mode 100644 index 0000000..a81d0c0 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/operations.py @@ -0,0 +1,57 @@ +# -*- 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: Ramón M. Gómez, ramongomez at us dot es +""" +# pylint: disable=unused-wildcard-import,wildcard-import + +from __future__ import unicode_literals + +import sys +import os + +# Importing platform operations and getting operating system data. +if sys.platform == 'win32': + from .windows.operations import * # @UnusedWildImport + os_type = 'Windows' + os_version = getWindowsVersion() +else: + if sys.platform == 'darwin': + from .macos.operations import * # @UnusedWildImport + os_type = 'MacOS' + os_version = getMacosVersion().replace(',', '') + else: + if os.path.exists('/scripts/oginit'): + from .oglive.operations import * # @UnusedWildImport + os_type = 'ogLive' + os_version = get_oglive_version().replace(',', '') + else: + from .linux.operations import * # @UnusedWildImport + os_type = 'Linux' + os_version = getLinuxVersion().replace(',', '') diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/scriptThread.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/scriptThread.py new file mode 100644 index 0000000..2a6779b --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/scriptThread.py @@ -0,0 +1,51 @@ +# -*- 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 opengnsys.log import logger + +import threading +import six + + +class ScriptExecutorThread(threading.Thread): + def __init__(self, script): + super(ScriptExecutorThread, self).__init__() + self.script = script + + def run(self): + try: + logger.debug('Executing script: {}'.format(self.script)) + six.exec_(self.script, globals(), None) + except Exception as e: + logger.error('Error executing script: {}'.format(e)) diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/service.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/service.py new file mode 100644 index 0000000..4a44028 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/service.py @@ -0,0 +1,249 @@ +# -*- 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 .log import logger +from .config import readConfig +from .utils import exceptionToMessage + +from . import ipc +from . import httpserver +from .loader import loadModules + +import socket +import time +import json +import six + +IPC_PORT = 10398 + + +class CommonService(object): + isAlive = True + ipc = None + httpServer = None + modules = None + + def __init__(self): + logger.info('----------------------------------------') + logger.info('Initializing OpenGnsys Agent') + + # Read configuration file before proceding & ensures minimal config is there + + self.config = readConfig() + + # Get opengnsys section as dict + cfg = dict(self.config.items('opengnsys')) + + # Set up log level + logger.setLevel(cfg.get('log', 'INFO')) + + + logger.debug('Loaded configuration from opengnsys.cfg:') + for section in self.config.sections(): + logger.debug('Section {} = {}'.format(section, self.config.items(section))) + + + if logger.logger.isWindows(): + # Logs will also go to windows event log for services + logger.logger.serviceLogger = True + + self.address = (cfg.get('address', '0.0.0.0'), int(cfg.get('port', '10997'))) + self.ipcport = int(cfg.get('ipc_port', IPC_PORT)) + + self.timeout = int(cfg.get('timeout', '20')) + + logger.debug('Socket timeout: {}'.format(self.timeout)) + socket.setdefaulttimeout(self.timeout) + + # Now load modules + self.modules = loadModules(self) + logger.debug('Modules: {}'.format(list(v.name for v in self.modules))) + + def stop(self): + ''' + Requests service termination + ''' + self.isAlive = False + + # ******************************** + # * Internal messages processors * + # ******************************** + def notifyLogin(self, username): + for v in self.modules: + try: + logger.debug('Notifying login of user {} to module {}'.format(username, v.name)) + v.onLogin(username) + except Exception as e: + logger.error('Got exception {} processing login message on {}'.format(e, v.name)) + + def notifyLogout(self, username): + for v in self.modules: + try: + logger.debug('Notifying logout of user {} to module {}'.format(username, v.name)) + v.onLogout(username) + except Exception as e: + logger.error('Got exception {} processing logout message on {}'.format(e, v.name)) + + def notifyMessage(self, data): + module, message, data = data.split('\0') + for v in self.modules: + if v.name == module: # Case Sensitive!!!! + try: + logger.debug('Notifying message {} to module {} with json data {}'.format(message, v.name, data)) + v.processClientMessage(message, json.loads(data)) + return + except Exception as e: + logger.error('Got exception {} processing generic message on {}'.format(e, v.name)) + + logger.error('Module {} not found, messsage {} not sent'.format(module, message)) + + + def clientMessageProcessor(self, msg, data): + ''' + Callback, invoked from IPC, on its own thread (not the main thread). + This thread will "block" communication with agent untill finished, but this should be no problem + ''' + logger.debug('Got message {}'.format(msg)) + + if msg == ipc.REQ_LOGIN: + self.notifyLogin(data) + elif msg == ipc.REQ_LOGOUT: + self.notifyLogout(data) + elif msg == ipc.REQ_MESSAGE: + self.notifyMessage(data) + + def initialize(self): + # ****************************************** + # * Initialize listeners, modules, etc... + # ****************************************** + + if six.PY3 is False: + import threading + threading._DummyThread._Thread__stop = lambda x: 42 + + logger.debug('Starting IPC listener at {}'.format(IPC_PORT)) + self.ipc = ipc.ServerIPC(self.ipcport, clientMessageProcessor=self.clientMessageProcessor) + self.ipc.start() + + # And http threaded server + self.httpServer = httpserver.HTTPServerThread(self.address, self) + self.httpServer.start() + + # And lastly invoke modules activation + validMods = [] + for mod in self.modules: + try: + logger.debug('Activating module {}'.format(mod.name)) + mod.activate() + validMods.append(mod) + except Exception as e: + logger.exception() + logger.error("Activation of {} failed: {}".format(mod.name, exceptionToMessage(e))) + + self.modules[:] = validMods # copy instead of assignment + + logger.debug('Modules after activation: {}'.format(list(v.name for v in self.modules))) + + def terminate(self): + # First invoke deactivate on modules + for mod in reversed(self.modules): + try: + logger.debug('Deactivating module {}'.format(mod.name)) + mod.deactivate() + except Exception as e: + logger.exception() + logger.error("Deactivation of {} failed: {}".format(mod.name, exceptionToMessage(e))) + + # Remove IPC threads + if self.ipc is not None: + try: + self.ipc.stop() + except Exception: + logger.error('Couln\'t stop ipc server') + + if self.httpServer is not None: + try: + self.httpServer.stop() + except Exception: + logger.error('Couln\'t stop RESTApi server') + + self.notifyStop() + + # **************************************** + # Methods that CAN BE overridden by agents + # **************************************** + def doWait(self, miliseconds): + ''' + Invoked to wait a bit + CAN be OVERRIDDEN + ''' + time.sleep(float(miliseconds) / 1000) + + def notifyStop(self): + ''' + Overridden to log stop + ''' + logger.info('Service is being stopped') + + # *************************************************** + # * Helpers, convenient methods to facilitate comms * + # *************************************************** + def sendClientMessage(self, toModule, message, data): + ''' + Sends a message to the clients using IPC + The data is converted to json, so ensure that it is serializable. + All IPC is asynchronous, so if you expect a response, this will be sent by client using another message + + @param toModule: Module that will receive this message + @param message: Message to send + @param data: data to send + ''' + self.ipc.sendMessageMessage('\0'.join((toModule, message, json.dumps(data)))) + + def sendScriptMessage(self, script): + ''' + Sends an script to be executed by client + ''' + self.ipc.sendScriptMessage(script) + + def sendLogoffMessage(self): + ''' + Sends a logoff message to client + ''' + self.ipc.sendLoggofMessage() + + def sendPopupMessage(self, title, message): + ''' + Sends a poup box to be displayed by client + ''' + self.ipc.sendPopupMessage(title, message) diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/utils.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/utils.py new file mode 100644 index 0000000..9480a6a --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/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/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/windows/OGAgentService.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/windows/OGAgentService.py new file mode 100644 index 0000000..71716d7 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/windows/OGAgentService.py @@ -0,0 +1,124 @@ +# -*- 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 win32security # @UnresolvedImport, pylint: disable=import-error +import win32net # @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 +import os + +from opengnsys import operations +from opengnsys.service import CommonService + +from opengnsys.log import logger + +class OGAgentSvc(win32serviceutil.ServiceFramework, CommonService): + ''' + This class represents a Windows Service for managing Agent interactions + with OpenGnsys Server + ''' + _svc_name_ = "OGAgent" + _svc_display_name_ = "OpenGnsys Agent Service" + _svc_description_ = "OpenGnsys Agent for Operating Systems" + # 'System Event Notification' is the SENS service + _svc_deps_ = ['EventLog'] + + def __init__(self, args): + win32serviceutil.ServiceFramework.__init__(self, args) + CommonService.__init__(self) + self.hWaitStop = win32event.CreateEvent(None, 1, 0, None) + self._user = 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 SvcDoRun(self): + ''' + Main service loop + ''' + try: + 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() + + # Initialize remaining service data + self.initialize() + except Exception: # Any init exception wil be caught, service must be then restarted + logger.exception() + logger.debug('Exiting service with failure status') + os._exit(-1) # pylint: disable=protected-access + + # ********************* + # * Main Service loop * + # ********************* + try: + while self.isAlive: + # Pumps & processes any waiting messages + pythoncom.PumpWaitingMessages() + win32event.WaitForSingleObject(self.hWaitStop, 1000) + except Exception as e: + logger.error('Caught exception on main loop: {}'.format(e)) + + logger.debug('Exited main loop, deregistering SENS') + + self.terminate() # Ends IPC servers + + self.notifyStop() + + +if __name__ == '__main__': + + win32serviceutil.HandleCommandLine(OGAgentSvc) diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/windows/__init__.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/windows/__init__.py new file mode 100644 index 0000000..e662942 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/windows/__init__.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 +''' +from __future__ import unicode_literals + +import os +import sys + +# Change to application directory. +os.chdir(os.path.dirname(sys.argv[0])) + diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/windows/log.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/windows/log.py new file mode 100644 index 0000000..745fd03 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/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 range(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(), 'opengnsys.log'), + filemode='a', + format='%(levelname)s %(asctime)s %(message)s', + level=logging.DEBUG + ) + self.logger = logging.getLogger('opengnsys') + 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/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/windows/operations.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/windows/operations.py new file mode 100644 index 0000000..95331fd --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/windows/operations.py @@ -0,0 +1,269 @@ +# -*- 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 os +import locale +import subprocess +import ctypes +from ctypes.wintypes import DWORD, LPCWSTR +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 + +from opengnsys import utils +from opengnsys.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(): + ''' + Obtains a list of network interfaces + @return: A "generator" of elements, that are dict-as-object, with this elements: + name: Name of the interface + mac: mac of the interface + ip: ip of the interface + ''' + 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: + if obj.DefaultIPGateway is None: # Skip adapters without default router + continue + for ip in obj.IPAddress: + if ':' in ip: # Is IPV6, skip this + continue + if ip is None or ip == '' or ip.startswith('169.254') or ip.startswith('0.'): # If single link ip, or no ip + continue + logger.debug('Net config found: {}=({}, {})'.format(obj.Caption, obj.MACAddress, ip)) + 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(): + ''' + Returns Windows version. + ''' + import _winreg + reg = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion') + try: + data = '{} {}'.format(_winreg.QueryValueEx(reg, 'ProductName')[0], _winreg.QueryValueEx(reg, 'ReleaseId')[0]) + except Exception: + data = '{} {}'.format(_winreg.QueryValueEx(reg, 'ProductName')[0], _winreg.QueryValueEx(reg, 'CurrentBuildNumber')[0]) + reg.Close() + return data + + +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 poweroff(flags=0): + ''' + Simple poweroff command. + ''' + reboot(flags=EWX_FORCEIFHUNG | EWX_SHUTDOWN) + +def logoff(): + win32api.ExitWindowsEx(EWX_LOGOFF) + + +def renameComputer(newName): + # Needs admin privileges to work + if ctypes.windll.kernel32.SetComputerNameExW(DWORD(win32con.ComputerNamePhysicalDnsHostname), LPCWSTR(newName)) == 0: # @UndefinedVariable + # 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): + ''' + Joins machine to a windows domain + :param domain: Domain to join to + :param ou: Ou that will hold machine + :param account: Account used to join domain + :param password: Password of account used to join domain + :param executeInOneStep: If true, means that this machine has been renamed and wants to add NETSETUP_JOIN_WITH_NEW_NAME to request so we can do rename/join in one step. + ''' + # 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) + if res == 1355: + error = "DC Is not reachable" + print('{} {}'.format(res, error)) + raise Exception('Error joining domain {}, with credentials {}/*****{}: {}, {}'.format(domain.value, account.value, ', under OU {}'.format(ou.value) if ou.value is not 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 initIdleDuration(atLeastSeconds): + ''' + In windows, there is no need to set screensaver + ''' + pass + + +def getIdleDuration(): + lastInputInfo = LASTINPUTINFO() + lastInputInfo.cbSize = ctypes.sizeof(lastInputInfo) + ctypes.windll.user32.GetLastInputInfo(ctypes.byref(lastInputInfo)) + millis = ctypes.windll.kernel32.GetTickCount() - lastInputInfo.dwTime # @UndefinedVariable + return millis / 1000.0 + + +def getCurrentUser(): + ''' + Returns current logged in username + ''' + return os.environ['USERNAME'] + + +def getSessionLanguage(): + ''' + Returns the user's session language + ''' + return locale.getdefaultlocale()[0] + + +def showPopup(title, message): + ''' + Displays a message box on user's session (during 1 min). + ''' + return subprocess.call('mshta "javascript:var sh=new ActiveXObject(\'WScript.Shell\'); sh.Popup( \'{}\', 60, \'{}\', 64); close()"'.format(message.encode('unicode_escape'), title.encode('unicode_escape')), shell=True) + + +def get_etc_path(): + """ + :return: + Returns etc directory path. + """ + return os.path.join('C:', os.sep, 'Windows', 'System32', 'drivers', 'etc') diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/workers/__init__.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/workers/__init__.py new file mode 100644 index 0000000..f2bcd7d --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/workers/__init__.py @@ -0,0 +1,2 @@ +from .server_worker import ServerWorker +from .client_worker import ClientWorker diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/workers/client_worker.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/workers/client_worker.py new file mode 100644 index 0000000..6a08380 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/workers/client_worker.py @@ -0,0 +1,114 @@ +# -*- 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 + +class ClientWorker(object): + ''' + A ServerWorker is a server module that "works" for service + Most method are invoked inside their own thread, except onActivation & onDeactivation. + This two methods are invoked inside main service thread, take that into account when creating them + + * You must provide a module name (override name on your class), so we can identify the module by a "valid" name. + A valid name is like a valid python variable (do not use spaces, etc...) + * The name of the module is used as REST message destination id: + https://sampleserver:8888/[name]/.... + Remember that module names and REST path are case sensitive!!! + + ''' + name = None + service = None + + def __init__(self, service): + self.service = service + + def activate(self): + ''' + Convenient method to wrap onActivation, so we can include easyly custom common logic for activation in a future + ''' + self.onActivation() + + def deactivate(self): + ''' + Convenient method to wrap onActivation, so we can include easyly custom common logic for deactivation in a future + ''' + self.onDeactivation() + + def processMessage(self, message, params): + ''' + This method can be overriden to provide your own message proccessor, or better you can + implement a method that is called "process_" + message and this default processMessage will invoke it + * Example: + We got a message from OGAgent "Mazinger", with json params + module.processMessage("mazinger", jsonParams) + + This method will process "mazinguer", and look for a "self" method that is called "process_mazinger", and invoke it this way: + return self.process_mazinger(jsonParams) + + The methods must return data that can be serialized to json (i.e. Ojects are not serializable to json, basic type are) + ''' + try: + operation = getattr(self, 'process_' + message) + except Exception: + raise Exception('Message processor for "{}" not found'.format(message)) + + return operation(params) + + def onActivation(self): + ''' + Invoked by Service for activation. + This MUST be overridden by modules! + This method is invoked inside main thread, so if it "hangs", complete service will hang + This should be no problem, but be advised about this + ''' + pass + + def onDeactivation(self): + ''' + Invoked by Service before unloading service + This MUST be overridden by modules! + This method is invoked inside main thread, so if it "hangs", complete service will hang + This should be no problem, but be advised about this + ''' + pass + + # ************************************* + # * Helper, convenient helper methods * + # ************************************* + def sendServerMessage(self, message, data): + ''' + Sends a message to connected ipc clients + By convenience, it uses the "current" moduel name as destination module name also. + If you need to send a message to a different module, you can use self.service.sendClientMessage(module, message, data) instead + og this helmer + ''' + self.service.ipc.sendMessage(self.name, message, data) + \ No newline at end of file diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/workers/server_worker.py b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/workers/server_worker.py new file mode 100644 index 0000000..141d657 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/OGAgent/opengnsys/workers/server_worker.py @@ -0,0 +1,186 @@ +# -*- 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 + +class ServerWorker(object): + ''' + A ServerWorker is a server module that "works" for service + Most method are invoked inside their own thread, except onActivation & onDeactivation. + This two methods are invoked inside main service thread, take that into account when creating them + + * You must provide a module name (override name on your class), so we can identify the module by a "valid" name. + A valid name is like a valid python variable (do not use spaces, etc...) + * The name of the module is used as REST message destination id: + https://sampleserver:8888/[name]/.... + Remember that module names and REST path are case sensitive!!! + + ''' + name = None + service = None + locked = False + + def __init__(self, service): + self.service = service + + def activate(self): + ''' + Convenient method to wrap onActivation, so we can include easyly custom common logic for activation in a future + ''' + self.onActivation() + + def deactivate(self): + ''' + Convenient method to wrap onActivation, so we can include easyly custom common logic for deactivation in a future + ''' + self.onDeactivation() + + def process(self, getParams, postParams, server): + ''' + This method is invoked on a message received with an empty path (that means a message with only the module name, like in "http://example.com/Sample" + Override it if you expect messages with that pattern + + Overriden method must return data that can be serialized to json (i.e. Ojects are not serializable to json, basic type are) + ''' + raise NotImplementedError('Generic message processor is not supported') + + def processServerMessage(self, path, getParams, postParams, server): + ''' + This method can be overriden to provide your own message proccessor, or better you can + implement a method that is called exactly as "process_" + path[0] (module name has been removed from path array) and this default processMessage will invoke it + * Example: + Imagine this invocation url (no matter if GET or POST): http://example.com:9999/Sample/mazinger/Z + The HTTP Server will remove "Sample" from path, parse arguments and invoke this method as this: + module.processMessage(["mazinger","Z"], getParams, postParams) + + This method will process "mazinguer", and look for a "self" method that is called "process_mazinger", and invoke it this way: + return self.process_mazinger(["Z"], getParams, postParams) + + In the case path is empty (that is, the path is composed only by the module name, like in "http://example.com/Sample", the "process" method + will be invoked directly + + The methods must return data that can be serialized to json (i.e. Ojects are not serializable to json, basic type are) + ''' + if self.locked is True: + raise Exception('system is busy') + + if len(path) == 0: + return self.process(getParams, postParams, server) + try: + operation = getattr(self, 'process_' + path[0]) + except Exception: + raise Exception('Message processor for "{}" not found'.format(path[0])) + + return operation(path[1:], getParams, postParams, server) + + + def processClientMessage(self, message, data): + ''' + Invoked by Service when a client message is received (A message from user space Agent) + + This method can be overriden to provide your own message proccessor, or better you can + implement a method that is called exactly "process_client_" + message (module name has been removed from path) and this default processMessage will invoke it + * Example: + We got a message from OGAgent "Mazinger", with json params + module.processClientMessage("mazinger", jsonParams) + + This method will process "mazinguer", and look for a "self" method that is called "process_client_mazinger", and invoke it this way: + self.process_client_mazinger(jsonParams) + + The methods returns nothing (client communications are done asynchronously) + ''' + try: + operation = getattr(self, 'process_client_' + message) + except Exception: + raise Exception('Message processor for "{}" not found'.format(message)) + + operation(data) + + # raise NotImplementedError('Got a client message but no proccessor is implemented') + + + def onActivation(self): + ''' + Invoked by Service for activation. + This MUST be overridden by modules! + This method is invoked inside main thread, so if it "hangs", complete service will hang + This should be no problem, but be advised about this + ''' + pass + + def onDeactivation(self): + ''' + Invoked by Service before unloading service + This MUST be overridden by modules! + This method is invoked inside main thread, so if it "hangs", complete service will hang + This should be no problem, but be advised about this + ''' + pass + + + def onLogin(self, user): + ''' + Invoked by Service when an user login is detected + This CAN be overridden by modules + This method is invoked whenever the client (user space agent) notifies the server (Service) that a user has logged in. + This method is run on its own thread + ''' + pass + + def onLogout(self, user): + ''' + Invoked by Service when an user login is detected + This CAN be overridden by modules + This method is invoked whenever the client (user space agent) notifies the server (Service) that a user has logged in. + This method is run on its own thread + ''' + pass + + # ************************************* + # * Helper, convenient helper methods * + # ************************************* + def sendClientMessage(self, message, data): + ''' + Sends a message to connected ipc clients + By convenience, it uses the "current" moduel name as destination module name also. + If you need to send a message to a different module, you can use self.service.sendClientMessage(module, message, data) instead + og this helmer + ''' + self.service.sendClientMessage(self.name, message, data) + + def sendScriptMessage(self, script): + self.service.sendScriptMessage(script) + + def sendLogoffMessage(self): + self.service.sendLogoffMessage() + + def sendPopupMessage(self): + self.service.sendPopupMessage() diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/doc/ogagent-oglive/changelog.Debian.gz b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/doc/ogagent-oglive/changelog.Debian.gz new file mode 100644 index 0000000000000000000000000000000000000000..3de583a4b48f59370190c92ee0e98b42de3fa4ed GIT binary patch literal 214 zcmV;{04e_;iwFP!000021FeoRP6IIzMf*L)FA1>L?5-jO2#8dxAfg~{;Do_gTF*pl znN65u++B7t1elf*=Ur(KNEU8vE_;1ElXUI_9QgcVsmL|v&lidvbfIODs_V5_^ z!{<;{z%|zW;!NXHN2q%}FKjjJZ1M%c-ls3DcPzccLqvx?Z?Je<7PhYOZo}y)SQ+%E zW8fdmM;la}mj5|N!|@4NsobKO;Ux=-)C^5`PgH^%YKf{!FZ>%*?l0}7_~h+?kq5Ss Q&3_900xCgIkx>Bv0QLc6n*aa+ literal 0 HcmV?d00001 diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/doc/ogagent-oglive/copyright b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/doc/ogagent-oglive/copyright new file mode 100644 index 0000000..7b6ef31 --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/doc/ogagent-oglive/copyright @@ -0,0 +1,26 @@ +Format-Specification: http://svn.debian.org/wsvn/dep/web/deps/dep5.mdwn?op=file&rev=135 +Name: ogagent +Maintainer: Ramón M. Gómez +Source: https://opengnsys.es + +Copyright: 2014 Virtual Cable S.L.U. +License: BSD-3-clause + +License: GPL-2+ +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +. +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +. +On Debian systems, the full text of the GNU General Public +License version 2 can be found in the file +`/usr/share/common-licenses/GPL-2'. diff --git a/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/doc/ogagent-oglive/readme.txt b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/doc/ogagent-oglive/readme.txt new file mode 100644 index 0000000..a2771de --- /dev/null +++ b/native/Sources/Clients/ogagent/oglive/debian/ogagent-oglive/usr/share/doc/ogagent-oglive/readme.txt @@ -0,0 +1,3 @@ +OGAgent is the agent intended for OpengGnsys interaction. + +Please, visit https://opengnsys.es for more information diff --git a/native/Sources/Includes/Database.cpp b/native/Sources/Includes/Database.cpp new file mode 100644 index 0000000..84add9c --- /dev/null +++ b/native/Sources/Includes/Database.cpp @@ -0,0 +1,203 @@ +// ******************************************************************************************************** +// Nombre del fichero: Database.cpp +// Autor: José Manuel Alonso (E.T.S.I.I.) Universidad de Sevilla +// Fecha Creación: Marzo-2010 +// Fecha Última modificación: Marzo-2010 +// Descripción: +// Fichero de implementación de la clase Database para funciones de manipulación +// de bases de datos sobre un Servidor Mysql +// ******************************************************************************************************** +#include "Database.h" +// __________________________________________________________________________ +void ErrorHandler(Herror hr, char* ErrStr) +{ + sprintf(ErrStr,"Error:\n"); + sprintf(ErrStr,"%sCode = %d\n",ErrStr ,hr.nError); + sprintf(ErrStr,"%sDescription = %s",ErrStr, (char*) hr.dError); +} +// __________________________________________________________________________ +Database::Database() +{ + m_Cnn=NULL; + sprintf(m_ErrStr,"NULL POINTER"); +} +// __________________________________________________________________________ +void Database::GetErrorErrStr(char* ErrStr) +{ + sprintf(ErrStr,"%s",m_ErrStr); +} +// __________________________________________________________________________ +void Table::GetErrorErrStr(char* ErrStr) +{ + sprintf(ErrStr,"%s",m_ErrStr); +} +// __________________________________________________________________________ +bool Database::Open(char* UserName, char* Pwd,char* server,char*Bd) +{ + Herror hr; + m_Cnn=mysql_init(NULL); + if(m_Cnn==NULL){ + hr.nError=0; + strcpy(hr.dError,"Error en la Creación del objeto MYSQL"); + ErrorHandler(hr,m_ErrStr); + return(false); // Fallo de inicializaci� + } + + if(!mysql_real_connect(m_Cnn, server,UserName,Pwd,Bd, MYSQL_PORT,NULL,0)){ + mysql_error(m_Cnn); + hr.nError=mysql_errno(m_Cnn); + strcpy(hr.dError,mysql_error(m_Cnn)); + ErrorHandler(hr,m_ErrStr); + return(false); // Fallo de conexi� + } + hr.nError=0; + strcpy(hr.dError,"Success"); + ErrorHandler(hr,m_ErrStr); + return (true); +} +// __________________________________________________________________________ +bool Database::Close() +{ + mysql_close(m_Cnn); + return(true); +} +// __________________________________________________________________________ +bool Database::Execute(char* CmdStr) +{ + Herror hr; + if (mysql_query(m_Cnn,CmdStr)){ // Ejecuta la consulta + mysql_error(m_Cnn); + hr.nError=mysql_errno(m_Cnn); + strcpy(hr.dError,mysql_error(m_Cnn)); + ErrorHandler(hr,m_ErrStr); + mysql_close(m_Cnn); + return(false); // Fallo de conexión + } + hr.nError=0; + strcpy(hr.dError,"Success"); + ErrorHandler(hr,m_ErrStr); + return (true); +} +// __________________________________________________________________________ +bool Database::Execute(char* CmdStr, Table& Tbl) +{ + Herror hr; + if (mysql_query(m_Cnn,CmdStr)) { // Ejecuta la consulta + mysql_error(m_Cnn); + hr.nError=mysql_errno(m_Cnn); + strcpy(hr.dError,mysql_error(m_Cnn)); + ErrorHandler(hr,m_ErrStr); + mysql_close(m_Cnn); + return(false); // Fallo de conexi� + } + + hr.nError=0; + strcpy(hr.dError,"Success"); + ErrorHandler(hr,m_ErrStr); + + Tbl.m_Rec = mysql_store_result(m_Cnn) ; // Toma el recordset + if(Tbl.m_Rec){ + Tbl.row=mysql_fetch_row(Tbl.m_Rec); + Tbl.fields = mysql_fetch_fields(Tbl.m_Rec); + Tbl.num_fields = mysql_num_fields(Tbl.m_Rec); + Tbl.numreg=mysql_num_rows(Tbl.m_Rec); + Tbl.eof=Tbl.numreg==0; // Consulta vacia + } + return (true); +} +// __________________________________________________________________________ +void Database::liberaResult (Table& Tbl) { + //Free resources after mysql_store_result + mysql_free_result(Tbl.m_Rec); +} +// __________________________________________________________________________ +Table::Table() +{ + m_Rec=NULL; +} +// __________________________________________________________________________ +bool Table::ISEOF() +{ + return(eof); +} +// __________________________________________________________________________ +bool Table::Get(const char* FieldName, char *FieldValue) +{ + char * aux; + aux=tomadato(FieldName); + if(aux) + strcpy(FieldValue,aux); + else + strcpy(FieldValue,""); + return(true); +} +// __________________________________________________________________________ +bool Table::Get(const char* FieldName,int &FieldValue) +{ + char *aux; + aux=tomadato(FieldName); + if(aux) + FieldValue=atoi(aux); + else + FieldValue=0; + return(true); +} +// __________________________________________________________________________ +bool Table::Get(const char* FieldName,char &FieldValue) +{ + char *aux; + aux=tomadato(FieldName); + FieldValue=aux[0]; + return(true); +} +// __________________________________________________________________________ +char* Table::tomadato(const char* FieldName) +{ + Herror hr; + unsigned int i; + + for(i = 0; i < num_fields; i++){ + if(strcmp((char*)fields[i].name,FieldName)==0){ + sprintf(m_ErrStr,"Success"); + return((char*)row[i]); + } + } + hr.nError=-1; + strcpy(hr.dError,"El nombre del campo no existe"); + ErrorHandler(hr,m_ErrStr); + return(NULL); // No existe el nombre del campo en la tabla +} +// __________________________________________________________________________ + +bool Table::MoveNext() +{ + eof=false; + row=mysql_fetch_row(m_Rec); + if(row==NULL){ + if(!mysql_eof(m_Rec)) + return(false); // Fallo de lectura + else + eof=true; // Fin de fichero + } + return (true); +} +// __________________________________________________________________________ +bool Table::MoveFirst() +{ + my_ulonglong auxnumreg; + + auxnumreg=0; + mysql_data_seek(m_Rec,auxnumreg); + return (MoveNext()); +} +// __________________________________________________________________________ +bool Table::MoveLast() +{ + my_ulonglong auxnumreg; + auxnumreg=numreg; + auxnumreg--; + if(auxnumreg<0) auxnumreg=0; // Principio de fichero + mysql_data_seek(m_Rec,auxnumreg); + return (MoveNext()); + return (true); +} diff --git a/native/Sources/Includes/Database.h b/native/Sources/Includes/Database.h new file mode 100644 index 0000000..ae318bd --- /dev/null +++ b/native/Sources/Includes/Database.h @@ -0,0 +1,61 @@ +// ****************************************************************************************************** +// Aplicación HIDRA +// Copyright 2004 Jos�Manuel Alonso. Todos los derechos reservados. +// Fichero: Database.h +// Descripción: +// Fichero de cabecera de la clase Database para implementar funciones de manipulaci� +// de bases de datos sobre un Servidor Mysql +// ****************************************************************************************************** +#include +#include +#include +#include +// __________________________________________________________________________ +class Database; +class Table; +// __________________________________________________________________________ +class Database +{ +public: + MYSQL *m_Cnn; + char m_ErrStr[500]; + Database(); + bool Open(char* UserName, char* Pwd,char* server,char*Database); + bool OpenTbl(int Mode, char* CmdStr, Table& Tbl); + bool Close(void); + bool Execute(char* CmdStr); + bool Execute(char* CmdStr, Table& Tbl); + void liberaResult(Table& Tbl); + void GetErrorErrStr(char* ErrStr); +}; +// __________________________________________________________________________ +class Table{ + char* tomadato(const char* FieldName); +public: + bool eof,bof; + MYSQL_RES * m_Rec ; + MYSQL_FIELD *fields; + unsigned int num_fields; + MYSQL_ROW row ; + MYSQL_ROW_OFFSET ptr; + my_ulonglong numreg; + char m_ErrStr[500]; + Table(); + void GetErrorErrStr(char* ErrStr); + bool ISEOF(); + bool MoveNext(); + bool MovePrevious(); + bool MoveFirst(); + bool MoveLast(); + + bool Get(const char* FieldName, char* FieldValue); + bool Get(const char* FieldName,int &FieldValue); + bool Get(const char* FieldName,char &FieldValue); +}; +// __________________________________________________________________________ +class Herror +{ +public: + int nError; // C�igo del error + char dError[500]; // Descripción del error +}; diff --git a/native/Sources/Includes/Database.o b/native/Sources/Includes/Database.o new file mode 100644 index 0000000000000000000000000000000000000000..aa88a5778e924e7938bd4aa3c7f503492dcef433 GIT binary patch literal 33312 zcmdUY3w)H-weS9B@?|oa$pZoy-oqO~Lc&WxgoGq8(GX%1R74#nlgXndGjSde#77jV zrihBItyL6Tt+iM1+8(iLt#YgtJ$>2ZIW6a0k5a1^AIC%0TI!>mwbovH^35VS?XUOT z-@W(yk#Fzy-)pbE_S$Q&y}#FRO>O-emt`q)SZbvjqztOm<|_yDm6WYir>QaO!=g!_ zYzPj#8Qk$+ad62o_4J(4({5(9E%ajDh}>mH|oDC zzxBl*-8y7zhf$QGhIbsw8>aCgYY(K4YkeE@AUz)3T|klsex9WmR6V8L!J13|%9_`{ zSMI_d|M0};Cr<_k4j!lE_G6UnJ_5Lb-S+X z3hvrTxm_x_^H9;wZv)5So$AjgPaLI#yKx|?RzJ{M}h-K!A{)VldG`lf#-t*zx*)xt@?q(TIFk>Q_ySjfwvp6&YFSv#I9#_oRQ(DjFkuHTEz4GJ?n zryv8`7^mRDfoGkDB3ETA|2OpY-HxHhl<{4706lx z&02l9G7qQ1t(Q1fS1v1hOgYgV@4?D()v@3}8sh$jBcR7oSo?s+WnRZlSh0dTt~#Mo zqj@Di;wXLB1MEkhhvh(rALnZR*Gq;y0yJV8+_mAzfa~3S6s)%af2(W|TJ#u6od%8>vmk=99G$RD81TL z*l}p-FxwZyv8yqV5$V<<-#!41?x1!oX4_R+x{QFL>v~U~JgKJ^WKIUN_6FU7SiGk- z5qu^J+koysB(pboqNg_wWr1jLAXt33vb3)&nS$NGged4wZVfwurGNWQ;IH&x7!q(6 z8g7RZlVc664lo{oDUzNcnRXnvu0NFW(7xkcKbAhPEV=6KcrQTye7N=mjvH_tquGLc zREJNKi);UO`h=;iYk~iw>-U_1A^3}8U>?%GYU;A81@(*U(bi1m()?A6}lIl&vh9y7N%`DR^=oXZi-ILd?2;dD?PF^7A}M z<>ydRmY1)E^PZa!2)N;_@{4mQ z$j=<RR8j#K(A z-{+J*;46l)bJ^eal~ekluaVO4_1SgOLRu1TO{xyG-rex`Fzjh8%xHWA<`j6@^N3?cr89w z+M;lmL|#RhE>JX~axQZD+o3Ry74W1yDL5}!h&6ITu927`1CFI;C_*Twy$IF8k+pFD zpfuV=!f>3OX!i(I1xH<@y5$w_!XUut1cR<-l>wwrmq7p2B2+);Vs|0>8v2Qa%Fu~B zaILGbA0YG{r}RtRMd+63N{&wJmcQvHHPOAO3bn`L4!Da@g#j$16ZTFscy0zTKL?yg zsnM9%(!Qmop>xU_V~{mxjk^Fn*23P({?WaGoGq>b^kvCcpZmu!wRV6a@v*YyvM!`Oy1Lze4aou+$*Q-&D0v)|R`c@SZ2 z%%U7xOnGk4nB+UU@{1WRw3a#(xr)mzpPpwg`1Jfc!t`%_dfvU^)AQ|3pPp~O^W6#a z&1E0+;k+Y%{%4egwaNGO1YiYQ-IK5YML7y}pD&+|Qw62#E@|||F^FPY(h|D-=abKe za@1ky@+%AP2W?Qd!Ezp?YewN5e<{1j<(cKD2Wu?SM~5 zo+|kBFM0!f6g`U17Uf-ZcGFoe$2R42>FlAiUZ1^`&!@AG&VD)<(7BM#MRYEv^9VYR zq;mo(Lpq|mcK<~?o?x=Kn@``ja*gD&)G>R$JVbD*qWQ{T|kwVL{-JMTr{yiQa1>GrPI z)cu4D9|%O9Q_LU?u{#Itz!<_dU0J4vaZ%Aw4-*-*?iTJIR;r`O1TJXg_dcj(3h# zd{F_6U@V$a-no_sg(_0bdxk{=e}~r8JJ0g=L)pCV+c6G zMU}@UM_5~ya%ALkID7Av_D5joqlg$aUVnx ziY?x4;eu?7fQe0bOu45wl0NMcbA48Y0SdNk0wHQcYY- zM5QLKqrnMkVh0iRnz){bMosJ_qD2#5Bm$RFrMx!~aj__+HkVN3C}(17KL}V#Zt_Ms<|}o_5*y|!OChV>BM7S^fp<= z55vVIEu!9Lj^&za(aS)drZ!k(kWueRyysgq2lSrAdx13)7r=n#ywJ-3JK&tHsf{`W zmX{{tTb1`Bi)NwTlz2C3s#0@atk;>KrY_MTTW?Cdmsk_gFfFCgI0Udubqv;96z^tD zZPfL`I(lEMsa73SH!IKYa6*3%Orfn1vE0u9F$JxlTq8a$feUfWj`E`JIzQTja30*} zQfdtC5k7-57hqohD8OK-c}}BsO95YC%B+4U5+}`Hij|N*2)vhM_|dC0|Co$u8bf4F z0^2sjs>O^mVr~KaPE$q_G{bnq@CXzh%3`pn3F_7aSjm-o8H!)cuuy3w;sq0_*!qu5 zSwF(s}iw0zR;U9o8kMc8$a9@J+4xGIR9int1y45T{hW5b3-c&-(F8LMY zXA{rqMaQ87<&>X8gSnJ?J(mXaMbP3GMiWH^XHb7%2k_rbfy1QpuwB&54H|jz5Bs3a zU1rKA12iMMY`!Tw3-GG!vT{>)0pOQpmsOauB;eaJWwY19HgcgUy9Mxlrc7Vy_G1}w zEizmOf#b*7T%Q1`R->$f#nf%G`M+i}okS+(!9g93o%K8p13eX;b?Qzy(LkTDmYxpx zL&(Z!E(1Xtk~Gu-4?-h&ohmfkJk$1GYY9~JPE))@4op9pxGs|4NXbpgL-jY6IOgfPVh z-Q+74UHy)RHJxCmhNrm;XB(XRv%9b#x>1cNW;C~!^sLtMqD&19|HvARJ-=MQzp{!^ zi-!z6XdXunr-mkR2hEoLiLUvqsj1TZfC?mh2qSALu;x8;8B5L~V0f*Ok;E;~Cj%!bFWH{ZYzLM8Z zWuP$5)Eja)P-NEvH|YfA-ZYfl8?xlya0bp=BX?j>E=Gqda}PM=Zp_GiV<@>-X34#> ztRL8oTz#>CBUyuC(At`tOUsRx2rbcJ?JWV+VTevO=P_VUOaXW<{vjJWh^j?SJQ_H~ zJS|X=T6-@5^bD*;&Vy@JPi~)sa~870VeS1gpw!|BXb8B+wX}Ato*B4ex%O;Ueq0Ef zavycnt*20rb>DZ^b?-T*{h?`6KS*}(Il7%>HO)g==q>0TEgAPveq4X6a*K@CP^mxT zYRdu5sFe|2l@X1ysvWX$**`0HlS9@_kcWr0x6>($41ybuX}JRq?sTS8N1fc6mZy#H z2U*mq#;t8?+FVWY6qvSHlP_%_w;BqoH1z`Yc!onuTpK6lTC^~NI5?K;1hkw0sB`Ga z>nBa^2L>7l+)a$lJ$uNy7n-^}h*Wp%3{!W`+YsJL+JUJT{>X`tQLVvxczl^t%n!zu zy?~y9wa{i1sVsjkvY4ZXGZ4LCIQ-*q=DYSNYTZA*A4;*NvTRic%DeK^a^G3LWj@^2 zxeJuLP?L2fOQGL6vjLa@8JkP&Wjq>Nk#SBg<0EtsPF=%MP6l=xUwh~ll1gTGD)FCKRYUCp2&dX}3goFT=i$dHe zL`xq9_4AyT8a0w*hY?K(NkQgpuLsIzj4$2TRXu7tWaB^N8sm;z>)5AHCNGT=G0w07RJba$a>wh=kx$ea#?z@t5- zNDud%LD!gKwUq>>C~OMug=p`A`CzLGa*TN+tQmzRKKJDDlhK2UVXcQ-dOlENKD!<4 zIm$5zRV=e;06{AhwgAKTLg(NOj`bqe*QdLnM|H82_XcaMMwgVSY}bdUPS6m}DQ;-# zS)euCDhH>?1Dj{?<=_K(=mTfLD4}QZD6Jqv`+VS~gUn}>OF3$<9BpAQR%Kj`TdT}y z2{Qwn23)cY_^PsDbZR{Mm6pbYG@oNT9_Gn1Hls=p6KO%WMd;21cr4$=-(`3G$8bwG8jv&ZC}t@Hao6fmG{BWLhM3pstAM+D{8B2`pCal#y1Alu>-EfqqNEgY}bB! z+#WkWugso$z%GPj`T@|o%$~5%E`X%yfIWJ@oev$o4GgQhV9&|jVwdEtw5<=pqEFe@ z57ycKAJ|rU6$sb@J?gN%>AZToAPwCd#73s=f+w%vm%&B?pFd?6bRYu8$UBC{9yQ+{ zzu%tFZcj?t{uQ+Ir@6tRJkh zeINb69&r(oqn@;_sV*1zcj%(#)IxX%q)nMzG?Q!3CEVH_UA8X1CAy|7kxbQYQQh76 zx&?^Y5>14Mf;!!4vVzZHEay6u3x`8t0UEmK+BBo z6^pPrR1QwfsRZ;;rMC6LK!hUkp5AaG)E)yJ;LZ)uX?VK}``QrgOF^^o?rtLM!^xBa zD}fQYGg^t}ma3Kw&7rlmEup5`<_+~N3T=#Xd&04{WVZRWfk0?GO70@@3SMsu_ki3% zeT{XRB5G@AG!YH84pG*ndc(;i?52AY@l-q#?+(FWB;hSNXfoCjz?alA23U$6!XAKC z9Jbq7Th*lC`-irS^Ylbh;WlsvwKbB0_aMRknXz-!YpA`Trnb6nT~)nGKqpntwoo!2 zxeV^y=ngtr;blE;a$RjpaD7cPYL#)g#ns*MWHei=n)Mr2*Vn2@XE>3Jrb2C92{0-6 zS}N3qO(iey4(Vo+T^+G#TL2lIy;@tl9-3;}sFK@~DR_gA(Vbm&Z)Bj8o!GteSVI2g;eP+0iAYax z))1Xp+ZThIKj+Xtp(V zwe>X_!;pT?3N3Z(Y8SA2?TKhKl!zwN-QdA}sVH~{Oe05Y@UH2CMQUp}5$lR|m>4mr z422)v(_|_R^Oa?VLTQ+WOBR6_bVNZc1Wufq8Sle6iX(Jcbej&Tz=$KzyA6_XDsAjZ zp&x^}?%xvh17O_%lY#~V4ah+$9qZ~dlQGrVm4qNchRupuBo7S$!E0TkO7sxTiH0-x_iDv$kQwIuJzjFAnQO8+d+CxGM(T3&D&w@gfrL4Yzi6ccr?bNyDBA zsyOpu3W5A*7S$kOCwfC{JWL8(& zY@mKPR$FllY0jxmSR28qc(!KwG;YDrI;tmBOIs2J_hlhUCZZj%*ulhUO-93sNT=es z>RdBvdC4@sxURW&{Tk?Y7?zhv3R)*~IfB>fx)#T*$qGJZc{3w?YLuvJIKQgCu0~NS zYzTHBOlsHFHncc5Ke&O!d!XxT*M*wauW!*E;E)NOhK1GX44ELbfEl&Bn^;OQkoL_G z@n9o%c{-j_ZLLP#wGA5r)pO@ooEfO7C|g)o9+=mYO2?vScSK=r?TP@2Haxp;>5_R% z76s;Y1m?AeliOmE&O|&GPbcT4V_UmoZS%s69=rg}?`AZ!V%aEldcSl^JfbHED-6~!HjUbS)0nb`(A+G`H3Z)yP(;w~YJhlkEA z1-x=YoW!*Rb~m(R)m`A(+=7=8G#a7acv9h6vbjt+LaFv)wO zJ#pB8;YFL?pz3Dm`mWA-0TK!)65(yCzOK1NRrj>P)ejG8Rv>W37srOrtS7MBODAF> z?i%gcvsR8XdD_=@HAYMwhw1Qo0rzWh`ijTwN_)d_MHth*F~m5n!7B&azjpP)MGy}? zY>ib6ugoZV!YZlzoh2DA6=1&*&AQaVy{O{?ax1QfwA;!S0GF4rPoveW6+%NO+}4IW z2$kxj%Otn};UJCm(FWT83(2;(%Y-%r+Rwl$6M>#2fRmiq4zlC7nhpH3#`!5W#r!uy7!3KV}W~3<7Ie59A$l5~xh$*b?lfI{-s*aY#X9SQgs55L0WbtCt66Z)kL`9(_s0;jd)=74-ZD zzR6?X3=PAd7zV#?82t8O@CSy$4+4(*-$)OTIEFw!jts;94)Z_4{1j7w|HLqOF7yxO z|CISD#sGgQ>G?<|4|jL?ZeIwu*6?s6(s@~^ zJ=}%YV?*2{*A?}xHYbB)!_HS5`XX@{9PaCn6)gYR|V@RCIaf7F3H`G4lXaj$LU z=kmHH^`Xz>fluPMIQTKv8#&)~;OH9$Cjl&HtOK8ng5ki#sb_@)clN8!fjjwI9k{b! zcR6sUoga1JPCdu)dX2?S{v{6F*fG2z~YeImEHu6@(7Hvubes+8>V^on-jatkgPz-_G%?Mc`k?3oSUze$>d{#Qd$g zI3xd%Ujw}YPjdWC3;Z)S>Wu=QgaZV}*L9MS{~h+Tdj?7|Ej0fC=j`{Pf{ z;bHiVoHYV(7JSv;LZPG4|nM)?b4!(6IU`kH5iLym~|6 z7|ZbZOyC-Y`()g2Bd3`Q&Jg%)#%lz=n#XISz&~cc-6HU{+~2E*!S@Kfg!TTm!0~zz zj~@zrBI7R${7g2`IQBc!Uk{I$*(Vqrf3*XTb%Osvrn>}wf&0BmV&V z&r^c`X`U~y3VavK`Mtnj=Ft`f`D9XZz}3 zb{K5;Cni1!{_nG%{~++2xxb@$9E_glaX(KN_&;lNs#<|R&3?5>;GZ!6c7Y${cJC7S zPPW5i0{?*JxOv=+o*$cdCGcIW|C<7TndSdp;Nw}J&joJgv)Q+pc31H_Qp)x=_-RZ} z68KlyAEpcZX~yRW{BrJZxxg3l_$?9mEcOR;zs2bD5s&+7!GAN`Hz@F*GTtEYC%N4g zf!8yBk-*<#J8u?vF593w#=n*KY(K;Q9C;0^iPhelG9`+rh{B8a=jt`@IuDTbru$5mliX=UGTrp`VR>FUFQFqz<07;9uW8=Z0DZ} z{C?&?Dewn)zK`TMgvF@m)r^-4d?x#|xxO>}mp~mnss#UYJdTY5uVs6N1pZ|$M#Tia ziS2Whz~emMZxZ2w8!tt@df5z(zem{tZkw2T`Rk6UEc)TVGd=}eh zj=-PhaXeGtJ9&Q92z({$bDqGb^0;3r@H?3QGJ%`%-6n7|zB>h8$9}#?;AO1mJpwoT zjfVto_8X52+>GN-1a9R2THx<Rsw|Dysoaprx2C)qxA_>d0{GmiIgznTTUnB!`z zz;ELIb_)Do+23LU-^6~K68O(p|Lp?5jQwz*!1eFwDfM>(H~Y*(0)LLj{pSLI$jl3Y zn>h21z)c)GA@Bn3S1vzzF!ua2`~N6`f6Dqy6}Z_atQ2@V&x?A2oBiZP0yq1vPJv&; z`-kfVzKi44Z34$%KgQ!;fg8ViLg2>E&kOuI_UC^T_~&f5R|P(q{ox&f8#%`X{skUK zH$K#a!`Ocs`+1(gzoFUGLV^DWx4Tr}|6<~az+YxOBJh#i-zx+@j_rJnzz_2H-X(Cu zf3LtF;CBCB;Q2gX4hy`A=jm$#U&8i(OW^yMe*!-UF!mh9?amgs{&6+<$q9kCaepHM zKgsbVE^ssNwhR2XY@dAse~IJOLjpJc^L>FE{eLL%f93i3lE969ekJhj>~HT2{4o2^ zhXTKV@qB#93Wu>Lz88l_k-#rueP#;$QXb#A0-w(KYJu-!KU^#DXLPgbQh`6m`a}eN zCC7nl1YW@W-xBy*=Kq<%^{?~6``7#+*4W`))@OvkC-eAD6gd294;^y^{xSRQYJtDP ze!fxQ53^sT1bz?4$r}W|jK}33fuCeK2L*2NeEF%sSF--U6}Wld;uC?N!}^cGmnz^e zc00=J%jp6yXS_z>cd-7O1iqgABq8u++^_ut@8o{{NZ|KzyYDcL?t!jg-bb|XH&x&; z`kUt~1p=Rq3~Ii}rv z1iyK&`rt79&kFn|o-cnAa?JbO#rX1*W86)<69sPM)G}`RW!@vdSnwM;Nx^U4WBAfA z{9hCJCZ5*^1@7l{{9PgcE!`dE;umb>I2H$2FB|J z-pBZP0^iB_`2xR<@l681i}B3@zmM^#!1pu$1%W@xcwFG-{ehIg4>SKZf&Yr}s|Efl z<2wX?jPV-<{t@H51rEQHM8|CcFJSy@0@pu%4c}G?d=m2;Kf>l1BF+HwBj&^_1#afi zW`Ub|-7oOBn7&uwCSDy9xQTm51#aRhO&};UdYZUxR@!>KC|K zAMX{oS(gt9-0TOA3f$~BjB$-VX5SJ(z|k+WFRB!{*>|b3NhwM@O)e4b z4wu14<|$Q%FWsxMR`^P{ED_fPm9|A&(;fO}NXm3R+}ett1(!LB!|<7S2YgoT#NaE~ zD2S!%(4NU64Bu%0jX&;$pXHXt;Fki*;FC=Fr2zPRgK7ADwhVqlpcg(v-o|pHouPL4 zktZ{8wHf~Aa{y|h>2rmIN~Yy6@v(U`+_>yv+^)UWjJr;bu|NUhTS9sp8+|P`M1`ufVwDC_RF+i31!YhwvTaMV#xn}*8kVM?im@9 zcNr86WxrsPu5o0#PSJiiM$&%g048N(jhkoV8U71B=b{R<kcqsh?TQz?7Y}ZBkRu(BFj&-l7s}s$02!z%X)Z>}TK?A%iky{YSX|g`z;#e-7#nHGW5UpcEU{iUucvh+(g~NfjVTSTQ#ANJ1{u}G?tkf@s{?dLUfM>}1+o`&G KStpsZtpA^wM;@X8 literal 0 HcmV?d00001 diff --git a/native/Sources/Includes/ogAdmLib.c b/native/Sources/Includes/ogAdmLib.c new file mode 100644 index 0000000..2f0a714 --- /dev/null +++ b/native/Sources/Includes/ogAdmLib.c @@ -0,0 +1,1074 @@ +// ************************************************************************************************************************************************** +// Libreria: ogAdmLib +// Autor: José Manuel Alonso (E.T.S.I.I.) Universidad de Sevilla +// Fecha Creación: Marzo-2010 +// Fecha Última modificación: Marzo-2010 +// Nombre del fichero: ogAdmLib.c +// Descripción: Este fichero implementa una libreria de funciones para uso común de los servicios +// ************************************************************************************************************************************************** +// ________________________________________________________________________________________________________ +// Función: tomaHora +// +// Descripción: +// Devuelve la hora del sistema +// Parametros: +// Ninguno +// ________________________________________________________________________________________________________ +struct tm * tomaHora() +{ + time_t rawtime; + time ( &rawtime ); + return(localtime(&rawtime)); +} +// ________________________________________________________________________________________________________ +// Función: registraLog +// +// Descripción: +// Registra los eventos en un fichero log ya sean errores o información +// Parametros: +// - fileLog : Ruta completa del archivo de log +// - msg : Descripción del error +// - swe: Switch que indica si se debe recuperar además el literal de error del propio sistema operativo +// ________________________________________________________________________________________________________ +void registraLog(const char* filelog,const char *msg,int swe) +{ + FILE *flog; + struct tm * t; + + t = tomaHora(); + flog=fopen(filelog,"at"); + if(swe) + fprintf (flog,"%02d/%02d/%d %02d:%02d %s: %s\n",t->tm_mday,t->tm_mon+1,t->tm_year+1900,t->tm_hour,t->tm_min,msg,strerror(errno)); + else + fprintf (flog,"%02d/%02d/%d %02d:%02d %s\n",t->tm_mday,t->tm_mon+1,t->tm_year+1900,t->tm_hour,t->tm_min,msg); + fclose(flog); +} +// ________________________________________________________________________________________________________ +// Función: errorLog +// +// Descripción: +// Registra los sucesos de errores preestablecidos en el fichero de log +// Parametros: +// - coderr : Código del mensaje de error +// - swe: Switch que indica si se debe recuperar además el literal de error del propio sistema operativo +// ________________________________________________________________________________________________________ +void errorLog(const char *modulo, int coderr, int swe) { + char msglog[LONSUC]; + + sprintf(msglog, "*** Error: %s. Módulo %s", tbErrores[coderr], modulo); + registraLog(szPathFileLog, msglog, swe); +} +// ________________________________________________________________________________________________________ +// Función: errorInfo +// +// Descripción: +// Registra los sucesos de errores dinámicos en el fichero de log +// Parametros: +// - msgerr : Descripción del error +// - swe: Switch que indica si se debe recuperar además el literal de error del propio sistema operativo +// ________________________________________________________________________________________________________ +void errorInfo(const char *modulo, char *msgerr) { + char msglog[LONSUC]; + + sprintf(msglog, "*** Error: %s. Módulo %s", msgerr, modulo); + registraLog(szPathFileLog, msglog, FALSE); +} +// ________________________________________________________________________________________________________ +// Función: infoLog +// +// Descripción: +// Registra los sucesos de información en el fichero de log +// Parametros: +// - coderr : Código del mensaje de información +// ________________________________________________________________________________________________________ +void infoLog(int codinf) { + char msglog[LONSUC]; + + sprintf(msglog, "*** Info: %s", tbMensajes[codinf]); + registraLog(szPathFileLog, msglog, FALSE); +} +// ________________________________________________________________________________________________________ +// Función: infoDebug +// +// Descripción: +// Registra los mensajes de debugs en el fichero de log +// Parametros: +// - msgdeb : Descripción del mensaje de información +// ________________________________________________________________________________________________________ +void infoDebug(char* msgdeb) { + char msglog[LONSUC+15]; // Cadena de registro (reserva caracteres para el prefijo). + + sprintf(msglog, "*** Debug: %d-%s", ndebug, msgdeb); + registraLog(szPathFileLog, msglog, FALSE); +} +//______________________________________________________________________________________________________ +// Función: ValidacionParametros +// +// Descripción: +// Valida que los parametros de ejecución del programa sean correctos +// Parámetros: +// - argc: Número de argumentos +// - argv: Puntero a cada argumento +// - eje: Tipo de ejecutable (1=Servicio,2=Repositorio o 3=Cliente) +// Devuelve: +// - TRUE si los argumentos pasados son correctos +// - FALSE en caso contrario +// Especificaciones: +// La sintaxis de los argumentos es la siguiente +// -f Archivo de configuración del servicio +// -l Archivo de logs +// -d Nivel de debuger (mensages que se escribirán en el archivo de logs) +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//______________________________________________________________________________________________________ +BOOLEAN validacionParametros(int argc, char*argv[],int eje) { + int i; + char modulo[] = "validacionParametros()"; + + switch(eje){ + case 1: // Administrador + strcpy(szPathFileCfg, "ogAdmServer.cfg"); // Valores por defecto de archivos + strcpy(szPathFileLog, "ogAdmServer.log"); // de configuración y de logs + break; + case 2: // Repositorio + strcpy(szPathFileCfg, "ogAdmRepo.cfg"); // Valores por defecto de archivos + strcpy(szPathFileLog, "ogAdmRepo.log"); // de configuración y de logs + break; + case 3: // Cliente OpenGnsys + strcpy(szPathFileCfg, "ogAdmClient.cfg"); // Valores por defecto de archivos + strcpy(szPathFileLog, "ogAdmClient.log"); // de configuración y de logs + break; + case 4: // Servicios DHCP,BOOTP Y TFTP + strcpy(szPathFileCfg, "ogAdmBoot.cfg"); // Valores por defecto de archivos + strcpy(szPathFileLog, "ogAdmBoot.log"); // de configuración y de logs + break; + case 5: // Agente + strcpy(szPathFileCfg, "ogAdmAgent.cfg"); // Valores por defecto de archivos + strcpy(szPathFileLog, "ogAdmAgent.log"); // de configuración y de logs + break; + case 6: // Agente + strcpy(szPathFileCfg, "ogAdmWinClient.cfg"); // Valores por defecto de archivos + strcpy(szPathFileLog, "ogAdmWinClient.log"); // de configuración y de logs + break; + case 7: // Agente + strcpy(szPathFileCfg, "ogAdmnxClient.cfg"); // Valores por defecto de archivos + strcpy(szPathFileLog, "ogAdmLnxClient.log"); // de configuración y de logs + break; + } + + ndebug = 1; // Nivel de debuger por defecto + + for (i = 1; (i + 1) < argc; i += 2) { + if (argv[i][0] == '-') { + switch (tolower(argv[i][1])) { + case 'f': + if (argv[i + 1] != NULL) + strcpy(szPathFileCfg, argv[i + 1]); + else { + errorLog(modulo, 10, FALSE); + return (FALSE); + } + break; + case 'l': + if (argv[i + 1] != NULL) + strcpy(szPathFileLog, argv[i + 1]); + else { + errorLog(modulo, 11, FALSE); + return (FALSE); + } + break; + case 'd': + if (argv[i + 1] != NULL) { + ndebug = atoi(argv[i + 1]); + if (ndebug < 1) + ndebug = 1; // Por defecto el nivel de debug es 1 + } else + ndebug = 1; // Por defecto el nivel de debug es 1 + break; + default: + errorLog(modulo, 12, FALSE); + exit(EXIT_FAILURE); + break; + } + } + } + return (TRUE); +} +//______________________________________________________________________________________________________ +// Función: reservaMemoria +// +// Descripción: +// Reserva memoria para una variable +// Parámetros: +// - lon: Longitud en bytes de la reserva +// Devuelve: +// Un puntero a la zona de memoria reservada que ha sido previamente rellena con zeros o nulos +//______________________________________________________________________________________________________ +char* reservaMemoria(int lon) +{ + char *mem; + + mem=(char*)malloc(lon); + if(mem!=NULL) + memset(mem,0,lon); + return(mem); +} +//______________________________________________________________________________________________________ +// Función: ampliaMemoria +// +// Descripción: +// Amplia memoria para una variable +// Parámetros: +// - ptr: Puntero al buffer de memoria que se quiere ampliar +// - lon: Longitud en bytes de la amplicación +// Devuelve: +// Un puntero a la zona de memoria reservada que ha sido previamente rellena con zeros o nulos +//______________________________________________________________________________________________________ +char* ampliaMemoria(char* ptr,int lon) +{ + char *mem; + + mem=(char*)realloc(ptr,lon*sizeof(char*)); + if(mem!=NULL) + return(mem); + return(NULL); +} +//______________________________________________________________________________________________________ +// Función: liberaMemoria +// +// Descripción: +// Libera memoria para una variable +// Parámetros: +// - ptr: Puntero al buffer de memoria que se quiere liberar +// Devuelve: +// Nada +//______________________________________________________________________________________________________ +void liberaMemoria(void* ptr) +{ + if(ptr){ + free (ptr); + } +} +// ________________________________________________________________________________________________________ +// Función: splitCadena +// +// Descripción: +// Trocea una cadena según un carácter delimitador +// Parámetros: +// - trozos: Array de punteros a cadenas +// - cadena: Cadena a trocear +// - chd: Carácter delimitador +// Devuelve: +// Número de trozos en que se divide la cadena +// ________________________________________________________________________________________________________ +int splitCadena(char **trozos,char *cadena, char chd) +{ + int w=0; + if(cadena==NULL) return(w); + + trozos[w++]=cadena; + while(*cadena!='\0'){ + if(*cadena==chd){ + *cadena='\0'; + if(*(cadena+1)!='\0') + trozos[w++]=cadena+1; + } + cadena++; + } + return(w); // Devuelve el número de trozos +} +// ________________________________________________________________________________________________________ +// Función: sustituir +// +// Descripción: +// Sustituye las apariciones de un caracter por otro en una cadena +// Parámetros: +// - cadena: Cadena a convertir +// - cho: Caracter a sustituir +// - chs: Caracter sustituto +// ________________________________________________________________________________________________________ +void sustituir(char *cadena,char cho,char chs) +{ + int x=0; + + while(cadena[x]!=0) { + if (cadena[x]==cho) + cadena[x]=chs; + x++; + } +} +// ________________________________________________________________________________________________________ +// Función: escaparCadena +// +// Descripción: +// Sustituye las apariciones de un caracter comila simple ' por \' +// Parámetros: +// - cadena: Cadena a escapar +// Devuelve: +// La cadena con las comillas simples sustituidas por \' +// ________________________________________________________________________________________________________ +char* escaparCadena(char *cadena) +{ + int b,c; + char *buffer; + + buffer = (char*) reservaMemoria(strlen(cadena)*2); // Toma memoria para el buffer de conversión + if (buffer == NULL) { // No hay memoria suficiente para el buffer + return (FALSE); + } + + c=b=0; + while(cadena[c]!=0) { + if (cadena[c]=='\''){ + buffer[b++]='\\'; + buffer[b++]='\''; + } + else{ + buffer[b++]=cadena[c]; + } + c++; + } + return(buffer); +} +// ________________________________________________________________________________________________________ +// Función: StrToUpper +// +// Descripción: +// Convierta una cadena en mayúsculas +// Parámetros: +// - cadena: Cadena a convertir +// ________________________________________________________________________________________________________ +char* StrToUpper(char *cadena) +{ + int x=0; + + while(cadena[x]!=0) { + if (cadena[x] >= 'a' && cadena[x] <= 'z') { + cadena[x] -= 32; + } + x++; + } + return(cadena); +} +// ________________________________________________________________________________________________________ +// Función: StrToUpper +// +// Descripción: +// Convierta una cadena en mayúsculas +// Parámetros: +// - cadena: Cadena a convertir +// ________________________________________________________________________________________________________ +char* StrToLower(char *cadena) +{ + int x=0; + + while(cadena[x]!=0) { + if (cadena[x] >= 'A' && cadena[x] <= 'Z') { + cadena[x] += 32; + } + x++; + } + return(cadena); +} +// ________________________________________________________________________________________________________ +// Función: INTROaFINCAD +// +// Descripción: +// Cambia caracteres INTROS por fin de cadena ('\0') en una trama +// Parametros: +// - parametros: Puntero a los parametros de la trama +// - lon: Longitud de la cadena de parametros +// ________________________________________________________________________________________________________ +void INTROaFINCAD(TRAMA* ptrTrama) +{ + char *i,*a,*b; + + a=ptrTrama->parametros; + b=a+ptrTrama->lonprm; + for(i=a;iparametros; + b=a+ptrTrama->lonprm; + for(i=a;iparametros; + b=a+ptrTrama->lonprm; + for(pos=a;pos=0;i--){ + if(cadena[i]<32) + cadena[i]='\0'; + else + return(cadena); + } + return(cadena); +} +// ________________________________________________________________________________________________________ +// Función: mandaTrama +// +// Descripción: +// Envía una trama por la red +// Parametros: +// - sock : El socket del host al que se dirige la trama +// - trama: El contenido de la trama +// - lon: Longitud de la parte de parametros de la trama que se va a mandar +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +BOOLEAN mandaTrama(SOCKET *sock, TRAMA* ptrTrama) +{ + int lonprm; + char *buffer,hlonprm[LONHEXPRM+1]; + BOOLEAN res; + + lonprm=strlen(ptrTrama->parametros); + sprintf(hlonprm,"%05X",LONGITUD_CABECERATRAMA+LONHEXPRM+lonprm); // Convierte en hexadecimal la longitud + + buffer=reservaMemoria(LONGITUD_CABECERATRAMA+LONHEXPRM+lonprm); // Longitud total de la trama + if(buffer==NULL) + return(FALSE); + memcpy(buffer,ptrTrama,LONGITUD_CABECERATRAMA); // Copia cabecera de trama + memcpy(&buffer[LONGITUD_CABECERATRAMA],hlonprm,LONHEXPRM); // Copia longitud de la trama + memcpy(&buffer[LONGITUD_CABECERATRAMA+LONHEXPRM],ptrTrama->parametros,lonprm); + res=sendData(sock,buffer,LONGITUD_CABECERATRAMA+LONHEXPRM+lonprm); + liberaMemoria(buffer); + return (res); +} +// ________________________________________________________________________________________________________ +// Función: sendData +// +// Descripción: +// Envía datos por la red a través de un socket +// Parametros: +// - sock : El socket por donde se envía +// - datos: El contenido a enviar +// - lon: Cantidad de bites a enviar +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +BOOLEAN sendData(SOCKET *sock, char* datos,int lon) +{ + int idx,ret; + idx = 0; + while (lon > 0) { + ret = send(*sock,&datos[idx],lon, 0); + if (ret == 0) { // Conexión cerrada por parte del cliente (Graceful close) + break; + } + else{ + if (ret == SOCKET_ERROR) + return (FALSE); + } + lon -= ret; + idx += ret; + } + return (TRUE); +} +// ________________________________________________________________________________________________________ +// Función: recibeTrama +// +// Descripción: +// Recibe una trama por la red +// Parametros: +// - sock : El socket del cliente +// - trama: El buffer para recibir la trama +// Devuelve: +// Un puntero a una estrucutra TRAMA o NULL si ha habido algún error +// FALSE: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +TRAMA* recibeTrama(SOCKET *sock) +{ + int ret,lon,lSize; + char *buffer,*bufferd,bloque[LONBLK],*hlonprm; + TRAMA * ptrTrama; + + lon=lSize=0; + do{ + if(!recData(sock,bloque,LONBLK,&ret)) // Lee bloque + return(NULL); + + if (lon==0 && lSize==0 && ret==0) // Comprueba trama válida + return(NULL); + + if(lSize==0){ // Comprueba tipo de trama y longitud total de los parámetros + if (strncmp(bloque, "@JMMLCAMDJ_MCDJ",15)!=0) + return(NULL); // No se reconoce la trama + hlonprm=reservaMemoria(LONHEXPRM+1); + if(!hlonprm) return(NULL); + memcpy(hlonprm,&bloque[LONGITUD_CABECERATRAMA],LONHEXPRM); + lSize=strtol(hlonprm,NULL,16); // Longitud total de la trama con los parametros encriptados + liberaMemoria(hlonprm); + buffer=(char*)reservaMemoria(lSize); // Toma memoria para la trama completa + if(!buffer) + return(NULL); + } + + if(ret>0){ // Datos recibidos + memcpy(&buffer[lon],bloque,ret); // Añade bloque + lon+=ret; + } + }while(lonparametros,bufferd,lon); + liberaMemoria((char*)buffer); + ptrTrama->lonprm=lon; // Almacena longitud de los parámetros ya desencriptados + return(ptrTrama); +} +// ________________________________________________________________________________________________________ +// Función: recData +// +// Descripción: +// Recibe datos por la red a través de un socket +// Parametros: +// - sock : El socket por el que se reciben los datos +// - datos: El buffer donde se almacenan +// - lon: Cantidad máxima de bites a recibir +// - ret: Cantidad de bites recibidos (Parámetro de salida) +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +BOOLEAN recData(SOCKET *sock, char* buffer,int lon,int* ret) +{ + *ret = 0; + + while (TRUE) { // Bucle para recibir datos del cliente + *ret = recv(*sock,buffer, lon, 0); + if (*ret == 0) // Conexión cerrada por parte del cliente (Graceful close) + break; + else { + if (*ret == SOCKET_ERROR) { + return (FALSE); + } else + // Datos recibidos + break; + } + } + return(TRUE); +} +//______________________________________________________________________________________________________ +// Función: enviaFlag +// +// Descripción: +// Envia una señal de sincronización +// Parámetros: +// - socket_c: (Salida) Socket utilizado para el envío (operativo) +// - ptrTrama: contenido del mensaje +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +BOOLEAN enviaFlag(SOCKET *socket_c,TRAMA *ptrTrama) +{ + char modulo[] = "enviaFlag()"; + if (!mandaTrama(socket_c,ptrTrama)) { + errorLog(modulo,26,FALSE); + return (FALSE); + } + return(TRUE); +} +//______________________________________________________________________________________________________ +// Función: recibeFlag +// +// Descripción: +// Recibe una señal de sincronización +// Parámetros: +// - socket_c: Socket utilizadopara la recepción (operativo) +// - ptrTrama: (Salida) Contenido del mensaje +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +BOOLEAN recibeFlag(SOCKET *socket_c,TRAMA *ptrTrama) +{ + ptrTrama=recibeTrama(socket_c); + if(!ptrTrama){ + return(FALSE); + } + return(TRUE); +} +//______________________________________________________________________________________________________ +// Función: URLEncode +// +// Descripción: +// Codifica una cadena en UrlEncode +// Parámetros: +// - src: La cadena a decodificar +// Devuelve: +// La cadena decodificada +// ________________________________________________________________________________________________________ +char* URLEncode(char *src) +{ + char *dest; + int i,j=0,lon; + + lon=strlen(src); + dest=(char*)reservaMemoria(lon*2); // Reserva buffer para la cadena + for(i=0;i0) + fwrite(buffer,1,lon,f); // Lee el contenido del fichero + }while(lon>0); // Bucle para recibir datos del cliente + fclose(f); + return(TRUE); +} +//______________________________________________________________________________________________________ +// Función: initParammetros +// +// Descripción: +// Libera memoria del buffer de los parametros de la trama y vuelve a reservar espacio +// Parámetros: +// - parametros : Puntero a la zona donde están los parametros de una trama +// - lon : Tamaño de la nueva reserva de espacio para los parametros +// Devuelve: +// Un puntero a la nueva zona de memoria o NULL si ha habido algún error +// Especificaciones: +// En caso de que el parámetro lon valga cero el tamaño a reservar será el estandar +//______________________________________________________________________________________________________ +BOOLEAN initParametros(TRAMA* ptrTrama,int lon) +{ + if(lon==0) lon=LONGITUD_PARAMETROS; + ptrTrama->parametros=(char*)ampliaMemoria(ptrTrama->parametros,lon); + if(!ptrTrama->parametros) + return(FALSE); + else + return(TRUE); +} +//______________________________________________________________________________________________________ +// Función: TCPConnect +// +// Descripción: +// Crea un socket y lo conecta a un servidor +// Parámetros: +// - ips : La Dirección IP del servidor +// - port : Puerto para la comunicación +// Devuelve: +// Un socket para comunicaciones por protocolo TCP +//______________________________________________________________________________________________________ +SOCKET TCPConnect(char *ips,char* port) +{ + SOCKET s; + struct sockaddr_in server; + char modulo[] = "TCPConnect()"; + + s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (s == INVALID_SOCKET){ + return (INVALID_SOCKET); + } + server.sin_family = AF_INET; + server.sin_port = htons((short)atoi(port)); + server.sin_addr.s_addr = inet_addr(ips); + + if (connect(s, (struct sockaddr *)&server, sizeof(server)) == INVALID_SOCKET){ + errorLog(modulo,38,TRUE); + return (INVALID_SOCKET); + } + + return(s); +} +//______________________________________________________________________________________________________ +// Función: AbreConexion +// +// Descripción: +// Abre la conexión entre el cliente y el servidor de administración +// Parámetros: +// - Ninguno +// Devuelve: +// Un socket de cliente para comunicaciones +//______________________________________________________________________________________________________ +SOCKET abreConexion(void) +{ + int swloop=0; + SOCKET s; + + while(swlooparroba='@'; // Cabecera de la trama + strncpy(ptrTrama->identificador,"JMMLCAMDJ_MCDJ",14); // identificador de la trama + ptrTrama->tipo=tipo; // Tipo de mensaje + + if (!mandaTrama(socket_c,ptrTrama)) { + errorLog(modulo,26,FALSE); + return (FALSE); + } + return(TRUE); +} +//______________________________________________________________________________________________________ +// Función: recibeMensaje +// +// Descripción: +// Recibe un mensaje del servidor de Administración +// Parámetros: +// - socket_c: Socket utilizadopara la recepción +// - ptrTrama: (Salida) Contenido del mensaje +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +TRAMA* recibeMensaje(SOCKET *socket_c) +{ + TRAMA* ptrTrama; + char modulo[] = "recibeMensaje()"; + + ptrTrama=recibeTrama(socket_c); + if(!ptrTrama){ + errorLog(modulo,17,FALSE); + return(NULL); + } + return(ptrTrama); +} + +// ________________________________________________________________________________________________________ diff --git a/native/Sources/Includes/ogAdmLib.h b/native/Sources/Includes/ogAdmLib.h new file mode 100644 index 0000000..42279a3 --- /dev/null +++ b/native/Sources/Includes/ogAdmLib.h @@ -0,0 +1,319 @@ +// ************************************************************************************************************************************************** +// Libreria: ogAdmLib +// Autor: José Manuel Alonso (E.T.S.I.I.) Universidad de Sevilla +// Fecha Creación: Marzo-2010 +// Fecha Última modificación: Marzo-2010 +// Nombre del fichero: ogAdmLib.h +// Descripción: Este fichero implementa el archivo de cabecera de la libreria ogAdmLib +// ************************************************************************************************************************************************** +// ________________________________________________________________________________________________________ +// Valores definidos +// ________________________________________________________________________________________________________ +#define LONSTD 1024 // Longitud de memoria estandar +#define LONINT 16 // Longitud de memoria estandar para un número entero +#define LONFIL 1024 // Longitud de memoria estandar para nombres de archivo completos (incluido path) +#define LONIP 16 // Longitud de memoria estandar para cadenas que contiene una dirección IP +#define LONMAC 16 // Longitud de memoria estandar para cadenas que contiene una dirección MAC +#define LONSQL 8192 // Longitud de memoria estandar para una sentencia SQL +#define LONPRM 4098 // Longitud estandar de los parámetros del fichero de configuración del servicio +#define LONSCP 4098 // Longitud estandar de los parámetros de las tramas +#define LONFUN 512 // Longitud estandar de los nombres de las funciones que procesan las tramas +#define LONSUC 4098 // Longitud de los mensajes de sucesos +#define LONBLK 8192 // Longitud de los paquetes de tramas leidos cada vez +#define MAXPRM 20 // Máximo número de parámeros del fichero de configuración del servicio +#define MAXPAR 128 // Maximo numero de particiones manejadas por el sistema, ahora con GPT es 128 +#define MAXLONURL 1024 // Longitud máxima de una dirección url con parámetros + +#define LONHEXPRM 5 // Longitud del campo que contiene el tamaño de la cadena de parámetros +#define LONGITUD_CABECERATRAMA 16 // Longitud de la cabecera de las tramas +#define LONGITUD_PARAMETROS 8192 // Longitud estandar de la información de la trama (parámetros) +#define MAXCMD_PARAMETROS 200 // Máximo número de parámetros de una trama + +#define MAXIMOS_CLIENTES 4000 // Máximo número de conexiones con ordenadores clientes +#define MAXIMAS_FUNCIONES LONSTD // Máximo número de funciones que procesan los mensajes entre servicio y clientes +#define MAXIMAS_LINEAS 3000 // Longitud máxima de lineas en un archivo de comandos + +#define AUTOINCORPORACION_OFF 0x0000 // Los ordenadores no se pueden dar de alta automáticamente +#define AUTOINCORPORACION_ONA 0x0001 // Los ordenadores se pueden dar de alta automáticamente si existe el aula +#define AUTOINCORPORACION_ONX 0x0002 // Los ordenadores se pueden dar de alta automáticamentee y si no existe el aula la crea + +#define DEBUG_BAJO 1 // Nivel de debug bajo +#define DEBUG_MEDIO 2 // Nivel de debug medio +#define DEBUG_ALTO 3 // Nivel de debug alto +#define DEBUG_MAXIMO 4 // Nivel de debug máximo + +#define CLIENTE_OCUPADO "BSY" // Cliente ocupado +#define CLIENTE_APAGADO "OFF" // Cliente apagado +#define CLIENTE_INICIANDO "INI" // Cliente iniciando + +#define CLIENTE_OPENGNSYS "OPG" // Cliente Opengnsys + +#define CLIENTE_WIN "WIN" // Cliente Windows genérico +#define CLIENTE_WNT "WNT" // Windows NT +#define CLIENTE_W2K "W2K" // Windows 2000 +#define CLIENTE_WS2 "WS2" // Windows Server 2003 +#define CLIENTE_WXP "WXP" // Cliente Windows XP +#define CLIENTE_W95 "W95" // Windows 95 +#define CLIENTE_W98 "W98" // Windows 98 +#define CLIENTE_WML "WML" // Windows Milenium +#define CLIENTE_MS2 "MS2" // MsDos +#define CLIENTE_WVI "WVI" // Cliente Windows Vista +#define CLIENTE_WI7 "WI7" // Cliente Windows 7 + +#define CLIENTE_LNX "LNX" // Cliente Linux + +#define ACCION_SINRESULTADO 0 // Sin resultado +#define ACCION_EXITOSA 1 // Finalizada con éxito +#define ACCION_FALLIDA 2 // Finalizada con errores + +#define ACCION_INICIADA 1 // Acción activa +#define ACCION_DETENIDA 2 // Acción momentanemente parada +#define ACCION_FINALIZADA 3 // Accion finalizada + +#define EJECUCION_COMANDO 1 +#define EJECUCION_PROCEDIMIENTO 2 +#define EJECUCION_TAREA 3 +#define EJECUCION_RESERVA 4 + +#define AMBITO_CENTROS 0x01 +#define AMBITO_GRUPOSAULAS 0x02 +#define AMBITO_AULAS 0x04 +#define AMBITO_GRUPOSORDENADORES 0x08 +#define AMBITO_ORDENADORES 0x10 + +// Código de los tipos de mensajes +#define MSG_COMANDO '1' // Mensaje del tipo comando +#define MSG_NOTIFICACION '2' // Respuesta a la ejecución un comando +#define MSG_PETICION '3' // Petición de cualquier actuación +#define MSG_RESPUESTA '4' // Respuesta a una petición +#define MSG_INFORMACION '5' // Envío de cualquier información sin espera de confirmación o respuesta + +#define ANNOREF 2009 // Año de referencia base + +#define LONGITUD_SCRIPTSALIDA 131072 // Longitud máxima de la información devuelta por una función de interface +#define MAXARGS 16 // Número máximo de argumentos enviados a un scripts +#define MAXCNX 5 // Máximos intentos de conexión al servidor de Administración + +#define PUERTO_WAKEUP 9 // Puerto wake up + +#define MAXHARDWARE 128 // Máximos elementos hardware a detectar +#define MAXSOFTWARE 8096 // Máximos elementos software a detectar +// ________________________________________________________________________________________________________ +// Tipos definidos +// ________________________________________________________________________________________________________ +typedef unsigned long DWORD; +typedef unsigned short WORD; +typedef int BOOLEAN; +typedef char BYTE; +typedef int SOCKET; +typedef void* LPVOID; + +#define TRUE 1 +#define FALSE 0 + +#define SOCKET_ERROR (-1) +#define INVALID_SOCKET (SOCKET)(~0) + +#define LEER 0 +#define ESCRIBIR 1 + +#define CHARNULL '\0' + +// ________________________________________________________________________________________________________ +// Variables globales +// ________________________________________________________________________________________________________ +char szPathFileCfg[LONSTD],szPathFileLog[LONSTD]; +int ndebug; // Nivel de debuger + +typedef struct{ // Estructura de las tramas + char arroba; // Caracter arroba siempre + char identificador[14]; // Identificador de la trama, siempre JMMLCAMDJ_MCDJ + char tipo; // Tipo de mensaje + long lonprm; // Longitud en hexadecimal de los parámetros + char *parametros; // Parámetros de la trama +}TRAMA; +// ________________________________________________________________________________________________________ +// Tabla de errores +// ________________________________________________________________________________________________________ +const char* tbErrores[]={"Se han generado errores. No se puede continuar la ejecución de este módulo",\ + "001-El nombre del fichero de configuración del programa está vacío",\ + "002-No existe fichero de configuración del programa",\ + "003-No hay memoria suficiente para el buffer",\ + "004-Error en el fichero de configuración del programa. No se ha definido el parámetro SERVIDORADM",\ + "005-Error en el fichero de configuración del programa. No se ha definido el parámetro PUERTO",\ + "006-Error en el fichero de configuración del programa. No se ha definido el parámetro USUARIO",\ + "007-Error en el fichero de configuración del programa. No se ha definido el parámetro PASSWORD",\ + "008-Error en el fichero de configuración del programa. No se ha definido el parámetro DATASOURCE",\ + "009-Error en el fichero de configuración del programa. No se ha definido el parámetro CATALOG",\ + "010-Error en los parámetros de ejecución del programa. Debe especificar el fichero de configuración",\ + "011-Error en los parámetros de ejecución del programa. Debe especificar el fichero de log",\ + "012-Error de sintaxis en los parámetros de ejecución del programa: Debe especificar -f nombre_del_fichero_de_configuración_del_programa -l nombre_del_fichero_de_log_del_programa -d nivel de debug",\ + "013-Error al crear socket ***socket() fallo",\ + "014-Error al enlazar socket al interface ***bind() fallo",\ + "015-Error al acceptar conexión de clientes ***accept() fallo",\ + "016-Error al crear hebra de cliente en módulo main()",\ + "017-Error al recibir trama ***recv() fallo",\ + "018-No se reconoce el mensaje enviado",\ + "019-Trama recibida NO válida",\ + "020-No se puede establecer conexión con la base de datos",\ + "021-No se han podido recuperar los datos de la consulta o bien insertar, modificar o eliminar datos",\ + "022-El cliente no se ha sido dado de alta en la base de datos del sistema. Se rechaza su petición de inclusión",\ + "023-Ha habido algún problema en la incorporación automática del cliente",\ + "024-Ha habido algún problema en la actualización de la configuración del cliente",\ + "025-La tabla de clientes está llena, no pueden registrarse más clientes en el sistema",\ + "026-Error al enviar trama ***send() fallo",\ + "027-No se encuentra Repositorio del cliente",\ + "028-Ha ocurrido algún error al tomar las particiones",\ + "029-Ha ocurrido algún problema en el proceso de inclusión del cliente. Se rechaza su petición",\ + "030-Ha ocurrido algún problema en el proceso de respuesta al comando",\ + "031-No se ha encontrado la acción a notificar es posible que se haya eliminado el registro",\ + "032-Ha ocurrido algún problema en el envío del comando",\ + "033-Error en el fichero de configuración del programa. No se ha definido el parámetro PATHSCRIPTS",\ + "034-Error en el fichero de configuración del programa. No se ha definido el parámetro URLMENU",\ + "035-Error en el fichero de configuración del programa. No se ha definido el parámetro URLMSG",\ + "036-No se ha podido recuperar la configuración de las particiones del disco",\ + "037-Ha ocurrido algún problema en el proceso de inclusión del cliente",\ + "038-No se ha podido establecer conexión con el Servidor de Administración",\ + "039-Ha ocurrido algún problema al procesar la trama recibida",\ + "040-Se han recibido parámetros con valores no válidos",\ + "041-Ha ocurrido algún problema en el proceso de inclusión del cliente",\ + "042-Ha ocurrido algún problema al enviar una petición de comandos o tareas pendientes al Servidor de Administración",\ + "043-Ha ocurrido algún problema al enviar una petición de comandos interactivos al Servidor de Administración",\ + "044-Ha ocurrido algún problema al enviar una respuesta de comandos al servidor",\ + "045-Ha ocurrido algún problema al recibir una petición de comandos o tareas pendientes desde el Servidor de Administración",\ + "046-Ha ocurrido algún problema al recibir un comando interactivo desde el Servidor de Administración",\ + "047-El cliente no está registrado en la tabla de sockest del sistema",\ + "048-Error al configurar opción BROADCAST para socket: setsockopt(SO_BROADCAST)",\ + "049-Error al enviar trama magic packet",\ + "050-Ha ocurrido algún problema al enviar un fichero por la red",\ + "051-Error en el fichero de configuración del programa. No se ha definido el parámetro PATHLOGFIL",\ + "052-No se puede crear archivo temporal para ejecución de Comandos",\ + "053-Ha ocurrido algún problema al procesar el Inventario Hardware del cliente",\ + "054-Existe un tipo de hardware que no está registrado",\ + "055-Ha ocurrido algún problema al actualizar el hardware del cliente",\ + "056-Error en el fichero de configuración del programa. No se ha definido el parámetro PATHINTERFACE",\ + "057-Ha ocurrido algún problema al enviar un archivo por la red",\ + "058-Ha ocurrido algún problema al recibir un archivo por la red",\ + "059-Error al crear la hebra DHCP o BOOTP",\ + "060-Error al crear la hebra TFTP",\ + "061-Error al crear socket para servicio DHCP",\ + "062-Error al enlazar socket con interface para servicio DHCP",\ + "063-No hay puertos libres para la hebra del servicio",\ + "064-Error al crear estructura de control para protocolo DHCP",\ + "065-Error al recibir mensaje DHCP. Se para el servicio",\ + "066-Error al crear la hebra cliente DHCP",\ + "067-Error al crear socket para servicio BOOTP",\ + "068-Error al enlazar socket con interface para servicio BOOTP",\ + "069-Error al crear estructura de control para protocolo BOOTP",\ + "070-Error al recibir mensaje BOOTP. Se para el servicio",\ + "071-Error al crear la hebra cliente BOOTP",\ + "072-Error al crear socket para servicio TFTP",\ + "073-Error al enlazar socket con interface para servicio TFTP",\ + "074-Error al crear estructura de control para protocolo TFTP",\ + "075-Error al recibir mensaje TFTP. Se para el servicio",\ + "076-Error al crear la hebra cliente TFTP",\ + "077-No se encontró opción DHCP",\ + "078-ERROR TFTP",\ + "079-Error al recibir mensaje TFTP en hebra cliente",\ + "080-Error al recibir mensaje DHCP",\ + "081-Error al crear socket de usuario para hebra",\ + "082-Ha ocurrido algún problema al procesar el Inventario software del cliente",\ + "083-Ha ocurrido algún problema al actualizar el software del cliente",\ + "084-Ha ocurrido algún problema al reiniciar la sesión del cliente",\ + "085-No se ha podido recuperar la dirección IP del cliente",\ + "086-Error al ejecutar el comando",\ + "087-Error al leer o escribir el contenido del archivo de eco de consola remota",\ + "088-Ha habido algún problerma al procesar la caché",\ + "089-Error en el fichero de configuración del programa. No se ha definido el parámetro URLMENU",\ + "090-Error en el fichero de configuración del programa. No se ha definido el parámetro URLMSG",\ + "091-Ha habido algún problema al enviar un mensaje de tipo petición al Servidor",\ + "092-Error en el fichero de configuración del programa. No se ha definido el parámetro IPLOCAL",\ + "093-No se puede cargar la librería Windows para trabajar con sockets",\ + "094-Ha habido algún problerma al procesar la actualización después de crear una imagen",\ + "095-Ha habido algún problerma al procesar la actualización después de restaurar una imagen",\ + "096-Ha habido algún problerma al procesar la actualización después de crear un software incremental",\ + "097-Este fichero de log está obsoleto, este proceso usa ahora syslog para gestionar los mensajes de log",\ +}; +// ________________________________________________________________________________________________________ +// Tabla de mensajes +// ________________________________________________________________________________________________________ +const char* tbMensajes[]={"",\ + "001-Inicio de sesion",\ + "002-Petición de inclusión de cliente",\ + "003-Abriendo sesión en el servidor de Administración",\ + "004-Cliente iniciado",\ + "005-Ejecución de archivo Autoexec",\ + "006-Procesa comandos pendientes",\ + "007-Acciones pendientes procesadas",\ + "008-Ejecución del script",\ + "009-Parámetro del script",\ + "010-Ha ocurrido algún error en la creación del proceso hijo",\ + "011-Aviso: La información de salida del script excede de la longitud permitida. Puede haberse truncado",\ + "012-Información devuelta por el script",\ + "013-Estatus de finalización del script",\ + "014-Configuración de particiones",\ + "015-Enviando petición de inclusión en el sistema al Servidor de Administración",\ + "016-Recibiendo respuesta de inclusión desde el Servidor de Administración",\ + "017-Enviando petición de comandos o tareas pendientes al Servidor de Administración",\ + "018-Recibiendo respuesta de comandos o tareas pendientes desde el Servidor de Administración",\ + "019-Disponibilidad de comandos activada",\ + "020-Disponibilidad de comandos desactivada",\ + "021-Ejecución de comando",\ + "022-Sin eco",\ + "023-Procesando caché",\ + "024-Repositorio iniciado",\ + +}; +// ________________________________________________________________________________________________________ +// Prototipo de funciones +// ________________________________________________________________________________________________________ +struct tm * tomaHora(); +void registraLog(const char *,const char *,int ); +void errorLog(const char *,int ,int); +#define og_log(err, swe) errorLog(__FUNCTION__, err, swe) +void errorInfo(const char *,char *); +#define og_info(err) errorInfo(__FUNCTION__, err) +void infoLog(int); +void infoDebug(char*); +BOOLEAN validacionParametros(int,char**,int); +char* reservaMemoria(int); +char* ampliaMemoria(char*,int); +void liberaMemoria(void*); +BOOLEAN initParametros(TRAMA*,int); +int splitCadena(char **,char *, char); +void sustituir(char *,char ,char ); +char* StrToUpper(char *); +char* StrToLower(char *); +void INTROaFINCAD(TRAMA*); +void FINCADaINTRO(TRAMA*); +int cuentaIPES(char*); +char *tomaParametro(const char*,TRAMA*); +char *copiaParametro(const char*,TRAMA *); +BOOLEAN contieneIP(char *,char *); +char* rTrim(char *); +SOCKET TCPConnect(char *,char *); +SOCKET abreConexion(void); +BOOLEAN enviaMensaje(SOCKET *,TRAMA *,char); +TRAMA* recibeMensaje(SOCKET *); +BOOLEAN mandaTrama(SOCKET*,TRAMA*); +BOOLEAN sendData(SOCKET *, char* ,int ); +BOOLEAN enviaTrama(SOCKET *,TRAMA *); +TRAMA* recibeTrama(SOCKET*); +BOOLEAN recData(SOCKET *,char*,int,int*); +BOOLEAN sendFlag(SOCKET *, char* ,int ); +BOOLEAN recibeFlag(SOCKET*,TRAMA*); +char* URLEncode(char *); +char* URLDecode(char *); +char* leeArchivo(char*); +int lonArchivo(char *); +BOOLEAN escribeArchivo(char *,char*); +BOOLEAN sendArchivo(SOCKET *,char *); +BOOLEAN recArchivo(SOCKET *,char *); +SOCKET TCPConnect(char *,char*); + +#include /* for offsetof. */ + +#define container_of(ptr, type, member) ({ \ + typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + diff --git a/native/Sources/Services/ogAdmAgent/Makefile b/native/Sources/Services/ogAdmAgent/Makefile new file mode 100644 index 0000000..b9d39b9 --- /dev/null +++ b/native/Sources/Services/ogAdmAgent/Makefile @@ -0,0 +1,42 @@ +# makefile + +# Nombre del proyecto +PROYECTO := ogAdmAgent + +# Directorio de instalación +INSTALL_DIR := /opt/opengnsys + +# Opciones de compilacion +CFLAGS := $(shell mysql_config --cflags) +CFLAGS += -g -Wall -I../../Includes +CPPFLAGS := $(CFLAGS) + +# Opciones de linkado +LDFLAGS := -Wl,--no-as-needed $(shell mysql_config --libs) -lpthread + +# Ficheros objetos +OBJS := ../../Includes/Database.o sources/ogAdmAgent.o + + +all: $(PROYECTO) + +$(PROYECTO): $(OBJS) + g++ $(LDFLAGS) $(OBJS) -o $(PROYECTO) + +install: $(PROYECTO) + cp $(PROYECTO) $(INSTALL_DIR)/sbin + cp $(PROYECTO).cfg $(INSTALL_DIR)/etc + +clean: + rm -f $(PROYECTO) $(OBJS) + +uninstall: clean + rm -f /usr/local/sbin/$(PROYECTO) /usr/local/etc/$(PROYECTO).cfg + +sources/%.o: sources/%.cpp + g++ $(CPPFLAGS) -c -o"$@" "$<" + +sources/%.o: sources/%.c + gcc $(CFLAGS) -c -o"$@" "$<" + + diff --git a/native/Sources/Services/ogAdmAgent/ogAdmAgent.cfg b/native/Sources/Services/ogAdmAgent/ogAdmAgent.cfg new file mode 100644 index 0000000..20126d3 --- /dev/null +++ b/native/Sources/Services/ogAdmAgent/ogAdmAgent.cfg @@ -0,0 +1,7 @@ +ServidorAdm=SERVERIP +PUERTO=2008 +USUARIO=DBUSER +PASSWORD=DBPASSWORD +datasource=localhost +CATALOG=DATABASE + diff --git a/native/Sources/Services/ogAdmAgent/sources/ogAdmAgent.cpp b/native/Sources/Services/ogAdmAgent/sources/ogAdmAgent.cpp new file mode 100644 index 0000000..6bf2351 --- /dev/null +++ b/native/Sources/Services/ogAdmAgent/sources/ogAdmAgent.cpp @@ -0,0 +1,914 @@ +// ******************************************************************************************************** +// Servicio: ogAdmAgent +// Autor: José Manuel Alonso (E.T.S.I.I.) Universidad de Sevilla +// Fecha Creación: Marzo-2010 +// Fecha Última modificación: Marzo-2010 +// Nombre del fichero: ogAdmAgent.cpp +// Descripción: Este fichero implementa el servicio agente del sistema. Revisa a intervalos +// regulares la base de datos para comprobar si existen acciones programadas. +// ******************************************************************************************************** +#include "ogAdmAgent.h" +#include "ogAdmLib.c" +//________________________________________________________________________________________________________ +// Función: tomaConfiguracion +// +// Descripción: +// Lee el fichero de configuración del servicio +// Parámetros: +// filecfg : Ruta completa al fichero de configuración +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//________________________________________________________________________________________________________ +BOOLEAN tomaConfiguracion(char* filecfg) +{ + char modulo[] = "tomaConfiguracion()"; + + if (filecfg == NULL || strlen(filecfg) == 0) { + errorLog(modulo, 1, FALSE); // Fichero de configuración del servicio vacío + return (FALSE); + } + FILE *fcfg; + long lSize; + char * buffer, *lineas[MAXPRM], *dualparametro[2]; + int i, numlin, resul; + + fcfg = fopen(filecfg, "rt"); + if (fcfg == NULL) { + errorLog(modulo, 2, FALSE); // No existe fichero de configuración del servicio + return (FALSE); + } + + fseek(fcfg, 0, SEEK_END); + lSize = ftell(fcfg); // Obtiene tamaño del fichero. + rewind(fcfg); + buffer = (char*) reservaMemoria(lSize + 1); // Toma memoria para el buffer de lectura. + if (buffer == NULL) { // No hay memoria suficiente para el buffer + errorLog(modulo, 3, FALSE); + return (FALSE); + } + fread(buffer, 1, lSize, fcfg); // Lee contenido del fichero + buffer[lSize] = (char) NULL; + fclose(fcfg); + + servidoradm[0] = (char) NULL; //inicializar variables globales + puerto[0] = (char) NULL; + usuario[0] = (char) NULL; + pasguor[0] = (char) NULL; + datasource[0] = (char) NULL; + catalog[0] = (char) NULL; + + numlin = splitCadena(lineas, buffer, '\n'); + for (i = 0; i < numlin; i++) { + splitCadena(dualparametro, lineas[i], '='); + resul = strcmp(StrToUpper(dualparametro[0]), "SERVIDORADM"); + if (resul == 0) + strcpy(servidoradm, dualparametro[1]); + resul = strcmp(StrToUpper(dualparametro[0]), "PUERTO"); + if (resul == 0) + strcpy(puerto, dualparametro[1]); + resul = strcmp(StrToUpper(dualparametro[0]), "USUARIO"); + if (resul == 0) + strcpy(usuario, dualparametro[1]); + resul = strcmp(StrToUpper(dualparametro[0]), "PASSWORD"); + if (resul == 0) + strcpy(pasguor, dualparametro[1]); + resul = strcmp(StrToUpper(dualparametro[0]), "DATASOURCE"); + if (resul == 0) + strcpy(datasource, dualparametro[1]); + resul = strcmp(StrToUpper(dualparametro[0]), "CATALOG"); + if (resul == 0) + strcpy(catalog, dualparametro[1]); + } + if (servidoradm[0] == (char) NULL) { + errorLog(modulo, 4, FALSE); // Falta parámetro SERVIDORADM + return (FALSE); + } + if (puerto[0] == (char) NULL) { + errorLog(modulo, 5, FALSE); // Falta parámetro PUERTO + return (FALSE); + } + if (usuario[0] == (char) NULL) { + errorLog(modulo, 6, FALSE); // Falta parámetro USUARIO + return (FALSE); + } + if (pasguor[0] == (char) NULL) { + errorLog(modulo, 7, FALSE); // Falta parámetro PASSWORD + return (FALSE); + } + if (datasource[0] == (char) NULL) { + errorLog(modulo, 8, FALSE); // Falta parámetro DATASOURCE + return (FALSE); + } + if (catalog[0] == (char) NULL) { + errorLog(modulo, 9, FALSE); // Falta parámetro CATALOG + return (FALSE); + } + return (TRUE); +} +// ________________________________________________________________________________________________________ +// +// Función: diadelaSemana +// +// Descripción: +// Calcula el número del día de la semana que corresponde a una fecha +// Parámetros: +// - dia: Un día +// - mes: Un mes +// - anno: Un año +// Devuelve: +// El número del día de la semana: 1=Lunes, 2=martes ... 6=sábado 7=domingo +// ________________________________________________________________________________________________________ + +int diadelaSemana(WORD dia,WORD mes,WORD anno) +{ + int i,cont,dias_anuales; + int desplazamiento_dias=6; + int orddiasem; + + cont =0; + for (i=1900;i0) cociente++; + return(cociente); +} +// ________________________________________________________________________________________________________ +// +// Función: buscaAccion +// +// Descripción: +// Busca en la base de datos, acciones programadas +// Parámetros: +// - db: Objeto base de datos (operativo) +// - dia : Día actual del mes +// - mes : mes en curso +// - anno : Año en curso +// - hora : Hora actual +// - minutos : Minutos actuales +// - diasemana : Dia de la semana 1=lunes,2=martes ... ( 0 Domingo) +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ + +BOOLEAN buscaAccion(Database db,WORD dia,WORD mes,WORD anno,WORD hora,WORD minutos,WORD diasemana) +{ + char msglog[LONSTD], sqlstr[LONSQL]; + Table tbl; + BYTE swampm,bitsemana; + int ordsem,ordulsem,ordiasem_1,maxdias; + int sesionprog; + char modulo[] = "buscaAccion()"; + + /* Año de comienzo */ + anno=anno-ANNOREF; // + /* Preparación hora */ + if(hora>11){ + hora-=12; + swampm=1; // Es P.M. + } + else + swampm=0; // Es am + /* Preparación semana */ + if(diasemana==0) diasemana=7; // El domingo + + // Cuestión semanas + ordiasem_1=diadelaSemana(1,mes,anno+2009); + ordsem=semanadelMes(ordiasem_1,dia); // Calcula el número de la semana + if (mes!=2) // Toma el último día de ese mes + maxdias=dias_meses[mes]; + else{ + if (bisiesto(anno+ANNOREF)) + maxdias=29; + else + maxdias=28; + } + ordulsem=semanadelMes(ordiasem_1,maxdias); // Calcula el número de la última semana + bitsemana=HEX_semanas[ordsem]; + if(ordsem==ordulsem) // Si es la última semana del mes + bitsemana|=HEX_semanas[6]; + + sprintf(sqlstr,"SELECT DISTINCT idprogramacion,tipoaccion,identificador,sesion,idcentro,"\ + "tareas.descripcion as descritarea"\ + " FROM programaciones"\ + " LEFT OUTER JOIN tareas ON tareas.idtarea=programaciones.identificador"\ + " WHERE suspendida=0 "\ + " AND (annos & %d <> 0) "\ + " AND (meses & %d<>0) "\ + " AND ((diario & %d<>0) OR (dias & %d<>0) OR (semanas & %d<>0))"\ + " AND (horas & %d<>0) AND ampm=%d AND minutos=%d",\ + HEX_annos[anno],\ + HEX_meses[mes],\ + HEX_dias[dia],\ + HEX_diasemana[diasemana],\ + bitsemana,\ + HEX_horas[hora],\ + swampm,minutos); + + if (!db.Execute(sqlstr, tbl)) { // Error al leer + errorLog(modulo, 21, FALSE); + db.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + if(tbl.ISEOF()){ + return(TRUE); // No hay acciones programadas + } + + while(!tbl.ISEOF()){ + if(!tbl.Get("idprogramacion",idprogramacion)){ + tbl.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + if(!tbl.Get("tipoaccion",tipoaccion)){ + tbl.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + if(!tbl.Get("identificador",idtipoaccion)){ + tbl.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + if(!tbl.Get("sesion",sesionprog)){ + tbl.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + if(!tbl.Get("idcentro",idcentro)){ + tbl.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + + if(tipoaccion==EJECUCION_COMANDO){ // Es una programación de un comando + return(ejecutarComando(db,idprogramacion,sesionprog)); + } + else{ + + if(tipoaccion==EJECUCION_TAREA){ + if(!tbl.Get("descritarea",descriaccion)){ + tbl.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + return(ejecutarTarea(db,idprogramacion,idtipoaccion)); + } + else{ + if(tipoaccion==EJECUCION_RESERVA){ + EjecutarReserva(idtipoaccion,db); // Es una programación de un trabajo + } + } + } + tbl.MoveNext(); + } + return(TRUE); +} +// ________________________________________________________________________________________________________ +// +// Función: ejecutarComando +// +// Descripción: +// Ejecuta un comando programado +// Parámetros: +// - db: Objeto base de datos (operativo) +// - idcomando: Identificador del comando +// - sesion: Sesión correspondiente al comando cuando se grabó en la tabla acciones +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ + +BOOLEAN ejecutarComando(Database db,int idprogramacion,int sesion ) +{ + struct tm* st; + char msglog[LONSTD], sqlstr[LONSQL]; + char fechahorareg[24]; + char modulo[] = "ejecutarComando()"; + + st = tomaHora(); + sprintf(fechahorareg,"%d/%d/%d %d:%d:%d", st->tm_year + 1900, st->tm_mon + 1, + st->tm_mday, st->tm_hour, st->tm_min, st->tm_sec); + + sprintf(sqlstr,"UPDATE acciones SET estado=%d,idprogramacion=%d,fechahorareg='%s'"\ + " WHERE sesion=%d", ACCION_INICIADA,idprogramacion,fechahorareg,sesion); + + if (!db.Execute(sqlstr)) { // Error al recuperar los datos + errorLog(modulo, 21, FALSE); + db.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + return(enviaPeticion(idprogramacion)); +} +// ________________________________________________________________________________________________________ +// +// Función: ejecutarProcedimiento +// +// Descripción: +// Ejecuta un procedimiento programado +// Parámetros: +// - db: Objeto base de datos (operativo) +// - idprocedimiento: Identificador del procedimiento +// - ambito: Ãmbito de aplicación +// - idambito: Identificador del ámbito +// - restrambito: cadena con los identificadores de los ordenadores a los que se aplica la acción +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ + +BOOLEAN ejecutarProcedimiento(Database db,int idprocedimiento,int ambito,int idambito,char* restrambito) +{ + char msglog[LONSTD], sqlstr[LONSQL],*parametros; + Table tbl; + int procedimientoid,idcomando,lonprm; + char modulo[] = "ejecutarProcedimiento()"; + + sprintf(sqlstr,"SELECT idcomando,procedimientoid,parametros,length(parametros) as lonprm"\ + " FROM procedimientos_acciones"\ + " WHERE idprocedimiento=%d ORDER BY orden",idprocedimiento); + + if (!db.Execute(sqlstr, tbl)) { // Error al leer + errorLog(modulo, 21, FALSE); + db.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + + if(tbl.ISEOF()){ + return(TRUE); // No exustde tarea + } + while(!tbl.ISEOF()){ + if(!tbl.Get("procedimientoid",procedimientoid)){ + tbl.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + if(procedimientoid>0){ // Procedimiento recursivo + if(!ejecutarProcedimiento(db,procedimientoid,ambito,idambito,restrambito)){ + return(false); + } + } + else{ + if(!tbl.Get("lonprm",lonprm)){ + tbl.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + parametros = reservaMemoria(lonprm+1); // Reserva para almacenar los parametros del procedimiento + if (parametros == NULL) { + errorLog(modulo, 3, FALSE); + return (FALSE); + } + if(!tbl.Get("parametros",parametros)){ + tbl.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + liberaMemoria(parametros); + return (FALSE); + } + if(!tbl.Get("idcomando",idcomando)){ + tbl.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + + if(!insertaComando(db,idcomando,parametros,idprocedimiento,ambito,idambito,restrambito)) { + + liberaMemoria(parametros); + return(false); + } + liberaMemoria(parametros); + } + tbl.MoveNext(); + } + return(TRUE); +} +// ________________________________________________________________________________________________________ +// +// Función: ejecutarTarea +// +// Descripción: +// Ejecuta una tarea programada +// Parámetros: +// - db: Objeto base de datos (operativo) +// - idtarea: Identificador de la tarea +// - idprogramacion: Identificador de la programación +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ + +BOOLEAN ejecutarTarea(Database db, int idprogramacion, int idtarea) +{ + char msglog[LONSTD], sqlstr[LONSQL]; + Table tbl; + int tareaid,ambito,idambito,idprocedimiento,lonrestrambito; + char* restrambito; + char modulo[] = "ejecutarTarea()"; + + sprintf(sqlstr,"SELECT tareas_acciones.orden,tareas_acciones.idprocedimiento,tareas_acciones.tareaid,"\ + " tareas.ambito,tareas.idambito,tareas.restrambito,length(tareas.restrambito) as lonrestrambito"\ + " FROM tareas"\ + " INNER JOIN tareas_acciones ON tareas_acciones.idtarea=tareas.idtarea"\ + " WHERE tareas_acciones.idtarea=%d ORDER BY tareas_acciones.orden",idtarea); + + if (!db.Execute(sqlstr, tbl)) { // Error al leer + errorLog(modulo, 21, FALSE); + db.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + + if(tbl.ISEOF()){ + return(TRUE); // No existe tarea + } + + while(!tbl.ISEOF()){ + if(!tbl.Get("tareaid",tareaid)){ + tbl.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + if(tareaid>0){ // Tarea recursiva + if(!ejecutarTarea(db,idprogramacion,tareaid)){ + return(false); + } + } + else{ + if(!tbl.Get("ambito",ambito)){ + tbl.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + if(!tbl.Get("idambito",idambito)){ + tbl.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + if(!tbl.Get("lonrestrambito",lonrestrambito)){ + tbl.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + restrambito = reservaMemoria(lonrestrambito+1); + if (restrambito == NULL) { + errorLog(modulo, 3, FALSE); + return (FALSE); + } + if(!tbl.Get("restrambito",restrambito)){ + tbl.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + liberaMemoria(restrambito); + return (FALSE); + } + liberaMemoria(restrambito); + RecopilaIpesMacs(db,ambito,idambito,restrambito); // Recopila Ipes del ámbito + if(!tbl.Get("idprocedimiento",idprocedimiento)){ + tbl.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + sesion=time(NULL); + + if(!ejecutarProcedimiento(db,idprocedimiento,ambito,idambito,restrambito)) + return(FALSE); + } + tbl.MoveNext(); + } + return(enviaPeticion(idprogramacion)); +} +// ________________________________________________________________________________________________________ +// +// Función: ejecutarTarea +// +// Descripción: +// Registra un procedimiento para un ambito concreto +// Parámetros: +// - db: Objeto base de datos (operativo) +// - idcomando: Identificador del comando +// - idprocedimiento: Identificador del procedimiento +// - ambito: Ãmbito de aplicación +// - idambito: Identificador del ámbito +// - restrambito: cadena con los identificadores de los ordenadores a los que se aplica la acción +// Devuelve: +// TRUE: Si el proceso es correcto +// FALSE: En caso de ocurrir algún error +//________________________________________________________________________________________________________ +BOOLEAN insertaComando(Database db,int idcomando,char*parametros,int idprocedimiento,int ambito,int idambito,char*restrambito) +{ + char msglog[LONSTD], sqlstr[LONSQL]; + struct tm* st; + char *auxID[MAXIMOS_CLIENTES],*auxIP[MAXIMOS_CLIENTES]; + char fechahorareg[24]; + int i; + char modulo[] = "insertaComando()"; + + if(concli==0) return(TRUE); // No hay ordenadores en el ámbito + + st = tomaHora(); + sprintf(fechahorareg,"%d/%d/%d %d:%d:%d", st->tm_year + 1900, st->tm_mon + 1, st->tm_mday, st->tm_hour, st->tm_min, st->tm_sec); + + splitCadena(auxID,cadenaid,','); + splitCadena(auxIP,cadenaip,';'); + + for (i=0;iparametros,"nfn=envioProgramacion\r"); // Nombre de la función a ejecutar en el servidor + lon+=sprintf(ptrTrama->parametros+lon,"idp=%d\r",idprogramacion); // Configuración de los Sistemas Operativos del cliente + + if(!enviaMensaje(&socket_c,ptrTrama,MSG_PETICION)){ + errorLog(modulo,91,FALSE); + liberaMemoria(ptrTrama); + return(FALSE); + } + liberaMemoria(ptrTrama); + return(TRUE); +} +// _____________________________________________________________________________________________________________ +// +// Función: RecopilaIpesMacs +// +// Descripción : +// Recopila las IPes, las Macs y los identificadores de ordenadores de un ámbito determinado +// +// Especificaciones: +// Esta Función recibe tres parámatros: +// db : Un objeto Base de datos totalmente operativo +// ambito: Tipo de ámbito +// idambito: Identificador del ámbito +// Devuelve: +// Todas los identificadores de ordenadores , las ipes y las macs de los ordenadores que componen el ámbito +// Para ellos habrá que tener declarada tres variables globales : +// cadenaid,cadenaip y cadenamac +// _____________________________________________________________________________________________________________ + +BOOLEAN RecopilaIpesMacs(Database db,int ambito,int idambito,char *restrambito) +{ + char sqlstr[LONSQL]; + + concli=0; + /* Reserva memoria al meno para caracter nulo */ + cadenaid=(char*) reservaMemoria(1); + cadenaip=(char*) reservaMemoria(1); + cadenamac=(char*) reservaMemoria(1); + + switch(ambito){ + case AMBITO_CENTROS : + sprintf(sqlstr,"SELECT ip,mac,idordenador FROM ordenadores INNER JOIN aulas WHERE ordenadores.idaula=aulas.idaula AND idcentro=%d ORDER BY ordenadores.idaula, ordenadores.ip;",idambito); + RecorreOrdenadores(db,sqlstr); + break; + case AMBITO_GRUPOSAULAS : + sprintf(sqlstr,"SELECT idgrupo FROM grupos WHERE idgrupo=%d AND tipo=%d",idambito,AMBITO_GRUPOSAULAS); + RecorreGruposAulas(db,sqlstr); + break; + case AMBITO_AULAS : + sprintf(sqlstr,"SELECT ip,mac,idordenador FROM ordenadores WHERE idaula=%d ORDER BY ip;",idambito); + RecorreOrdenadores(db,sqlstr); + break; + case AMBITO_GRUPOSORDENADORES : + sprintf(sqlstr,"SELECT idgrupo FROM gruposordenadores WHERE idgrupo=%d",idambito); + RecorreGruposOrdenadores(db,sqlstr); + break; + case AMBITO_ORDENADORES : + sprintf(sqlstr,"SELECT ip,mac,idordenador FROM ordenadores WHERE idordenador=%d",idambito); + RecorreOrdenadores(db,sqlstr); + break; + default: // Se trata de un conjunto aleatorio de ordenadores + sprintf(sqlstr,"SELECT ip,mac,idordenador FROM ordenadores WHERE idordenador IN (%s)",restrambito); + RecorreOrdenadores(db,sqlstr); + + } + return (TRUE); +} +//________________________________________________________________________________________________________ + +BOOLEAN RecorreCentro(Database db, char* sqlstr) +{ + char msglog[LONSTD]; + Table tbl; + int idcentro; + char modulo[] = "RecorreCentro()"; + + if (!db.Execute(sqlstr, tbl)) { // Error al leer + errorLog(modulo, 21, FALSE); + db.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + if(!tbl.ISEOF()){ + if(!tbl.Get("idcentro",idcentro)){ + tbl.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + sprintf(sqlstr,"SELECT idgrupo FROM grupos WHERE idcentro=%d AND grupoid=0 AND tipo=%d",idcentro,AMBITO_GRUPOSAULAS); + RecorreGruposAulas(db,sqlstr); + sprintf(sqlstr,"SELECT idaula FROM aulas WHERE idcentro=%d AND grupoid=0",idcentro); + RecorreAulas(db,sqlstr); + } + return (TRUE); +} +//________________________________________________________________________________________________________ + +BOOLEAN RecorreGruposAulas(Database db, char* sqlstr) +{ + char msglog[LONSTD]; + Table tbl; + int idgrupo; + char modulo[] = "RecorreGruposAulas()"; + + if (!db.Execute(sqlstr, tbl)) { // Error al leer + errorLog(modulo, 21, FALSE); + db.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + while(!tbl.ISEOF()){ + if(!tbl.Get("idgrupo",idgrupo)){ + tbl.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + sprintf(sqlstr,"SELECT idgrupo FROM grupos WHERE grupoid=%d AND tipo=%d",idgrupo,AMBITO_GRUPOSAULAS); + RecorreGruposAulas(db,sqlstr); + sprintf(sqlstr,"SELECT idaula FROM aulas WHERE grupoid=%d",idgrupo); + RecorreAulas(db,sqlstr); + tbl.MoveNext(); + } + return (TRUE); +} +//________________________________________________________________________________________________________ + +BOOLEAN RecorreAulas(Database db, char* sqlstr) +{ + char msglog[LONSTD]; + Table tbl; + int idaula; + char modulo[] = "RecorreAulas()"; + + if (!db.Execute(sqlstr, tbl)) { // Error al leer + errorLog(modulo, 21, FALSE); + db.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + while(!tbl.ISEOF()){ + if(!tbl.Get("idaula",idaula)){ + tbl.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + sprintf(sqlstr,"SELECT idgrupo FROM gruposordenadores WHERE idaula=%d AND grupoid=0",idaula); + RecorreGruposOrdenadores(db,sqlstr); + sprintf(sqlstr,"SELECT ip,mac,idordenador FROM ordenadores WHERE idaula=%d AND grupoid=0",idaula); + RecorreOrdenadores(db,sqlstr); + tbl.MoveNext(); + } + return (TRUE); +} +//________________________________________________________________________________________________________ + +BOOLEAN RecorreGruposOrdenadores(Database db, char* sqlstr) +{ + char msglog[LONSTD]; + Table tbl; + int idgrupo; + char modulo[] = "RecorreGruposOrdenadores()"; + + if (!db.Execute(sqlstr, tbl)) { // Error al leer + errorLog(modulo, 21, FALSE); + db.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + while(!tbl.ISEOF()){ + if(!tbl.Get("idgrupo",idgrupo)){ + tbl.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + sprintf(sqlstr,"SELECT idgrupo FROM gruposordenadores WHERE grupoid=%d",idgrupo); + RecorreGruposOrdenadores(db,sqlstr); + sprintf(sqlstr,"SELECT ip,mac,idordenador FROM ordenadores WHERE grupoid=%d",idgrupo); + RecorreOrdenadores(db,sqlstr); + tbl.MoveNext(); + } + return (TRUE); +} +//________________________________________________________________________________________________________ + +BOOLEAN RecorreOrdenadores(Database db, char* sqlstr) +{ + char msglog[LONSTD]; + Table tbl; + int idordenador,o,p,m,lon; + char ido[16],ip[LONIP],mac[LONMAC]; + char modulo[] = "RecorreOrdenadores()"; + + if (!db.Execute(sqlstr, tbl)) { // Error al leer + errorLog(modulo, 21, FALSE); + db.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + o=p=m=0; + while(!tbl.ISEOF()){ + if(!tbl.Get("idordenador",idordenador)){ + tbl.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + if(!tbl.Get("ip",ip)){ + tbl.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + if(!tbl.Get("mac",mac)){ + tbl.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + return (FALSE); + } + sprintf(ido,"%d",idordenador); + lon=strlen(ido); + if(lon>16) lon=16; + cadenaid=(char*) ampliaMemoria(cadenaid,o+lon+1); + memcpy(&cadenaid[o],ido,lon); + o+=lon; + cadenaid[o++]=','; + + lon=strlen(ip); + if(lon>16) lon=LONIP; + cadenaip=(char*) ampliaMemoria(cadenaip,p+lon+1); + memcpy(&cadenaip[p],ip,lon); + p+=lon; + cadenaip[p++]=';'; + + lon=strlen(mac); + if(lon>16) lon=LONMAC; + cadenamac=(char*) ampliaMemoria(cadenamac,m+lon+1); + memcpy(&cadenamac[m],mac,lon); + m+=lon; + cadenamac[m++]=';'; + + concli++; + tbl.MoveNext(); + } + if(o>0) o--; + if(p>0) p--; + if(m>0) m--; + cadenaid[o]='\0'; + cadenaip[p]='\0'; + cadenamac[m]='\0'; + + return (TRUE); +} +// ******************************************************************************************************** +// PROGRAMA PRINCIPAL (SERVICIO) +// ******************************************************************************************************** +int main(int argc, char *argv[]) +{ + int pseg; + char msglog[LONSTD]; + struct tm* st; + Database db; + char modulo[] = "main()"; + + /* Validación de parámetros de ejecución y lectura del fichero de configuración del servicio */ + + if (!validacionParametros(argc, argv, 5)) // Valida parámetros de ejecución + exit(EXIT_FAILURE); + + if (!tomaConfiguracion(szPathFileCfg)) { // Toma parametros de configuracion + exit(EXIT_FAILURE); + } + + /* Bucle principal del servicio */ + + while (TRUE){ + st = tomaHora(); + pseg=65-st->tm_sec; // Calcula segundos de inactividad de la hebra + sleep(pseg); + + // Toma la hora + st = tomaHora(); + + if (!db.Open(usuario, pasguor, datasource, catalog)) { // Error de conexion + errorLog(modulo, 20, FALSE); + db.GetErrorErrStr(msglog); + errorInfo(modulo, msglog); + exit(EXIT_FAILURE); + } + buscaAccion(db,st->tm_mday,st->tm_mon+1,st->tm_year+1900,st->tm_hour,st->tm_min,st->tm_wday ); + db.Close(); // Cierra conexión + } + exit(EXIT_SUCCESS); +} + + diff --git a/native/Sources/Services/ogAdmAgent/sources/ogAdmAgent.h b/native/Sources/Services/ogAdmAgent/sources/ogAdmAgent.h new file mode 100644 index 0000000..b1c429b --- /dev/null +++ b/native/Sources/Services/ogAdmAgent/sources/ogAdmAgent.h @@ -0,0 +1,81 @@ +// ******************************************************************************************************** +// Servicio: ogAdmAgent +// Autor: José Manuel Alonso (E.T.S.I.I.) Universidad de Sevilla +// Fecha Creación: Marzo-2010 +// Fecha Última modificación: Marzo-2010 +// Nombre del fichero: ogAdmAgent.h +// Descripción: Este fichero implementa el servicio agente del sistema. Revisa a intervalos +// regulares la base de datos para comprobar si existen acciones programadas. +// ******************************************************************************************************** +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Database.h" +#include "ogAdmLib.h" + +// ________________________________________________________________________________________________________ +// +// Valores hexadecimales para consultas +// ________________________________________________________________________________________________________ + +WORD HEX_annos[]={0,0x0001,0x0002,0x0004,0x0008,0x0010,0x0020,0x0040,0x0080,0x0100,0x0200,0x0400,0x0800,0x1000,0x2000,0x4000,0x8000}; +WORD HEX_meses[]={0,0x0001,0x0002,0x0004,0x0008,0x0010,0x0020,0x0040,0x0080,0x0100,0x0200,0x0400,0x0800}; +int HEX_dias[]={0,0x00000001,0x00000002,0x00000004,0x00000008,0x00000010,0x00000020,0x00000040,0x00000080,0x00000100,0x00000200, + 0x00000400,0x00000800,0x00001000,0x00002000,0x00004000,0x00008000,0x00010000,0x00020000,0x00040000,0x00080000, + 0x00100000,0x00200000,0x00400000,0x00800000,0x01000000,0x02000000,0x04000000,0x08000000,0x10000000,0x20000000,0x40000000}; +WORD HEX_horas[]={0x0001,0x0002,0x0004,0x0008,0x0010,0x0020,0x0040,0x0080,0x0100,0x0200,0x0400,0x0800 }; +BYTE HEX_diasemana[]={0,0x01,0x02,0x04,0x08,0x10,0x20,0x40}; +BYTE HEX_semanas[]={0,0x01,0x02,0x04,0x08,0x10,0x20}; +WORD dias_meses[]={0,31,28,31,30,31,30,31,31,30,31,30,31}; + +// ________________________________________________________________________________________________________ +// +// Variables globales +// ________________________________________________________________________________________________________ + +char servidoradm[LONPRM]; // Dirección IP del servidor de administración +char puerto[LONPRM]; // Puerto de comunicación +char usuario[LONPRM]; // Usuario de acceso a la base de datos +char pasguor[LONPRM]; // Password del usuario +char datasource[LONPRM]; // Dirección IP del gestor de base de datos +char catalog[LONPRM]; // Nombre de la base de datos + +int idprogramacion; +int tipoaccion,idtipoaccion; +char descriaccion[250]; +char *cadenaid; +char *cadenaip; +char *cadenamac; +int concli; +int sesion; +int idcentro; + +// ________________________________________________________________________________________________________ +// Prototipo de funciones +// ________________________________________________________________________________________________________ +BOOLEAN tomaConfiguracion(char*); +int diadelaSemana(WORD,WORD,WORD); +BOOLEAN bisiesto(WORD); +BOOLEAN buscaAccion(Database,WORD,WORD,WORD,WORD,WORD,WORD); +BOOLEAN ejecutarComando(Database,int,int ); +BOOLEAN ejecutarProcedimiento(Database,int,int,int,char*); +BOOLEAN ejecutarTarea(Database,int, int); +BOOLEAN insertaComando(Database,int,char*,int,int,int,char*); +BOOLEAN EjecutarReserva(int,Database); +BOOLEAN enviaPeticion(int); +BOOLEAN RecopilaIpesMacs(Database,int,int,char *); +BOOLEAN RecorreCentro(Database, char*); +BOOLEAN RecorreGruposAulas(Database, char*); +BOOLEAN RecorreAulas(Database, char*); +BOOLEAN RecorreGruposOrdenadores(Database, char*); +BOOLEAN RecorreOrdenadores(Database, char*); + diff --git a/native/Sources/Services/ogAdmAgent/sources/ogAdmAgent.o b/native/Sources/Services/ogAdmAgent/sources/ogAdmAgent.o new file mode 100644 index 0000000000000000000000000000000000000000..ccf5962b7794d776c2a5ff8a01b60c970ecbc92f GIT binary patch literal 127632 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 BTRFS +KERNEL=$(file -bkr /opt/opengnsys/tftpboot/ogclient/ogvmlinuz |awk '/Linux/ {for(i=1;i<=NF;i++) if($i~/version/) {v=$(i+1);printf("%d",v);sub(/[0-9]*\./,"",v);printf(".%02d",v)}}') +[ $KERNEL \< 3.07 ] && IMGFS="EXT4" || IMGFS=${IMGFS:-"BTRFS"} + +# Añade registro de incidencias. +function echolog () { + logger --tag $0 --priority local0.info "$*" + echo "$*" +} + +function mountImage () { + #@param 1 image_file + #@param 2 mount_dir + #@param 3 openciones mount + [ "$3" != "" ] && OPTMOUNT=" -o $3 " + # Si está montado nada que hacer + df |grep "$2$" 2>&1 >/dev/null && return 0 + # FS de la imagen segun el contenido del archivo .img + if file "$1" |grep -i -e " ext4 filesystem " 2>&1 > /dev/null ; then + echolog "mount $OPTMOUNT -t ext4 $1 $2" + mount $OPTMOUNT -t ext4 $1 $2 + else + echolog "mount $OPTMOUNT -o compress=lzo $1 $2" + mount $OPTMOUNT -o compress=lzo "$1" "$2" + fi + # Si esta montado da error 32, lo damos como bueno + RETVAL=$? + [ $RETVAL -eq 32 ] && RETVAL=0 + return $RETVAL +} + + +PARM1=$(echo $PARM | cut -f1 -d" ") +PARM2=$(echo $PARM | cut -f2 -d" ") +PARM3=$(echo $PARM | cut -f3 -d" ") +PARM4=$(echo $PARM | cut -f4 -d" ") + +# Determinamos el tipo de sistema de fichero de las imagenes segun el kernel que tenga + + +case "$PARM1" in + START_MULTICAST) + #1 START_MULTICAST + #2 fichero a enviar + #3 opciones de multicast + FILE="$PARM2" + MCASTOPT="$PARM3" + echolog "Ejecutar $(which sendFileMcast) $FILE $MCASTOPT" + sendFileMcast $FILE $MCASTOPT |logger --tag $0 --priority local0.info + case $? in + 1) echolog "Parametros insuficientes" + exit 1 ;; + 2) echolog "Fichero no accesible" + exit 2 ;; + 3) echolog "Sesion multicast no valida" + exit 3 ;; + esac + ;; + CREATE_IMAGE) + # Creamos/Redimensionamos el fichero de imagen y lo montamos para que se pueda escribir sobre el + #1 CREATE_IMAGE + #2 nombre imagen + #3 tipo de imagen [ img | diff ] + #4 tamaño imagen + LOOPDEVICE=$(losetup -f) + DIRMOUNT="$REPODIR/mount/$PARM2" + if [ "$PARM3" == "img" ] ; then + IMGEXT="img" + else + IMGEXT="img.diff" + DIRMOUNT="$DIRMOUNT.diff" + fi + IMGFILE="$REPODIR/$PARM2.$IMGEXT" + IMGDIR="$(dirname $IMGFILE)" + [ -d $IMGDIR ] || mkdir -p $IMGDIR + mkdir -p "$DIRMOUNT" + + LOCKFILE="$IMGFILE.lock" + + SIZEREQUIRED="$PARM4" + + # Si existe la imagen hacemos copia de seguridad y la redimesionamos + if [ -f "$IMGFILE" ]; then + echolog "La imagen $IMGFILE ya existe." + # TODO modificar ogGetImageSize + IMGSIZE=$(ls -l --block-size=1024 $IMGFILE| cut -f5 -d" ") + + if [ "$BACKUP" == "true" -o "$BACKUP" == "TRUE" -o $IMGSIZE -lt $SIZEREQUIRED ]; then + # Si la imagen esta montada la desmonto + if [ -r "$DIRMOUNT/ogimg.info" ]; then + echolog "umount $DIRMOUNT" + umount "$DIRMOUNT" + [ $? -ne 0 ] && echolog "Error: No podemos desmontar la imagen para hacer copia de seguridad o redimensionar" && exit 1 + fi + fi + + # Copia de seguridad de la imagen + if [ "$BACKUP" == "true" -o "$BACKUP" == "TRUE" ]; then + echolog "Copia de seguridad de la imagen anterior" + echolog "cp $IMGFILE $IMGFILE.ant" + cp "$IMGFILE" "$IMGFILE.ant" + echolog mv -f "$IMGFILE.torrent" "$IMGFILE.torrent.ant" 2>/dev/null + mv -f "$IMGFILE.torrent" "$IMGFILE.torrent.ant" 2>/dev/null + fi + + # Redimensionamos la imagen al tamaño necesario + if [ $IMGSIZE -lt $SIZEREQUIRED ];then + echolog "Redimensionamos la imagen $IMGFILE al tamaño necesario: $SIZEREQUIRED" + echolog "truncate --size=\">$SIZEREQUIRED\"k $IMGFILE" + truncate --size=">$SIZEREQUIRED"k $IMGFILE 2>&1 |logger --tag $0 --priority local0.info + # FS de la imagen segun el contenido del archivo .img + if file "$IMGFILE" |grep -i -e " ext4 filesystem " 2>&1 > /dev/null ; then + losetup $LOOPDEVICE "$IMGFILE" + echolog "resize2fs -f $LOOPDEVICE" + resize2fs -f $LOOPDEVICE |logger --tag $0 --priority local0.info + else + mount -o compress=lzo "$IMGFILE" "$DIRMOUNT" + echolog "btrfs filesystem resize max $DIRMOUNT" + btrfs filesystem resize max "$DIRMOUNT" 2>&1 |logger --tag $0 --priority local0.info + fi + fi + + + # Si no existe la imagen creamos el fichero. + else + echolog "Creamos la imagen $IMGFILE al tamaño necesario: $SIZEREQUIRED" + touch "$IMGFILE" + echolog "truncate --size=\">$SIZEREQUIRED\"k $IMGFILE" + truncate --size=">$SIZEREQUIRED"k $IMGFILE 2>&1 |logger --tag $0 --priority local0.info + #Formateamos imagen + echo losetup $LOOPDEVICE "$IMGFILE" + losetup $LOOPDEVICE "$IMGFILE" + if [ $IMGFS == "EXT4" ] ; then + echolog " mkfs.ext4 -i 4096 -b 4096 -L ${PARM2##*\/} $LOOPDEVICE" + mkfs.ext4 -i 4096 -b 4096 -L ${PARM2##*\/} $LOOPDEVICE + else + echolog mkfs.btrfs -L ${PARM2##*\/} $LOOPDEVICE + mkfs.btrfs -L ${PARM2##*\/} $LOOPDEVICE #&> $OGLOGCOMMAND + fi + fi + # Montamos la imagen. + mountImage "$IMGFILE" "$DIRMOUNT" + if [ $? -ne 0 ]; then + rmdir "$DIRMOUNT" + echolog "Error al crear/redimensionar la imagen" + exit 1 + fi + + #touch "$DIRMOUNT/ogimg.info" + echo "mounted"> "$LOCKFILE" + TIME2=$[SECONDS-TIME] + echolog "Fin creación/redimension de la imagen: $[TIME2/60]m $[TIME2%60]s" + # Si existe dispositivo loop lo borramos. + [ $LOOPDEVICE ] && losetup -a| grep $LOOPDEVICE &> /dev/null && losetup -d $LOOPDEVICE + # TODO: comprobar que no se quede el losetup bloqueado. + + ;; + MOUNT_IMAGE) + # Montamos el fichero imagen para que se pueda + #1 MOUNT_IMAGE + #2 nombre imagen + #3 tipo de imagen [ img | diff ] + DIRMOUNT="$REPODIR""mount/$PARM2" + if [ "$PARM3" == "img" ] ; then + IMGEXT="img" + else + IMGEXT="img.diff" + DIRMOUNT="$DIRMOUNT.diff" + fi + IMGFILE="$REPODIR/$PARM2.$IMGEXT" + echolog "Montamos la imagen $IMGFILE " + mkdir -p "$DIRMOUNT" + mountImage "$IMGFILE" "$DIRMOUNT" ro || (echolog "Error al montar la imagen"; exit 1) + ;; + UMOUNT_IMAGE) + # Desmontamos el fichero imagen. + # Si el directorio esta ocupado no se desmontará + #1 UMOUNT_IMAGE + #2 nombre imagen + #3 tipo de imagen [ img | diff ] + IMGTYPE="$PARM3" + DIRMOUNT="$REPODIR/mount/$PARM2" + if [ "$IMGTYPE" == "img" ]; then + IMGEXT="img" + else + DIRMOUNT="$DIRMOUNT.$IMGTYPE" + IMGEXT="img.diff" + fi + LOCKFILE="$REPODIR/$PARM2.$IMGEXT.lock" + echolog "Desmontamos la imagen $PARM2 $PARM3 " + umount $DIRMOUNT + rmdir $DIRMOUNT + [ -f $LOCKFILE ] && sed -i s/mounted//g $LOCKFILE + + ;; + REDUCE_IMAGE) + # Reduce el archivo de la imagen a tamaño datos + 500M + #1 REDUCE_IMAGE + #2 Nombre Imagen + #3 Tipo de imagen [ img |diff ] + DIRMOUNT="${REPODIR}mount/${PARM2}" + if [ "$PARM3" == "img" ] ; then + IMGEXT="img" + else + IMGEXT="img.diff" + DIRMOUNT="$DIRMOUNT.diff" + fi + IMGFILE="$REPODIR$PARM2.$IMGEXT" + LOCKFILE="$IMGFILE.lock" + [ ! -f $IMGFILE ] && echolog "Imagen $IMGFILE no existe" && exit 1 + + # Para imagenes EXT4 reduzco, para BTRFS solo desmonto. + if file $IMGFILE |grep -i -e " ext4 filesystem " 2>&1 > /dev/null ; then + + [ -d $DIRMOUNT ] || mkdir $DIRMOUNT + mountImage "$IMGFILE" "$DIRMOUNT" || (echolog "Error al montar la imagen $IMGFILE"; exit 1) + + + # Si el espacio libre menor que 200Mb desmontamos la imagen y nos salimos + AVAILABLE=$(df -k|grep $DIRMOUNT|awk '{print $4}') + if [ $AVAILABLE -lt 200000 ]; then + echolog "reducir imagen REPO $PARM2 $IMGEXT. tamaño minimo, nada que hacer" + umount $DIRMOUNT || (echolog "Error al desmontar la imagen $IMGFILE"; exit 1) + else + + # Calculamos la diferencia entre el tamaño interno y externo + EXTSIZE=$(ls -l --block-size=1024 $IMGFILE | cut -f5 -d" ") + INTSIZE=$(df -k|grep "$DIRMOUNT"|awk '{print $2}') + let EDGESIZE=$EXTSIZE-$INTSIZE + + echolog "reducir imagen REPO $PARM2 $IMGEXT, tamaño final: $ENDSIZE" + umount $DIRMOUNT + LOOPDEVICE=$(losetup -f) + losetup $LOOPDEVICE "$IMGFILE" + + # Redimensiono sistema de ficheros + echolog "resize2fs -fpM $LOOPDEVICE " + resize2fs -fpM $LOOPDEVICE # 2>&1 |logger --tag $0 --priority local0.info + mountImage "$IMGFILE" "$DIRMOUNT" + # Calculamos el tamaño final del archivo + INTSIZE=$(df -k|grep "$DIRMOUNT"|awk '{print $2}') + let EXTSIZE=$INTSIZE+$EDGESIZE + umount $DIRMOUNT || (echolog "Error al desmontar la imagen $IMGFILE"; exit 1) + # Si existe dispositivo loop lo borramos. + [ $LOOPDEVICE ] && losetup -a| grep $LOOPDEVICE &> /dev/null && losetup -d $LOOPDEVICE + # Corto el archivo al tamaño del sistema de ficheros + echo "truncate --size=\"$EXTSIZE\"k $IMGFILE" + echolog "truncate --size=\"$EXTSIZE\"k $IMGFILE" + truncate --size="$EXTSIZE"k $IMGFILE + fi + else + umount $DIRMOUNT || (echolog "Error al desmontar la imagen $IMGFILE"; exit 1) + fi + rmdir $DIRMOUNT + echo "reduced" >$LOCKFILE + + ;; + default) + echolog "Solicitud con parametros \"$PARM\" no realizada, no registrada o con errores" + ;; +esac diff --git a/native/Sources/Services/ogAdmServer/Makefile b/native/Sources/Services/ogAdmServer/Makefile new file mode 100644 index 0000000..0c3360e --- /dev/null +++ b/native/Sources/Services/ogAdmServer/Makefile @@ -0,0 +1,38 @@ +# makefile + +# Nombre del proyecto +PROYECTO := ogAdmServer + +# Directorio de instalación +INSTALL_DIR := /opt/opengnsys + +# Opciones de compilacion +CFLAGS := $(shell mysql_config --cflags) +CFLAGS += -g -Wall -I../../Includes + +# Opciones de linkado +LDFLAGS := -Wl,--no-as-needed $(shell mysql_config --libs) -lev -ljansson -ldbi + +# Ficheros objetos +OBJS := sources/ogAdmServer.o sources/dbi.o + + +all: $(PROYECTO) + +$(PROYECTO): $(OBJS) + gcc $(LDFLAGS) $(CFLAGS) $(OBJS) -o $(PROYECTO) + +install: $(PROYECTO) + cp $(PROYECTO) $(INSTALL_DIR)/sbin + cp $(PROYECTO).cfg $(INSTALL_DIR)/etc + +clean: + rm -f $(PROYECTO) $(OBJS) + +uninstall: clean + rm -f /usr/local/sbin/$(PROYECTO) /usr/local/etc/$(PROYECTO).cfg + +sources/%.o: sources/%.c + gcc $(CFLAGS) -c -o"$@" "$<" + + diff --git a/native/Sources/Services/ogAdmServer/Sources-Services-.txt b/native/Sources/Services/ogAdmServer/Sources-Services-.txt new file mode 100644 index 0000000..8ed387e --- /dev/null +++ b/native/Sources/Services/ogAdmServer/Sources-Services-.txt @@ -0,0 +1 @@ +Sources/Services/ogAdmServer \ No newline at end of file diff --git a/native/Sources/Services/ogAdmServer/ogAdmServer.cfg b/native/Sources/Services/ogAdmServer/ogAdmServer.cfg new file mode 100644 index 0000000..790b233 --- /dev/null +++ b/native/Sources/Services/ogAdmServer/ogAdmServer.cfg @@ -0,0 +1,8 @@ +ServidorAdm=SERVERIP +PUERTO=2008 +USUARIO=DBUSER +PASSWORD=DBPASSWORD +datasource=localhost +CATALOG=DATABASE +INTERFACE=eth0 +APITOKEN=REPOKEY diff --git a/native/Sources/Services/ogAdmServer/sources/dbi.c b/native/Sources/Services/ogAdmServer/sources/dbi.c new file mode 100644 index 0000000..cc5c5b5 --- /dev/null +++ b/native/Sources/Services/ogAdmServer/sources/dbi.c @@ -0,0 +1,37 @@ +#include "dbi.h" + +struct og_dbi *og_dbi_open(struct og_dbi_config *config) +{ + struct og_dbi *dbi; + + dbi = (struct og_dbi *)malloc(sizeof(struct og_dbi)); + if (!dbi) + return NULL; + + dbi_initialize_r(NULL, &dbi->inst); + dbi->conn = dbi_conn_new_r("mysql", dbi->inst); + if (!dbi->conn) { + free(dbi); + return NULL; + } + + dbi_conn_set_option(dbi->conn, "host", config->host); + dbi_conn_set_option(dbi->conn, "username", config->user); + dbi_conn_set_option(dbi->conn, "password", config->passwd); + dbi_conn_set_option(dbi->conn, "dbname", config->database); + dbi_conn_set_option(dbi->conn, "encoding", "UTF-8"); + + if (dbi_conn_connect(dbi->conn) < 0) { + free(dbi); + return NULL; + } + + return dbi; +} + +void og_dbi_close(struct og_dbi *dbi) +{ + dbi_conn_close(dbi->conn); + dbi_shutdown_r(dbi->inst); + free(dbi); +} diff --git a/native/Sources/Services/ogAdmServer/sources/dbi.h b/native/Sources/Services/ogAdmServer/sources/dbi.h new file mode 100644 index 0000000..bbb91d7 --- /dev/null +++ b/native/Sources/Services/ogAdmServer/sources/dbi.h @@ -0,0 +1,22 @@ +#ifndef __OG_DBI +#define __OG_DBI + +#include + +struct og_dbi_config { + const char *user; + const char *passwd; + const char *host; + const char *database; +}; + +struct og_dbi { + dbi_conn conn; + dbi_inst inst; +}; + +struct og_dbi *og_dbi_open(struct og_dbi_config *config); +void og_dbi_close(struct og_dbi *db); +#define OG_DB_IP_MAXLEN 15 + +#endif diff --git a/native/Sources/Services/ogAdmServer/sources/dbi.o b/native/Sources/Services/ogAdmServer/sources/dbi.o new file mode 100644 index 0000000000000000000000000000000000000000..f42f644975120dd17e4d0168e01b47973cd77f2c GIT binary patch literal 6728 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 +#include +#include +#include +#include +#include +#include +#include + +static char usuario[LONPRM]; // Usuario de acceso a la base de datos +static char pasguor[LONPRM]; // Password del usuario +static char datasource[LONPRM]; // Dirección IP del gestor de base de datos +static char catalog[LONPRM]; // Nombre de la base de datos +static char interface[LONPRM]; // Interface name +static char auth_token[LONPRM]; // API token + +static struct og_dbi_config dbi_config = { + .user = usuario, + .passwd = pasguor, + .host = datasource, + .database = catalog, +}; + +//________________________________________________________________________________________________________ +// Función: tomaConfiguracion +// +// Descripción: +// Lee el fichero de configuración del servicio +// Parámetros: +// filecfg : Ruta completa al fichero de configuración +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +//________________________________________________________________________________________________________ +static bool tomaConfiguracion(const char *filecfg) +{ + char buf[1024], *line; + char *key, *value; + FILE *fcfg; + + if (filecfg == NULL || strlen(filecfg) == 0) { + syslog(LOG_ERR, "No configuration file has been specified\n"); + return false; + } + + fcfg = fopen(filecfg, "rt"); + if (fcfg == NULL) { + syslog(LOG_ERR, "Cannot open configuration file `%s'\n", + filecfg); + return false; + } + + servidoradm[0] = '\0'; //inicializar variables globales + + line = fgets(buf, sizeof(buf), fcfg); + while (line != NULL) { + const char *delim = "="; + + line[strlen(line) - 1] = '\0'; + + key = strtok(line, delim); + value = strtok(NULL, delim); + + if (!strcmp(StrToUpper(key), "SERVIDORADM")) + snprintf(servidoradm, sizeof(servidoradm), "%s", value); + else if (!strcmp(StrToUpper(key), "PUERTO")) + snprintf(puerto, sizeof(puerto), "%s", value); + else if (!strcmp(StrToUpper(key), "USUARIO")) + snprintf(usuario, sizeof(usuario), "%s", value); + else if (!strcmp(StrToUpper(key), "PASSWORD")) + snprintf(pasguor, sizeof(pasguor), "%s", value); + else if (!strcmp(StrToUpper(key), "DATASOURCE")) + snprintf(datasource, sizeof(datasource), "%s", value); + else if (!strcmp(StrToUpper(key), "CATALOG")) + snprintf(catalog, sizeof(catalog), "%s", value); + else if (!strcmp(StrToUpper(key), "INTERFACE")) + snprintf(interface, sizeof(interface), "%s", value); + else if (!strcmp(StrToUpper(key), "APITOKEN")) + snprintf(auth_token, sizeof(auth_token), "%s", value); + + line = fgets(buf, sizeof(buf), fcfg); + } + + fclose(fcfg); + + if (!servidoradm[0]) { + syslog(LOG_ERR, "Missing SERVIDORADM in configuration file\n"); + return false; + } + if (!puerto[0]) { + syslog(LOG_ERR, "Missing PUERTO in configuration file\n"); + return false; + } + if (!usuario[0]) { + syslog(LOG_ERR, "Missing USUARIO in configuration file\n"); + return false; + } + if (!pasguor[0]) { + syslog(LOG_ERR, "Missing PASSWORD in configuration file\n"); + return false; + } + if (!datasource[0]) { + syslog(LOG_ERR, "Missing DATASOURCE in configuration file\n"); + return false; + } + if (!catalog[0]) { + syslog(LOG_ERR, "Missing CATALOG in configuration file\n"); + return false; + } + if (!interface[0]) + syslog(LOG_ERR, "Missing INTERFACE in configuration file\n"); + + return true; +} + +enum og_client_state { + OG_CLIENT_RECEIVING_HEADER = 0, + OG_CLIENT_RECEIVING_PAYLOAD, + OG_CLIENT_PROCESSING_REQUEST, +}; + +#define OG_MSG_REQUEST_MAXLEN 16384 + +/* Shut down connection if there is no complete message after 10 seconds. */ +#define OG_CLIENT_TIMEOUT 10 + +struct og_client { + struct ev_io io; + struct ev_timer timer; + struct sockaddr_in addr; + enum og_client_state state; + char buf[OG_MSG_REQUEST_MAXLEN]; + unsigned int buf_len; + unsigned int msg_len; + int keepalive_idx; + bool rest; + int content_length; + char auth_token[64]; +}; + +static inline int og_client_socket(const struct og_client *cli) +{ + return cli->io.fd; +} + +// ________________________________________________________________________________________________________ +// Función: clienteDisponible +// +// Descripción: +// Comprueba la disponibilidad del cliente para recibir comandos interactivos +// Parametros: +// - ip : La ip del cliente a buscar +// - idx: (Salida) Indice que ocupa el cliente, de estar ya registrado +// Devuelve: +// true: Si el cliente está disponible +// false: En caso contrario +// ________________________________________________________________________________________________________ +bool clienteDisponible(char *ip, int* idx) +{ + int estado; + + if (clienteExistente(ip, idx)) { + estado = strcmp(tbsockets[*idx].estado, CLIENTE_OCUPADO); // Cliente ocupado + if (estado == 0) + return false; + + estado = strcmp(tbsockets[*idx].estado, CLIENTE_APAGADO); // Cliente apagado + if (estado == 0) + return false; + + estado = strcmp(tbsockets[*idx].estado, CLIENTE_INICIANDO); // Cliente en proceso de inclusión + if (estado == 0) + return false; + + return true; // En caso contrario el cliente está disponible + } + return false; // Cliente no está registrado en el sistema +} +// ________________________________________________________________________________________________________ +// Función: clienteExistente +// +// Descripción: +// Comprueba si el cliente está registrado en la tabla de socket del sistema +// Parametros: +// - ip : La ip del cliente a buscar +// - idx:(Salida) Indice que ocupa el cliente, de estar ya registrado +// Devuelve: +// true: Si el cliente está registrado +// false: En caso contrario +// ________________________________________________________________________________________________________ +bool clienteExistente(char *ip, int* idx) +{ + int i; + for (i = 0; i < MAXIMOS_CLIENTES; i++) { + if (contieneIP(ip, tbsockets[i].ip)) { // Si existe la IP en la cadena + *idx = i; + return true; + } + } + return false; +} +// ________________________________________________________________________________________________________ +// Función: hayHueco +// +// Descripción: +// Esta función devuelve true o false dependiendo de que haya hueco en la tabla de sockets para un nuevo cliente. +// Parametros: +// - idx: Primer indice libre que se podrn utilizar +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +static bool hayHueco(int *idx) +{ + int i; + + for (i = 0; i < MAXIMOS_CLIENTES; i++) { + if (strncmp(tbsockets[i].ip, "\0", 1) == 0) { // Hay un hueco + *idx = i; + return true; + } + } + return false; +} +// ________________________________________________________________________________________________________ +// Función: InclusionClienteWin +// +// Descripción: +// Esta función incorpora el socket de un nuevo cliente Windows o Linux a la tabla de clientes +// Parámetros: +// - socket_c: Socket del cliente que envió el mensaje +// - ptrTrama: Trama recibida por el servidor con el contenido y los parámetros +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +static bool InclusionClienteWinLnx(TRAMA *ptrTrama, struct og_client *cli) +{ + int socket_c = og_client_socket(cli); + int res,idordenador,lon; + char nombreordenador[LONFIL]; + + res = procesoInclusionClienteWinLnx(socket_c, ptrTrama, &idordenador, + nombreordenador); + + // Prepara la trama de respuesta + + initParametros(ptrTrama,0); + ptrTrama->tipo=MSG_RESPUESTA; + lon = sprintf(ptrTrama->parametros, "nfn=RESPUESTA_InclusionClienteWinLnx\r"); + lon += sprintf(ptrTrama->parametros + lon, "ido=%d\r", idordenador); + lon += sprintf(ptrTrama->parametros + lon, "npc=%s\r", nombreordenador); + lon += sprintf(ptrTrama->parametros + lon, "res=%d\r", res); + + if (!mandaTrama(&socket_c, ptrTrama)) { + syslog(LOG_ERR, "failed to send response to %s:%hu reason=%s\n", + inet_ntoa(cli->addr.sin_addr), ntohs(cli->addr.sin_port), + strerror(errno)); + return false; + } + return true; +} +// ________________________________________________________________________________________________________ +// Función: procesoInclusionClienteWinLnx +// +// Descripción: +// Implementa el proceso de inclusión en el sistema del Cliente Windows o Linux +// Parámetros de entrada: +// - socket_c: Socket del cliente que envió el mensaje +// - ptrTrama: Trama recibida por el servidor con el contenido y los parámetros +// Parámetros de salida: +// - ido: Identificador del ordenador +// - nombreordenador: Nombre del ordenador +// Devuelve: +// Código del error producido en caso de ocurrir algún error, 0 si el proceso es correcto +// ________________________________________________________________________________________________________ +bool procesoInclusionClienteWinLnx(int socket_c, TRAMA *ptrTrama, int *idordenador, char *nombreordenador) + { + struct og_dbi *dbi; + const char *msglog; + dbi_result result; + char *iph; + + // Toma parámetros + iph = copiaParametro("iph",ptrTrama); // Toma ip + + dbi = og_dbi_open(&dbi_config); + if (!dbi) { + syslog(LOG_ERR, "cannot open connection database (%s:%d)\n", + __func__, __LINE__); + goto err_dbi_open; + } + + result = dbi_conn_queryf(dbi->conn, + "SELECT idordenador,nombreordenador FROM ordenadores " + " WHERE ordenadores.ip = '%s'", iph); + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + goto err_query_fail; + } + + if (!dbi_result_next_row(result)) { + syslog(LOG_ERR, "client does not exist in database (%s:%d)\n", + __func__, __LINE__); + dbi_result_free(result); + goto err_query_fail; + } + + syslog(LOG_DEBUG, "Client %s requesting inclusion\n", iph); + + *idordenador = dbi_result_get_uint(result, "idordenador"); + nombreordenador = (char *)dbi_result_get_string(result, "nombreordenador"); + + dbi_result_free(result); + og_dbi_close(dbi); + + if (!registraCliente(iph)) { // Incluyendo al cliente en la tabla de sokets + liberaMemoria(iph); + syslog(LOG_ERR, "client table is full\n"); + return false; + } + liberaMemoria(iph); + return true; + +err_query_fail: + og_dbi_close(dbi); +err_dbi_open: + liberaMemoria(iph); + return false; +} +// ________________________________________________________________________________________________________ +// Función: InclusionCliente +// +// Descripción: +// Esta función incorpora el socket de un nuevo cliente a la tabla de clientes y le devuelve alguna de sus propiedades: +// nombre, identificador, tamaño de la caché , etc ... +// Parámetros: +// - socket_c: Socket del cliente que envió el mensaje +// - ptrTrama: Trama recibida por el servidor con el contenido y los parámetros +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +static bool InclusionCliente(TRAMA *ptrTrama, struct og_client *cli) +{ + int socket_c = og_client_socket(cli); + + if (!procesoInclusionCliente(cli, ptrTrama)) { + initParametros(ptrTrama,0); + strcpy(ptrTrama->parametros, "nfn=RESPUESTA_InclusionCliente\rres=0\r"); + if (!mandaTrama(&socket_c, ptrTrama)) { + syslog(LOG_ERR, "failed to send response to %s:%hu reason=%s\n", + inet_ntoa(cli->addr.sin_addr), ntohs(cli->addr.sin_port), + strerror(errno)); + return false; + } + } + return true; +} +// ________________________________________________________________________________________________________ +// Función: procesoInclusionCliente +// +// Descripción: +// Implementa el proceso de inclusión en el sistema del Cliente +// Parámetros: +// - socket_c: Socket del cliente que envió el mensaje +// - ptrTrama: Trama recibida por el servidor con el contenido y los parámetros +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +bool procesoInclusionCliente(struct og_client *cli, TRAMA *ptrTrama) +{ + int socket_c = og_client_socket(cli); + const char *msglog, *str; + struct og_dbi *dbi; + dbi_result result; + + char *iph, *cfg; + char nombreordenador[LONFIL]; + int lon, resul, idordenador, cache, idproautoexec, idaula, idcentro; + + // Toma parámetros + iph = copiaParametro("iph",ptrTrama); // Toma ip + cfg = copiaParametro("cfg",ptrTrama); // Toma configuracion + + dbi = og_dbi_open(&dbi_config); + if (!dbi) { + syslog(LOG_ERR, "cannot open connection database (%s:%d)\n", + __func__, __LINE__); + goto err_dbi_open; + } + + // Recupera los datos del cliente + result = dbi_conn_queryf(dbi->conn, + "SELECT ordenadores.*,aulas.idaula,centros.idcentro FROM ordenadores " + " INNER JOIN aulas ON aulas.idaula=ordenadores.idaula" + " INNER JOIN centros ON centros.idcentro=aulas.idcentro" + " WHERE ordenadores.ip = '%s'", iph); + + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + goto err_query_fail; + } + + if (!dbi_result_next_row(result)) { + syslog(LOG_ERR, "client does not exist in database (%s:%d)\n", + __func__, __LINE__); + dbi_result_free(result); + goto err_query_fail; + } + + syslog(LOG_DEBUG, "Client %s requesting inclusion\n", iph); + + idordenador = dbi_result_get_uint(result, "idordenador"); + str = (char *)dbi_result_get_string(result, "nombreordenador"); + sprintf(nombreordenador, "%s", str); + cache = dbi_result_get_uint(result, "cache"); + idproautoexec = dbi_result_get_uint(result, "idproautoexec"); + idaula = dbi_result_get_uint(result, "idaula"); + idcentro = dbi_result_get_uint(result, "idcentro"); + dbi_result_free(result); + + resul = actualizaConfiguracion(dbi, cfg, idordenador); // Actualiza la configuración del ordenador + liberaMemoria(cfg); + og_dbi_close(dbi); + + if (!resul) { + liberaMemoria(iph); + syslog(LOG_ERR, "Cannot add client to database\n"); + return false; + } + + if (!registraCliente(iph)) { // Incluyendo al cliente en la tabla de sokets + liberaMemoria(iph); + syslog(LOG_ERR, "client table is full\n"); + return false; + } + + /*------------------------------------------------------------------------------------------------------------------------------ + Prepara la trama de respuesta + -------------------------------------------------------------------------------------------------------------------------------*/ + initParametros(ptrTrama,0); + ptrTrama->tipo=MSG_RESPUESTA; + lon = sprintf(ptrTrama->parametros, "nfn=RESPUESTA_InclusionCliente\r"); + lon += sprintf(ptrTrama->parametros + lon, "ido=%d\r", idordenador); + lon += sprintf(ptrTrama->parametros + lon, "npc=%s\r", nombreordenador); + lon += sprintf(ptrTrama->parametros + lon, "che=%d\r", cache); + lon += sprintf(ptrTrama->parametros + lon, "exe=%d\r", idproautoexec); + lon += sprintf(ptrTrama->parametros + lon, "ida=%d\r", idaula); + lon += sprintf(ptrTrama->parametros + lon, "idc=%d\r", idcentro); + lon += sprintf(ptrTrama->parametros + lon, "res=%d\r", 1); // Confirmación proceso correcto + + if (!mandaTrama(&socket_c, ptrTrama)) { + syslog(LOG_ERR, "failed to send response to %s:%hu reason=%s\n", + inet_ntoa(cli->addr.sin_addr), ntohs(cli->addr.sin_port), + strerror(errno)); + return false; + } + liberaMemoria(iph); + return true; + +err_query_fail: + og_dbi_close(dbi); +err_dbi_open: + liberaMemoria(iph); + liberaMemoria(cfg); + return false; +} +// ________________________________________________________________________________________________________ +// Función: actualizaConfiguracion +// +// Descripción: +// Esta función actualiza la base de datos con la configuracion de particiones de un cliente +// Parámetros: +// - db: Objeto base de datos (ya operativo) +// - tbl: Objeto tabla +// - cfg: cadena con una Configuración +// - ido: Identificador del ordenador cliente +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// Especificaciones: +// Los parametros de la configuración son: +// par= Número de partición +// cpt= Codigo o tipo de partición +// sfi= Sistema de ficheros que está implementado en la partición +// soi= Nombre del sistema de ficheros instalado en la partición +// tam= Tamaño de la partición +// ________________________________________________________________________________________________________ +bool actualizaConfiguracion(struct og_dbi *dbi, char *cfg, int ido) +{ + int lon, p, c,i, dato, swu, idsoi, idsfi,k; + char *ptrPar[MAXPAR], *ptrCfg[7], *ptrDual[2], tbPar[LONSTD]; + char *ser, *disk, *par, *cpt, *sfi, *soi, *tam, *uso; // Parametros de configuración. + dbi_result result, result_update; + const char *msglog; + + lon = 0; + p = splitCadena(ptrPar, cfg, '\n'); + for (i = 0; i < p; i++) { + c = splitCadena(ptrCfg, ptrPar[i], '\t'); + + // Si la 1ª línea solo incluye el número de serie del equipo; actualizar BD. + if (i == 0 && c == 1) { + splitCadena(ptrDual, ptrCfg[0], '='); + ser = ptrDual[1]; + if (strlen(ser) > 0) { + // Solo actualizar si número de serie no existía. + result = dbi_conn_queryf(dbi->conn, + "UPDATE ordenadores SET numserie='%s'" + " WHERE idordenador=%d AND numserie IS NULL", + ser, ido); + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return false; + } + dbi_result_free(result); + } + continue; + } + + // Distribución de particionado. + disk = par = cpt = sfi = soi = tam = uso = NULL; + + splitCadena(ptrDual, ptrCfg[0], '='); + disk = ptrDual[1]; // Número de disco + + splitCadena(ptrDual, ptrCfg[1], '='); + par = ptrDual[1]; // Número de partición + + k=splitCadena(ptrDual, ptrCfg[2], '='); + if(k==2){ + cpt = ptrDual[1]; // Código de partición + }else{ + cpt = (char*)"0"; + } + + k=splitCadena(ptrDual, ptrCfg[3], '='); + if(k==2){ + sfi = ptrDual[1]; // Sistema de ficheros + /* Comprueba existencia del s0xistema de ficheros instalado */ + idsfi = checkDato(dbi, sfi, "sistemasficheros", "descripcion","idsistemafichero"); + } + else + idsfi=0; + + k=splitCadena(ptrDual, ptrCfg[4], '='); + if(k==2){ // Sistema operativo detecdtado + soi = ptrDual[1]; // Nombre del S.O. instalado + /* Comprueba existencia del sistema operativo instalado */ + idsoi = checkDato(dbi, soi, "nombresos", "nombreso", "idnombreso"); + } + else + idsoi=0; + + splitCadena(ptrDual, ptrCfg[5], '='); + tam = ptrDual[1]; // Tamaño de la partición + + splitCadena(ptrDual, ptrCfg[6], '='); + uso = ptrDual[1]; // Porcentaje de uso del S.F. + + lon += sprintf(tbPar + lon, "(%s, %s),", disk, par); + + result = dbi_conn_queryf(dbi->conn, + "SELECT numdisk, numpar, tamano, uso, idsistemafichero, idnombreso" + " FROM ordenadores_particiones" + " WHERE idordenador=%d AND numdisk=%s AND numpar=%s", + ido, disk, par); + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return false; + } + if (!dbi_result_next_row(result)) { + result_update = dbi_conn_queryf(dbi->conn, + "INSERT INTO ordenadores_particiones(idordenador,numdisk,numpar,codpar,tamano,uso,idsistemafichero,idnombreso,idimagen)" + " VALUES(%d,%s,%s,0x%s,%s,%s,%d,%d,0)", + ido, disk, par, cpt, tam, uso, idsfi, idsoi); + if (!result_update) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return false; + } + dbi_result_free(result_update); + + } else { // Existe el registro + swu = true; // Se supone que algún dato ha cambiado + + dato = dbi_result_get_ulonglong(result, "tamano"); + if (atoi(tam) == dato) {// Parámetro tamaño igual al almacenado + dato = dbi_result_get_uint(result, "idsistemafichero"); + if (idsfi == dato) {// Parámetro sistema de fichero igual al almacenado + dato = dbi_result_get_uint(result, "idnombreso"); + if (idsoi == dato) {// Parámetro sistema de fichero distinto al almacenado + swu = false; // Todos los parámetros de la partición son iguales, no se actualiza + } + } + } + if (swu) { // Hay que actualizar los parámetros de la partición + result_update = dbi_conn_queryf(dbi->conn, + "UPDATE ordenadores_particiones SET " + " codpar=0x%s," + " tamano=%s," + " uso=%s," + " idsistemafichero=%d," + " idnombreso=%d," + " idimagen=0," + " idperfilsoft=0," + " fechadespliegue=NULL" + " WHERE idordenador=%d AND numdisk=%s AND numpar=%s", + cpt, tam, uso, idsfi, idsoi, ido, disk, par); + } else { // Actualizar porcentaje de uso. + result_update = dbi_conn_queryf(dbi->conn, + "UPDATE ordenadores_particiones SET " + " codpar=0x%s," + " uso=%s" + " WHERE idordenador=%d AND numdisk=%s AND numpar=%s", + cpt, uso, ido, disk, par); + } + if (!result_update) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return false; + } + + dbi_result_free(result_update); + } + dbi_result_free(result); + } + lon += sprintf(tbPar + lon, "(0,0)"); + // Eliminar particiones almacenadas que ya no existen + result_update = dbi_conn_queryf(dbi->conn, + "DELETE FROM ordenadores_particiones WHERE idordenador=%d AND (numdisk, numpar) NOT IN (%s)", + ido, tbPar); + if (!result_update) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return false; + } + dbi_result_free(result_update); + + return true; +} +// ________________________________________________________________________________________________________ +// Función: checkDato +// +// Descripción: +// Esta función comprueba si existe un dato en una tabla y si no es así lo incluye. devuelve en +// cualquier caso el identificador del registro existenet o del insertado +// Parámetros: +// - db: Objeto base de datos (ya operativo) +// - tbl: Objeto tabla +// - dato: Dato +// - tabla: Nombre de la tabla +// - nomdato: Nombre del dato en la tabla +// - nomidentificador: Nombre del identificador en la tabla +// Devuelve: +// El identificador del registro existente o el del insertado +// +// Especificaciones: +// En caso de producirse algún error se devuelve el valor 0 +// ________________________________________________________________________________________________________ + +int checkDato(struct og_dbi *dbi, char *dato, const char *tabla, + const char *nomdato, const char *nomidentificador) +{ + const char *msglog; + int identificador; + dbi_result result; + + if (strlen(dato) == 0) + return (0); // EL dato no tiene valor + result = dbi_conn_queryf(dbi->conn, + "SELECT %s FROM %s WHERE %s ='%s'", nomidentificador, + tabla, nomdato, dato); + + // Ejecuta consulta + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return (0); + } + if (!dbi_result_next_row(result)) { // Software NO existente + dbi_result_free(result); + + result = dbi_conn_queryf(dbi->conn, + "INSERT INTO %s (%s) VALUES('%s')", tabla, nomdato, dato); + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + og_info((char *)msglog); + return (0); + } + // Recupera el identificador del software + identificador = dbi_conn_sequence_last(dbi->conn, NULL); + } else { + identificador = dbi_result_get_uint(result, nomidentificador); + } + dbi_result_free(result); + + return (identificador); +} +// ________________________________________________________________________________________________________ +// Función: registraCliente +// +// Descripción: +// Incluye al cliente en la tabla de sokets +// Parámetros: +// - iph: Dirección ip del cliente +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +bool registraCliente(char *iph) +{ + int idx; + + if (!clienteExistente(iph, &idx)) { // Si no existe la IP ... + if (!hayHueco(&idx)) { // Busca hueco para el nuevo cliente + return false; // No hay huecos + } + } + strcpy(tbsockets[idx].ip, iph); // Copia IP + strcpy(tbsockets[idx].estado, CLIENTE_INICIANDO); // Actualiza el estado del cliente + return true; +} +// ________________________________________________________________________________________________________ +// Función: AutoexecCliente +// +// Descripción: +// Envía archivo de autoexec al cliente +// Parámetros: +// - socket_c: Socket del cliente que envió el mensaje +// - ptrTrama: Trama recibida por el servidor con el contenido y los parámetros +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +static bool AutoexecCliente(TRAMA *ptrTrama, struct og_client *cli) +{ + int socket_c = og_client_socket(cli); + int lon; + char *iph, *exe; + FILE *fileexe; + char fileautoexec[LONPRM]; + char parametros[LONGITUD_PARAMETROS]; + struct og_dbi *dbi; + + iph = copiaParametro("iph",ptrTrama); // Toma dirección IP del cliente + exe = copiaParametro("exe",ptrTrama); // Toma identificador del procedimiento inicial + + sprintf(fileautoexec, "/tmp/Sautoexec-%s", iph); + liberaMemoria(iph); + fileexe = fopen(fileautoexec, "wb"); // Abre fichero de script + if (fileexe == NULL) { + syslog(LOG_ERR, "cannot create temporary file\n"); + return false; + } + + dbi = og_dbi_open(&dbi_config); + if (!dbi) { + syslog(LOG_ERR, "cannot open connection database (%s:%d)\n", + __func__, __LINE__); + return false; + } + initParametros(ptrTrama,0); + if (recorreProcedimientos(dbi, parametros, fileexe, exe)) { + lon = sprintf(ptrTrama->parametros, "nfn=RESPUESTA_AutoexecCliente\r"); + lon += sprintf(ptrTrama->parametros + lon, "nfl=%s\r", fileautoexec); + lon += sprintf(ptrTrama->parametros + lon, "res=1\r"); + } else { + lon = sprintf(ptrTrama->parametros, "nfn=RESPUESTA_AutoexecCliente\r"); + lon += sprintf(ptrTrama->parametros + lon, "res=0\r"); + } + + og_dbi_close(dbi); + fclose(fileexe); + + if (!mandaTrama(&socket_c, ptrTrama)) { + liberaMemoria(exe); + syslog(LOG_ERR, "failed to send response to %s:%hu reason=%s\n", + inet_ntoa(cli->addr.sin_addr), ntohs(cli->addr.sin_port), + strerror(errno)); + return false; + } + liberaMemoria(exe); + return true; +} +// ________________________________________________________________________________________________________ +// Función: recorreProcedimientos +// +// Descripción: +// Crea un archivo con el código de un procedimiento separando cada comando por un salto de linea +// Parámetros: +// Database db,char* parametros,FILE* fileexe,char* idp +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +bool recorreProcedimientos(struct og_dbi *dbi, char *parametros, FILE *fileexe, char *idp) +{ + char idprocedimiento[LONPRM]; + int procedimientoid, lsize; + const char *msglog, *param; + dbi_result result; + + result = dbi_conn_queryf(dbi->conn, + "SELECT procedimientoid,parametros FROM procedimientos_acciones" + " WHERE idprocedimiento=%s ORDER BY orden", idp); + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return false; + } + while (dbi_result_next_row(result)) { + procedimientoid = dbi_result_get_uint(result, "procedimientoid"); + if (procedimientoid > 0) { // Procedimiento recursivo + sprintf(idprocedimiento, "%d", procedimientoid); + if (!recorreProcedimientos(dbi, parametros, fileexe, idprocedimiento)) { + return false; + } + } else { + param = dbi_result_get_string(result, "parametros"); + sprintf(parametros, "%s@", param); + lsize = strlen(parametros); + fwrite(parametros, 1, lsize, fileexe); // Escribe el código a ejecutar + } + } + dbi_result_free(result); + + return true; +} +// ________________________________________________________________________________________________________ +// Función: ComandosPendientes +// +// Descripción: +// Esta función busca en la base de datos,comandos pendientes de ejecutar por un ordenador concreto +// Parámetros: +// - socket_c: Socket del cliente que envió el mensaje +// - ptrTrama: Trama recibida por el servidor con el contenido y los parámetros +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +static bool ComandosPendientes(TRAMA *ptrTrama, struct og_client *cli) +{ + int socket_c = og_client_socket(cli); + char *ido,*iph,pids[LONPRM]; + int ids, idx; + + iph = copiaParametro("iph",ptrTrama); // Toma dirección IP + ido = copiaParametro("ido",ptrTrama); // Toma identificador del ordenador + + if (!clienteExistente(iph, &idx)) { // Busca índice del cliente + liberaMemoria(iph); + liberaMemoria(ido); + syslog(LOG_ERR, "client does not exist\n"); + return false; + } + if (buscaComandos(ido, ptrTrama, &ids)) { // Existen comandos pendientes + ptrTrama->tipo = MSG_COMANDO; + sprintf(pids, "\rids=%d\r", ids); + strcat(ptrTrama->parametros, pids); + strcpy(tbsockets[idx].estado, CLIENTE_OCUPADO); + } else { + initParametros(ptrTrama,0); + strcpy(ptrTrama->parametros, "nfn=NoComandosPtes\r"); + } + if (!mandaTrama(&socket_c, ptrTrama)) { + liberaMemoria(iph); + liberaMemoria(ido); + syslog(LOG_ERR, "failed to send response to %s:%hu reason=%s\n", + inet_ntoa(cli->addr.sin_addr), ntohs(cli->addr.sin_port), + strerror(errno)); + return false; + } + liberaMemoria(iph); + liberaMemoria(ido); + return true; +} +// ________________________________________________________________________________________________________ +// Función: buscaComandos +// +// Descripción: +// Busca en la base de datos,comandos pendientes de ejecutar por el cliente +// Parámetros: +// - ido: Identificador del ordenador +// - cmd: Parámetros del comando (Salida) +// - ids: Identificador de la sesion(Salida) +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +bool buscaComandos(char *ido, TRAMA *ptrTrama, int *ids) +{ + const char *param, *msglog; + struct og_dbi *dbi; + dbi_result result; + unsigned int lonprm; + + dbi = og_dbi_open(&dbi_config); + if (!dbi) { + syslog(LOG_ERR, "cannot open connection database (%s:%d)\n", + __func__, __LINE__); + goto err_dbi_open; + } + result = dbi_conn_queryf(dbi->conn, + "SELECT sesion, parametros"\ + " FROM acciones WHERE idordenador=%s AND estado='%d'"\ + " ORDER BY idaccion", ido, ACCION_INICIADA); + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + goto err_query_fail; + } + if (!dbi_result_next_row(result)) { + dbi_result_free(result); + og_dbi_close(dbi); + return false; // No hay comandos pendientes + } + + *ids = dbi_result_get_uint(result, "sesion"); + param = dbi_result_get_string(result, "parametros"); + lonprm = strlen(param); + + if(!initParametros(ptrTrama,lonprm + LONGITUD_PARAMETROS)){ + syslog(LOG_ERR, "%s:%d OOM\n", __FILE__, __LINE__); + goto err_init_params; + } + sprintf(ptrTrama->parametros, "%s", param); + + dbi_result_free(result); + og_dbi_close(dbi); + + return true; // Hay comandos pendientes, se toma el primero de la cola + +err_init_params: + dbi_result_free(result); +err_query_fail: + og_dbi_close(dbi); +err_dbi_open: + return false; +} +// ________________________________________________________________________________________________________ +// Función: DisponibilidadComandos +// +// Descripción: +// Esta función habilita a un cliente para recibir comandos desde la consola +// Parámetros: +// - socket_c: Socket del cliente que envió el mensaje +// - ptrTrama: Trama recibida por el servidor con el contenido y los parámetros +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +// +static bool DisponibilidadComandos(TRAMA *ptrTrama, struct og_client *cli) +{ + char *iph, *tpc; + int idx; + + iph = copiaParametro("iph",ptrTrama); // Toma ip + if (!clienteExistente(iph, &idx)) { // Busca índice del cliente + liberaMemoria(iph); + syslog(LOG_ERR, "client does not exist\n"); + return false; + } + tpc = copiaParametro("tpc",ptrTrama); // Tipo de cliente (Plataforma y S.O.) + strcpy(tbsockets[idx].estado, tpc); + cli->keepalive_idx = idx; + liberaMemoria(iph); + liberaMemoria(tpc); + return true; +} +// ________________________________________________________________________________________________________ +// Función: respuestaEstandar +// +// Descripción: +// Esta función actualiza la base de datos con el resultado de la ejecución de un comando con seguimiento +// Parámetros: +// - res: resultado de la ejecución del comando +// - der: Descripción del error si hubiese habido +// - iph: Dirección IP +// - ids: identificador de la sesión +// - ido: Identificador del ordenador que notifica +// - db: Objeto base de datos (operativo) +// - tbl: Objeto tabla +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +static bool respuestaEstandar(TRAMA *ptrTrama, char *iph, char *ido, + struct og_dbi *dbi) +{ + char *res, *ids, *der; + char fechafin[LONPRM]; + const char *msglog; + dbi_result result; + struct tm* st; + int idaccion; + + ids = copiaParametro("ids",ptrTrama); + res = copiaParametro("res",ptrTrama); + + if (ids == NULL) { + if (atoi(res) == ACCION_FALLIDA) { + liberaMemoria(res); + return false; + } + liberaMemoria(res); + return true; + } + + if (atoi(ids) == 0) { + liberaMemoria(ids); + if (atoi(res) == ACCION_FALLIDA) { + liberaMemoria(res); + return false; + } + liberaMemoria(res); + return true; + } + + result = dbi_conn_queryf(dbi->conn, + "SELECT * FROM acciones WHERE idordenador=%s" + " AND sesion=%s ORDER BY idaccion", ido,ids); + + liberaMemoria(ids); + + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return false; + } + if (!dbi_result_next_row(result)) { + syslog(LOG_ERR, "no actions available\n"); + dbi_result_free(result); + return true; + } + + idaccion = dbi_result_get_uint(result, "idaccion"); + dbi_result_free(result); + + st = tomaHora(); + sprintf(fechafin, "%d/%d/%d %d:%d:%d", st->tm_year + 1900, st->tm_mon + 1, + st->tm_mday, st->tm_hour, st->tm_min, st->tm_sec); + + der = copiaParametro("der",ptrTrama); // Toma descripción del error (si hubiera habido) + + result = dbi_conn_queryf(dbi->conn, + "UPDATE acciones"\ + " SET resultado='%s',estado='%d',fechahorafin='%s',descrinotificacion='%s'"\ + " WHERE idordenador=%s AND idaccion=%d", + res, ACCION_FINALIZADA, fechafin, der, ido, idaccion); + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + liberaMemoria(res); + liberaMemoria(der); + og_info((char *)msglog); + return false; + } + dbi_result_free(result); + + liberaMemoria(der); + + if (atoi(res) == ACCION_FALLIDA) { + liberaMemoria(res); + return false; + } + + liberaMemoria(res); + return true; +} + +static bool og_send_cmd(char *ips_array[], int ips_array_len, + const char *state, TRAMA *ptrTrama) +{ + int i, idx; + + for (i = 0; i < ips_array_len; i++) { + if (clienteDisponible(ips_array[i], &idx)) { // Si el cliente puede recibir comandos + int sock = tbsockets[idx].cli ? tbsockets[idx].cli->io.fd : -1; + + strcpy(tbsockets[idx].estado, state); // Actualiza el estado del cliente + if (sock >= 0 && !mandaTrama(&sock, ptrTrama)) { + syslog(LOG_ERR, "failed to send response to %s:%s\n", + ips_array[i], strerror(errno)); + } + } + } + return true; +} + +// ________________________________________________________________________________________________________ +// Función: enviaComando +// +// Descripción: +// Envía un comando a los clientes +// Parámetros: +// - ptrTrama: Trama recibida por el servidor con el contenido y los parámetros +// - estado: Estado en el se deja al cliente mientras se ejecuta el comando +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +bool enviaComando(TRAMA* ptrTrama, const char *estado) +{ + char *iph, *Ipes, *ptrIpes[MAXIMOS_CLIENTES]; + int lon; + + iph = copiaParametro("iph",ptrTrama); // Toma dirección/es IP + lon = strlen(iph); // Calcula longitud de la cadena de direccion/es IPE/S + Ipes = (char*) reservaMemoria(lon + 1); + if (Ipes == NULL) { + syslog(LOG_ERR, "%s:%d OOM\n", __FILE__, __LINE__); + return false; + } + + strcpy(Ipes, iph); // Copia cadena de IPES + liberaMemoria(iph); + + lon = splitCadena(ptrIpes, Ipes, ';'); + FINCADaINTRO(ptrTrama); + + if (!og_send_cmd(ptrIpes, lon, estado, ptrTrama)) + return false; + + liberaMemoria(Ipes); + return true; +} +//______________________________________________________________________________________________________ +// Función: respuestaConsola +// +// Descripción: +// Envia una respuesta a la consola sobre el resultado de la ejecución de un comando +// Parámetros: +// - socket_c: (Salida) Socket utilizado para el envío +// - res: Resultado del envío del comando +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +bool respuestaConsola(int socket_c, TRAMA *ptrTrama, int res) +{ + initParametros(ptrTrama,0); + sprintf(ptrTrama->parametros, "res=%d\r", res); + if (!mandaTrama(&socket_c, ptrTrama)) { + syslog(LOG_ERR, "%s:%d failed to send response: %s\n", + __func__, __LINE__, strerror(errno)); + return false; + } + return true; +} +// ________________________________________________________________________________________________________ +// Función: Levanta +// +// Descripción: +// Enciende ordenadores a través de la red cuyas macs se pasan como parámetro +// Parámetros: +// - iph: Cadena de direcciones ip separadas por ";" +// - mac: Cadena de direcciones mac separadas por ";" +// - mar: Método de arranque (1=Broadcast, 2=Unicast) +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +bool Levanta(char *ptrIP[], char *ptrMacs[], char *ptrNetmasks[], int lon, char *mar) +{ + unsigned int on = 1; + struct sockaddr_in local; + int i, res; + int s; + + /* Creación de socket para envío de magig packet */ + s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (s < 0) { + syslog(LOG_ERR, "cannot create socket for magic packet\n"); + return false; + } + res = setsockopt(s, SOL_SOCKET, SO_BROADCAST, (unsigned int *) &on, + sizeof(on)); + if (res < 0) { + syslog(LOG_ERR, "cannot set broadcast socket\n"); + return false; + } + + memset(&local, 0, sizeof(local)); + local.sin_family = AF_INET; + local.sin_port = htons(PUERTO_WAKEUP); + local.sin_addr.s_addr = htonl(INADDR_ANY); + + for (i = 0; i < lon; i++) { + if (!WakeUp(s, ptrIP[i], ptrMacs[i], ptrNetmasks[i], mar)) { + syslog(LOG_ERR, "problem sending magic packet\n"); + close(s); + return false; + } + } + close(s); + return true; +} + +#define OG_WOL_SEQUENCE 6 +#define OG_WOL_MACADDR_LEN 6 +#define OG_WOL_REPEAT 16 + +struct wol_msg { + char secuencia_FF[OG_WOL_SEQUENCE]; + char macbin[OG_WOL_REPEAT][OG_WOL_MACADDR_LEN]; +}; + +static bool wake_up_broadcast(int sd, struct sockaddr_in *client, + const struct wol_msg *msg, const struct in_addr *addr) +{ + struct sockaddr_in *broadcast_addr; + struct ifaddrs *ifaddr, *ifa; + int ret1, ret2; + + if (getifaddrs(&ifaddr) < 0) { + syslog(LOG_ERR, "cannot get list of addresses\n"); + return false; + } + + + client->sin_addr.s_addr = htonl(INADDR_BROADCAST); + + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL || + ifa->ifa_addr->sa_family != AF_INET || + strcmp(ifa->ifa_name, interface) != 0) + continue; + + broadcast_addr = + (struct sockaddr_in *)ifa->ifa_ifu.ifu_broadaddr; + client->sin_addr.s_addr = broadcast_addr->sin_addr.s_addr; + break; + } + freeifaddrs(ifaddr); + + ret1 = sendto(sd, msg, sizeof(*msg), 0, + (struct sockaddr *)client, sizeof(*client)); + client->sin_addr.s_addr = addr->s_addr; + ret2 = sendto(sd, msg, sizeof(*msg), 0, + (struct sockaddr *)client, sizeof(*client)); + if ((ret1 < 0) & (ret2 < 0)) { + syslog(LOG_ERR, "failed to send broadcast wol\n"); + return false; + } + + return true; +} + +static bool wake_up_unicast(int sd, struct sockaddr_in *client, + const struct wol_msg *msg, + const struct in_addr *addr) +{ + int ret; + + client->sin_addr.s_addr = addr->s_addr; + + ret = sendto(sd, msg, sizeof(*msg), 0, + (struct sockaddr *)client, sizeof(*client)); + if (ret < 0) { + syslog(LOG_ERR, "failed to send unicast wol\n"); + return false; + } + + return true; +} + + + +enum wol_delivery_type { + OG_WOL_BROADCAST = 1, + OG_WOL_UNICAST = 2 +}; + +//_____________________________________________________________________________________________________________ +// Función: WakeUp +// +// Descripción: +// Enciende el ordenador cuya MAC se pasa como parámetro +// Parámetros: +// - s : Socket para enviar trama magic packet +// - iph : Cadena con la dirección ip +// - mac : Cadena con la dirección mac en formato XXXXXXXXXXXX +// - mar: Método de arranque (1=Broadcast, 2=Unicast) +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +//_____________________________________________________________________________________________________________ +// +bool WakeUp(int s, char* iph, char *mac, char *netmask, char *mar) +{ + unsigned int macaddr[OG_WOL_MACADDR_LEN]; + char HDaddress_bin[OG_WOL_MACADDR_LEN]; + struct sockaddr_in WakeUpCliente; + struct wol_msg Trama_WakeUp; + struct in_addr addr, netmask_addr, broadcast_addr ={}; + bool ret; + int i; + + if (!inet_aton(iph, &addr)) { + syslog(LOG_ERR, "bad IP address\n"); + return false; + } + + if (!inet_aton(netmask, &netmask_addr)) { + syslog(LOG_ERR, "bad netmask address: %s\n", netmask); + return false; + } + broadcast_addr.s_addr = addr.s_addr | ~netmask_addr.s_addr; + + for (i = 0; i < 6; i++) // Primera secuencia de la trama Wake Up (0xFFFFFFFFFFFF) + Trama_WakeUp.secuencia_FF[i] = 0xFF; + + sscanf(mac, "%02x%02x%02x%02x%02x%02x", + &macaddr[0], &macaddr[1], &macaddr[2], + &macaddr[3], &macaddr[4], &macaddr[5]); + + for (i = 0; i < 6; i++) + HDaddress_bin[i] = (uint8_t)macaddr[i]; + + for (i = 0; i < 16; i++) // Segunda secuencia de la trama Wake Up , repetir 16 veces su la MAC + memcpy(&Trama_WakeUp.macbin[i][0], &HDaddress_bin, 6); + + /* Creación de socket del cliente que recibe la trama magic packet */ + WakeUpCliente.sin_family = AF_INET; + WakeUpCliente.sin_port = htons((short) PUERTO_WAKEUP); + + switch (atoi(mar)) { + case OG_WOL_BROADCAST: + ret = wake_up_broadcast(s, &WakeUpCliente, &Trama_WakeUp, &broadcast_addr ); + break; + case OG_WOL_UNICAST: + ret = wake_up_unicast(s, &WakeUpCliente, &Trama_WakeUp, &addr); + break; + default: + syslog(LOG_ERR, "unknown wol type\n"); + ret = false; + break; + } + return ret; +} +// ________________________________________________________________________________________________________ +// Función: RESPUESTA_Arrancar +// +// Descripción: +// Respuesta del cliente al comando Arrancar +// Parámetros: +// - socket_c: Socket del cliente que envió el mensaje +// - ptrTrama: Trama recibida por el servidor con el contenido y los parámetros +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +static bool RESPUESTA_Arrancar(TRAMA* ptrTrama, struct og_client *cli) +{ + struct og_dbi *dbi; + char *iph, *ido; + char *tpc; + int i; + + dbi = og_dbi_open(&dbi_config); + if (!dbi) { + syslog(LOG_ERR, "cannot open connection database (%s:%d)\n", + __func__, __LINE__); + return false; + } + + iph = copiaParametro("iph",ptrTrama); // Toma dirección ip + ido = copiaParametro("ido",ptrTrama); // Toma identificador del ordenador + + if (!respuestaEstandar(ptrTrama, iph, ido, dbi)) { + liberaMemoria(iph); + liberaMemoria(ido); + syslog(LOG_ERR, "failed to register notification\n"); + og_dbi_close(dbi); + return false; + } + + tpc = copiaParametro("tpc",ptrTrama); // Tipo de cliente (Plataforma y S.O.) + if (clienteExistente(iph, &i)) // Actualiza estado + strcpy(tbsockets[i].estado, tpc); + + liberaMemoria(iph); + liberaMemoria(ido); + liberaMemoria(tpc); + og_dbi_close(dbi); + + return true; +} +// ________________________________________________________________________________________________________ +// Función: RESPUESTA_Apagar +// +// Descripción: +// Respuesta del cliente al comando Apagar +// Parámetros: +// - socket_c: Socket del cliente que envió el mensaje +// - ptrTrama: Trama recibida por el servidor con el contenido y los parámetros +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +static bool RESPUESTA_Apagar(TRAMA* ptrTrama, struct og_client *cli) +{ + struct og_dbi *dbi; + char *iph, *ido; + int i; + + dbi = og_dbi_open(&dbi_config); + if (!dbi) { + syslog(LOG_ERR, "cannot open connection database (%s:%d)\n", + __func__, __LINE__); + return false; + } + + iph = copiaParametro("iph",ptrTrama); // Toma dirección ip + ido = copiaParametro("ido",ptrTrama); // Toma identificador del ordenador + + if (!respuestaEstandar(ptrTrama, iph, ido, dbi)) { + liberaMemoria(iph); + liberaMemoria(ido); + syslog(LOG_ERR, "failed to register notification\n"); + og_dbi_close(dbi); + return false; // Error al registrar notificacion + } + + if (clienteExistente(iph, &i)) // Actualiza estado + strcpy(tbsockets[i].estado, CLIENTE_APAGADO); + + liberaMemoria(iph); + liberaMemoria(ido); + og_dbi_close(dbi); + + return true; +} +// ________________________________________________________________________________________________________ +// Función: RESPUESTA_CrearImagen +// +// Descripción: +// Respuesta del cliente al comando CrearImagen +// Parámetros: +// - socket_c: Socket del cliente que envió el mensaje +// - ptrTrama: Trama recibida por el servidor con el contenido y los parámetros +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +static bool RESPUESTA_CrearImagen(TRAMA* ptrTrama, struct og_client *cli) +{ + char *iph, *dsk, *par, *cpt, *ipr, *ido; + struct og_dbi *dbi; + char *idi; + bool res; + + dbi = og_dbi_open(&dbi_config); + if (!dbi) { + syslog(LOG_ERR, "cannot open connection database (%s:%d)\n", + __func__, __LINE__); + return false; + } + + iph = copiaParametro("iph",ptrTrama); // Toma dirección ip + ido = copiaParametro("ido",ptrTrama); // Toma identificador del ordenador + + if (!respuestaEstandar(ptrTrama, iph, ido, dbi)) { + liberaMemoria(iph); + liberaMemoria(ido); + syslog(LOG_ERR, "failed to register notification\n"); + og_dbi_close(dbi); + return false; // Error al registrar notificacion + } + + // Acciones posteriores + idi = copiaParametro("idi",ptrTrama); + dsk = copiaParametro("dsk",ptrTrama); + par = copiaParametro("par",ptrTrama); + cpt = copiaParametro("cpt",ptrTrama); + ipr = copiaParametro("ipr",ptrTrama); + + res=actualizaCreacionImagen(dbi, idi, dsk, par, cpt, ipr, ido); + + liberaMemoria(idi); + liberaMemoria(par); + liberaMemoria(cpt); + liberaMemoria(ipr); + og_dbi_close(dbi); + + if (!res) + syslog(LOG_ERR, "Problem processing update\n"); + + return res; +} +// ________________________________________________________________________________________________________ +// Función: actualizaCreacionImagen +// +// Descripción: +// Esta función actualiza la base de datos con el resultado de la creación de una imagen +// Parámetros: +// - db: Objeto base de datos (ya operativo) +// - tbl: Objeto tabla +// - idi: Identificador de la imagen +// - dsk: Disco de donde se creó +// - par: Partición de donde se creó +// - cpt: Código de partición +// - ipr: Ip del repositorio +// - ido: Identificador del ordenador modelo +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +bool actualizaCreacionImagen(struct og_dbi *dbi, char *idi, char *dsk, + char *par, char *cpt, char *ipr, char *ido) +{ + const char *msglog; + dbi_result result; + int idr,ifs; + + /* Toma identificador del repositorio correspondiente al ordenador modelo */ + result = dbi_conn_queryf(dbi->conn, + "SELECT repositorios.idrepositorio" + " FROM repositorios" + " LEFT JOIN ordenadores USING (idrepositorio)" + " WHERE repositorios.ip='%s' AND ordenadores.idordenador=%s", ipr, ido); + + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return false; + } + if (!dbi_result_next_row(result)) { + syslog(LOG_ERR, + "repository does not exist in database (%s:%d)\n", + __func__, __LINE__); + dbi_result_free(result); + return false; + } + idr = dbi_result_get_uint(result, "idrepositorio"); + dbi_result_free(result); + + /* Toma identificador del perfilsoftware */ + result = dbi_conn_queryf(dbi->conn, + "SELECT idperfilsoft" + " FROM ordenadores_particiones" + " WHERE idordenador=%s AND numdisk=%s AND numpar=%s", ido, dsk, par); + + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return false; + } + if (!dbi_result_next_row(result)) { + syslog(LOG_ERR, + "software profile does not exist in database (%s:%d)\n", + __func__, __LINE__); + dbi_result_free(result); + return false; + } + ifs = dbi_result_get_uint(result, "idperfilsoft"); + dbi_result_free(result); + + /* Actualizar los datos de la imagen */ + result = dbi_conn_queryf(dbi->conn, + "UPDATE imagenes" + " SET idordenador=%s, numdisk=%s, numpar=%s, codpar=%s," + " idperfilsoft=%d, idrepositorio=%d," + " fechacreacion=NOW(), revision=revision+1" + " WHERE idimagen=%s", ido, dsk, par, cpt, ifs, idr, idi); + + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return false; + } + dbi_result_free(result); + + /* Actualizar los datos en el cliente */ + result = dbi_conn_queryf(dbi->conn, + "UPDATE ordenadores_particiones" + " SET idimagen=%s, revision=(SELECT revision FROM imagenes WHERE idimagen=%s)," + " fechadespliegue=NOW()" + " WHERE idordenador=%s AND numdisk=%s AND numpar=%s", + idi, idi, ido, dsk, par); + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return false; + } + dbi_result_free(result); + + return true; +} +// ________________________________________________________________________________________________________ +// Función: RESPUESTA_CrearImagenBasica +// +// Descripción: +// Respuesta del cliente al comando CrearImagenBasica +// Parámetros: +// - socket_c: Socket del cliente que envió el mensaje +// - ptrTrama: Trama recibida por el servidor con el contenido y los parámetros +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +static bool RESPUESTA_CrearImagenBasica(TRAMA* ptrTrama, struct og_client *cli) +{ + // La misma respuesta que la creación de imagen monolítica + return RESPUESTA_CrearImagen(ptrTrama, cli); +} +// ________________________________________________________________________________________________________ +// Función: RESPUESTA_CrearSoftIncremental +// +// Descripción: +// Respuesta del cliente al comando crearImagenDiferencial +// Parámetros: +// - socket_c: Socket del cliente que envió el mensaje +// - ptrTrama: Trama recibida por el servidor con el contenido y los parámetros +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +static bool RESPUESTA_CrearSoftIncremental(TRAMA* ptrTrama, struct og_client *cli) +{ + char *iph,*par,*ido,*idf; + int ifs; + const char *msglog; + struct og_dbi *dbi; + dbi_result result; + + dbi = og_dbi_open(&dbi_config); + if (!dbi) { + syslog(LOG_ERR, "cannot open connection database (%s:%d)\n", + __func__, __LINE__); + return false; + } + + iph = copiaParametro("iph",ptrTrama); // Toma dirección ip + ido = copiaParametro("ido",ptrTrama); // Toma identificador del ordenador + + if (!respuestaEstandar(ptrTrama, iph, ido, dbi)) { + og_dbi_close(dbi); + liberaMemoria(iph); + liberaMemoria(ido); + syslog(LOG_ERR, "failed to register notification\n"); + return false; + } + + par = copiaParametro("par",ptrTrama); + + /* Toma identificador del perfilsoftware creado por el inventario de software */ + result = dbi_conn_queryf(dbi->conn, + "SELECT idperfilsoft FROM ordenadores_particiones WHERE idordenador=%s AND numpar=%s", + ido, par); + liberaMemoria(iph); + liberaMemoria(ido); + liberaMemoria(par); + + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + og_dbi_close(dbi); + return false; + } + if (!dbi_result_next_row(result)) { + syslog(LOG_ERR, + "software profile does not exist in database (%s:%d)\n", + __func__, __LINE__); + dbi_result_free(result); + og_dbi_close(dbi); + return false; + } + ifs = dbi_result_get_uint(result, "idperfilsoft"); + dbi_result_free(result); + + /* Actualizar los datos de la imagen */ + idf = copiaParametro("idf", ptrTrama); + result = dbi_conn_queryf(dbi->conn, + "UPDATE imagenes SET idperfilsoft=%d WHERE idimagen=%s", + ifs, idf); + liberaMemoria(idf); + + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + og_dbi_close(dbi); + return false; + } + dbi_result_free(result); + + og_dbi_close(dbi); + + return true; +} +// ________________________________________________________________________________________________________ +// Función: RESPUESTA_RestaurarImagen +// +// Descripción: +// Respuesta del cliente al comando RestaurarImagen +// Parámetros: +// - socket_c: Socket del cliente que envió el mensaje +// - ptrTrama: Trama recibida por el servidor con el contenido y los parámetros +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +// +static bool RESPUESTA_RestaurarImagen(TRAMA* ptrTrama, struct og_client *cli) +{ + bool res; + char *iph, *ido, *idi, *dsk, *par, *ifs, *cfg; + struct og_dbi *dbi; + + dbi = og_dbi_open(&dbi_config); + if (!dbi) { + syslog(LOG_ERR, "cannot open connection database (%s:%d)\n", + __func__, __LINE__); + return false; + } + + iph = copiaParametro("iph",ptrTrama); // Toma dirección ip + ido = copiaParametro("ido",ptrTrama); // Toma identificador del ordenador + + if (!respuestaEstandar(ptrTrama, iph, ido, dbi)) { + og_dbi_close(dbi); + liberaMemoria(iph); + liberaMemoria(ido); + syslog(LOG_ERR, "failed to register notification\n"); + return false; + } + + // Acciones posteriores + idi = copiaParametro("idi",ptrTrama); // Toma identificador de la imagen + dsk = copiaParametro("dsk",ptrTrama); // Número de disco + par = copiaParametro("par",ptrTrama); // Número de partición + ifs = copiaParametro("ifs",ptrTrama); // Identificador del perfil software contenido + cfg = copiaParametro("cfg",ptrTrama); // Configuración de discos + if(cfg){ + actualizaConfiguracion(dbi, cfg, atoi(ido)); // Actualiza la configuración del ordenador + liberaMemoria(cfg); + } + res=actualizaRestauracionImagen(dbi, idi, dsk, par, ido, ifs); + + liberaMemoria(iph); + liberaMemoria(ido); + liberaMemoria(idi); + liberaMemoria(par); + liberaMemoria(ifs); + og_dbi_close(dbi); + + if(!res) + syslog(LOG_ERR, "Problem after restoring image\n"); + + return res; +} +// ________________________________________________________________________________________________________ +// +// Función: RESPUESTA_RestaurarImagenBasica +// +// Descripción: +// Respuesta del cliente al comando RestaurarImagen +// Parámetros: +// - socket_c: Socket del cliente que envió el mensaje +// - ptrTrama: Trama recibida por el servidor con el contenido y los parámetros +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +// +static bool RESPUESTA_RestaurarImagenBasica(TRAMA* ptrTrama, struct og_client *cli) +{ + return RESPUESTA_RestaurarImagen(ptrTrama, cli); +} +// ________________________________________________________________________________________________________ +// Función: RESPUESTA_RestaurarSoftIncremental +// +// Descripción: +// Respuesta del cliente al comando RestaurarSoftIncremental +// Parámetros: +// - socket_c: Socket del cliente que envió el mensaje +// - ptrTrama: Trama recibida por el servidor con el contenido y los parámetros +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +static bool RESPUESTA_RestaurarSoftIncremental(TRAMA* ptrTrama, struct og_client *cli) +{ + return RESPUESTA_RestaurarImagen(ptrTrama, cli); +} +// ________________________________________________________________________________________________________ +// Función: actualizaRestauracionImagen +// +// Descripción: +// Esta función actualiza la base de datos con el resultado de la restauración de una imagen +// Parámetros: +// - db: Objeto base de datos (ya operativo) +// - tbl: Objeto tabla +// - idi: Identificador de la imagen +// - dsk: Disco de donde se restauró +// - par: Partición de donde se restauró +// - ido: Identificador del cliente donde se restauró +// - ifs: Identificador del perfil software contenido en la imagen +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +bool actualizaRestauracionImagen(struct og_dbi *dbi, char *idi, + char *dsk, char *par, char *ido, char *ifs) +{ + const char *msglog; + dbi_result result; + + /* Actualizar los datos de la imagen */ + result = dbi_conn_queryf(dbi->conn, + "UPDATE ordenadores_particiones" + " SET idimagen=%s, idperfilsoft=%s, fechadespliegue=NOW()," + " revision=(SELECT revision FROM imagenes WHERE idimagen=%s)," + " idnombreso=IFNULL((SELECT idnombreso FROM perfilessoft WHERE idperfilsoft=%s),0)" + " WHERE idordenador=%s AND numdisk=%s AND numpar=%s", idi, ifs, idi, ifs, ido, dsk, par); + + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return false; + } + dbi_result_free(result); + + return true; +} +// ________________________________________________________________________________________________________ +// Función: RESPUESTA_EjecutarScript +// +// Descripción: +// Respuesta del cliente al comando EjecutarScript +// Parámetros: +// - socket_c: Socket del cliente que envió el mensaje +// - ptrTrama: Trama recibida por el servidor con el contenido y los parámetros +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +static bool RESPUESTA_EjecutarScript(TRAMA* ptrTrama, struct og_client *cli) +{ + char *iph, *ido,*cfg; + struct og_dbi *dbi; + bool res = true; + + dbi = og_dbi_open(&dbi_config); + if (!dbi) { + syslog(LOG_ERR, "cannot open connection database (%s:%d)\n", + __func__, __LINE__); + return false; + } + + iph = copiaParametro("iph",ptrTrama); // Toma dirección ip + ido = copiaParametro("ido",ptrTrama); // Toma identificador del ordenador + + if (!respuestaEstandar(ptrTrama, iph, ido, dbi)) { + og_dbi_close(dbi); + liberaMemoria(iph); + liberaMemoria(ido); + syslog(LOG_ERR, "failed to register notification\n"); + return false; + } + + cfg = copiaParametro("cfg",ptrTrama); // Toma configuración de particiones + if(cfg){ + res = actualizaConfiguracion(dbi, cfg, atoi(ido)); // Actualiza la configuración del ordenador + liberaMemoria(cfg); + } + + liberaMemoria(iph); + liberaMemoria(ido); + og_dbi_close(dbi); + + if (!res) + syslog(LOG_ERR, "Problem updating client configuration\n"); + + return res; +} +// ________________________________________________________________________________________________________ +// Función: RESPUESTA_InventarioHardware +// +// Descripción: +// Respuesta del cliente al comando InventarioHardware +// Parámetros: +// - socket_c: Socket del cliente que envió el mensaje +// - ptrTrama: Trama recibida por el servidor con el contenido y los parámetros +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +static bool RESPUESTA_InventarioHardware(TRAMA* ptrTrama, struct og_client *cli) +{ + bool res; + char *iph, *ido, *idc, *npc, *hrd, *buffer; + struct og_dbi *dbi; + + dbi = og_dbi_open(&dbi_config); + if (!dbi) { + syslog(LOG_ERR, "cannot open connection database (%s:%d)\n", + __func__, __LINE__); + return false; + } + + iph = copiaParametro("iph",ptrTrama); // Toma dirección ip del cliente + ido = copiaParametro("ido",ptrTrama); // Toma identificador del cliente + + if (!respuestaEstandar(ptrTrama, iph, ido, dbi)) { + og_dbi_close(dbi); + liberaMemoria(iph); + liberaMemoria(ido); + syslog(LOG_ERR, "failed to register notification\n"); + return false; + } + // Lee archivo de inventario enviado anteriormente + hrd = copiaParametro("hrd",ptrTrama); + buffer = rTrim(leeArchivo(hrd)); + + npc = copiaParametro("npc",ptrTrama); + idc = copiaParametro("idc",ptrTrama); // Toma identificador del Centro + + if (buffer) + res=actualizaHardware(dbi, buffer, ido, npc, idc); + else + res = false; + + liberaMemoria(iph); + liberaMemoria(ido); + liberaMemoria(npc); + liberaMemoria(idc); + liberaMemoria(buffer); + og_dbi_close(dbi); + + if (!res) + syslog(LOG_ERR, "Problem updating client configuration\n"); + + return res; +} +// ________________________________________________________________________________________________________ +// Función: actualizaHardware +// +// Descripción: +// Actualiza la base de datos con la configuracion hardware del cliente +// Parámetros: +// - db: Objeto base de datos (ya operativo) +// - tbl: Objeto tabla +// - hrd: cadena con el inventario hardware +// - ido: Identificador del ordenador +// - npc: Nombre del ordenador +// - idc: Identificador del centro o Unidad organizativa +// ________________________________________________________________________________________________________ +// +bool actualizaHardware(struct og_dbi *dbi, char *hrd, char *ido, char *npc, + char *idc) +{ + const char *msglog; + int idtipohardware, idperfilhard; + int lon, i, j, aux; + bool retval; + char *whard; + int tbidhardware[MAXHARDWARE]; + char *tbHardware[MAXHARDWARE],*dualHardware[2], strInt[LONINT], *idhardwares; + dbi_result result; + + /* Toma Centro (Unidad Organizativa) */ + result = dbi_conn_queryf(dbi->conn, + "SELECT idperfilhard FROM ordenadores WHERE idordenador=%s", + ido); + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return false; + } + if (!dbi_result_next_row(result)) { + syslog(LOG_ERR, "client does not exist in database (%s:%d)\n", + __func__, __LINE__); + dbi_result_free(result); + return false; + } + idperfilhard = dbi_result_get_uint(result, "idperfilhard"); + dbi_result_free(result); + + whard=escaparCadena(hrd); // Codificar comillas simples + if(!whard) + return false; + /* Recorre componentes hardware*/ + lon = splitCadena(tbHardware, whard, '\n'); + if (lon > MAXHARDWARE) + lon = MAXHARDWARE; // Limita el número de componentes hardware + /* + for (i=0;iconn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return false; + } + if (!dbi_result_next_row(result)) { // Tipo de Hardware NO existente + dbi_result_free(result); + return false; + } else { // Tipo de Hardware Existe + idtipohardware = dbi_result_get_uint(result, "idtipohardware"); + dbi_result_free(result); + + result = dbi_conn_queryf(dbi->conn, + "SELECT idhardware FROM hardwares WHERE idtipohardware=%d AND descripcion='%s'", + idtipohardware, dualHardware[1]); + + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return false; + } + + if (!dbi_result_next_row(result)) { // Hardware NO existente + dbi_result_free(result); + result = dbi_conn_queryf(dbi->conn, + "INSERT hardwares (idtipohardware,descripcion,idcentro,grupoid) " + " VALUES(%d,'%s',%s,0)", idtipohardware, + dualHardware[1], idc); + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return false; + } + + // Recupera el identificador del hardware + tbidhardware[i] = dbi_conn_sequence_last(dbi->conn, NULL); + } else { + tbidhardware[i] = dbi_result_get_uint(result, "idhardware"); + } + dbi_result_free(result); + } + } + // Ordena tabla de identificadores para cosultar si existe un pefil con esas especificaciones + + for (i = 0; i < lon - 1; i++) { + for (j = i + 1; j < lon; j++) { + if (tbidhardware[i] > tbidhardware[j]) { + aux = tbidhardware[i]; + tbidhardware[i] = tbidhardware[j]; + tbidhardware[j] = aux; + } + } + } + /* Crea cadena de identificadores de componentes hardware separados por coma */ + sprintf(strInt, "%d", tbidhardware[lon - 1]); // Pasa a cadena el último identificador que es de mayor longitud + aux = strlen(strInt); // Calcula longitud de cadena para reservar espacio a todos los perfiles + idhardwares = reservaMemoria(sizeof(aux) * lon + lon); + if (idhardwares == NULL) { + syslog(LOG_ERR, "%s:%d OOM\n", __FILE__, __LINE__); + return false; + } + aux = sprintf(idhardwares, "%d", tbidhardware[0]); + for (i = 1; i < lon; i++) + aux += sprintf(idhardwares + aux, ",%d", tbidhardware[i]); + + if (!cuestionPerfilHardware(dbi, idc, ido, idperfilhard, idhardwares, + npc, tbidhardware, lon)) { + syslog(LOG_ERR, "Problem updating client hardware\n"); + retval=false; + } + else { + retval=true; + } + liberaMemoria(whard); + liberaMemoria(idhardwares); + return (retval); +} +// ________________________________________________________________________________________________________ +// Función: cuestionPerfilHardware +// +// Descripción: +// Comprueba existencia de perfil hardware y actualización de éste para el ordenador +// Parámetros: +// - db: Objeto base de datos (ya operativo) +// - tbl: Objeto tabla +// - idc: Identificador de la Unidad organizativa donde se encuentra el cliente +// - ido: Identificador del ordenador +// - tbidhardware: Identificador del tipo de hardware +// - con: Número de componentes detectados para configurar un el perfil hardware +// - npc: Nombre del cliente +// ________________________________________________________________________________________________________ +bool cuestionPerfilHardware(struct og_dbi *dbi, char *idc, char *ido, + int idperfilhardware, char *idhardwares, char *npc, int *tbidhardware, + int lon) +{ + const char *msglog; + dbi_result result; + int i; + int nwidperfilhard; + + // Busca perfil hard del ordenador que contenga todos los componentes hardware encontrados + result = dbi_conn_queryf(dbi->conn, + "SELECT idperfilhard FROM" + " (SELECT perfileshard_hardwares.idperfilhard as idperfilhard," + " group_concat(cast(perfileshard_hardwares.idhardware AS char( 11) )" + " ORDER BY perfileshard_hardwares.idhardware SEPARATOR ',' ) AS idhardwares" + " FROM perfileshard_hardwares" + " GROUP BY perfileshard_hardwares.idperfilhard) AS temp" + " WHERE idhardwares LIKE '%s'", idhardwares); + + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return false; + } + if (!dbi_result_next_row(result)) { + // No existe un perfil hardware con esos componentes de componentes hardware, lo crea + dbi_result_free(result); + result = dbi_conn_queryf(dbi->conn, + "INSERT perfileshard (descripcion,idcentro,grupoid)" + " VALUES('Perfil hardware (%s) ',%s,0)", npc, idc); + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return false; + } + dbi_result_free(result); + + // Recupera el identificador del nuevo perfil hardware + nwidperfilhard = dbi_conn_sequence_last(dbi->conn, NULL); + + // Crea la relación entre perfiles y componenetes hardware + for (i = 0; i < lon; i++) { + result = dbi_conn_queryf(dbi->conn, + "INSERT perfileshard_hardwares (idperfilhard,idhardware)" + " VALUES(%d,%d)", nwidperfilhard, tbidhardware[i]); + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return false; + } + dbi_result_free(result); + } + } else { // Existe un perfil con todos esos componentes + nwidperfilhard = dbi_result_get_uint(result, "idperfilhard"); + dbi_result_free(result); + } + if (idperfilhardware != nwidperfilhard) { // No coinciden los perfiles + // Actualiza el identificador del perfil hardware del ordenador + result = dbi_conn_queryf(dbi->conn, + "UPDATE ordenadores SET idperfilhard=%d" + " WHERE idordenador=%s", nwidperfilhard, ido); + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return false; + } + dbi_result_free(result); + } + /* Eliminar Relación de hardwares con Perfiles hardware que quedan húerfanos */ + result = dbi_conn_queryf(dbi->conn, + "DELETE FROM perfileshard_hardwares WHERE idperfilhard IN " + " (SELECT idperfilhard FROM perfileshard WHERE idperfilhard NOT IN" + " (SELECT DISTINCT idperfilhard from ordenadores))"); + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return false; + } + dbi_result_free(result); + + /* Eliminar Perfiles hardware que quedan húerfanos */ + result = dbi_conn_queryf(dbi->conn, + "DELETE FROM perfileshard WHERE idperfilhard NOT IN" + " (SELECT DISTINCT idperfilhard FROM ordenadores)"); + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return false; + } + dbi_result_free(result); + + /* Eliminar Relación de hardwares con Perfiles hardware que quedan húerfanos */ + result = dbi_conn_queryf(dbi->conn, + "DELETE FROM perfileshard_hardwares WHERE idperfilhard NOT IN" + " (SELECT idperfilhard FROM perfileshard)"); + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return false; + } + dbi_result_free(result); + + return true; +} +// ________________________________________________________________________________________________________ +// Función: RESPUESTA_InventarioSoftware +// +// Descripción: +// Respuesta del cliente al comando InventarioSoftware +// Parámetros: +// - socket_c: Socket del cliente que envió el mensaje +// - ptrTrama: Trama recibida por el servidor con el contenido y los parámetros +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +static bool RESPUESTA_InventarioSoftware(TRAMA* ptrTrama, struct og_client *cli) +{ + bool res; + char *iph, *ido, *npc, *idc, *par, *sft, *buffer; + struct og_dbi *dbi; + + dbi = og_dbi_open(&dbi_config); + if (!dbi) { + syslog(LOG_ERR, "cannot open connection database (%s:%d)\n", + __func__, __LINE__); + return false; + } + + iph = copiaParametro("iph",ptrTrama); // Toma dirección ip + ido = copiaParametro("ido",ptrTrama); // Toma identificador del ordenador + + if (!respuestaEstandar(ptrTrama, iph, ido, dbi)) { + og_dbi_close(dbi); + liberaMemoria(iph); + liberaMemoria(ido); + syslog(LOG_ERR, "failed to register notification\n"); + return false; + } + + npc = copiaParametro("npc",ptrTrama); + idc = copiaParametro("idc",ptrTrama); // Toma identificador del Centro + par = copiaParametro("par",ptrTrama); + sft = copiaParametro("sft",ptrTrama); + + buffer = rTrim(leeArchivo(sft)); + if (buffer) + res=actualizaSoftware(dbi, buffer, par, ido, npc, idc); + else + res = false; + + liberaMemoria(iph); + liberaMemoria(ido); + liberaMemoria(npc); + liberaMemoria(idc); + liberaMemoria(par); + liberaMemoria(sft); + og_dbi_close(dbi); + + if (!res) + syslog(LOG_ERR, "cannot update software\n"); + + return res; +} +// ________________________________________________________________________________________________________ +// Función: actualizaSoftware +// +// Descripción: +// Actualiza la base de datos con la configuración software del cliente +// Parámetros: +// - db: Objeto base de datos (ya operativo) +// - tbl: Objeto tabla +// - sft: cadena con el inventario software +// - par: Número de la partición +// - ido: Identificador del ordenador del cliente en la tabla +// - npc: Nombre del ordenador +// - idc: Identificador del centro o Unidad organizativa +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// +// Versión 1.1.0: Se incluye el sistema operativo. Autora: Irina Gómez - ETSII Universidad Sevilla +// ________________________________________________________________________________________________________ +bool actualizaSoftware(struct og_dbi *dbi, char *sft, char *par,char *ido, + char *npc, char *idc) +{ + int i, j, lon, aux, idperfilsoft, idnombreso; + bool retval; + char *wsft; + int tbidsoftware[MAXSOFTWARE]; + char *tbSoftware[MAXSOFTWARE], strInt[LONINT], *idsoftwares; + const char *msglog; + dbi_result result; + + /* Toma Centro (Unidad Organizativa) y perfil software */ + result = dbi_conn_queryf(dbi->conn, + "SELECT idperfilsoft,numpar" + " FROM ordenadores_particiones" + " WHERE idordenador=%s", ido); + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return false; + } + idperfilsoft = 0; // Por defecto se supone que el ordenador no tiene aún detectado el perfil software + while (dbi_result_next_row(result)) { + aux = dbi_result_get_uint(result, "numpar"); + if (aux == atoi(par)) { // Se encuentra la partición + idperfilsoft = dbi_result_get_uint(result, "idperfilsoft"); + break; + } + } + dbi_result_free(result); + wsft=escaparCadena(sft); // Codificar comillas simples + if(!wsft) + return false; + + /* Recorre componentes software*/ + lon = splitCadena(tbSoftware, wsft, '\n'); + + if (lon == 0) + return true; // No hay lineas que procesar + if (lon > MAXSOFTWARE) + lon = MAXSOFTWARE; // Limita el número de componentes software + + idnombreso = 0; + for (i = 0; i < lon; i++) { + // Primera línea es el sistema operativo: se obtiene identificador + if (i == 0) { + idnombreso = checkDato(dbi, rTrim(tbSoftware[i]), "nombresos", "nombreso", "idnombreso"); + continue; + } + + result = dbi_conn_queryf(dbi->conn, + "SELECT idsoftware FROM softwares WHERE descripcion ='%s'", + rTrim(tbSoftware[i])); + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return false; + } + + if (!dbi_result_next_row(result)) { + dbi_result_free(result); + result = dbi_conn_queryf(dbi->conn, + "INSERT INTO softwares (idtiposoftware,descripcion,idcentro,grupoid)" + " VALUES(2,'%s',%s,0)", tbSoftware[i], idc); + if (!result) { // Error al insertar + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return false; + } + + // Recupera el identificador del software + tbidsoftware[i] = dbi_conn_sequence_last(dbi->conn, NULL); + } else { + tbidsoftware[i] = dbi_result_get_uint(result, "idsoftware"); + } + dbi_result_free(result); + } + + // Ordena tabla de identificadores para cosultar si existe un pefil con esas especificaciones + + for (i = 0; i < lon - 1; i++) { + for (j = i + 1; j < lon; j++) { + if (tbidsoftware[i] > tbidsoftware[j]) { + aux = tbidsoftware[i]; + tbidsoftware[i] = tbidsoftware[j]; + tbidsoftware[j] = aux; + } + } + } + /* Crea cadena de identificadores de componentes software separados por coma */ + sprintf(strInt, "%d", tbidsoftware[lon - 1]); // Pasa a cadena el último identificador que es de mayor longitud + aux = strlen(strInt); // Calcula longitud de cadena para reservar espacio a todos los perfiles + idsoftwares = reservaMemoria((sizeof(aux)+1) * lon + lon); + if (idsoftwares == NULL) { + syslog(LOG_ERR, "%s:%d OOM\n", __FILE__, __LINE__); + return false; + } + aux = sprintf(idsoftwares, "%d", tbidsoftware[0]); + for (i = 1; i < lon; i++) + aux += sprintf(idsoftwares + aux, ",%d", tbidsoftware[i]); + + // Comprueba existencia de perfil software y actualización de éste para el ordenador + if (!cuestionPerfilSoftware(dbi, idc, ido, idperfilsoft, idnombreso, idsoftwares, + npc, par, tbidsoftware, lon)) { + syslog(LOG_ERR, "cannot update software\n"); + og_info((char *)msglog); + retval=false; + } + else { + retval=true; + } + liberaMemoria(wsft); + liberaMemoria(idsoftwares); + return (retval); +} +// ________________________________________________________________________________________________________ +// Función: CuestionPerfilSoftware +// +// Parámetros: +// - db: Objeto base de datos (ya operativo) +// - tbl: Objeto tabla +// - idcentro: Identificador del centro en la tabla +// - ido: Identificador del ordenador del cliente en la tabla +// - idnombreso: Identificador del sistema operativo +// - idsoftwares: Cadena con los identificadores de componentes software separados por comas +// - npc: Nombre del ordenador del cliente +// - particion: Número de la partición +// - tbidsoftware: Array con los identificadores de componentes software +// - lon: Número de componentes +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// +// Versión 1.1.0: Se incluye el sistema operativo. Autora: Irina Gómez - ETSII Universidad Sevilla +//_________________________________________________________________________________________________________ +bool cuestionPerfilSoftware(struct og_dbi *dbi, char *idc, char *ido, + int idperfilsoftware, int idnombreso, + char *idsoftwares, char *npc, char *par, + int *tbidsoftware, int lon) +{ + int i, nwidperfilsoft; + const char *msglog; + dbi_result result; + + // Busca perfil soft del ordenador que contenga todos los componentes software encontrados + result = dbi_conn_queryf(dbi->conn, + "SELECT idperfilsoft FROM" + " (SELECT perfilessoft_softwares.idperfilsoft as idperfilsoft," + " group_concat(cast(perfilessoft_softwares.idsoftware AS char( 11) )" + " ORDER BY perfilessoft_softwares.idsoftware SEPARATOR ',' ) AS idsoftwares" + " FROM perfilessoft_softwares" + " GROUP BY perfilessoft_softwares.idperfilsoft) AS temp" + " WHERE idsoftwares LIKE '%s'", idsoftwares); + + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + return false; + } + if (!dbi_result_next_row(result)) { // No existe un perfil software con esos componentes de componentes software, lo crea + dbi_result_free(result); + result = dbi_conn_queryf(dbi->conn, + "INSERT perfilessoft (descripcion, idcentro, grupoid, idnombreso)" + " VALUES('Perfil Software (%s, Part:%s) ',%s,0,%i)", npc, par, idc,idnombreso); + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + og_info((char *)msglog); + return false; + } + + dbi_result_free(result); + // Recupera el identificador del nuevo perfil software + nwidperfilsoft = dbi_conn_sequence_last(dbi->conn, NULL); + + // Crea la relación entre perfiles y componenetes software + for (i = 0; i < lon; i++) { + result = dbi_conn_queryf(dbi->conn, + "INSERT perfilessoft_softwares (idperfilsoft,idsoftware)" + " VALUES(%d,%d)", nwidperfilsoft, tbidsoftware[i]); + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + og_info((char *)msglog); + return false; + } + dbi_result_free(result); + } + } else { // Existe un perfil con todos esos componentes + nwidperfilsoft = dbi_result_get_uint(result, "idperfilsoft"); + dbi_result_free(result); + } + + if (idperfilsoftware != nwidperfilsoft) { // No coinciden los perfiles + // Actualiza el identificador del perfil software del ordenador + result = dbi_conn_queryf(dbi->conn, + "UPDATE ordenadores_particiones SET idperfilsoft=%d,idimagen=0" + " WHERE idordenador=%s AND numpar=%s", nwidperfilsoft, ido, par); + if (!result) { // Error al insertar + dbi_conn_error(dbi->conn, &msglog); + og_info((char *)msglog); + return false; + } + dbi_result_free(result); + } + + /* DEPURACIÓN DE PERFILES SOFTWARE */ + + /* Eliminar Relación de softwares con Perfiles software que quedan húerfanos */ + result = dbi_conn_queryf(dbi->conn, + "DELETE FROM perfilessoft_softwares WHERE idperfilsoft IN "\ + " (SELECT idperfilsoft FROM perfilessoft WHERE idperfilsoft NOT IN"\ + " (SELECT DISTINCT idperfilsoft from ordenadores_particiones) AND idperfilsoft NOT IN"\ + " (SELECT DISTINCT idperfilsoft from imagenes))"); + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + og_info((char *)msglog); + return false; + } + dbi_result_free(result), + /* Eliminar Perfiles software que quedan húerfanos */ + result = dbi_conn_queryf(dbi->conn, + "DELETE FROM perfilessoft WHERE idperfilsoft NOT IN" + " (SELECT DISTINCT idperfilsoft from ordenadores_particiones)"\ + " AND idperfilsoft NOT IN"\ + " (SELECT DISTINCT idperfilsoft from imagenes)"); + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + og_info((char *)msglog); + return false; + } + dbi_result_free(result), + + /* Eliminar Relación de softwares con Perfiles software que quedan húerfanos */ + result = dbi_conn_queryf(dbi->conn, + "DELETE FROM perfilessoft_softwares WHERE idperfilsoft NOT IN" + " (SELECT idperfilsoft from perfilessoft)"); + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + og_info((char *)msglog); + return false; + } + dbi_result_free(result); + + return true; +} +// ________________________________________________________________________________________________________ +// Función: enviaArchivo +// +// Descripción: +// Envia un archivo por la red, por bloques +// Parámetros: +// - socket_c: Socket del cliente que envió el mensaje +// - ptrTrama: Trama recibida por el servidor con el contenido y los parámetros +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +static bool enviaArchivo(TRAMA *ptrTrama, struct og_client *cli) +{ + int socket_c = og_client_socket(cli); + char *nfl; + + // Toma parámetros + nfl = copiaParametro("nfl",ptrTrama); // Toma nombre completo del archivo + if (!sendArchivo(&socket_c, nfl)) { + liberaMemoria(nfl); + syslog(LOG_ERR, "Problem sending file\n"); + return false; + } + liberaMemoria(nfl); + return true; +} +// ________________________________________________________________________________________________________ +// Función: enviaArchivo +// +// Descripción: +// Envia un archivo por la red, por bloques +// Parámetros: +// - socket_c: Socket del cliente que envió el mensaje +// - ptrTrama: Trama recibida por el servidor con el contenido y los parámetros +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +static bool recibeArchivo(TRAMA *ptrTrama, struct og_client *cli) +{ + int socket_c = og_client_socket(cli); + char *nfl; + + // Toma parámetros + nfl = copiaParametro("nfl",ptrTrama); // Toma nombre completo del archivo + ptrTrama->tipo = MSG_NOTIFICACION; + enviaFlag(&socket_c, ptrTrama); + if (!recArchivo(&socket_c, nfl)) { + liberaMemoria(nfl); + syslog(LOG_ERR, "Problem receiving file\n"); + return false; + } + liberaMemoria(nfl); + return true; +} +// ________________________________________________________________________________________________________ +// Función: envioProgramacion +// +// Descripción: +// Envia un comando de actualización a todos los ordenadores que han sido programados con +// alguna acción para que entren en el bucle de comandos pendientes y las ejecuten +// Parámetros: +// - socket_c: Socket del cliente que envió el mensaje +// - ptrTrama: Trama recibida por el servidor con el contenido y los parámetros +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +static bool envioProgramacion(TRAMA *ptrTrama, struct og_client *cli) +{ + char *ptrIP[MAXIMOS_CLIENTES],*ptrMacs[MAXIMOS_CLIENTES],*ptrNetmasks[MAXIMOS_CLIENTES]; + char *idp, *iph, *mac, *netmask; + int idx,idcomando,lon; + const char *msglog; + struct og_dbi *dbi; + dbi_result result; + + dbi = og_dbi_open(&dbi_config); + if (!dbi) { + syslog(LOG_ERR, "cannot open connection database (%s:%d)\n", + __func__, __LINE__); + return false; + } + + idp = copiaParametro("idp",ptrTrama); // Toma identificador de la programación de la tabla acciones + + result = dbi_conn_queryf(dbi->conn, + "SELECT ordenadores.ip,ordenadores.mac,aulas.netmask, acciones.idcomando FROM acciones "\ + " INNER JOIN ordenadores ON ordenadores.ip=acciones.ip"\ + " INNER JOIN aulas ON ordenadores.idaula=aulas.idaula "\ + " WHERE acciones.idprogramacion=%s",idp); + + liberaMemoria(idp); + + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + og_dbi_close(dbi); + return false; + } + + /* Prepara la trama de actualizacion */ + + initParametros(ptrTrama,0); + ptrTrama->tipo=MSG_COMANDO; + sprintf(ptrTrama->parametros, "nfn=Actualizar\r"); + + while (dbi_result_next_row(result)) { + iph = (char *)dbi_result_get_string(result, "ip"); + idcomando = dbi_result_get_uint(result, "idcomando"); + + if (idcomando == 1){ // Arrancar + mac = (char *)dbi_result_get_string(result, "mac"); + netmask = (char *)dbi_result_get_string(result, "netmask"); + lon = splitCadena(ptrIP, iph, ';'); + lon = splitCadena(ptrMacs, mac, ';'); + lon = splitCadena(ptrNetmasks, netmask, ';'); + + // Se manda por broadcast y por unicast + if (!Levanta(ptrIP, ptrMacs, ptrNetmasks, lon, (char*)"1")) { + dbi_result_free(result); + og_dbi_close(dbi); + return false; + } + + if (!Levanta(ptrIP, ptrMacs, ptrNetmasks, lon, (char*)"2")) { + dbi_result_free(result); + og_dbi_close(dbi); + return false; + } + + } + if (clienteDisponible(iph, &idx)) { // Si el cliente puede recibir comandos + int sock = tbsockets[idx].cli ? tbsockets[idx].cli->io.fd : -1; + + strcpy(tbsockets[idx].estado, CLIENTE_OCUPADO); // Actualiza el estado del cliente + if (sock >= 0 && !mandaTrama(&sock, ptrTrama)) { + syslog(LOG_ERR, "failed to send response: %s\n", + strerror(errno)); + } + //close(tbsockets[idx].sock); // Cierra el socket del cliente hasta nueva disponibilidad + } + } + dbi_result_free(result); + og_dbi_close(dbi); + + return true; // No existen registros +} + +// This object stores function handler for messages +static struct { + const char *nf; // Nombre de la función + bool (*fcn)(TRAMA *, struct og_client *cli); +} tbfuncionesServer[] = { + { "InclusionCliente", InclusionCliente, }, + { "InclusionClienteWinLnx", InclusionClienteWinLnx, }, + { "AutoexecCliente", AutoexecCliente, }, + { "ComandosPendientes", ComandosPendientes, }, + { "DisponibilidadComandos", DisponibilidadComandos, }, + { "RESPUESTA_Arrancar", RESPUESTA_Arrancar, }, + { "RESPUESTA_Apagar", RESPUESTA_Apagar, }, + { "RESPUESTA_Reiniciar", RESPUESTA_Apagar, }, + { "RESPUESTA_IniciarSesion", RESPUESTA_Apagar, }, + { "RESPUESTA_CrearImagen", RESPUESTA_CrearImagen, }, + { "RESPUESTA_CrearImagenBasica", RESPUESTA_CrearImagenBasica, }, + { "RESPUESTA_CrearSoftIncremental", RESPUESTA_CrearSoftIncremental, }, + { "RESPUESTA_RestaurarImagen", RESPUESTA_RestaurarImagen }, + { "RESPUESTA_RestaurarImagenBasica", RESPUESTA_RestaurarImagenBasica, }, + { "RESPUESTA_RestaurarSoftIncremental", RESPUESTA_RestaurarSoftIncremental, }, + { "RESPUESTA_Configurar", RESPUESTA_EjecutarScript, }, + { "RESPUESTA_EjecutarScript", RESPUESTA_EjecutarScript, }, + { "RESPUESTA_InventarioHardware", RESPUESTA_InventarioHardware, }, + { "RESPUESTA_InventarioSoftware", RESPUESTA_InventarioSoftware, }, + { "enviaArchivo", enviaArchivo, }, + { "recibeArchivo", recibeArchivo, }, + { "envioProgramacion", envioProgramacion, }, + { NULL, NULL, }, +}; + +// ________________________________________________________________________________________________________ +// Función: gestionaTrama +// +// Descripción: +// Procesa las tramas recibidas . +// Parametros: +// - s : Socket usado para comunicaciones +// Devuelve: +// true: Si el proceso es correcto +// false: En caso de ocurrir algún error +// ________________________________________________________________________________________________________ +static void gestionaTrama(TRAMA *ptrTrama, struct og_client *cli) +{ + int i, res; + char *nfn; + + if (ptrTrama){ + INTROaFINCAD(ptrTrama); + nfn = copiaParametro("nfn",ptrTrama); // Toma nombre de la función + + for (i = 0; tbfuncionesServer[i].fcn; i++) { + if (!strncmp(tbfuncionesServer[i].nf, nfn, + strlen(tbfuncionesServer[i].nf))) { + res = tbfuncionesServer[i].fcn(ptrTrama, cli); + if (!res) { + syslog(LOG_ERR, "Failed handling of %s for client %s:%hu\n", + tbfuncionesServer[i].nf, + inet_ntoa(cli->addr.sin_addr), + ntohs(cli->addr.sin_port)); + } else { + syslog(LOG_DEBUG, "Successful handling of %s for client %s:%hu\n", + tbfuncionesServer[i].nf, + inet_ntoa(cli->addr.sin_addr), + ntohs(cli->addr.sin_port)); + } + break; + } + } + if (!tbfuncionesServer[i].fcn) + syslog(LOG_ERR, "unknown request %s from client %s:%hu\n", + nfn, inet_ntoa(cli->addr.sin_addr), + ntohs(cli->addr.sin_port)); + + liberaMemoria(nfn); + } +} + +static void og_client_release(struct ev_loop *loop, struct og_client *cli) +{ + if (cli->keepalive_idx >= 0) { + syslog(LOG_DEBUG, "closing keepalive connection for %s:%hu in slot %d\n", + inet_ntoa(cli->addr.sin_addr), + ntohs(cli->addr.sin_port), cli->keepalive_idx); + tbsockets[cli->keepalive_idx].cli = NULL; + } + + ev_io_stop(loop, &cli->io); + close(cli->io.fd); + free(cli); +} + +static void og_client_keepalive(struct ev_loop *loop, struct og_client *cli) +{ + struct og_client *old_cli; + + old_cli = tbsockets[cli->keepalive_idx].cli; + if (old_cli && old_cli != cli) { + syslog(LOG_DEBUG, "closing old keepalive connection for %s:%hu\n", + inet_ntoa(old_cli->addr.sin_addr), + ntohs(old_cli->addr.sin_port)); + + og_client_release(loop, old_cli); + } + tbsockets[cli->keepalive_idx].cli = cli; +} + +static void og_client_reset_state(struct og_client *cli) +{ + cli->state = OG_CLIENT_RECEIVING_HEADER; + cli->buf_len = 0; +} + +static int og_client_state_recv_hdr(struct og_client *cli) +{ + char hdrlen[LONHEXPRM]; + + /* Still too short to validate protocol fingerprint and message + * length. + */ + if (cli->buf_len < 15 + LONHEXPRM) + return 0; + + if (strncmp(cli->buf, "@JMMLCAMDJ_MCDJ", 15)) { + syslog(LOG_ERR, "bad fingerprint from client %s:%hu, closing\n", + inet_ntoa(cli->addr.sin_addr), + ntohs(cli->addr.sin_port)); + return -1; + } + + memcpy(hdrlen, &cli->buf[LONGITUD_CABECERATRAMA], LONHEXPRM); + cli->msg_len = strtol(hdrlen, NULL, 16); + + /* Header announces more that we can fit into buffer. */ + if (cli->msg_len >= sizeof(cli->buf)) { + syslog(LOG_ERR, "too large message %u bytes from %s:%hu\n", + cli->msg_len, inet_ntoa(cli->addr.sin_addr), + ntohs(cli->addr.sin_port)); + return -1; + } + + return 1; +} + +static TRAMA *og_msg_alloc(char *data, unsigned int len) +{ + TRAMA *ptrTrama; + + ptrTrama = (TRAMA *)reservaMemoria(sizeof(TRAMA)); + if (!ptrTrama) { + syslog(LOG_ERR, "OOM\n"); + return NULL; + } + + initParametros(ptrTrama, len); + memcpy(ptrTrama, "@JMMLCAMDJ_MCDJ", LONGITUD_CABECERATRAMA); + memcpy(ptrTrama->parametros, data, len); + ptrTrama->lonprm = len; + + return ptrTrama; +} + +static void og_msg_free(TRAMA *ptrTrama) +{ + liberaMemoria(ptrTrama->parametros); + liberaMemoria(ptrTrama); +} + +static int og_client_state_process_payload(struct og_client *cli) +{ + TRAMA *ptrTrama; + char *data; + int len; + + len = cli->msg_len - (LONGITUD_CABECERATRAMA + LONHEXPRM); + data = &cli->buf[LONGITUD_CABECERATRAMA + LONHEXPRM]; + + ptrTrama = og_msg_alloc(data, len); + if (!ptrTrama) + return -1; + + gestionaTrama(ptrTrama, cli); + + og_msg_free(ptrTrama); + + return 1; +} + +#define OG_CLIENTS_MAX 4096 +#define OG_PARTITION_MAX 4 + +struct og_partition { + const char *number; + const char *code; + const char *size; + const char *filesystem; + const char *format; +}; + +struct og_sync_params { + const char *sync; + const char *diff; + const char *remove; + const char *compress; + const char *cleanup; + const char *cache; + const char *cleanup_cache; + const char *remove_dst; + const char *diff_id; + const char *diff_name; + const char *path; + const char *method; +}; + +struct og_msg_params { + const char *ips_array[OG_CLIENTS_MAX]; + const char *mac_array[OG_CLIENTS_MAX]; + const char *netmask_array[OG_CLIENTS_MAX]; + unsigned int ips_array_len; + const char *wol_type; + char run_cmd[4096]; + const char *disk; + const char *partition; + const char *repository; + const char *name; + const char *id; + const char *code; + const char *type; + const char *profile; + const char *cache; + const char *cache_size; + bool echo; + struct og_partition partition_setup[OG_PARTITION_MAX]; + struct og_sync_params sync_setup; + uint64_t flags; +}; + +#define OG_REST_PARAM_ADDR (1UL << 0) +#define OG_REST_PARAM_MAC (1UL << 1) +#define OG_REST_PARAM_WOL_TYPE (1UL << 2) +#define OG_REST_PARAM_RUN_CMD (1UL << 3) +#define OG_REST_PARAM_DISK (1UL << 4) +#define OG_REST_PARAM_PARTITION (1UL << 5) +#define OG_REST_PARAM_REPO (1UL << 6) +#define OG_REST_PARAM_NAME (1UL << 7) +#define OG_REST_PARAM_ID (1UL << 8) +#define OG_REST_PARAM_CODE (1UL << 9) +#define OG_REST_PARAM_TYPE (1UL << 10) +#define OG_REST_PARAM_PROFILE (1UL << 11) +#define OG_REST_PARAM_CACHE (1UL << 12) +#define OG_REST_PARAM_CACHE_SIZE (1UL << 13) +#define OG_REST_PARAM_PART_0 (1UL << 14) +#define OG_REST_PARAM_PART_1 (1UL << 15) +#define OG_REST_PARAM_PART_2 (1UL << 16) +#define OG_REST_PARAM_PART_3 (1UL << 17) +#define OG_REST_PARAM_SYNC_SYNC (1UL << 18) +#define OG_REST_PARAM_SYNC_DIFF (1UL << 19) +#define OG_REST_PARAM_SYNC_REMOVE (1UL << 20) +#define OG_REST_PARAM_SYNC_COMPRESS (1UL << 21) +#define OG_REST_PARAM_SYNC_CLEANUP (1UL << 22) +#define OG_REST_PARAM_SYNC_CACHE (1UL << 23) +#define OG_REST_PARAM_SYNC_CLEANUP_CACHE (1UL << 24) +#define OG_REST_PARAM_SYNC_REMOVE_DST (1UL << 25) +#define OG_REST_PARAM_SYNC_DIFF_ID (1UL << 26) +#define OG_REST_PARAM_SYNC_DIFF_NAME (1UL << 27) +#define OG_REST_PARAM_SYNC_PATH (1UL << 28) +#define OG_REST_PARAM_SYNC_METHOD (1UL << 29) +#define OG_REST_PARAM_ECHO (1UL << 30) + +static bool og_msg_params_validate(const struct og_msg_params *params, + const uint64_t flags) +{ + return (params->flags & flags) == flags; +} + +static int og_json_parse_clients(json_t *element, struct og_msg_params *params) +{ + unsigned int i; + json_t *k; + + if (json_typeof(element) != JSON_ARRAY) + return -1; + + for (i = 0; i < json_array_size(element); i++) { + k = json_array_get(element, i); + if (json_typeof(k) != JSON_STRING) + return -1; + + params->ips_array[params->ips_array_len++] = + json_string_value(k); + + params->flags |= OG_REST_PARAM_ADDR; + } + + return 0; +} + +static int og_json_parse_string(json_t *element, const char **str) +{ + if (json_typeof(element) != JSON_STRING) + return -1; + + *str = json_string_value(element); + return 0; +} + +static int og_json_parse_bool(json_t *element, bool *value) +{ + if (json_typeof(element) == JSON_TRUE) + *value = true; + else if (json_typeof(element) == JSON_FALSE) + *value = false; + else + return -1; + + return 0; +} + +static int og_json_parse_sync_params(json_t *element, + struct og_msg_params *params) +{ + const char *key; + json_t *value; + int err = 0; + + json_object_foreach(element, key, value) { + if (!strcmp(key, "sync")) { + err = og_json_parse_string(value, ¶ms->sync_setup.sync); + params->flags |= OG_REST_PARAM_SYNC_SYNC; + } else if (!strcmp(key, "diff")) { + err = og_json_parse_string(value, ¶ms->sync_setup.diff); + params->flags |= OG_REST_PARAM_SYNC_DIFF; + } else if (!strcmp(key, "remove")) { + err = og_json_parse_string(value, ¶ms->sync_setup.remove); + params->flags |= OG_REST_PARAM_SYNC_REMOVE; + } else if (!strcmp(key, "compress")) { + err = og_json_parse_string(value, ¶ms->sync_setup.compress); + params->flags |= OG_REST_PARAM_SYNC_COMPRESS; + } else if (!strcmp(key, "cleanup")) { + err = og_json_parse_string(value, ¶ms->sync_setup.cleanup); + params->flags |= OG_REST_PARAM_SYNC_CLEANUP; + } else if (!strcmp(key, "cache")) { + err = og_json_parse_string(value, ¶ms->sync_setup.cache); + params->flags |= OG_REST_PARAM_SYNC_CACHE; + } else if (!strcmp(key, "cleanup_cache")) { + err = og_json_parse_string(value, ¶ms->sync_setup.cleanup_cache); + params->flags |= OG_REST_PARAM_SYNC_CLEANUP_CACHE; + } else if (!strcmp(key, "remove_dst")) { + err = og_json_parse_string(value, ¶ms->sync_setup.remove_dst); + params->flags |= OG_REST_PARAM_SYNC_REMOVE_DST; + } else if (!strcmp(key, "diff_id")) { + err = og_json_parse_string(value, ¶ms->sync_setup.diff_id); + params->flags |= OG_REST_PARAM_SYNC_DIFF_ID; + } else if (!strcmp(key, "diff_name")) { + err = og_json_parse_string(value, ¶ms->sync_setup.diff_name); + params->flags |= OG_REST_PARAM_SYNC_DIFF_NAME; + } else if (!strcmp(key, "path")) { + err = og_json_parse_string(value, ¶ms->sync_setup.path); + params->flags |= OG_REST_PARAM_SYNC_PATH; + } else if (!strcmp(key, "method")) { + err = og_json_parse_string(value, ¶ms->sync_setup.method); + params->flags |= OG_REST_PARAM_SYNC_METHOD; + } + + if (err != 0) + return err; + } + return err; +} + +#define OG_PARAM_PART_NUMBER (1UL << 0) +#define OG_PARAM_PART_CODE (1UL << 1) +#define OG_PARAM_PART_FILESYSTEM (1UL << 2) +#define OG_PARAM_PART_SIZE (1UL << 3) +#define OG_PARAM_PART_FORMAT (1UL << 4) + +static int og_json_parse_partition(json_t *element, + struct og_msg_params *params, + unsigned int i) +{ + struct og_partition *part = ¶ms->partition_setup[i]; + uint64_t flags = 0UL; + const char *key; + json_t *value; + int err = 0; + + json_object_foreach(element, key, value) { + if (!strcmp(key, "partition")) { + err = og_json_parse_string(value, &part->number); + flags |= OG_PARAM_PART_NUMBER; + } else if (!strcmp(key, "code")) { + err = og_json_parse_string(value, &part->code); + flags |= OG_PARAM_PART_CODE; + } else if (!strcmp(key, "filesystem")) { + err = og_json_parse_string(value, &part->filesystem); + flags |= OG_PARAM_PART_FILESYSTEM; + } else if (!strcmp(key, "size")) { + err = og_json_parse_string(value, &part->size); + flags |= OG_PARAM_PART_SIZE; + } else if (!strcmp(key, "format")) { + err = og_json_parse_string(value, &part->format); + flags |= OG_PARAM_PART_FORMAT; + } + + if (err < 0) + return err; + } + + if (flags != (OG_PARAM_PART_NUMBER | + OG_PARAM_PART_CODE | + OG_PARAM_PART_FILESYSTEM | + OG_PARAM_PART_SIZE | + OG_PARAM_PART_FORMAT)) + return -1; + + params->flags |= (OG_REST_PARAM_PART_0 << i); + + return err; +} + +static int og_json_parse_partition_setup(json_t *element, + struct og_msg_params *params) +{ + unsigned int i; + json_t *k; + + if (json_typeof(element) != JSON_ARRAY) + return -1; + + for (i = 0; i < json_array_size(element) && i < OG_PARTITION_MAX; ++i) { + k = json_array_get(element, i); + + if (json_typeof(k) != JSON_OBJECT) + return -1; + + if (og_json_parse_partition(k, params, i) != 0) + return -1; + } + return 0; +} + +static int og_cmd_legacy_send(struct og_msg_params *params, const char *cmd, + const char *state) +{ + char buf[4096] = {}; + int len, err = 0; + TRAMA *msg; + + len = snprintf(buf, sizeof(buf), "nfn=%s\r", cmd); + + msg = og_msg_alloc(buf, len); + if (!msg) + return -1; + + if (!og_send_cmd((char **)params->ips_array, params->ips_array_len, + state, msg)) + err = -1; + + og_msg_free(msg); + + return err; +} + +static int og_cmd_post_clients(json_t *element, struct og_msg_params *params) +{ + const char *key; + json_t *value; + int err = 0; + + if (json_typeof(element) != JSON_OBJECT) + return -1; + + json_object_foreach(element, key, value) { + if (!strcmp(key, "clients")) + err = og_json_parse_clients(value, params); + + if (err < 0) + break; + } + + if (!og_msg_params_validate(params, OG_REST_PARAM_ADDR)) + return -1; + + return og_cmd_legacy_send(params, "Sondeo", CLIENTE_APAGADO); +} + +struct og_buffer { + char *data; + int len; +}; + +static int og_json_dump_clients(const char *buffer, size_t size, void *data) +{ + struct og_buffer *og_buffer = (struct og_buffer *)data; + + memcpy(og_buffer->data + og_buffer->len, buffer, size); + og_buffer->len += size; + + return 0; +} + +static int og_cmd_get_clients(json_t *element, struct og_msg_params *params, + char *buffer_reply) +{ + json_t *root, *array, *addr, *state, *object; + struct og_buffer og_buffer = { + .data = buffer_reply, + }; + int i; + + array = json_array(); + if (!array) + return -1; + + for (i = 0; i < MAXIMOS_CLIENTES; i++) { + if (tbsockets[i].ip[0] == '\0') + continue; + + object = json_object(); + if (!object) { + json_decref(array); + return -1; + } + addr = json_string(tbsockets[i].ip); + if (!addr) { + json_decref(object); + json_decref(array); + return -1; + } + json_object_set_new(object, "addr", addr); + + state = json_string(tbsockets[i].estado); + if (!state) { + json_decref(object); + json_decref(array); + return -1; + } + json_object_set_new(object, "state", state); + + json_array_append_new(array, object); + } + root = json_pack("{s:o}", "clients", array); + if (!root) { + json_decref(array); + return -1; + } + + json_dump_callback(root, og_json_dump_clients, &og_buffer, 0); + json_decref(root); + + return 0; +} + +static int og_json_parse_target(json_t *element, struct og_msg_params *params) +{ + const char *key; + json_t *value; + + if (json_typeof(element) != JSON_OBJECT) { + return -1; + } + + json_object_foreach(element, key, value) { + if (!strcmp(key, "addr")) { + if (json_typeof(value) != JSON_STRING) + return -1; + + params->ips_array[params->ips_array_len] = + json_string_value(value); + + params->flags |= OG_REST_PARAM_ADDR; + } else if (!strcmp(key, "mac")) { + if (json_typeof(value) != JSON_STRING) + return -1; + + params->mac_array[params->ips_array_len] = + json_string_value(value); + + params->flags |= OG_REST_PARAM_MAC; + } + } + + return 0; +} + +static int og_json_parse_targets(json_t *element, struct og_msg_params *params) +{ + unsigned int i; + json_t *k; + int err; + + if (json_typeof(element) != JSON_ARRAY) + return -1; + + for (i = 0; i < json_array_size(element); i++) { + k = json_array_get(element, i); + + if (json_typeof(k) != JSON_OBJECT) + return -1; + + err = og_json_parse_target(k, params); + if (err < 0) + return err; + + params->ips_array_len++; + } + return 0; +} + +static int og_json_parse_type(json_t *element, struct og_msg_params *params) +{ + const char *type; + + if (json_typeof(element) != JSON_STRING) + return -1; + + params->wol_type = json_string_value(element); + + type = json_string_value(element); + if (!strcmp(type, "unicast")) + params->wol_type = "2"; + else if (!strcmp(type, "broadcast")) + params->wol_type = "1"; + + params->flags |= OG_REST_PARAM_WOL_TYPE; + + return 0; +} + +static int og_cmd_wol(json_t *element, struct og_msg_params *params) +{ + char ips_str[(OG_DB_IP_MAXLEN + 1) * OG_CLIENTS_MAX + 1] = {}; + int ips_str_len = 0; + const char *msglog; + struct og_dbi *dbi; + int i = 0; + dbi_result result; + const char *key; + json_t *value; + int err = 0; + + if (json_typeof(element) != JSON_OBJECT) + return -1; + + json_object_foreach(element, key, value) { + if (!strcmp(key, "clients")) { + err = og_json_parse_targets(value, params); + } else if (!strcmp(key, "type")) { + err = og_json_parse_type(value, params); + } + + if (err < 0) + break; + } + + if (!og_msg_params_validate(params, OG_REST_PARAM_ADDR | + OG_REST_PARAM_MAC | + OG_REST_PARAM_WOL_TYPE)) + return -1; + + for (i = 0; i < params->ips_array_len; ++i) { + ips_str_len += snprintf(ips_str + ips_str_len, + sizeof(ips_str) - ips_str_len, + "'%s',", params->ips_array[i]); + } + ips_str[ips_str_len - 1] = '\0'; + + dbi = og_dbi_open(&dbi_config); + if (!dbi) { + syslog(LOG_ERR, "cannot open connection database (%s:%d)\n", + __func__, __LINE__); + return -1; + } + + result = dbi_conn_queryf(dbi->conn, + "SELECT ordenadores.ip, ordenadores.mac, " + "aulas.netmask " + "FROM ordenadores " + "INNER JOIN aulas " + "ON ordenadores.idaula = aulas.idaula " + "WHERE ordenadores.ip IN (%s)", + ips_str); + if (!result) { + dbi_conn_error(dbi->conn, &msglog); + syslog(LOG_ERR, "failed to query database (%s:%d) %s\n", + __func__, __LINE__, msglog); + og_dbi_close(dbi); + return -1; + } + + for (i = 0; dbi_result_next_row(result); i++) { + params->ips_array[i] = dbi_result_get_string_copy(result, "ip"); + params->mac_array[i] = dbi_result_get_string_copy(result, "mac"); + params->netmask_array[i] = dbi_result_get_string_copy(result, "netmask"); + } + + dbi_result_free(result); + og_dbi_close(dbi); + + if (!Levanta((char **)params->ips_array, (char **)params->mac_array, + (char **)params->netmask_array, i, + (char *)params->wol_type)) + return -1; + + return 0; +} + +static int og_json_parse_run(json_t *element, struct og_msg_params *params) +{ + if (json_typeof(element) != JSON_STRING) + return -1; + + snprintf(params->run_cmd, sizeof(params->run_cmd), "%s", + json_string_value(element)); + + params->flags |= OG_REST_PARAM_RUN_CMD; + + return 0; +} + +static int og_cmd_run_post(json_t *element, struct og_msg_params *params) +{ + char buf[4096] = {}, iph[4096] = {}; + int err = 0, len; + const char *key; + unsigned int i; + json_t *value; + TRAMA *msg; + + if (json_typeof(element) != JSON_OBJECT) + return -1; + + json_object_foreach(element, key, value) { + if (!strcmp(key, "clients")) + err = og_json_parse_clients(value, params); + else if (!strcmp(key, "run")) + err = og_json_parse_run(value, params); + else if (!strcmp(key, "echo")) { + err = og_json_parse_bool(value, ¶ms->echo); + params->flags |= OG_REST_PARAM_ECHO; + } + + if (err < 0) + break; + } + + if (!og_msg_params_validate(params, OG_REST_PARAM_ADDR | + OG_REST_PARAM_RUN_CMD | + OG_REST_PARAM_ECHO)) + return -1; + + for (i = 0; i < params->ips_array_len; i++) { + len = snprintf(iph + strlen(iph), sizeof(iph), "%s;", + params->ips_array[i]); + } + + if (params->echo) { + len = snprintf(buf, sizeof(buf), + "nfn=ConsolaRemota\riph=%s\rscp=%s\r", + iph, params->run_cmd); + } else { + len = snprintf(buf, sizeof(buf), + "nfn=EjecutarScript\riph=%s\rscp=%s\r", + iph, params->run_cmd); + } + + msg = og_msg_alloc(buf, len); + if (!msg) + return -1; + + if (!og_send_cmd((char **)params->ips_array, params->ips_array_len, + CLIENTE_OCUPADO, msg)) + err = -1; + + og_msg_free(msg); + + if (err < 0) + return err; + + for (i = 0; i < params->ips_array_len; i++) { + char filename[4096]; + FILE *f; + + sprintf(filename, "/tmp/_Seconsola_%s", params->ips_array[i]); + f = fopen(filename, "wt"); + fclose(f); + } + + return 0; +} + +static int og_cmd_run_get(json_t *element, struct og_msg_params *params, + char *buffer_reply) +{ + struct og_buffer og_buffer = { + .data = buffer_reply, + }; + json_t *root, *value, *array; + const char *key; + unsigned int i; + int err = 0; + + if (json_typeof(element) != JSON_OBJECT) + return -1; + + json_object_foreach(element, key, value) { + if (!strcmp(key, "clients")) + err = og_json_parse_clients(value, params); + + if (err < 0) + return err; + } + + if (!og_msg_params_validate(params, OG_REST_PARAM_ADDR)) + return -1; + + array = json_array(); + if (!array) + return -1; + + for (i = 0; i < params->ips_array_len; i++) { + json_t *object, *output, *addr; + char data[4096] = {}; + char filename[4096]; + int fd, numbytes; + + sprintf(filename, "/tmp/_Seconsola_%s", params->ips_array[i]); + + fd = open(filename, O_RDONLY); + if (!fd) + return -1; + + numbytes = read(fd, data, sizeof(data)); + if (numbytes < 0) { + close(fd); + return -1; + } + data[sizeof(data) - 1] = '\0'; + close(fd); + + object = json_object(); + if (!object) { + json_decref(array); + return -1; + } + addr = json_string(params->ips_array[i]); + if (!addr) { + json_decref(object); + json_decref(array); + return -1; + } + json_object_set_new(object, "addr", addr); + + output = json_string(data); + if (!output) { + json_decref(object); + json_decref(array); + return -1; + } + json_object_set_new(object, "output", output); + + json_array_append_new(array, object); + } + + root = json_pack("{s:o}", "clients", array); + if (!root) + return -1; + + json_dump_callback(root, og_json_dump_clients, &og_buffer, 0); + json_decref(root); + + return 0; +} + +static int og_cmd_session(json_t *element, struct og_msg_params *params) +{ + char buf[4096], iph[4096]; + int err = 0, len; + const char *key; + unsigned int i; + json_t *value; + TRAMA *msg; + + if (json_typeof(element) != JSON_OBJECT) + return -1; + + json_object_foreach(element, key, value) { + if (!strcmp(key, "clients")) { + err = og_json_parse_clients(value, params); + } else if (!strcmp(key, "disk")) { + err = og_json_parse_string(value, ¶ms->disk); + params->flags |= OG_REST_PARAM_DISK; + } else if (!strcmp(key, "partition")) { + err = og_json_parse_string(value, ¶ms->partition); + params->flags |= OG_REST_PARAM_PARTITION; + } + + if (err < 0) + return err; + } + + if (!og_msg_params_validate(params, OG_REST_PARAM_ADDR | + OG_REST_PARAM_DISK | + OG_REST_PARAM_PARTITION)) + return -1; + + for (i = 0; i < params->ips_array_len; i++) { + snprintf(iph + strlen(iph), sizeof(iph), "%s;", + params->ips_array[i]); + } + len = snprintf(buf, sizeof(buf), + "nfn=IniciarSesion\riph=%s\rdsk=%s\rpar=%s\r", + iph, params->disk, params->partition); + + msg = og_msg_alloc(buf, len); + if (!msg) + return -1; + + if (!og_send_cmd((char **)params->ips_array, params->ips_array_len, + CLIENTE_APAGADO, msg)) + err = -1; + + og_msg_free(msg); + + return 0; +} + +static int og_cmd_poweroff(json_t *element, struct og_msg_params *params) +{ + const char *key; + json_t *value; + int err = 0; + + if (json_typeof(element) != JSON_OBJECT) + return -1; + + json_object_foreach(element, key, value) { + if (!strcmp(key, "clients")) + err = og_json_parse_clients(value, params); + + if (err < 0) + break; + } + + if (!og_msg_params_validate(params, OG_REST_PARAM_ADDR)) + return -1; + + return og_cmd_legacy_send(params, "Apagar", CLIENTE_OCUPADO); +} + +static int og_cmd_refresh(json_t *element, struct og_msg_params *params) +{ + const char *key; + json_t *value; + int err = 0; + + if (json_typeof(element) != JSON_OBJECT) + return -1; + + json_object_foreach(element, key, value) { + if (!strcmp(key, "clients")) + err = og_json_parse_clients(value, params); + + if (err < 0) + break; + } + + if (!og_msg_params_validate(params, OG_REST_PARAM_ADDR)) + return -1; + + return og_cmd_legacy_send(params, "Actualizar", CLIENTE_APAGADO); +} + +static int og_cmd_reboot(json_t *element, struct og_msg_params *params) +{ + const char *key; + json_t *value; + int err = 0; + + if (json_typeof(element) != JSON_OBJECT) + return -1; + + json_object_foreach(element, key, value) { + if (!strcmp(key, "clients")) + err = og_json_parse_clients(value, params); + + if (err < 0) + break; + } + + if (!og_msg_params_validate(params, OG_REST_PARAM_ADDR)) + return -1; + + return og_cmd_legacy_send(params, "Reiniciar", CLIENTE_OCUPADO); +} + +static int og_cmd_stop(json_t *element, struct og_msg_params *params) +{ + const char *key; + json_t *value; + int err = 0; + + if (json_typeof(element) != JSON_OBJECT) + return -1; + + json_object_foreach(element, key, value) { + if (!strcmp(key, "clients")) + err = og_json_parse_clients(value, params); + + if (err < 0) + break; + } + + if (!og_msg_params_validate(params, OG_REST_PARAM_ADDR)) + return -1; + + return og_cmd_legacy_send(params, "Purgar", CLIENTE_APAGADO); +} + +static int og_cmd_hardware(json_t *element, struct og_msg_params *params) +{ + const char *key; + json_t *value; + int err = 0; + + if (json_typeof(element) != JSON_OBJECT) + return -1; + + json_object_foreach(element, key, value) { + if (!strcmp(key, "clients")) + err = og_json_parse_clients(value, params); + + if (err < 0) + break; + } + + if (!og_msg_params_validate(params, OG_REST_PARAM_ADDR)) + return -1; + + return og_cmd_legacy_send(params, "InventarioHardware", + CLIENTE_OCUPADO); +} + +static int og_cmd_software(json_t *element, struct og_msg_params *params) +{ + char buf[4096] = {}; + int err = 0, len; + const char *key; + json_t *value; + TRAMA *msg; + + if (json_typeof(element) != JSON_OBJECT) + return -1; + + json_object_foreach(element, key, value) { + if (!strcmp(key, "clients")) + err = og_json_parse_clients(value, params); + else if (!strcmp(key, "disk")) { + err = og_json_parse_string(value, ¶ms->disk); + params->flags |= OG_REST_PARAM_DISK; + } + else if (!strcmp(key, "partition")) { + err = og_json_parse_string(value, ¶ms->partition); + params->flags |= OG_REST_PARAM_PARTITION; + } + + if (err < 0) + break; + } + + if (!og_msg_params_validate(params, OG_REST_PARAM_ADDR | + OG_REST_PARAM_DISK | + OG_REST_PARAM_PARTITION)) + return -1; + + len = snprintf(buf, sizeof(buf), + "nfn=InventarioSoftware\rdsk=%s\rpar=%s\r", + params->disk, params->partition); + + msg = og_msg_alloc(buf, len); + if (!msg) + return -1; + + og_send_cmd((char **)params->ips_array, params->ips_array_len, + CLIENTE_OCUPADO, msg); + + og_msg_free(msg); + + return 0; +} + +static int og_cmd_create_image(json_t *element, struct og_msg_params *params) +{ + char buf[4096] = {}; + int err = 0, len; + const char *key; + json_t *value; + TRAMA *msg; + + if (json_typeof(element) != JSON_OBJECT) + return -1; + + json_object_foreach(element, key, value) { + if (!strcmp(key, "disk")) { + err = og_json_parse_string(value, ¶ms->disk); + params->flags |= OG_REST_PARAM_DISK; + } else if (!strcmp(key, "partition")) { + err = og_json_parse_string(value, ¶ms->partition); + params->flags |= OG_REST_PARAM_PARTITION; + } else if (!strcmp(key, "name")) { + err = og_json_parse_string(value, ¶ms->name); + params->flags |= OG_REST_PARAM_NAME; + } else if (!strcmp(key, "repository")) { + err = og_json_parse_string(value, ¶ms->repository); + params->flags |= OG_REST_PARAM_REPO; + } else if (!strcmp(key, "clients")) { + err = og_json_parse_clients(value, params); + } else if (!strcmp(key, "id")) { + err = og_json_parse_string(value, ¶ms->id); + params->flags |= OG_REST_PARAM_ID; + } else if (!strcmp(key, "code")) { + err = og_json_parse_string(value, ¶ms->code); + params->flags |= OG_REST_PARAM_CODE; + } + + if (err < 0) + break; + } + + if (!og_msg_params_validate(params, OG_REST_PARAM_ADDR | + OG_REST_PARAM_DISK | + OG_REST_PARAM_PARTITION | + OG_REST_PARAM_CODE | + OG_REST_PARAM_ID | + OG_REST_PARAM_NAME | + OG_REST_PARAM_REPO)) + return -1; + + len = snprintf(buf, sizeof(buf), + "nfn=CrearImagen\rdsk=%s\rpar=%s\rcpt=%s\ridi=%s\rnci=%s\ripr=%s\r", + params->disk, params->partition, params->code, + params->id, params->name, params->repository); + + msg = og_msg_alloc(buf, len); + if (!msg) + return -1; + + og_send_cmd((char **)params->ips_array, params->ips_array_len, + CLIENTE_OCUPADO, msg); + + og_msg_free(msg); + + return 0; +} + +static int og_cmd_restore_image(json_t *element, struct og_msg_params *params) +{ + char buf[4096] = {}; + int err = 0, len; + const char *key; + json_t *value; + TRAMA *msg; + + if (json_typeof(element) != JSON_OBJECT) + return -1; + + json_object_foreach(element, key, value) { + if (!strcmp(key, "disk")) { + err = og_json_parse_string(value, ¶ms->disk); + params->flags |= OG_REST_PARAM_DISK; + } else if (!strcmp(key, "partition")) { + err = og_json_parse_string(value, ¶ms->partition); + params->flags |= OG_REST_PARAM_PARTITION; + } else if (!strcmp(key, "name")) { + err = og_json_parse_string(value, ¶ms->name); + params->flags |= OG_REST_PARAM_NAME; + } else if (!strcmp(key, "repository")) { + err = og_json_parse_string(value, ¶ms->repository); + params->flags |= OG_REST_PARAM_REPO; + } else if (!strcmp(key, "clients")) { + err = og_json_parse_clients(value, params); + } else if (!strcmp(key, "type")) { + err = og_json_parse_string(value, ¶ms->type); + params->flags |= OG_REST_PARAM_TYPE; + } else if (!strcmp(key, "profile")) { + err = og_json_parse_string(value, ¶ms->profile); + params->flags |= OG_REST_PARAM_PROFILE; + } else if (!strcmp(key, "id")) { + err = og_json_parse_string(value, ¶ms->id); + params->flags |= OG_REST_PARAM_ID; + } + + if (err < 0) + break; + } + + if (!og_msg_params_validate(params, OG_REST_PARAM_ADDR | + OG_REST_PARAM_DISK | + OG_REST_PARAM_PARTITION | + OG_REST_PARAM_NAME | + OG_REST_PARAM_REPO | + OG_REST_PARAM_TYPE | + OG_REST_PARAM_PROFILE | + OG_REST_PARAM_ID)) + return -1; + + len = snprintf(buf, sizeof(buf), + "nfn=RestaurarImagen\ridi=%s\rdsk=%s\rpar=%s\rifs=%s\r" + "nci=%s\ripr=%s\rptc=%s\r", + params->id, params->disk, params->partition, + params->profile, params->name, + params->repository, params->type); + + msg = og_msg_alloc(buf, len); + if (!msg) + return -1; + + og_send_cmd((char **)params->ips_array, params->ips_array_len, + CLIENTE_OCUPADO, msg); + + og_msg_free(msg); + + return 0; +} + +static int og_cmd_setup(json_t *element, struct og_msg_params *params) +{ + char buf[4096] = {}; + int err = 0, len; + const char *key; + json_t *value; + TRAMA *msg; + + if (json_typeof(element) != JSON_OBJECT) + return -1; + + json_object_foreach(element, key, value) { + if (!strcmp(key, "clients")) { + err = og_json_parse_clients(value, params); + } else if (!strcmp(key, "disk")) { + err = og_json_parse_string(value, ¶ms->disk); + params->flags |= OG_REST_PARAM_DISK; + } else if (!strcmp(key, "cache")) { + err = og_json_parse_string(value, ¶ms->cache); + params->flags |= OG_REST_PARAM_CACHE; + } else if (!strcmp(key, "cache_size")) { + err = og_json_parse_string(value, ¶ms->cache_size); + params->flags |= OG_REST_PARAM_CACHE_SIZE; + } else if (!strcmp(key, "partition_setup")) { + err = og_json_parse_partition_setup(value, params); + } + + if (err < 0) + break; + } + + if (!og_msg_params_validate(params, OG_REST_PARAM_ADDR | + OG_REST_PARAM_DISK | + OG_REST_PARAM_CACHE | + OG_REST_PARAM_CACHE_SIZE | + OG_REST_PARAM_PART_0 | + OG_REST_PARAM_PART_1 | + OG_REST_PARAM_PART_2 | + OG_REST_PARAM_PART_3)) + return -1; + + len = snprintf(buf, sizeof(buf), + "nfn=Configurar\rdsk=%s\rcfg=dis=%s*che=%s*tch=%s!", + params->disk, params->disk, params->cache, params->cache_size); + + for (unsigned int i = 0; i < OG_PARTITION_MAX; ++i) { + const struct og_partition *part = ¶ms->partition_setup[i]; + + len += snprintf(buf + strlen(buf), sizeof(buf), + "par=%s*cpt=%s*sfi=%s*tam=%s*ope=%s%%", + part->number, part->code, part->filesystem, part->size, part->format); + } + + msg = og_msg_alloc(buf, len + 1); + if (!msg) + return -1; + + og_send_cmd((char **)params->ips_array, params->ips_array_len, + CLIENTE_OCUPADO, msg); + + og_msg_free(msg); + + return 0; +} + +static int og_cmd_run_schedule(json_t *element, struct og_msg_params *params) +{ + const char *key; + json_t *value; + int err = 0; + + json_object_foreach(element, key, value) { + if (!strcmp(key, "clients")) + err = og_json_parse_clients(value, params); + + if (err < 0) + break; + } + + if (!og_msg_params_validate(params, OG_REST_PARAM_ADDR)) + return -1; + + og_cmd_legacy_send(params, "EjecutaComandosPendientes", CLIENTE_OCUPADO); + + return 0; +} + +static int og_cmd_create_basic_image(json_t *element, struct og_msg_params *params) +{ + char buf[4096] = {}; + int err = 0, len; + const char *key; + json_t *value; + TRAMA *msg; + + if (json_typeof(element) != JSON_OBJECT) + return -1; + + json_object_foreach(element, key, value) { + if (!strcmp(key, "clients")) { + err = og_json_parse_clients(value, params); + } else if (!strcmp(key, "disk")) { + err = og_json_parse_string(value, ¶ms->disk); + params->flags |= OG_REST_PARAM_DISK; + } else if (!strcmp(key, "partition")) { + err = og_json_parse_string(value, ¶ms->partition); + params->flags |= OG_REST_PARAM_PARTITION; + } else if (!strcmp(key, "code")) { + err = og_json_parse_string(value, ¶ms->code); + params->flags |= OG_REST_PARAM_CODE; + } else if (!strcmp(key, "id")) { + err = og_json_parse_string(value, ¶ms->id); + params->flags |= OG_REST_PARAM_ID; + } else if (!strcmp(key, "name")) { + err = og_json_parse_string(value, ¶ms->name); + params->flags |= OG_REST_PARAM_NAME; + } else if (!strcmp(key, "repository")) { + err = og_json_parse_string(value, ¶ms->repository); + params->flags |= OG_REST_PARAM_REPO; + } else if (!strcmp(key, "sync_params")) { + err = og_json_parse_sync_params(value, params); + } + + if (err < 0) + break; + } + + if (!og_msg_params_validate(params, OG_REST_PARAM_ADDR | + OG_REST_PARAM_DISK | + OG_REST_PARAM_PARTITION | + OG_REST_PARAM_CODE | + OG_REST_PARAM_ID | + OG_REST_PARAM_NAME | + OG_REST_PARAM_REPO | + OG_REST_PARAM_SYNC_SYNC | + OG_REST_PARAM_SYNC_DIFF | + OG_REST_PARAM_SYNC_REMOVE | + OG_REST_PARAM_SYNC_COMPRESS | + OG_REST_PARAM_SYNC_CLEANUP | + OG_REST_PARAM_SYNC_CACHE | + OG_REST_PARAM_SYNC_CLEANUP_CACHE | + OG_REST_PARAM_SYNC_REMOVE_DST)) + return -1; + + len = snprintf(buf, sizeof(buf), + "nfn=CrearImagenBasica\rdsk=%s\rpar=%s\rcpt=%s\ridi=%s\r" + "nci=%s\ripr=%s\rrti=\rmsy=%s\rwhl=%s\reli=%s\rcmp=%s\rbpi=%s\r" + "cpc=%s\rbpc=%s\rnba=%s\r", + params->disk, params->partition, params->code, params->id, + params->name, params->repository, params->sync_setup.sync, + params->sync_setup.diff, params->sync_setup.remove, + params->sync_setup.compress, params->sync_setup.cleanup, + params->sync_setup.cache, params->sync_setup.cleanup_cache, + params->sync_setup.remove_dst); + + msg = og_msg_alloc(buf, len); + if (!msg) + return -1; + + og_send_cmd((char **)params->ips_array, params->ips_array_len, + CLIENTE_OCUPADO, msg); + + og_msg_free(msg); + + return 0; +} + +static int og_cmd_create_incremental_image(json_t *element, struct og_msg_params *params) +{ + char buf[4096] = {}; + int err = 0, len; + const char *key; + json_t *value; + TRAMA *msg; + + if (json_typeof(element) != JSON_OBJECT) + return -1; + + json_object_foreach(element, key, value) { + if (!strcmp(key, "clients")) + err = og_json_parse_clients(value, params); + else if (!strcmp(key, "disk")) { + err = og_json_parse_string(value, ¶ms->disk); + params->flags |= OG_REST_PARAM_DISK; + } else if (!strcmp(key, "partition")) { + err = og_json_parse_string(value, ¶ms->partition); + params->flags |= OG_REST_PARAM_PARTITION; + } else if (!strcmp(key, "id")) { + err = og_json_parse_string(value, ¶ms->id); + params->flags |= OG_REST_PARAM_ID; + } else if (!strcmp(key, "name")) { + err = og_json_parse_string(value, ¶ms->name); + params->flags |= OG_REST_PARAM_NAME; + } else if (!strcmp(key, "repository")) { + err = og_json_parse_string(value, ¶ms->repository); + params->flags |= OG_REST_PARAM_REPO; + } else if (!strcmp(key, "sync_params")) { + err = og_json_parse_sync_params(value, params); + } + + if (err < 0) + break; + } + + if (!og_msg_params_validate(params, OG_REST_PARAM_ADDR | + OG_REST_PARAM_DISK | + OG_REST_PARAM_PARTITION | + OG_REST_PARAM_ID | + OG_REST_PARAM_NAME | + OG_REST_PARAM_REPO | + OG_REST_PARAM_SYNC_SYNC | + OG_REST_PARAM_SYNC_PATH | + OG_REST_PARAM_SYNC_DIFF | + OG_REST_PARAM_SYNC_DIFF_ID | + OG_REST_PARAM_SYNC_DIFF_NAME | + OG_REST_PARAM_SYNC_REMOVE | + OG_REST_PARAM_SYNC_COMPRESS | + OG_REST_PARAM_SYNC_CLEANUP | + OG_REST_PARAM_SYNC_CACHE | + OG_REST_PARAM_SYNC_CLEANUP_CACHE | + OG_REST_PARAM_SYNC_REMOVE_DST)) + return -1; + + len = snprintf(buf, sizeof(buf), + "nfn=CrearSoftIncremental\rdsk=%s\rpar=%s\ridi=%s\rnci=%s\r" + "rti=%s\ripr=%s\ridf=%s\rncf=%s\rmsy=%s\rwhl=%s\reli=%s\rcmp=%s\r" + "bpi=%s\rcpc=%s\rbpc=%s\rnba=%s\r", + params->disk, params->partition, params->id, params->name, + params->sync_setup.path, params->repository, params->sync_setup.diff_id, + params->sync_setup.diff_name, params->sync_setup.sync, + params->sync_setup.diff, params->sync_setup.remove_dst, + params->sync_setup.compress, params->sync_setup.cleanup, + params->sync_setup.cache, params->sync_setup.cleanup_cache, + params->sync_setup.remove_dst); + + msg = og_msg_alloc(buf, len); + if (!msg) + return -1; + + og_send_cmd((char **)params->ips_array, params->ips_array_len, + CLIENTE_OCUPADO, msg); + + og_msg_free(msg); + + return 0; +} + +static int og_cmd_restore_basic_image(json_t *element, struct og_msg_params *params) +{ + char buf[4096] = {}; + int err = 0, len; + const char *key; + json_t *value; + TRAMA *msg; + + if (json_typeof(element) != JSON_OBJECT) + return -1; + + json_object_foreach(element, key, value) { + if (!strcmp(key, "clients")) { + err = og_json_parse_clients(value, params); + } else if (!strcmp(key, "disk")) { + err = og_json_parse_string(value, ¶ms->disk); + params->flags |= OG_REST_PARAM_DISK; + } else if (!strcmp(key, "partition")) { + err = og_json_parse_string(value, ¶ms->partition); + params->flags |= OG_REST_PARAM_PARTITION; + } else if (!strcmp(key, "id")) { + err = og_json_parse_string(value, ¶ms->id); + params->flags |= OG_REST_PARAM_ID; + } else if (!strcmp(key, "name")) { + err = og_json_parse_string(value, ¶ms->name); + params->flags |= OG_REST_PARAM_NAME; + } else if (!strcmp(key, "repository")) { + err = og_json_parse_string(value, ¶ms->repository); + params->flags |= OG_REST_PARAM_REPO; + } else if (!strcmp(key, "profile")) { + err = og_json_parse_string(value, ¶ms->profile); + params->flags |= OG_REST_PARAM_PROFILE; + } else if (!strcmp(key, "type")) { + err = og_json_parse_string(value, ¶ms->type); + params->flags |= OG_REST_PARAM_TYPE; + } else if (!strcmp(key, "sync_params")) { + err = og_json_parse_sync_params(value, params); + } + + if (err < 0) + break; + } + + if (!og_msg_params_validate(params, OG_REST_PARAM_ADDR | + OG_REST_PARAM_DISK | + OG_REST_PARAM_PARTITION | + OG_REST_PARAM_ID | + OG_REST_PARAM_NAME | + OG_REST_PARAM_REPO | + OG_REST_PARAM_PROFILE | + OG_REST_PARAM_TYPE | + OG_REST_PARAM_SYNC_PATH | + OG_REST_PARAM_SYNC_METHOD | + OG_REST_PARAM_SYNC_SYNC | + OG_REST_PARAM_SYNC_DIFF | + OG_REST_PARAM_SYNC_REMOVE | + OG_REST_PARAM_SYNC_COMPRESS | + OG_REST_PARAM_SYNC_CLEANUP | + OG_REST_PARAM_SYNC_CACHE | + OG_REST_PARAM_SYNC_CLEANUP_CACHE | + OG_REST_PARAM_SYNC_REMOVE_DST)) + return -1; + + len = snprintf(buf, sizeof(buf), + "nfn=RestaurarImagenBasica\rdsk=%s\rpar=%s\ridi=%s\rnci=%s\r" + "ipr=%s\rifs=%s\rrti=%s\rmet=%s\rmsy=%s\rtpt=%s\rwhl=%s\r" + "eli=%s\rcmp=%s\rbpi=%s\rcpc=%s\rbpc=%s\rnba=%s\r", + params->disk, params->partition, params->id, params->name, + params->repository, params->profile, params->sync_setup.path, + params->sync_setup.method, params->sync_setup.sync, params->type, + params->sync_setup.diff, params->sync_setup.remove, + params->sync_setup.compress, params->sync_setup.cleanup, + params->sync_setup.cache, params->sync_setup.cleanup_cache, + params->sync_setup.remove_dst); + + msg = og_msg_alloc(buf, len); + if (!msg) + return -1; + + og_send_cmd((char **)params->ips_array, params->ips_array_len, + CLIENTE_OCUPADO, msg); + + og_msg_free(msg); + + return 0; +} + +static int og_cmd_restore_incremental_image(json_t *element, struct og_msg_params *params) +{ + char buf[4096] = {}; + int err = 0, len; + const char *key; + json_t *value; + TRAMA *msg; + + if (json_typeof(element) != JSON_OBJECT) + return -1; + + json_object_foreach(element, key, value) { + if (!strcmp(key, "clients")) { + err = og_json_parse_clients(value, params); + } else if (!strcmp(key, "disk")) { + err = og_json_parse_string(value, ¶ms->disk); + params->flags |= OG_REST_PARAM_DISK; + } else if (!strcmp(key, "partition")) { + err = og_json_parse_string(value, ¶ms->partition); + params->flags |= OG_REST_PARAM_PARTITION; + } else if (!strcmp(key, "id")) { + err = og_json_parse_string(value, ¶ms->id); + params->flags |= OG_REST_PARAM_ID; + } else if (!strcmp(key, "name")) { + err = og_json_parse_string(value, ¶ms->name); + params->flags |= OG_REST_PARAM_NAME; + } else if (!strcmp(key, "repository")) { + err = og_json_parse_string(value, ¶ms->repository); + params->flags |= OG_REST_PARAM_REPO; + } else if (!strcmp(key, "profile")) { + err = og_json_parse_string(value, ¶ms->profile); + params->flags |= OG_REST_PARAM_PROFILE; + } else if (!strcmp(key, "type")) { + err = og_json_parse_string(value, ¶ms->type); + params->flags |= OG_REST_PARAM_TYPE; + } else if (!strcmp(key, "sync_params")) { + err = og_json_parse_sync_params(value, params); + } + + if (err < 0) + break; + } + + if (!og_msg_params_validate(params, OG_REST_PARAM_ADDR | + OG_REST_PARAM_DISK | + OG_REST_PARAM_PARTITION | + OG_REST_PARAM_ID | + OG_REST_PARAM_NAME | + OG_REST_PARAM_REPO | + OG_REST_PARAM_PROFILE | + OG_REST_PARAM_TYPE | + OG_REST_PARAM_SYNC_DIFF_ID | + OG_REST_PARAM_SYNC_DIFF_NAME | + OG_REST_PARAM_SYNC_PATH | + OG_REST_PARAM_SYNC_METHOD | + OG_REST_PARAM_SYNC_SYNC | + OG_REST_PARAM_SYNC_DIFF | + OG_REST_PARAM_SYNC_REMOVE | + OG_REST_PARAM_SYNC_COMPRESS | + OG_REST_PARAM_SYNC_CLEANUP | + OG_REST_PARAM_SYNC_CACHE | + OG_REST_PARAM_SYNC_CLEANUP_CACHE | + OG_REST_PARAM_SYNC_REMOVE_DST)) + return -1; + + len = snprintf(buf, sizeof(buf), + "nfn=RestaurarSoftIncremental\rdsk=%s\rpar=%s\ridi=%s\rnci=%s\r" + "ipr=%s\rifs=%s\ridf=%s\rncf=%s\rrti=%s\rmet=%s\rmsy=%s\r" + "tpt=%s\rwhl=%s\reli=%s\rcmp=%s\rbpi=%s\rcpc=%s\rbpc=%s\r" + "nba=%s\r", + params->disk, params->partition, params->id, params->name, + params->repository, params->profile, params->sync_setup.diff_id, + params->sync_setup.diff_name, params->sync_setup.path, + params->sync_setup.method, params->sync_setup.sync, params->type, + params->sync_setup.diff, params->sync_setup.remove, + params->sync_setup.compress, params->sync_setup.cleanup, + params->sync_setup.cache, params->sync_setup.cleanup_cache, + params->sync_setup.remove_dst); + + msg = og_msg_alloc(buf, len); + if (!msg) + return -1; + + og_send_cmd((char **)params->ips_array, params->ips_array_len, + CLIENTE_OCUPADO, msg); + + og_msg_free(msg); + + return 0; +} + +static int og_client_method_not_found(struct og_client *cli) +{ + /* To meet RFC 7231, this function MUST generate an Allow header field + * containing the correct methods. For example: "Allow: POST\r\n" + */ + char buf[] = "HTTP/1.1 405 Method Not Allowed\r\n" + "Content-Length: 0\r\n\r\n"; + + send(og_client_socket(cli), buf, strlen(buf), 0); + + return -1; +} + +static int og_client_bad_request(struct og_client *cli) +{ + char buf[] = "HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n"; + + send(og_client_socket(cli), buf, strlen(buf), 0); + + return -1; +} + +static int og_client_not_found(struct og_client *cli) +{ + char buf[] = "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n"; + + send(og_client_socket(cli), buf, strlen(buf), 0); + + return -1; +} + +static int og_client_not_authorized(struct og_client *cli) +{ + char buf[] = "HTTP/1.1 401 Unauthorized\r\n" + "WWW-Authenticate: Basic\r\n" + "Content-Length: 0\r\n\r\n"; + + send(og_client_socket(cli), buf, strlen(buf), 0); + + return -1; +} + +static int og_server_internal_error(struct og_client *cli) +{ + char buf[] = "HTTP/1.1 500 Internal Server Error\r\n" + "Content-Length: 0\r\n\r\n"; + + send(og_client_socket(cli), buf, strlen(buf), 0); + + return -1; +} + +static int og_client_payload_too_large(struct og_client *cli) +{ + char buf[] = "HTTP/1.1 413 Payload Too Large\r\n" + "Content-Length: 0\r\n\r\n"; + + send(og_client_socket(cli), buf, strlen(buf), 0); + + return -1; +} + +#define OG_MSG_RESPONSE_MAXLEN 65536 + +static int og_client_ok(struct og_client *cli, char *buf_reply) +{ + char buf[OG_MSG_RESPONSE_MAXLEN] = {}; + int err = 0, len; + + len = snprintf(buf, sizeof(buf), + "HTTP/1.1 200 OK\r\nContent-Length: %ld\r\n\r\n%s", + strlen(buf_reply), buf_reply); + if (len >= (int)sizeof(buf)) + err = og_server_internal_error(cli); + + send(og_client_socket(cli), buf, strlen(buf), 0); + + return err; +} + +enum og_rest_method { + OG_METHOD_GET = 0, + OG_METHOD_POST, +}; + +static int og_client_state_process_payload_rest(struct og_client *cli) +{ + char buf_reply[OG_MSG_RESPONSE_MAXLEN] = {}; + struct og_msg_params params = {}; + enum og_rest_method method; + const char *cmd, *body; + json_error_t json_err; + json_t *root = NULL; + int err = 0; + + syslog(LOG_DEBUG, "%s:%hu %.32s ...\n", + inet_ntoa(cli->addr.sin_addr), + ntohs(cli->addr.sin_port), cli->buf); + + if (!strncmp(cli->buf, "GET", strlen("GET"))) { + method = OG_METHOD_GET; + cmd = cli->buf + strlen("GET") + 2; + } else if (!strncmp(cli->buf, "POST", strlen("POST"))) { + method = OG_METHOD_POST; + cmd = cli->buf + strlen("POST") + 2; + } else + return og_client_method_not_found(cli); + + body = strstr(cli->buf, "\r\n\r\n") + 4; + + if (strcmp(cli->auth_token, auth_token)) { + syslog(LOG_ERR, "wrong Authentication key\n"); + return og_client_not_authorized(cli); + } + + if (cli->content_length) { + root = json_loads(body, 0, &json_err); + if (!root) { + syslog(LOG_ERR, "malformed json line %d: %s\n", + json_err.line, json_err.text); + return og_client_not_found(cli); + } + } + + if (!strncmp(cmd, "clients", strlen("clients"))) { + if (method != OG_METHOD_POST && + method != OG_METHOD_GET) + return og_client_method_not_found(cli); + + if (method == OG_METHOD_POST && !root) { + syslog(LOG_ERR, "command clients with no payload\n"); + return og_client_bad_request(cli); + } + switch (method) { + case OG_METHOD_POST: + err = og_cmd_post_clients(root, ¶ms); + break; + case OG_METHOD_GET: + err = og_cmd_get_clients(root, ¶ms, buf_reply); + break; + } + } else if (!strncmp(cmd, "wol", strlen("wol"))) { + if (method != OG_METHOD_POST) + return og_client_method_not_found(cli); + + if (!root) { + syslog(LOG_ERR, "command wol with no payload\n"); + return og_client_bad_request(cli); + } + err = og_cmd_wol(root, ¶ms); + } else if (!strncmp(cmd, "shell/run", strlen("shell/run"))) { + if (method != OG_METHOD_POST) + return og_client_method_not_found(cli); + + if (!root) { + syslog(LOG_ERR, "command run with no payload\n"); + return og_client_bad_request(cli); + } + err = og_cmd_run_post(root, ¶ms); + } else if (!strncmp(cmd, "shell/output", strlen("shell/output"))) { + if (method != OG_METHOD_POST) + return og_client_method_not_found(cli); + + if (!root) { + syslog(LOG_ERR, "command output with no payload\n"); + return og_client_bad_request(cli); + } + + err = og_cmd_run_get(root, ¶ms, buf_reply); + } else if (!strncmp(cmd, "session", strlen("session"))) { + if (method != OG_METHOD_POST) + return og_client_method_not_found(cli); + + if (!root) { + syslog(LOG_ERR, "command session with no payload\n"); + return og_client_bad_request(cli); + } + err = og_cmd_session(root, ¶ms); + } else if (!strncmp(cmd, "poweroff", strlen("poweroff"))) { + if (method != OG_METHOD_POST) + return og_client_method_not_found(cli); + + if (!root) { + syslog(LOG_ERR, "command poweroff with no payload\n"); + return og_client_bad_request(cli); + } + err = og_cmd_poweroff(root, ¶ms); + } else if (!strncmp(cmd, "reboot", strlen("reboot"))) { + if (method != OG_METHOD_POST) + return og_client_method_not_found(cli); + + if (!root) { + syslog(LOG_ERR, "command reboot with no payload\n"); + return og_client_bad_request(cli); + } + err = og_cmd_reboot(root, ¶ms); + } else if (!strncmp(cmd, "stop", strlen("stop"))) { + if (method != OG_METHOD_POST) + return og_client_method_not_found(cli); + + if (!root) { + syslog(LOG_ERR, "command stop with no payload\n"); + return og_client_bad_request(cli); + } + err = og_cmd_stop(root, ¶ms); + } else if (!strncmp(cmd, "refresh", strlen("refresh"))) { + if (method != OG_METHOD_POST) + return og_client_method_not_found(cli); + + if (!root) { + syslog(LOG_ERR, "command refresh with no payload\n"); + return og_client_bad_request(cli); + } + err = og_cmd_refresh(root, ¶ms); + } else if (!strncmp(cmd, "hardware", strlen("hardware"))) { + if (method != OG_METHOD_POST) + return og_client_method_not_found(cli); + + if (!root) { + syslog(LOG_ERR, "command hardware with no payload\n"); + return og_client_bad_request(cli); + } + err = og_cmd_hardware(root, ¶ms); + } else if (!strncmp(cmd, "software", strlen("software"))) { + if (method != OG_METHOD_POST) + return og_client_method_not_found(cli); + + if (!root) { + syslog(LOG_ERR, "command software with no payload\n"); + return og_client_bad_request(cli); + } + err = og_cmd_software(root, ¶ms); + } else if (!strncmp(cmd, "image/create/basic", + strlen("image/create/basic"))) { + if (method != OG_METHOD_POST) + return og_client_method_not_found(cli); + + if (!root) { + syslog(LOG_ERR, "command create with no payload\n"); + return og_client_bad_request(cli); + } + err = og_cmd_create_basic_image(root, ¶ms); + } else if (!strncmp(cmd, "image/create/incremental", + strlen("image/create/incremental"))) { + if (method != OG_METHOD_POST) + return og_client_method_not_found(cli); + + if (!root) { + syslog(LOG_ERR, "command create with no payload\n"); + return og_client_bad_request(cli); + } + err = og_cmd_create_incremental_image(root, ¶ms); + } else if (!strncmp(cmd, "image/create", strlen("image/create"))) { + if (method != OG_METHOD_POST) + return og_client_method_not_found(cli); + + if (!root) { + syslog(LOG_ERR, "command create with no payload\n"); + return og_client_bad_request(cli); + } + err = og_cmd_create_image(root, ¶ms); + } else if (!strncmp(cmd, "image/restore/basic", + strlen("image/restore/basic"))) { + if (method != OG_METHOD_POST) + return og_client_method_not_found(cli); + + if (!root) { + syslog(LOG_ERR, "command create with no payload\n"); + return og_client_bad_request(cli); + } + err = og_cmd_restore_basic_image(root, ¶ms); + } else if (!strncmp(cmd, "image/restore/incremental", + strlen("image/restore/incremental"))) { + if (method != OG_METHOD_POST) + return og_client_method_not_found(cli); + + if (!root) { + syslog(LOG_ERR, "command create with no payload\n"); + return og_client_bad_request(cli); + } + err = og_cmd_restore_incremental_image(root, ¶ms); + } else if (!strncmp(cmd, "image/restore", strlen("image/restore"))) { + if (method != OG_METHOD_POST) + return og_client_method_not_found(cli); + + if (!root) { + syslog(LOG_ERR, "command create with no payload\n"); + return og_client_bad_request(cli); + } + err = og_cmd_restore_image(root, ¶ms); + } else if (!strncmp(cmd, "setup", strlen("setup"))) { + if (method != OG_METHOD_POST) + return og_client_method_not_found(cli); + + if (!root) { + syslog(LOG_ERR, "command create with no payload\n"); + return og_client_bad_request(cli); + } + err = og_cmd_setup(root, ¶ms); + } else if (!strncmp(cmd, "run/schedule", strlen("run/schedule"))) { + if (method != OG_METHOD_POST) + return og_client_method_not_found(cli); + + if (!root) { + syslog(LOG_ERR, "command create with no payload\n"); + return og_client_bad_request(cli); + } + + err = og_cmd_run_schedule(root, ¶ms); + } else { + syslog(LOG_ERR, "unknown command: %.32s ...\n", cmd); + err = og_client_not_found(cli); + } + + if (root) + json_decref(root); + + if (err < 0) + return og_client_bad_request(cli); + + err = og_client_ok(cli, buf_reply); + if (err < 0) { + syslog(LOG_ERR, "HTTP response to %s:%hu is too large\n", + inet_ntoa(cli->addr.sin_addr), + ntohs(cli->addr.sin_port)); + } + + return err; +} + +static int og_client_state_recv_hdr_rest(struct og_client *cli) +{ + char *ptr; + + ptr = strstr(cli->buf, "\r\n\r\n"); + if (!ptr) + return 0; + + cli->msg_len = ptr - cli->buf + 4; + + ptr = strstr(cli->buf, "Content-Length: "); + if (ptr) { + sscanf(ptr, "Content-Length: %i[^\r\n]", &cli->content_length); + if (cli->content_length < 0) + return -1; + cli->msg_len += cli->content_length; + } + + ptr = strstr(cli->buf, "Authorization: "); + if (ptr) + sscanf(ptr, "Authorization: %63[^\r\n]", cli->auth_token); + + return 1; +} + +static void og_client_read_cb(struct ev_loop *loop, struct ev_io *io, int events) +{ + struct og_client *cli; + int ret; + + cli = container_of(io, struct og_client, io); + + if (events & EV_ERROR) { + syslog(LOG_ERR, "unexpected error event from client %s:%hu\n", + inet_ntoa(cli->addr.sin_addr), + ntohs(cli->addr.sin_port)); + goto close; + } + + ret = recv(io->fd, cli->buf + cli->buf_len, + sizeof(cli->buf) - cli->buf_len, 0); + if (ret <= 0) { + if (ret < 0) { + syslog(LOG_ERR, "error reading from client %s:%hu (%s)\n", + inet_ntoa(cli->addr.sin_addr), ntohs(cli->addr.sin_port), + strerror(errno)); + } else { + syslog(LOG_DEBUG, "closed connection by %s:%hu\n", + inet_ntoa(cli->addr.sin_addr), ntohs(cli->addr.sin_port)); + } + goto close; + } + + if (cli->keepalive_idx >= 0) + return; + + ev_timer_again(loop, &cli->timer); + + cli->buf_len += ret; + if (cli->buf_len >= sizeof(cli->buf)) { + syslog(LOG_ERR, "client request from %s:%hu is too long\n", + inet_ntoa(cli->addr.sin_addr), ntohs(cli->addr.sin_port)); + og_client_payload_too_large(cli); + goto close; + } + + switch (cli->state) { + case OG_CLIENT_RECEIVING_HEADER: + if (cli->rest) + ret = og_client_state_recv_hdr_rest(cli); + else + ret = og_client_state_recv_hdr(cli); + + if (ret < 0) + goto close; + if (!ret) + return; + + cli->state = OG_CLIENT_RECEIVING_PAYLOAD; + /* Fall through. */ + case OG_CLIENT_RECEIVING_PAYLOAD: + /* Still not enough data to process request. */ + if (cli->buf_len < cli->msg_len) + return; + + cli->state = OG_CLIENT_PROCESSING_REQUEST; + /* fall through. */ + case OG_CLIENT_PROCESSING_REQUEST: + if (cli->rest) { + ret = og_client_state_process_payload_rest(cli); + if (ret < 0) { + syslog(LOG_ERR, "Failed to process HTTP request from %s:%hu\n", + inet_ntoa(cli->addr.sin_addr), + ntohs(cli->addr.sin_port)); + } + } else { + ret = og_client_state_process_payload(cli); + } + if (ret < 0) + goto close; + + if (cli->keepalive_idx < 0) { + syslog(LOG_DEBUG, "server closing connection to %s:%hu\n", + inet_ntoa(cli->addr.sin_addr), ntohs(cli->addr.sin_port)); + goto close; + } else { + syslog(LOG_DEBUG, "leaving client %s:%hu in keepalive mode\n", + inet_ntoa(cli->addr.sin_addr), + ntohs(cli->addr.sin_port)); + og_client_keepalive(loop, cli); + og_client_reset_state(cli); + } + break; + default: + syslog(LOG_ERR, "unknown state, critical internal error\n"); + goto close; + } + return; +close: + ev_timer_stop(loop, &cli->timer); + og_client_release(loop, cli); +} + +static void og_client_timer_cb(struct ev_loop *loop, ev_timer *timer, int events) +{ + struct og_client *cli; + + cli = container_of(timer, struct og_client, timer); + if (cli->keepalive_idx >= 0) { + ev_timer_again(loop, &cli->timer); + return; + } + syslog(LOG_ERR, "timeout request for client %s:%hu\n", + inet_ntoa(cli->addr.sin_addr), ntohs(cli->addr.sin_port)); + + og_client_release(loop, cli); +} + +static int socket_s, socket_rest; + +static void og_server_accept_cb(struct ev_loop *loop, struct ev_io *io, + int events) +{ + struct sockaddr_in client_addr; + socklen_t addrlen = sizeof(client_addr); + struct og_client *cli; + int client_sd; + + if (events & EV_ERROR) + return; + + client_sd = accept(io->fd, (struct sockaddr *)&client_addr, &addrlen); + if (client_sd < 0) { + syslog(LOG_ERR, "cannot accept client connection\n"); + return; + } + + cli = (struct og_client *)calloc(1, sizeof(struct og_client)); + if (!cli) { + close(client_sd); + return; + } + memcpy(&cli->addr, &client_addr, sizeof(client_addr)); + cli->keepalive_idx = -1; + + if (io->fd == socket_rest) + cli->rest = true; + + syslog(LOG_DEBUG, "connection from client %s:%hu\n", + inet_ntoa(cli->addr.sin_addr), ntohs(cli->addr.sin_port)); + + ev_io_init(&cli->io, og_client_read_cb, client_sd, EV_READ); + ev_io_start(loop, &cli->io); + ev_timer_init(&cli->timer, og_client_timer_cb, OG_CLIENT_TIMEOUT, 0.); + ev_timer_start(loop, &cli->timer); +} + +static int og_socket_server_init(const char *port) +{ + struct sockaddr_in local; + int sd, on = 1; + + sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sd < 0) { + syslog(LOG_ERR, "cannot create main socket\n"); + return -1; + } + setsockopt(sd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(int)); + + local.sin_addr.s_addr = htonl(INADDR_ANY); + local.sin_family = AF_INET; + local.sin_port = htons(atoi(port)); + + if (bind(sd, (struct sockaddr *) &local, sizeof(local)) < 0) { + close(sd); + syslog(LOG_ERR, "cannot bind socket\n"); + return -1; + } + + listen(sd, 250); + + return sd; +} + +int main(int argc, char *argv[]) +{ + struct ev_io ev_io_server, ev_io_server_rest; + struct ev_loop *loop = ev_default_loop(0); + int i; + + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) + exit(EXIT_FAILURE); + + openlog("ogAdmServer", LOG_PID, LOG_DAEMON); + + /*-------------------------------------------------------------------------------------------------------- + Validación de parámetros de ejecución y lectura del fichero de configuración del servicio + ---------------------------------------------------------------------------------------------------------*/ + if (!validacionParametros(argc, argv, 1)) // Valida parámetros de ejecución + exit(EXIT_FAILURE); + + if (!tomaConfiguracion(szPathFileCfg)) { // Toma parametros de configuracion + exit(EXIT_FAILURE); + } + + /*-------------------------------------------------------------------------------------------------------- + // Inicializa array de información de los clientes + ---------------------------------------------------------------------------------------------------------*/ + for (i = 0; i < MAXIMOS_CLIENTES; i++) { + tbsockets[i].ip[0] = '\0'; + tbsockets[i].cli = NULL; + } + /*-------------------------------------------------------------------------------------------------------- + Creación y configuración del socket del servicio + ---------------------------------------------------------------------------------------------------------*/ + socket_s = og_socket_server_init(puerto); + if (socket_s < 0) + exit(EXIT_FAILURE); + + ev_io_init(&ev_io_server, og_server_accept_cb, socket_s, EV_READ); + ev_io_start(loop, &ev_io_server); + + socket_rest = og_socket_server_init("8888"); + if (socket_rest < 0) + exit(EXIT_FAILURE); + + ev_io_init(&ev_io_server_rest, og_server_accept_cb, socket_rest, EV_READ); + ev_io_start(loop, &ev_io_server_rest); + + infoLog(1); // Inicio de sesión + + /* old log file has been deprecated. */ + og_log(97, false); + + syslog(LOG_INFO, "Waiting for connections\n"); + + while (1) + ev_loop(loop, 0); + + exit(EXIT_SUCCESS); +} diff --git a/native/Sources/Services/ogAdmServer/sources/ogAdmServer.h b/native/Sources/Services/ogAdmServer/sources/ogAdmServer.h new file mode 100644 index 0000000..a8128c1 --- /dev/null +++ b/native/Sources/Services/ogAdmServer/sources/ogAdmServer.h @@ -0,0 +1,60 @@ +// ******************************************************************************************************** +// Servicio: ogAdmServer +// Autor: José Manuel Alonso (E.T.S.I.I.) Universidad de Sevilla +// Fecha Creación: Marzo-2010 +// Fecha Última modificación: Marzo-2010 +// Nombre del fichero: ogAdmServer.h +// Descripción: Este fichero implementa el servicio de administración general del sistema +// ******************************************************************************************************** +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ogAdmLib.h" +// ________________________________________________________________________________________________________ +// Variables globales +// ________________________________________________________________________________________________________ +char servidoradm[LONPRM]; // Dirección IP del servidor de administración +char puerto[LONPRM]; // Puerto de comunicación + +struct og_client; + +typedef struct{ // Estructura usada para guardar información de los clientes + char ip[LONIP]; // IP del cliente + char estado[4]; // Tipo de Sistema Operativo en que se encuentra el cliente + struct og_client *cli; +}SOCKETCL; +SOCKETCL tbsockets[MAXIMOS_CLIENTES]; + +struct og_dbi; + +bool registraCliente(char *); +bool procesoInclusionClienteWinLnx(int socket, TRAMA*,int*,char*); +bool procesoInclusionCliente(struct og_client *, TRAMA*); +bool clienteExistente(char *,int *); +bool clienteDisponible(char *,int *); +bool actualizaConfiguracion(struct og_dbi *,char* ,int); +bool recorreProcedimientos(struct og_dbi *,char* ,FILE*,char*); +bool buscaComandos(char *,TRAMA *,int *); +bool respuestaConsola(int socket, TRAMA *,int); +bool enviaComando(TRAMA *ptrTrama,const char*); +bool Levanta(char**, char**, char**, int, char*); +bool WakeUp(int,char*,char*,char*,char*); +void PasaHexBin(char *,char *); +bool actualizaCreacionImagen(struct og_dbi *,char*,char*,char*,char*,char*,char*); +bool actualizaRestauracionImagen(struct og_dbi *,char*,char*,char*,char*,char*); +bool actualizaHardware(struct og_dbi *dbi, char* ,char*,char*,char*); +bool cuestionPerfilHardware(struct og_dbi *dbi,char*,char*,int,char*,char*,int *,int); +bool actualizaSoftware(struct og_dbi *, char* , char* , char*,char*,char*); +bool cuestionPerfilSoftware(struct og_dbi *, char*, char*,int,int,char*,char*,char*,int *,int); + +int checkDato(struct og_dbi *,char*,const char*,const char*,const char*); diff --git a/native/Sources/Services/ogAdmServer/sources/ogAdmServer.o b/native/Sources/Services/ogAdmServer/sources/ogAdmServer.o new file mode 100644 index 0000000000000000000000000000000000000000..1da52d87c6d062732c27a0a0c2e2c443eb26fde5 GIT binary patch literal 249504 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⪻ILq0-CT|7E@9 zhzp%w=HQog4p=0*j`SHl^kW5HQE@_Y4Hy~c)5Wr~mpO(hoYNMI`g@rt(8JkAiC~XC z0D-Z*(qcXeL$3T~z3D3xdXVp92In6Mu?Xb*U*02*=E6H_T}`AbE5+Yli5rJ^#@2`_WRp!}tx zD(QLDkon~zy+r{>iOjDQX)+I>&CL305qmW+^T99g2_i;>*F=CMU*tVs6c@1ImFcOF z9&p0+jF%p8OL|@?;=@Did`!<7=|R;YY2(Qd??>|3DGOKb`B70^_8#|w!@}ZQ!z}(x zz~WVPhQ+rQv4e!#9M&wpwTL|=ARpFhGf>3d91KMdtIa^sXi^(dBxXh0u;O9NVZBKg z>AldS=wZDXGz>jXa4c`gF!b1AP4}=7)RUOOdb6{Lojx?_aF)e45o_@$gg<6j10BXe z`r}2hw=e)rJ8byS8s-DK`OhBtK){FAqUj_9cvDfm)}nfG5;qGgE5jf@7qD_aidAw; zWzj?t-B-w5qMf^Re6+BXT%Mabpv~rEvI^DIgpnUBWcP+VH0WUO20YCK6ZuWs zq;&d*zb<4aN}K~99}rHtnTNIW{$QAnwm>p(1ay3`aE=I53;TnG{K*tR6e>PgxQYtu zV5Ok&p~4EfnS>uB|IJF(7hWfuHw|85_|;b!-khL*}reaD0wAtTN`Xk<6jNFk-Xtx+)C5 zj@d=JnOI;5uPVHlz*DVO=&UZBAe=akx+fe}As>XfnPXaw#utvJyMv^4LLomj!Ujo& zi-|@!n?*P^1}80?Nk%x0R*aq=VWs%zc*rfBVN?jw_XmjnbDa0rft|h>UugKi-tk}L z>6ZYne~oL`pw~Sc=g3aU^cBSB=fX?UHjw`8k?#geJ{Q-{O-GRD;+JiVFzJHzGhq-* z7yyANz09!pnYi{Y9SXlCZF$ec7t;YNuoMtagh6~K0MQ$Ocp@J5hflXe>}c~8bO%+bbR!`g8hVHjGm)n`80&l91h@A zUv7$i4e@LCB3=c?zdFvk4!EKRjd5L>zFCrqHCcH~CUD-ecyLaE7|wt8$aR1(dbuoK zL*=d@lFW;jP}}?+)))531Hmox;*-g%B04BG(0TC%BxdkRP`V^QQK(FWscaAY#ESuy zi8vQw11c-xQ5tkI8>CwLDjFO|J!>^#-Qx0MgpC|mf zV5QvOr0@TB_WtbY(S$1vKHn~wEKxA}KO$8x!y(4e~cU@%`Y$}*e3woIYncaeNQ^B=_vX{9%vs>i4zQE@jGB0L( zCh49JyvNL0sC)L~1P|X~>1_~Xum{TQmYjG)0iUBH??exMza!>UkVYYGGmtkFR7=kp zx~p}klVv$@QmJigCPhGf(KKc_=oS1WNguaOQA~b%*jbML#d#9ga;B7)SOfe5)CC zTr3?XD?Ae8<8jIg6>G_n*hDd(F=R{+#*8ruZy$^qW0Lkm@~Ik_va}zLv9`dGv#xvw z)Mj<%JYAv*G>tW((n=76goou}9=@hyiaoOZ8pFf#*bLdx95}FB(bs@`<+1rhAPf^d zOmcZ_k@UQF_io{Ad2FThyow&?Q$>tZVK4Iv&jIHuVq;~_i+97sEU!`+fer3~-S#ci zW=6o^&}PO}ig;jf=xH8=QC7yvw80(FPNFhaDLs#7d!|ayG1D_T7A4j3G9NWPV`6MT z*u0vas#t`W0GD8Ts$>3u^z7HpraCr>K1~KcD59x0oH=do87sm=!Nv596Y;?v9XOw{ zpU3_!wsTrqY@`<*!*Y}e2FAun6YI8m7C#hy^K_gQ})86E9d#K}JEuOX`=-4s{; zeV zTVk|%lOOg7oBXgx*yQJVgzJ6&Tu;^`T<`Oz;j<3ldY{dKjv9r@*ZT|UaTsPYtL+T} z!^!xfCR^k~bAd%~^^HZ78QkcH{lPZV-grLa7RtXC4N<;|^?^k1U27=+T9obB&AisH z=(kb4uSGT9ul9@0el5yY>}Fo+7nA*3bQMVol`NV8(LNQ`+u1YyLdiGevw)y|Ksvq| zW#sWtNzlRO2xbh34S&nbI50reek-c?QXd>3oBvi+Kf`lyKn(m_Q8sWKplX1pqgp}l z)*VkrwSpek9nVA~L^if(y5rkXzd|~O1sUg6ho$q~2KBLo##jkLO^l~DY&^e(=;+TL8Cq_Pr#3n-n@B)w zYoiMVmnDjRq0Os{a%r2Kq(sEJ=x9+O+HI59tc&uVh1~}ICKNj6$c!U;13ph|DewyO zMdeJKzYECZ(?fks&sZx#$g(i1!ot7-8~@oO-@^=#uDz=0<&t}Kh%Bn28z>3{yVux- zK$O)R5V7c`ArP@>QwYSjBOzs99Vq+j0f=u$YQw?k+mY)CMm89IHGQuq~Sixkq4OTzT0BkKbCxp+S$@FviQ1vmM@lsmNl2S)9R>BF z2x}eWXft)gkqBiz*&@%*h(5Z7(Q$^n%g|>YLfjqEv1l_}(d*}-m-?9A3HS^ou9@DJ zFuhr=TfWlJ+Y*@@Qkj-Wqfr?#L?;r8Vs`~Ro4d*wq7#`DGDIhGg>XO{q7yNWTN|Pq zDG@V+A<~a1dah`M7$SaA5gpTH1`>GYsvDUr9rtU?RKm5uG9!jZU^Bi5+=~jE$rCZ# zqY?e6rbO%|z&>ARsE>I#+DZ_0%)_Tt$Yv@7o0;2Sc=(jMD6BJ2sTIUTwi(u$Z>X$^ zunBxaX%o@;*f*3mk3={%ln4XCek5F_6?;?WQnNEH&tK~m_Q+1R-^=w zs{yA~V7076OMv}3^rMgI9|ihx&Gg?FrhiuuM*cCN|2{QM=r>{HKDAWnHyVAvpvS_X zbJLF*>~vW9m|+KmXL*0EA_rh0c$Sk<{pER3r9MWr)JhN{jA~mL)lUVe-gli5Rhya- zMzu}x1DPE0*!VgOsxV(dwbP)g&AZc}y4j{*sr7C#IA^6D3^uyS*6n{q7$!wPi7b$-H7)hCVT3eOfUP5=gsyi-LW%QX7 zFfiyCEgeq;9n+-a$=l>v-8q3S^7Gua!ZSB>>Nd-V>#|M*CFYP4xxVRzOz^$pwUG;4 z)kwTS+We+BNi=4gc=0#AD`^b+ruZ22EpNhNZtG(%OHEkL?InVdW5m{F#pt_2V)Uuy z7=4cpE7lLg2z)$HthWpTKlG*$0-!IJ5S~N?X?)e(Y&8(I-GZ_P9<0v(Y;Bs29?yuLfv8wZ_=P zqh5tPjc-mQ9`&Y3hpDYR>Rl=w$bNa_^r*K|NPQTTkybtGtv9ZjABz85PoEm3 z{Rlgsg&i|kFBivj%+r;XMjKlGm>FHAX*d|B;gLZ07p*lk9Q4MD@VkVu2fd3Wq}{C8 z^&RxClJSo7xf3zJ9pPp61*|#|EVIL_Ca|ax?AP|S!<$Q|RH!3RS(BVax5s`S^@;sj z`MW)K^f+10_KeCFLz$yJLNp$G-hZ1YTw_?_wg%`V*BPoCJ^m6dIC!DD(VHt&E|>Ni zuT&qQX?GDyDWase3|&7?T3*Ia%s98L=zlr6pHE*0fDkd*S^AKWTRCireAMM5SxP84PTucw#sTjn;~x`_2Km@uyV?s#B>X4fl8uB@ z2=Q;|RMN7z`Bb?1)u1WWB?7=fp~y-QVeFV&!|eVA;WKenQubA?u8#Oe zw5Z#Jp{OR2fvFgj*Rd0bvL=mzd9Hd<4-XQj4sJS9v_pdal8RV~Y zM~iBIT~l+Fu@3X>=vD4$@g2sXt~TcKtXM-{_QcOXviO5DRb1$S z!@A(`2WQD%AH$j3%`;4Ya#+3ji7F{hy>5C&=(#6yvu`-s%^pEdYG;aiw424#QlgJ< zIzDj<&e`<5<*--aXQ5<{5;9o*8Gc*>SS6qjK`hOC+xB*?O$y& zO7plgfs`f)1dltj1m+S^na7>0!-3$_4u4XJQWKf+X=j$qFt-00M?Z$6GmXz0wDei- z|8h)ilzDU*xMX>Bo-W}MO=B*_tOTJiT>7IO;?mzj8|cp-Idp@e@Q*gvhw;EpkK(G+ zA8jrWBS!RyfBU1oL{$3qZml!6cHj5**szkjs!q@Fmx%>Ve z(TmsZa_M-Y#~v6Sq#0GxF%ry}ARR}7j%m{Ia1T8j@VY%)n#c9{8@4WYzSt{kykS=n zg-HK`j%iebKxhm)=G_{hju(e*{O$p=Tw6!NXEo3_<&&RaOs~`;-n2)FH@Ll5_K|Pe ze4GQ%1xngL>zlSd8G8*Kz1%TFZh5uG#t#e83|#|zrNocsF!`q*1_{k z&@t-!K4E%VcUYo8h&Mf~4&n1Ksl+}G>RBBkS`+RFYr+knT{L0p7H9(3C-1Q5lDj~? zd`MLH4tt>_HLnf{TlX2k->%EbciKAi9|0HXsgOHu?c7ceOC;NG>%H@n`j)#4eP0u4wL}i9FX|LTN7JLI+NgM?)mOcx%fY~J3( z!H5u2_EHi83Sh$Wb|ICX0~C|42@6uDkx4ggH85-JnS!|4FV)yrn(S0ot+7{`gv4~* zC~AnRJNxR%BDWypm?Dw}o`Tm@5hEu~6g8%a6p^touWkl1S+B*s+88j0o9lz7I54_{ zHFms}AX4yPhGo5$d){5jD-)^ziSa5L_g>?452i3Yigoz4m7>DOhw+h4jQ=qrocPM{ zSjl&F`?Mba6W6WW%-4s<@^0Zz`t0UAdi?c3^Q@}~;EN%FLW=xnk6d~qz6*k*%!^}* zv*6oU*#E!qa!#hpnoSeEXF~4y7P%V)-NCoRmp(I|#DKd6*fY}wSQ7#JP9CsNhJgJp z0QRf__Q?q$V4s|!feC9q6@X*P?q{#8fmkd=H0%+38z^Jm-oKEp>9M64eJ>~rs5VzX zhq0xnKYQepm?#3Oovowa@L2xz&0NjU7d3d`>*z za)&3#VwSZ*CqqKka{iKHc*|kSLInW+Y#(Dnm&gVA&mMWfAn5L!BM&9&$CjK1w?fa7 z%oz=PWc0hVZDDw#e4bmLq(gfaX;_d{*wZ|pY|Z?=PhZ$0T?PiGOrQpB7+9t48J>Q^ zOpME(>G5~A!kDaB3fjb$Z%4e?7VWkP=o7y9Yd8Q<0b89+$ceFGB*kt zkDxOu@IY8q7!c5`KXWfY;IFP}m82hvC#bL?b6Ncdh-n=_$VBF)5V=>bw zNfdqn(X#JxIRlVSn~;mK?-h1=nIC+44?YuMf5g?f^qDVfq19$8Ly34_u)+hu3d`I|qJ@%qK&;P$xFR3oG}&cp zK-L!mh;IcDr-{)=Ibj?2*>9GE|hOKIKJZO12pY=ej&R*GUiUp{-xQQY{R1pXqh4vr(tO8 znyB7T+0Q#6E8_@(HSLibu%2kf^A1}G;G4FoCu*F(8isLqfb*vU7+-b9lBNeJm~=R*$H|DnU;9UgLYMQ|JH zplQo~$hlHSSG`836N&>*I6{a$@=~xyr|<&LGr}4f$`0YTe4dTH#STY5acMrIW$$o! zf8}LP8Jq1gJ9hIKExXInkDdiSrOO#3yK(cWEzHs0!F<-s?sf{u1ERnh_?^{ZPGxO> zORM43YNuK_C7-3RS3COjtVuYv+Hk6ox8e30qf;G1^BSQr8!%>v*DMHp-9H6u&Nf;k z^;vtiGfRB8)_^%?4XK~mwMMJ8b=Nv2^lhm)iQziAUFQ@~XbrM#yDV%uZGjfnr3~F= zj($1=0P**>K9|%~{ocMve4eK24<^U%U}duZ zAR8sh!C6>#v&ZI2ix z-b|}KVr!SzL90Dt>tpUN6gtof+}}I`dyiIB%Yt*_KYQesV3m7pKH;EngH`S|RBWbI z?ln{}P4^laI_T4B`11Gj!2MbPcTWiJ9U-`Pgy7zhh0CJ2B`k_Pf-}6e+hJI9i#^6f z-JO=*WmwZCvb_F60G0;;t`7m&oCP3(|MD<|zYkD&IDl}u6j>NV%41e}A7@gF|%*J+5G63AWA?||D=?8&cS5}W|q(4{H%Y-u;yAby%zSrZ=% zE7l{y0?ok!F?$T1?g4eNu9&T3ND~&I0B_Hi1sd&P%Z_Ikm_@*TuOVi?dVl0E0eTdD zJCgnT>NOaq42n0F_SsONU#V}kNB$|e<1E*zfXgWM6=(TK6||1+#xKCzFY~r53-8$w zyyXGB3D~m=fM=@@Fb3NM!1wb2yf@_S{+<~?nZREH0Pn}__v+`&Pwmm|Ip%D1RO*(L zWxw|dT7Wi5*!vp_gAGZ3Fy@Jm8ZI z@PsxSd*q5PL*}Fj1f0}|&iw63^QGA&peN}=U@e^~yrY3OjYVcOwU`e75TqI49jd~kqd2$2RaOIT^Hg5HLPzM%NRiBl z6R0cVIN++q&pcc;$s^!1sv646>_l|QnD%q)$1pv(sM0(PW>*7Xz(XQ%tQJQw zV_Z4?R7M;EK^O6Be9!@|P{)M0O^n4iHeb$I*W_ZonCY1W+^xwh4VLtbs$5Y_DIiWT zRg(Y;PZ$RrW5Ef(rfGh$EG%m}69*2=7-i33KF>7FVcf3(xwAB!Nj8w56bY|-QLqT+ z�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 MH> $SERVERLOG + exit $? + ;; + default) + echolog "Solicitud con parametros \"$PARM\" no realizada, no registrada o con errores" + ;; +esac + diff --git a/native/Sources/Services/opengnsys.default b/native/Sources/Services/opengnsys.default new file mode 100644 index 0000000..d7376cf --- /dev/null +++ b/native/Sources/Services/opengnsys.default @@ -0,0 +1,15 @@ +# RUN_OGADMSERVER defined as OpenGnsys Admin Server +# RUN_OGADMREPO defined as OpenGnsys Repository Manager +# RUN_OGADMAGENT run task scheduler service, only if Admin Server is enabled +# RUN_BTTRACKER run Bittorrent Tracker, only if Repository is enabled +# RUN_BTSEEDER start seeding of selected torrent files, only if Repository is enabled +# BTSEEDER_PRIORITY nice priority to seed torrent files; recommended values: +# 8 for Admin Server or Repo without Torrent +# 0 for Admin Server and Repo with Torrent +# -8 for Repo with Torrent +RUN_OGADMSERVER="yes" +RUN_OGADMREPO="yes" +RUN_OGADMAGENT="yes" +RUN_BTTRACKER="yes" +RUN_BTSEEDER="yes" +BTSEEDER_PRIORITY=0 diff --git a/native/Sources/Services/opengnsys.init b/native/Sources/Services/opengnsys.init new file mode 100755 index 0000000..ae27593 --- /dev/null +++ b/native/Sources/Services/opengnsys.init @@ -0,0 +1,224 @@ +#!/bin/bash + +### BEGIN INIT INFO +# Provides: opengnsys +# Required-Start: +# Required-Stop: +# Default-Start: 2 3 4 5 +# Default-Stop: 1 +# Short-Description: Servicios del sistema OpenGnsys +# Description: Servicios del sistema OpenGnsys +### END INIT INFO + +# +# Definiciones globales +# +BASEDIR=/opt/opengnsys +OPENGNSYSUSER="opengnsys" +IMAGEDIR=$BASEDIR/images +CLIENTLOGDIR=$BASEDIR/log/clients + +# +# Servidor de OpenGnsys +# +SERVERNAME=ogAdmServer +SERVERDAEMON=$BASEDIR/sbin/$SERVERNAME +SERVERCFG=$BASEDIR/etc/$SERVERNAME.cfg +SERVERLOG=$BASEDIR/log/$SERVERNAME.log +SERVERDAEMON_OPTIONS="-f $SERVERCFG -l $SERVERLOG" + +# +# Servidor de Repositorio +# +############## ADV +REPOAUXNAME=ogAdmRepoAux +REPOAUXDAEMON=$BASEDIR/sbin/$REPOAUXNAME +REPOAUXPORT=$(awk -F= '/PUERTO/ {print $2+1}' $SERVERCFG 2>/dev/null) +############## ADV +############# IRINA # para setBootMode desde el cliente +SERVERAUXNAME=ogAdmServerAux +SERVERAUXDAEMON=$BASEDIR/sbin/$SERVERAUXNAME +SERVERAUXPORT=2011 +############# IRINA + +# +# Servidor de tareas programadas +# +AGENTNAME=ogAdmAgent +AGENTDAEMON=$BASEDIR/sbin/$AGENTNAME +AGENTCFG=$BASEDIR/etc/$AGENTNAME.cfg +AGENTLOG=$BASEDIR/log/$AGENTNAME.log +AGENTDAEMON_OPTIONS="-f $AGENTCFG -l $AGENTLOG" + +# +# Opciones Bittorrent +# + +BTTRACK=/usr/bin/bttrack.bittorrent +BTSEEDER=/usr/bin/btlaunchmany.bittornado +BTTRACKPORT=6969 +BTTRACKDFILE=/tmp/dstate +BTTRACKLOG=$BASEDIR/log/bttrack.log +BTINTERVAL=30 +BTTORRENTSDIR=$BASEDIR/images +BTTRACK_OPTIONS=" --port $BTTRACKPORT --dfile $BTTRACKDFILE --reannounce_interval $BTINTERVAL --logfile $BTTRACKLOG --allowed_dir $BTTORRENTSDIR --allow_get 1" +BTTRACKPID="/var/run/bttrack.pid" +BTSEEDERPID="/var/run/btseeder.pid" + + +export PATH="${PATH:+$PATH:}/usr/sbin:/sbin:/usr/bin" + +# Read config file if it is present. +if [ -r /etc/default/opengnsys ]; then + source /etc/default/opengnsys +fi + +# Configuración de arranque según la distribución Linux usada. +config() { + if [ -f /etc/os-release ]; then + source /etc/os-release + OSDISTRIB="$ID" + else + OSDISTRIB=$(lsb_release -is 2>/dev/null) + fi + OSDISTRIB="${OSDISTRIB,,}" + case "$OSDISTRIB" in + ubuntu|debian|linuxmint) + INITFUNCTIONS=/lib/lsb/init-functions + DAEMONSTART="start-stop-daemon --start --quiet --background --exec" + EXTRAOPTS="--" + DAEMONSTOP="start-stop-daemon --stop --quiet --oknodo --name" + ACTIONMSG="log_daemon_msg" + SUCCESSMSG="log_end_msg 0" + FAILMSG="log_end_msg 1" + TRACKERSTART="start-stop-daemon --make-pidfile --pidfile $BTTRACKPID --start --quiet --background --exec" + BTTRACK_OPTIONS="$BTTRACK_OPTIONS --parse_allowed_interval 1" + TRACKERSTOP="start-stop-daemon --stop --quiet --oknodo --retry 30 --pidfile $BTTRACKPID" + SEEDERSTART="start-stop-daemon --make-pidfile --pidfile $BTSEEDERPID --start --quiet --background --exec" + SEEDERSTOP="start-stop-daemon --stop --quiet --oknodo --pidfile $BTSEEDERPID" + ;; + centos|fedora) + INITFUNCTIONS=/etc/init.d/functions + DAEMONSTART="daemon" + ENDOPTS="&" + DAEMONSTOP="killproc" + ACTIONMSG="echo -n" + SUCCESSMSG="eval ( success; echo )" + FAILMSG="eval ( failure; echo )" + BTTRACK=/usr/bin/bttrack.py + BTSEEDER=/usr/bin/btlaunchmany.py + TRACKERSTART="daemon --pidfile $BTTRACKPID" + TRACKERSTOP="killproc -p $BTTRACKPID $BTTRACK" + SEEDERSTART="daemon --pidfile $BTSEEDERPID" + SEEDERSTOP="killproc -p $BTSEEDERPID $BTSEEDER" + ;; + *) echo "Distribución Linux desconcocida o no soportada." + exit ;; + esac + if [ -r $INITFUNCTIONS ]; then + source $INITFUNCTIONS + fi +} + +arranca_demonios() { + # Comprobar que está instalado OpenGnsys. + if [ ! -d $BASEDIR ]; then + $ACTIONMSG "ERROR: No existe el directorio $BASEDIR" + $FAILMSG + exit $? + fi + # Deshabilitar modo reforzado de SELinux. + [ -f /selinux/enforce ] && echo 0 > /selinux/enforce + # Verificar permisos básicos. + if [ "$(stat --printf="%A%G" $IMAGEDIR 2>/dev/null)" != "drwxrwxr-x$OPENGNSYSUSER" ]; then + mkdir $IMAGEDIR 2>/dev/null + chmod 775 $IMAGEDIR + chgrp $OPENGNSYSUSER $IMAGEDIR + fi + if [ "$(stat --printf="%A%G" $CLIENTLOGDIR 2>/dev/null)" != "drwxrwxr-x$OPENGNSYSUSER" ]; then + mkdir -p $CLIENTLOGDIR 2>/dev/null + chmod 775 $CLIENTLOGDIR + chgrp $OPENGNSYSUSER $CLIENTLOGDIR + fi + # Arrancar los servicios indicados. + if [ $RUN_OGADMSERVER = "yes" ]; then + $ACTIONMSG "Iniciando demonio: $SERVERNAME" + $DAEMONSTART $SERVERDAEMON $EXTRAOPTS $SERVERDAEMON_OPTIONS $ENDOPTS + [ $? = 0 ] && $SUCCESSMSG || $FAILMSG + # Para SetBootmode desde el cliente + $ACTIONMSG "Iniciando demonio: $SERVERAUXNAME" # + faucet $SERVERAUXPORT --daemon --in bash -c "$SERVERAUXDAEMON" # NUEVO + [ $? = 0 ] && $SUCCESSMSG || $FAILMSG + fi + if [ $RUN_OGADMREPO = "yes" ]; then + $ACTIONMSG "Iniciando demonio: $REPOAUXNAME" + faucet $REPOAUXPORT --daemon --in bash -c "$REPOAUXDAEMON" + [ $? = 0 ] && $SUCCESSMSG || $FAILMSG + fi + if [ $RUN_OGADMSERVER = "yes" ] && [ $RUN_OGADMAGENT = "yes" ]; then + sleep 5 # Damos tiempo a que ogAdmServer este funcionando + fi + if [ $RUN_OGADMAGENT = "yes" ]; then + $ACTIONMSG "Iniciando demonio: $AGENTNAME" + $DAEMONSTART $AGENTDAEMON $EXTRAOPTS $AGENTDAEMON_OPTIONS $ENDOPTS + [ $? = 0 ] && $SUCCESSMSG || $FAILMSG + fi + if [ $RUN_BTTRACKER = "yes" ]; then + $ACTIONMSG "Iniciando demonio: $BTTRACK" + $TRACKERSTART $BTTRACK $EXTRAOPTS $BTTRACK_OPTIONS $ENDOPTS + [ $? = 0 ] && $SUCCESSMSG || $FAILMSG + fi + if [ $RUN_BTSEEDER = "yes" ]; then + $ACTIONMSG "Iniciando demonio: $BTSEEDER" + $SEEDERSTART $BTSEEDER $EXTRAOPTS $BTTORRENTSDIR &>/dev/null $ENDOPTS + [ $? = 0 ] && $SUCCESSMSG || $FAILMSG + fi + +} + +para_demonios() { + if [ -e $BTSEEDERPID ]; then + $ACTIONMSG "Parando demonio: $BTSEEDER" + $SEEDERSTOP + [ $? = 0 ] && $SUCCESSMSG || $FAILMSG + rm -f $BTSEEDERPID + fi + if [ -e $BTTRACKPID ]; then + $ACTIONMSG "Parando demonio: $BTTRACK" + $TRACKERSTOP + [ $? = 0 ] && $SUCCESSMSG || $FAILMSG + rm -f $BTTRACKPID + fi + $ACTIONMSG "Parando demonio: $AGENTNAME" + $DAEMONSTOP $AGENTNAME + [ $? = 0 ] && $SUCCESSMSG || $FAILMSG + $ACTIONMSG "Parando demonio: $REPOAUXNAME" + pkill faucet + [ $? -le 1 ] && $SUCCESSMSG || $FAILMSG + $ACTIONMSG "Parando demonio: $SERVERNAME" + $DAEMONSTOP $SERVERNAME + [ $? = 0 ] && $SUCCESSMSG || $FAILMSG +} + +config + +case "$1" in + start) + arranca_demonios + ;; + stop) + para_demonios + ;; + restart) + para_demonios + arranca_demonios + ;; + + *) + echo "Uso: $0 {start|stop|restart}" + exit 1 + ;; +esac + +exit 0 +