Compare commits

..

192 Commits

Author SHA1 Message Date
Natalia Serrano c39b2530f9 Merge pull request 'refs #1848 decorate oglive methods' (#27) from decorare-oglive-methods into main
Reviewed-on: #27
2025-04-24 15:36:03 +02:00
Natalia Serrano 15e11e6ea3 refs #1848 decorate oglive methods 2025-04-24 15:35:07 +02:00
Natalia Serrano ff042ca0d5 Merge pull request 'refs #1834 update changelogs again' (#26) from oglog2 into main
Reviewed-on: #26
2025-04-14 13:59:32 +02:00
Natalia Serrano 4cf3d505de refs #1834 update changelogs again 2025-04-14 13:59:10 +02:00
Natalia Serrano 52297f2815 Merge pull request 'create json log, clean logging up' (#25) from oglog into main
Reviewed-on: #25
2025-04-14 13:57:32 +02:00
Natalia Serrano 4fe87fc926 refs #1834 update changelogs 2025-04-14 13:51:27 +02:00
Natalia Serrano a7c5ae4d27 refs #1834 create additional json log 2025-04-14 13:49:43 +02:00
Natalia Serrano 5c2fbc36e3 refs #1886 make log a bit cleaner 2025-04-10 14:55:06 +02:00
Natalia Serrano 61e2061cba Merge pull request 'refs #1853 update debian changelog' (#24) from ping4 into main
Reviewed-on: #24
2025-04-10 11:48:00 +02:00
Natalia Serrano 584abfa44c refs #1853 update debian changelog 2025-04-10 11:38:29 +02:00
Natalia Serrano 6e39e473d2 Merge pull request 'refs #1853 ping ogcore from the OS as well' (#23) from ping3 into main
Reviewed-on: #23
2025-04-10 11:32:17 +02:00
Natalia Serrano 16554e5d93 refs #1853 ping ogcore from the OS as well 2025-04-10 11:31:47 +02:00
Natalia Serrano 0e113ecd49 Merge pull request 'refs #1850 actually send ping' (#22) from ping2 into main
Reviewed-on: #22
2025-04-07 13:33:18 +02:00
Natalia Serrano 70517e7ada refs #1850 actually send ping 2025-04-07 13:32:52 +02:00
Natalia Serrano 886df695dc Merge pull request 'ping1' (#21) from ping1 into main
Reviewed-on: #21
2025-04-07 12:03:46 +02:00
Natalia Serrano 08dce3f2d9 refs #1850 improve changelog message 2025-04-07 12:03:18 +02:00
Natalia Serrano b9ce7b7f25 refs #1850 ping ogcore, send timestamp 2025-04-07 12:01:46 +02:00
Natalia Serrano 4d34c5d274 Merge pull request 'refs #1784 ignore module name in URLs' (#20) from override-module into main
Reviewed-on: #20
2025-03-31 11:55:00 +02:00
Natalia Serrano 9af7469fc7 refs #1784 ignore module name in URLs 2025-03-31 11:54:17 +02:00
Natalia Serrano f1c3cd2460 Merge pull request 'refs #1747 EjecutarScript: b64decode the param "scp"' (#19) from ejecutarscript-b64 into main
Reviewed-on: #19
2025-03-26 12:15:23 +01:00
Natalia Serrano e9a0b47e86 refs #1747 EjecutarScript: b64decode the param "scp" 2025-03-26 12:14:11 +01:00
Natalia Serrano db8f96d9d8 Merge pull request 'fix-cfg2obj' (#18) from fix-cfg2obj into main
Reviewed-on: #18
2025-03-25 14:02:47 +01:00
Natalia Serrano a57d148922 refs #1760 make cfg2obj more robust 2025-03-25 13:58:28 +01:00
Natalia Serrano c9d6a54e54 refs #1760 make cfg2obj more robust 2025-03-25 13:34:59 +01:00
Natalia Serrano 5212678dd6 Merge pull request 'refs #1735 revert the new PTT param' (#17) from no-ptt-param into main
Reviewed-on: #17
2025-03-21 14:23:42 +01:00
Natalia Serrano 074efd42dc refs #1735 revert the new PTT param 2025-03-21 14:22:53 +01:00
Natalia Serrano e32a5f01c2 Merge pull request 'Get "ptt" in Configurar, run some python scripts' (#16) from configure-ptt-che into main
Reviewed-on: #16
2025-03-19 13:42:34 +01:00
Natalia Serrano c218eff895 refs #1698 use python scripts for most of the functionality in interfaceAdm 2025-03-19 13:39:07 +01:00
Natalia Serrano 5e4ef1851f refs #1696 remove unused "che" parameter, accept a new "ptt" parameter 2025-03-12 11:44:34 +01:00
Natalia Serrano a02029f0e2 Merge pull request 'Report progress, unify modules' (#15) from report-progress into main
Reviewed-on: #15
2025-03-12 11:39:57 +01:00
Natalia Serrano 5c52377acf refs #1589 send POST /stopped to ogcore 2025-02-20 12:00:09 +01:00
Natalia Serrano 684cdddab0 refs #1483 fix LANG bug 2025-02-19 14:48:35 +01:00
Natalia Serrano 5ce090133e refs #1483 report progress of operations to ogcore 2025-02-18 14:07:37 +01:00
Natalia Serrano d5275c6dbe refs #1483 report progress of operations to ogcore 2025-02-18 14:03:31 +01:00
Natalia Serrano 1de04f3a7a refs #1461 extract progress from other place 2025-02-11 12:04:21 +01:00
Natalia Serrano f8d6706897 refs #1461 keep track of RestaurarImagen unicast 2025-02-11 09:36:27 +01:00
Natalia Serrano b5b09ec132 refs #1460 remove debug code 2025-02-06 11:17:03 +01:00
Natalia Serrano f8563ec1a6 refs #1460 merge server modules 2025-02-06 10:26:55 +01:00
Natalia Serrano 658c72ad51 Merge pull request 'ogcore1' (#14) from ogcore1 into main
Reviewed-on: #14
2025-01-14 12:05:36 +01:00
Natalia Serrano 14e893a21e refs #1338 bump version 2025-01-14 12:01:36 +01:00
Natalia Serrano 173379f99a refs #1338 change menubrowser URL 2025-01-14 11:59:15 +01:00
Natalia Serrano 69be238f9f refs #1108 kill subprocesses in oglive 2024-11-29 10:24:15 +01:00
Natalia Serrano be1fd7d624 refs #1112 fix bug while accessing object member 2024-11-28 10:18:34 +01:00
Natalia Serrano 7f45c3083d refs #1112 implement Configurar() 2024-11-27 20:03:58 +01:00
Natalia Serrano 74a6937501 refs #1112 use old browser again 2024-11-20 14:25:14 +01:00
Natalia Serrano a00fbcb76e refs #1112 do not use envvars for the operating-system module 2024-11-20 13:46:12 +01:00
Natalia Serrano 7293aee3ea refs #1112 do not use envvars for the operating-system module 2024-11-20 13:44:57 +01:00
Natalia Serrano 831da3a053 refs #1112 avoid KeyErrors 2024-11-18 12:15:10 +01:00
Natalia Serrano 2f4ade71dd refs #1112 avoid KeyErrors 2024-11-18 09:36:29 +01:00
Natalia Serrano 9ac107dde4 refs #1112 use envvars for configuration 2024-11-15 12:23:42 +01:00
Natalia Serrano 029d3a778d refs #1152 handle HTTP error responses without dying 2024-11-15 11:59:40 +01:00
Natalia Serrano 87a5258de5 refs #1108 add WIP for killing subprocesses 2024-11-15 11:41:12 +01:00
Natalia Serrano e2fcf02222 refs #1107 fix syntax 2024-11-06 14:17:27 +01:00
Natalia Serrano dd82e4db50 refs #1106 remove vim swapfile from the deb packages 2024-11-06 13:40:39 +01:00
Natalia Serrano ef0920079b refs #1107 do not try to avoid races, as there are none now 2024-11-06 13:35:12 +01:00
Natalia Serrano 6163c0b435 refs #1105 include job_id in async responses 2024-11-06 13:23:32 +01:00
Natalia Serrano b8733fea49 refs #1107 run one monitoring thread, not two 2024-11-06 13:23:11 +01:00
Natalia Serrano 66b6ea4fc4 refs #1104 return inventory inline 2024-11-06 13:08:28 +01:00
Natalia Serrano b7b2a58186 Merge pull request 'fixes-win' (#13) from fixes-win into main
Reviewed-on: #13
2024-10-30 11:58:36 +01:00
Natalia Serrano 4c789b6f43 refs #1011 change webhook URL 2024-10-22 11:46:26 +02:00
Natalia Serrano 3929421cf5 refs #1009 add changelog entry 2024-10-22 11:23:54 +02:00
Natalia Serrano 85dd23f957 refs #1009 make status() synchronous 2024-10-22 11:23:13 +02:00
Natalia Serrano 1fdeb2adeb refs #986 use logger.debug 2024-10-18 13:26:17 +02:00
Natalia Serrano 324ffdaffc Merge pull request 'new-browser' (#12) from new-browser into main
Reviewed-on: #12
2024-10-17 11:57:27 +02:00
Natalia Serrano 96bb0a7198 refs #973 release version 1.4.3 2024-10-17 11:04:13 +02:00
Natalia Serrano 1d89f6d50c refs #973 run new browser 2024-10-16 20:02:28 +02:00
Natalia Serrano ca12de8351 Merge pull request 'ogadmclient-status' (#11) from ogadmclient-status into main
Reviewed-on: #11
2024-10-15 17:12:55 +02:00
Natalia Serrano bf099333b5 refs #932 prevent python from dying on windows 2024-10-15 16:39:22 +02:00
Natalia Serrano fcf9c5b2ba refs #932 help pyinstaller find all opengnsys modules 2024-10-15 16:15:24 +02:00
Natalia Serrano c7b48a6c70 refs #946 return cfg as json everywhere, not only in inclusionCliente 2024-10-15 15:06:39 +02:00
Natalia Serrano 9245bff31d refs #944 release ogagent-1.4.2 2024-10-15 11:30:39 +02:00
Natalia Serrano 0f5cf07aa0 refs #946 simplify code, support "ser=text" 2024-10-15 11:29:58 +02:00
Natalia Serrano a2df6afda7 refs #946 return cfg as json, not the legacy text string 2024-10-15 11:26:05 +02:00
Natalia Serrano 376dec466f refs #948 get MAC address 2024-10-15 11:18:37 +02:00
Natalia Serrano 58b7f0d406 refs #945 put more info in ogAdmClient/status, make reply async 2024-10-15 10:54:59 +02:00
Natalia Serrano 2d6cd923a0 Merge pull request 'ogagent unification3' (#10) from unification3 into main
Reviewed-on: #10
2024-10-14 09:20:31 +02:00
Natalia Serrano 3a9c293c33 refs #915 release ogagent 1.4.1 2024-10-11 19:55:35 +02:00
Natalia Serrano c19128619f refs #915 release new version 2024-10-11 13:13:06 +02:00
Natalia Serrano def6750cd1 refs #880 monitor running threads 2024-10-08 17:55:51 +02:00
Natalia Serrano e1bd063bde refs #890 implement EjecutaComandosPendientes() 2024-10-08 11:44:57 +02:00
Natalia Serrano a0fb19ddbd refs #888 implement EjecutarScript() 2024-10-08 11:37:15 +02:00
Natalia Serrano 6757ab5697 refs #786 move code 2024-10-08 11:04:39 +02:00
Natalia Serrano 4fdcbe620f refs #786 make ConsolaRemota() asynchronous 2024-10-08 11:03:17 +02:00
Natalia Serrano 3bfaf3c838 refs #783 #784 #785 #786 #881 #882 #883 #879 move code around 2024-10-08 10:51:52 +02:00
Natalia Serrano 2d7d023e99 refs #885 move Configurar() to CloningEngine 2024-10-08 10:46:14 +02:00
Natalia Serrano e39bb7401e refs #887 fix syntax error 2024-10-08 10:32:42 +02:00
Natalia Serrano 9e3d8be629 refs #886 move InventarioHardware() to the CloningEngine module 2024-10-08 10:32:11 +02:00
Natalia Serrano e2f161ae97 refs #887 handle potential InventariandoSoftware() exceptions 2024-10-08 10:25:54 +02:00
Natalia Serrano a3f4eafffb refs #887 implement InventarioSoftware() 2024-10-08 10:19:04 +02:00
Natalia Serrano 08dba6d99a refs #886 implement InventarioHardware() 2024-10-08 09:31:03 +02:00
Natalia Serrano 239bfc21f7 refs #885 implement Configurar() 2024-10-07 19:51:31 +02:00
Natalia Serrano 7efb0fdcc8 refs #884 implement IniciarSesion() 2024-10-07 18:18:07 +02:00
Natalia Serrano 1ee279afd5 refs #883 implement Reiniciar() 2024-10-07 18:12:15 +02:00
Natalia Serrano 8d9a9ef5c3 refs #882 implement Apagar() 2024-10-07 18:12:02 +02:00
Natalia Serrano f21a75a23d refs #881 implement Arrancar() 2024-10-07 17:59:30 +02:00
Natalia Serrano d3829cd46f refs #879 implement Comando() 2024-10-07 17:55:34 +02:00
Natalia Serrano 1e1974432e refs #784 make Purgar() actually terminate the agent 2024-10-03 15:05:15 +02:00
Natalia Serrano 068e0cf633 refs #806 join threads when a new operation is requested 2024-10-03 14:39:31 +02:00
Natalia Serrano 72e4198762 refs #784 make Purgar() return something 2024-10-03 14:21:26 +02:00
Natalia Serrano 647489d507 refs #783 make Actualizar() asynchronous 2024-10-03 14:20:48 +02:00
Natalia Serrano 3191a171a1 refs #783 fix return code 2024-10-01 14:15:59 +02:00
Natalia Serrano e28094ec1b refs #786 implement process_ConsolaRemota() 2024-10-01 14:15:10 +02:00
Natalia Serrano 62a2514569 refs #785 implement process_Sondeo() 2024-10-01 13:33:54 +02:00
Natalia Serrano aa0f62edcc refs #784 implement process_Purgar() 2024-10-01 13:33:04 +02:00
Natalia Serrano 5cb2ef6cfc refs #783 implement process_Actualizar() 2024-10-01 13:31:27 +02:00
Natalia Serrano 1cf3c6bf2c Merge pull request 'ogagent unification 2' (#9) from unification2 into main
Reviewed-on: #9
2024-10-01 13:28:00 +02:00
Natalia Serrano 74ef2b7e15 refs #789 run only one concurrent job 2024-10-01 12:08:57 +02:00
Natalia Serrano 9b91eedf1b refs #789 perform long-running tasks in the background 2024-09-30 17:36:25 +02:00
Natalia Serrano 39a367be79 refs #708 merge 2024-09-20 14:29:03 +02:00
Natalia Serrano 1864995066 refs #708 move duplicated code into its own parent class 2024-09-20 14:28:20 +02:00
Natalia Serrano 4b46827d17 refs #708 bump version 2024-09-20 14:28:20 +02:00
Natalia Serrano ba681a8116 refs #708 split CloningEngine stuff off ogAdmClient module, restore stock config 2024-09-20 14:28:20 +02:00
Natalia Serrano 258df2af7c refs #708 ogLive agent: unhardcode string 2024-09-20 14:28:20 +02:00
Natalia Serrano 22f83d8dea refs #708 ogLive agent: do not activate within an operating system 2024-09-20 14:28:20 +02:00
Natalia Serrano 0a6edd8cfe refs #708 kill some unused code 2024-09-20 14:28:20 +02:00
Natalia Serrano 82bf3a15f6 refs #708 fix incorrect usage of an f-string 2024-09-20 14:28:20 +02:00
Natalia Serrano 38815028a6 refs #708 OS agent: unhardcode string, do not activate within ogLive 2024-09-20 14:28:20 +02:00
Natalia Serrano 3682ac2b1d refs #708 handle some invalid URLs and return 404 2024-09-20 14:28:20 +02:00
Natalia Serrano 274d8d448c refs #708 support python 3.12 when loading modules 2024-09-20 14:28:20 +02:00
Natalia Serrano 18f1314521 refs #708 remove comments and a useless debug 2024-09-20 14:28:20 +02:00
Natalia Serrano ebf822aad6 refs #705 implement RestaurarImagen() and add some fixes 2024-09-20 14:28:20 +02:00
Natalia Serrano f5f58ce796 refs #707 implement an empty RestaurarSoftIncremental() 2024-09-20 14:28:20 +02:00
Natalia Serrano 3b9cab338d refs #706 implement an empty RestaurarImagenBasica() 2024-09-20 14:28:20 +02:00
Natalia Serrano 60d7561afd refs #704 implement an empty CrearSoftIncremental() 2024-09-20 14:28:20 +02:00
Natalia Serrano 040ca6612f refs #703 improve error reporting, fix bugs 2024-09-20 14:28:20 +02:00
Natalia Serrano 8686b09d0e refs #703 add support for HTTP error codes, have process_CrearImagenBasica() return 404 2024-09-20 14:28:20 +02:00
Natalia Serrano b58c2c1f7f refs #703 use logger.warning() in module ogAdmClient 2024-09-20 14:28:20 +02:00
Natalia Serrano e9f0e44010 refs #703 implement logger.warning() just like the python logging module 2024-09-20 14:28:20 +02:00
Natalia Serrano 2dc264a187 refs #702 implement CrearImagen() 2024-09-20 14:28:20 +02:00
Natalia Serrano 178d724ec0 refs #702 have RESTApi.py handle errors and non-json responses 2024-09-20 14:28:20 +02:00
Natalia Serrano 07a8a5b4af refs #702 have interfaceAdmin() raise exceptions 2024-09-20 14:28:20 +02:00
Natalia Serrano a97755e368 refs #702 remove unused code 2024-09-20 14:28:20 +02:00
Natalia Serrano cc3146c15f refs #702 use instance variables 2024-09-20 14:28:20 +02:00
Natalia Serrano 79dcefc0b4 refs #702 reformat function calls 2024-09-20 14:28:20 +02:00
Natalia Serrano a67669b99f refs #708 move duplicated code into its own parent class 2024-09-20 14:20:33 +02:00
Natalia Serrano fa328348f2 refs #708 bump version 2024-09-19 14:33:37 +02:00
Natalia Serrano 2ba25ffa7b refs #708 split CloningEngine stuff off ogAdmClient module, restore stock config 2024-09-19 14:33:09 +02:00
Natalia Serrano 35fbb59444 refs #708 ogLive agent: unhardcode string 2024-09-19 14:25:58 +02:00
Natalia Serrano b7b7351783 refs #708 ogLive agent: do not activate within an operating system 2024-09-19 14:23:47 +02:00
Natalia Serrano dd3703ce63 refs #708 kill some unused code 2024-09-19 14:21:07 +02:00
Natalia Serrano e5d2904cb9 refs #708 fix incorrect usage of an f-string 2024-09-19 14:19:26 +02:00
Natalia Serrano a5d0da2403 refs #708 OS agent: unhardcode string, do not activate within ogLive 2024-09-19 14:16:31 +02:00
Natalia Serrano 8a369923ec refs #708 handle some invalid URLs and return 404 2024-09-19 14:15:01 +02:00
Natalia Serrano 8c9fc6be3f refs #708 support python 3.12 when loading modules 2024-09-19 12:57:53 +02:00
Natalia Serrano c92093ca8c refs #708 remove comments and a useless debug 2024-09-19 11:41:43 +02:00
Natalia Serrano f497cfaf4d refs #705 implement RestaurarImagen() and add some fixes 2024-09-19 11:34:08 +02:00
Natalia Serrano ca3f9257ae refs #707 implement an empty RestaurarSoftIncremental() 2024-09-19 10:30:50 +02:00
Natalia Serrano 9e279dca35 refs #706 implement an empty RestaurarImagenBasica() 2024-09-19 10:29:59 +02:00
Natalia Serrano 00dc9804dd refs #704 implement an empty CrearSoftIncremental() 2024-09-19 10:28:06 +02:00
Natalia Serrano 7defe5cc63 refs #703 improve error reporting, fix bugs 2024-09-19 10:21:00 +02:00
Natalia Serrano 5b058a5e33 refs #703 add support for HTTP error codes, have process_CrearImagenBasica() return 404 2024-09-19 10:19:35 +02:00
Natalia Serrano ffa80a9af2 Merge pull request 'Do not load modules unconditionally' (#8) from modules into main
Reviewed-on: #8
2024-09-19 09:36:26 +02:00
Natalia Serrano 71e34b3d40 refs #703 use logger.warning() in module ogAdmClient 2024-09-18 14:52:29 +02:00
Natalia Serrano 1334a87c4d refs #703 implement logger.warning() just like the python logging module 2024-09-13 13:45:51 +02:00
Natalia Serrano 5cf45c580e refs #702 implement CrearImagen() 2024-09-13 13:09:07 +02:00
Natalia Serrano ecee987dff refs #702 have RESTApi.py handle errors and non-json responses 2024-09-13 12:35:15 +02:00
Natalia Serrano 9fe674f30e refs #702 have interfaceAdmin() raise exceptions 2024-09-13 11:08:14 +02:00
Natalia Serrano 32d3621923 refs #702 remove unused code 2024-09-13 11:05:26 +02:00
Natalia Serrano 9d3a320f36 refs #702 use instance variables 2024-09-13 11:03:48 +02:00
Natalia Serrano 9d6596668f refs #702 reformat function calls 2024-09-12 14:57:24 +02:00
Natalia Serrano 1d93de1b59 refs #531 remove unused code, bump version 2024-08-29 11:03:00 +02:00
Natalia Serrano 0cadbf3381 refs #579 do not load modules unconditionally, look for everything in the module paths 2024-08-28 15:11:49 +02:00
Natalia Serrano 53f44ad3ab Merge pull request 'Initial, very incomplete implementation of ogAdmClient in python' (#7) from ogadmcli into main
Reviewed-on: #7
2024-08-27 11:36:40 +02:00
Natalia Serrano 023886cea3 refs #527 decode a base64 blob from ogcore 2024-08-27 11:36:16 +02:00
Natalia Serrano 25cfb31725 refs #527 remove unused code 2024-08-21 14:44:30 +02:00
Natalia Serrano 2c3bbe82a8 Merge branch 'main' into ogadmcli 2024-08-08 12:06:54 +02:00
Natalia Serrano 4b5193105c Merge pull request 'job manager' (#5) from ogagent-jobs into main
Reviewed-on: #5
2024-08-07 14:03:24 +02:00
Natalia Serrano 08f7e44870 refs #500 make longer IDs to avoid collissions 2024-08-05 15:33:00 +02:00
Natalia Serrano 67d5e81838 Merge pull request 'refs #538 fix updating the ogagent version across the codebase' (#6) from versions into ogadmcli
Reviewed-on: #6
2024-08-02 13:51:58 +02:00
Natalia Serrano b7788d9c1d refs #538 fix updating the ogagent version across the codebase 2024-08-02 13:49:02 +02:00
Natalia Serrano 367e6dc782 refs #536 add openapi specification 2024-08-02 10:47:16 +02:00
Natalia Serrano f7fd5d5570 refs #534 add ogcore-mock 2024-08-01 12:01:37 +02:00
Natalia Serrano b6945279bc refs #522 remove duplicated initialisation code 2024-08-01 12:01:07 +02:00
Natalia Serrano d4e21dae13 refs #532 let jobmgr kill jobs 2024-08-01 11:18:40 +02:00
Natalia Serrano 6740704919 refs #500 bump version 2024-07-30 14:48:56 +02:00
Natalia Serrano da7dd418c0 refs #500 fix portability issue 2024-07-30 14:47:48 +02:00
Natalia Serrano 8c6a6523d8 refs #500 #501 #502 implement job manager 2024-07-30 13:14:50 +02:00
Natalia Serrano f25252fcf9 refs #526 translate my comments 2024-07-27 10:08:42 +02:00
Natalia Serrano bfe563d902 refs #526 remove unwanted code 2024-07-26 14:38:58 +02:00
Natalia Serrano 94eaba7688 refs #526 implement procesaComandos() 2024-07-26 14:38:19 +02:00
Natalia Serrano 1d0057fd38 refs #526 move onActivation() below 2024-07-26 13:43:30 +02:00
Natalia Serrano e92d8855a1 refs #526 implement muestraMenu() 2024-07-26 13:40:46 +02:00
Natalia Serrano 97246759c1 refs #529 remove useless ogAdmClient.py 2024-07-26 13:14:21 +02:00
Natalia Serrano 5482d25116 refs #525 partially implement comandosPendientes() 2024-07-26 12:17:02 +02:00
Natalia Serrano 5f7ca5be15 refs #524 implement autoexecCliente() and its companion ejecutaArchivo() 2024-07-26 10:45:02 +02:00
Natalia Serrano 886bf5e616 refs #523 implement inclusionCliente() 2024-07-25 12:43:07 +02:00
Natalia Serrano bf061b13db refs #522 #527 begin integrating ogAdmClient.c into the agent 2024-07-24 15:15:21 +02:00
Natalia Serrano 360d0f8fb8 Merge pull request 'Build and package ogagent py3/qt6 for windows and linux' (#2) from py3-win into main
Reviewed-on: #2
2024-07-23 09:58:23 +02:00
Natalia Serrano 64089e07af Merge pull request 'fix agent for macos' (#3) from ogagent-macos into py3-win
Reviewed-on: #3
2024-07-22 15:36:14 +02:00
Natalia Serrano dc7c6af6e8 refs #474 fix regex to match ipv6 addresses
The former regex failed to match an IPv6 that didn't include "::", which are
obviously valid. Change the regex so they are caught.
2024-07-19 14:10:30 +02:00
Natalia Serrano 10a4c28ea6 Merge pull request 'Fix script execution on windows' (#4) from windows-fixes into ogagent-macos
Reviewed-on: #4
2024-07-19 10:45:19 +02:00
Natalia Serrano eab72819f2 refs #464 install nsis non-interactively 2024-07-11 11:13:56 +02:00
50 changed files with 2801 additions and 1310 deletions

289
CHANGELOG.md 100644
View File

@ -0,0 +1,289 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [4.0.0] - 2025-04-24
### Added
- Authn/authz to the oglive agent
## [3.3.0] - 2025-04-14
### Added
- Log stuff to a new json log
## [3.2.0] - 2025-04-10
### Added
- Operating system: periodically ping ogcore
## [3.1.0] - 2025-04-07
### Added
- Oglive: periodically ping ogcore
## [3.0.0] - 2025-03-31
### Changed
- Ignore module provided in the URLs to the API
## [2.0.0] - 2025-03-26
### Changed
- EjecutarScript/ConsolaRemota: expect "scp" parameter encoded in base64
## [1.7.1] - 2025-03-25
### Fixed
- Make cfg2obj more robust
## [1.7.0] - 2025-03-21
### Removed
- Delete the new "ptt" parameter. It's not needed.
## [1.6.0] - 2025-03-12
### Changed
- Don't invoke bash code for some functionalities
## [1.5.0] - 2025-03-12
### Changed
- Accept new "ptt" parameter in /ogAdmCli/Configurar
### Removed
- No longer recognise the unused "che" parameter in /ogAdmCli/Configurar
## [1.4.9] - 2025-02-20
### Changed
- Notify ogcore when agent shuts down within oglive
## [1.4.8] - 2025-02-18
### Changed
- Optionally return disk config in /status
## [1.4.7] - 2025-02-04
### Changed
- Merge server modules
### Added
- Track the progress of children
## [1.4.6] - 2025-01-14
### Changed
- Point to the new menu browser
## [1.4.5] - 2024-11-29
### Added
- Kill long running jobs in oglive
## [1.4.5~pre8] - 2024-11-27
### Added
- Add Configurar() to the CloningEngine module
## [1.4.5~pre7] - 2024-11-20
### Changed
- Use old browser again
## [1.4.5~pre6] - 2024-11-20
### Changed
- Do not use envvars for the operating-system module
## [1.4.5~pre5] - 2024-11-18
### Fixed
- Avoid some KeyErrors
## [1.4.5~pre4] - 2024-11-15
### Fixed
- Don't die when ogcore returns HTTP 4xx or 5xx
### Changed
- Get ogcore IP and port from the environment
## [1.4.5~pre3] - 2024-11-06
- Kill long running jobs in oglive (not-yet-working draft)
## [1.4.5~pre2] - 2024-11-06
### Fixed
- Remove race condition due to several monitoring threads
### Changed
- Include job_id in asynchronous responses
### Removed
- Remove vim swapfiles from the package contents
## [1.4.5~pre1] - 2024-11-06
### Changed
- CrearImagen: return inventory inline
## [1.4.4] - 2024-10-17
### Fixed
- Use logger.debug() to prevent the windows agent from dying
### Changed
- Make status() call synchronous
## [1.4.3] - 2024-10-17
### Changed
- Use new OGBrowser
## [1.4.2] - 2024-10-15
### Added
- Have ogAdmClient/status return information about network and disks
## [1.4.1] - 2024-10-11
### Fixed
- Bugfix: move data structure to the right class
## [1.4.0] - 2024-10-11
- Add more functionality
### Changed
- Begin using semantic versioning
## [1.3.8] - 2024-10-01
### Added
- Add more functionality to the ogAdmClient module
## [1.3.7] - 2024-09-27
### Added
- CloningEngine: RESTfully keep a list of long-running jobs
## [1.3.6] - 2024-09-19
### Added
- Add more functionality to the ogAdmClient module
- Add CloningEngine module
## [1.3.5] - 2024-08-29
### Changed
- Don't unconditionally load modules--dynamically load everything
### Removed
- Remove old, unused code
## [1.3.4] - 2024-07-30
### Added
- Implement JobMgr
## [1.3.1] - 2024-06-26
### Changed
- Migrate the update script from shell to python
- pyinstaller: include the 'img' subdir
- take icons from 'img'
## [1.3.0-2] - 2024-04-25
### Fixed
- Add missing dependency on zenity
## [1.3.0] - 2024-04-25
### Changed
- Upgrade to Qt 6
## [1.2.0] - 2020-05-4
### Changed
- Python 3 and Qt 5 compatibility
## [1.1.1b] - 2020-02-7
### Changed
- Use python-distro to detect the distribution version
## [1.1.1] - 2019-05-23
### Changed
- Set connection timeout
- Compatibility with "Exam Mode" from the University of Seville
## [1.1.0a] - 2019-05-22
### Fixed
- Fix a bug when activating the agent with some network devices
## [1.1.0] - 2016-10-13
### Changed
- Functional OpenGnsys Agent interacting with OpenGnsys Server 1.1.0
## [1.0.0] - 2015-07-18
### Added
- Initial release for OpenGnsys Agent

View File

@ -1,3 +1,210 @@
ogagent (4.0.0-1) stable; urgency=medium
* Handle authn/authz in the oglive agent
-- OpenGnsys developers <info@opengnsys.es> Thu, 24 Apr 2025 13:28:57 +0200
ogagent (3.3.0-1) stable; urgency=medium
* Create an additional json log file
-- OpenGnsys developers <info@opengnsys.es> Mon, 14 Apr 2025 13:50:32 +0200
ogagent (3.2.0-1) stable; urgency=medium
* Operating system: periodically ping ogcore
-- OpenGnsys developers <info@opengnsys.es> Thu, 10 Apr 2025 11:37:35 +0200
ogagent (3.1.0-1) stable; urgency=medium
* Oglive: periodically ping ogcore
-- OpenGnsys developers <info@opengnsys.es> Mon, 07 Apr 2025 11:50:05 +0200
ogagent (3.0.0-1) stable; urgency=medium
* Ignore module provided in the URLs to the API
-- OpenGnsys developers <info@opengnsys.es> Mon, 31 Mar 2025 10:16:07 +0200
ogagent (2.0.0-1) stable; urgency=medium
* EjecutarScript/ConsolaRemota: expect "scp" parameter encoded in base64
-- OpenGnsys developers <info@opengnsys.es> Wed, 26 Mar 2025 10:40:14 +0100
ogagent (1.7.1-1) stable; urgency=medium
* Make cfg2obj more robust
-- OpenGnsys developers <info@opengnsys.es> Tue, 25 Mar 2025 13:31:33 +0100
ogagent (1.7.0-1) stable; urgency=medium
* Delete the new "ptt" parameter. It's not needed.
-- OpenGnsys developers <info@opengnsys.es> Fri, 21 Mar 2025 14:19:56 +0100
ogagent (1.6.0-1) stable; urgency=medium
* Don't invoke bash code for some functionalities
-- OpenGnsys developers <info@opengnsys.es> Wed, 12 Mar 2025 11:59:36 +0100
ogagent (1.5.0-1) stable; urgency=medium
* Accept new "ptt" parameter in /ogAdmCli/Configurar
* No longer recognise the unused "che" parameter in /ogAdmCli/Configurar
-- OpenGnsys developers <info@opengnsys.es> Wed, 12 Mar 2025 11:45:37 +0100
ogagent (1.4.9-1) stable; urgency=medium
* Notify ogcore when agent shuts down within oglive
-- OpenGnsys developers <info@opengnsys.es> Thu, 20 Feb 2025 11:58:29 +0100
ogagent (1.4.8-1) stable; urgency=medium
* Optionally return disk config in /status
-- OpenGnsys developers <info@opengnsys.es> Tue, 18 Feb 2025 13:48:54 +0100
ogagent (1.4.7-1) stable; urgency=medium
* Merge server modules
* Track the progress of children
-- OpenGnsys developers <info@opengnsys.es> Tue, 04 Feb 2025 14:12:19 +0100
ogagent (1.4.6-1) stable; urgency=medium
* Point to the new menu browser
-- OpenGnsys developers <info@opengnsys.es> Tue, 14 Jan 2025 12:00:24 +0100
ogagent (1.4.5-1) stable; urgency=medium
* Kill long running jobs in oglive
-- OpenGnsys developers <info@opengnsys.es> Fri, 29 Nov 2024 10:22:36 +0100
ogagent (1.4.5~pre8-1) stable; urgency=medium
* Add Configurar() to the CloningEngine module
-- OpenGnsys developers <info@opengnsys.es> Wed, 27 Nov 2024 20:02:42 +0100
ogagent (1.4.5~pre7-1) stable; urgency=medium
* Use old browser again
-- OpenGnsys developers <info@opengnsys.es> Wed, 20 Nov 2024 14:24:44 +0100
ogagent (1.4.5~pre6-1) stable; urgency=medium
* Do not use envvars for the operating-system module
-- OpenGnsys developers <info@opengnsys.es> Wed, 20 Nov 2024 13:45:21 +0100
ogagent (1.4.5~pre5-1) stable; urgency=medium
* Avoid some KeyErrors
-- OpenGnsys developers <info@opengnsys.es> Mon, 18 Nov 2024 12:14:27 +0100
ogagent (1.4.5~pre4-1) stable; urgency=medium
* Don't die when ogcore returns HTTP 4xx or 5xx
* Get ogcore IP and port from the environment
-- OpenGnsys developers <info@opengnsys.es> Fri, 15 Nov 2024 11:43:01 +0100
ogagent (1.4.5~pre3-1) stable; urgency=medium
* Kill long running jobs in oglive (not-yet-working draft)
-- OpenGnsys developers <info@opengnsys.es> Wed, 06 Nov 2024 14:11:32 +0100
ogagent (1.4.5~pre2-1) stable; urgency=medium
* Remove race condition due to several monitoring threads
* Include job_id in asynchronous responses
* Remove vim swapfiles from the package contents
-- OpenGnsys developers <info@opengnsys.es> Wed, 06 Nov 2024 13:24:03 +0100
ogagent (1.4.5~pre1-1) stable; urgency=medium
* CrearImagen: return inventory inline
-- OpenGnsys developers <info@opengnsys.es> Wed, 06 Nov 2024 12:41:14 +0100
ogagent (1.4.4-1) stable; urgency=medium
* Use logger.debug() to prevent the windows agent from dying
* Make status() call synchronous
-- OpenGnsys developers <info@opengnsys.es> Thu, 17 Oct 2024 19:13:58 +0200
ogagent (1.4.3-1) stable; urgency=medium
* Use new OGBrowser
-- OpenGnsys developers <info@opengnsys.es> Thu, 17 Oct 2024 09:43:08 +0200
ogagent (1.4.2-1) stable; urgency=medium
* Have ogAdmClient/status return information about network and disks
-- OpenGnsys developers <info@opengnsys.es> Tue, 15 Oct 2024 10:35:16 +0200
ogagent (1.4.1-1) stable; urgency=medium
* Bugfix: move data structure to the right class
-- OpenGnsys developers <info@opengnsys.es> Fri, 11 Oct 2024 13:51:55 +0200
ogagent (1.4.0-1) stable; urgency=medium
* Add more functionality
* Begin using semantic versioning
-- OpenGnsys developers <info@opengnsys.es> Fri, 11 Oct 2024 13:06:51 +0200
ogagent (1.3.8-1) stable; urgency=medium
* Add more functionality to the ogAdmClient module
-- OpenGnsys developers <info@opengnsys.es> Tue, 01 Oct 2024 13:41:48 +0200
ogagent (1.3.7-1) stable; urgency=medium
* CloningEngine: RESTfully keep a list of long-running jobs
-- OpenGnsys developers <info@opengnsys.es> Fri, 27 Sep 2024 18:03:16 +0200
ogagent (1.3.6-1) stable; urgency=medium
* Add more functionality to the ogAdmClient module
* Add CloningEngine module
-- OpenGnsys developers <info@opengnsys.es> Thu, 19 Sep 2024 13:28:17 +0200
ogagent (1.3.5-1) stable; urgency=medium
* Don't unconditionally load modules--dynamically load everything
* Remove old, unused code
-- OpenGnsys developers <info@opengnsys.es> Thu, 29 Aug 2024 10:47:12 +0200
ogagent (1.3.4-1) stable; urgency=medium
* Implement JobMgr
-- OpenGnsys developers <info@opengnsys.es> Tue, 30 Jul 2024 13:39:55 +0200
ogagent (1.3.1-1) stable; urgency=medium
* Migrate the update script from shell to python

View File

@ -22,6 +22,7 @@ install: build
dh_prep
dh_installdirs
$(MAKE) DESTDIR=$(CURDIR)/debian/ogagent install-ogagent
find $(CURDIR) -name '*.swp' -exec rm -f '{}' ';'
binary-arch: build install
# emptyness
binary-indep: build install

View File

@ -12,8 +12,8 @@ mkdir -p build && cd build
mkdir -p flat/base.pkg flat/Resources/en.lproj
mkdir -p root/Applications
# Copy application and script files. Exclude 'test_modules'
cp -a ../../src root/Applications/OGAgent.app; rm -rf root/Applications/OGAgent.app/test_modules
# Copy application and script files
cp -a ../../src root/Applications/OGAgent.app
cp -a ../scripts .
# Create plist file.

View File

@ -176,7 +176,7 @@ def parse_ifconfig(res, af, address):
addr["broadcast"] = re.findall(r"broadcast (\d+\.\d+\.\d+\.\d+)", r)[0]
link["addr_info"] = link.get("addr_info", []) + [addr]
elif address and re.match(r"^\s+inet6 ", r) and af != 4:
(local, prefixlen) = re.findall(r"inet6 ([0-9a-f:]*::[0-9a-f:]+)%*\w* prefixlen (\d+)", r)[0]
(local, prefixlen) = re.findall(r"inet6 ((?:[a-f0-9:]+:+)+[a-f0-9]+)%*\w* +prefixlen (\d+)", r)[0]
link["addr_info"] = link.get("addr_info", []) + [{
"family": "inet6",
"local": local,

238
ogcore-mock.py 100644
View File

@ -0,0 +1,238 @@
from flask import Flask, request, jsonify, render_template_string, abort
import os
import logging
import json
import subprocess
import base64
## FLASK_APP=/path/to/ogcore-mock.py FLASK_ENV=development FLASK_RUN_CERT=adhoc sudo --preserve-env flask run --host 192.168.1.249 --port 443
app = Flask(__name__)
logging.basicConfig(level=logging.INFO)
## agente lin/win/mac
@app.route('/opengnsys/rest/ogagent/<cucu>', methods=['POST'])
def og_agent(cucu):
logging.info(f'{request.get_json()}')
return jsonify({})
## agente oglive: modulo ogAdmClient
@app.route('/opengnsys/rest/ogAdmClient/InclusionCliente', methods=['POST'])
def inclusion_cliente():
logging.info(f'{request.get_json()}')
#procesoInclusionCliente() or { return (jsonify { 'res': 0 }) }
j = request.get_json(force=True)
iph = j['iph'] ## Toma ip
cfg = j['cfg'] ## Toma configuracion
logging.info(f'iph ({iph}) cfg ({cfg})')
# dbi->query (sprintf "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 (!dbi_result_next_row(result)) { log_error ('client does not exist in database') }
# log_debug (sprintf 'Client %s requesting inclusion', iph);
idordenador = 42 #dbi_result_get_uint(result, "idordenador")
nombreordenador = 'hal9000' #dbi_result_get_string(result, "nombreordenador");
cache = 42 #dbi_result_get_uint(result, "cache");
idproautoexec = 42 #dbi_result_get_uint(result, "idproautoexec");
idaula = 42 #dbi_result_get_uint(result, "idaula");
idcentro = 42 #dbi_result_get_uint(result, "idcentro");
# resul = actualizaConfiguracion(dbi, cfg, idordenador); ## Actualiza la configuración del ordenador
# if (!resul) { log_error ('Cannot add client to database') }
# if (!registraCliente(iph)) { log_error ('client table is full') } ## Incluyendo al cliente en la tabla de sokets
return jsonify({
'res': 1, ## int, Confirmación proceso correcto
'ido': idordenador, ## int
'npc': nombreordenador, ## string
'che': cache, ## int
'exe': idproautoexec, ## int
'ida': idaula, ## int
'idc': idcentro ## int
})
def _recorreProcedimientos(parametros, fileexe, idp):
#char idprocedimiento[LONPRM];
#int procedimientoid, lsize;
#const char *msglog, *param;
#dbi_result result;
#dbi->query (sprintf 'SELECT procedimientoid,parametros FROM procedimientos_acciones WHERE idprocedimiento=%s ORDER BY orden', $idp);
#if (!result) { log_error ('failed to query database'); return 0; }
if 1: #while (dbi_result_next_row(result)) {
procedimientoid = 0 ## dbi_result_get_uint(result, "procedimientoid");
if (procedimientoid > 0): ## Procedimiento recursivo
if (not _recorreProcedimientos (parametros, fileexe, procedimientoid)):
return 0
else:
#param = '@'.join (["nfn=EjecutarScript\rscp=uptime", "nfn=EjecutarScript\rscp=cat /proc/uptime"]) ## dbi_result_get_string(result, "parametros");
param = '@'.join (["nfn=popup\rtitle=my title\rmessage=my message"])
parametros = '{}@'.format (param)
fileexe.write (parametros)
#}
return 1
@app.route('/opengnsys/rest/ogAdmClient/AutoexecCliente', methods=['POST'])
def autoexec_client():
logging.info(f'{request.get_json()}')
j = request.get_json(force=True)
iph = j['iph'] ## Toma dirección IP del cliente
exe = j['exe'] ## Toma identificador del procedimiento inicial
logging.info(f'iph ({iph}) exe ({exe})')
fileautoexec = '/tmp/Sautoexec-{}'.format(iph)
logging.info ('fileautoexec ({})'.format (fileautoexec));
try:
fileexe = open (fileautoexec, 'w')
except Exception as e:
logging.error ('cannot create temporary file: {}'.format (e))
return jsonify({})
if (_recorreProcedimientos ('', fileexe, exe)):
res = jsonify ({ 'res': 1, 'nfl': fileautoexec })
else:
res = jsonify ({ 'res': 0 })
fileexe.close()
return res
@app.route('/opengnsys/rest/ogAdmClient/enviaArchivo', methods=['POST'])
def envia_archivo():
logging.info(f'{request.get_json()}')
j = request.get_json(force=True)
nfl = j['nfl'] ## Toma nombre completo del archivo
logging.info(f'nfl ({nfl})')
contents = subprocess.run (['cat', nfl], capture_output=True).stdout
b64 = base64.b64encode (contents).decode ('utf-8')
return jsonify({'contents': b64})
def clienteExistente(iph):
## esto queda totalmente del lado del servidor, no lo implemento en python
return 42
def buscaComandos(ido):
#dbi->query (sprintf "SELECT sesion, parametros FROM acciones WHERE idordenador=%s AND estado='%d' ORDER BY idaccion", ido, ACCION_INICIADA);
#dbi_result_next_row(result) ## cogemos solo una fila
#if not row { return; }
return
#ids = 42 #dbi_result_get_uint(result, "sesion");
#param = "nfn=popup\rtitle=my title\rmessage=my message" #dbi_result_get_string(result, "parametros");
## convertirlo a json, aqui lo pongo a capon
#return jsonify ({ 'nfn': 'popup', 'title': 'my title', 'message': 'my message', 'ids': ids })
@app.route('/opengnsys/rest/ogAdmClient/ComandosPendientes', methods=['POST'])
def comandos_pendientes():
logging.info(f'{request.get_json()}')
j = request.get_json(force=True)
iph = j['iph'] ## Toma dirección IP
ido = j['ido'] ## Toma identificador del ordenador
logging.info(f'iph ({iph}) ido ({ido})')
idx = clienteExistente(iph) ## Busca índice del cliente
if not idx:
## que devuelvo?? pongamos un 404...
abort(404, 'Client does not exist')
param = buscaComandos(ido) ## Existen comandos pendientes, buscamos solo uno
if param is None:
return jsonify({'nfn': 'NoComandosPtes'})
#strcpy(tbsockets[idx].estado, CLIENTE_OCUPADO); ## esto queda totalmente del lado del servidor, no lo implemento en python
return jsonify(param)
@app.route('/opengnsys/rest/ogAdmClient/DisponibilidadComandos', methods=['POST'])
def disponibilidad_comandos():
logging.info(f'{request.get_json()}')
j = request.get_json(force=True)
iph = j['iph']
tpc = j['tpc']
logging.info(f'iph ({iph}) tpc ({tpc})')
idx = clienteExistente(iph) ## Busca índice del cliente
if not idx:
## que devuelvo?? pongamos un 404...
abort(404, 'Client does not exist')
#strcpy(tbsockets[idx].estado, tpc); ## esto queda totalmente del lado del servidor, no lo implemento en python
return jsonify({})
@app.route('/opengnsys/rest/ogAdmClient/recibeArchivo', methods=['POST'])
def oac_recibe_archivo():
logging.info(f'{request.get_json()}')
j = request.get_json(force=True)
nfl = j['nfl']
contents = j['contents']
logging.info(f'nfl ({nfl}) contents ({contents})')
dec = base64.b64decode (contents).decode ('utf-8')
logging.info(f'dec ({dec})')
return jsonify({'anything':'anything'}) ## if we return {}, then we trigger "if not {}" which happens to be true
@app.route('/opengnsys/rest/clients/status/webhook', methods=['POST'])
def oac_callback():
logging.info(f'{request.get_json()}')
return jsonify({'anything':'anything'})
@app.route('/opengnsys/rest/ogAdmClient/<cucu>', methods=['GET', 'POST'])
def oac_cucu(cucu):
#j = request.get_json(force=True)
#logging.info(f'{request.get_json()} {j}')
#if 'cucu' not in j:
# abort(400, 'missing parameter 'cucu'')
#return jsonify({'cucu': j['cucu']})
abort (404)
## agente oglive: modulo CloningEngine
@app.route('/opengnsys/rest/CloningEngine/recibeArchivo', methods=['POST'])
def ce_recibe_archivo():
logging.info(f'{request.get_json()}')
j = request.get_json(force=True)
nfl = j['nfl']
contents = j['contents']
logging.info(f'nfl ({nfl}) contents ({contents})')
dec = base64.b64decode (contents).decode ('utf-8')
logging.info(f'dec ({dec})')
return jsonify({'anything':'anything'}) ## if we return {}, then we trigger "if not {}" which happens to be true
@app.route('/opengnsys/rest/CloningEngine/callback', methods=['POST'])
def ce_callback():
logging.info(f'{request.get_json()}')
return jsonify({'anything':'anything'})
@app.route('/opengnsys/rest/CloningEngine/<cucu>', methods=['GET', 'POST'])
def ce_cucu(cucu):
abort (404)
@app.errorhandler(404)
def _page_not_found(e):
if type(e.description) is dict:
return jsonify (e.description), e.code
else:
return render_template_string('''<!DOCTYPE html><html>not found</html>'''), e.code
@app.errorhandler(500)
def _internal_server_error(e):
return render_template_string('''<!DOCTYPE html><html>err</html>'''), e.code
@app.errorhandler(Exception)
def _exception(e):
print(e)
return render_template_string('''<!DOCTYPE html><html>exception</html>'''), e.code
if __name__ == '__main__':
app.run(host = '192.168.1.249', port = 443, debug=True)

297
openapi.yml 100644
View File

@ -0,0 +1,297 @@
openapi: 3.0.3
info:
title: OgAgent API
description: OgAgent API
version: 0.0.1
paths:
/opengnsys/status:
post:
summary: Get status of the agent
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/StatusReq'
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/StatusRes'
/opengnsys/poweroff:
post:
summary: Power agent off
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/EmptyObj'
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/LaunchedRes'
/opengnsys/reboot:
post:
summary: Reboot agent
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/EmptyObj'
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/LaunchedRes'
/opengnsys/script:
post:
summary: Run script on agent
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ScriptReq'
required: true
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/ScriptRes'
/opengnsys/terminatescript:
post:
summary: Terminate running script on agent
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/TerminateScriptReq'
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/EmptyObj'
/opengnsys/preparescripts:
post:
summary: Prepare list of scripts running on agent
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/EmptyObj'
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/EmptyObj'
/opengnsys/getscripts:
post:
summary: Get the list of scripts running on agent
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/EmptyObj'
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/GetScriptsRes'
/opengnsys/logoff:
post:
summary: Log remote user off
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/EmptyObj'
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/LogoffRes'
/opengnsys/popup:
post:
summary: Show message on agent
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Popup'
required: true
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/LaunchedRes'
components:
schemas:
EmptyObj:
type: object
additionalProperties: false
Jobid:
type: string
example:
- "deadbeef"
StatusReq:
type: object
properties:
detail:
type: boolean
StatusRes:
type: object
required:
- status
properties:
status:
type: string
enum:
- "LNX"
- "OSX"
- "WIN"
loggedin:
type: boolean
session:
type: string
example:
- "x11"
agent_version:
type: string
os_version:
type: string
sys_load:
type: number
LaunchedRes:
type: object
required:
- op
properties:
op:
type: string
enum:
- "launched"
ScriptReq:
type: object
required:
- script
properties:
script:
type: string
example:
- "uptime\nwho"
- "Start-Process notepad.exe"
client:
type: boolean
default: false
ScriptRes:
type: object
required:
- op
properties:
op:
type: string
enum:
- "launched"
jobid:
$ref: '#/components/schemas/Jobid'
TerminateScriptReq:
type: object
required:
- jobid
properties:
jobid:
$ref: '#/components/schemas/Jobid'
RunningScript:
type: object
required:
- jobid
- pid
- starttime
- script
- client
- status
- stdout
- stderr
properties:
jobid:
$ref: '#/components/schemas/Jobid'
pid:
type: integer
starttime:
type: string
example: "2024-12-31 23:59:59.123456+0000"
script:
type: string
client:
type: boolean
status:
type: string
enum:
- "running"
- "finished"
stdout:
type: string
stderr:
type: string
rc:
type: integer
GetScriptsRes:
type: array
items:
$ref: '#/components/schemas/RunningScript'
LogoffRes:
type: object
required:
- op
properties:
op:
type: string
enum:
- "sent to client"
Popup:
type: object
properties:
title:
type: string
message:
type: string

View File

@ -1,56 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
import win32service
import win32serviceutil
svc_name = "UDSActor"
try:
hscm = win32service.OpenSCManager(None, None, win32service.SC_MANAGER_ALL_ACCESS)
try:
hs = win32serviceutil.SmartOpenService(hscm, svc_name, win32service.SERVICE_ALL_ACCESS)
service_failure_actions = {
'ResetPeriod': 864000, # Time in ms after which to reset the failure count to zero.
'RebootMsg': u'', # Not using reboot option
'Command': u'', # Not using run-command option
'Actions': [
(win32service.SC_ACTION_RESTART, 5000), # action, delay in ms
(win32service.SC_ACTION_RESTART, 5000)
]
}
win32service.ChangeServiceConfig2(hs, win32service.SERVICE_CONFIG_FAILURE_ACTIONS, service_failure_actions)
finally:
win32service.CloseServiceHandle(hs)
finally:
win32service.CloseServiceHandle(hscm)

View File

@ -1,5 +0,0 @@
<RCC>
<qresource prefix="images">
<file>img/oga.png</file>
</qresource>
</RCC>

View File

@ -1,5 +1,9 @@
# -*- mode: python ; coding: utf-8 -*-
## generated on windows using:
## pyi-makespec.exe --windowed --icon img\oga.ico --manifest OGAgent.manifest OGAgentUser.py opengnsys\windows\OGAgentService.py
## move OGAgentUser.spec OGAgent.spec
ogausr_a = Analysis(
['OGAgentUser.py'],
@ -9,7 +13,7 @@ ogausr_a = Analysis(
# ('cfg', 'cfg'), ## add the entire directory
('img', 'img'), ## add the entire directory
],
hiddenimports=['win32timezone', 'socketserver', 'http.server', 'urllib'],
hiddenimports=['win32timezone', 'socketserver', 'http.server', 'urllib', 'opengnsys.modules.client.OpenGnSys', 'opengnsys.modules.server.ogAdmClient', 'opengnsys.modules.server.OpenGnSys'],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
@ -22,7 +26,7 @@ ogasvc_a = Analysis(
pathex=[],
binaries=[],
datas=[],
hiddenimports=['win32timezone', 'socketserver', 'http.server', 'urllib'],
hiddenimports=['win32timezone', 'socketserver', 'http.server', 'urllib', 'opengnsys.modules.client.OpenGnSys', 'opengnsys.modules.server.ogAdmClient', 'opengnsys.modules.server.OpenGnSys'],
hookspath=[],
hooksconfig={},
runtime_hooks=[],

View File

@ -43,7 +43,7 @@ from opengnsys import VERSION, ipc, operations, utils
from opengnsys.config import readConfig
from opengnsys.loader import loadModules
from opengnsys.log import logger
from opengnsys.scriptThread import ScriptExecutorThread
from opengnsys.jobmgr import JobMgr
from opengnsys.service import IPC_PORT
trayIcon = None
@ -117,6 +117,10 @@ class MessagesProcessor(QtCore.QThread):
if self.ipc:
self.ipc.sendLogout(username)
def sendMessage(self, module, message, data):
if self.ipc:
self.ipc.sendMessage(module, message, data)
def run(self):
if self.ipc is None:
return
@ -149,6 +153,7 @@ class MessagesProcessor(QtCore.QThread):
class OGASystemTray(QtWidgets.QSystemTrayIcon):
jobmgr = JobMgr()
def __init__(self, app_, parent=None):
self.app = app_
self.config = readConfig(client=True)
@ -205,7 +210,7 @@ class OGASystemTray(QtWidgets.QSystemTrayIcon):
valid_mods.append(mod)
except Exception as e:
logger.exception()
logger.error("Activation of {} failed: {}".format(mod.name, utils.exceptionToMessage(e)))
logger.debug ("Activation of {} failed: {}".format(mod.name, utils.exceptionToMessage(e)))
self.modules[:] = valid_mods # copy instead of assignment
# If this is running, it's because he have logged in, inform service of this fact
self.ipc.sendLogin((operations.getCurrentUser(), operations.getSessionLanguage(),
@ -218,7 +223,7 @@ class OGASystemTray(QtWidgets.QSystemTrayIcon):
mod.deactivate()
except Exception as e:
logger.exception()
logger.error("Deactivation of {} failed: {}".format(mod.name, utils.exceptionToMessage(e)))
logger.debug ("Deactivation of {} failed: {}".format(mod.name, utils.exceptionToMessage(e)))
def timerFnc(self):
pass
@ -231,7 +236,7 @@ class OGASystemTray(QtWidgets.QSystemTrayIcon):
logger.debug('msg: {}, {}'.format(type(msg), msg))
module, message, data = msg
except Exception as e:
logger.error('Got exception {} processing message {}'.format(e, msg))
logger.debug ('Got exception {} processing message {}'.format(e, msg))
return
for v in self.modules:
@ -241,15 +246,15 @@ class OGASystemTray(QtWidgets.QSystemTrayIcon):
v.processMessage(message, json.loads(data))
return
except Exception as e:
logger.error('Got exception {} processing generic message on {}'.format(e, v.name))
logger.debug ('Got exception {} processing generic message on {}'.format(e, v.name))
logger.error('Module {} not found, messsage {} not sent'.format(module, message))
logger.debug ('Module {} not found, messsage {} not sent'.format(module, message))
## when is this run??
def executeScript(self, script):
logger.debug('Executing script')
script = base64.b64decode(script.encode('ascii'))
th = ScriptExecutorThread(script)
th.start()
logger.debug('Executing received script "{}"'.format(script))
self.jobmgr.launch_job (script, True)
def logoff(self):
logger.debug('Logoff invoked')
@ -266,7 +271,7 @@ class OGASystemTray(QtWidgets.QSystemTrayIcon):
self.deinitialize()
except Exception:
logger.exception()
logger.error('Got exception deinitializing modules')
logger.debug ('Got exception deinitializing modules')
try:
# If we close Client, send Logoff to Broker
@ -277,7 +282,7 @@ class OGASystemTray(QtWidgets.QSystemTrayIcon):
except Exception:
# May we have lost connection with server, simply log and exit in that case
logger.exception()
logger.exception("Got an exception, processing quit")
logger.debug ('Got an exception, processing quit')
try:
# operations.logoff() # Uncomment this after testing to logoff user
@ -311,7 +316,7 @@ if __name__ == '__main__':
trayIcon = OGASystemTray(app)
except Exception as e:
logger.exception()
logger.error('OGA Service is not running, or it can\'t contact with OGA Server. User Tools stopped: {}'.format(
logger.debug ('OGA Service is not running, or it can\'t contact with OGA Server. User Tools stopped: {}'.format(
utils.exceptionToMessage(e)))
sys.exit(1)
@ -319,7 +324,7 @@ if __name__ == '__main__':
trayIcon.initialize() # Initialize modules, etc..
except Exception as e:
logger.exception()
logger.error('Exception initializing OpenGnsys User Agent {}'.format(utils.exceptionToMessage(e)))
logger.debug ('Exception initializing OpenGnsys User Agent {}'.format(utils.exceptionToMessage(e)))
trayIcon.quit()
sys.exit(1)

View File

@ -1 +1 @@
1.3.3
4.0.0

View File

@ -65,7 +65,7 @@
<item>
<widget class="QLabel" name="VersionLabel">
<property name="text">
<string>Version 1.3.0</string>
<string>Version 0</string>
</property>
</widget>
</item>
@ -217,9 +217,6 @@ p, li { white-space: pre-wrap; }
</item>
</layout>
</widget>
<resources>
<include location="OGAgent.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>

View File

@ -4,10 +4,10 @@ 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
#path=test_modules/server,more_modules/server
# Remote OpenGnsys Service
remote=https://192.168.2.10/opengnsys/rest
remote=https://192.168.2.1/opengnsys/rest
# Alternate OpenGnsys Service (comment out to enable this option)
#altremote=https://10.0.2.2/opengnsys/rest
@ -20,7 +20,11 @@ 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
[ogAdmClient]
#path=test_modules/server,more_modules/server
remote={}://{}/opengnsys/rest
log=DEBUG
pathinterface=/opt/opengnsys/interfaceAdm
urlMenu={}://{}/menu-browser
urlMsg=http://localhost/cgi-bin/httpd-log.sh

View File

@ -58,8 +58,12 @@ class ConnectionError(RESTError):
# Disable warnings log messages
try:
import urllib3 # @UnusedImport
requests_log = logging.getLogger ('urllib3')
requests_log.setLevel (logging.INFO)
except Exception:
from requests.packages import urllib3 # @Reimport
requests_log = logging.getLogger ('requests.packages.urllib3')
requests_log.setLevel (logging.INFO)
try:
urllib3.disable_warnings() # @UndefinedVariable
@ -142,9 +146,15 @@ class REST(object):
else:
r = requests.post(url, data=data, headers={'content-type': 'application/json'})
r.raise_for_status()
ct = r.headers['Content-Type']
if 'application/json' != ct:
raise Exception (f'response content-type is not "application/json" but "{ct}"')
r = json.loads(r.content) # Using instead of r.json() to make compatible with old requests lib versions
except requests.exceptions.RequestException as e:
raise ConnectionError(e)
code = e.response.status_code
logger.warning (f'request failed, HTTP code "{code}"')
return None
except Exception as e:
raise ConnectionError(exceptionToMessage(e))
@ -156,12 +166,17 @@ class REST(object):
@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))
#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))
#logger.debug('Requesting {}'.format(url))
return self._request(url, data)
try:
res = self._request(url, data)
return res
except:
logger.exception()
return None

View File

@ -37,7 +37,7 @@ import six
from . import modules
from .RESTApi import REST, RESTError
VERSION='1.3.0'
VERSION='0'
__title__ = 'OpenGnsys Agent'
__version__ = VERSION

View File

@ -30,6 +30,7 @@
"""
import os
import json
import ssl
import threading
@ -58,7 +59,7 @@ class HTTPServerHandler(BaseHTTPRequestHandler):
def sendJsonResponse(self, data):
try: self.send_response(200)
except Exception as e: logger.warn (str(e))
except Exception as e: logger.warn ('exception: "{}"'.format(str(e)))
data = json.dumps(data)
self.send_header('Content-type', 'application/json')
self.send_header('Content-Length', str(len(data)))
@ -77,8 +78,11 @@ class HTTPServerHandler(BaseHTTPRequestHandler):
except Exception:
params = {}
## quick override because universities do not actually want the module to be extracted out of the URL
module = 'ogAdmClient' if os.path.exists ('/scripts/oginit') else 'opengnsys'
for v in self.service.modules:
if v.name == path[0]: # Case Sensitive!!!!
if v.name == module: # Case Sensitive!!!!
return v, path[1:], params
return None, path, params
@ -88,11 +92,32 @@ class HTTPServerHandler(BaseHTTPRequestHandler):
Locates witch module will process the message based on path (first folder on url path)
"""
try:
if module is None:
raise Exception ({ '_httpcode': 404, '_msg': f'Module {path[0]} not found' })
data = module.processServerMessage(path, get_params, post_params, self)
self.sendJsonResponse(data)
except Exception as e:
logger.exception()
self.sendJsonError(500, exceptionToMessage(e))
n_args = len (e.args)
if 0 == n_args:
logger.debug ('Empty exception raised from message processor for "{}"'.format(path[0]))
self.sendJsonError(500, exceptionToMessage(e))
else:
arg0 = e.args[0]
if type (arg0) is str:
logger.debug ('Message processor for "{}" returned exception string "{}"'.format(path[0], str(e)))
self.sendJsonError (500, exceptionToMessage(e))
elif type (arg0) is dict:
if '_httpcode' in arg0:
logger.debug ('Message processor for "{}" returned HTTP code "{}" with exception string "{}"'.format(path[0], str(arg0['_httpcode']), str(arg0['_msg'])))
self.sendJsonError (arg0['_httpcode'], arg0['_msg'])
else:
logger.debug ('Message processor for "{}" returned exception dict "{}" with no HTTP code'.format(path[0], str(e)))
self.sendJsonError (500, exceptionToMessage(e))
else:
logger.debug ('Message processor for "{}" returned non-string and non-dict exception "{}", type "{}"'.format(path[0], str(e), type(e)))
self.sendJsonError (500, exceptionToMessage(e))
## not reached
def do_GET(self):
module, path, params = self.parseUrl()

View File

@ -0,0 +1,62 @@
import threading
import subprocess
import hashlib
import time
from datetime import datetime, timezone
from opengnsys import operations
from opengnsys.log import logger
def job_readstdout(job):
for l in iter(job['p'].stdout.readline, b''):
job['stdout'] += l.decode ('utf-8', 'ignore')
def job_readstderr(job):
for l in iter(job['p'].stderr.readline, b''):
job['stderr'] += l.decode ('utf-8', 'ignore')
class JobMgr():
jobs = {}
def launch_job(self, script, is_client):
logger.debug ('in launch_job(), is_client "{}"'.format(is_client))
args = operations.build_popen_args (script)
logger.debug ('args "{}"'.format (args))
now = datetime.now (tz=timezone.utc)
ts = now.strftime ('%Y-%m-%d %H:%M:%S.%f%z') ## '%s' doesn't work on windows
jobid = hashlib.sha256 (now.isoformat().encode('UTF-8') + script.encode ('UTF-8')).hexdigest()[0:12]
p = subprocess.Popen (args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
self.jobs[jobid] = { 'p': p, 'pid': p.pid, 'starttime': ts, 'script': script, 'client': is_client, 'status': 'running', 'stdout': '', 'stderr': '' }
self.jobs[jobid]['t1'] = threading.Thread (target=job_readstdout, args=(self.jobs[jobid],))
self.jobs[jobid]['t2'] = threading.Thread (target=job_readstderr, args=(self.jobs[jobid],))
self.jobs[jobid]['t1'].start()
self.jobs[jobid]['t2'].start()
logger.debug ('jobs "{}"'.format (self.jobs))
return jobid
def prepare_jobs(self):
## can't return self.jobs because the Popen object at self.jobs[id]['p'] is not serializable. So, need to create a new dict to return
st = []
for jobid in self.jobs:
j = self.jobs[jobid]
entry = dict ((k, j[k]) for k in ['pid', 'starttime', 'script', 'client', 'status', 'stdout', 'stderr'])
entry['jobid'] = jobid
if j['p'].poll() is not None: ## process finished
entry['rc'] = j['p'].returncode
entry['status'] = 'finished'
st.append (entry)
return st
def terminate_job(self, jobid):
if jobid not in self.jobs: return {}
p = self.jobs[jobid]['p']
p.terminate()
time.sleep (1)
if p.poll() is not None:
return { 'terminated': True }
p.kill()
time.sleep (1)
if p.poll() is not None:
return { 'killed': True }
return { 'killed': False }

View File

View File

@ -34,6 +34,8 @@ import logging
import os
import tempfile
from ..log_format import JsonFormatter
# Logging levels
OTHER, DEBUG, INFO, WARN, ERROR, FATAL = (10000 * (x + 1) for x in range(6))
@ -48,15 +50,25 @@ class LocalLogger(object):
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)
fname1 = os.path.join (logDir, 'opengnsys.log')
fmt1 = logging.Formatter (fmt='%(levelname)s %(asctime)s (%(threadName)s) (%(funcName)s) %(message)s')
fh1 = logging.FileHandler (filename=fname1, mode='a')
fh1.setFormatter (fmt1)
fh1.setLevel (logging.DEBUG)
fname2 = os.path.join (logDir, 'opengnsys.json.log')
fmt2 = JsonFormatter ({"timestamp": "asctime", "severity": "levelname", "threadName": "threadName", "function": "funcName", "message": "message"}, time_format='%Y-%m-%d %H:%M:%S', msec_format='')
fh2 = logging.FileHandler (filename=fname2, mode='a')
fh2.setFormatter (fmt2)
fh2.setLevel (logging.DEBUG)
self.logger = logging.getLogger ('opengnsys')
self.logger.setLevel (logging.DEBUG)
self.logger.addHandler (fh1)
self.logger.addHandler (fh2)
os.chmod (fname1, 0o0600)
os.chmod (fname2, 0o0600)
return
except Exception:
pass
@ -69,7 +81,7 @@ class LocalLogger(object):
# 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)
self.logger.log(int(level / 1000) - 10, message, stacklevel=4)
def isWindows(self):
return False

View File

@ -44,7 +44,6 @@ import array
import six
import distro
from opengnsys import utils
from .renamer import rename
def _getMacAddr(ifname):
@ -114,13 +113,6 @@ def _getIpAndMac(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
@ -135,10 +127,6 @@ def getNetworkInfo():
yield utils.Bunch(name=ifname, mac=mac, ip=ip)
def getDomainName():
return ''
def getLinuxVersion():
"""
Returns the version of the Linux distribution
@ -191,83 +179,6 @@ def logoff():
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
@ -303,3 +214,7 @@ def get_etc_path():
Returns etc directory path.
"""
return os.sep + 'etc'
def build_popen_args(script):
return ['/bin/sh', '-c', script]

View File

@ -1,61 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
import 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()

View File

@ -1,68 +0,0 @@
# -*- 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 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

View File

@ -1,66 +0,0 @@
# -*- 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 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

View File

@ -1,74 +0,0 @@
# -*- 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 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

View File

@ -35,6 +35,7 @@
# Modules under "opengsnsys/modules" are always autoloaded
import sys
import pkgutil
import os.path
@ -42,6 +43,8 @@ from opengnsys.workers import ServerWorker
from opengnsys.workers import ClientWorker
from .log import logger
PY3_12 = sys.version_info[0:2] >= (3, 12)
def loadModules(controller, client=False):
'''
@ -55,12 +58,10 @@ def loadModules(controller, client=False):
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
@ -90,7 +91,11 @@ def loadModules(controller, client=False):
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 PY3_12:
loader = module_loader.find_spec(name).loader
else:
loader = module_loader.find_module(name)
loader.load_module(name)
if controller.config.has_option('opengnsys', 'path') is True:
@ -98,14 +103,14 @@ def loadModules(controller, client=False):
else:
paths = ()
# paths += (os.path.dirname(sys.modules[modPath].__file__),)
paths += (os.path.dirname(sys.modules[modPath].__file__),)
logger.debug('Loading modules from {}'.format(paths))
# Load modules
logger.debug('Loading modules from {}'.format(paths))
doLoad(paths)
# Add to list of available modules
logger.debug('Adding {} classes'.format('server' if modType == ServerWorker else 'client'))
recursiveAdd(modType)
return ogModules

View File

@ -79,6 +79,9 @@ class Logger(object):
def warn(self, message):
self.log(WARN, message)
def warning(self, message):
self.log(WARN, message)
def info(self, message):
self.log(INFO, message)
@ -94,7 +97,7 @@ class Logger(object):
except Exception:
tb = '(could not get traceback!)'
self.log(DEBUG, tb)
self.log(DEBUG, 'traceback follows: "{}"'.format(tb))
def flush(self):
pass

View File

@ -0,0 +1,55 @@
import json
import logging
class JsonFormatter(logging.Formatter):
"""
Formatter that outputs JSON strings after parsing the LogRecord.
@param dict fmt_dict: Key: logging format attribute pairs. Defaults to {"message": "message"}.
@param str time_format: time.strftime() format string. Default: "%Y-%m-%dT%H:%M:%S"
@param str msec_format: Microsecond formatting. Appended at the end. Default: "%s.%03dZ"
"""
def __init__(self, fmt_dict: dict = None, time_format: str = "%Y-%m-%dT%H:%M:%S", msec_format: str = "%s.%03dZ"):
self.fmt_dict = fmt_dict if fmt_dict is not None else {"message": "message"}
self.default_time_format = time_format
self.default_msec_format = msec_format
self.datefmt = None
def usesTime(self) -> bool:
"""
Overwritten to look for the attribute in the format dict values instead of the fmt string.
"""
return "asctime" in self.fmt_dict.values()
def formatMessage(self, record) -> dict:
"""
Overwritten to return a dictionary of the relevant LogRecord attributes instead of a string.
KeyError is raised if an unknown attribute is provided in the fmt_dict.
"""
return {fmt_key: record.__dict__[fmt_val] for fmt_key, fmt_val in self.fmt_dict.items()}
def format(self, record) -> str:
"""
Mostly the same as the parent's class method, the difference being that a dict is manipulated and dumped as JSON
instead of a string.
"""
record.message = record.getMessage()
if self.usesTime():
record.asctime = self.formatTime(record, self.datefmt)
message_dict = self.formatMessage(record)
if record.exc_info:
# Cache the traceback text to avoid converting it multiple times
# (it's constant anyway)
if not record.exc_text:
record.exc_text = self.formatException(record.exc_info)
if record.exc_text:
message_dict["exc_info"] = record.exc_text
if record.stack_info:
message_dict["stack_info"] = self.formatStack(record.stack_info)
return json.dumps(message_dict, default=str)

View File

@ -112,13 +112,6 @@ def _getIpAndMac(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
@ -133,10 +126,6 @@ def getNetworkInfo():
yield utils.Bunch(name=ifname, mac=mac, ip=ip)
def getDomainName():
return ''
def getMacosVersion():
return 'macOS {}'.format(platform.mac_ver()[0])
@ -180,83 +169,6 @@ def logoff():
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
@ -297,3 +209,7 @@ def get_etc_path():
Returns etc directory path.
"""
return os.sep + 'etc'
def build_popen_args(script):
return ['/bin/sh', '-c', script]

View File

@ -34,35 +34,41 @@ from opengnsys.workers import ClientWorker
from opengnsys import operations
from opengnsys.log import logger
from opengnsys.scriptThread import ScriptExecutorThread
from opengnsys.jobmgr import JobMgr
class OpenGnSysWorker(ClientWorker):
name = 'opengnsys'
jobmgr = JobMgr()
@staticmethod
def onActivation():
def onActivation(self):
logger.debug('Activate invoked')
@staticmethod
def onDeactivation():
def onDeactivation(self):
logger.debug('Deactivate invoked')
# Processes script execution
@staticmethod
def process_script(json_params):
logger.debug('Processed message: script({})'.format(json_params))
thr = ScriptExecutorThread(json_params['code'])
thr.start()
def process_script(self, json_params):
script = json_params['code']
logger.debug('Processing message: script({})'.format(script))
self.jobmgr.launch_job (script, True)
#self.sendServerMessage('script', {'op', 'launched'})
@staticmethod
def process_logoff(json_params):
def process_terminatescript(self, json_params):
jobid = json_params['jobid']
logger.debug('Processing terminatescript request, jobid "{}"'.format (jobid))
self.jobmgr.terminate_job (jobid)
def process_preparescripts(self, json_params):
logger.debug('Processing preparescripts request')
st = self.jobmgr.prepare_jobs()
logger.debug('Sending preparescripts to server with data "{}"'.format(st))
self.sendServerMessage('preparescripts', st)
def process_logoff(self, json_params):
logger.debug('Processed message: logoff({})'.format(json_params))
operations.logoff()
@staticmethod
def process_popup(json_params):
def process_popup(self, json_params):
logger.debug('Processed message: popup({})'.format(json_params))
ret = operations.showPopup(json_params['title'], json_params['message'])
#self.sendServerMessage('popup', {'op', ret})

View File

@ -30,7 +30,6 @@
@author: Ramón M. Gómez, ramongomez at us dot es
"""
import base64
import os
import random
@ -45,7 +44,7 @@ import urllib.request
from configparser import NoOptionError
from opengnsys import REST, operations, VERSION
from opengnsys.log import logger
from opengnsys.scriptThread import ScriptExecutorThread
from opengnsys.jobmgr import JobMgr
from opengnsys.workers import ServerWorker
@ -65,7 +64,7 @@ def check_secret(fnc):
else:
raise Exception('Unauthorized operation')
except Exception as e:
logger.error(str(e))
logger.debug (str(e))
raise Exception(e)
return wrapper
@ -83,7 +82,7 @@ def execution_level(level):
else:
raise Exception('Unauthorized operation')
except Exception as e:
logger.error(str(e))
logger.debug (str(e))
raise Exception(e)
return wrapper
@ -91,17 +90,6 @@ def execution_level(level):
return check_permitted
# 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
class OpenGnSysWorker(ServerWorker):
name = 'opengnsys' # Module name
interface = None # Bound interface for OpenGnsys
@ -111,25 +99,45 @@ class OpenGnSysWorker(ServerWorker):
random = None # Random string for secure connections
length = 32 # Random string length
exec_level = None # Execution level (permitted operations)
jobmgr = JobMgr()
## pings ogcore
def mon (self):
n = 0
while True:
time.sleep (1)
n += 1
if not n % 10:
body = {
"iph": self.interface.ip,
"timestamp": int (time.time()),
}
logger.debug (f'about to send ping ({body})')
self.REST.sendMessage ('clients/status/webhook', body)
def onActivation(self):
"""
Sends OGAgent activation notification to OpenGnsys server
"""
if os.path.exists ('/scripts/oginit'):
## estamos en oglive, este modulo no debe cargarse
## esta lógica la saco de src/opengnsys/linux/operations.py, donde hay un if similar
raise Exception ('Refusing to load within an ogLive image')
e = None # Error info
t = 0 # Count of time
# 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
try:
url = self.service.config.get('opengnsys', 'remote')
url = self.service.config.get(self.name, 'remote')
except NoOptionError as e:
logger.error("Configuration error: {}".format(e))
raise e
self.REST = REST(url)
# Execution level ('full' by default)
try:
self.exec_level = self.service.config.get('opengnsys', 'level')
self.exec_level = self.service.config.get(self.name, 'level')
except NoOptionError:
self.exec_level = 'full'
# Get network interfaces until they are active or timeout (5 minutes)
@ -166,7 +174,7 @@ class OpenGnSysWorker(ServerWorker):
logger.warn (str (e))
# 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'))
self.REST = REST(self.service.config.get(self.name, 'altremote'))
self.REST.sendMessage('ogagent/started', {'mac': self.interface.mac, 'ip': self.interface.ip,
'secret': self.random, 'ostype': operations.os_type,
'osversion': operations.os_version, 'alt_url': True,
@ -194,6 +202,10 @@ class OpenGnSysWorker(ServerWorker):
if os.path.isfile(new_hosts_file):
shutil.copyfile(new_hosts_file, hosts_file)
threading.Thread (name='monitoring_thread', target=self.mon, daemon=True).start()
logger.debug ('onActivation ok')
def onDeactivation(self):
"""
Sends OGAgent stopping notification to OpenGnsys server
@ -202,9 +214,6 @@ class OpenGnSysWorker(ServerWorker):
self.REST.sendMessage('ogagent/stopped', {'mac': self.interface.mac, 'ip': self.interface.ip,
'ostype': operations.os_type, 'osversion': operations.os_version})
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
@ -335,30 +344,49 @@ class OpenGnSysWorker(ServerWorker):
logger.debug('Processing script request')
# Decoding script
script = urllib.parse.unquote(base64.b64decode(post_params.get('script')).decode('utf-8'))
logger.debug('received script {}'.format(script))
if operations.os_type == 'Windows':
## for windows, we turn the script into utf16le, then to b64 again, and feed the blob to powershell
u16 = script.encode ('utf-16le') ## utf16
b64 = base64.b64encode (u16).decode ('utf-8') ## b64 (which returns bytes, so we need an additional decode(utf8))
script = """
import os
import tempfile
import subprocess
cp = subprocess.run ("powershell -WindowStyle Hidden -EncodedCommand {}", capture_output=True)
subprocs_log = os.path.join (tempfile.gettempdir(), 'opengnsys-subprocs.log')
with open (subprocs_log, 'ab') as fd: ## TODO improve this logging
fd.write (cp.stdout)
fd.write (cp.stderr)
""".format (b64)
else:
script = 'import subprocess; subprocess.check_output("""{0}""",shell=True)'.format(script)
# Executing script.
logger.debug('received script "{}"'.format(script))
if post_params.get('client', 'false') == 'false':
thr = ScriptExecutorThread(script)
thr.start()
else:
jobid = self.jobmgr.launch_job (script, False)
return {'op': 'launched', 'jobid': jobid}
else: ## post_params.get('client') is not 'false'
## send script as-is
self.sendClientMessage('script', {'code': script})
return {'op': 'launched'}
#return {'op': 'launched', 'jobid': jobid} ## TODO obtain jobid generated at the client (can it be done?)
return {'op': 'launched'}
@execution_level('full')
@check_secret
def process_terminatescript(self, path, get_params, post_params, server):
jobid = post_params.get('jobid', None)
logger.debug('Processing terminate_script request, jobid "{}"'.format (jobid))
if jobid is None:
return {}
self.sendClientMessage('terminatescript', {'jobid': jobid})
self.jobmgr.terminate_job (jobid)
return {}
@execution_level('full')
@check_secret
def process_preparescripts(self, path, get_params, post_params, server):
logger.debug('Processing preparescripts request')
self.st = self.jobmgr.prepare_jobs()
logger.debug('Sending preparescripts to client')
self.sendClientMessage('preparescripts', None)
return {}
def process_client_preparescripts(self, params):
logger.debug('Processing preparescripts message from client')
for p in params:
#logger.debug ('p "{}"'.format(p))
self.st.append (p)
@execution_level('full')
@check_secret
def process_getscripts(self, path, get_params, post_params, server):
logger.debug('Processing getscripts request')
return self.st
@execution_level('full')
@check_secret

View File

@ -0,0 +1,875 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# Copyright (c) 2024-2025 Qindel Formación y Servicios 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
@author: Natalia Serrano, nserrano at qindel dot com
"""
import base64
import os
import signal
import string
import random
import subprocess
from pathlib import Path
from urllib.parse import unquote
from opengnsys import VERSION
from opengnsys.log import logger
from opengnsys.workers import ogLiveWorker
# 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
if not server: ## this happens on startup, eg. onActivation->autoexecCliente->ejecutaArchivo->popup->check_secret
return fnc (*args, **kwargs)
if this.random == server.headers['Authorization']:
return fnc (*args, **kwargs)
else:
raise Exception ('Unauthorized operation')
except Exception as e:
logger.error (str (e))
raise Exception (e)
return wrapper
# Check if operation is permitted
def execution_level(level):
def check_permitted(fnc):
def wrapper(*args, **kwargs):
levels = ['status', 'halt', 'full']
this = args[0]
try:
if levels.index(level) <= levels.index(this.exec_level):
return fnc(*args, **kwargs)
else:
raise Exception('Unauthorized operation')
except Exception as e:
logger.debug (str(e))
raise Exception(e)
return wrapper
return check_permitted
class ogAdmClientWorker (ogLiveWorker):
name = 'ogAdmClient' # Module name
REST = None # REST object
def onDeactivation (self):
"""
Sends OGAgent stopping notification to OpenGnsys server
"""
logger.debug ('onDeactivation')
self.REST.sendMessage ('ogAdmClient/stopped', {'mac': self.mac, 'ip': self.IPlocal, 'idcentro': self.idcentro, 'idaula': self.idaula,
'idordenador': self.idordenador, 'nombreordenador': self.nombreordenador})
def InventariandoSoftware (self, dsk, par, nfn):
sft_src = f'/tmp/CSft-{self.IPlocal}-{par}'
try:
self.interfaceAdmin (nfn, [dsk, par, sft_src])
herror = 0
except:
herror = 1
if herror:
logger.warning ('Error al ejecutar el comando')
b64 = ''
self.muestraMensaje (20)
else:
if not os.path.exists (sft_src):
raise Exception (f'interfaceAdmin({nfn}) returned success but did not create file ({sft_src})')
sft_src_contents = Path (sft_src).read_bytes()
b64 = base64.b64encode (sft_src_contents).decode ('utf-8')
self.muestraMensaje (19)
cmd = {
'nfn': 'RESPUESTA_InventarioSoftware',
'dsk': dsk, ## not in the original C code, around ogAdmClient.c:1944
'par': par,
'contents': b64,
}
return self.respuestaEjecucionComando (cmd, herror, 0)
def ejecutaArchivo (self,fn):
logger.debug ('fn ({})'.format (fn))
## in the "file" there's not just some bash, but a sequence of parameters such as "nfn=Function\rparam1=foo\rparam2=bar"
buffer = subprocess.run (['cat', fn], capture_output=True).stdout.strip().decode ('utf-8')
logger.debug ('buffer ({})'.format (buffer.replace ('\r', '\\r'))) ## change \r so as not to mess with the log
if buffer:
for l in buffer.split ('@'):
if not len (l): continue
logger.debug ('line ({})'.format (l.replace ('\r', '\\r'))) ## change \r so as not to mess with the log
## at this point, an option would be fire up a curl to localhost, but we can also parse the params and locally call the desired function:
post_params = {}
for param in l.split ("\r"):
k, v = param.split ('=')
post_params[k] = v
logger.debug ('post_params "{}"'.format (post_params))
func_name = post_params.pop ('nfn', None)
if func_name is None:
logger.error ('Ha ocurrido algún problema al procesar la trama recibida')
break
func = getattr (self, 'process_' + func_name)
## func is already a ref to self.func, so we don't have to call self.func(...) or func(self, ...)
logger.debug ('calling function "{}" with post_params "{}"'.format (func_name, post_params))
output = func ([], {}, post_params, None)
logger.debug ('output "{}"'.format (output))
if not output:
logger.error ('Ha ocurrido algún problema al procesar la trama recibida')
break
def inclusionCliente (self):
cfg = self.LeeConfiguracion()
if not cfg:
logger.warning ('No se ha podido recuperar la configuración de las particiones del disco')
logger.warning ('Ha ocurrido algún problema en el proceso de inclusión del cliente')
logger.error ('LeeConfiguracion() failed')
return False
res = self.enviaMensajeServidor ('InclusionCliente', { 'cfg': self.cfg2obj (cfg), 'secret': self.random, 'agent_version': VERSION })
logger.debug ('res ({})'.format (res))
## RESPUESTA_InclusionCliente
if (type (res) is not dict or 0 == res['res']) :
logger.error ('Ha ocurrido algún problema en el proceso de inclusión del cliente')
return False
if (not res['ido'] or not res['npc']):
logger.error ('Se han recibido parámetros con valores no válidos')
return False
self.idordenador = res['ido'] ## Identificador del ordenador
self.nombreordenador = res['npc'] ## Nombre del ordenador
self.cache = res['che'] ## Tamaño de la caché reservada al cliente
self.idproautoexec = res['exe'] ## Procedimento de inicio (Autoexec)
self.idcentro = res['idc'] ## Identificador de la Unidad Organizativa
self.idaula = res['ida'] ## Identificador del aula
return True
def cuestionCache (self):
return True ## ogAdmClient.c:425
def autoexecCliente (self):
res = self.enviaMensajeServidor ('AutoexecCliente', { 'exe': self.idproautoexec })
logger.debug ('res ({})'.format (res))
if (type (res) is not dict):
logger.error ('Ha ocurrido algún problema al enviar una petición de comandos o tareas pendientes al Servidor de Administración')
logger.error ('Ha ocurrido algún problema al recibir una petición de comandos o tareas pendientes desde el Servidor de Administración')
return False
## RESPUESTA_AutoexecCliente
if (type (res) is not dict or 0 == res['res']) :
logger.error ('Ha ocurrido algún problema al procesar la trama recibida')
return False
logger.info (res)
res = self.enviaMensajeServidor ('enviaArchivo', { 'nfl': res['nfl'] })
if (type (res) is not dict):
logger.error ('Ha ocurrido algún problema al enviar una petición de comandos o tareas pendientes al Servidor de Administración')
logger.error ('Ha ocurrido algún problema al recibir un archivo por la red')
return False
logger.debug (f'res ({res})')
fileautoexec = '/tmp/_autoexec_{}'.format (self.IPlocal)
logger.debug ('fileautoexec ({})'.format (fileautoexec))
with open (fileautoexec, 'w') as fd:
fd.write (base64.b64decode (res['contents']).decode ('utf-8'))
self.ejecutaArchivo (fileautoexec)
return True
def comandosPendientes (self):
while (True):
res = self.enviaMensajeServidor ('ComandosPendientes') ## receives just one command
if (type (res) is not dict):
logger.error ('Ha ocurrido algún problema al enviar una petición de comandos o tareas pendientes al Servidor de Administración')
logger.error ('Ha ocurrido algún problema al recibir una petición de comandos o tareas pendientes desde el Servidor de Administración')
return False
logger.info (res)
if ('NoComandosPtes' == res['nfn']):
break
## TODO manage the rest of cases... we might have to do something similar to ejecutaArchivo
#if (!gestionaTrama (ptrTrama)){ // Análisis de la trama
# logger.error ('Ha ocurrido algún problema al procesar la trama recibida')
# return False
#}
## ATM let's just return false to avoid a possible infinite loop
return False
return True
def procesaComandos (self):
res = self.enviaMensajeServidor ('DisponibilidadComandos', { 'tpc': 'OPG' }) ## Activar disponibilidad
logger.debug ('res ({})'.format (res))
if (type (res) is not dict):
logger.error ('Ha ocurrido algún problema al enviar una petición de comandos interactivos al Servidor de Administración')
return False
logger.info ('Disponibilidad de comandos activada') ## Disponibilidad de cliente activada
## we now return true and the outer agent code gets to wait for requests from outside
## TODO thing is, ogAdmClient always calls comandosPendientes() after every received request. How do we do that here?
#
#ptrTrama=recibeMensaje (&socket_c);
#if (!ptrTrama){
# errorLog (modulo,46,FALSE); 'Ha ocurrido algún problema al recibir un comando interactivo desde el Servidor de Administración'
# return;
#}
#close (socket_c);
#if (!gestionaTrama (ptrTrama)){ // Análisis de la trama
# errorLog (modulo,39,FALSE); 'Ha ocurrido algún problema al procesar la trama recibida'
# return;
#}
#if (!comandosPendientes (ptrTrama)){
# errorLog (modulo,42,FALSE); 'Ha ocurrido algún problema al enviar una petición de comandos o tareas pendientes al Servidor de Administración'
#}
def onActivation (self):
super().onActivation()
self.exec_level = 'full'
self.random = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(32))
logger.info ('Inicio de sesion')
logger.info ('Abriendo sesión en el servidor de Administración')
if (not self.inclusionCliente()):
raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo')
logger.info ('Cliente iniciado')
logger.info ('Procesando caché')
if not self.cuestionCache():
raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo')
if self.idproautoexec > 0:
logger.info ('Ejecución de archivo Autoexec')
if not self.autoexecCliente():
raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo')
logger.info ('Procesa comandos pendientes')
if not self.comandosPendientes():
raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo')
logger.info ('Acciones pendientes procesadas')
self.muestraMenu()
self.procesaComandos()
logger.info ('onActivation ok')
def do_CrearImagen (self, post_params):
for k in ['dsk', 'par', 'cpt', 'idi', 'nci', 'ipr', 'nfn', 'ids']:
if k not in post_params:
logger.error (f'required parameter ({k}) not in POST params')
return {}
dsk = post_params['dsk'] ## Disco
par = post_params['par'] ## Número de partición
cpt = post_params['cpt'] ## Código de la partición
idi = post_params['idi'] ## Identificador de la imagen
nci = post_params['nci'] ## Nombre canónico de la imagen
ipr = post_params['ipr'] ## Ip del repositorio
nfn = post_params['nfn']
ids = post_params['ids']
self.muestraMensaje (7)
try:
res = self.InventariandoSoftware (dsk, par, 'InventarioSoftware') ## Crea inventario Software previamente
except:
logger.warning ('Error al ejecutar el comando')
return {}
if res['contents']:
self.muestraMensaje (2)
inv_sft = res['contents']
try:
self.interfaceAdmin (nfn, [dsk, par, nci, ipr])
self.muestraMensaje (9)
herror = 0
except:
logger.warning ('Error al ejecutar el comando')
self.muestraMensaje (10)
herror = 1
else:
logger.warning ('Error al ejecutar el comando')
herror = 1
inv_sft = ''
self.muestraMenu()
cmd = {
'nfn': 'RESPUESTA_CrearImagen',
'idi': idi, ## Identificador de la imagen
'dsk': dsk, ## Número de disco
'par': par, ## Número de partición de donde se creó
'cpt': cpt, ## Tipo o código de partición
'ipr': ipr, ## Ip del repositorio donde se alojó
'inv_sft': inv_sft,
}
return self.respuestaEjecucionComando (cmd, herror, ids)
def do_RestaurarImagen (self, post_params):
for k in ['dsk', 'par', 'idi', 'ipr', 'nci', 'ifs', 'ptc', 'nfn', 'ids']:
if k not in post_params:
logger.error (f'required parameter ({k}) not in POST params')
return {}
dsk = post_params['dsk']
par = post_params['par']
idi = post_params['idi']
ipr = post_params['ipr']
nci = post_params['nci']
ifs = post_params['ifs']
ptc = post_params['ptc'] ## Protocolo de clonación: Unicast, Multicast, Torrent
nfn = post_params['nfn']
ids = post_params['ids']
self.muestraMensaje (3)
try:
## the ptc.split() is useless right now, since interfaceAdmin() does ' '.join(params) in order to spawn a shell
## however we're going to need it in the future (when everything gets translated into python), plus it's harmless now. So let's do it
#self.interfaceAdmin (nfn, [dsk, par, nci, ipr, ptc])
self.interfaceAdmin (nfn, [dsk, par, nci, ipr] + ptc.split())
self.muestraMensaje (11)
herror = 0
except:
logger.warning ('Error al ejecutar el comando')
self.muestraMensaje (12)
herror = 1
cfg = self.LeeConfiguracion()
if not cfg:
logger.warning ('No se ha podido recuperar la configuración de las particiones del disco')
self.muestraMenu()
cmd = {
'nfn': 'RESPUESTA_RestaurarImagen',
'idi': idi, ## Identificador de la imagen
'dsk': dsk, ## Número de disco
'par': par, ## Número de partición
'ifs': ifs, ## Identificador del perfil software
'cfg': self.cfg2obj(cfg), ## Configuración de discos
}
return self.respuestaEjecucionComando (cmd, herror, ids)
def do_Configurar (self, post_params):
for k in ['nfn', 'dsk', 'cfg', 'ids']:
if k not in post_params:
logger.error (f'required parameter ({k}) not in POST params')
return {}
nfn = post_params['nfn']
dsk = post_params['dsk']
cfg = post_params['cfg']
ids = post_params['ids']
self.muestraMensaje (4)
params = []
disk_info = cfg.pop (0)
logger.debug (f'disk_info ({disk_info})')
for k in ['dis', 'tch']:
params.append (f'{k}={disk_info[k]}')
disk_info_str = '*'.join (params)
partitions = []
for entry in cfg:
logger.debug (f'entry ({entry})')
params = []
for k in ['par', 'cpt', 'sfi', 'tam', 'ope']:
params.append (f'{k}={entry[k]}')
partitions.append ('*'.join (params))
part_info_str = '%'.join (partitions)
cfg_str = f'{disk_info_str}!{part_info_str}%'
try:
self.interfaceAdmin (nfn, ['ignored', cfg_str])
self.muestraMensaje (14)
herror = 0
except:
logger.warning ('Error al ejecutar el comando')
self.muestraMensaje (13)
herror = 1
cfg = self.LeeConfiguracion()
if not cfg:
logger.warning ('No se ha podido recuperar la configuración de las particiones del disco')
return {}
cmd = {
'nfn': 'RESPUESTA_Configurar',
'cfg': self.cfg2obj (cfg),
}
self.muestraMenu()
return self.respuestaEjecucionComando (cmd, herror, ids)
def do_InventarioHardware (self, post_params):
for k in ['nfn', 'ids']:
if k not in post_params:
logger.error (f'required parameter ({k}) not in POST params')
return {}
nfn = post_params['nfn']
ids = post_params['ids']
self.muestraMensaje (6)
hrdsrc = f'/tmp/Chrd-{self.IPlocal}' ## Nombre que tendra el archivo de inventario
hrddst = f'/tmp/Shrd-{self.IPlocal}' ## Nombre que tendra el archivo en el Servidor
try:
self.interfaceAdmin (nfn, [hrdsrc])
hrdsrc_contents = Path (hrdsrc).read_bytes()
logger.debug (f'hrdsrc_contents 1 ({hrdsrc_contents})')
herror = 0
except:
logger.warning ('Error al ejecutar el comando')
self.muestraMensaje (18)
herror = 1
if herror:
hrddst = ''
else:
logger.debug (f'hrdsrc_contents 2 ({hrdsrc_contents})')
## Envía fichero de inventario al servidor
res = self.enviaMensajeServidor ('recibeArchivo', { 'nfl': hrddst, 'contents': base64.b64encode (hrdsrc_contents).decode ('utf-8') })
logger.debug (res)
if not res:
logger.error ('Ha ocurrido algún problema al enviar un archivo por la red')
herror = 12 ## Error de envío de fichero por la red
self.muestraMensaje (17)
## Envia respuesta de ejecución de la función de interface
cmd = {
'nfn': 'RESPUESTA_InventarioHardware',
'hrd': hrddst,
}
self.muestraMenu()
return self.respuestaEjecucionComando (cmd, herror, ids)
def do_InventarioSoftware (self, post_params):
for k in ['nfn', 'dsk', 'par', 'ids']:
if k not in post_params:
logger.error (f'required parameter ({k}) not in POST params')
return {}
nfn = post_params['nfn']
dsk = post_params['dsk']
par = post_params['par']
ids = post_params['ids']
self.muestraMensaje (7)
try:
cmd = self.InventariandoSoftware (dsk, par, 'InventarioSoftware')
herror = 0
except:
logger.warning ('Error al ejecutar el comando')
cmd = { 'nfn': 'RESPUESTA_InventarioSoftware' }
herror = 1
self.muestraMenu()
return self.respuestaEjecucionComando (cmd, herror, ids)
def do_Actualizar (self, post_params):
self.muestraMensaje (1)
#if !comandosPendientes: error 84 'Ha ocurrido algún problema al reiniciar la sesión del cliente'
cfg = self.LeeConfiguracion()
if not cfg:
logger.warning ('No se ha podido recuperar la configuración de las particiones del disco')
logger.error ('LeeConfiguracion() failed')
return {}
cmd = {
'nfn': 'RESPUESTA_Actualizar',
'cfg': self.cfg2obj (cfg),
}
self.muestraMenu()
return self.respuestaEjecucionComando (cmd, 0)
def do_Comando (self, post_params):
for k in ['nfn', 'ids']:
if k not in post_params:
logger.error (f'required parameter ({k}) not in POST params')
return {}
nfn = post_params['nfn']
ids = post_params['ids']
try:
self.interfaceAdmin (nfn)
herror = 0
except:
logger.warning ('Error al ejecutar el comando')
herror = 1
cmd = {
'nfn': 'RESPUESTA_Comando',
}
return self.respuestaEjecucionComando (cmd, herror, ids)
def do_ConsolaRemota (self, post_params):
for k in ['nfn', 'scp']:
if k not in post_params:
logger.error (f'required parameter ({k}) not in POST params')
return {}
nfn = post_params['nfn']
scp = base64.b64decode (unquote (post_params['scp'])).decode ('utf-8')
filescript = f'/tmp/_script_{self.IPlocal}'
ecosrc = f'/tmp/_econsola_{self.IPlocal}'
ecodst = f'/tmp/_Seconsola_{self.IPlocal}' ## Nombre que tendra el archivo en el Servidor
with open (filescript, 'w') as fd:
fd.write (scp)
try:
self.interfaceAdmin (nfn, [filescript, ecosrc])
ecosrc_contents = Path (ecosrc).read_bytes()
except:
logger.error ('Error al ejecutar el comando')
return {}
logger.debug ('sending recibeArchivo to server')
res = self.enviaMensajeServidor ('recibeArchivo', { 'nfl': ecodst, 'contents': base64.b64encode (ecosrc_contents).decode ('utf-8') })
logger.debug (res)
if not res:
logger.error ('Ha ocurrido algún problema al enviar un archivo por la red')
return {}
def do_Apagar (self, post_params):
for k in ['nfn', 'ids']:
if k not in post_params:
logger.error (f'required parameter ({k}) not in POST params')
return {}
nfn = post_params['nfn']
ids = post_params['ids']
try:
self.interfaceAdmin (nfn)
herror = 0
except:
logger.warning ('Error al ejecutar el comando')
herror = 1
cmd = {
'nfn': 'RESPUESTA_Apagar',
}
return self.respuestaEjecucionComando (cmd, herror, ids)
def do_Reiniciar (self, post_params):
for k in ['nfn', 'ids']:
if k not in post_params:
logger.error (f'required parameter ({k}) not in POST params')
return {}
nfn = post_params['nfn']
ids = post_params['ids']
try:
self.interfaceAdmin (nfn)
herror = 0
except:
logger.warning ('Error al ejecutar el comando')
herror = 1
cmd = {
'nfn': 'RESPUESTA_Reiniciar',
}
return self.respuestaEjecucionComando (cmd, herror, ids)
def do_IniciarSesion (self, post_params):
for k in ['nfn', 'dsk', 'par', 'ids']:
if k not in post_params:
logger.error (f'required parameter ({k}) not in POST params')
return {}
nfn = post_params['nfn']
dsk = post_params['dsk']
par = post_params['par']
ids = post_params['ids']
try:
self.interfaceAdmin (nfn, [dsk, par])
herror = 0
except:
logger.warning ('Error al ejecutar el comando')
herror = 1
cmd = {
'nfn': 'RESPUESTA_IniciarSesion',
}
return self.respuestaEjecucionComando (cmd, herror, ids)
def do_EjecutarScript (self, post_params):
for k in ['nfn', 'scp', 'ids']:
if k not in post_params:
logger.error (f'required parameter ({k}) not in POST params')
return {}
nfn = post_params['nfn']
scp = base64.b64decode (unquote (post_params['scp'])).decode ('utf-8')
ids = post_params['ids']
self.muestraMensaje (8)
filescript = f'/tmp/_script_{self.IPlocal}' ## Nombre del archivo de script
with open (filescript, 'w') as fd:
fd.write (scp)
try:
self.interfaceAdmin (nfn, [filescript])
self.muestraMensaje (22)
herror = 0
except:
logger.warning ('Error al ejecutar el comando')
self.muestraMensaje (21)
herror = 1
## Toma configuración de particiones
cfg = self.LeeConfiguracion()
if not cfg:
logger.warning ('No se ha podido recuperar la configuración de las particiones del disco')
herror = 36
#herror=ejecutarCodigoBash(scp); ## ogAdmClient.c:2004
cmd = {
'nfn': 'RESPUESTA_EjecutarScript',
'cfg': self.cfg2obj (cfg),
}
self.muestraMenu()
return self.respuestaEjecucionComando (cmd, herror, ids)
@execution_level('status')
def process_status (self, path, get_params, post_params, server):
logger.debug ('in process_status, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
full_config = 'full-config' in post_params and post_params['full-config']
thr_status = {}
for k in self.thread_list:
thr_status[k] = {
'running': self.thread_list[k]['running'],
'result': self.thread_list[k]['result'],
}
ret = {
'nfn': 'RESPUESTA_status',
'mac': self.mac,
'st': 'OGL',
'ip': self.IPlocal,
'threads': thr_status,
}
if full_config:
cfg = self.LeeConfiguracion()
ret['cfg'] = self.cfg2obj (cfg)
return ret
@check_secret
def process_popup (self, path, get_params, post_params, server):
logger.debug ('in process_popup, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
logger.debug ('type(post_params) "{}"'.format (type (post_params)))
## in process_popup, should not happen, path "[]" get_params "{}" post_params "{'title': 'mi titulo', 'message': 'mi mensaje'}" server "<opengnsys.httpserver.HTTPServerHandler object at 0x7fa788cb8fa0>"
## type(post_params) "<class 'dict'>"
return {'debug':'test'}
@execution_level('full')
@check_secret
def process_Actualizar (self, path, get_params, post_params, server):
logger.debug ('in process_Actualizar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
return self._long_running_job ('Actualizar', self.do_Actualizar, args=(post_params,))
@execution_level('full')
@check_secret
def process_Purgar (self, path, get_params, post_params, server):
logger.debug ('in process_Purgar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
os.kill (os.getpid(), signal.SIGTERM)
return {}
#exit (0) ## ogAdmClient.c:905
@execution_level('full')
@check_secret
def process_Comando (self, path, get_params, post_params, server):
logger.debug ('in process_Comando, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
return self._long_running_job ('Comando', self.do_Comando, args=(post_params,))
def process_Sondeo (self, path, get_params, post_params, server):
logger.debug ('in process_Sondeo, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
return {} ## ogAdmClient.c:920
@execution_level('full')
@check_secret
def process_ConsolaRemota (self, path, get_params, post_params, server):
logger.debug ('in process_ConsolaRemota, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
return self._long_running_job ('ConsolaRemota', self.do_ConsolaRemota, args=(post_params,))
@execution_level('full')
@check_secret
def process_Arrancar (self, path, get_params, post_params, server):
logger.debug ('in process_Arrancar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
for k in ['ids']:
if k not in post_params:
logger.error (f'required parameter ({k}) not in POST params')
return {}
ids = post_params['ids']
cmd = {
'nfn': 'RESPUESTA_Arrancar',
'tpc': 'OPG',
}
return self.respuestaEjecucionComando (cmd, 0, ids)
@execution_level('halt')
@check_secret
def process_Apagar (self, path, get_params, post_params, server):
logger.debug ('in process_Apagar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
return self._long_running_job ('Apagar', self.do_Apagar, args=(post_params,))
@execution_level('halt')
@check_secret
def process_Reiniciar (self, path, get_params, post_params, server):
logger.debug ('in process_Reiniciar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
return self._long_running_job ('Reiniciar', self.do_Reiniciar, args=(post_params,))
@execution_level('full')
@check_secret
def process_IniciarSesion (self, path, get_params, post_params, server):
logger.debug ('in process_IniciarSesion, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
return self._long_running_job ('IniciarSesion', self.do_IniciarSesion, args=(post_params,))
@execution_level('full')
@check_secret
def process_EjecutarScript (self, path, get_params, post_params, server):
logger.debug ('in process_EjecutarScript, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
return self._long_running_job ('EjecutarScript', self.do_EjecutarScript, args=(post_params,))
@execution_level('full')
@check_secret
def process_EjecutaComandosPendientes (self, path, get_params, post_params, server):
logger.debug ('in process_EjecutaComandosPendientes, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
return {'true':'true'} ## ogAdmClient.c:2138
@execution_level('full')
@check_secret
def process_CrearImagen (self, path, get_params, post_params, server):
logger.debug ('in process_CrearImagen, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
logger.debug ('type(post_params) "{}"'.format (type (post_params)))
return self._long_running_job ('CrearImagen', self.do_CrearImagen, args=(post_params,))
#def process_CrearImagenBasica (self, path, get_params, post_params, server):
# logger.debug ('in process_CrearImagenBasica, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
# logger.warning ('this method has been removed')
# raise Exception ({ '_httpcode': 404, '_msg': 'This method has been removed' })
#def process_CrearSoftIncremental (self, path, get_params, post_params, server):
# logger.debug ('in process_CrearSoftIncremental, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
# logger.warning ('this method has been removed')
# raise Exception ({ '_httpcode': 404, '_msg': 'This method has been removed' })
@execution_level('full')
@check_secret
def process_RestaurarImagen (self, path, get_params, post_params, server):
logger.debug ('in process_RestaurarImagen, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
logger.debug ('type(post_params) "{}"'.format (type (post_params)))
return self._long_running_job ('RestaurarImagen', self.do_RestaurarImagen, args=(post_params,))
#def process_RestaurarImagenBasica (self, path, get_params, post_params, server):
# logger.debug ('in process_RestaurarImagenBasica, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
# logger.warning ('this method has been removed')
# raise Exception ({ '_httpcode': 404, '_msg': 'This method has been removed' })
#def process_RestaurarSoftIncremental (self, path, get_params, post_params, server):
# logger.warning ('in process_RestaurarSoftIncremental')
# logger.debug ('in process_RestaurarSoftIncremental, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
# logger.warning ('this method has been removed')
# raise Exception ({ '_httpcode': 404, '_msg': 'This method has been removed' })
## una partición + cache en disco de 30 Gb:
@execution_level('full')
@check_secret
def process_Configurar (self, path, get_params, post_params, server):
logger.debug ('in process_Configurar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
return self._long_running_job ('Configurar', self.do_Configurar, args=(post_params,))
@execution_level('full')
@check_secret
def process_InventarioHardware (self, path, get_params, post_params, server):
logger.debug ('in process_InventarioHardware, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
return self._long_running_job ('InventarioHardware', self.do_InventarioHardware, args=(post_params,))
@execution_level('full')
@check_secret
def process_InventarioSoftware (self, path, get_params, post_params, server):
logger.debug ('in process_InventarioSoftware, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
return self._long_running_job ('InventarioSoftware', self.do_InventarioSoftware, args=(post_params,))
@execution_level('full')
@check_secret
def process_KillJob (self, path, get_params, post_params, server):
logger.debug ('in process_KillJob, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
jid = post_params['job_id']
r = self.killer (jid)
logger.debug (f'r bef ({r})')
r.update ({ 'nfn':'RESPUESTA_KillJob', 'job':jid })
logger.debug (f'r aft ({r})')
return r

View File

@ -1,51 +0,0 @@
# -*- 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))

View File

@ -158,7 +158,7 @@ class CommonService(object):
validMods.append(mod)
except Exception as e:
logger.exception()
logger.error("Activation of {} failed: {}".format(mod.name, exceptionToMessage(e)))
logger.debug("Activation of {} failed: {}".format(mod.name, exceptionToMessage(e)))
self.modules[:] = validMods # copy instead of assignment

View File

@ -36,6 +36,8 @@ import logging
import os
import tempfile
from ..log_format import JsonFormatter
# Valid logging levels, from UDS Broker (uds.core.utils.log)
OTHER, DEBUG, INFO, WARN, ERROR, FATAL = (10000 * (x + 1) for x in range(6))
@ -44,13 +46,24 @@ 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\appdata\local\temp
logging.basicConfig(
filename=os.path.join(tempfile.gettempdir(), 'opengnsys.log'),
filemode='a',
format='%(levelname)s %(asctime)s (%(threadName)s) %(message)s',
level=logging.DEBUG
)
fname1 = os.path.join (tempfile.gettempdir(), 'opengnsys.log')
fmt1 = logging.Formatter (fmt='%(levelname)s %(asctime)s (%(threadName)s) (%(funcName)s) %(message)s')
fh1 = logging.FileHandler (filename=fname1, mode='a')
fh1.setFormatter (fmt1)
fh1.setLevel (logging.DEBUG)
fname2 = os.path.join (tempfile.gettempdir(), 'opengnsys.json.log')
fmt2 = JsonFormatter ({"timestamp": "asctime", "severity": "levelname", "threadName": "threadName", "function": "funcName", "message": "message"}, time_format='%Y-%m-%d %H:%M:%S', msec_format='')
fh2 = logging.FileHandler (filename=fname2, mode='a')
fh2.setFormatter (fmt2)
fh2.setLevel (logging.DEBUG)
self.logger = logging.getLogger('opengnsys')
self.logger.setLevel (logging.DEBUG)
self.logger.addHandler (fh1)
self.logger.addHandler (fh2)
self.serviceLogger = False
def log(self, level, message):
@ -58,7 +71,7 @@ class LocalLogger(object):
# 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)
self.logger.log(int(level / 1000 - 10), message, stacklevel=4)
if level < INFO or self.serviceLogger is False: # Only information and above will be on event log
return

View File

@ -35,6 +35,7 @@ import os
import locale
import subprocess
import ctypes
import base64
from ctypes.wintypes import DWORD, LPCWSTR
import win32com.client # @UnresolvedImport, pylint: disable=import-error
import win32net # @UnresolvedImport, pylint: disable=import-error
@ -46,15 +47,6 @@ 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
@ -81,23 +73,6 @@ def getNetworkInfo():
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.
@ -115,8 +90,8 @@ def getWindowsVersion():
EWX_LOGOFF = 0x00000000
EWX_SHUTDOWN = 0x00000001
EWX_REBOOT = 0x00000002
EWX_FORCE = 0x00000004
EWX_POWEROFF = 0x00000008
#EWX_FORCE = 0x00000004
#EWX_POWEROFF = 0x00000008
EWX_FORCEIFHUNG = 0x00000010
@ -137,109 +112,6 @@ 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
@ -275,3 +147,10 @@ def get_etc_path():
Returns etc directory path.
"""
return os.path.join('C:', os.sep, 'Windows', 'System32', 'drivers', 'etc')
def build_popen_args(script):
## turn the script into utf16le, then to b64 again, and feed the blob to powershell
u16 = script.encode ('utf-16le') ## utf16
b64 = base64.b64encode (u16).decode ('utf-8') ## b64 (which returns bytes, so we need an additional decode(utf8))
return ['powershell', '-WindowStyle', 'Hidden', '-EncodedCommand', b64]

View File

@ -1,2 +1,3 @@
from .server_worker import ServerWorker
from .client_worker import ClientWorker
from .oglive_worker import ogLiveWorker, ThreadWithResult

View File

@ -0,0 +1,514 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2024 Qindel Formación y Servicios 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: Natalia Serrano, nserrano at qindel dot com
"""
# pylint: disable=unused-wildcard-import,wildcard-import
import os
import re
import time
import random
import subprocess
import threading
import signal
from configparser import NoOptionError
from opengnsys import REST
from opengnsys.log import logger
from .server_worker import ServerWorker
## https://stackoverflow.com/questions/6893968/how-to-get-the-return-value-from-a-thread
class ThreadWithResult (threading.Thread):
def run (self):
try:
self.result = None
if self._target is not None:
## the first arg in self._args is the queue
self.pid_q = self._args[0]
self.stdout_q = self._args[1]
self._args = self._args[2:]
try:
self.result = self._target (*self._args, **self._kwargs)
except Exception as e:
self.result = { 'res': 2, 'der': f'got exception: ({e})' } ## res=2 as defined in ogAdmClient.c:2048
finally:
# Avoid a refcycle if the thread is running a function with an argument that has a member that points to the thread.
del self._target, self._args, self._kwargs, self.pid_q, self.stdout_q
class ogLiveWorker(ServerWorker):
thread_list = {}
thread_lock = threading.Lock()
tbErroresScripts = [
"Se han generado errores desconocidos. No se puede continuar la ejecución de este módulo", ## 0
"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",
]
def notifier (self, job_id, result):
result['job_id'] = job_id
self.REST.sendMessage ('clients/status/webhook', result)
def killer (self, job_id):
logger.debug (f'killer() called, job_id ({job_id})')
if job_id not in self.thread_list: return { 'res': 2, 'der': 'Unknown job' }
with self.thread_lock:
if 'thread' not in self.thread_list[job_id]: return { 'res': 2, 'der': 'Job is not running' }
t = self.thread_list[job_id]['thread']
pid = self.thread_list[job_id]['child_pid']
logger.debug (f'pid ({pid})')
try_times = 8
sig = signal.SIGTERM
msg = f'could not kill pid ({pid}) after ({try_times}) tries'
success = 2 ## mimic cmd['res'] in respuestaEjecucionComando(): "1" means success, "2" means failed
while True:
t.join (0.05)
if not t.is_alive():
msg = 'job terminated'
success = 1
logger.debug (msg)
self.thread_list[job_id]['child_pid'] = None
break
## race condition: if the subprocess finishes just here, then we already checked that t.is_alive() is true, but os.path.exists(/proc/pid) will be false below. msg will be 'nothing to kill'.
## this is fine in the first iteration of the loop, before we send any signals. In the rest of iterations, after some signals were sent, msg should be 'job terminated' instead.
if pid:
if os.path.exists (f'/proc/{pid}'):
logger.debug (f'sending signal ({sig}) to pid ({pid})')
## if the process finishes just here, nothing happens: the signal is sent to the void
os.kill (pid, sig)
#subprocess.run (['kill', '--signal', str(sig), str(pid)])
else:
msg = f'pid ({pid}) is gone, nothing to kill'
success = 1
logger.debug (msg)
self.thread_list[job_id]['child_pid'] = None
break
else:
msg = 'no PID to kill'
logger.debug (msg)
if not try_times: break
if 4 == try_times: sig = signal.SIGKILL ## change signal after a few tries
try_times -= 1
time.sleep (0.4)
return { 'res':success, 'der':msg }
def _extract_progress (self, job_id, ary=[]):
progress = None
for i in ary:
if m := re.search (r'^\[([0-9]+)\]', i): ## look for strings like '[10]', '[60]'
#logger.debug (f"matched regex, m.groups ({m.groups()})")
progress = float (m.groups()[0]) / 100
return progress
## monitors child threads, waits for them to finish
## pings ogcore
def mon (self):
n = 0
while True:
with self.thread_lock:
for k in self.thread_list:
elem = self.thread_list[k]
if 'thread' not in elem: continue
#logger.debug (f'considering thread ({k})')
if self.pid_q:
if not self.pid_q.empty():
elem['child_pid'] = self.pid_q.get()
logger.debug (f'queue not empty, got pid ({elem["child_pid"]})')
if self.stdout_q:
partial = ''
while not self.stdout_q.empty():
partial += self.stdout_q.get()
lines = partial.splitlines()
if len (lines):
p = self._extract_progress (k, lines)
if p:
m = { "job_id": k, "progress": p }
self.REST.sendMessage ('clients/status/webhook', { "job_id": k, "progress": p })
elem['thread'].join (0.05)
if not elem['thread'].is_alive():
logger.debug (f'is no longer alive, k ({k}) thread ({elem["thread"]})')
elem['running'] = False
elem['result'] = elem['thread'].result
del elem['thread']
self.notifier (k, elem['result'])
time.sleep (1)
n += 1
if not n % 10:
alive_threads = []
for k in self.thread_list:
elem = self.thread_list[k]
if 'thread' not in elem: continue
alive_threads.append (k)
if alive_threads:
s = ','.join (alive_threads)
logger.debug (f'alive threads: {s}')
body = {
'iph': self.IPlocal,
'timestamp': int (time.time()),
}
#logger.debug (f'about to send ping ({body})')
self.REST.sendMessage ('clients/status/webhook', body)
def interfaceAdmin (self, method, parametros=[]):
if method in ['Apagar', 'CambiarAcceso', 'Configurar', 'CrearImagen', 'EjecutarScript', 'getConfiguration', 'getIpAddress', 'IniciarSesion', 'InventarioHardware', 'InventarioSoftware', 'Reiniciar', 'RestaurarImagen']:
## python
logger.debug (f'({method}) is a python method')
exe = '{}/{}.py'.format (self.pathinterface, method)
proc = [exe]+parametros
else: ## ConsolaRemota procesaCache
## bash
logger.debug (f'({method}) is a bash method')
exe = '{}/{}'.format (self.pathinterface, method)
LANG = os.environ.get ('LANG', 'en_GB.UTF-8').replace ('UTF_8', 'UTF-8')
devel_bash_prefix = f'''
PATH=/opt/opengnsys/scripts/:$PATH;
source /opt/opengnsys/etc/lang.{LANG}.conf;
for I in /opt/opengnsys/lib/engine/bin/*.lib; do source $I; done;
for i in $(declare -F |cut -f3 -d" "); do export -f $i; done;
'''
if parametros:
proc = ['bash', '-c', '{} {} {}'.format (devel_bash_prefix, exe, ' '.join (parametros))]
else:
proc = ['bash', '-c', '{} {}'.format (devel_bash_prefix, exe)]
logger.debug ('subprocess.run ("{}")'.format (' '.join (proc)))
p = subprocess.Popen (proc, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if self.pid_q:
self.pid_q.put (p.pid)
else:
## esto sucede por ejemplo cuando arranca el agente, que estamos en interfaceAdmin() en el mismo hilo, sin _long_running_job ni hilo separado
#logger.debug ('no queue--not writing any PID to it')
pass
sout = serr = ''
while p.poll() is None:
for l in iter (p.stdout.readline, b''):
partial = l.decode ('utf-8', 'ignore')
if self.stdout_q: self.stdout_q.put (partial)
sout += partial
for l in iter (p.stderr.readline, b''):
partial = l.decode ('utf-8', 'ignore')
serr += partial
time.sleep (1)
sout = sout.strip()
serr = serr.strip()
## DEBUG
logger.debug (f'stdout follows:')
for l in sout.splitlines():
logger.debug (f' {l}')
#logger.debug (f'stderr follows:')
#for l in serr.splitlines():
# logger.debug (f' {l}')
## /DEBUG
if 0 != p.returncode:
cmd_txt = ' '.join (proc)
logger.error (f'command ({cmd_txt}) failed, stderr follows:')
for l in serr.splitlines():
logger.error (f' {l}')
raise Exception (f'command ({cmd_txt}) failed, see log for details')
return sout
def tomaIPlocal (self):
try:
self.IPlocal = self.interfaceAdmin ('getIpAddress')
except Exception as e:
logger.error (e)
logger.error ('No se ha podido recuperar la dirección IP del cliente')
return False
logger.info ('local IP is "{}"'.format (self.IPlocal))
return True
def tomaMAClocal (self):
## tomaIPlocal() calls interfaceAdm('getIpAddress')
## getIpAddress runs 'ip addr show' and returns the IP address of every network interface except "lo"
## (ie. breaks badly if there's more than one network interface)
## let's make the same assumptions here
mac = subprocess.run (["ip -json link show |jq -r '.[] |select (.ifname != \"lo\") |.address'"], shell=True, capture_output=True, text=True)
self.mac = mac.stdout.strip()
return True
def enviaMensajeServidor (self, path, obj={}):
obj['iph'] = self.IPlocal ## Ip del ordenador
obj['ido'] = self.idordenador ## Identificador del ordenador
obj['npc'] = self.nombreordenador ## Nombre del ordenador
obj['idc'] = self.idcentro ## Identificador del centro
obj['ida'] = self.idaula ## Identificador del aula
res = self.REST.sendMessage ('/'.join ([self.name, path]), obj)
if (type (res) is not dict):
logger.error (f'response is not a dict ({res})')
return False
return res
## en C, esto envia una trama de respuesta al servidor. Devuelve un boolean
## en python, simplemente termina de construir la respuesta y la devuelve; no envía nada por la red. El caller la usa en return() para enviar implícitamente la respuesta
def respuestaEjecucionComando (self, cmd, herror, ids=None):
if ids: ## Existe seguimiento
cmd['ids'] = ids ## Añade identificador de la sesión
if 0 == herror: ## el comando terminó con resultado satisfactorio
cmd['res'] = 1
cmd['der'] = ''
else: ## el comando tuvo algún error
cmd['res'] = 2
cmd['der'] = self.tbErroresScripts[herror]
return cmd
def cargaPaginaWeb (self, url=None):
if (not url): url = self.urlMenu
os.system ('pkill -9 browser')
p = subprocess.Popen (['/usr/bin/browser', '-qws', url])
try:
p.wait (2) ## if the process dies before 2 seconds...
logger.error ('Error al ejecutar browser, return code "{}"'.format (p.returncode))
return False
except subprocess.TimeoutExpired:
pass
return True
def muestraMenu (self):
self.cargaPaginaWeb()
def muestraMensaje (self, idx):
self.cargaPaginaWeb (f'{self.urlMsg}?idx={idx}')
def LeeConfiguracion (self):
try:
parametroscfg = self.interfaceAdmin ('getConfiguration') ## Configuración de los Sistemas Operativos del cliente
except Exception as e:
logger.error (e)
logger.error ('No se ha podido recuperar la dirección IP del cliente')
return None
#logger.debug ('parametroscfg ({})'.format (parametroscfg))
return parametroscfg
def cfg2obj (self, cfg):
obj = []
ptrPar = cfg.split ('\n')
for line in ptrPar:
elem = {}
ptrCfg = line.split ('\t')
for item in ptrCfg:
if '=' not in item:
logger.warning (f'invalid item ({item})')
continue
k, v = item.split ('=', maxsplit=1)
elem[k] = v
obj.append (elem)
return obj
def onActivation (self):
if not os.path.exists ('/scripts/oginit'):
## no estamos en oglive, este modulo no debe cargarse
## esta lógica la saco de src/opengnsys/linux/operations.py, donde hay un if similar
raise Exception ('Refusing to load within an operating system')
self.pathinterface = None
self.IPlocal = None ## Ip del ordenador
self.mac = None ## MAC del ordenador
self.idordenador = None ## Identificador del ordenador
self.nombreordenador = None ## Nombre del ordenador
self.cache = None
self.idproautoexec = None
self.idcentro = None ## Identificador del centro
self.idaula = None ## Identificador del aula
self.pid_q = None ## for passing PIDs around
self.stdout_q = None ## for passing stdout
self.progress_jobs = {}
ogcore_scheme = os.environ.get ('OGAGENTCFG_OGCORE_SCHEME', 'https')
ogcore_ip = os.environ.get ('OGAGENTCFG_OGCORE_IP', '192.168.2.1')
ogcore_port = os.environ.get ('OGAGENTCFG_OGCORE_PORT', '8443')
urlmenu_scheme = os.environ.get ('OGAGENTCFG_URLMENU_SCHEME', 'https')
urlmenu_ip = os.environ.get ('OGAGENTCFG_URLMENU_IP', '192.168.2.1')
urlmenu_port = os.environ.get ('OGAGENTCFG_URLMENU_PORT', '8443')
ogcore_ip_port = ':'.join (map (str, filter (None, [ogcore_ip, ogcore_port ])))
urlmenu_ip_port = ':'.join (map (str, filter (None, [urlmenu_ip, urlmenu_port])))
try:
url = self.service.config.get (self.name, 'remote')
loglevel = self.service.config.get (self.name, 'log')
self.pathinterface = self.service.config.get (self.name, 'pathinterface')
self.urlMenu = self.service.config.get (self.name, 'urlMenu')
self.urlMsg = self.service.config.get (self.name, 'urlMsg')
url = url.format (ogcore_scheme, ogcore_ip_port)
self.urlMenu = self.urlMenu.format (urlmenu_scheme, urlmenu_ip_port)
except NoOptionError as e:
logger.error ("Configuration error: {}".format (e))
raise e
logger.setLevel (loglevel)
self.REST = REST (url)
if not self.tomaIPlocal():
raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo')
if not self.tomaMAClocal():
raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo')
threading.Thread (name='monitoring_thread', target=self.mon, daemon=True).start()
def _long_running_job (self, name, f, args):
any_job_running = False
for k in self.thread_list:
if self.thread_list[k]['running']:
any_job_running = True
break
if any_job_running:
logger.info ('some job is already running, refusing to launch another one')
return { 'job_id': None, 'message': 'some job is already running, refusing to launch another one' }
job_id = '{}-{}'.format (name, ''.join (random.choice ('0123456789abcdef') for _ in range (8)))
import queue
self.pid_q = queue.Queue() ## a single queue works for us because we never have more than one long_running_job at the same time
self.stdout_q = queue.Queue()
self.thread_list[job_id] = {
'thread': ThreadWithResult (target=f, args=(self.pid_q, self.stdout_q) + args),
'starttime': time.time(),
'child_pid': None,
'running': True,
'result': None
}
self.thread_list[job_id]['thread'].start()
return { 'job_id': job_id }
## para matar threads tengo lo siguiente:
## - aqui en _long_running_job meto una cola en self.pid_q
## - (self.pid_q fue inicializado a None al instanciar el objeto, para evitar error "objeto no tiene 'pid_q'")
## - en el thread_list también tengo un child_pid para almacenar el pid de los procesos hijos
## - al crear el ThreadWithResult le paso la cola, y luego en run() la recojo y la meto en el self.pid_q del thread
## - en interfaceAdmin() al hacer subprocess.Popen(), recojo el pid y lo escribo en la queue
## - en mon() recojo pids de la queue y los meto en thread_list 'child_pid'
## - algunas funciones llaman a interfaceAdmin más de una vez, y escriben más de un pid en la cola, y en mon() voy recogiendo y actualizando
## - por ejemplo EjecutarScript llama a interfaceAdmin() y luego llama a LeeConfiguracion() el cual llama a interfaceAdmin() otra vez
## - y cuando nos llamen a KillJob, terminamos en killer() el cual coge el 'child_pid' y zas
## - pero a lo mejor el child ya terminó
## - o a lo mejor el KillJob nos llegó demasiado pronto y todavía no hubo ningún child
##
## $ curl --insecure -X POST --data '{"nfn":"EjecutarScript","scp":"cd /usr; sleep 30; pwd; ls","ids":"0"}' https://192.168.2.199:8000/ogAdmClient/EjecutarScript
## {"job_id": "EjecutarScript-333feb3f"}
## $ curl --insecure -X POST --data '{"job_id":"EjecutarScript-333feb3f"}' https://192.168.2.199:8000/ogAdmClient/KillJob
##
## funciona bien, excepto que el PID no muere xD, ni siquiera haciendo subprocess.run('kill')
## para mostrar el progreso de los jobs reutilizo la misma infra
## una cola self.stdout_q
## en interfaceAdmin escribo la stdout parcial que ya venia recogiendo
## mon() lo recoge y le hace un POST a ogcore

View File

@ -96,8 +96,8 @@ class ServerWorker(object):
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]))
except:
raise Exception ({ '_httpcode': 404, '_msg': f'{path[0]}: method not found' })
return operation(path[1:], getParams, postParams, server)

View File

@ -1,170 +0,0 @@
'''
Created on Jul 9, 2015
@author: dkmaster
'''
# Pydev can't parse "six.moves.xxxx" because it is loaded lazy
from six.moves.socketserver import ThreadingMixIn # @UnresolvedImport
from six.moves.SimpleHTTPServer import SimpleHTTPRequestHandler # @UnresolvedImport
from six.moves.BaseHTTPServer import HTTPServer # @UnresolvedImport
from six.moves.urllib.parse import unquote # @UnresolvedImport
import json
import threading
import ssl
import os.path
import tempfile
# For testing
# --------------------
CERTFILE = 'UDSActor.pem'
def createSelfSignedCert(force=False):
certFile = os.path.join(tempfile.gettempdir(), CERTFILE)
if os.path.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
# --------------
class HTTPServerHandler(SimpleHTTPRequestHandler):
service = None
protocol_version = 'HTTP/1.1'
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 = {}
return (path, params)
def do_GET(self):
path, params = self.parseUrl()
self.sendJsonResponse({'path': path, 'params': params})
def do_POST(self):
path, getParams = self.parseUrl()
# Now post parameters, that are in JSON format
class HTTPThreadingServer(ThreadingMixIn, HTTPServer):
pass
class HTTPServerThread(threading.Thread):
def __init__(self, address, service):
super(self.__class__, self).__init__()
HTTPServerHandler.service = service
self.certFile = createSelfSignedCert()
self.server = HTTPThreadingServer(address, HTTPServerHandler)
self.server.socket = ssl.wrap_socket(self.server.socket, certfile=self.certFile, server_side=True)
def getServerUrl(self):
return 'https://{}:{}/{}'.format(self.server.server_address[0], self.server.server_address[1], HTTPServerHandler.uuid)
def stop(self):
self.server.shutdown()
def run(self):
self.server.serve_forever()
if __name__ == '__main__':
thr = HTTPServerThread(('0.0.0.0', 8000), None)
print('Server started: {}'.format(thr))
thr.start()

View File

@ -1,38 +0,0 @@
#!/usr/bin/env python
# -*- 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 opengnsys.workers import ClientWorker
class Sample1(ClientWorker):
name = 'Sample1'

View File

@ -1,2 +0,0 @@
# Module must be imported on package, so we can initialize and load it
from .sample1 import Sample1

View File

@ -1,40 +0,0 @@
#!/usr/bin/env python
# -*- 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 opengnsys.workers import ServerWorker
from .sample_pkg import test
class Sample1(ServerWorker):
name='Sample1'

View File

@ -1,36 +0,0 @@
#!/usr/bin/env python
# -*- 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
'''
def test():
return 'Test'

View File

@ -1,210 +0,0 @@
# -*- 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
# Pydev can't parse "six.moves.xxxx" because it is loaded lazy
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
import logging
from tempfile import gettempdir
from os.path import exists, join
logger = logging.getLogger(__name__)
CERTFILE = 'OGTestServer.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
class HTTPServerHandler(BaseHTTPRequestHandler):
service = None
protocol_version = 'HTTP/1.0'
server_version = 'OpenGnsys Test REST 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 = {}
return (path, params)
def do_GET(self):
path, params = self.parseUrl()
self.sendJsonResponse({'path': path, 'params': params})
def do_POST(self):
path, getParams = self.parseUrl()
# Now post parameters, that are in JSON format
self.sendJsonResponse({'path': path, 'params': getParams})
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
self.certFile = createSelfSignedCert()
self.server = HTTPThreadingServer(address, HTTPServerHandler)
self.server.socket = ssl.wrap_socket(self.server.socket, certfile=self.certFile, server_side=True)
logger.info('Initialized HTTPS Server thread on {}'.format(address))
def getServerUrl(self):
return 'https://{}:{}/{}'.format(self.server.server_address[0], self.server.server_address[1], HTTPServerHandler.uuid)
def stop(self):
self.server.shutdown()
def run(self):
self.server.serve_forever()
if __name__ == '__main__':
logging.basicConfig(
filename='/tmp/restserver.log',
filemode='w',
format='%(levelname)s %(asctime)s %(message)s',
level=logging.DEBUG
)
thr = HTTPServerThread(('0.0.0.0', 9999), None)
print('Server started: {}'.format(thr))
thr.run()

View File

@ -17,11 +17,13 @@ def update_version():
with fileinput.FileInput ('about-dialog.ui', inplace=True) as file:
for line in file:
print (line.replace ('Version [^<]*', f'Version {version}'), end='')
new = re.sub (r'Version [^<]*', 'Version {}'.format(version), line)
print (new, end='')
with fileinput.FileInput ('opengnsys/__init__.py', inplace=True) as file:
for line in file:
print(line.replace ('VERSION=.*', f"VERSION='{version}'"), end='')
new = re.sub (r'VERSION=.*', "VERSION='{}'".format(version), line)
print (new, end='')
with open ('../windows/VERSION', 'w') as outfile:
outfile.write (win_version + '\n')

View File

@ -33,7 +33,7 @@ setx PATH "C:\Python312;C:\Python312\Scripts;%PATH%"
python -m pip install --upgrade wheel pip
pip install -r F:\src\requirements.txt
setup\nsis-install.exe
setup\nsis-install.exe /S
powershell -command "Expand-Archive setup\NSIS_Simple_Firewall_Plugin_1.20.zip nsis-fp"
copy nsis-fp\SimpleFC.dll "C:\Program Files (x86)\NSIS\Plugins\x86-ansi\"