meshcentral.js 311 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317
  1. /**
  2. * @description MeshCentral main module
  3. * @author Ylian Saint-Hilaire
  4. * @copyright Intel Corporation 2018-2022
  5. * @license Apache-2.0
  6. * @version v0.0.1
  7. */
  8. /*xjslint node: true */
  9. /*xjslint plusplus: true */
  10. /*xjslint maxlen: 256 */
  11. /*jshint node: true */
  12. /*jshint strict: false */
  13. /*jshint esversion: 6 */
  14. "use strict";
  15. const common = require('./common.js');
  16. // If app metrics is available
  17. if (process.argv[2] == '--launch') { try { require('appmetrics-dash').monitor({ url: '/', title: 'MeshCentral', port: 88, host: '127.0.0.1' }); } catch (ex) { } }
  18. function CreateMeshCentralServer(config, args) {
  19. const obj = {};
  20. obj.db = null;
  21. obj.webserver = null; // HTTPS main web server, typically on port 443
  22. obj.redirserver = null; // HTTP relay web server, typically on port 80
  23. obj.mpsserver = null; // Intel AMT CIRA server, typically on port 4433
  24. obj.mqttbroker = null; // MQTT server, not is not often used
  25. obj.swarmserver = null; // Swarm server, this is used only to update older MeshCentral v1 agents
  26. obj.smsserver = null; // SMS server, used to send user SMS messages
  27. obj.msgserver = null; // Messaging server, used to sent used messages
  28. obj.amtEventHandler = null;
  29. obj.pluginHandler = null;
  30. obj.amtScanner = null;
  31. obj.amtManager = null; // Intel AMT manager, used to oversee all Intel AMT devices, activate them and sync policies
  32. obj.meshScanner = null;
  33. obj.taskManager = null;
  34. obj.letsencrypt = null; // Let's encrypt server, used to get and renew TLS certificates
  35. obj.eventsDispatch = {};
  36. obj.fs = require('fs');
  37. obj.path = require('path');
  38. obj.crypto = require('crypto');
  39. obj.exeHandler = require('./exeHandler.js');
  40. obj.platform = require('os').platform();
  41. obj.args = args;
  42. obj.common = common;
  43. obj.configurationFiles = null;
  44. obj.certificates = null;
  45. obj.connectivityByNode = {}; // This object keeps a list of all connected CIRA and agents, by nodeid->value (value: 1 = Agent, 2 = CIRA, 4 = AmtDirect)
  46. obj.peerConnectivityByNode = {}; // This object keeps a list of all connected CIRA and agents of peers, by serverid->nodeid->value (value: 1 = Agent, 2 = CIRA, 4 = AmtDirect)
  47. obj.debugSources = [];
  48. obj.debugRemoteSources = null;
  49. obj.config = config; // Configuration file
  50. obj.dbconfig = {}; // Persistance values, loaded from database
  51. obj.certificateOperations = null;
  52. obj.defaultMeshCmd = null;
  53. obj.defaultMeshCores = {};
  54. obj.defaultMeshCoresDeflate = {};
  55. obj.defaultMeshCoresHash = {};
  56. obj.meshToolsBinaries = {}; // Mesh Tools Binaries, ToolName --> { hash:(sha384 hash), size:(binary size), path:(binary path) }
  57. obj.meshAgentBinaries = {}; // Mesh Agent Binaries, Architecture type --> { hash:(sha384 hash), size:(binary size), path:(binary path) }
  58. obj.meshAgentInstallScripts = {}; // Mesh Install Scripts, Script ID -- { hash:(sha384 hash), size:(binary size), path:(binary path) }
  59. obj.multiServer = null;
  60. obj.ipKvmManager = null;
  61. obj.maintenanceTimer = null;
  62. obj.serverId = null;
  63. obj.serverKey = Buffer.from(obj.crypto.randomBytes(48), 'binary');
  64. obj.loginCookieEncryptionKey = null;
  65. obj.invitationLinkEncryptionKey = null;
  66. obj.serverSelfWriteAllowed = true;
  67. obj.serverStatsCounter = Math.floor(Math.random() * 1000);
  68. obj.taskLimiter = obj.common.createTaskLimiterQueue(50, 20, 60); // (maxTasks, maxTaskTime, cleaningInterval) This is a task limiter queue to smooth out server work.
  69. obj.agentUpdateBlockSize = 65531; // MeshAgent update block size
  70. obj.serverWarnings = []; // List of warnings that should be shown to administrators
  71. obj.cookieUseOnceTable = {}; // List of cookies that are already expired
  72. obj.cookieUseOnceTableCleanCounter = 0; // Clean the cookieUseOnceTable each 20 additions
  73. obj.firstStats = true; // True until this server saves it's not stats to the database
  74. // Server version
  75. obj.currentVer = null;
  76. function getCurrentVersion() { try { obj.currentVer = JSON.parse(obj.fs.readFileSync(obj.path.join(__dirname, 'package.json'), 'utf8')).version; } catch (ex) { } return obj.currentVer; } // Fetch server version
  77. getCurrentVersion();
  78. // Setup the default configuration and files paths
  79. if ((__dirname.endsWith('/node_modules/meshcentral')) || (__dirname.endsWith('\\node_modules\\meshcentral')) || (__dirname.endsWith('/node_modules/meshcentral/')) || (__dirname.endsWith('\\node_modules\\meshcentral\\'))) {
  80. obj.parentpath = obj.path.join(__dirname, '../..');
  81. obj.datapath = obj.path.join(__dirname, '../../meshcentral-data');
  82. obj.filespath = obj.path.join(__dirname, '../../meshcentral-files');
  83. obj.backuppath = obj.path.join(__dirname, '../../meshcentral-backups');
  84. obj.recordpath = obj.path.join(__dirname, '../../meshcentral-recordings');
  85. obj.webViewsPath = obj.path.join(__dirname, 'views');
  86. obj.webPublicPath = obj.path.join(__dirname, 'public');
  87. obj.webEmailsPath = obj.path.join(__dirname, 'emails');
  88. if (obj.fs.existsSync(obj.path.join(__dirname, '../../meshcentral-web/views'))) { obj.webViewsOverridePath = obj.path.join(__dirname, '../../meshcentral-web/views'); }
  89. if (obj.fs.existsSync(obj.path.join(__dirname, '../../meshcentral-web/public'))) { obj.webPublicOverridePath = obj.path.join(__dirname, '../../meshcentral-web/public'); }
  90. if (obj.fs.existsSync(obj.path.join(__dirname, '../../meshcentral-web/emails'))) { obj.webEmailsOverridePath = obj.path.join(__dirname, '../../meshcentral-web/emails'); }
  91. } else {
  92. obj.parentpath = __dirname;
  93. obj.datapath = obj.path.join(__dirname, '../meshcentral-data');
  94. obj.filespath = obj.path.join(__dirname, '../meshcentral-files');
  95. obj.backuppath = obj.path.join(__dirname, '../meshcentral-backups');
  96. obj.recordpath = obj.path.join(__dirname, '../meshcentral-recordings');
  97. obj.webViewsPath = obj.path.join(__dirname, 'views');
  98. obj.webPublicPath = obj.path.join(__dirname, 'public');
  99. obj.webEmailsPath = obj.path.join(__dirname, 'emails');
  100. if (obj.fs.existsSync(obj.path.join(__dirname, '../meshcentral-web/views'))) { obj.webViewsOverridePath = obj.path.join(__dirname, '../meshcentral-web/views'); }
  101. if (obj.fs.existsSync(obj.path.join(__dirname, '../meshcentral-web/public'))) { obj.webPublicOverridePath = obj.path.join(__dirname, '../meshcentral-web/public'); }
  102. if (obj.fs.existsSync(obj.path.join(__dirname, '../meshcentral-web/emails'))) { obj.webEmailsOverridePath = obj.path.join(__dirname, '../meshcentral-web/emails'); }
  103. }
  104. // Clean up any temporary files
  105. const removeTime = new Date(Date.now()).getTime() - (30 * 60 * 1000); // 30 minutes
  106. const dir = obj.fs.readdir(obj.path.join(obj.filespath, 'tmp'), function (err, files) {
  107. if (err != null) return;
  108. for (var i in files) { try { const filepath = obj.path.join(obj.filespath, 'tmp', files[i]); if (obj.fs.statSync(filepath).mtime.getTime() < removeTime) { obj.fs.unlink(filepath, function () { }); } } catch (ex) { } }
  109. });
  110. // Look to see if data and/or file path is specified
  111. if (obj.config.settings && (typeof obj.config.settings.datapath == 'string')) { obj.datapath = obj.config.settings.datapath; }
  112. if (obj.config.settings && (typeof obj.config.settings.filespath == 'string')) { obj.filespath = obj.config.settings.filespath; }
  113. // Create data and files folders if needed
  114. try { obj.fs.mkdirSync(obj.datapath); } catch (ex) { }
  115. try { obj.fs.mkdirSync(obj.filespath); } catch (ex) { }
  116. // Windows Specific Code, setup service and event log
  117. obj.service = null;
  118. obj.servicelog = null;
  119. if (obj.platform == 'win32') {
  120. const nodewindows = require('node-windows');
  121. obj.service = nodewindows.Service;
  122. const eventlogger = nodewindows.EventLogger;
  123. obj.servicelog = new eventlogger('MeshCentral');
  124. }
  125. // Start the Meshcentral server
  126. obj.Start = function () {
  127. var i;
  128. try { require('./pass').hash('test', function () { }, 0); } catch (ex) { console.log('Old version of node, must upgrade.'); return; } // TODO: Not sure if this test works or not.
  129. // Check for invalid arguments
  130. const validArguments = ['_', 'user', 'port', 'aliasport', 'mpsport', 'mpsaliasport', 'redirport', 'rediraliasport', 'cert', 'mpscert', 'deletedomain', 'deletedefaultdomain', 'showall', 'showusers', 'showitem', 'listuserids', 'showusergroups', 'shownodes', 'showallmeshes', 'showmeshes', 'showevents', 'showsmbios', 'showpower', 'clearpower', 'showiplocations', 'help', 'exactports', 'xinstall', 'xuninstall', 'install', 'uninstall', 'start', 'stop', 'restart', 'debug', 'filespath', 'datapath', 'noagentupdate', 'launch', 'noserverbackup', 'mongodb', 'mongodbcol', 'wanonly', 'lanonly', 'nousers', 'mpspass', 'ciralocalfqdn', 'dbexport', 'dbexportmin', 'dbimport', 'dbmerge', 'dbfix', 'dbencryptkey', 'selfupdate', 'tlsoffload', 'usenodedefaulttlsciphers', 'tlsciphers', 'userallowedip', 'userblockedip', 'swarmallowedip', 'agentallowedip', 'agentblockedip', 'fastcert', 'swarmport', 'logintoken', 'logintokenkey', 'logintokengen', 'mailtokengen', 'admin', 'unadmin', 'sessionkey', 'sessiontime', 'minify', 'minifycore', 'dblistconfigfiles', 'dbshowconfigfile', 'dbpushconfigfiles', 'oldencrypt', 'dbpullconfigfiles', 'dbdeleteconfigfiles', 'vaultpushconfigfiles', 'vaultpullconfigfiles', 'vaultdeleteconfigfiles', 'configkey', 'loadconfigfromdb', 'npmpath', 'serverid', 'recordencryptionrecode', 'vault', 'token', 'unsealkey', 'name', 'log', 'dbstats', 'translate', 'createaccount', 'setuptelegram', 'resetaccount', 'pass', 'removesubdomain', 'adminaccount', 'domain', 'email', 'configfile', 'maintenancemode', 'nedbtodb', 'removetestagents', 'agentupdatetest', 'hashpassword', 'hashpass', 'indexmcrec', 'mpsdebug', 'dumpcores', 'dev', 'mysql', 'mariadb', 'trustedproxy'];
  131. for (var arg in obj.args) { obj.args[arg.toLocaleLowerCase()] = obj.args[arg]; if (validArguments.indexOf(arg.toLocaleLowerCase()) == -1) { console.log('Invalid argument "' + arg + '", use --help.'); return; } }
  132. const ENVVAR_PREFIX = "meshcentral_"
  133. let envArgs = []
  134. for (let [envvar, envval] of Object.entries(process.env)) {
  135. if (envvar.toLocaleLowerCase().startsWith(ENVVAR_PREFIX)) {
  136. let argname = envvar.slice(ENVVAR_PREFIX.length).toLocaleLowerCase()
  137. if (!!argname && !(validArguments.indexOf(argname) == -1)) {
  138. envArgs = envArgs.concat([`--${argname}`, envval])
  139. }
  140. }
  141. }
  142. envArgs = require('minimist')(envArgs)
  143. obj.args = Object.assign(envArgs, obj.args)
  144. if (obj.args.mongodb == true) { console.log('Must specify: --mongodb [connectionstring] \r\nSee https://docs.mongodb.com/manual/reference/connection-string/ for MongoDB connection string.'); return; }
  145. if (obj.args.mysql == true) { console.log('Must specify: --mysql [connectionstring] \r\nExample mysql://user:password@127.0.0.1:3306/database'); return; }
  146. if (obj.args.mariadb == true) { console.log('Must specify: --mariadb [connectionstring] \r\nExample mariadb://user:password@127.0.0.1:3306/database'); return; }
  147. for (i in obj.config.settings) { obj.args[i] = obj.config.settings[i]; } // Place all settings into arguments, arguments have already been placed into settings so arguments take precedence.
  148. if ((obj.args.help == true) || (obj.args['?'] == true)) {
  149. console.log('MeshCentral v' + getCurrentVersion() + ', remote computer management web portal.');
  150. console.log('This software is open source under Apache 2.0 license.');
  151. console.log('Details at: https://www.meshcentral.com\r\n');
  152. if ((obj.platform == 'win32') || (obj.platform == 'linux')) {
  153. console.log('Run as a background service');
  154. console.log(' --install/uninstall Install MeshCentral as a background service.');
  155. console.log(' --start/stop/restart Control MeshCentral background service.');
  156. console.log('');
  157. console.log('Run standalone, console application');
  158. }
  159. console.log(' --user [username] Always login as [username] if account exists.');
  160. console.log(' --port [number] Web server port number.');
  161. console.log(' --redirport [number] Creates an additional HTTP server to redirect users to the HTTPS server.');
  162. console.log(' --exactports Server must run with correct ports or exit.');
  163. console.log(' --noagentupdate Server will not update mesh agent native binaries.');
  164. console.log(' --nedbtodb Transfer all NeDB records into current database.');
  165. console.log(' --listuserids Show a list of a user identifiers in the database.');
  166. console.log(' --cert [name], (country), (org) Create a web server certificate with [name] server name.');
  167. console.log(' country and organization can optionally be set.');
  168. console.log('');
  169. console.log('Server recovery commands, use only when MeshCentral is offline.');
  170. console.log(' --createaccount [userid] Create a new user account.');
  171. console.log(' --resetaccount [userid] Unlock an account, disable 2FA and set a new account password.');
  172. console.log(' --adminaccount [userid] Promote account to site administrator.');
  173. return;
  174. }
  175. // Fix a NeDB database
  176. if (obj.args.dbfix) {
  177. var lines = null, badJsonCount = 0, fieldNames = [], fixedDb = [];
  178. try { lines = obj.fs.readFileSync(obj.getConfigFilePath(obj.args.dbfix), { encoding: 'utf8' }).split('\n'); } catch (ex) { console.log('Invalid file: ' + obj.args.dbfix + ': ' + ex); process.exit(); }
  179. for (var i = 0; i < lines.length; i++) {
  180. var x = null;
  181. try { x = JSON.parse(lines[i]); } catch (ex) { badJsonCount++; }
  182. if (x != null) { fixedDb.push(lines[i]); for (var j in x) { if (fieldNames.indexOf(j) == -1) { fieldNames.push(j); } } }
  183. }
  184. console.log('Lines: ' + lines.length + ', badJSON: ' + badJsonCount + ', Feilds: ' + fieldNames);
  185. obj.fs.writeFileSync(obj.getConfigFilePath(obj.args.dbfix) + '-fixed', fixedDb.join('\n'), { encoding: 'utf8' });
  186. return;
  187. }
  188. // Check for invalid cert name
  189. if ((obj.args.cert != null) && ((typeof obj.args.cert != "string") || (obj.args.cert.indexOf('@') >= 0) || (obj.args.cert.indexOf('/') >= 0) || (obj.args.cert.indexOf(':') >= 0))) { console.log("Invalid certificate name"); process.exit(); return; }
  190. // Perform a password hash
  191. if (obj.args.hashpassword) { require('./pass').hash(obj.args.hashpassword, function (err, salt, hash, tag) { console.log(salt + ',' + hash); process.exit(); }); return; }
  192. // Dump to mesh cores
  193. if (obj.args.dumpcores) { obj.updateMeshCore(function () { console.log('Done.'); }, true); return; }
  194. // Setup Telegram
  195. if (obj.args.setuptelegram) { require('./meshmessaging.js').SetupTelegram(obj); return; }
  196. // Perform web site translations into different languages
  197. if (obj.args.translate) {
  198. // Check NodeJS version
  199. const NodeJSVer = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
  200. if (NodeJSVer < 8) { console.log("Translation feature requires Node v8 or above, current version is " + process.version + "."); process.exit(); return; }
  201. // Check if translate.json is in the "meshcentral-data" folder, if so use that and translate default pages.
  202. var translationFile = null, customTranslation = false;
  203. if (require('fs').existsSync(obj.path.join(obj.datapath, 'translate.json'))) { translationFile = obj.path.join(obj.datapath, 'translate.json'); console.log("Using translate.json in meshcentral-data."); customTranslation = true; }
  204. if (translationFile == null) { if (require('fs').existsSync(obj.path.join(__dirname, 'translate', 'translate.json'))) { translationFile = obj.path.join(__dirname, 'translate', 'translate.json'); console.log("Using default translate.json."); } }
  205. if (translationFile == null) { console.log("Unable to find translate.json."); process.exit(); return; }
  206. // Perform translation operations
  207. var didSomething = false;
  208. process.chdir(obj.path.join(__dirname, 'translate'));
  209. const translateEngine = require('./translate/translate.js')
  210. if (customTranslation == true) {
  211. // Translate all of the default files using custom translation file
  212. translateEngine.startEx(['', '', 'minifyall']);
  213. translateEngine.startEx(['', '', 'translateall', translationFile]);
  214. translateEngine.startEx(['', '', 'extractall', translationFile]);
  215. didSomething = true;
  216. } else {
  217. // Translate all of the default files
  218. translateEngine.startEx(['', '', 'minifyall']);
  219. translateEngine.startEx(['', '', 'translateall']);
  220. translateEngine.startEx(['', '', 'extractall']);
  221. didSomething = true;
  222. }
  223. // Check if "meshcentral-web" exists, if so, translate all pages in that folder.
  224. if (obj.webViewsOverridePath != null) {
  225. didSomething = true;
  226. var files = obj.fs.readdirSync(obj.webViewsOverridePath);
  227. for (var i in files) {
  228. var file = obj.path.join(obj.webViewsOverridePath, files[i]);
  229. if (file.endsWith('.handlebars') && !file.endsWith('-min.handlebars')) {
  230. translateEngine.startEx(['', '', 'minify', file]);
  231. }
  232. }
  233. files = obj.fs.readdirSync(obj.webViewsOverridePath);
  234. for (var i in files) {
  235. var file = obj.path.join(obj.webViewsOverridePath, files[i]);
  236. if (file.endsWith('.handlebars') && !file.endsWith('-min.handlebars')) {
  237. translateEngine.startEx(['', '', 'translate', '*', translationFile, file, '--subdir:translations']);
  238. }
  239. }
  240. }
  241. // Check domains and see if "meshcentral-web-DOMAIN" exists, if so, translate all pages in that folder
  242. for (i in obj.config.domains) {
  243. if (i == "") continue;
  244. var path = obj.path.join(obj.datapath, '..', 'meshcentral-web-' + i, 'views');
  245. if (require('fs').existsSync(path)) {
  246. didSomething = true;
  247. var files = obj.fs.readdirSync(path);
  248. for (var a in files) {
  249. var file = obj.path.join(path, files[a]);
  250. if (file.endsWith('.handlebars') && !file.endsWith('-min.handlebars')) {
  251. translateEngine.startEx(['', '', 'minify', file]);
  252. }
  253. }
  254. files = obj.fs.readdirSync(path);
  255. for (var a in files) {
  256. var file = obj.path.join(path, files[a]);
  257. if (file.endsWith('.handlebars') && !file.endsWith('-min.handlebars')) {
  258. translateEngine.startEx(['', '', 'translate', '*', translationFile, file, '--subdir:translations']);
  259. }
  260. }
  261. }
  262. }
  263. /*
  264. if (obj.webPublicOverridePath != null) {
  265. didSomething = true;
  266. var files = obj.fs.readdirSync(obj.webPublicOverridePath);
  267. for (var i in files) {
  268. var file = obj.path.join(obj.webPublicOverridePath, files[i]);
  269. if (file.endsWith('.htm') && !file.endsWith('-min.htm')) {
  270. translateEngine.startEx(['', '', 'translate', '*', translationFile, file, '--subdir:translations']);
  271. }
  272. }
  273. }
  274. */
  275. if (didSomething == false) { console.log("Nothing to do."); }
  276. console.log('Finished Translating.')
  277. process.exit();
  278. return;
  279. }
  280. // Setup the Node+NPM path if possible, this makes it possible to update the server even if NodeJS and NPM are not in default paths.
  281. if (obj.args.npmpath == null) {
  282. try {
  283. var nodepath = process.argv[0];
  284. var npmpath = obj.path.join(obj.path.dirname(process.argv[0]), 'npm');
  285. if (obj.fs.existsSync(nodepath) && obj.fs.existsSync(npmpath)) {
  286. if (nodepath.indexOf(' ') >= 0) { nodepath = '"' + nodepath + '"'; }
  287. if (npmpath.indexOf(' ') >= 0) { npmpath = '"' + npmpath + '"'; }
  288. if (obj.platform == 'win32') { obj.args.npmpath = npmpath; } else { obj.args.npmpath = (nodepath + ' ' + npmpath); }
  289. }
  290. } catch (ex) { }
  291. }
  292. // Linux background service systemd handling
  293. if (obj.platform == 'linux') {
  294. if (obj.args.install == true) {
  295. // Install MeshCentral in Systemd
  296. console.log('Installing MeshCentral as background Service...');
  297. var systemdConf = null;
  298. const userinfo = require('os').userInfo();
  299. if (require('fs').existsSync('/etc/systemd/system')) { systemdConf = '/etc/systemd/system/meshcentral.service'; }
  300. else if (require('fs').existsSync('/lib/systemd/system')) { systemdConf = '/lib/systemd/system/meshcentral.service'; }
  301. else if (require('fs').existsSync('/usr/lib/systemd/system')) { systemdConf = '/usr/lib/systemd/system/meshcentral.service'; }
  302. else { console.log('Unable to find systemd configuration folder.'); process.exit(); return; }
  303. console.log('Writing config file...');
  304. require('child_process').exec('which node', {}, function (error, stdout, stderr) {
  305. if ((error != null) || (stdout.indexOf('\n') == -1)) { console.log('ERROR: Unable to get node location: ' + error); process.exit(); return; }
  306. const nodePath = stdout.substring(0, stdout.indexOf('\n'));
  307. const config = '[Unit]\nDescription=MeshCentral Server\nWants=network-online.target\nAfter=network-online.target\n\n[Service]\nType=simple\nLimitNOFILE=1000000\nExecStart=' + nodePath + ' ' + __dirname + '/meshcentral\nWorkingDirectory=' + userinfo.homedir + '\nEnvironment=NODE_ENV=production\nUser=' + userinfo.username + '\nGroup=' + userinfo.username + '\nRestart=always\n# Restart service after 10 seconds if node service crashes\nRestartSec=10\n# Set port permissions capability\nAmbientCapabilities=cap_net_bind_service\n\n[Install]\nWantedBy=multi-user.target\n';
  308. require('child_process').exec('echo \"' + config + '\" | sudo tee ' + systemdConf, {}, function (error, stdout, stderr) {
  309. if ((error != null) && (error != '')) { console.log('ERROR: Unable to write config file: ' + error); process.exit(); return; }
  310. console.log('Enabling service...');
  311. require('child_process').exec('sudo systemctl enable meshcentral.service', {}, function (error, stdout, stderr) {
  312. if ((error != null) && (error != '')) { console.log('ERROR: Unable to enable MeshCentral as a service: ' + error); process.exit(); return; }
  313. if (stdout.length > 0) { console.log(stdout); }
  314. console.log('Starting service...');
  315. require('child_process').exec('sudo systemctl start meshcentral.service', {}, function (error, stdout, stderr) {
  316. if ((error != null) && (error != '')) { console.log('ERROR: Unable to start MeshCentral as a service: ' + error); process.exit(); return; }
  317. if (stdout.length > 0) { console.log(stdout); }
  318. console.log('Done.');
  319. });
  320. });
  321. });
  322. });
  323. return;
  324. } else if (obj.args.uninstall == true) {
  325. // Uninstall MeshCentral in Systemd
  326. console.log('Uninstalling MeshCentral background service...');
  327. var systemdConf = null;
  328. if (require('fs').existsSync('/etc/systemd/system')) { systemdConf = '/etc/systemd/system/meshcentral.service'; }
  329. else if (require('fs').existsSync('/lib/systemd/system')) { systemdConf = '/lib/systemd/system/meshcentral.service'; }
  330. else if (require('fs').existsSync('/usr/lib/systemd/system')) { systemdConf = '/usr/lib/systemd/system/meshcentral.service'; }
  331. else { console.log('Unable to find systemd configuration folder.'); process.exit(); return; }
  332. console.log('Stopping service...');
  333. require('child_process').exec('sudo systemctl stop meshcentral.service', {}, function (err, stdout, stderr) {
  334. if ((err != null) && (err != '')) { console.log('ERROR: Unable to stop MeshCentral as a service: ' + err); }
  335. if (stdout.length > 0) { console.log(stdout); }
  336. console.log('Disabling service...');
  337. require('child_process').exec('sudo systemctl disable meshcentral.service', {}, function (err, stdout, stderr) {
  338. if ((err != null) && (err != '')) { console.log('ERROR: Unable to disable MeshCentral as a service: ' + err); }
  339. if (stdout.length > 0) { console.log(stdout); }
  340. console.log('Removing config file...');
  341. require('child_process').exec('sudo rm ' + systemdConf, {}, function (err, stdout, stderr) {
  342. if ((err != null) && (err != '')) { console.log('ERROR: Unable to delete MeshCentral config file: ' + err); }
  343. console.log('Done.');
  344. });
  345. });
  346. });
  347. return;
  348. } else if (obj.args.start == true) {
  349. // Start MeshCentral in Systemd
  350. require('child_process').exec('sudo systemctl start meshcentral.service', {}, function (err, stdout, stderr) {
  351. if ((err != null) && (err != '')) { console.log('ERROR: Unable to start MeshCentral: ' + err); process.exit(); return; }
  352. console.log('Done.');
  353. });
  354. return;
  355. } else if (obj.args.stop == true) {
  356. // Stop MeshCentral in Systemd
  357. require('child_process').exec('sudo systemctl stop meshcentral.service', {}, function (err, stdout, stderr) {
  358. if ((err != null) && (err != '')) { console.log('ERROR: Unable to stop MeshCentral: ' + err); process.exit(); return; }
  359. console.log('Done.');
  360. });
  361. return;
  362. } else if (obj.args.restart == true) {
  363. // Restart MeshCentral in Systemd
  364. require('child_process').exec('sudo systemctl restart meshcentral.service', {}, function (err, stdout, stderr) {
  365. if ((err != null) && (err != '')) { console.log('ERROR: Unable to restart MeshCentral: ' + err); process.exit(); return; }
  366. console.log('Done.');
  367. });
  368. return;
  369. }
  370. }
  371. // FreeBSD background service handling, MUST USE SPAWN FOR SERVICE COMMANDS!
  372. if (obj.platform == 'freebsd') {
  373. if (obj.args.install == true) {
  374. // Install MeshCentral in rc.d
  375. console.log('Installing MeshCentral as background Service...');
  376. var systemdConf = "/usr/local/etc/rc.d/meshcentral";
  377. const userinfo = require('os').userInfo();
  378. console.log('Writing config file...');
  379. require('child_process').exec('which node', {}, function (error, stdout, stderr) {
  380. if ((error != null) || (stdout.indexOf('\n') == -1)) { console.log('ERROR: Unable to get node location: ' + error); process.exit(); return; }
  381. const nodePath = stdout.substring(0, stdout.indexOf('\n'));
  382. const config = '#!/bin/sh\n# MeshCentral FreeBSD Service Script\n# PROVIDE: meshcentral\n# REQUIRE: NETWORKING\n# KEYWORD: shutdown\n. /etc/rc.subr\nname=meshcentral\nuser=' + userinfo.username + '\nrcvar=meshcentral_enable\n: \\${meshcentral_enable:=\\"NO\\"}\n: \\${meshcentral_args:=\\"\\"}\npidfile=/var/run/meshcentral/meshcentral.pid\ncommand=\\"/usr/sbin/daemon\\"\nmeshcentral_chdir=\\"' + obj.parentpath + '\\"\ncommand_args=\\"-r -u \\${user} -P \\${pidfile} -S -T meshcentral -m 3 ' + nodePath + ' ' + __dirname + ' \\${meshcentral_args}\\"\nload_rc_config \\$name\nrun_rc_command \\"\\$1\\"\n';
  383. require('child_process').exec('echo \"' + config + '\" | tee ' + systemdConf + ' && chmod +x ' + systemdConf, {}, function (error, stdout, stderr) {
  384. if ((error != null) && (error != '')) { console.log('ERROR: Unable to write config file: ' + error); process.exit(); return; }
  385. console.log('Enabling service...');
  386. require('child_process').exec('sysrc meshcentral_enable="YES"', {}, function (error, stdout, stderr) {
  387. if ((error != null) && (error != '')) { console.log('ERROR: Unable to enable MeshCentral as a service: ' + error); process.exit(); return; }
  388. if (stdout.length > 0) { console.log(stdout); }
  389. console.log('Starting service...');
  390. const service = require('child_process').spawn('service', ['meshcentral', 'start']);
  391. service.stdout.on('data', function (data) { console.log(data.toString()); });
  392. service.stderr.on('data', function (data) { console.log(data.toString()); });
  393. service.on('exit', function (code) {
  394. console.log((code === 0) ? 'Done.' : 'ERROR: Unable to start MeshCentral as a service');
  395. process.exit(); // Must exit otherwise we just hang
  396. });
  397. });
  398. });
  399. });
  400. return;
  401. } else if (obj.args.uninstall == true) {
  402. // Uninstall MeshCentral in rc.d
  403. console.log('Uninstalling MeshCentral background service...');
  404. var systemdConf = "/usr/local/etc/rc.d/meshcentral";
  405. console.log('Stopping service...');
  406. const service = require('child_process').spawn('service', ['meshcentral', 'stop']);
  407. service.stdout.on('data', function (data) { console.log(data.toString()); });
  408. service.stderr.on('data', function (data) { console.log(data.toString()); });
  409. service.on('exit', function (code) {
  410. if (code !== 0) { console.log('ERROR: Unable to stop MeshCentral as a service'); }
  411. console.log('Disabling service...');
  412. require('child_process').exec('sysrc -x meshcentral_enable', {}, function (err, stdout, stderr) {
  413. if ((err != null) && (err != '')) { console.log('ERROR: Unable to disable MeshCentral as a service: ' + err); }
  414. if (stdout.length > 0) { console.log(stdout); }
  415. console.log('Removing config file...');
  416. require('child_process').exec('rm ' + systemdConf, {}, function (err, stdout, stderr) {
  417. if ((err != null) && (err != '')) { console.log('ERROR: Unable to delete MeshCentral config file: ' + err); }
  418. console.log('Done.');
  419. process.exit(); // Must exit otherwise we just hang
  420. });
  421. });
  422. });
  423. return;
  424. } else if (obj.args.start == true) {
  425. // Start MeshCentral in rc.d
  426. const service = require('child_process').spawn('service', ['meshcentral', 'start']);
  427. service.stdout.on('data', function (data) { console.log(data.toString()); });
  428. service.stderr.on('data', function (data) { console.log(data.toString()); });
  429. service.on('exit', function (code) {
  430. console.log((code === 0) ? 'Done.' : 'ERROR: Unable to start MeshCentral as a service: ' + error);
  431. process.exit(); // Must exit otherwise we just hang
  432. });
  433. return;
  434. } else if (obj.args.stop == true) {
  435. // Stop MeshCentral in rc.d
  436. const service = require('child_process').spawn('service', ['meshcentral', 'stop']);
  437. service.stdout.on('data', function (data) { console.log(data.toString()); });
  438. service.stderr.on('data', function (data) { console.log(data.toString()); });
  439. service.on('exit', function (code) {
  440. console.log((code === 0) ? 'Done.' : 'ERROR: Unable to stop MeshCentral as a service: ' + error);
  441. process.exit(); // Must exit otherwise we just hang
  442. });
  443. return;
  444. } else if (obj.args.restart == true) {
  445. // Restart MeshCentral in rc.d
  446. const service = require('child_process').spawn('service', ['meshcentral', 'restart']);
  447. service.stdout.on('data', function (data) { console.log(data.toString()); });
  448. service.stderr.on('data', function (data) { console.log(data.toString()); });
  449. service.on('exit', function (code) {
  450. console.log((code === 0) ? 'Done.' : 'ERROR: Unable to restart MeshCentral as a service: ' + error);
  451. process.exit(); // Must exit otherwise we just hang
  452. });
  453. return;
  454. }
  455. }
  456. // Index a recorded file
  457. if (obj.args.indexmcrec != null) {
  458. if (typeof obj.args.indexmcrec != 'string') {
  459. console.log('Usage: --indexmrec [filename.mcrec]');
  460. } else if (obj.fs.existsSync(obj.args.indexmcrec)) {
  461. console.log('Indexing file: ' + obj.args.indexmcrec);
  462. require(require('path').join(__dirname, 'mcrec.js')).indexFile(obj.args.indexmcrec);
  463. } else {
  464. console.log('Unable to find file: ' + obj.args.indexmcrec);
  465. }
  466. return;
  467. }
  468. // Windows background service handling
  469. if ((obj.platform == 'win32') && (obj.service != null)) {
  470. // Build MeshCentral parent path and Windows Service path
  471. var mcpath = __dirname;
  472. if (mcpath.endsWith('\\node_modules\\meshcentral') || mcpath.endsWith('/node_modules/meshcentral')) { mcpath = require('path').join(mcpath, '..', '..'); }
  473. const servicepath = obj.path.join(mcpath, 'WinService');
  474. // Check if we need to install, start, stop, remove ourself as a background service
  475. if (((obj.args.xinstall == true) || (obj.args.xuninstall == true) || (obj.args.start == true) || (obj.args.stop == true) || (obj.args.restart == true))) {
  476. var env = [], xenv = ['user', 'port', 'aliasport', 'mpsport', 'mpsaliasport', 'redirport', 'exactport', 'rediraliasport', 'debug'];
  477. for (i in xenv) { if (obj.args[xenv[i]] != null) { env.push({ name: 'mesh' + xenv[i], value: obj.args[xenv[i]] }); } } // Set some args as service environment variables.
  478. var serviceFilePath = null;
  479. if (obj.fs.existsSync(obj.path.join(servicepath, 'winservice.js'))) { serviceFilePath = obj.path.join(servicepath, 'winservice.js'); }
  480. else if (obj.fs.existsSync(obj.path.join(__dirname, '../WinService/winservice.js'))) { serviceFilePath = obj.path.join(__dirname, '../WinService/winservice.js'); }
  481. else if (obj.fs.existsSync(obj.path.join(__dirname, 'winservice.js'))) { serviceFilePath = obj.path.join(__dirname, 'winservice.js'); }
  482. if (serviceFilePath == null) { console.log('Unable to find winservice.js'); return; }
  483. const svc = new obj.service({ name: 'MeshCentral', description: 'MeshCentral Remote Management Server', script: servicepath, env: env, wait: 2, grow: 0.5 });
  484. svc.on('install', function () { console.log('MeshCentral service installed.'); svc.start(); });
  485. svc.on('uninstall', function () { console.log('MeshCentral service uninstalled.'); process.exit(); });
  486. svc.on('start', function () { console.log('MeshCentral service started.'); process.exit(); });
  487. svc.on('stop', function () { console.log('MeshCentral service stopped.'); if (obj.args.stop) { process.exit(); } if (obj.args.restart) { console.log('Holding 5 seconds...'); setTimeout(function () { svc.start(); }, 5000); } });
  488. svc.on('alreadyinstalled', function () { console.log('MeshCentral service already installed.'); process.exit(); });
  489. svc.on('invalidinstallation', function () { console.log('Invalid MeshCentral service installation.'); process.exit(); });
  490. if (obj.args.xinstall == true) { try { svc.install(); } catch (ex) { logException(ex); } }
  491. if (obj.args.stop == true || obj.args.restart == true) { try { svc.stop(); } catch (ex) { logException(ex); } }
  492. if (obj.args.start == true) { try { svc.start(); } catch (ex) { logException(ex); } }
  493. if (obj.args.xuninstall == true) { try { svc.uninstall(); } catch (ex) { logException(ex); } }
  494. return;
  495. }
  496. // Windows service install using the external winservice.js
  497. if (obj.args.install == true) {
  498. console.log('Installing MeshCentral as Windows Service...');
  499. if (obj.fs.existsSync(servicepath) == false) { try { obj.fs.mkdirSync(servicepath); } catch (ex) { console.log('ERROR: Unable to create WinService folder: ' + ex); process.exit(); return; } }
  500. try { obj.fs.createReadStream(obj.path.join(__dirname, 'winservice.js')).pipe(obj.fs.createWriteStream(obj.path.join(servicepath, 'winservice.js'))); } catch (ex) { console.log('ERROR: Unable to copy winservice.js: ' + ex); process.exit(); return; }
  501. require('child_process').exec('node winservice.js --install', { maxBuffer: 512000, timeout: 120000, cwd: servicepath }, function (error, stdout, stderr) {
  502. if ((error != null) && (error != '')) { console.log('ERROR: Unable to install MeshCentral as a service: ' + error); process.exit(); return; }
  503. console.log(stdout);
  504. });
  505. return;
  506. } else if (obj.args.uninstall == true) {
  507. console.log('Uninstalling MeshCentral Windows Service...');
  508. if (obj.fs.existsSync(servicepath) == true) {
  509. require('child_process').exec('node winservice.js --uninstall', { maxBuffer: 512000, timeout: 120000, cwd: servicepath }, function (error, stdout, stderr) {
  510. if ((error != null) && (error != '')) { console.log('ERROR: Unable to uninstall MeshCentral service: ' + error); process.exit(); return; }
  511. console.log(stdout);
  512. try { obj.fs.unlinkSync(obj.path.join(servicepath, 'winservice.js')); } catch (ex) { }
  513. try { obj.fs.rmdirSync(servicepath); } catch (ex) { }
  514. });
  515. } else if (obj.fs.existsSync(obj.path.join(__dirname, '../WinService')) == true) {
  516. require('child_process').exec('node winservice.js --uninstall', { maxBuffer: 512000, timeout: 120000, cwd: obj.path.join(__dirname, '../WinService') }, function (error, stdout, stderr) {
  517. if ((error != null) && (error != '')) { console.log('ERROR: Unable to uninstall MeshCentral service: ' + error); process.exit(); return; }
  518. console.log(stdout);
  519. try { obj.fs.unlinkSync(obj.path.join(__dirname, '../WinService/winservice.js')); } catch (ex) { }
  520. try { obj.fs.rmdirSync(obj.path.join(__dirname, '../WinService')); } catch (ex) { }
  521. });
  522. } else {
  523. require('child_process').exec('node winservice.js --uninstall', { maxBuffer: 512000, timeout: 120000, cwd: __dirname }, function (error, stdout, stderr) {
  524. if ((error != null) && (error != '')) { console.log('ERROR: Unable to uninstall MeshCentral service: ' + error); process.exit(); return; }
  525. console.log(stdout);
  526. });
  527. }
  528. return;
  529. }
  530. }
  531. // If "--launch" is in the arguments, launch now
  532. if (obj.args.launch) {
  533. if (obj.args.vault) { obj.StartVault(); } else { obj.StartEx(); }
  534. } else {
  535. // if "--launch" is not specified, launch the server as a child process.
  536. const startArgs = [];
  537. for (i in process.argv) {
  538. if (i > 0) {
  539. const arg = process.argv[i];
  540. if ((arg.length > 0) && ((arg.indexOf(' ') >= 0) || (arg.indexOf('&') >= 0))) { startArgs.push(arg); } else { startArgs.push(arg); }
  541. }
  542. }
  543. startArgs.push('--launch', process.pid);
  544. obj.launchChildServer(startArgs);
  545. }
  546. };
  547. // Launch MeshCentral as a child server and monitor it.
  548. obj.launchChildServer = function (startArgs) {
  549. const child_process = require('child_process');
  550. try { if (process.traceDeprecation === true) { startArgs.unshift('--trace-deprecation'); } } catch (ex) { }
  551. try { if (process.traceProcessWarnings === true) { startArgs.unshift('--trace-warnings'); } } catch (ex) { }
  552. childProcess = child_process.execFile(process.argv[0], startArgs, { maxBuffer: Infinity, cwd: obj.parentpath }, function (error, stdout, stderr) {
  553. if (childProcess.xrestart == 1) {
  554. setTimeout(function () { obj.launchChildServer(startArgs); }, 500); // This is an expected restart.
  555. } else if (childProcess.xrestart == 2) {
  556. console.log('Expected exit...');
  557. process.exit(); // User CTRL-C exit.
  558. } else if (childProcess.xrestart == 3) {
  559. // Server self-update exit
  560. var version = '';
  561. if (typeof obj.args.selfupdate == 'string') { version = '@' + obj.args.selfupdate; }
  562. else if (typeof obj.args.specificupdate == 'string') { version = '@' + obj.args.specificupdate; delete obj.args.specificupdate; }
  563. const child_process = require('child_process');
  564. const npmpath = ((typeof obj.args.npmpath == 'string') ? obj.args.npmpath : 'npm');
  565. const npmproxy = ((typeof obj.args.npmproxy == 'string') ? (' --proxy ' + obj.args.npmproxy) : '');
  566. const env = Object.assign({}, process.env); // Shallow clone
  567. if (typeof obj.args.npmproxy == 'string') { env['HTTP_PROXY'] = env['HTTPS_PROXY'] = env['http_proxy'] = env['https_proxy'] = obj.args.npmproxy; }
  568. // always use --save-exact - https://stackoverflow.com/a/64507176/1210734
  569. const xxprocess = child_process.exec(npmpath + ' install --save-exact --no-audit meshcentral' + version + npmproxy, { maxBuffer: Infinity, cwd: obj.parentpath, env: env }, function (error, stdout, stderr) {
  570. if ((error != null) && (error != '')) { console.log('Update failed: ' + error); }
  571. });
  572. xxprocess.data = '';
  573. xxprocess.stdout.on('data', function (data) { xxprocess.data += data; });
  574. xxprocess.stderr.on('data', function (data) { xxprocess.data += data; });
  575. xxprocess.on('close', function (code) {
  576. if (code == 0) { console.log('Update completed...'); }
  577. // Run the server updated script if present
  578. if (typeof obj.config.settings.runonserverupdated == 'string') {
  579. const child_process = require('child_process');
  580. var parentpath = __dirname;
  581. if ((__dirname.endsWith('/node_modules/meshcentral')) || (__dirname.endsWith('\\node_modules\\meshcentral')) || (__dirname.endsWith('/node_modules/meshcentral/')) || (__dirname.endsWith('\\node_modules\\meshcentral\\'))) { parentpath = require('path').join(__dirname, '../..'); }
  582. child_process.exec(obj.config.settings.runonserverupdated + ' ' + getCurrentVersion(), { maxBuffer: 512000, timeout: 120000, cwd: parentpath }, function (error, stdout, stderr) { });
  583. }
  584. if (obj.args.cleannpmcacheonupdate === true) {
  585. // Perform NPM cache clean
  586. console.log('Cleaning NPM cache...');
  587. const xxxprocess = child_process.exec(npmpath + ' cache clean --force', { maxBuffer: Infinity, cwd: obj.parentpath, env: env }, function (error, stdout, stderr) { });
  588. xxxprocess.on('close', function (code) { setTimeout(function () { obj.launchChildServer(startArgs); }, 1000); });
  589. } else {
  590. // Run the updated server
  591. setTimeout(function () { obj.launchChildServer(startArgs); }, 1000);
  592. }
  593. });
  594. } else {
  595. if (error != null) {
  596. // This is an un-expected restart
  597. console.log(error);
  598. console.log('ERROR: MeshCentral failed with critical error, check mesherrors.txt. Restarting in 5 seconds...');
  599. setTimeout(function () { obj.launchChildServer(startArgs); }, 5000);
  600. // Run the server error script if present
  601. if (typeof obj.config.settings.runonservererror == 'string') {
  602. const child_process = require('child_process');
  603. var parentpath = __dirname;
  604. if ((__dirname.endsWith('/node_modules/meshcentral')) || (__dirname.endsWith('\\node_modules\\meshcentral')) || (__dirname.endsWith('/node_modules/meshcentral/')) || (__dirname.endsWith('\\node_modules\\meshcentral\\'))) { parentpath = require('path').join(__dirname, '../..'); }
  605. child_process.exec(obj.config.settings.runonservererror + ' ' + getCurrentVersion(), { maxBuffer: 512000, timeout: 120000, cwd: parentpath }, function (error, stdout, stderr) { });
  606. }
  607. }
  608. }
  609. });
  610. childProcess.stdout.on('data', function (data) {
  611. if (data[data.length - 1] == '\n') { data = data.substring(0, data.length - 1); }
  612. if (data.indexOf('Updating settings folder...') >= 0) { childProcess.xrestart = 1; }
  613. else if (data.indexOf('Updating server certificates...') >= 0) { childProcess.xrestart = 1; }
  614. else if (data.indexOf('Server Ctrl-C exit...') >= 0) { childProcess.xrestart = 2; }
  615. else if (data.indexOf('Starting self upgrade...') >= 0) { childProcess.xrestart = 3; }
  616. else if (data.indexOf('Server restart...') >= 0) { childProcess.xrestart = 1; }
  617. else if (data.indexOf('Starting self upgrade to: ') >= 0) { obj.args.specificupdate = data.substring(26).split('\r')[0].split('\n')[0]; childProcess.xrestart = 3; }
  618. var datastr = data;
  619. while (datastr.endsWith('\r') || datastr.endsWith('\n')) { datastr = datastr.substring(0, datastr.length - 1); }
  620. console.log(datastr);
  621. });
  622. childProcess.stderr.on('data', function (data) {
  623. var datastr = data;
  624. while (datastr.endsWith('\r') || datastr.endsWith('\n')) { datastr = datastr.substring(0, datastr.length - 1); }
  625. console.log('ERR: ' + datastr);
  626. if (data.startsWith('le.challenges[tls-sni-01].loopback')) { return; } // Ignore this error output from GreenLock
  627. if (data[data.length - 1] == '\n') { data = data.substring(0, data.length - 1); }
  628. obj.logError(data);
  629. });
  630. childProcess.on('close', function (code) { if ((code != 0) && (code != 123)) { /* console.log("Exited with code " + code); */ } });
  631. };
  632. obj.logError = function (err) {
  633. try {
  634. var errlogpath = null;
  635. if (typeof obj.args.mesherrorlogpath == 'string') { errlogpath = obj.path.join(obj.args.mesherrorlogpath, 'mesherrors.txt'); } else { errlogpath = obj.getConfigFilePath('mesherrors.txt'); }
  636. obj.fs.appendFileSync(errlogpath, '-------- ' + new Date().toLocaleString() + ' ---- ' + getCurrentVersion() + ' --------\r\n\r\n' + err + '\r\n\r\n\r\n');
  637. } catch (ex) { console.log('ERROR: Unable to write to mesherrors.txt.'); }
  638. };
  639. // Get current and latest MeshCentral server versions using NPM
  640. obj.getLatestServerVersion = function (callback) {
  641. if (callback == null) return;
  642. try {
  643. if (typeof obj.args.selfupdate == 'string') { callback(getCurrentVersion(), obj.args.selfupdate); return; } // If we are targetting a specific version, return that one as current.
  644. const child_process = require('child_process');
  645. const npmpath = ((typeof obj.args.npmpath == 'string') ? obj.args.npmpath : 'npm');
  646. const npmproxy = ((typeof obj.args.npmproxy == 'string') ? (' --proxy ' + obj.args.npmproxy) : '');
  647. const env = Object.assign({}, process.env); // Shallow clone
  648. if (typeof obj.args.npmproxy == 'string') { env['HTTP_PROXY'] = env['HTTPS_PROXY'] = env['http_proxy'] = env['https_proxy'] = obj.args.npmproxy; }
  649. const xxprocess = child_process.exec(npmpath + npmproxy + ' view meshcentral dist-tags.latest', { maxBuffer: 512000, cwd: obj.parentpath, env: env }, function (error, stdout, stderr) { });
  650. xxprocess.data = '';
  651. xxprocess.stdout.on('data', function (data) { xxprocess.data += data; });
  652. xxprocess.stderr.on('data', function (data) { });
  653. xxprocess.on('close', function (code) {
  654. var latestVer = null;
  655. if (code == 0) { try { latestVer = xxprocess.data.split(' ').join('').split('\r').join('').split('\n').join(''); } catch (ex) { } }
  656. callback(getCurrentVersion(), latestVer);
  657. });
  658. } catch (ex) { callback(getCurrentVersion(), null, ex); } // If the system is running out of memory, an exception here can easily happen.
  659. };
  660. // Get current version and all MeshCentral server tags using NPM
  661. obj.getServerTags = function (callback) {
  662. if (callback == null) return;
  663. try {
  664. if (typeof obj.args.selfupdate == 'string') { callback({ current: getCurrentVersion(), latest: obj.args.selfupdate }); return; } // If we are targetting a specific version, return that one as current.
  665. const child_process = require('child_process');
  666. const npmpath = ((typeof obj.args.npmpath == 'string') ? obj.args.npmpath : 'npm');
  667. const npmproxy = ((typeof obj.args.npmproxy == 'string') ? (' --proxy ' + obj.args.npmproxy) : '');
  668. const env = Object.assign({}, process.env); // Shallow clone
  669. if (typeof obj.args.npmproxy == 'string') { env['HTTP_PROXY'] = env['HTTPS_PROXY'] = env['http_proxy'] = env['https_proxy'] = obj.args.npmproxy; }
  670. const xxprocess = child_process.exec(npmpath + npmproxy + ' dist-tag ls meshcentral', { maxBuffer: 512000, cwd: obj.parentpath, env: env }, function (error, stdout, stderr) { });
  671. xxprocess.data = '';
  672. xxprocess.stdout.on('data', function (data) { xxprocess.data += data; });
  673. xxprocess.stderr.on('data', function (data) { });
  674. xxprocess.on('close', function (code) {
  675. var tags = { current: getCurrentVersion() };
  676. if (code == 0) {
  677. try {
  678. var lines = xxprocess.data.split('\r\n').join('\n').split('\n');
  679. for (var i in lines) { var s = lines[i].split(': '); if ((s.length == 2) && (obj.args.npmtag == null) || (obj.args.npmtag == s[0])) { tags[s[0]] = s[1]; } }
  680. } catch (ex) { }
  681. }
  682. callback(tags);
  683. });
  684. } catch (ex) { callback({ current: getCurrentVersion() }, ex); } // If the system is running out of memory, an exception here can easily happen.
  685. };
  686. // Use NPM to get list of versions
  687. obj.getServerVersions = function (callback) {
  688. try {
  689. const child_process = require('child_process');
  690. const npmpath = ((typeof obj.args.npmpath == 'string') ? obj.args.npmpath : 'npm');
  691. const npmproxy = ((typeof obj.args.npmproxy == 'string') ? (' --proxy ' + obj.args.npmproxy) : '');
  692. const env = Object.assign({}, process.env); // Shallow clone
  693. if (typeof obj.args.npmproxy == 'string') { env['HTTP_PROXY'] = env['HTTPS_PROXY'] = env['http_proxy'] = env['https_proxy'] = obj.args.npmproxy; }
  694. const xxprocess = child_process.exec(npmpath + npmproxy + ' view meshcentral versions --json', { maxBuffer: 512000, cwd: obj.parentpath, env: env }, function (error, stdout, stderr) { });
  695. xxprocess.data = '';
  696. xxprocess.stdout.on('data', function (data) { xxprocess.data += data; });
  697. xxprocess.stderr.on('data', function (data) { });
  698. xxprocess.on('close', function (code) {
  699. (code == 0) ? callback(xxprocess.data) : callback('{}');
  700. });
  701. } catch (ex) { callback('{}'); }
  702. };
  703. // Initiate server self-update
  704. obj.performServerUpdate = function (version) {
  705. if (obj.serverSelfWriteAllowed != true) return false;
  706. if ((version == null) || (version == '') || (typeof version != 'string')) { console.log('Starting self upgrade...'); } else { console.log('Starting self upgrade to: ' + version); }
  707. process.exit(200);
  708. return true;
  709. };
  710. // Initiate server self-update
  711. obj.performServerCertUpdate = function () { console.log('Updating server certificates...'); process.exit(200); };
  712. // Start by loading configuration from Vault
  713. obj.StartVault = function () {
  714. // Check that the configuration can only be loaded from one place
  715. if ((obj.args.vault != null) && (obj.args.loadconfigfromdb != null)) { console.log("Can't load configuration from both database and Vault."); process.exit(); return; }
  716. // Fix arguments if needed
  717. if (typeof obj.args.vault == 'string') {
  718. obj.args.vault = { endpoint: obj.args.vault };
  719. if (typeof obj.args.token == 'string') { obj.args.vault.token = obj.args.token; }
  720. if (typeof obj.args.unsealkey == 'string') { obj.args.vault.unsealkey = obj.args.unsealkey; }
  721. if (typeof obj.args.name == 'string') { obj.args.vault.name = obj.args.name; }
  722. }
  723. // Load configuration for HashiCorp's Vault if needed
  724. if (obj.args.vault) {
  725. if (obj.args.vault.endpoint == null) { console.log('Missing Vault endpoint.'); process.exit(); return; }
  726. if (obj.args.vault.token == null) { console.log('Missing Vault token.'); process.exit(); return; }
  727. if (obj.args.vault.unsealkey == null) { console.log('Missing Vault unsealkey.'); process.exit(); return; }
  728. if (obj.args.vault.name == null) { obj.args.vault.name = 'meshcentral'; }
  729. // Get new instance of the client
  730. const vault = require("node-vault")({ endpoint: obj.args.vault.endpoint, token: obj.args.vault.token });
  731. vault.unseal({ key: obj.args.vault.unsealkey })
  732. .then(function () {
  733. if (obj.args.vaultdeleteconfigfiles) {
  734. vault.delete('secret/data/' + obj.args.vault.name)
  735. .then(function (r) { console.log('Done.'); process.exit(); })
  736. .catch(function (x) { console.log(x); process.exit(); });
  737. } else if (obj.args.vaultpushconfigfiles) {
  738. // Push configuration files into Vault
  739. if ((obj.args.vaultpushconfigfiles == '*') || (obj.args.vaultpushconfigfiles === true)) { obj.args.vaultpushconfigfiles = obj.datapath; }
  740. obj.fs.readdir(obj.args.vaultpushconfigfiles, function (err, files) {
  741. if (err != null) { console.log('ERROR: Unable to read from folder ' + obj.args.vaultpushconfigfiles); process.exit(); return; }
  742. var configFound = false;
  743. for (var i in files) { if (files[i] == 'config.json') { configFound = true; } }
  744. if (configFound == false) { console.log('ERROR: No config.json in folder ' + obj.args.vaultpushconfigfiles); process.exit(); return; }
  745. var configFiles = {};
  746. for (var i in files) {
  747. const file = files[i];
  748. if ((file == 'config.json') || file.endsWith('.key') || file.endsWith('.crt') || (file == 'terms.txt') || file.endsWith('.jpg') || file.endsWith('.png')) {
  749. const path = obj.path.join(obj.args.vaultpushconfigfiles, files[i]), binary = Buffer.from(obj.fs.readFileSync(path, { encoding: 'binary' }), 'binary');
  750. console.log('Pushing ' + file + ', ' + binary.length + ' bytes.');
  751. if (file.endsWith('.json') || file.endsWith('.key') || file.endsWith('.crt')) { configFiles[file] = binary.toString(); } else { configFiles[file] = binary.toString('base64'); }
  752. }
  753. }
  754. vault.write('secret/data/' + obj.args.vault.name, { "data": configFiles })
  755. .then(function (r) { console.log('Done.'); process.exit(); })
  756. .catch(function (x) { console.log(x); process.exit(); });
  757. });
  758. } else {
  759. // Read configuration files from Vault
  760. vault.read('secret/data/' + obj.args.vault.name)
  761. .then(function (r) {
  762. if ((r == null) || (r.data == null) || (r.data.data == null)) { console.log('Unable to read configuration from Vault.'); process.exit(); return; }
  763. var configFiles = obj.configurationFiles = r.data.data;
  764. // Decode Base64 when needed
  765. for (var file in configFiles) { if (!file.endsWith('.json') && !file.endsWith('.key') && !file.endsWith('.crt')) { configFiles[file] = Buffer.from(configFiles[file], 'base64'); } }
  766. // Save all of the files
  767. if (obj.args.vaultpullconfigfiles) {
  768. for (var i in configFiles) {
  769. var fullFileName = obj.path.join(obj.args.vaultpullconfigfiles, i);
  770. try { obj.fs.writeFileSync(fullFileName, configFiles[i]); } catch (ex) { console.log('Unable to write to ' + fullFileName); process.exit(); return; }
  771. console.log('Pulling ' + i + ', ' + configFiles[i].length + ' bytes.');
  772. }
  773. console.log('Done.');
  774. process.exit();
  775. }
  776. // Parse the new configuration file
  777. var config2 = null;
  778. try { config2 = JSON.parse(configFiles['config.json']); } catch (ex) { console.log('Error, unable to parse config.json from Vault.'); process.exit(); return; }
  779. // Set the command line arguments to the config file if they are not present
  780. if (!config2.settings) { config2.settings = {}; }
  781. for (var i in args) { config2.settings[i] = args[i]; }
  782. obj.args = args = config2.settings;
  783. // Lower case all keys in the config file
  784. obj.common.objKeysToLower(config2, ['ldapoptions', 'defaultuserwebstate', 'forceduserwebstate', 'httpheaders', 'telegram/proxy']);
  785. // Grad some of the values from the original config.json file if present.
  786. if ((config.settings.vault != null) && (config2.settings != null)) { config2.settings.vault = config.settings.vault; }
  787. // We got a new config.json from the database, let's use it.
  788. config = obj.config = config2;
  789. obj.StartEx();
  790. })
  791. .catch(function (x) { console.log(x); process.exit(); });
  792. }
  793. }).catch(function (x) { console.log(x); process.exit(); });
  794. return;
  795. }
  796. }
  797. // Look for easy command line instructions and do them here.
  798. obj.StartEx = function () {
  799. var i;
  800. //var wincmd = require('node-windows');
  801. //wincmd.list(function (svc) { console.log(svc); }, true);
  802. // Setup syslog support. Not supported on Windows.
  803. if ((require('os').platform() != 'win32') && ((config.settings.syslog != null) || (config.settings.syslogjson != null) || (config.settings.syslogauth != null))) {
  804. if (config.settings.syslog === true) { config.settings.syslog = 'meshcentral'; }
  805. if (config.settings.syslogjson === true) { config.settings.syslogjson = 'meshcentral-json'; }
  806. if (config.settings.syslogauth === true) { config.settings.syslogauth = 'meshcentral-auth'; }
  807. if (typeof config.settings.syslog == 'string') {
  808. obj.syslog = require('modern-syslog');
  809. console.log('Starting ' + config.settings.syslog + ' syslog.');
  810. obj.syslog.init(config.settings.syslog, obj.syslog.LOG_PID | obj.syslog.LOG_ODELAY, obj.syslog.LOG_LOCAL0);
  811. obj.syslog.log(obj.syslog.LOG_INFO, "MeshCentral v" + getCurrentVersion() + " Server Start");
  812. }
  813. if (typeof config.settings.syslogjson == 'string') {
  814. obj.syslogjson = require('modern-syslog');
  815. console.log('Starting ' + config.settings.syslogjson + ' JSON syslog.');
  816. obj.syslogjson.init(config.settings.syslogjson, obj.syslogjson.LOG_PID | obj.syslogjson.LOG_ODELAY, obj.syslogjson.LOG_LOCAL0);
  817. obj.syslogjson.log(obj.syslogjson.LOG_INFO, "MeshCentral v" + getCurrentVersion() + " Server Start");
  818. }
  819. if (typeof config.settings.syslogauth == 'string') {
  820. obj.syslogauth = require('modern-syslog');
  821. console.log('Starting ' + config.settings.syslogauth + ' auth syslog.');
  822. obj.syslogauth.init(config.settings.syslogauth, obj.syslogauth.LOG_PID | obj.syslogauth.LOG_ODELAY, obj.syslogauth.LOG_LOCAL0);
  823. obj.syslogauth.log(obj.syslogauth.LOG_INFO, "MeshCentral v" + getCurrentVersion() + " Server Start");
  824. }
  825. }
  826. // Setup TCP syslog support, this works on all OS's.
  827. if (config.settings.syslogtcp != null) {
  828. const syslog = require('syslog');
  829. if (config.settings.syslogtcp === true) {
  830. obj.syslogtcp = syslog.createClient(514, 'localhost');
  831. } else {
  832. const sp = config.settings.syslogtcp.split(':');
  833. obj.syslogtcp = syslog.createClient(parseInt(sp[1]), sp[0]);
  834. }
  835. obj.syslogtcp.log("MeshCentral v" + getCurrentVersion() + " Server Start", obj.syslogtcp.LOG_INFO);
  836. }
  837. // Check top level configuration for any unrecognized values
  838. if (config) { for (var i in config) { if ((typeof i == 'string') && (i.length > 0) && (i[0] != '_') && (['settings', 'domaindefaults', 'domains', 'configfiles', 'smtp', 'letsencrypt', 'peers', 'sms', 'messaging', 'sendgrid', 'sendmail', 'firebase', 'firebaserelay', '$schema'].indexOf(i) == -1)) { addServerWarning('Unrecognized configuration option \"' + i + '\".', 3, [i]); } } }
  839. // Read IP lists from files if applicable
  840. config.settings.userallowedip = obj.args.userallowedip = readIpListFromFile(obj.args.userallowedip);
  841. config.settings.userblockedip = obj.args.userblockedip = readIpListFromFile(obj.args.userblockedip);
  842. config.settings.agentallowedip = obj.args.agentallowedip = readIpListFromFile(obj.args.agentallowedip);
  843. config.settings.agentblockedip = obj.args.agentblockedip = readIpListFromFile(obj.args.agentblockedip);
  844. config.settings.swarmallowedip = obj.args.swarmallowedip = readIpListFromFile(obj.args.swarmallowedip);
  845. // Check IP lists and ranges
  846. if (typeof obj.args.userallowedip == 'string') { if (obj.args.userallowedip == '') { config.settings.userallowedip = obj.args.userallowedip = null; } else { config.settings.userallowedip = obj.args.userallowedip = obj.args.userallowedip.split(' ').join('').split(','); } }
  847. if (typeof obj.args.userblockedip == 'string') { if (obj.args.userblockedip == '') { config.settings.userblockedip = obj.args.userblockedip = null; } else { config.settings.userblockedip = obj.args.userblockedip = obj.args.userblockedip.split(' ').join('').split(','); } }
  848. if (typeof obj.args.agentallowedip == 'string') { if (obj.args.agentallowedip == '') { config.settings.agentallowedip = obj.args.agentallowedip = null; } else { config.settings.agentallowedip = obj.args.agentallowedip = obj.args.agentallowedip.split(' ').join('').split(','); } }
  849. if (typeof obj.args.agentblockedip == 'string') { if (obj.args.agentblockedip == '') { config.settings.agentblockedip = obj.args.agentblockedip = null; } else { config.settings.agentblockedip = obj.args.agentblockedip = obj.args.agentblockedip.split(' ').join('').split(','); } }
  850. if (typeof obj.args.swarmallowedip == 'string') { if (obj.args.swarmallowedip == '') { obj.args.swarmallowedip = null; } else { obj.args.swarmallowedip = obj.args.swarmallowedip.split(' ').join('').split(','); } }
  851. if ((typeof obj.args.agentupdateblocksize == 'number') && (obj.args.agentupdateblocksize >= 1024) && (obj.args.agentupdateblocksize <= 65531)) { obj.agentUpdateBlockSize = obj.args.agentupdateblocksize; }
  852. if (typeof obj.args.trustedproxy == 'string') { obj.args.trustedproxy = obj.args.trustedproxy.split(' ').join('').split(','); }
  853. if (typeof obj.args.tlsoffload == 'string') { obj.args.tlsoffload = obj.args.tlsoffload.split(' ').join('').split(','); }
  854. // Check the "cookieIpCheck" value
  855. if ((obj.args.cookieipcheck === false) || (obj.args.cookieipcheck == 'none')) { obj.args.cookieipcheck = 'none'; }
  856. else if ((typeof obj.args.cookieipcheck != 'string') || (obj.args.cookieipcheck.toLowerCase() != 'strict')) { obj.args.cookieipcheck = 'lax'; }
  857. else { obj.args.cookieipcheck = 'strict'; }
  858. // Check the "cookieSameSite" value
  859. if (typeof obj.args.cookiesamesite != 'string') { delete obj.args.cookiesamesite; }
  860. else if (['none', 'lax', 'strict'].indexOf(obj.args.cookiesamesite.toLowerCase()) == -1) { delete obj.args.cookiesamesite; } else { obj.args.cookiesamesite = obj.args.cookiesamesite.toLowerCase(); }
  861. // Check if WebSocket compression is supported. It's known to be broken in NodeJS v11.11 to v12.15, and v13.2
  862. const verSplit = process.version.substring(1).split('.');
  863. const ver = parseInt(verSplit[0]) + (parseInt(verSplit[1]) / 100);
  864. if (((ver >= 11.11) && (ver <= 12.15)) || (ver == 13.2)) {
  865. if ((obj.args.wscompression === true) || (obj.args.agentwscompression === true)) { addServerWarning('WebSocket compression is disabled, this feature is broken in NodeJS v11.11 to v12.15 and v13.2', 4); }
  866. obj.args.wscompression = obj.args.agentwscompression = false;
  867. obj.config.settings.wscompression = obj.config.settings.agentwscompression = false;
  868. }
  869. // Local console tracing
  870. if (typeof obj.args.debug == 'string') { obj.debugSources = obj.args.debug.toLowerCase().split(','); }
  871. else if (typeof obj.args.debug == 'object') { obj.debugSources = obj.args.debug; }
  872. else if (obj.args.debug === true) { obj.debugSources = '*'; }
  873. require('./db.js').CreateDB(obj,
  874. function (db) {
  875. obj.db = db;
  876. obj.db.SetupDatabase(function (dbversion) {
  877. // See if any database operations needs to be completed
  878. if (obj.args.deletedomain) { obj.db.DeleteDomain(obj.args.deletedomain, function () { console.log('Deleted domain ' + obj.args.deletedomain + '.'); process.exit(); }); return; }
  879. if (obj.args.deletedefaultdomain) { obj.db.DeleteDomain('', function () { console.log('Deleted default domain.'); process.exit(); }); return; }
  880. if (obj.args.showall) { obj.db.GetAll(function (err, docs) { console.log(JSON.stringify(docs, null, 2)); process.exit(); }); return; }
  881. if (obj.args.showusers) { obj.db.GetAllType('user', function (err, docs) { console.log(JSON.stringify(docs, null, 2)); process.exit(); }); return; }
  882. if (obj.args.showitem) { obj.db.Get(obj.args.showitem, function (err, docs) { console.log(JSON.stringify(docs, null, 2)); process.exit(); }); return; }
  883. if (obj.args.listuserids) { obj.db.GetAllType('user', function (err, docs) { for (var i in docs) { console.log(docs[i]._id); } process.exit(); }); return; }
  884. if (obj.args.showusergroups) { obj.db.GetAllType('ugrp', function (err, docs) { console.log(JSON.stringify(docs, null, 2)); process.exit(); }); return; }
  885. if (obj.args.shownodes) { obj.db.GetAllType('node', function (err, docs) { console.log(JSON.stringify(docs, null, 2)); process.exit(); }); return; }
  886. if (obj.args.showallmeshes) { obj.db.GetAllType('mesh', function (err, docs) { console.log(JSON.stringify(docs, null, 2)); process.exit(); }); return; }
  887. if (obj.args.showmeshes) { obj.db.GetAllType('mesh', function (err, docs) { var x = []; for (var i in docs) { if (docs[i].deleted == null) { x.push(docs[i]); } } console.log(JSON.stringify(x, null, 2)); process.exit(); }); return; }
  888. if (obj.args.showevents) { obj.db.GetAllEvents(function (err, docs) { console.log(JSON.stringify(docs, null, 2)); process.exit(); }); return; }
  889. if (obj.args.showsmbios) { obj.db.GetAllSMBIOS(function (err, docs) { console.log(JSON.stringify(docs, null, 2)); process.exit(); }); return; }
  890. if (obj.args.showpower) { obj.db.getAllPower(function (err, docs) { console.log(JSON.stringify(docs, null, 2)); process.exit(); }); return; }
  891. if (obj.args.clearpower) { obj.db.removeAllPowerEvents(function () { process.exit(); }); return; }
  892. if (obj.args.showiplocations) { obj.db.GetAllType('iploc', function (err, docs) { console.log(docs); process.exit(); }); return; }
  893. if (obj.args.logintoken) { obj.getLoginToken(obj.args.logintoken, function (r) { console.log(r); process.exit(); }); return; }
  894. if (obj.args.logintokenkey) { obj.showLoginTokenKey(function (r) { console.log(r); process.exit(); }); return; }
  895. if (obj.args.recordencryptionrecode) { obj.db.performRecordEncryptionRecode(function (count) { console.log('Re-encoded ' + count + ' record(s).'); process.exit(); }); return; }
  896. if (obj.args.dbstats) { obj.db.getDbStats(function (stats) { console.log(stats); process.exit(); }); return; }
  897. if (obj.args.createaccount) { // Create a new user account
  898. if ((typeof obj.args.createaccount != 'string') || ((obj.args.pass == null) && (obj.args.hashpass == null)) || (obj.args.pass == '') || (obj.args.hashpass == '') || (obj.args.createaccount.indexOf(' ') >= 0)) { console.log("Usage: --createaccount [userid] --pass [password] --domain (domain) --email (email) --name (name)."); process.exit(); return; }
  899. var userid = 'user/' + (obj.args.domain ? obj.args.domain : '') + '/' + obj.args.createaccount.toLowerCase(), domainid = obj.args.domain ? obj.args.domain : '';
  900. if (obj.args.createaccount.startsWith('user/')) { userid = obj.args.createaccount; domainid = obj.args.createaccount.split('/')[1]; }
  901. if (userid.split('/').length != 3) { console.log("Invalid userid."); process.exit(); return; }
  902. obj.db.Get(userid, function (err, docs) {
  903. if (err != null) { console.log("Database error: " + err); process.exit(); return; }
  904. if ((docs != null) && (docs.length != 0)) { console.log('User already exists.'); process.exit(); return; }
  905. if ((domainid != '') && ((config.domains == null) || (config.domains[domainid] == null))) { console.log("Invalid domain."); process.exit(); return; }
  906. const user = { _id: userid, type: 'user', name: (typeof obj.args.name == 'string') ? obj.args.name : (userid.split('/')[2]), domain: domainid, creation: Math.floor(Date.now() / 1000), links: {} };
  907. if (typeof obj.args.email == 'string') { user.email = obj.args.email; user.emailVerified = true; }
  908. if (obj.args.hashpass) {
  909. // Create an account using a pre-hashed password. Use --hashpassword to pre-hash a password.
  910. var hashpasssplit = obj.args.hashpass.split(',');
  911. if (hashpasssplit.length != 2) { console.log("Invalid hashed password."); process.exit(); return; }
  912. user.salt = hashpasssplit[0];
  913. user.hash = hashpasssplit[1];
  914. obj.db.Set(user, function () { console.log("Done. This command will only work if MeshCentral is stopped."); process.exit(); return; });
  915. } else {
  916. // Hash the password and create the account.
  917. require('./pass').hash(obj.args.pass, function (err, salt, hash, tag) { if (err) { console.log("Unable create account password: " + err); process.exit(); return; } user.salt = salt; user.hash = hash; obj.db.Set(user, function () { console.log("Done."); process.exit(); return; }); }, 0);
  918. }
  919. });
  920. return;
  921. }
  922. if (obj.args.resetaccount) { // Unlock a user account, set a new password and remove 2FA
  923. if ((typeof obj.args.resetaccount != 'string') || (obj.args.resetaccount.indexOf(' ') >= 0)) { console.log("Usage: --resetaccount [userid] --domain (domain) --pass [password]."); process.exit(); return; }
  924. var userid = 'user/' + (obj.args.domain ? obj.args.domain : '') + '/' + obj.args.resetaccount.toLowerCase();
  925. if (obj.args.resetaccount.startsWith('user/')) { userid = obj.args.resetaccount; }
  926. if (userid.split('/').length != 3) { console.log("Invalid userid."); process.exit(); return; }
  927. obj.db.Get(userid, function (err, docs) {
  928. if (err != null) { console.log("Database error: " + err); process.exit(); return; }
  929. if ((docs == null) || (docs.length == 0)) { console.log("Unknown userid, usage: --resetaccount [userid] --domain (domain) --pass [password]."); process.exit(); return; }
  930. const user = docs[0]; if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { user.siteadmin -= 32; } // Unlock the account.
  931. delete user.phone; delete user.otpekey; delete user.otpsecret; delete user.otpkeys; delete user.otphkeys; delete user.otpdev; delete user.otpsms; delete user.otpmsg; // Disable 2FA
  932. delete user.msghandle; // Disable users 2fa messaging too
  933. var config = getConfig(false);
  934. if (config.domains[user.domain].auth || config.domains[user.domain].authstrategies) {
  935. console.log('This users domain has external authentication methods enabled so the password will not be changed if you set one')
  936. obj.db.Set(user, function () { console.log("Done."); process.exit(); return; });
  937. } else {
  938. if (obj.args.hashpass && (typeof obj.args.hashpass == 'string')) {
  939. // Reset an account using a pre-hashed password. Use --hashpassword to pre-hash a password.
  940. var hashpasssplit = obj.args.hashpass.split(',');
  941. if (hashpasssplit.length != 2) { console.log("Invalid hashed password."); process.exit(); return; }
  942. user.salt = hashpasssplit[0];
  943. user.hash = hashpasssplit[1];
  944. obj.db.Set(user, function () { console.log("Done. This command will only work if MeshCentral is stopped."); process.exit(); return; });
  945. } else if (obj.args.pass && (typeof obj.args.pass == 'string')) {
  946. // Hash the password and reset the account.
  947. require('./pass').hash(String(obj.args.pass), user.salt, function (err, hash, tag) {
  948. if (err) { console.log("Unable to reset password: " + err); process.exit(); return; }
  949. user.hash = hash;
  950. obj.db.Set(user, function () { console.log("Done."); process.exit(); return; });
  951. }, 0);
  952. } else {
  953. console.log('Not setting a users password');
  954. obj.db.Set(user, function () { console.log("Done."); process.exit(); return; });
  955. }
  956. }
  957. });
  958. return;
  959. }
  960. if (obj.args.adminaccount) { // Set a user account to server administrator
  961. if ((typeof obj.args.adminaccount != 'string') || (obj.args.adminaccount.indexOf(' ') >= 0)) { console.log("Invalid userid, usage: --adminaccount [username] --domain (domain)"); process.exit(); return; }
  962. var userid = 'user/' + (obj.args.domain ? obj.args.domain : '') + '/' + obj.args.adminaccount.toLowerCase();
  963. if (obj.args.adminaccount.startsWith('user/')) { userid = obj.args.adminaccount; }
  964. if (userid.split('/').length != 3) { console.log("Invalid userid."); process.exit(); return; }
  965. obj.db.Get(userid, function (err, docs) {
  966. if (err != null) { console.log("Database error: " + err); process.exit(); return; }
  967. if ((docs == null) || (docs.length == 0)) { console.log("Unknown userid, usage: --adminaccount [userid] --domain (domain)."); process.exit(); return; }
  968. docs[0].siteadmin = 0xFFFFFFFF; // Set user as site administrator
  969. obj.db.Set(docs[0], function () { console.log("Done. This command will only work if MeshCentral is stopped."); process.exit(); return; });
  970. });
  971. return;
  972. }
  973. if (obj.args.removesubdomain) { // Remove all references to a sub domain from the database
  974. if ((typeof obj.args.removesubdomain != 'string') || (obj.args.removesubdomain.indexOf(' ') >= 0)) { console.log("Invalid sub domain, usage: --removesubdomain [domain]"); process.exit(); return; }
  975. obj.db.removeDomain(obj.args.removesubdomain, function () { console.log("Done."); process.exit(); return; });
  976. return;
  977. }
  978. if (obj.args.removetestagents) { // Remove all test agents from the database
  979. db.GetAllType('node', function (err, docs) {
  980. if ((err != null) || (docs.length == 0)) {
  981. console.log('Unable to get any nodes from the database');
  982. process.exit(0);
  983. } else {
  984. // Load all users
  985. const allusers = {}, removeCount = 0;
  986. obj.db.GetAllType('user', function (err, docs) {
  987. obj.common.unEscapeAllLinksFieldName(docs);
  988. for (i in docs) { allusers[docs[i]._id] = docs[i]; }
  989. });
  990. // Look at all devices
  991. for (var i in docs) {
  992. if ((docs[i] != null) && (docs[i].agent != null) && (docs[i].agent.id == 23)) {
  993. // Remove this test node
  994. const node = docs[i];
  995. // Delete this node including network interface information, events and timeline
  996. removeCount++;
  997. db.Remove(node._id); // Remove node with that id
  998. db.Remove('if' + node._id); // Remove interface information
  999. db.Remove('nt' + node._id); // Remove notes
  1000. db.Remove('lc' + node._id); // Remove last connect time
  1001. db.Remove('si' + node._id); // Remove system information
  1002. if (db.RemoveSMBIOS) { db.RemoveSMBIOS(node._id); } // Remove SMBios data
  1003. db.RemoveAllNodeEvents(node._id); // Remove all events for this node
  1004. db.removeAllPowerEventsForNode(node._id); // Remove all power events for this node
  1005. if (typeof node.pmt == 'string') { db.Remove('pmt_' + node.pmt); } // Remove Push Messaging Token
  1006. db.Get('ra' + node._id, function (err, nodes) {
  1007. if ((nodes != null) && (nodes.length == 1)) { db.Remove('da' + nodes[0].daid); } // Remove diagnostic agent to real agent link
  1008. db.Remove('ra' + node._id); // Remove real agent to diagnostic agent link
  1009. });
  1010. // Remove any user node links
  1011. if (node.links != null) {
  1012. for (var i in node.links) {
  1013. if (i.startsWith('user/')) {
  1014. var cuser = allusers[i];
  1015. if ((cuser != null) && (cuser.links != null) && (cuser.links[node._id] != null)) {
  1016. // Remove the user link & save the user
  1017. delete cuser.links[node._id];
  1018. if (Object.keys(cuser.links).length == 0) { delete cuser.links; }
  1019. db.SetUser(cuser);
  1020. }
  1021. }
  1022. }
  1023. }
  1024. }
  1025. }
  1026. if (removeCount == 0) {
  1027. console.log("Done, no devices removed.");
  1028. process.exit(0);
  1029. } else {
  1030. console.log("Removed " + removeCount + " device(s), holding 10 seconds...");
  1031. setTimeout(function () { console.log("Done."); process.exit(0); }, 10000)
  1032. }
  1033. }
  1034. });
  1035. return;
  1036. }
  1037. // Import NeDB data into database
  1038. if (obj.args.nedbtodb) {
  1039. if (db.databaseType == 1) { console.log("NeDB is current database, can't perform transfer."); process.exit(); return; }
  1040. console.log("Transfering NeDB data into database...");
  1041. db.nedbtodb(function (msg) { console.log(msg); process.exit(); })
  1042. return;
  1043. }
  1044. // Show a list of all configuration files in the database
  1045. if (obj.args.dblistconfigfiles) {
  1046. obj.db.GetAllType('cfile', function (err, docs) {
  1047. if (err == null) {
  1048. if (docs.length == 0) {
  1049. console.log("No files found.");
  1050. } else {
  1051. for (var i in docs) {
  1052. if (typeof obj.args.dblistconfigfiles == 'string') {
  1053. const data = obj.db.decryptData(obj.args.dblistconfigfiles, docs[i].data);
  1054. if (data == null) {
  1055. console.log(docs[i]._id.split('/')[1] + ', ' + Buffer.from(docs[i].data, 'base64').length + ' encrypted bytes - Unable to decrypt.');
  1056. } else {
  1057. console.log(docs[i]._id.split('/')[1] + ', ' + data.length + ' bytes, decoded correctly.');
  1058. }
  1059. } else {
  1060. console.log(docs[i]._id.split('/')[1] + ', ' + Buffer.from(docs[i].data, 'base64').length + ' encrypted bytes.');
  1061. }
  1062. }
  1063. }
  1064. } else { console.log('Unable to read from database.'); } process.exit();
  1065. });
  1066. return;
  1067. }
  1068. // Display the content of a configuration file in the database
  1069. if (obj.args.dbshowconfigfile) {
  1070. if (typeof obj.args.configkey != 'string') { console.log("Error, --configkey is required."); process.exit(); return; }
  1071. obj.db.getConfigFile(obj.args.dbshowconfigfile, function (err, docs) {
  1072. if (err == null) {
  1073. if (docs.length == 0) { console.log("File not found."); } else {
  1074. const data = obj.db.decryptData(obj.args.configkey, docs[0].data);
  1075. if (data == null) { console.log("Invalid config key."); } else { console.log(data); }
  1076. }
  1077. } else { console.log("Unable to read from database."); }
  1078. process.exit();
  1079. }); return;
  1080. }
  1081. // Delete all configuration files from database
  1082. if (obj.args.dbdeleteconfigfiles) {
  1083. console.log("Deleting all configuration files from the database..."); obj.db.RemoveAllOfType('cfile', function () { console.log('Done.'); process.exit(); });
  1084. }
  1085. // Push all relevent files from meshcentral-data into the database
  1086. if (obj.args.dbpushconfigfiles) {
  1087. if (typeof obj.args.configkey != 'string') { console.log("Error, --configkey is required."); process.exit(); return; }
  1088. if ((obj.args.dbpushconfigfiles !== true) && (typeof obj.args.dbpushconfigfiles != 'string')) {
  1089. console.log("Usage: --dbpulldatafiles (path) This will import files from folder into the database");
  1090. console.log(" --dbpulldatafiles This will import files from meshcentral-data into the db.");
  1091. process.exit();
  1092. } else {
  1093. if ((obj.args.dbpushconfigfiles == '*') || (obj.args.dbpushconfigfiles === true)) { obj.args.dbpushconfigfiles = obj.datapath; }
  1094. obj.fs.readdir(obj.args.dbpushconfigfiles, function (err, files) {
  1095. if (err != null) { console.log('ERROR: Unable to read from folder ' + obj.args.dbpushconfigfiles); process.exit(); return; }
  1096. var configFound = false;
  1097. for (var i in files) { if (files[i] == 'config.json') { configFound = true; } }
  1098. if (configFound == false) { console.log('ERROR: No config.json in folder ' + obj.args.dbpushconfigfiles); process.exit(); return; }
  1099. obj.db.RemoveAllOfType('cfile', function () {
  1100. obj.fs.readdir(obj.args.dbpushconfigfiles, function (err, files) {
  1101. var lockCount = 1
  1102. for (var i in files) {
  1103. const file = files[i];
  1104. if ((file == 'config.json') || file.endsWith('.key') || file.endsWith('.crt') || (file == 'terms.txt') || file.endsWith('.jpg') || file.endsWith('.png')) {
  1105. const path = obj.path.join(obj.args.dbpushconfigfiles, files[i]), binary = Buffer.from(obj.fs.readFileSync(path, { encoding: 'binary' }), 'binary');
  1106. console.log('Pushing ' + file + ', ' + binary.length + ' bytes.');
  1107. lockCount++;
  1108. if (obj.args.oldencrypt) {
  1109. obj.db.setConfigFile(file, obj.db.oldEncryptData(obj.args.configkey, binary), function () { if ((--lockCount) == 0) { console.log('Done.'); process.exit(); } });
  1110. } else {
  1111. obj.db.setConfigFile(file, obj.db.encryptData(obj.args.configkey, binary), function () { if ((--lockCount) == 0) { console.log('Done.'); process.exit(); } });
  1112. }
  1113. }
  1114. }
  1115. if (--lockCount == 0) { process.exit(); }
  1116. });
  1117. });
  1118. });
  1119. }
  1120. return;
  1121. }
  1122. // Pull all database files into meshcentral-data
  1123. if (obj.args.dbpullconfigfiles) {
  1124. if (typeof obj.args.configkey != 'string') { console.log("Error, --configkey is required."); process.exit(); return; }
  1125. if (typeof obj.args.dbpullconfigfiles != 'string') {
  1126. console.log("Usage: --dbpulldatafiles (path)");
  1127. process.exit();
  1128. } else {
  1129. obj.db.GetAllType('cfile', function (err, docs) {
  1130. if (err == null) {
  1131. if (docs.length == 0) {
  1132. console.log("File not found.");
  1133. } else {
  1134. for (var i in docs) {
  1135. const file = docs[i]._id.split('/')[1], binary = obj.db.decryptData(obj.args.configkey, docs[i].data);
  1136. if (binary == null) {
  1137. console.log("Invalid config key.");
  1138. } else {
  1139. const fullFileName = obj.path.join(obj.args.dbpullconfigfiles, file);
  1140. try { obj.fs.writeFileSync(fullFileName, binary); } catch (ex) { console.log('Unable to write to ' + fullFileName); process.exit(); return; }
  1141. console.log('Pulling ' + file + ', ' + binary.length + ' bytes.');
  1142. }
  1143. }
  1144. }
  1145. } else {
  1146. console.log("Unable to read from database.");
  1147. }
  1148. process.exit();
  1149. });
  1150. }
  1151. return;
  1152. }
  1153. if (obj.args.dbexport) {
  1154. // Export the entire database to a JSON file
  1155. if (obj.args.dbexport == true) { obj.args.dbexport = obj.getConfigFilePath('meshcentral.db.json'); }
  1156. obj.db.GetAll(function (err, docs) {
  1157. obj.fs.writeFileSync(obj.args.dbexport, JSON.stringify(docs));
  1158. console.log('Exported ' + docs.length + ' objects(s) to ' + obj.args.dbexport + '.'); process.exit();
  1159. });
  1160. return;
  1161. }
  1162. if (obj.args.dbexportmin) {
  1163. // Export a minimal database to a JSON file. Export only users, meshes and nodes.
  1164. // This is a useful command to look at the database.
  1165. if (obj.args.dbexportmin == true) { obj.args.dbexportmin = obj.getConfigFilePath('meshcentral.db.json'); }
  1166. obj.db.GetAllType({ $in: ['user', 'node', 'mesh'] }, function (err, docs) {
  1167. obj.fs.writeFileSync(obj.args.dbexportmin, JSON.stringify(docs));
  1168. console.log('Exported ' + docs.length + ' objects(s) to ' + obj.args.dbexportmin + '.'); process.exit();
  1169. });
  1170. return;
  1171. }
  1172. if (obj.args.dbimport) {
  1173. // Import the entire database from a JSON file
  1174. if (obj.args.dbimport == true) { obj.args.dbimport = obj.getConfigFilePath('meshcentral.db.json'); }
  1175. var json = null, json2 = '', badCharCount = 0;
  1176. try { json = obj.fs.readFileSync(obj.args.dbimport, { encoding: 'utf8' }); } catch (ex) { console.log('Invalid JSON file: ' + obj.args.dbimport + ': ' + ex); process.exit(); }
  1177. for (i = 0; i < json.length; i++) { if (json.charCodeAt(i) >= 32) { json2 += json[i]; } else { var tt = json.charCodeAt(i); if (tt != 10 && tt != 13) { badCharCount++; } } } // Remove all bad chars
  1178. if (badCharCount > 0) { console.log(badCharCount + ' invalid character(s) where removed.'); }
  1179. try { json = JSON.parse(json2); } catch (ex) { console.log('Invalid JSON format: ' + obj.args.dbimport + ': ' + e); process.exit(); }
  1180. if ((json == null) || (typeof json.length != 'number') || (json.length < 1)) { console.log('Invalid JSON format: ' + obj.args.dbimport + '.'); }
  1181. // Escape MongoDB invalid field chars
  1182. for (i in json) {
  1183. const doc = json[i];
  1184. for (var j in doc) { if (j.indexOf('.') >= 0) { console.log("Invalid field name (" + j + ") in document: " + json[i]); return; } }
  1185. //if ((json[i].type == 'ifinfo') && (json[i].netif2 != null)) { for (var j in json[i].netif2) { var esc = obj.common.escapeFieldName(j); if (esc !== j) { json[i].netif2[esc] = json[i].netif2[j]; delete json[i].netif2[j]; } } }
  1186. //if ((json[i].type == 'mesh') && (json[i].links != null)) { for (var j in json[i].links) { var esc = obj.common.escapeFieldName(j); if (esc !== j) { json[i].links[esc] = json[i].links[j]; delete json[i].links[j]; } } }
  1187. }
  1188. //for (i in json) { if ((json[i].type == "node") && (json[i].host != null)) { json[i].rname = json[i].host; delete json[i].host; } } // DEBUG: Change host to rname
  1189. setTimeout(function () { // If the Mongo database is being created for the first time, there is a race condition here. This will get around it.
  1190. obj.db.RemoveAll(function () {
  1191. obj.db.InsertMany(json, function (err) {
  1192. if (err != null) { console.log(err); } else { console.log('Imported ' + json.length + ' objects(s) from ' + obj.args.dbimport + '.'); } process.exit();
  1193. });
  1194. });
  1195. }, 100);
  1196. return;
  1197. }
  1198. /*
  1199. if (obj.args.dbimport) {
  1200. // Import the entire database from a very large JSON file
  1201. obj.db.RemoveAll(function () {
  1202. if (obj.args.dbimport == true) { obj.args.dbimport = obj.getConfigFilePath('meshcentral.db.json'); }
  1203. var json = null, json2 = "", badCharCount = 0;
  1204. const StreamArray = require('stream-json/streamers/StreamArray');
  1205. const jsonStream = StreamArray.withParser();
  1206. jsonStream.on('data', function (data) { obj.db.Set(data.value); });
  1207. jsonStream.on('end', () => { console.log('Done.'); process.exit(); });
  1208. obj.fs.createReadStream(obj.args.dbimport).pipe(jsonStream.input);
  1209. });
  1210. return;
  1211. }
  1212. */
  1213. if (obj.args.dbmerge) {
  1214. // Import the entire database from a JSON file
  1215. if (obj.args.dbmerge == true) { obj.args.dbmerge = obj.getConfigFilePath('meshcentral.db.json'); }
  1216. var json = null, json2 = "", badCharCount = 0;
  1217. try { json = obj.fs.readFileSync(obj.args.dbmerge, { encoding: 'utf8' }); } catch (ex) { console.log('Invalid JSON file: ' + obj.args.dbmerge + ': ' + ex); process.exit(); }
  1218. for (i = 0; i < json.length; i++) { if (json.charCodeAt(i) >= 32) { json2 += json[i]; } else { var tt = json.charCodeAt(i); if (tt != 10 && tt != 13) { badCharCount++; } } } // Remove all bad chars
  1219. if (badCharCount > 0) { console.log(badCharCount + ' invalid character(s) where removed.'); }
  1220. try { json = JSON.parse(json2); } catch (ex) { console.log('Invalid JSON format: ' + obj.args.dbmerge + ': ' + ex); process.exit(); }
  1221. if ((json == null) || (typeof json.length != 'number') || (json.length < 1)) { console.log('Invalid JSON format: ' + obj.args.dbimport + '.'); }
  1222. // Get all users from current database
  1223. obj.db.GetAllType('user', function (err, docs) {
  1224. const users = {}, usersCount = 0;
  1225. for (var i in docs) { users[docs[i]._id] = docs[i]; usersCount++; }
  1226. // Fetch all meshes from the database
  1227. obj.db.GetAllType('mesh', function (err, docs) {
  1228. obj.common.unEscapeAllLinksFieldName(docs);
  1229. const meshes = {}, meshesCount = 0;
  1230. for (var i in docs) { meshes[docs[i]._id] = docs[i]; meshesCount++; }
  1231. console.log('Loaded ' + usersCount + ' users and ' + meshesCount + ' meshes.');
  1232. // Look at each object in the import file
  1233. const objectToAdd = [];
  1234. for (var i in json) {
  1235. const newobj = json[i];
  1236. if (newobj.type == 'user') {
  1237. // Check if the user already exists
  1238. var existingUser = users[newobj._id];
  1239. if (existingUser) {
  1240. // Merge the links
  1241. if (typeof newobj.links == 'object') {
  1242. for (var j in newobj.links) {
  1243. if ((existingUser.links == null) || (existingUser.links[j] == null)) {
  1244. if (existingUser.links == null) { existingUser.links = {}; }
  1245. existingUser.links[j] = newobj.links[j];
  1246. }
  1247. }
  1248. }
  1249. if (existingUser.name == 'admin') { existingUser.links = {}; }
  1250. objectToAdd.push(existingUser); // Add this user
  1251. } else {
  1252. objectToAdd.push(newobj); // Add this user
  1253. }
  1254. } else if (newobj.type == 'mesh') {
  1255. // Add this object
  1256. objectToAdd.push(newobj);
  1257. } // Don't add nodes.
  1258. }
  1259. console.log('Importing ' + objectToAdd.length + ' object(s)...');
  1260. var pendingCalls = 1;
  1261. for (var i in objectToAdd) {
  1262. pendingCalls++;
  1263. obj.db.Set(objectToAdd[i], function (err) { if (err != null) { console.log(err); } else { if (--pendingCalls == 0) { process.exit(); } } });
  1264. }
  1265. if (--pendingCalls == 0) { process.exit(); }
  1266. });
  1267. });
  1268. return;
  1269. }
  1270. // Check if the database is capable of performing a backup
  1271. obj.db.checkBackupCapability(function (err, msg) { if (msg != null) { obj.addServerWarning(msg, true) } });
  1272. // Load configuration for database if needed
  1273. if (obj.args.loadconfigfromdb) {
  1274. var key = null;
  1275. if (typeof obj.args.configkey == 'string') { key = obj.args.configkey; }
  1276. else if (typeof obj.args.loadconfigfromdb == 'string') { key = obj.args.loadconfigfromdb; }
  1277. if (key == null) { console.log("Error, --configkey is required."); process.exit(); return; }
  1278. obj.db.getAllConfigFiles(key, function (configFiles) {
  1279. if (configFiles == null) { console.log("Error, no configuration files found or invalid configkey."); process.exit(); return; }
  1280. if (!configFiles['config.json']) { console.log("Error, could not file config.json from database."); process.exit(); return; }
  1281. if (typeof configFiles['config.json'] == 'object') { configFiles['config.json'] = configFiles['config.json'].toString(); }
  1282. if (configFiles['config.json'].charCodeAt(0) == 65279) { configFiles['config.json'] = configFiles['config.json'].substring(1); }
  1283. obj.configurationFiles = configFiles;
  1284. // Parse the new configuration file
  1285. var config2 = null;
  1286. try { config2 = JSON.parse(configFiles['config.json']); } catch (ex) { console.log('Error, unable to parse config.json from database.', ex); process.exit(); return; }
  1287. // Set the command line arguments to the config file if they are not present
  1288. if (!config2.settings) { config2.settings = {}; }
  1289. for (i in args) { config2.settings[i] = args[i]; }
  1290. // Lower case all keys in the config file
  1291. common.objKeysToLower(config2, ['ldapoptions', 'defaultuserwebstate', 'forceduserwebstate', 'httpheaders', 'telegram/proxy']);
  1292. // Grab some of the values from the original config.json file if present.
  1293. config2['mysql'] = config['mysql'];
  1294. config2['mariadb'] = config['mariadb'];
  1295. config2['mongodb'] = config['mongodb'];
  1296. config2['mongodbcol'] = config['mongodbcol'];
  1297. config2['dbencryptkey'] = config['dbencryptkey'];
  1298. config2['acebase'] = config['acebase'];
  1299. config2['sqlite3'] = config['sqlite3'];
  1300. // We got a new config.json from the database, let's use it.
  1301. config = obj.config = config2;
  1302. obj.StartEx1b();
  1303. });
  1304. } else {
  1305. config = obj.config = getConfig(obj.args.vault == null);
  1306. obj.StartEx1b();
  1307. }
  1308. });
  1309. }
  1310. );
  1311. };
  1312. // Time to start the server of real.
  1313. obj.StartEx1b = async function () {
  1314. var i;
  1315. // Add NodeJS version warning if needed
  1316. if (Number(process.version.match(/^v(\d+\.\d+)/)[1]) < 16) { addServerWarning("MeshCentral will require Node v16 or above in the future, your current version is " + process.version + "."); }
  1317. // Setup certificate operations
  1318. obj.certificateOperations = require('./certoperations.js').CertificateOperations(obj);
  1319. // Linux format /var/log/auth.log
  1320. if (obj.config.settings.authlog != null) {
  1321. obj.fs.open(obj.config.settings.authlog, 'a', function (err, fd) {
  1322. if (err == null) { obj.authlogfile = fd; } else { console.log('ERROR: Unable to open: ' + obj.config.settings.authlog); }
  1323. })
  1324. }
  1325. // Start CrowdSec bouncer if needed: https://www.crowdsec.net/
  1326. if (typeof obj.args.crowdsec == 'object') { obj.crowdSecBounser = require('./crowdsec.js').CreateCrowdSecBouncer(obj, obj.args.crowdsec); }
  1327. // Check if self update is allowed. If running as a Windows service, self-update is not possible.
  1328. if (obj.fs.existsSync(obj.path.join(__dirname, 'daemon'))) { obj.serverSelfWriteAllowed = false; }
  1329. // If we are targetting a specific version, update now.
  1330. if ((obj.serverSelfWriteAllowed == true) && (typeof obj.args.selfupdate == 'string')) {
  1331. obj.args.selfupdate = obj.args.selfupdate.toLowerCase();
  1332. if (getCurrentVersion() !== obj.args.selfupdate) { obj.performServerUpdate(); return; } // We are targetting a specific version, run self update now.
  1333. }
  1334. // Write the server state
  1335. obj.updateServerState('state', 'starting');
  1336. if (process.pid) { obj.updateServerState('server-pid', process.pid); }
  1337. if (process.ppid) { obj.updateServerState('server-parent-pid', process.ppid); }
  1338. // Read environment variables. For a subset of arguments, we allow them to be read from environment variables.
  1339. const xenv = ['user', 'port', 'mpsport', 'mpsaliasport', 'redirport', 'rediraliasport', 'exactport', 'debug'];
  1340. for (i in xenv) { if ((obj.args[xenv[i]] == null) && (process.env['mesh' + xenv[i]])) { obj.args[xenv[i]] = obj.common.toNumber(process.env['mesh' + xenv[i]]); } }
  1341. // Validate the domains, this is used for multi-hosting
  1342. if (obj.config.domains == null) { obj.config.domains = {}; }
  1343. if (obj.config.domains[''] == null) { obj.config.domains[''] = {}; }
  1344. if (obj.config.domains[''].dns != null) { console.log("ERROR: Default domain can't have a DNS name."); return; }
  1345. var xdomains = {}; for (i in obj.config.domains) { xdomains[i.toLowerCase()] = obj.config.domains[i]; } obj.config.domains = xdomains;
  1346. var bannedDomains = ['public', 'private', 'images', 'scripts', 'styles', 'views']; // List of banned domains
  1347. for (i in obj.config.domains) { for (var j in bannedDomains) { if (i == bannedDomains[j]) { console.log("ERROR: Domain '" + i + "' is not allowed domain name in config.json."); delete obj.config.domains[i]; } } }
  1348. for (i in obj.config.domains) { if ((i.length > 64) || (Buffer.from(i).length > 64)) { console.log("ERROR: Domain '" + i + "' is longer that 64 bytes, this is not allowed."); delete obj.config.domains[i]; } }
  1349. for (i in obj.config.domains) {
  1350. // Remove any domains that start with underscore
  1351. if (i.startsWith('_')) { delete obj.config.domains[i]; continue; }
  1352. // Apply default domain settings if present
  1353. if (typeof obj.config.domaindefaults == 'object') { for (var j in obj.config.domaindefaults) { if (obj.config.domains[i][j] == null) { obj.config.domains[i][j] = obj.config.domaindefaults[j]; } } }
  1354. // Perform domain setup
  1355. if (typeof obj.config.domains[i] != 'object') { console.log("ERROR: Invalid domain configuration in config.json."); process.exit(); return; }
  1356. if ((i.length > 0) && (i[0] == '_')) { delete obj.config.domains[i]; continue; } // Remove any domains with names that start with _
  1357. if (typeof config.domains[i].auth == 'string') { config.domains[i].auth = config.domains[i].auth.toLowerCase(); }
  1358. if (obj.config.domains[i].limits == null) { obj.config.domains[i].limits = {}; }
  1359. if (obj.config.domains[i].dns == null) { obj.config.domains[i].url = (i == '') ? '/' : ('/' + i + '/'); } else { obj.config.domains[i].url = '/'; }
  1360. obj.config.domains[i].id = i;
  1361. if ((typeof obj.config.domains[i].maxdeviceview != 'number') || (obj.config.domains[i].maxdeviceview < 1)) { delete obj.config.domains[i].maxdeviceview; }
  1362. if (typeof obj.config.domains[i].loginkey == 'string') { obj.config.domains[i].loginkey = [obj.config.domains[i].loginkey]; }
  1363. if ((obj.config.domains[i].loginkey != null) && (obj.common.validateAlphaNumericArray(obj.config.domains[i].loginkey, 1, 128) == false)) { console.log("ERROR: Invalid login key, must be alpha-numeric string with no spaces."); process.exit(); return; }
  1364. if (typeof obj.config.domains[i].agentkey == 'string') { obj.config.domains[i].agentkey = [obj.config.domains[i].agentkey]; }
  1365. if ((obj.config.domains[i].agentkey != null) && (obj.common.validateAlphaNumericArray(obj.config.domains[i].agentkey, 1, 128) == false)) { console.log("ERROR: Invalid agent key, must be alpha-numeric string with no spaces."); process.exit(); return; }
  1366. obj.config.domains[i].userallowedip = obj.config.domains[i].userallowedip = readIpListFromFile(obj.config.domains[i].userallowedip);
  1367. obj.config.domains[i].userblockedip = obj.config.domains[i].userblockedip = readIpListFromFile(obj.config.domains[i].userblockedip);
  1368. obj.config.domains[i].agentallowedip = obj.config.domains[i].agentallowedip = readIpListFromFile(obj.config.domains[i].agentallowedip);
  1369. obj.config.domains[i].agentblockedip = obj.config.domains[i].agentblockedip = readIpListFromFile(obj.config.domains[i].agentblockedip);
  1370. if (typeof obj.config.domains[i].userallowedip == 'string') { if (obj.config.domains[i].userallowedip == '') { delete obj.config.domains[i].userallowedip; } else { obj.config.domains[i].userallowedip = obj.config.domains[i].userallowedip.split(' ').join('').split(','); } }
  1371. if (typeof obj.config.domains[i].userblockedip == 'string') { if (obj.config.domains[i].userblockedip == '') { delete obj.config.domains[i].userblockedip; } else { obj.config.domains[i].userblockedip = obj.config.domains[i].userblockedip.split(' ').join('').split(','); } }
  1372. if (typeof obj.config.domains[i].agentallowedip == 'string') { if (obj.config.domains[i].agentallowedip == '') { delete obj.config.domains[i].agentallowedip; } else { obj.config.domains[i].agentallowedip = obj.config.domains[i].agentallowedip.split(' ').join('').split(','); } }
  1373. if (typeof obj.config.domains[i].agentblockedip == 'string') { if (obj.config.domains[i].agentblockedip == '') { delete obj.config.domains[i].agentblockedip; } else { obj.config.domains[i].agentblockedip = obj.config.domains[i].agentblockedip.split(' ').join('').split(','); } }
  1374. if (typeof obj.config.domains[i].ignoreagenthashcheck == 'string') { if (obj.config.domains[i].ignoreagenthashcheck == '') { delete obj.config.domains[i].ignoreagenthashcheck; } else { obj.config.domains[i].ignoreagenthashcheck = obj.config.domains[i].ignoreagenthashcheck.split(','); } }
  1375. if (typeof obj.config.domains[i].allowedorigin == 'string') { if (obj.config.domains[i].allowedorigin == '') { delete obj.config.domains[i].allowedorigin; } else { obj.config.domains[i].allowedorigin = obj.config.domains[i].allowedorigin.split(','); } }
  1376. if ((obj.config.domains[i].passwordrequirements != null) && (typeof obj.config.domains[i].passwordrequirements == 'object')) {
  1377. if (typeof obj.config.domains[i].passwordrequirements.skip2factor == 'string') {
  1378. obj.config.domains[i].passwordrequirements.skip2factor = obj.config.domains[i].passwordrequirements.skip2factor.split(',');
  1379. } else {
  1380. delete obj.config.domains[i].passwordrequirements.skip2factor;
  1381. }
  1382. // Fix the list of users to add "user/domain/" if needed
  1383. if (Array.isArray(obj.config.domains[i].passwordrequirements.logintokens)) {
  1384. var newValues = [];
  1385. for (var j in obj.config.domains[i].passwordrequirements.logintokens) {
  1386. var splitVal = obj.config.domains[i].passwordrequirements.logintokens[j].split('/');;
  1387. if (splitVal.length == 1) { newValues.push('user/' + i + '/' + splitVal[0]); }
  1388. if (splitVal.length == 2) { newValues.push('user/' + splitVal[0] + '/' + splitVal[1]); }
  1389. if (splitVal.length == 3) { newValues.push(splitVal[0] + '/' + splitVal[1] + '/' + splitVal[2]); }
  1390. }
  1391. obj.config.domains[i].passwordrequirements.logintokens = newValues;
  1392. }
  1393. }
  1394. if ((obj.config.domains[i].auth == 'ldap') && (typeof obj.config.domains[i].ldapoptions != 'object')) {
  1395. if (i == '') { console.log("ERROR: Default domain is LDAP, but is missing LDAPOptions."); } else { console.log("ERROR: Domain '" + i + "' is LDAP, but is missing LDAPOptions."); }
  1396. process.exit();
  1397. return;
  1398. }
  1399. if ((obj.config.domains[i].auth == 'ldap') || (obj.config.domains[i].auth == 'sspi')) { obj.config.domains[i].newaccounts = 0; } // No new accounts allowed in SSPI/LDAP authentication modes.
  1400. if (obj.config.domains[i].sitestyle == null) { obj.config.domains[i].sitestyle = 2; } // Default to site style #2
  1401. // Convert newAccountsRights from a array of strings to flags number.
  1402. obj.config.domains[i].newaccountsrights = obj.common.meshServerRightsArrayToNumber(obj.config.domains[i].newaccountsrights);
  1403. if (typeof (obj.config.domains[i].newaccountsrights) != 'number') { delete obj.config.domains[i].newaccountsrights; }
  1404. // Check if there is a web views path and/or web public path for this domain
  1405. if ((__dirname.endsWith('/node_modules/meshcentral')) || (__dirname.endsWith('\\node_modules\\meshcentral')) || (__dirname.endsWith('/node_modules/meshcentral/')) || (__dirname.endsWith('\\node_modules\\meshcentral\\'))) {
  1406. if ((obj.config.domains[i].webviewspath == null) && (obj.fs.existsSync(obj.path.join(__dirname, '../../meshcentral-web-' + i + '/views')))) { obj.config.domains[i].webviewspath = obj.path.join(__dirname, '../../meshcentral-web-' + i + '/views'); }
  1407. if ((obj.config.domains[i].webpublicpath == null) && (obj.fs.existsSync(obj.path.join(__dirname, '../../meshcentral-web-' + i + '/public')))) { obj.config.domains[i].webpublicpath = obj.path.join(__dirname, '../../meshcentral-web-' + i + '/public'); }
  1408. if ((obj.config.domains[i].webemailspath == null) && (obj.fs.existsSync(obj.path.join(__dirname, '../../meshcentral-web-' + i + '/emails')))) { obj.config.domains[i].webemailspath = obj.path.join(__dirname, '../../meshcentral-web-' + i + '/emails'); }
  1409. } else {
  1410. if ((obj.config.domains[i].webviewspath == null) && (obj.fs.existsSync(obj.path.join(__dirname, '../meshcentral-web-' + i + '/views')))) { obj.config.domains[i].webviewspath = obj.path.join(__dirname, '../meshcentral-web-' + i + '/views'); }
  1411. if ((obj.config.domains[i].webpublicpath == null) && (obj.fs.existsSync(obj.path.join(__dirname, '../meshcentral-web-' + i + '/public')))) { obj.config.domains[i].webpublicpath = obj.path.join(__dirname, '../meshcentral-web-' + i + '/public'); }
  1412. if ((obj.config.domains[i].webemailspath == null) && (obj.fs.existsSync(obj.path.join(__dirname, '../meshcentral-web-' + i + '/emails')))) { obj.config.domains[i].webemailspath = obj.path.join(__dirname, '../meshcentral-web-' + i + '/emails'); }
  1413. }
  1414. // Check agent customization if any
  1415. if (typeof obj.config.domains[i].agentcustomization == 'object') {
  1416. if (typeof obj.config.domains[i].agentcustomization.displayname != 'string') { delete obj.config.domains[i].agentcustomization.displayname; } else { obj.config.domains[i].agentcustomization.displayname = obj.config.domains[i].agentcustomization.displayname.split('\r').join('').split('\n').join(''); }
  1417. if (typeof obj.config.domains[i].agentcustomization.description != 'string') { delete obj.config.domains[i].agentcustomization.description; } else { obj.config.domains[i].agentcustomization.description = obj.config.domains[i].agentcustomization.description.split('\r').join('').split('\n').join(''); }
  1418. if (typeof obj.config.domains[i].agentcustomization.companyname != 'string') { delete obj.config.domains[i].agentcustomization.companyname; } else { obj.config.domains[i].agentcustomization.companyname = obj.config.domains[i].agentcustomization.companyname.split('\r').join('').split('\n').join(''); }
  1419. if (typeof obj.config.domains[i].agentcustomization.servicename != 'string') { delete obj.config.domains[i].agentcustomization.servicename; } else { obj.config.domains[i].agentcustomization.servicename = obj.config.domains[i].agentcustomization.servicename.split('\r').join('').split('\n').join('').split(' ').join('').split('"').join('').split('\'').join('').split('>').join('').split('<').join('').split('/').join('').split('\\').join(''); }
  1420. if (typeof obj.config.domains[i].agentcustomization.image != 'string') { delete obj.config.domains[i].agentcustomization.image; } else { try { obj.config.domains[i].agentcustomization.image = 'data:image/png;base64,' + Buffer.from(obj.fs.readFileSync(obj.getConfigFilePath(obj.config.domains[i].agentcustomization.image)), 'binary').toString('base64'); } catch (ex) { console.log(ex); delete obj.config.domains[i].agentcustomization.image; } }
  1421. } else {
  1422. delete obj.config.domains[i].agentcustomization;
  1423. }
  1424. // Convert user consent flags
  1425. if (typeof obj.config.domains[i].userconsentflags == 'object') {
  1426. var flags = 0;
  1427. if (obj.config.domains[i].userconsentflags.desktopnotify == true) { flags |= 1; }
  1428. if (obj.config.domains[i].userconsentflags.terminalnotify == true) { flags |= 2; }
  1429. if (obj.config.domains[i].userconsentflags.filenotify == true) { flags |= 4; }
  1430. if (obj.config.domains[i].userconsentflags.desktopprompt == true) { flags |= 8; }
  1431. if (obj.config.domains[i].userconsentflags.terminalprompt == true) { flags |= 16; }
  1432. if (obj.config.domains[i].userconsentflags.fileprompt == true) { flags |= 32; }
  1433. if (obj.config.domains[i].userconsentflags.desktopprivacybar == true) { flags |= 64; }
  1434. obj.config.domains[i].userconsentflags = flags;
  1435. }
  1436. // If we have Intel AMT manager settings, take a look at them here.
  1437. if (typeof obj.config.domains[i].amtmanager == 'object') {
  1438. if (typeof obj.config.domains[i].amtmanager.tlsrootcert == 'object') {
  1439. obj.config.domains[i].amtmanager.tlsrootcert2 = obj.certificateOperations.loadGenericCertAndKey(obj.config.domains[i].amtmanager.tlsrootcert);
  1440. if (obj.config.domains[i].amtmanager.tlsrootcert2 == null) { // Show an error message if needed
  1441. if (i == '') {
  1442. addServerWarning("Unable to load Intel AMT TLS root certificate for default domain.", 5);
  1443. } else {
  1444. addServerWarning("Unable to load Intel AMT TLS root certificate for domain " + i + ".", 6, [i]);
  1445. }
  1446. }
  1447. }
  1448. }
  1449. // Check agentfileinfo
  1450. if (typeof obj.config.domains[i].agentfileinfo == 'object') {
  1451. if ((obj.config.domains[i].agentfileinfo.fileversionnumber != null) && (obj.common.parseVersion(obj.config.domains[i].agentfileinfo.fileversionnumber) == null)) { delete obj.config.domains[i].agentfileinfo.fileversionnumber; }
  1452. if ((obj.config.domains[i].agentfileinfo.productversionnumber != null) && (obj.common.parseVersion(obj.config.domains[i].agentfileinfo.productversionnumber) == null)) { delete obj.config.domains[i].agentfileinfo.productversionnumber; }
  1453. if ((obj.config.domains[i].agentfileinfo.fileversionnumber == null) && (typeof obj.config.domains[i].agentfileinfo.fileversion == 'string') && (obj.common.parseVersion(obj.config.domains[i].agentfileinfo.fileversion) != null)) { obj.config.domains[i].agentfileinfo.fileversionnumber = obj.config.domains[i].agentfileinfo.fileversion; }
  1454. if (typeof obj.config.domains[i].agentfileinfo.icon == 'string') {
  1455. // Load the agent .ico file
  1456. var icon = null;
  1457. try { icon = require('./authenticode.js').loadIcon(obj.path.join(obj.datapath, obj.config.domains[i].agentfileinfo.icon)); } catch (ex) { }
  1458. if (icon != null) {
  1459. // The icon file was correctly loaded
  1460. obj.config.domains[i].agentfileinfo.icon = icon;
  1461. } else {
  1462. // Failed to load the icon file, display a server warning
  1463. addServerWarning("Unable to load agent icon file: " + obj.config.domains[i].agentfileinfo.icon + ".", 23, [obj.config.domains[i].agentfileinfo.icon]);
  1464. delete obj.config.domains[i].agentfileinfo.icon;
  1465. }
  1466. } else {
  1467. // Invalid icon file path
  1468. delete obj.config.domains[i].agentfileinfo.icon;
  1469. }
  1470. if (typeof obj.config.domains[i].agentfileinfo.logo == 'string') {
  1471. // Load the agent .bmp file
  1472. var logo = null;
  1473. try { logo = require('./authenticode.js').loadBitmap(obj.path.join(obj.datapath, obj.config.domains[i].agentfileinfo.logo)); } catch (ex) { }
  1474. if (logo != null) {
  1475. // The logo file was correctly loaded
  1476. obj.config.domains[i].agentfileinfo.logo = logo;
  1477. } else {
  1478. // Failed to load the icon file, display a server warning
  1479. addServerWarning("Unable to load agent logo file: " + obj.config.domains[i].agentfileinfo.logo + ".", 24, [obj.config.domains[i].agentfileinfo.logo]);
  1480. delete obj.config.domains[i].agentfileinfo.logo;
  1481. }
  1482. } else {
  1483. // Invalid icon file path
  1484. delete obj.config.domains[i].agentfileinfo.logo;
  1485. }
  1486. }
  1487. }
  1488. // Log passed arguments into Windows Service Log
  1489. //if (obj.servicelog != null) { var s = ''; for (i in obj.args) { if (i != '_') { if (s.length > 0) { s += ', '; } s += i + "=" + obj.args[i]; } } logInfoEvent('MeshServer started with arguments: ' + s); }
  1490. // Look at passed in arguments
  1491. if ((obj.args.user != null) && (typeof obj.args.user != 'string')) { delete obj.args.user; }
  1492. if ((obj.args.ciralocalfqdn != null) && ((obj.args.lanonly == true) || (obj.args.wanonly == true))) { addServerWarning("CIRA local FQDN's ignored when server in LAN-only or WAN-only mode.", 7); }
  1493. if ((obj.args.ciralocalfqdn != null) && (obj.args.ciralocalfqdn.split(',').length > 4)) { addServerWarning("Can't have more than 4 CIRA local FQDN's. Ignoring value.", 8); obj.args.ciralocalfqdn = null; }
  1494. if (obj.args.ignoreagenthashcheck === true) { addServerWarning("Agent hash checking is being skipped, this is unsafe.", 9); }
  1495. if (obj.args.port == null || typeof obj.args.port != 'number') { obj.args.port = 443; }
  1496. if (obj.args.aliasport != null && (typeof obj.args.aliasport != 'number')) obj.args.aliasport = null;
  1497. if (obj.args.mpsport == null || typeof obj.args.mpsport != 'number') obj.args.mpsport = 4433;
  1498. if (obj.args.mpsaliasport != null && (typeof obj.args.mpsaliasport != 'number')) obj.args.mpsaliasport = null;
  1499. if (obj.args.rediraliasport != null && (typeof obj.args.rediraliasport != 'number')) obj.args.rediraliasport = null;
  1500. if (obj.args.redirport == null) obj.args.redirport = 80;
  1501. if (obj.args.minifycore == null) obj.args.minifycore = false;
  1502. if (typeof obj.args.agentidletimeout != 'number') { obj.args.agentidletimeout = 150000; } else { obj.args.agentidletimeout *= 1000 } // Default agent idle timeout is 2m, 30sec.
  1503. if ((obj.args.lanonly != true) && (typeof obj.args.webrtconfig == 'object')) { // fix incase you are using an old mis-spelt webrtconfig
  1504. obj.args.webrtcconfig = obj.args.webrtconfig;
  1505. delete obj.args.webrtconfig;
  1506. }
  1507. if ((obj.args.lanonly != true) && (obj.args.webrtcconfig == null)) { obj.args.webrtcconfig = { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }, { urls: 'stun:stun.cloudflare.com:3478' }] }; } // Setup default WebRTC STUN servers
  1508. else if ((obj.args.lanonly != true) && (typeof obj.args.webrtcconfig == 'object')) {
  1509. if (obj.args.webrtcconfig.iceservers) { // webrtc is case-sensitive, so must rename iceservers to iceServers!
  1510. obj.args.webrtcconfig.iceServers = obj.args.webrtcconfig.iceservers;
  1511. delete obj.args.webrtcconfig.iceservers;
  1512. }
  1513. }
  1514. if (typeof obj.args.ignoreagenthashcheck == 'string') { if (obj.args.ignoreagenthashcheck == '') { delete obj.args.ignoreagenthashcheck; } else { obj.args.ignoreagenthashcheck = obj.args.ignoreagenthashcheck.split(','); } }
  1515. // Setup a site administrator
  1516. if ((obj.args.admin) && (typeof obj.args.admin == 'string')) {
  1517. var adminname = obj.args.admin.split('/');
  1518. if (adminname.length == 1) { adminname = 'user//' + adminname[0]; }
  1519. else if (adminname.length == 2) { adminname = 'user/' + adminname[0] + '/' + adminname[1]; }
  1520. else { console.log("Invalid administrator name."); process.exit(); return; }
  1521. obj.db.Get(adminname, function (err, user) {
  1522. if (user.length != 1) { console.log("Invalid user name."); process.exit(); return; }
  1523. user[0].siteadmin = 4294967295; // 0xFFFFFFFF
  1524. obj.db.Set(user[0], function () {
  1525. if (user[0].domain == '') { console.log('User ' + user[0].name + ' set to site administrator.'); } else { console.log("User " + user[0].name + " of domain " + user[0].domain + " set to site administrator."); }
  1526. process.exit();
  1527. return;
  1528. });
  1529. });
  1530. return;
  1531. }
  1532. // Remove a site administrator
  1533. if ((obj.args.unadmin) && (typeof obj.args.unadmin == 'string')) {
  1534. var adminname = obj.args.unadmin.split('/');
  1535. if (adminname.length == 1) { adminname = 'user//' + adminname[0]; }
  1536. else if (adminname.length == 2) { adminname = 'user/' + adminname[0] + '/' + adminname[1]; }
  1537. else { console.log("Invalid administrator name."); process.exit(); return; }
  1538. obj.db.Get(adminname, function (err, user) {
  1539. if (user.length != 1) { console.log("Invalid user name."); process.exit(); return; }
  1540. if (user[0].siteadmin) { delete user[0].siteadmin; }
  1541. obj.db.Set(user[0], function () {
  1542. if (user[0].domain == '') { console.log("User " + user[0].name + " is not a site administrator."); } else { console.log("User " + user[0].name + " of domain " + user[0].domain + " is not a site administrator."); }
  1543. process.exit();
  1544. return;
  1545. });
  1546. });
  1547. return;
  1548. }
  1549. // Setup agent error log
  1550. if ((obj.config) && (obj.config.settings) && (obj.config.settings.agentlogdump != null)) {
  1551. obj.fs.open(obj.path.join(obj.datapath, 'agenterrorlogs.txt'), 'a', function (err, fd) { obj.agentErrorLog = fd; })
  1552. }
  1553. // Perform other database cleanup
  1554. obj.db.cleanup();
  1555. // Set all nodes to power state of unknown (0)
  1556. obj.db.storePowerEvent({ time: new Date(), nodeid: '*', power: 0, s: 1 }, obj.multiServer); // s:1 indicates that the server is starting up.
  1557. // Read or setup database configuration values
  1558. obj.db.Get('dbconfig', function (err, dbconfig) {
  1559. if ((dbconfig != null) && (dbconfig.length == 1)) { obj.dbconfig = dbconfig[0]; } else { obj.dbconfig = { _id: 'dbconfig', version: 1 }; }
  1560. if (obj.dbconfig.amtWsEventSecret == null) { obj.crypto.randomBytes(32, function (err, buf) { obj.dbconfig.amtWsEventSecret = buf.toString('hex'); obj.db.Set(obj.dbconfig); }); }
  1561. // This is used by the user to create a username/password for a Intel AMT WSMAN event subscription
  1562. if (obj.args.getwspass) {
  1563. if (obj.args.getwspass.length == 64) {
  1564. obj.crypto.randomBytes(6, function (err, buf) {
  1565. while (obj.dbconfig.amtWsEventSecret == null) { process.nextTick(); }
  1566. const username = buf.toString('hex');
  1567. const nodeid = obj.args.getwspass;
  1568. const pass = obj.crypto.createHash('sha384').update(username.toLowerCase() + ':' + nodeid + ':' + obj.dbconfig.amtWsEventSecret).digest('base64').substring(0, 12).split('/').join('x').split('\\').join('x');
  1569. console.log("--- Intel(r) AMT WSMAN eventing credentials ---");
  1570. console.log("Username: " + username);
  1571. console.log("Password: " + pass);
  1572. console.log("Argument: " + nodeid);
  1573. process.exit();
  1574. });
  1575. } else {
  1576. console.log("Invalid NodeID.");
  1577. process.exit();
  1578. }
  1579. return;
  1580. }
  1581. // Setup the task manager
  1582. if ((obj.config) && (obj.config.settings) && (obj.config.settings.taskmanager == true)) {
  1583. obj.taskManager = require('./taskmanager').createTaskManager(obj);
  1584. }
  1585. // Start plugin manager if configuration allows this.
  1586. if ((obj.config) && (obj.config.settings) && (obj.config.settings.plugins != null) && (obj.config.settings.plugins != false) && ((typeof obj.config.settings.plugins != 'object') || (obj.config.settings.plugins.enabled != false))) {
  1587. obj.pluginHandler = require('./pluginHandler.js').pluginHandler(obj);
  1588. }
  1589. // Load the default meshcore and meshcmd
  1590. obj.updateMeshCore();
  1591. obj.updateMeshCmd();
  1592. // Setup and start the redirection server if needed. We must start the redirection server before Let's Encrypt.
  1593. if ((obj.args.redirport != null) && (typeof obj.args.redirport == 'number') && (obj.args.redirport != 0)) {
  1594. obj.redirserver = require('./redirserver.js').CreateRedirServer(obj, obj.db, obj.args, obj.StartEx2);
  1595. } else {
  1596. obj.StartEx2(); // If not needed, move on.
  1597. }
  1598. });
  1599. }
  1600. // Done starting the redirection server, go on to load the server certificates
  1601. obj.StartEx2 = function () {
  1602. // Load server certificates
  1603. obj.certificateOperations.GetMeshServerCertificate(obj.args, obj.config, function (certs) {
  1604. // Get the current node version
  1605. if ((obj.config.letsencrypt == null) || (obj.redirserver == null)) {
  1606. obj.StartEx3(certs); // Just use the configured certificates
  1607. } else if ((obj.config.letsencrypt != null) && (obj.config.letsencrypt.nochecks == true)) {
  1608. // Use Let's Encrypt with no checking
  1609. obj.letsencrypt = require('./letsencrypt.js').CreateLetsEncrypt(obj);
  1610. obj.letsencrypt.getCertificate(certs, obj.StartEx3); // Use Let's Encrypt with no checking, use at your own risk.
  1611. } else {
  1612. // Check Let's Encrypt settings
  1613. var leok = true;
  1614. if ((typeof obj.config.letsencrypt.names != 'string') && (typeof obj.config.settings.cert == 'string')) { obj.config.letsencrypt.names = obj.config.settings.cert; }
  1615. if (typeof obj.config.letsencrypt.email != 'string') { leok = false; addServerWarning("Missing Let's Encrypt email address.", 10); }
  1616. else if (typeof obj.config.letsencrypt.names != 'string') { leok = false; addServerWarning("Invalid Let's Encrypt host names.", 11); }
  1617. else if (obj.config.letsencrypt.names.indexOf('*') >= 0) { leok = false; addServerWarning("Invalid Let's Encrypt names, can't contain a *.", 12); }
  1618. else if (obj.config.letsencrypt.email.split('@').length != 2) { leok = false; addServerWarning("Invalid Let's Encrypt email address.", 10); }
  1619. else if (obj.config.letsencrypt.email.trim() !== obj.config.letsencrypt.email) { leok = false; addServerWarning("Invalid Let's Encrypt email address.", 10); }
  1620. else {
  1621. const le = require('./letsencrypt.js');
  1622. try { obj.letsencrypt = le.CreateLetsEncrypt(obj); } catch (ex) { console.log(ex); }
  1623. if (obj.letsencrypt == null) { addServerWarning("Unable to setup Let's Encrypt module.", 13); leok = false; }
  1624. }
  1625. if (leok == true) {
  1626. // Check that the email address domain MX resolves.
  1627. require('dns').resolveMx(obj.config.letsencrypt.email.split('@')[1], function (err, addresses) {
  1628. if (err == null) {
  1629. // Check that all names resolve
  1630. checkResolveAll(obj.config.letsencrypt.names.split(','), function (err) {
  1631. if (err == null) {
  1632. obj.letsencrypt.getCertificate(certs, obj.StartEx3); // Use Let's Encrypt
  1633. } else {
  1634. for (var i in err) { addServerWarning("Invalid Let's Encrypt names, unable to resolve: " + err[i], 14, [err[i]]); }
  1635. obj.StartEx3(certs); // Let's Encrypt did not load, just use the configured certificates
  1636. }
  1637. });
  1638. } else {
  1639. addServerWarning("Invalid Let's Encrypt email address, unable to resolve: " + obj.config.letsencrypt.email.split('@')[1], 15, [obj.config.letsencrypt.email.split('@')[1]]);
  1640. obj.StartEx3(certs); // Let's Encrypt did not load, just use the configured certificates
  1641. }
  1642. });
  1643. } else {
  1644. obj.StartEx3(certs); // Let's Encrypt did not load, just use the configured certificates
  1645. }
  1646. }
  1647. });
  1648. };
  1649. // Start the server with the given certificates, but check if we have web certificates to load
  1650. obj.StartEx3 = function (certs) {
  1651. obj.certificates = certs;
  1652. obj.certificateOperations.acceleratorStart(certs); // Set the state of the accelerators
  1653. // Load any domain web certificates
  1654. for (var i in obj.config.domains) {
  1655. // Load any Intel AMT ACM activation certificates
  1656. if (obj.config.domains[i].amtacmactivation == null) { obj.config.domains[i].amtacmactivation = {}; }
  1657. obj.certificateOperations.loadIntelAmtAcmCerts(obj.config.domains[i].amtacmactivation);
  1658. if (obj.config.domains[i].amtacmactivation.acmCertErrors != null) { for (var j in obj.config.domains[i].amtacmactivation.acmCertErrors) { obj.addServerWarning(obj.config.domains[i].amtacmactivation.acmCertErrors[j]); } }
  1659. if (typeof obj.config.domains[i].certurl == 'string') {
  1660. obj.supportsProxyCertificatesRequest = true; // If a certurl is set, enable proxy cert requests
  1661. // Then, fix the URL and add 'https://' if needed
  1662. if (obj.config.domains[i].certurl.indexOf('://') < 0) { obj.config.domains[i].certurl = 'https://' + obj.config.domains[i].certurl; }
  1663. }
  1664. }
  1665. // Load CloudFlare trusted proxies list if needed
  1666. if ((obj.config.settings.trustedproxy != null) && (typeof obj.config.settings.trustedproxy == 'string') && (obj.config.settings.trustedproxy.toLowerCase() == 'cloudflare')) {
  1667. obj.config.settings.extrascriptsrc = 'ajax.cloudflare.com'; // Add CloudFlare as a trusted script source. This allows for CloudFlare's RocketLoader feature.
  1668. delete obj.args.trustedproxy;
  1669. delete obj.config.settings.trustedproxy;
  1670. obj.certificateOperations.loadTextFile('https://www.cloudflare.com/ips-v4', null, function (url, data, tag) {
  1671. if (data != null) {
  1672. if (Array.isArray(obj.args.trustedproxy) == false) { obj.args.trustedproxy = []; }
  1673. const ipranges = data.split('\n');
  1674. for (var i in ipranges) { if (ipranges[i] != '') { obj.args.trustedproxy.push(ipranges[i]); } }
  1675. obj.certificateOperations.loadTextFile('https://www.cloudflare.com/ips-v6', null, function (url, data, tag) {
  1676. if (data != null) {
  1677. var ipranges = data.split('\n');
  1678. for (var i in ipranges) { if (ipranges[i] != '') { obj.args.trustedproxy.push(ipranges[i]); } }
  1679. obj.config.settings.trustedproxy = obj.args.trustedproxy;
  1680. } else {
  1681. addServerWarning("Unable to load CloudFlare trusted proxy IPv6 address list.", 16);
  1682. }
  1683. obj.StartEx4(); // Keep going
  1684. });
  1685. } else {
  1686. addServerWarning("Unable to load CloudFlare trusted proxy IPv4 address list.", 16);
  1687. obj.StartEx4(); // Keep going
  1688. }
  1689. });
  1690. } else {
  1691. obj.StartEx4(); // Keep going
  1692. }
  1693. }
  1694. // Start the server with the given certificates
  1695. obj.StartEx4 = function () {
  1696. var i;
  1697. // If the certificate is un-configured, force LAN-only mode
  1698. if (obj.certificates.CommonName.indexOf('.') == -1) { /*console.log('Server name not configured, running in LAN-only mode.');*/ obj.args.lanonly = true; }
  1699. // Write server version and run mode
  1700. const productionMode = (process.env.NODE_ENV && (process.env.NODE_ENV == 'production'));
  1701. const runmode = (obj.args.lanonly ? 2 : (obj.args.wanonly ? 1 : 0));
  1702. console.log("MeshCentral v" + getCurrentVersion() + ', ' + (["Hybrid (LAN + WAN) mode", "WAN mode", "LAN mode"][runmode]) + (productionMode ? ", Production mode." : '.'));
  1703. // Check that no sub-domains have the same DNS as the parent
  1704. for (i in obj.config.domains) {
  1705. if ((obj.config.domains[i].dns != null) && (obj.certificates.CommonName.toLowerCase() === obj.config.domains[i].dns.toLowerCase())) {
  1706. console.log("ERROR: Server sub-domain can't have same DNS name as the parent."); process.exit(0); return;
  1707. }
  1708. }
  1709. // Load the list of MeshCentral tools
  1710. obj.updateMeshTools();
  1711. // Load MeshAgent translation strings
  1712. try {
  1713. var translationpath = obj.path.join(__dirname, 'agents', 'agent-translations.json');
  1714. const translationpath2 = obj.path.join(obj.datapath, 'agents', 'agent-translations.json');
  1715. if (obj.fs.existsSync(translationpath2)) { translationpath = translationpath2; } // If the agent is present in "meshcentral-data/agents", use that one instead.
  1716. var translations = JSON.parse(obj.fs.readFileSync(translationpath).toString());
  1717. if (translations['zh-chs']) { translations['zh-hans'] = translations['zh-chs']; delete translations['zh-chs']; }
  1718. if (translations['zh-cht']) { translations['zh-hant'] = translations['zh-cht']; delete translations['zh-cht']; }
  1719. // If there is domain customizations to the agent strings, do this here.
  1720. for (var i in obj.config.domains) {
  1721. var domainTranslations = translations;
  1722. if ((typeof obj.config.domains[i].agentcustomization == 'object') && (typeof obj.config.domains[i].agentcustomization.installtext == 'string')) {
  1723. domainTranslations = Object.assign({}, domainTranslations); // Shallow clone
  1724. for (var j in domainTranslations) { delete domainTranslations[j].description; }
  1725. domainTranslations.en.description = obj.config.domains[i].agentcustomization.installtext;
  1726. }
  1727. obj.config.domains[i].agentTranslations = JSON.stringify(domainTranslations);
  1728. }
  1729. } catch (ex) { }
  1730. // Load any domain specific agents
  1731. for (var i in obj.config.domains) { if ((i != '') && (obj.config.domains[i].share == null)) { obj.updateMeshAgentsTable(obj.config.domains[i], function () { }); } }
  1732. // Load the list of mesh agents and install scripts
  1733. if ((obj.args.noagentupdate == 1) || (obj.args.noagentupdate == true)) { for (i in obj.meshAgentsArchitectureNumbers) { obj.meshAgentsArchitectureNumbers[i].update = false; } }
  1734. obj.signMeshAgents(obj.config.domains[''], function () {
  1735. obj.updateMeshAgentsTable(obj.config.domains[''], function () {
  1736. obj.updateMeshAgentInstallScripts();
  1737. // Setup and start the web server
  1738. obj.crypto.randomBytes(48, function (err, buf) {
  1739. // Setup Mesh Multi-Server if needed
  1740. obj.multiServer = require('./multiserver.js').CreateMultiServer(obj, obj.args);
  1741. if (obj.multiServer != null) {
  1742. if ((obj.db.databaseType != 3) || (obj.db.changeStream != true)) { console.log("ERROR: Multi-server support requires use of MongoDB with ReplicaSet and ChangeStream enabled."); process.exit(0); return; }
  1743. if (typeof obj.args.sessionkey != 'string') { console.log("ERROR: Multi-server support requires \"SessionKey\" be set in the settings section of config.json, same key for all servers."); process.exit(0); return; }
  1744. obj.serverId = obj.multiServer.serverid;
  1745. for (var serverid in obj.config.peers.servers) { obj.peerConnectivityByNode[serverid] = {}; }
  1746. }
  1747. // If the server is set to "nousers", allow only loopback unless IP filter is set
  1748. if ((obj.args.nousers == true) && (obj.args.userallowedip == null)) { obj.args.userallowedip = "::1,127.0.0.1"; }
  1749. // Set the session length to 60 minutes if not set and set a random key if needed
  1750. if ((obj.args.sessiontime != null) && ((typeof obj.args.sessiontime != 'number') || (obj.args.sessiontime < 1))) { delete obj.args.sessiontime; }
  1751. if (typeof obj.args.sessionkey != 'string') { obj.args.sessionkey = buf.toString('hex').toUpperCase(); }
  1752. // Create MQTT Broker to hook into webserver and mpsserver
  1753. if ((typeof obj.config.settings.mqtt == 'object') && (typeof obj.config.settings.mqtt.auth == 'object') && (typeof obj.config.settings.mqtt.auth.keyid == 'string') && (typeof obj.config.settings.mqtt.auth.key == 'string')) { obj.mqttbroker = require("./mqttbroker.js").CreateMQTTBroker(obj, obj.db, obj.args); }
  1754. // Start the web server and if needed, the redirection web server.
  1755. obj.webserver = require('./webserver.js').CreateWebServer(obj, obj.db, obj.args, obj.certificates, obj.StartEx5);
  1756. if (obj.redirserver != null) { obj.redirserver.hookMainWebServer(obj.certificates); }
  1757. // Change RelayDNS to a array of strings
  1758. if (typeof obj.args.relaydns == 'string') { obj.args.relaydns = [obj.args.relaydns]; }
  1759. if (obj.common.validateStrArray(obj.args.relaydns, 1) == false) { delete obj.args.relaydns; }
  1760. // Start the HTTP relay web server if needed
  1761. if ((obj.args.relaydns == null) && (typeof obj.args.relayport == 'number') && (obj.args.relayport != 0)) {
  1762. obj.webrelayserver = require('./webrelayserver.js').CreateWebRelayServer(obj, obj.db, obj.args, obj.certificates, function () { });
  1763. }
  1764. // Update proxy certificates
  1765. if (obj.supportsProxyCertificatesRequest == true) { obj.updateProxyCertificates(true); }
  1766. // Setup the Intel AMT event handler
  1767. obj.amtEventHandler = require('./amtevents.js').CreateAmtEventsHandler(obj);
  1768. // Setup the Intel AMT local network scanner
  1769. if (obj.args.wanonly != true) {
  1770. if (obj.args.amtscanner != false) { obj.amtScanner = require('./amtscanner.js').CreateAmtScanner(obj).start(); }
  1771. if (obj.args.meshscanner != false) { obj.meshScanner = require('./meshscanner.js').CreateMeshScanner(obj).start(); }
  1772. }
  1773. // Setup and start the MPS server
  1774. obj.mpsserver = require('./mpsserver.js').CreateMpsServer(obj, obj.db, obj.args, obj.certificates);
  1775. // Setup the Intel AMT manager
  1776. if (obj.args.amtmanager !== false) {
  1777. obj.amtManager = require('./amtmanager.js').CreateAmtManager(obj);
  1778. }
  1779. // Setup and start the legacy swarm server
  1780. if ((obj.certificates.swarmserver != null) && (obj.args.swarmport != null) && (obj.args.swarmport !== 0)) {
  1781. obj.swarmserver = require('./swarmserver.js').CreateSwarmServer(obj, obj.db, obj.args, obj.certificates);
  1782. }
  1783. // Setup the main email server
  1784. if (obj.config.sendgrid != null) {
  1785. // Sendgrid server
  1786. obj.mailserver = require('./meshmail.js').CreateMeshMail(obj);
  1787. obj.mailserver.verify();
  1788. if (obj.args.lanonly == true) { addServerWarning("SendGrid server has limited use in LAN mode.", 17); }
  1789. } else if (obj.config.smtp != null) {
  1790. // SMTP server
  1791. obj.mailserver = require('./meshmail.js').CreateMeshMail(obj);
  1792. obj.mailserver.verify();
  1793. if (obj.args.lanonly == true) { addServerWarning("SMTP server has limited use in LAN mode.", 18); }
  1794. } else if (obj.config.sendmail != null) {
  1795. // Sendmail server
  1796. obj.mailserver = require('./meshmail.js').CreateMeshMail(obj);
  1797. obj.mailserver.verify();
  1798. if (obj.args.lanonly == true) { addServerWarning("SMTP server has limited use in LAN mode.", 18); }
  1799. }
  1800. // Setup the email server for each domain
  1801. for (i in obj.config.domains) {
  1802. if (obj.config.domains[i].sendgrid != null) {
  1803. // Sendgrid server
  1804. obj.config.domains[i].mailserver = require('./meshmail.js').CreateMeshMail(obj, obj.config.domains[i]);
  1805. obj.config.domains[i].mailserver.verify();
  1806. if (obj.args.lanonly == true) { addServerWarning("SendGrid server has limited use in LAN mode.", 17); }
  1807. } else if ((obj.config.domains[i].smtp != null) && (obj.config.domains[i].smtp.host != null) && (obj.config.domains[i].smtp.from != null)) {
  1808. // SMTP server
  1809. obj.config.domains[i].mailserver = require('./meshmail.js').CreateMeshMail(obj, obj.config.domains[i]);
  1810. obj.config.domains[i].mailserver.verify();
  1811. if (obj.args.lanonly == true) { addServerWarning("SMTP server has limited use in LAN mode.", 18); }
  1812. } else if (obj.config.domains[i].sendmail != null) {
  1813. // Sendmail server
  1814. obj.config.domains[i].mailserver = require('./meshmail.js').CreateMeshMail(obj, obj.config.domains[i]);
  1815. obj.config.domains[i].mailserver.verify();
  1816. if (obj.args.lanonly == true) { addServerWarning("SMTP server has limited use in LAN mode.", 18); }
  1817. } else {
  1818. // Setup the parent mail server for this domain
  1819. if (obj.mailserver != null) { obj.config.domains[i].mailserver = obj.mailserver; }
  1820. }
  1821. }
  1822. // Setup SMS gateway
  1823. if (config.sms != null) {
  1824. obj.smsserver = require('./meshsms.js').CreateMeshSMS(obj);
  1825. if ((obj.smsserver != null) && (obj.args.lanonly == true)) { addServerWarning("SMS gateway has limited use in LAN mode.", 19); }
  1826. }
  1827. // Setup user messaging
  1828. if (config.messaging != null) {
  1829. obj.msgserver = require('./meshmessaging.js').CreateServer(obj);
  1830. }
  1831. // Setup web based push notifications
  1832. if ((typeof config.settings.webpush == 'object') && (typeof config.settings.webpush.email == 'string')) {
  1833. obj.webpush = require('web-push');
  1834. var vapidKeys = null;
  1835. try { vapidKeys = JSON.parse(obj.fs.readFileSync(obj.path.join(obj.datapath, 'vapid.json')).toString()); } catch (ex) { }
  1836. if ((vapidKeys == null) || (typeof vapidKeys.publicKey != 'string') || (typeof vapidKeys.privateKey != 'string')) {
  1837. console.log("Generating web push VAPID keys...");
  1838. vapidKeys = obj.webpush.generateVAPIDKeys();
  1839. obj.common.moveOldFiles([obj.path.join(obj.datapath, 'vapid.json')]);
  1840. obj.fs.writeFileSync(obj.path.join(obj.datapath, 'vapid.json'), JSON.stringify(vapidKeys));
  1841. }
  1842. obj.webpush.vapidPublicKey = vapidKeys.publicKey;
  1843. obj.webpush.setVapidDetails('mailto:' + config.settings.webpush.email, vapidKeys.publicKey, vapidKeys.privateKey);
  1844. if (typeof config.settings.webpush.gcmapi == 'string') { webpush.setGCMAPIKey(config.settings.webpush.gcmapi); }
  1845. }
  1846. // Setup Firebase
  1847. if ((config.firebase != null) && (typeof config.firebase.senderid == 'string') && (typeof config.firebase.serverkey == 'string')) {
  1848. obj.firebase = require('./firebase').CreateFirebase(obj, config.firebase.senderid, config.firebase.serverkey);
  1849. } else if ((typeof config.firebaserelay == 'object') && (typeof config.firebaserelay.url == 'string')) {
  1850. // Setup the push messaging relay
  1851. obj.firebase = require('./firebase').CreateFirebaseRelay(obj, config.firebaserelay.url, config.firebaserelay.key);
  1852. } else if (obj.config.settings.publicpushnotifications === true) {
  1853. // Setup the Firebase push messaging relay using https://alt.meshcentral.com, this is the public push notification server.
  1854. obj.firebase = require('./firebase').CreateFirebaseRelay(obj, 'https://alt.meshcentral.com/firebaserelay.aspx');
  1855. }
  1856. // Start periodic maintenance
  1857. obj.maintenanceTimer = setInterval(obj.maintenanceActions, 1000 * 60 * 60); // Run this every hour
  1858. // Dispatch an event that the server is now running
  1859. obj.DispatchEvent(['*'], obj, { etype: 'server', action: 'started', msg: 'Server started' });
  1860. // Plugin hook. Need to run something at server startup? This is the place.
  1861. if (obj.pluginHandler) { obj.pluginHandler.callHook('server_startup'); }
  1862. // Setup the login cookie encryption key
  1863. if ((obj.config) && (obj.config.settings) && (typeof obj.config.settings.logincookieencryptionkey == 'string')) {
  1864. // We have a string, hash it and use that as a key
  1865. try { obj.loginCookieEncryptionKey = Buffer.from(obj.config.settings.logincookieencryptionkey, 'hex'); } catch (ex) { }
  1866. if ((obj.loginCookieEncryptionKey == null) || (obj.loginCookieEncryptionKey.length != 80)) { addServerWarning("Invalid \"LoginCookieEncryptionKey\" in config.json.", 20); obj.loginCookieEncryptionKey = null; }
  1867. }
  1868. // Login cookie encryption key not set, use one from the database
  1869. if (obj.loginCookieEncryptionKey == null) {
  1870. obj.db.Get('LoginCookieEncryptionKey', function (err, docs) {
  1871. if ((docs != null) && (docs.length > 0) && (docs[0].key != null) && (obj.args.logintokengen == null) && (docs[0].key.length >= 160)) {
  1872. obj.loginCookieEncryptionKey = Buffer.from(docs[0].key, 'hex');
  1873. } else {
  1874. obj.loginCookieEncryptionKey = obj.generateCookieKey(); obj.db.Set({ _id: 'LoginCookieEncryptionKey', key: obj.loginCookieEncryptionKey.toString('hex'), time: Date.now() });
  1875. }
  1876. });
  1877. }
  1878. // Load the invitation link encryption key from the database
  1879. obj.db.Get('InvitationLinkEncryptionKey', function (err, docs) {
  1880. if ((docs != null) && (docs.length > 0) && (docs[0].key != null) && (docs[0].key.length >= 160)) {
  1881. obj.invitationLinkEncryptionKey = Buffer.from(docs[0].key, 'hex');
  1882. } else {
  1883. obj.invitationLinkEncryptionKey = obj.generateCookieKey(); obj.db.Set({ _id: 'InvitationLinkEncryptionKey', key: obj.invitationLinkEncryptionKey.toString('hex'), time: Date.now() });
  1884. }
  1885. });
  1886. // Setup Intel AMT hello server
  1887. if ((typeof config.settings.amtprovisioningserver == 'object') && (typeof config.settings.amtprovisioningserver.devicegroup == 'string') && (typeof config.settings.amtprovisioningserver.newmebxpassword == 'string') && (typeof config.settings.amtprovisioningserver.trustedfqdn == 'string') && (typeof config.settings.amtprovisioningserver.ip == 'string')) {
  1888. obj.amtProvisioningServer = require('./amtprovisioningserver').CreateAmtProvisioningServer(obj, config.settings.amtprovisioningserver);
  1889. }
  1890. // Start collecting server stats every 5 minutes
  1891. obj.trafficStats = obj.webserver.getTrafficStats();
  1892. setInterval(function () {
  1893. obj.serverStatsCounter++;
  1894. var hours = 720; // Start with all events lasting 30 days.
  1895. if (((obj.serverStatsCounter) % 2) == 1) { hours = 3; } // Half of the event get removed after 3 hours.
  1896. else if ((Math.floor(obj.serverStatsCounter / 2) % 2) == 1) { hours = 8; } // Another half of the event get removed after 8 hours.
  1897. else if ((Math.floor(obj.serverStatsCounter / 4) % 2) == 1) { hours = 24; } // Another half of the event get removed after 24 hours.
  1898. else if ((Math.floor(obj.serverStatsCounter / 8) % 2) == 1) { hours = 48; } // Another half of the event get removed after 48 hours.
  1899. else if ((Math.floor(obj.serverStatsCounter / 16) % 2) == 1) { hours = 72; } // Another half of the event get removed after 72 hours.
  1900. const expire = new Date();
  1901. expire.setTime(expire.getTime() + (60 * 60 * 1000 * hours));
  1902. // Get traffic data
  1903. var trafficStats = obj.webserver.getTrafficDelta(obj.trafficStats);
  1904. obj.trafficStats = trafficStats.current;
  1905. var data = {
  1906. time: new Date(),
  1907. expire: expire,
  1908. mem: process.memoryUsage(),
  1909. conn: {
  1910. ca: Object.keys(obj.webserver.wsagents).length,
  1911. cu: Object.keys(obj.webserver.wssessions).length,
  1912. us: Object.keys(obj.webserver.wssessions2).length,
  1913. rs: obj.webserver.relaySessionCount
  1914. },
  1915. traffic: trafficStats.delta
  1916. };
  1917. try { data.cpu = require('os').loadavg(); } catch (ex) { }
  1918. if (obj.mpsserver != null) {
  1919. data.conn.am = 0;
  1920. for (var i in obj.mpsserver.ciraConnections) { data.conn.am += obj.mpsserver.ciraConnections[i].length; }
  1921. }
  1922. if (obj.firstStats === true) { delete obj.firstStats; data.first = true; }
  1923. if (obj.multiServer != null) { data.s = obj.multiServer.serverid; }
  1924. obj.db.SetServerStats(data); // Save the stats to the database
  1925. obj.DispatchEvent(['*'], obj, { action: 'servertimelinestats', data: data }); // Event the server stats
  1926. }, 300000);
  1927. obj.debug('main', "Server started");
  1928. if (obj.args.nousers == true) { obj.updateServerState('nousers', '1'); }
  1929. obj.updateServerState('state', "running");
  1930. // Setup auto-backup defaults
  1931. if (obj.config.settings.autobackup == null || obj.config.settings.autobackup === true) { obj.config.settings.autobackup = { backupintervalhours: 24, keeplastdaysbackup: 10 }; }
  1932. else if (obj.config.settings.autobackup === false) { delete obj.config.settings.autobackup; }
  1933. else if (typeof obj.config.settings.autobackup == 'object'){
  1934. if (typeof obj.config.settings.autobackup.backupintervalhours != 'number') { obj.config.settings.autobackup.backupintervalhours = 24; }
  1935. if (typeof obj.config.settings.autobackup.keeplastdaysbackup != 'number') { obj.config.settings.autobackup.keeplastdaysbackup = 10; }
  1936. }
  1937. // Check that autobackup path is not within the "meshcentral-data" folder.
  1938. if ((typeof obj.config.settings.autobackup == 'object') && (typeof obj.config.settings.autobackup.backuppath == 'string') && (obj.path.normalize(obj.config.settings.autobackup.backuppath).startsWith(obj.path.normalize(obj.datapath)))) {
  1939. addServerWarning("Backup path can't be set within meshcentral-data folder, backup settings ignored.", 21);
  1940. delete obj.config.settings.autobackup;
  1941. }
  1942. // Load Intel AMT passwords from the "amtactivation.log" file
  1943. obj.loadAmtActivationLogPasswords(function (amtPasswords) {
  1944. obj.amtPasswords = amtPasswords;
  1945. });
  1946. // Setup users that can see all device groups
  1947. if (typeof obj.config.settings.managealldevicegroups == 'string') { obj.config.settings.managealldevicegroups = obj.config.settings.managealldevicegroups.split(','); }
  1948. else if (Array.isArray(obj.config.settings.managealldevicegroups) == false) { obj.config.settings.managealldevicegroups = []; }
  1949. for (i in obj.config.domains) {
  1950. if (Array.isArray(obj.config.domains[i].managealldevicegroups)) {
  1951. for (var j in obj.config.domains[i].managealldevicegroups) {
  1952. if (typeof obj.config.domains[i].managealldevicegroups[j] == 'string') {
  1953. const u = 'user/' + i + '/' + obj.config.domains[i].managealldevicegroups[j];
  1954. if (obj.config.settings.managealldevicegroups.indexOf(u) == -1) { obj.config.settings.managealldevicegroups.push(u); }
  1955. }
  1956. }
  1957. }
  1958. }
  1959. obj.config.settings.managealldevicegroups.sort();
  1960. // Start watchdog timer if needed
  1961. // This is used to monitor if NodeJS is servicing IO correctly or getting held up a lot. Add this line to the settings section of config.json
  1962. // "watchDog": { "interval": 100, "timeout": 150 }
  1963. // This will check every 100ms, if the timer is more than 150ms late, it will warn.
  1964. if ((typeof config.settings.watchdog == 'object') && (typeof config.settings.watchdog.interval == 'number') && (typeof config.settings.watchdog.timeout == 'number') && (config.settings.watchdog.interval >= 50) && (config.settings.watchdog.timeout >= 50)) {
  1965. obj.watchdogtime = Date.now();
  1966. obj.watchdogmax = 0;
  1967. obj.watchdogmaxtime = null;
  1968. obj.watchdogtable = [];
  1969. obj.watchdog = setInterval(function () {
  1970. const now = Date.now(), delta = now - obj.watchdogtime - config.settings.watchdog.interval;
  1971. if (delta > obj.watchdogmax) { obj.watchdogmax = delta; obj.watchdogmaxtime = new Date().toLocaleString(); }
  1972. if (delta > config.settings.watchdog.timeout) {
  1973. const msg = obj.common.format("Watchdog timer timeout, {0}ms.", delta);
  1974. obj.watchdogtable.push(new Date().toLocaleString() + ', ' + delta + 'ms');
  1975. while (obj.watchdogtable.length > 10) { obj.watchdogtable.shift(); }
  1976. obj.debug('main', msg);
  1977. try {
  1978. var errlogpath = null;
  1979. if (typeof obj.args.mesherrorlogpath == 'string') { errlogpath = obj.path.join(obj.args.mesherrorlogpath, 'mesherrors.txt'); } else { errlogpath = obj.getConfigFilePath('mesherrors.txt'); }
  1980. obj.fs.appendFileSync(errlogpath, new Date().toLocaleString() + ': ' + msg + '\r\n');
  1981. } catch (ex) { console.log('ERROR: Unable to write to mesherrors.txt.'); }
  1982. }
  1983. obj.watchdogtime = now;
  1984. }, config.settings.watchdog.interval);
  1985. obj.debug('main', "Started watchdog timer.");
  1986. }
  1987. });
  1988. });
  1989. });
  1990. };
  1991. // Called when the web server finished loading
  1992. obj.StartEx5 = function () {
  1993. // Setup the email server for each domain
  1994. var ipKvmSupport = false;
  1995. for (var i in obj.config.domains) { if (obj.config.domains[i].ipkvm == true) { ipKvmSupport = true; } }
  1996. if (ipKvmSupport) { obj.ipKvmManager = require('./meshipkvm').CreateIPKVMManager(obj); }
  1997. // Run the server start script if present
  1998. if (typeof obj.config.settings.runonserverstarted == 'string') {
  1999. const child_process = require('child_process');
  2000. var parentpath = __dirname;
  2001. if ((__dirname.endsWith('/node_modules/meshcentral')) || (__dirname.endsWith('\\node_modules\\meshcentral')) || (__dirname.endsWith('/node_modules/meshcentral/')) || (__dirname.endsWith('\\node_modules\\meshcentral\\'))) { parentpath = require('path').join(__dirname, '../..'); }
  2002. child_process.exec(obj.config.settings.runonserverstarted + ' ' + getCurrentVersion(), { maxBuffer: 512000, timeout: 120000, cwd: parentpath }, function (error, stdout, stderr) { });
  2003. }
  2004. }
  2005. // Refresh any certificate hashs from the reverse proxy
  2006. obj.pendingProxyCertificatesRequests = 0;
  2007. obj.lastProxyCertificatesRequest = null;
  2008. obj.supportsProxyCertificatesRequest = false;
  2009. obj.updateProxyCertificates = function (force) {
  2010. if (force !== true) {
  2011. if ((obj.pendingProxyCertificatesRequests > 0) || (obj.supportsProxyCertificatesRequest == false)) return;
  2012. if ((obj.lastProxyCertificatesRequest != null) && ((Date.now() - obj.lastProxyCertificatesRequest) < 120000)) return; // Don't allow this call more than every 2 minutes.
  2013. obj.lastProxyCertificatesRequest = Date.now();
  2014. }
  2015. // Load any domain web certificates
  2016. for (var i in obj.config.domains) {
  2017. if (obj.config.domains[i].certurl != null) {
  2018. // Load web certs
  2019. obj.pendingProxyCertificatesRequests++;
  2020. var dnsname = obj.config.domains[i].dns;
  2021. if ((dnsname == null) && (obj.config.settings.cert != null)) { dnsname = obj.config.settings.cert; }
  2022. obj.certificateOperations.loadCertificate(obj.config.domains[i].certurl, dnsname, obj.config.domains[i], function (url, cert, xhostname, xdomain) {
  2023. obj.pendingProxyCertificatesRequests--;
  2024. if (cert != null) {
  2025. // Hash the entire cert
  2026. const hash = obj.crypto.createHash('sha384').update(Buffer.from(cert, 'binary')).digest('hex');
  2027. if (xdomain.certhash != hash) { // The certificate has changed.
  2028. xdomain.certkeyhash = hash;
  2029. xdomain.certhash = hash;
  2030. try {
  2031. // Decode a RSA certificate and hash the public key, if this is not RSA, skip this.
  2032. const forgeCert = obj.certificateOperations.forge.pki.certificateFromAsn1(obj.certificateOperations.forge.asn1.fromDer(cert));
  2033. xdomain.certkeyhash = obj.certificateOperations.forge.pki.getPublicKeyFingerprint(forgeCert.publicKey, { md: obj.certificateOperations.forge.md.sha384.create(), encoding: 'hex' });
  2034. obj.webserver.webCertificateExpire[xdomain.id] = Date.parse(forgeCert.validity.notAfter); // Update certificate expire time
  2035. //console.log('V1: ' + xdomain.certkeyhash);
  2036. } catch (ex) {
  2037. delete obj.webserver.webCertificateExpire[xdomain.id]; // Remove certificate expire time
  2038. delete xdomain.certkeyhash;
  2039. }
  2040. if (obj.webserver) {
  2041. obj.webserver.webCertificateHashs[xdomain.id] = obj.webserver.webCertificateFullHashs[xdomain.id] = Buffer.from(hash, 'hex').toString('binary');
  2042. if (xdomain.certkeyhash != null) { obj.webserver.webCertificateHashs[xdomain.id] = Buffer.from(xdomain.certkeyhash, 'hex').toString('binary'); }
  2043. // Disconnect all agents with bad web certificates
  2044. for (var i in obj.webserver.wsagentsWithBadWebCerts) { obj.webserver.wsagentsWithBadWebCerts[i].close(1); }
  2045. }
  2046. console.log(obj.common.format("Loaded web certificate from \"{0}\", host: \"{1}\"", url, xhostname));
  2047. console.log(obj.common.format(" SHA384 cert hash: {0}", xdomain.certhash));
  2048. if ((xdomain.certkeyhash != null) && (xdomain.certhash != xdomain.certkeyhash)) { console.log(obj.common.format(" SHA384 key hash: {0}", xdomain.certkeyhash)); }
  2049. }
  2050. } else {
  2051. console.log(obj.common.format("Failed to load web certificate at: \"{0}\", host: \"{1}\"", url, xhostname));
  2052. }
  2053. });
  2054. }
  2055. }
  2056. }
  2057. // Perform maintenance operations (called every hour)
  2058. obj.maintenanceActions = function () {
  2059. // Perform database maintenance
  2060. obj.db.maintenance();
  2061. // Clean up any temporary files
  2062. const removeTime = new Date(Date.now()).getTime() - (30 * 60 * 1000); // 30 minutes
  2063. const dir = obj.fs.readdir(obj.path.join(obj.filespath, 'tmp'), function (err, files) {
  2064. if (err != null) return;
  2065. for (var i in files) { try { const filepath = obj.path.join(obj.filespath, 'tmp', files[i]); if (obj.fs.statSync(filepath).mtime.getTime() < removeTime) { obj.fs.unlink(filepath, function () { }); } } catch (ex) { } }
  2066. });
  2067. // Check for self-update that targets a specific version
  2068. if ((typeof obj.args.selfupdate == 'string') && (getCurrentVersion() === obj.args.selfupdate)) { obj.args.selfupdate = false; }
  2069. // Check if we need to perform server self-update
  2070. if ((obj.args.selfupdate) && (obj.serverSelfWriteAllowed == true)) {
  2071. obj.db.getValueOfTheDay('performSelfUpdate', 1, function (performSelfUpdate) {
  2072. if (performSelfUpdate.value > 0) {
  2073. performSelfUpdate.value--;
  2074. obj.db.Set(performSelfUpdate);
  2075. obj.getLatestServerVersion(function (currentVer, latestVer) { if (currentVer != latestVer) { obj.performServerUpdate(); return; } });
  2076. } else {
  2077. checkAutobackup();
  2078. }
  2079. });
  2080. } else {
  2081. checkAutobackup();
  2082. }
  2083. };
  2084. // Check if we need to perform an automatic backup
  2085. function checkAutobackup() {
  2086. if (obj.config.settings.autobackup && (typeof obj.config.settings.autobackup.backupintervalhours == 'number')) {
  2087. obj.db.Get('LastAutoBackupTime', function (err, docs) {
  2088. if (err != null) return;
  2089. var lastBackup = 0;
  2090. const now = new Date().getTime();
  2091. if (docs.length == 1) { lastBackup = docs[0].value; }
  2092. const delta = now - lastBackup;
  2093. if (delta > (obj.config.settings.autobackup.backupintervalhours * 60 * 60 * 1000)) {
  2094. // A new auto-backup is required.
  2095. obj.db.Set({ _id: 'LastAutoBackupTime', value: now }); // Save the current time in the database
  2096. obj.db.performBackup(); // Perform the backup
  2097. }
  2098. });
  2099. }
  2100. }
  2101. // Stop the Meshcentral server
  2102. obj.Stop = function (restoreFile) {
  2103. // If the database is not setup, exit now.
  2104. if (!obj.db) return;
  2105. // Dispatch an event saying the server is now stopping
  2106. obj.DispatchEvent(['*'], obj, { etype: 'server', action: 'stopped', msg: "Server stopped" });
  2107. // Set all nodes to power state of unknown (0)
  2108. obj.db.storePowerEvent({ time: new Date(), nodeid: '*', power: 0, s: 2 }, obj.multiServer, function () { // s:2 indicates that the server is shutting down.
  2109. if (restoreFile) {
  2110. obj.debug('main', obj.common.format("Server stopped, updating settings: {0}", restoreFile));
  2111. console.log("Updating settings folder...");
  2112. const yauzl = require('yauzl');
  2113. yauzl.open(restoreFile, { lazyEntries: true }, function (err, zipfile) {
  2114. if (err) throw err;
  2115. zipfile.readEntry();
  2116. zipfile.on('entry', function (entry) {
  2117. if (/\/$/.test(entry.fileName)) {
  2118. // Directory file names end with '/'.
  2119. // Note that entires for directories themselves are optional.
  2120. // An entry's fileName implicitly requires its parent directories to exist.
  2121. zipfile.readEntry();
  2122. } else {
  2123. // File entry
  2124. zipfile.openReadStream(entry, function (err, readStream) {
  2125. if (err) throw err;
  2126. readStream.on('end', function () { zipfile.readEntry(); });
  2127. var directory = obj.path.dirname(entry.fileName);
  2128. if (directory != '.') {
  2129. directory = obj.getConfigFilePath(directory)
  2130. if (obj.fs.existsSync(directory) == false) { obj.fs.mkdirSync(directory); }
  2131. }
  2132. //console.log('Extracting:', obj.getConfigFilePath(entry.fileName));
  2133. readStream.pipe(obj.fs.createWriteStream(obj.getConfigFilePath(entry.fileName)));
  2134. });
  2135. }
  2136. });
  2137. zipfile.on('end', function () { setTimeout(function () { obj.fs.unlinkSync(restoreFile); process.exit(123); }); });
  2138. });
  2139. } else {
  2140. obj.debug('main', "Server stopped");
  2141. process.exit(0);
  2142. }
  2143. });
  2144. // Update the server state
  2145. obj.updateServerState('state', "stopped");
  2146. };
  2147. // Event Dispatch
  2148. obj.AddEventDispatch = function (ids, target) {
  2149. obj.debug('dispatch', 'AddEventDispatch', ids);
  2150. for (var i in ids) { var id = ids[i]; if (!obj.eventsDispatch[id]) { obj.eventsDispatch[id] = [target]; } else { obj.eventsDispatch[id].push(target); } }
  2151. };
  2152. obj.RemoveEventDispatch = function (ids, target) {
  2153. obj.debug('dispatch', 'RemoveEventDispatch', ids);
  2154. for (var i in ids) {
  2155. const id = ids[i];
  2156. if (obj.eventsDispatch[id]) {
  2157. var j = obj.eventsDispatch[id].indexOf(target);
  2158. if (j >= 0) {
  2159. if (obj.eventsDispatch[id].length == 1) {
  2160. delete obj.eventsDispatch[id];
  2161. } else {
  2162. const newList = []; // We create a new list so not to modify the original list. Allows this function to be called during an event dispatch.
  2163. for (var k in obj.eventsDispatch[i]) { if (obj.eventsDispatch[i][k] != target) { newList.push(obj.eventsDispatch[i][k]); } }
  2164. obj.eventsDispatch[i] = newList;
  2165. }
  2166. }
  2167. }
  2168. }
  2169. };
  2170. obj.RemoveEventDispatchId = function (id) {
  2171. obj.debug('dispatch', 'RemoveEventDispatchId', id);
  2172. if (obj.eventsDispatch[id] != null) { delete obj.eventsDispatch[id]; }
  2173. };
  2174. obj.RemoveAllEventDispatch = function (target) {
  2175. obj.debug('dispatch', 'RemoveAllEventDispatch');
  2176. for (var i in obj.eventsDispatch) {
  2177. const j = obj.eventsDispatch[i].indexOf(target);
  2178. if (j >= 0) {
  2179. if (obj.eventsDispatch[i].length == 1) {
  2180. delete obj.eventsDispatch[i];
  2181. } else {
  2182. const newList = []; // We create a new list so not to modify the original list. Allows this function to be called during an event dispatch.
  2183. for (var k in obj.eventsDispatch[i]) { if (obj.eventsDispatch[i][k] != target) { newList.push(obj.eventsDispatch[i][k]); } }
  2184. obj.eventsDispatch[i] = newList;
  2185. }
  2186. }
  2187. }
  2188. };
  2189. obj.DispatchEvent = function (ids, source, event, fromPeerServer) {
  2190. // If the database is not setup, exit now.
  2191. if (!obj.db) return;
  2192. // Send event to syslog if needed
  2193. if (obj.syslog && event.msg) { obj.syslog.log(obj.syslog.LOG_INFO, event.msg); }
  2194. if (obj.syslogjson) { obj.syslogjson.log(obj.syslogjson.LOG_INFO, JSON.stringify(event)); }
  2195. if (obj.syslogtcp && event.msg) { obj.syslogtcp.log(event.msg, obj.syslogtcp.LOG_INFO); }
  2196. obj.debug('dispatch', 'DispatchEvent', ids);
  2197. if ((typeof event == 'object') && (!event.nolog)) {
  2198. event.time = new Date();
  2199. // The event we store is going to skip some of the fields so we don't store too much stuff in the database.
  2200. const storeEvent = Object.assign({}, event);
  2201. if (storeEvent.node) { delete storeEvent.node; } // Skip the "node" field. May skip more in the future.
  2202. if (storeEvent.links) {
  2203. // Escape "links" names that may have "." and/or "$"
  2204. storeEvent.links = Object.assign({}, storeEvent.links);
  2205. for (var i in storeEvent.links) { var ue = obj.common.escapeFieldName(i); if (ue !== i) { storeEvent.links[ue] = storeEvent.links[i]; delete storeEvent.links[i]; } }
  2206. }
  2207. storeEvent.ids = ids;
  2208. obj.db.StoreEvent(storeEvent);
  2209. }
  2210. const targets = []; // List of targets we dispatched the event to, we don't want to dispatch to the same target twice.
  2211. for (var j in ids) {
  2212. const id = ids[j];
  2213. const eventsDispatch = obj.eventsDispatch[id];
  2214. if (eventsDispatch) {
  2215. for (var i in eventsDispatch) {
  2216. if (targets.indexOf(eventsDispatch[i]) == -1) { // Check if we already displatched to this target
  2217. targets.push(eventsDispatch[i]);
  2218. try { eventsDispatch[i].HandleEvent(source, event, ids, id); } catch (ex) { console.log(ex, eventsDispatch[i]); }
  2219. }
  2220. }
  2221. }
  2222. }
  2223. if ((fromPeerServer == null) && (obj.multiServer != null) && ((typeof event != 'object') || (event.nopeers != 1))) { obj.multiServer.DispatchEvent(ids, source, event); }
  2224. };
  2225. // Get the connection state of a node
  2226. obj.GetConnectivityState = function (nodeid) { return obj.connectivityByNode[nodeid]; };
  2227. // Get the routing server id for a given node and connection type, can never be self.
  2228. obj.GetRoutingServerIdNotSelf = function (nodeid, connectType) {
  2229. if (obj.multiServer == null) return null;
  2230. for (var serverid in obj.peerConnectivityByNode) {
  2231. if (serverid == obj.serverId) continue;
  2232. var state = obj.peerConnectivityByNode[serverid][nodeid];
  2233. if ((state != null) && ((state.connectivity & connectType) != 0)) { return { serverid: serverid, meshid: state.meshid }; }
  2234. }
  2235. return null;
  2236. };
  2237. // Get the routing server id for a given node and connection type, self first
  2238. obj.GetRoutingServerId = function (nodeid, connectType) {
  2239. if (obj.multiServer == null) return null;
  2240. // Look at our own server first
  2241. var connections = obj.peerConnectivityByNode[obj.serverId];
  2242. if (connections != null) {
  2243. var state = connections[nodeid];
  2244. if ((state != null) && ((state.connectivity & connectType) != 0)) { return { serverid: obj.serverId, meshid: state.meshid }; }
  2245. }
  2246. // Look at other servers
  2247. for (var serverid in obj.peerConnectivityByNode) {
  2248. if (serverid == obj.serverId) continue;
  2249. var state = obj.peerConnectivityByNode[serverid][nodeid];
  2250. if ((state != null) && ((state.connectivity & connectType) != 0)) { return { serverid: serverid, meshid: state.meshid }; }
  2251. }
  2252. return null;
  2253. };
  2254. // Update the connection state of a node when in multi-server mode
  2255. // Update obj.connectivityByNode using obj.peerConnectivityByNode for the list of nodes in argument
  2256. obj.UpdateConnectivityState = function (nodeids) {
  2257. for (var nodeid in nodeids) {
  2258. var meshid = null, state = null, oldConnectivity = 0, oldPowerState = 0, newConnectivity = 0, newPowerState = 0;
  2259. var oldState = obj.connectivityByNode[nodeid];
  2260. if (oldState != null) { meshid = oldState.meshid; oldConnectivity = oldState.connectivity; oldPowerState = oldState.powerState; }
  2261. for (var serverid in obj.peerConnectivityByNode) {
  2262. var peerState = obj.peerConnectivityByNode[serverid][nodeid];
  2263. if (peerState != null) {
  2264. if (state == null) {
  2265. // Copy the state
  2266. state = {};
  2267. newConnectivity = state.connectivity = peerState.connectivity;
  2268. newPowerState = state.powerState = peerState.powerState;
  2269. meshid = state.meshid = peerState.meshid;
  2270. //if (peerState.agentPower) { state.agentPower = peerState.agentPower; }
  2271. //if (peerState.ciraPower) { state.ciraPower = peerState.ciraPower; }
  2272. //if (peerState.amtPower) { state.amtPower = peerState.amtPower; }
  2273. } else {
  2274. // Merge the state
  2275. state.connectivity |= peerState.connectivity;
  2276. newConnectivity = state.connectivity;
  2277. if ((peerState.powerState != 0) && ((state.powerState == 0) || (peerState.powerState < state.powerState))) { newPowerState = state.powerState = peerState.powerState; }
  2278. meshid = state.meshid = peerState.meshid;
  2279. //if (peerState.agentPower) { state.agentPower = peerState.agentPower; }
  2280. //if (peerState.ciraPower) { state.ciraPower = peerState.ciraPower; }
  2281. //if (peerState.amtPower) { state.amtPower = peerState.amtPower; }
  2282. }
  2283. }
  2284. }
  2285. obj.connectivityByNode[nodeid] = state;
  2286. //console.log('xx', nodeid, meshid, newConnectivity, oldPowerState, newPowerState, oldPowerState);
  2287. // Event any changes on this server only
  2288. if ((newConnectivity != oldPowerState) || (newPowerState != oldPowerState)) {
  2289. obj.DispatchEvent(obj.webserver.CreateNodeDispatchTargets(meshid, nodeid), obj, { action: 'nodeconnect', meshid: meshid, nodeid: nodeid, domain: nodeid.split('/')[1], conn: newConnectivity, pwr: newPowerState, nolog: 1, nopeers: 1, id: Math.random() });
  2290. }
  2291. }
  2292. };
  2293. // See if we need to notifiy any user of device state change
  2294. obj.NotifyUserOfDeviceStateChange = function (meshid, nodeid, connectTime, connectType, powerState, serverid, stateSet, extraInfo) {
  2295. // Check if there is a email server for this domain
  2296. const meshSplit = meshid.split('/');
  2297. if (meshSplit.length != 3) return;
  2298. const domainId = meshSplit[1];
  2299. if (obj.config.domains[domainId] == null) return;
  2300. const mailserver = obj.config.domains[domainId].mailserver;
  2301. if ((mailserver == null) && (obj.msgserver == null)) return;
  2302. // Get the device group for this device
  2303. const mesh = obj.webserver.meshes[meshid];
  2304. if ((mesh == null) || (mesh.links == null)) return;
  2305. // Get the list of users that have visibility to this device
  2306. // This includes users that are part of user groups
  2307. const users = [];
  2308. for (var i in mesh.links) {
  2309. if (i.startsWith('user/') && (users.indexOf(i) < 0)) { users.push(i); }
  2310. if (i.startsWith('ugrp/')) {
  2311. var usergrp = obj.webserver.userGroups[i];
  2312. if (usergrp.links != null) { for (var j in usergrp.links) { if (j.startsWith('user/') && (users.indexOf(j) < 0)) { users.push(j); } } }
  2313. }
  2314. }
  2315. // Check if any user needs email notification
  2316. for (var i in users) {
  2317. const user = obj.webserver.users[users[i]];
  2318. if (user != null) {
  2319. var notify = 0;
  2320. // Device group notifications
  2321. const meshLinks = user.links[meshid];
  2322. if ((meshLinks != null) && (meshLinks.notify != null)) { notify |= meshLinks.notify; }
  2323. // User notifications
  2324. if (user.notify != null) {
  2325. if (user.notify[meshid] != null) { notify |= user.notify[meshid]; }
  2326. if (user.notify[nodeid] != null) { notify |= user.notify[nodeid]; }
  2327. }
  2328. // Email notifications
  2329. if ((user.email != null) && (user.emailVerified == true) && (mailserver != null) && ((notify & 48) != 0)) {
  2330. if (stateSet == true) {
  2331. if ((notify & 16) != 0) {
  2332. mailserver.notifyDeviceConnect(user, meshid, nodeid, connectTime, connectType, powerState, serverid, extraInfo);
  2333. } else {
  2334. mailserver.cancelNotifyDeviceDisconnect(user, meshid, nodeid, connectTime, connectType, powerState, serverid, extraInfo);
  2335. }
  2336. }
  2337. else if (stateSet == false) {
  2338. if ((notify & 32) != 0) {
  2339. mailserver.notifyDeviceDisconnect(user, meshid, nodeid, connectTime, connectType, powerState, serverid, extraInfo);
  2340. } else {
  2341. mailserver.cancelNotifyDeviceConnect(user, meshid, nodeid, connectTime, connectType, powerState, serverid, extraInfo);
  2342. }
  2343. }
  2344. }
  2345. // Messaging notifications
  2346. if ((obj.msgserver != null) && ((notify & 384) != 0)) {
  2347. if (stateSet == true) {
  2348. if ((notify & 128) != 0) {
  2349. obj.msgserver.notifyDeviceConnect(user, meshid, nodeid, connectTime, connectType, powerState, serverid, extraInfo);
  2350. } else {
  2351. obj.msgserver.cancelNotifyDeviceDisconnect(user, meshid, nodeid, connectTime, connectType, powerState, serverid, extraInfo);
  2352. }
  2353. }
  2354. else if (stateSet == false) {
  2355. if ((notify & 256) != 0) {
  2356. obj.msgserver.notifyDeviceDisconnect(user, meshid, nodeid, connectTime, connectType, powerState, serverid, extraInfo);
  2357. } else {
  2358. obj.msgserver.cancelNotifyDeviceConnect(user, meshid, nodeid, connectTime, connectType, powerState, serverid, extraInfo);
  2359. }
  2360. }
  2361. }
  2362. }
  2363. }
  2364. }
  2365. // See if we need to notifiy any user of device requested help
  2366. //if (typeof device.name == 'string') { parent.parent.NotifyUserOfDeviceHelpRequest(domain, device._id, device.meshid, device.name, command.msgArgs[0], command.msgArgs[1]); }
  2367. obj.NotifyUserOfDeviceHelpRequest = function (domain, meshid, nodeid, devicename, helpusername, helprequest) {
  2368. // Check if there is a email server for this domain
  2369. const meshSplit = meshid.split('/');
  2370. if (meshSplit.length != 3) return;
  2371. const domainId = meshSplit[1];
  2372. if (obj.config.domains[domainId] == null) return;
  2373. const mailserver = obj.config.domains[domainId].mailserver;
  2374. if ((mailserver == null) && (obj.msgserver == null)) return;
  2375. // Get the device group for this device
  2376. const mesh = obj.webserver.meshes[meshid];
  2377. if ((mesh == null) || (mesh.links == null)) return;
  2378. // Get the list of users that have visibility to this device
  2379. // This includes users that are part of user groups
  2380. const users = [];
  2381. for (var i in mesh.links) {
  2382. if (i.startsWith('user/') && (users.indexOf(i) < 0)) { users.push(i); }
  2383. if (i.startsWith('ugrp/')) {
  2384. var usergrp = obj.webserver.userGroups[i];
  2385. if (usergrp.links != null) { for (var j in usergrp.links) { if (j.startsWith('user/') && (users.indexOf(j) < 0)) { users.push(j); } } }
  2386. }
  2387. }
  2388. // Check if any user needs email notification
  2389. for (var i in users) {
  2390. const user = obj.webserver.users[users[i]];
  2391. if (user != null) {
  2392. var notify = 0;
  2393. // Device group notifications
  2394. const meshLinks = user.links[meshid];
  2395. if ((meshLinks != null) && (meshLinks.notify != null)) { notify |= meshLinks.notify; }
  2396. // User notifications
  2397. if (user.notify != null) {
  2398. if (user.notify[meshid] != null) { notify |= user.notify[meshid]; }
  2399. if (user.notify[nodeid] != null) { notify |= user.notify[nodeid]; }
  2400. }
  2401. // Mail help request
  2402. if ((user.email != null) && (user.emailVerified == true) && ((notify & 64) != 0)) { mailserver.sendDeviceHelpMail(domain, user.name, user.email, devicename, nodeid, helpusername, helprequest, user.llang); }
  2403. // Message help request
  2404. if ((user.msghandle != null) && ((notify & 512) != 0)) { obj.msgserver.sendDeviceHelpRequest(domain, user.name, user.msghandle, devicename, nodeid, helpusername, helprequest, user.llang); }
  2405. }
  2406. }
  2407. }
  2408. // Set the connectivity state of a node and setup the server so that messages can be routed correctly.
  2409. // meshId: mesh identifier of format mesh/domain/meshidhex
  2410. // nodeId: node identifier of format node/domain/nodeidhex
  2411. // connectTime: time of connection, milliseconds elapsed since the UNIX epoch.
  2412. // connectType: Bitmask, 1 = MeshAgent, 2 = Intel AMT CIRA, 4 = Intel AMT local, 8 = Intel AMT Relay, 16 = MQTT
  2413. // powerState: Value, 0 = Unknown, 1 = S0 power on, 2 = S1 Sleep, 3 = S2 Sleep, 4 = S3 Sleep, 5 = S4 Hibernate, 6 = S5 Soft-Off, 7 = Present, 8 = Off
  2414. //var connectTypeStrings = ['', 'MeshAgent', 'Intel AMT CIRA', '', 'Intel AMT local', '', '', '', 'Intel AMT Relay', '', '', '', '', '', '', '', 'MQTT'];
  2415. //var powerStateStrings = ['Unknown', 'Powered', 'Sleep', 'Sleep', 'Deep Sleep', 'Hibernating', 'Soft-Off', 'Present', 'Off'];
  2416. obj.SetConnectivityState = function (meshid, nodeid, connectTime, connectType, powerState, serverid, extraInfo) {
  2417. //console.log('SetConnectivity for ' + nodeid.substring(0, 16) + ', Type: ' + connectTypeStrings[connectType] + ', Power: ' + powerStateStrings[powerState] + (serverid == null ? ('') : (', ServerId: ' + serverid)));
  2418. if ((serverid == null) && (obj.multiServer != null)) { obj.multiServer.DispatchMessage({ action: 'SetConnectivityState', meshid: meshid, nodeid: nodeid, connectTime: connectTime, connectType: connectType, powerState: powerState, extraInfo: extraInfo }); }
  2419. if (obj.multiServer == null) {
  2420. // Single server mode
  2421. // Change the node connection state
  2422. var eventConnectChange = 0;
  2423. var state = obj.connectivityByNode[nodeid];
  2424. if (state) {
  2425. // Change the connection in the node and mesh state lists
  2426. if ((state.connectivity & connectType) == 0) { state.connectivity |= connectType; eventConnectChange = 1; }
  2427. state.meshid = meshid;
  2428. } else {
  2429. // Add the connection to the node and mesh state list
  2430. obj.connectivityByNode[nodeid] = state = { connectivity: connectType, meshid: meshid };
  2431. eventConnectChange = 1;
  2432. }
  2433. // Set node power state
  2434. if (connectType == 1) { state.agentPower = powerState; } else if (connectType == 2) { state.ciraPower = powerState; } else if (connectType == 4) { state.amtPower = powerState; }
  2435. var powerState = 0, oldPowerState = state.powerState;
  2436. if ((state.connectivity & 1) != 0) { powerState = state.agentPower; } else if ((state.connectivity & 2) != 0) { powerState = state.ciraPower; } else if ((state.connectivity & 4) != 0) { powerState = state.amtPower; }
  2437. if ((state.powerState == null) || (state.powerState == undefined) || (state.powerState != powerState)) {
  2438. state.powerState = powerState;
  2439. eventConnectChange = 1;
  2440. // Set new power state in database
  2441. const record = { time: new Date(connectTime), nodeid: nodeid, power: powerState };
  2442. if (oldPowerState != null) { record.oldPower = oldPowerState; }
  2443. obj.db.storePowerEvent(record, obj.multiServer);
  2444. }
  2445. // Event the node connection change
  2446. if (eventConnectChange == 1) {
  2447. obj.DispatchEvent(obj.webserver.CreateNodeDispatchTargets(meshid, nodeid), obj, { action: 'nodeconnect', meshid: meshid, nodeid: nodeid, domain: nodeid.split('/')[1], conn: state.connectivity, pwr: state.powerState, ct: connectTime, nolog: 1, nopeers: 1, id: Math.random() });
  2448. // Save indication of node connection change
  2449. const lc = { _id: 'lc' + nodeid, type: 'lastconnect', domain: nodeid.split('/')[1], meshid: meshid, time: Date.now(), cause: 1, connectType: connectType };
  2450. if (extraInfo && extraInfo.remoteaddrport) { lc.addr = extraInfo.remoteaddrport; }
  2451. obj.db.Set(lc);
  2452. // Notify any users of device connection
  2453. obj.NotifyUserOfDeviceStateChange(meshid, nodeid, connectTime, connectType, powerState, serverid, true, extraInfo);
  2454. }
  2455. } else {
  2456. // Multi server mode
  2457. // Change the node connection state
  2458. if (serverid == null) { serverid = obj.serverId; }
  2459. if (obj.peerConnectivityByNode[serverid] == null) return; // Guard against unknown serverid's
  2460. var eventConnectChange = 0;
  2461. var state = obj.peerConnectivityByNode[serverid][nodeid];
  2462. if (state) {
  2463. // Change the connection in the node and mesh state lists
  2464. if ((state.connectivity & connectType) == 0) { state.connectivity |= connectType; eventConnectChange = 1; }
  2465. state.meshid = meshid;
  2466. } else {
  2467. // Add the connection to the node and mesh state list
  2468. obj.peerConnectivityByNode[serverid][nodeid] = state = { connectivity: connectType, meshid: meshid };
  2469. eventConnectChange = 1;
  2470. }
  2471. // Set node power state
  2472. if (connectType == 1) { state.agentPower = powerState; } else if (connectType == 2) { state.ciraPower = powerState; } else if (connectType == 4) { state.amtPower = powerState; }
  2473. var powerState = 0, oldPowerState = state.powerState;
  2474. if ((state.connectivity & 1) != 0) { powerState = state.agentPower; } else if ((state.connectivity & 2) != 0) { powerState = state.ciraPower; } else if ((state.connectivity & 4) != 0) { powerState = state.amtPower; }
  2475. if ((state.powerState == null) || (state.powerState == undefined) || (state.powerState != powerState)) {
  2476. state.powerState = powerState;
  2477. eventConnectChange = 1;
  2478. // Set new power state in database
  2479. var record = { time: new Date(connectTime), nodeid: nodeid, power: powerState, server: obj.multiServer.serverid };
  2480. if (oldPowerState != null) { record.oldPower = oldPowerState; }
  2481. obj.db.storePowerEvent(record, obj.multiServer);
  2482. }
  2483. if (eventConnectChange == 1) {
  2484. // Update the combined node state
  2485. var x = {}; x[nodeid] = 1;
  2486. obj.UpdateConnectivityState(x);
  2487. // Save indication of node connection change
  2488. if (serverid == obj.serverId) {
  2489. const lc = { _id: 'lc' + nodeid, type: 'lastconnect', domain: nodeid.split('/')[1], meshid: meshid, time: Date.now(), cause: 1, connectType: connectType, serverid: obj.serverId };
  2490. if (extraInfo && extraInfo.remoteaddrport) { lc.addr = extraInfo.remoteaddrport; }
  2491. obj.db.Set(lc);
  2492. }
  2493. // Notify any users of device connection
  2494. obj.NotifyUserOfDeviceStateChange(meshid, nodeid, connectTime, connectType, powerState, serverid, true, extraInfo);
  2495. }
  2496. }
  2497. };
  2498. // Clear the connectivity state of a node and setup the server so that messages can be routed correctly.
  2499. // meshId: mesh identifier of format mesh/domain/meshidhex
  2500. // nodeId: node identifier of format node/domain/nodeidhex
  2501. // connectType: Bitmask, 1 = MeshAgent, 2 = Intel AMT CIRA, 3 = Intel AMT local.
  2502. obj.ClearConnectivityState = function (meshid, nodeid, connectType, serverid, extraInfo) {
  2503. //console.log('ClearConnectivity for ' + nodeid.substring(0, 16) + ', Type: ' + connectTypeStrings[connectType] + (serverid == null?(''):(', ServerId: ' + serverid)));
  2504. if ((serverid == null) && (obj.multiServer != null)) { obj.multiServer.DispatchMessage({ action: 'ClearConnectivityState', meshid: meshid, nodeid: nodeid, connectType: connectType, extraInfo: extraInfo }); }
  2505. if (obj.multiServer == null) {
  2506. // Single server mode
  2507. var eventConnectChange = 0;
  2508. // Remove the agent connection from the nodes connection list
  2509. const state = obj.connectivityByNode[nodeid];
  2510. if (state == null) return;
  2511. if ((state.connectivity & connectType) != 0) {
  2512. state.connectivity -= connectType;
  2513. // Save indication of node connection change
  2514. const lc = { _id: 'lc' + nodeid, type: 'lastconnect', domain: nodeid.split('/')[1], meshid: meshid, time: Date.now(), cause: 0, connectType: connectType };
  2515. if (extraInfo && extraInfo.remoteaddrport) { lc.addr = extraInfo.remoteaddrport; }
  2516. obj.db.Set(lc);
  2517. // If the node is completely disconnected, clean it up completely
  2518. if (state.connectivity == 0) { delete obj.connectivityByNode[nodeid]; }
  2519. eventConnectChange = 1;
  2520. }
  2521. // Clear node power state
  2522. var powerState = 0;
  2523. const oldPowerState = state.powerState;
  2524. if (connectType == 1) { state.agentPower = 0; } else if (connectType == 2) { state.ciraPower = 0; } else if (connectType == 4) { state.amtPower = 0; }
  2525. if ((state.connectivity & 1) != 0) { powerState = state.agentPower; } else if ((state.connectivity & 2) != 0) { powerState = state.ciraPower; } else if ((state.connectivity & 4) != 0) { powerState = state.amtPower; }
  2526. if ((state.powerState == null) || (state.powerState != powerState)) {
  2527. state.powerState = powerState;
  2528. eventConnectChange = 1;
  2529. // Set new power state in database
  2530. obj.db.storePowerEvent({ time: new Date(), nodeid: nodeid, power: powerState, oldPower: oldPowerState }, obj.multiServer);
  2531. }
  2532. // Event the node connection change
  2533. if (eventConnectChange == 1) {
  2534. obj.DispatchEvent(obj.webserver.CreateNodeDispatchTargets(meshid, nodeid), obj, { action: 'nodeconnect', meshid: meshid, nodeid: nodeid, domain: nodeid.split('/')[1], conn: state.connectivity, pwr: state.powerState, nolog: 1, nopeers: 1, id: Math.random() });
  2535. // Notify any users of device disconnection
  2536. obj.NotifyUserOfDeviceStateChange(meshid, nodeid, Date.now(), connectType, -1, serverid, false, extraInfo);
  2537. }
  2538. } else {
  2539. // Multi server mode
  2540. // Remove the agent connection from the nodes connection list
  2541. if (serverid == null) { serverid = obj.serverId; }
  2542. if (obj.peerConnectivityByNode[serverid] == null) return; // Guard against unknown serverid's
  2543. var state = obj.peerConnectivityByNode[serverid][nodeid];
  2544. if (state == null) return;
  2545. // If existing state exist, remove this connection
  2546. if ((state.connectivity & connectType) != 0) {
  2547. state.connectivity -= connectType; // Remove one connectivity mode
  2548. // Save indication of node connection change
  2549. if (serverid == obj.serverId) {
  2550. const lc = { _id: 'lc' + nodeid, type: 'lastconnect', domain: nodeid.split('/')[1], meshid: meshid, time: Date.now(), cause: 0, connectType: connectType, serverid: obj.serverId };
  2551. if (extraInfo && extraInfo.remoteaddrport) { lc.addr = extraInfo.remoteaddrport; }
  2552. obj.db.Set(lc);
  2553. }
  2554. // If the node is completely disconnected, clean it up completely
  2555. if (state.connectivity == 0) { delete obj.peerConnectivityByNode[serverid][nodeid]; state.powerState = 0; }
  2556. // Notify any users of device disconnection
  2557. obj.NotifyUserOfDeviceStateChange(meshid, nodeid, Date.now(), connectType, -1, serverid, false, extraInfo);
  2558. }
  2559. // Clear node power state
  2560. if (connectType == 1) { state.agentPower = 0; } else if (connectType == 2) { state.ciraPower = 0; } else if (connectType == 4) { state.amtPower = 0; }
  2561. var powerState = 0;
  2562. if ((state.connectivity & 1) != 0) { powerState = state.agentPower; } else if ((state.connectivity & 2) != 0) { powerState = state.ciraPower; } else if ((state.connectivity & 4) != 0) { powerState = state.amtPower; }
  2563. if ((state.powerState == null) || (state.powerState != powerState)) { state.powerState = powerState; }
  2564. // Update the combined node state
  2565. var x = {}; x[nodeid] = 1;
  2566. obj.UpdateConnectivityState(x);
  2567. }
  2568. };
  2569. // Escape a code string
  2570. obj.escapeCodeString = function (str, keepUtf8) {
  2571. const escapeCodeStringTable = { '\'': '\\\'', '\"': '\\"', '\\': '\\\\', '\b': '\\b', '\f': '\\f', '\n': '\\n', '\r': '\\r', '\t': '\\t' };
  2572. var r = '', c, cr, table;
  2573. for (var i = 0; i < str.length; i++) {
  2574. c = str[i];
  2575. table = escapeCodeStringTable[c];
  2576. if (table != null) {
  2577. r += table;
  2578. } else if (keepUtf8 === true) {
  2579. r += c;
  2580. } else {
  2581. cr = c.charCodeAt(0);
  2582. if ((cr >= 32) && (cr <= 127)) { r += c; }
  2583. }
  2584. }
  2585. return r;
  2586. }
  2587. // Update the default mesh core
  2588. obj.updateMeshCore = function (func, dumpToFile) {
  2589. // Figure out where meshcore.js is
  2590. var meshcorePath = obj.datapath;
  2591. if (obj.fs.existsSync(obj.path.join(meshcorePath, 'meshcore.js')) == false) {
  2592. meshcorePath = obj.path.join(__dirname, 'agents');
  2593. if (obj.fs.existsSync(obj.path.join(meshcorePath, 'meshcore.js')) == false) {
  2594. obj.defaultMeshCores = obj.defaultMeshCoresHash = {}; if (func != null) { func(false); } // meshcore.js not found
  2595. }
  2596. }
  2597. // Read meshcore.js and all .js files in the modules folder.
  2598. var meshCore = null, modulesDir = null;
  2599. const modulesAdd = {
  2600. 'windows-amt': ['var addedModules = [];\r\n'],
  2601. 'linux-amt': ['var addedModules = [];\r\n'],
  2602. 'linux-noamt': ['var addedModules = [];\r\n']
  2603. };
  2604. // Read the recovery core if present
  2605. var meshRecoveryCore = null;
  2606. if (obj.fs.existsSync(obj.path.join(__dirname, 'agents', 'recoverycore.js')) == true) {
  2607. try { meshRecoveryCore = obj.fs.readFileSync(obj.path.join(__dirname, 'agents', 'recoverycore.js')).toString(); } catch (ex) { }
  2608. if (meshRecoveryCore != null) {
  2609. modulesAdd['windows-recovery'] = ['var addedModules = [];\r\n'];
  2610. modulesAdd['linux-recovery'] = ['var addedModules = [];\r\n'];
  2611. }
  2612. }
  2613. // Read the agent recovery core if present
  2614. var meshAgentRecoveryCore = null;
  2615. if (obj.fs.existsSync(obj.path.join(__dirname, 'agents', 'meshcore_diagnostic.js')) == true) {
  2616. try { meshAgentRecoveryCore = obj.fs.readFileSync(obj.path.join(__dirname, 'agents', 'meshcore_diagnostic.js')).toString(); } catch (ex) { }
  2617. if (meshAgentRecoveryCore != null) {
  2618. modulesAdd['windows-agentrecovery'] = ['var addedModules = [];\r\n'];
  2619. modulesAdd['linux-agentrecovery'] = ['var addedModules = [];\r\n'];
  2620. }
  2621. }
  2622. // Read the tiny core if present
  2623. var meshTinyCore = null;
  2624. if (obj.fs.existsSync(obj.path.join(__dirname, 'agents', 'tinycore.js')) == true) {
  2625. try { meshTinyCore = obj.fs.readFileSync(obj.path.join(__dirname, 'agents', 'tinycore.js')).toString(); } catch (ex) { }
  2626. if (meshTinyCore != null) {
  2627. modulesAdd['windows-tiny'] = ['var addedModules = [];\r\n'];
  2628. modulesAdd['linux-tiny'] = ['var addedModules = [];\r\n'];
  2629. }
  2630. }
  2631. if (obj.args.minifycore !== false) { try { meshCore = obj.fs.readFileSync(obj.path.join(meshcorePath, 'meshcore.min.js')).toString(); } catch (ex) { } } // Favor minified meshcore if present.
  2632. if (meshCore == null) { try { meshCore = obj.fs.readFileSync(obj.path.join(meshcorePath, 'meshcore.js')).toString(); } catch (ex) { } } // Use non-minified meshcore.
  2633. if (meshCore != null) {
  2634. var moduleDirPath = null;
  2635. if (obj.args.minifycore !== false) { try { moduleDirPath = obj.path.join(meshcorePath, 'modules_meshcore_min'); modulesDir = obj.fs.readdirSync(moduleDirPath); } catch (ex) { } } // Favor minified modules if present.
  2636. if (modulesDir == null) { try { moduleDirPath = obj.path.join(meshcorePath, 'modules_meshcore'); modulesDir = obj.fs.readdirSync(moduleDirPath); } catch (ex) { } } // Use non-minified mofules.
  2637. if (modulesDir != null) {
  2638. for (var i in modulesDir) {
  2639. if (modulesDir[i].toLowerCase().endsWith('.json')) {
  2640. // We are adding a JSON file to the meshcores
  2641. var moduleName = modulesDir[i].substring(0, modulesDir[i].length - 5);
  2642. if (moduleName.endsWith('.min')) { moduleName = moduleName.substring(0, moduleName.length - 6); } // Remove the ".min" for ".min.json" files.
  2643. const jsonData = obj.escapeCodeString(obj.fs.readFileSync(obj.path.join(moduleDirPath, modulesDir[i])).toString('utf8'), true);
  2644. const moduleData = ['var ', moduleName, ' = JSON.parse(\'', jsonData, '\');\r\n'];
  2645. // Add to all major cores
  2646. modulesAdd['windows-amt'].push(...moduleData);
  2647. modulesAdd['linux-amt'].push(...moduleData);
  2648. modulesAdd['linux-noamt'].push(...moduleData);
  2649. }
  2650. if (modulesDir[i].toLowerCase().endsWith('.js')) {
  2651. // We are adding a JS file to the meshcores
  2652. var moduleName = modulesDir[i].substring(0, modulesDir[i].length - 3);
  2653. if (moduleName.endsWith('.min')) { moduleName = moduleName.substring(0, moduleName.length - 4); } // Remove the ".min" for ".min.js" files.
  2654. const moduleData = ['try { addModule("', moduleName, '", "', obj.escapeCodeString(obj.fs.readFileSync(obj.path.join(moduleDirPath, modulesDir[i])).toString('binary')), '"); addedModules.push("', moduleName, '"); } catch (ex) { }\r\n'];
  2655. // Merge this module
  2656. // NOTE: "smbios" module makes some non-AI Linux segfault, only include for IA platforms.
  2657. if (moduleName.startsWith('amt-') || (moduleName == 'smbios')) {
  2658. // Add to IA / Intel AMT cores only
  2659. modulesAdd['windows-amt'].push(...moduleData);
  2660. modulesAdd['linux-amt'].push(...moduleData);
  2661. } else if (moduleName.startsWith('win-')) {
  2662. // Add to Windows cores only
  2663. modulesAdd['windows-amt'].push(...moduleData);
  2664. } else if (moduleName.startsWith('linux-')) {
  2665. // Add to Linux cores only
  2666. modulesAdd['linux-amt'].push(...moduleData);
  2667. modulesAdd['linux-noamt'].push(...moduleData);
  2668. } else {
  2669. // Add to all cores
  2670. modulesAdd['windows-amt'].push(...moduleData);
  2671. modulesAdd['linux-amt'].push(...moduleData);
  2672. modulesAdd['linux-noamt'].push(...moduleData);
  2673. }
  2674. // Merge this module to recovery modules if needed
  2675. if (modulesAdd['windows-recovery'] != null) {
  2676. if ((moduleName == 'win-console') || (moduleName == 'win-message-pump') || (moduleName == 'win-terminal') || (moduleName == 'win-virtual-terminal')) {
  2677. modulesAdd['windows-recovery'].push(...moduleData);
  2678. }
  2679. }
  2680. // Merge this module to agent recovery modules if needed
  2681. if (modulesAdd['windows-agentrecovery'] != null) {
  2682. if ((moduleName == 'win-console') || (moduleName == 'win-message-pump') || (moduleName == 'win-terminal') || (moduleName == 'win-virtual-terminal')) {
  2683. modulesAdd['windows-agentrecovery'].push(...moduleData);
  2684. }
  2685. }
  2686. }
  2687. }
  2688. }
  2689. // Add plugins to cores
  2690. if (obj.pluginHandler) { obj.pluginHandler.addMeshCoreModules(modulesAdd); }
  2691. // If we need to dump modules to file, create a meshcores folder
  2692. if (dumpToFile) { try { obj.fs.mkdirSync('meshcores'); } catch (ex) { } }
  2693. // Merge the cores and compute the hashes
  2694. for (var i in modulesAdd) {
  2695. if ((i == 'windows-recovery') || (i == 'linux-recovery')) {
  2696. obj.defaultMeshCores[i] = [obj.common.IntToStr(0), ...modulesAdd[i], meshRecoveryCore].join('');
  2697. } else if ((i == 'windows-agentrecovery') || (i == 'linux-agentrecovery')) {
  2698. obj.defaultMeshCores[i] = [obj.common.IntToStr(0), ...modulesAdd[i], meshAgentRecoveryCore].join('');
  2699. } else if ((i == 'windows-tiny') || (i == 'linux-tiny')) {
  2700. obj.defaultMeshCores[i] = [obj.common.IntToStr(0), ...modulesAdd[i], meshTinyCore].join('');
  2701. } else {
  2702. obj.defaultMeshCores[i] = [obj.common.IntToStr(0), ...modulesAdd[i], meshCore].join('');
  2703. }
  2704. obj.defaultMeshCores[i] = Buffer.from(obj.defaultMeshCores[i], 'utf8');
  2705. obj.defaultMeshCoresHash[i] = obj.crypto.createHash('sha384').update(obj.defaultMeshCores[i]).digest('binary');
  2706. obj.debug('main', 'Core module ' + i + ' is ' + obj.defaultMeshCores[i].length + ' bytes.');
  2707. // Write all modules to files. Great for debugging.
  2708. if (dumpToFile) {
  2709. console.log('Core module ' + i + ' is ' + obj.defaultMeshCores[i].length + ' bytes, saving to meshcores/' + i + '.js.'); // Print the core size and filename
  2710. obj.fs.writeFile('meshcores/' + i + '.js', obj.defaultMeshCores[i].slice(4), function () { }); // Write the core to file
  2711. }
  2712. // Compress the mesh cores with DEFLATE
  2713. const callback = function MeshCoreDeflateCb(err, buffer) { if (err == null) { obj.defaultMeshCoresDeflate[MeshCoreDeflateCb.i] = buffer; } }
  2714. callback.i = i;
  2715. require('zlib').deflate(obj.defaultMeshCores[i], { level: require('zlib').Z_BEST_COMPRESSION }, callback);
  2716. }
  2717. }
  2718. // We are done creating all the mesh cores.
  2719. if (func != null) { func(true); }
  2720. };
  2721. // Update the default meshcmd
  2722. obj.updateMeshCmdTimer = 'notset';
  2723. obj.updateMeshCmd = function (func) {
  2724. // Figure out where meshcmd.js is and read it.
  2725. var meshCmd = null, meshcmdPath, moduleAdditions = ['var addedModules = [];\r\n'], moduleDirPath, modulesDir = null;
  2726. if ((obj.args.minifycore !== false) && (obj.fs.existsSync(obj.path.join(obj.datapath, 'meshcmd.min.js')))) { meshcmdPath = obj.path.join(obj.datapath, 'meshcmd.min.js'); meshCmd = obj.fs.readFileSync(meshcmdPath).toString(); }
  2727. else if (obj.fs.existsSync(obj.path.join(obj.datapath, 'meshcmd.js'))) { meshcmdPath = obj.path.join(obj.datapath, 'meshcmd.js'); meshCmd = obj.fs.readFileSync(meshcmdPath).toString(); }
  2728. else if ((obj.args.minifycore !== false) && (obj.fs.existsSync(obj.path.join(__dirname, 'agents', 'meshcmd.min.js')))) { meshcmdPath = obj.path.join(__dirname, 'agents', 'meshcmd.min.js'); meshCmd = obj.fs.readFileSync(meshcmdPath).toString(); }
  2729. else if (obj.fs.existsSync(obj.path.join(__dirname, 'agents', 'meshcmd.js'))) { meshcmdPath = obj.path.join(__dirname, 'agents', 'meshcmd.js'); meshCmd = obj.fs.readFileSync(meshcmdPath).toString(); }
  2730. else { obj.defaultMeshCmd = null; if (func != null) { func(false); } return; } // meshcmd.js not found
  2731. meshCmd = meshCmd.replace("'***Mesh*Cmd*Version***'", '\'' + getCurrentVersion() + '\'');
  2732. // Figure out where the modules_meshcmd folder is.
  2733. if (obj.args.minifycore !== false) { try { moduleDirPath = obj.path.join(meshcmdPath, 'modules_meshcmd_min'); modulesDir = obj.fs.readdirSync(moduleDirPath); } catch (ex) { } } // Favor minified modules if present.
  2734. if (modulesDir == null) { try { moduleDirPath = obj.path.join(meshcmdPath, 'modules_meshcmd'); modulesDir = obj.fs.readdirSync(moduleDirPath); } catch (ex) { } } // Use non-minified mofules.
  2735. if (obj.args.minifycore !== false) { if (modulesDir == null) { try { moduleDirPath = obj.path.join(__dirname, 'agents', 'modules_meshcmd_min'); modulesDir = obj.fs.readdirSync(moduleDirPath); } catch (ex) { } } } // Favor minified modules if present.
  2736. if (modulesDir == null) { try { moduleDirPath = obj.path.join(__dirname, 'agents', 'modules_meshcmd'); modulesDir = obj.fs.readdirSync(moduleDirPath); } catch (ex) { } } // Use non-minified mofules.
  2737. // Read all .js files in the meshcmd modules folder.
  2738. if (modulesDir != null) {
  2739. for (var i in modulesDir) {
  2740. if (modulesDir[i].toLowerCase().endsWith('.js')) {
  2741. // Merge this module
  2742. var moduleName = modulesDir[i].substring(0, modulesDir[i].length - 3);
  2743. if (moduleName.endsWith('.min')) { moduleName = moduleName.substring(0, moduleName.length - 4); } // Remove the ".min" for ".min.js" files.
  2744. moduleAdditions.push('try { addModule("', moduleName, '", "', obj.escapeCodeString(obj.fs.readFileSync(obj.path.join(moduleDirPath, modulesDir[i])).toString('binary')), '"); addedModules.push("', moduleName, '"); } catch (ex) { }\r\n');
  2745. }
  2746. }
  2747. }
  2748. // Set the new default meshcmd.js
  2749. moduleAdditions.push(meshCmd);
  2750. obj.defaultMeshCmd = moduleAdditions.join('');
  2751. //console.log('MeshCmd is ' + obj.defaultMeshCmd.length + ' bytes.'); // DEBUG, Print the merged meshcmd.js size
  2752. //obj.fs.writeFile("C:\\temp\\meshcmd.js", obj.defaultMeshCmd.substring(4)); // DEBUG, Write merged meshcmd.js to file
  2753. if (func != null) { func(true); }
  2754. // Monitor for changes in meshcmd.js
  2755. if (obj.updateMeshCmdTimer === 'notset') {
  2756. obj.updateMeshCmdTimer = null;
  2757. obj.fs.watch(meshcmdPath, function (eventType, filename) {
  2758. if (obj.updateMeshCmdTimer != null) { clearTimeout(obj.updateMeshCmdTimer); obj.updateMeshCmdTimer = null; }
  2759. obj.updateMeshCmdTimer = setTimeout(function () { obj.updateMeshCmd(); }, 5000);
  2760. });
  2761. }
  2762. };
  2763. // List of possible mesh agent install scripts
  2764. const meshToolsList = {
  2765. 'MeshCentralRouter': { localname: 'MeshCentralRouter.exe', dlname: 'winrouter' },
  2766. 'MeshCentralAssistant': { localname: 'MeshCentralAssistant.exe', dlname: 'winassistant', winhash: true }
  2767. //'MeshCentralRouterMacOS': { localname: 'MeshCentralRouter.dmg', dlname: 'MeshCentralRouter.dmg' }
  2768. };
  2769. // Update the list of available mesh agents
  2770. obj.updateMeshTools = function () {
  2771. for (var toolname in meshToolsList) {
  2772. if (meshToolsList[toolname].winhash === true) {
  2773. var toolpath = obj.path.join(__dirname, 'agents', meshToolsList[toolname].localname);
  2774. const toolpath2 = obj.path.join(obj.datapath, 'agents', meshToolsList[toolname].localname);
  2775. if (obj.fs.existsSync(toolpath2)) { toolpath = toolpath2; } // If the tool is present in "meshcentral-data/agents", use that one instead.
  2776. var hashStream = obj.crypto.createHash('sha384');
  2777. hashStream.toolname = toolname;
  2778. hashStream.toolpath = toolpath;
  2779. hashStream.dlname = meshToolsList[toolname].dlname;
  2780. hashStream.hashx = 0;
  2781. hashStream.on('data', function (data) {
  2782. obj.meshToolsBinaries[this.toolname] = { hash: data.toString('hex'), hashx: this.hashx, path: this.toolpath, dlname: this.dlname, url: this.url };
  2783. obj.meshToolsBinaries[this.toolname].url = 'https://' + obj.certificates.CommonName + ':' + ((typeof obj.args.aliasport == 'number') ? obj.args.aliasport : obj.args.port) + '/meshagents?meshaction=' + this.dlname;
  2784. var stats = null;
  2785. try { stats = obj.fs.statSync(this.toolpath); } catch (ex) { }
  2786. if (stats != null) { obj.meshToolsBinaries[this.toolname].size = stats.size; }
  2787. });
  2788. const options = { sourcePath: toolpath, targetStream: hashStream };
  2789. obj.exeHandler.hashExecutableFile(options);
  2790. } else {
  2791. var toolpath = obj.path.join(__dirname, 'agents', meshToolsList[toolname].localname);
  2792. const toolpath2 = obj.path.join(obj.datapath, 'agents', meshToolsList[toolname].localname);
  2793. if (obj.fs.existsSync(toolpath2)) { toolpath = toolpath2; } // If the tool is present in "meshcentral-data/agents", use that one instead.
  2794. var stream = null;
  2795. try {
  2796. stream = obj.fs.createReadStream(toolpath);
  2797. stream.on('data', function (data) { this.hash.update(data, 'binary'); this.hashx += data.length; });
  2798. stream.on('error', function (data) {
  2799. // If there is an error reading this file, make sure this agent is not in the agent table
  2800. if (obj.meshToolsBinaries[this.toolname] != null) { delete obj.meshToolsBinaries[this.toolname]; }
  2801. });
  2802. stream.on('end', function () {
  2803. // Add the agent to the agent table with all information and the hash
  2804. obj.meshToolsBinaries[this.toolname] = {};
  2805. obj.meshToolsBinaries[this.toolname].hash = this.hash.digest('hex');
  2806. obj.meshToolsBinaries[this.toolname].hashx = this.hashx;
  2807. obj.meshToolsBinaries[this.toolname].path = this.agentpath;
  2808. obj.meshToolsBinaries[this.toolname].dlname = this.dlname;
  2809. obj.meshToolsBinaries[this.toolname].url = 'https://' + obj.certificates.CommonName + ':' + ((typeof obj.args.aliasport == 'number') ? obj.args.aliasport : obj.args.port) + '/meshagents?meshaction=' + this.dlname;
  2810. var stats = null;
  2811. try { stats = obj.fs.statSync(this.agentpath); } catch (ex) { }
  2812. if (stats != null) { obj.meshToolsBinaries[this.toolname].size = stats.size; }
  2813. });
  2814. stream.toolname = toolname;
  2815. stream.agentpath = toolpath;
  2816. stream.dlname = meshToolsList[toolname].dlname;
  2817. stream.hash = obj.crypto.createHash('sha384', stream);
  2818. stream.hashx = 0;
  2819. } catch (ex) { }
  2820. }
  2821. }
  2822. };
  2823. // List of possible mesh agent install scripts
  2824. const meshAgentsInstallScriptList = {
  2825. 1: { id: 1, localname: 'meshinstall-linux.sh', rname: 'meshinstall.sh', linux: true },
  2826. 2: { id: 2, localname: 'meshinstall-initd.sh', rname: 'meshagent', linux: true },
  2827. 5: { id: 5, localname: 'meshinstall-bsd-rcd.sh', rname: 'meshagent', linux: true },
  2828. 6: { id: 6, localname: 'meshinstall-linux.js', rname: 'meshinstall.js', linux: true }
  2829. };
  2830. // Update the list of available mesh agents
  2831. obj.updateMeshAgentInstallScripts = function () {
  2832. for (var scriptid in meshAgentsInstallScriptList) {
  2833. var scriptpath = obj.path.join(__dirname, 'agents', meshAgentsInstallScriptList[scriptid].localname);
  2834. var stream = null;
  2835. try {
  2836. stream = obj.fs.createReadStream(scriptpath);
  2837. stream.xdata = '';
  2838. stream.on('data', function (data) { this.hash.update(data, 'binary'); this.xdata += data; });
  2839. stream.on('error', function (data) {
  2840. // If there is an error reading this file, make sure this agent is not in the agent table
  2841. if (obj.meshAgentInstallScripts[this.info.id] != null) { delete obj.meshAgentInstallScripts[this.info.id]; }
  2842. });
  2843. stream.on('end', function () {
  2844. // Add the agent to the agent table with all information and the hash
  2845. obj.meshAgentInstallScripts[this.info.id] = Object.assign({}, this.info);
  2846. obj.meshAgentInstallScripts[this.info.id].hash = this.hash.digest('hex');
  2847. obj.meshAgentInstallScripts[this.info.id].path = this.agentpath;
  2848. obj.meshAgentInstallScripts[this.info.id].data = this.xdata;
  2849. obj.meshAgentInstallScripts[this.info.id].url = 'https://' + obj.certificates.CommonName + ':' + ((typeof obj.args.aliasport == 'number') ? obj.args.aliasport : obj.args.port) + '/meshagents?script=' + this.info.id;
  2850. var stats = null;
  2851. try { stats = obj.fs.statSync(this.agentpath); } catch (ex) { }
  2852. if (stats != null) { obj.meshAgentInstallScripts[this.info.id].size = stats.size; }
  2853. // Place Unit line breaks on Linux scripts if not already present.
  2854. if (obj.meshAgentInstallScripts[this.info.id].linux === true) { obj.meshAgentInstallScripts[this.info.id].data = obj.meshAgentInstallScripts[this.info.id].data.split('\r\n').join('\n') }
  2855. });
  2856. stream.info = meshAgentsInstallScriptList[scriptid];
  2857. stream.agentpath = scriptpath;
  2858. stream.hash = obj.crypto.createHash('sha384', stream);
  2859. } catch (ex) { }
  2860. }
  2861. };
  2862. // List of possible mesh agents
  2863. obj.meshAgentsArchitectureNumbers = {
  2864. 0: { id: 0, localname: 'Unknown', rname: 'meshconsole.exe', desc: 'Unknown agent', update: false, amt: true, platform: 'unknown', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' },
  2865. 1: { id: 1, localname: 'MeshConsole.exe', rname: 'meshconsole32.exe', desc: 'Windows x86-32 console', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery', arcore: 'windows-agentrecovery', tcore: 'windows-tiny' },
  2866. 2: { id: 2, localname: 'MeshConsole64.exe', rname: 'meshconsole64.exe', desc: 'Windows x86-64 console', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery', arcore: 'windows-agentrecovery', tcore: 'windows-tiny' },
  2867. 3: { id: 3, localname: 'MeshService.exe', rname: 'meshagent32.exe', desc: 'Windows x86-32 service', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery', arcore: 'windows-agentrecovery', tcore: 'windows-tiny', codesign: true },
  2868. 4: { id: 4, localname: 'MeshService64.exe', rname: 'meshagent64.exe', desc: 'Windows x86-64 service', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery', arcore: 'windows-agentrecovery', tcore: 'windows-tiny', codesign: true },
  2869. 5: { id: 5, localname: 'meshagent_x86', rname: 'meshagent', desc: 'Linux x86-32', update: true, amt: true, platform: 'linux', core: 'linux-amt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' },
  2870. 6: { id: 6, localname: 'meshagent_x86-64', rname: 'meshagent', desc: 'Linux x86-64', update: true, amt: true, platform: 'linux', core: 'linux-amt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' },
  2871. 7: { id: 7, localname: 'meshagent_mips', rname: 'meshagent', desc: 'Linux MIPS', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' },
  2872. 8: { id: 8, localname: 'MeshAgent-Linux-XEN-x86-32', rname: 'meshagent', desc: 'XEN x86-64', update: true, amt: false, platform: 'linux', core: 'linux-amt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' },
  2873. 9: { id: 9, localname: 'meshagent_arm', rname: 'meshagent', desc: 'Linux ARM5', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' },
  2874. 10: { id: 10, localname: 'MeshAgent-Linux-ARM-PlugPC', rname: 'meshagent', desc: 'Linux ARM PlugPC', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' },
  2875. 11: { id: 11, localname: 'meshagent_osx-x86-32', rname: 'meshosx', desc: 'Apple macOS x86-32', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' }, // Apple x86-32 binary, no longer supported.
  2876. 12: { id: 12, localname: 'MeshAgent-Android-x86', rname: 'meshandroid', desc: 'Android x86-32', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' },
  2877. 13: { id: 13, localname: 'meshagent_pogo', rname: 'meshagent', desc: 'Linux ARM PogoPlug', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' },
  2878. 14: { id: 14, localname: 'meshagent_android.apk', rname: 'meshandroid.apk', desc: 'Android', update: false, amt: false, platform: 'android', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' }, // Get this one from Google Play
  2879. 15: { id: 15, localname: 'meshagent_poky', rname: 'meshagent', desc: 'Linux Poky x86-32', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' },
  2880. 16: { id: 16, localname: 'meshagent_osx-x86-64', rname: 'meshagent', desc: 'Apple macOS x86-64', update: true, amt: false, platform: 'osx', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' }, // Apple x86-64 binary
  2881. 17: { id: 17, localname: 'MeshAgent-ChromeOS', rname: 'meshagent', desc: 'Google ChromeOS', update: false, amt: false, platform: 'chromeos', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' }, // Get this one from Chrome store
  2882. 18: { id: 18, localname: 'meshagent_poky64', rname: 'meshagent', desc: 'Linux Poky x86-64', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' },
  2883. 19: { id: 19, localname: 'meshagent_x86_nokvm', rname: 'meshagent', desc: 'Linux x86-32 NoKVM', update: true, amt: true, platform: 'linux', core: 'linux-amt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' },
  2884. 20: { id: 20, localname: 'meshagent_x86-64_nokvm', rname: 'meshagent', desc: 'Linux x86-64 NoKVM', update: true, amt: true, platform: 'linux', core: 'linux-amt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' },
  2885. 21: { id: 21, localname: 'MeshAgent-WinMinCore-Console-x86-32.exe', rname: 'meshagent.exe', desc: 'Windows MinCore Console x86-32', update: true, amt: false, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery', arcore: 'windows-agentrecovery', tcore: 'windows-tiny' },
  2886. 22: { id: 22, localname: 'MeshAgent-WinMinCore-Service-x86-64.exe', rname: 'meshagent.exe', desc: 'Windows MinCore Service x86-32', update: true, amt: false, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery', arcore: 'windows-agentrecovery', tcore: 'windows-tiny' },
  2887. 23: { id: 23, localname: 'MeshAgent-NodeJS', rname: 'meshagent', desc: 'NodeJS', update: false, amt: false, platform: 'node', core: 'nodejs', rcore: 'nodejs', arcore: 'nodejs', tcore: 'nodejs' }, // NodeJS based agent
  2888. 24: { id: 24, localname: 'meshagent_arm-linaro', rname: 'meshagent', desc: 'Linux ARM Linaro', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' },
  2889. 25: { id: 25, localname: 'meshagent_armhf', rname: 'meshagent', desc: 'Linux ARM - HardFloat', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' }, // "armv6l" and "armv7l"
  2890. 26: { id: 26, localname: 'meshagent_aarch64', rname: 'meshagent', desc: 'Linux ARM 64 bit (glibc/2.24)', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' }, // This is replaced by ARCHID 32
  2891. 27: { id: 27, localname: 'meshagent_armhf2', rname: 'meshagent', desc: 'Linux ARM - HardFloat', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' }, // Raspbian 7 2015-02-02 for old Raspberry Pi.
  2892. 28: { id: 28, localname: 'meshagent_mips24kc', rname: 'meshagent', desc: 'Linux MIPS24KC/MUSL (OpenWRT)', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' }, // MIPS Router with OpenWRT
  2893. 29: { id: 29, localname: 'meshagent_osx-arm-64', rname: 'meshagent', desc: 'Apple macOS ARM-64', update: true, amt: false, platform: 'osx', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' }, // Apple Silicon ARM 64bit
  2894. 30: { id: 30, localname: 'meshagent_freebsd_x86-64', rname: 'meshagent', desc: 'FreeBSD x86-64', update: true, amt: false, platform: 'freebsd', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' }, // FreeBSD x64
  2895. 32: { id: 32, localname: 'meshagent_aarch64', rname: 'meshagent', desc: 'Linux ARM 64 bit (glibc/2.24)', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' },
  2896. 33: { id: 33, localname: 'meshagent_openwrt_x86_64', rname: 'meshagent', desc: 'OpenWRT x86-64', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' }, // This is replaced with ARCHID 36.
  2897. 34: { id: 34, localname: 'assistant_windows', rname: 'meshassistant', desc: 'MeshCentral Assistant (Windows)', update: false, amt: false, platform: 'assistant', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' }, // MeshCentral Assistant for Windows
  2898. 35: { id: 35, localname: 'meshagent_linux-armada370-hf', rname: 'meshagent', desc: 'Armada370 - ARM32/HF (libc/2.26)', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' }, // Armada370
  2899. 36: { id: 36, localname: 'meshagent_openwrt_x86_64', rname: 'meshagent', desc: 'OpenWRT x86-64', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' }, // OpenWRT x86-64
  2900. 37: { id: 37, localname: 'meshagent_openbsd_x86-64', rname: 'meshagent', desc: 'OpenBSD x86-64', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' }, // OpenBSD x86-64
  2901. 40: { id: 40, localname: 'meshagent_mipsel24kc', rname: 'meshagent', desc: 'Linux MIPSEL24KC (OpenWRT)', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' }, // MIPS Router with OpenWRT
  2902. 41: { id: 41, localname: 'meshagent_aarch64-cortex-a53', rname: 'meshagent', desc: 'ARMADA/CORTEX-A53/MUSL (OpenWRT)', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' }, // OpenWRT Routers
  2903. 42: { id: 42, localname: 'MeshConsoleARM64.exe', rname: 'meshconsolearm64.exe', desc: 'Windows ARM-64 console', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery', arcore: 'windows-agentrecovery', tcore: 'windows-tiny' },
  2904. 43: { id: 43, localname: 'MeshServiceARM64.exe', rname: 'meshagentarm64.exe', desc: 'Windows ARM-64 service', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery', arcore: 'windows-agentrecovery', tcore: 'windows-tiny', codesign: true },
  2905. 10003: { id: 10003, localname: 'MeshService.exe', rname: 'meshagent32.exe', desc: 'Windows x86-32 service', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery', arcore: 'windows-agentrecovery', tcore: 'windows-tiny', unsigned: true },
  2906. 10004: { id: 10004, localname: 'MeshService64.exe', rname: 'meshagent64.exe', desc: 'Windows x86-64 service', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery', arcore: 'windows-agentrecovery', tcore: 'windows-tiny', unsigned: true },
  2907. 10005: { id: 10005, localname: 'meshagent_osx-universal-64', rname: 'meshagent', desc: 'Apple macOS Universal Binary', update: true, amt: false, platform: 'osx', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' }, // Apple Silicon + x86 universal binary
  2908. 10006: { id: 10006, localname: 'MeshCentralAssistant.exe', rname: 'MeshCentralAssistant.exe', desc: 'MeshCentral Assistant for Windows', update: false, amt: false, platform: 'win32' }, // MeshCentral Assistant
  2909. 11000: { id: 11000, localname: 'MeshCmd.exe', rname: 'MeshCmd.exe', desc: 'Windows x86-32 meshcmd', update: false, amt: true, platform: 'win32', codesign: true }, // MeshCMD for Windows x86 32-bit
  2910. 11001: { id: 11001, localname: 'MeshCmd64.exe', rname: 'MeshCmd64.exe', desc: 'Windows x86-64 meshcmd', update: false, amt: true, platform: 'win32', codesign: true }, // MeshCMD for Windows x86 64-bit
  2911. 11002: { id: 11002, localname: 'MeshCmdARM64.exe', rname: 'MeshCmdARM64.exe', desc: 'Windows ARM-64 meshcmd', update: false, amt: true, platform: 'win32', codesign: true } // MeshCMD for Windows ARM 64-bit
  2912. };
  2913. // Sign windows agents
  2914. obj.signMeshAgents = function (domain, func) {
  2915. // Setup the domain is specified
  2916. var objx = domain, suffix = '';
  2917. if (domain.id == '') { objx = obj; } else { suffix = '-' + domain.id; objx.meshAgentBinaries = {}; }
  2918. // Check if a custom agent signing certificate is available
  2919. var agentSignCertInfo = require('./authenticode.js').loadCertificates([obj.path.join(obj.datapath, 'agentsigningcert.pem')]);
  2920. // If not using a custom signing cert, get agent code signature certificate ready with the full cert chain
  2921. if ((agentSignCertInfo == null) && (obj.certificates.codesign != null)) {
  2922. agentSignCertInfo = {
  2923. cert: obj.certificateOperations.forge.pki.certificateFromPem(obj.certificates.codesign.cert),
  2924. key: obj.certificateOperations.forge.pki.privateKeyFromPem(obj.certificates.codesign.key),
  2925. extraCerts: [obj.certificateOperations.forge.pki.certificateFromPem(obj.certificates.root.cert)]
  2926. }
  2927. }
  2928. if (agentSignCertInfo == null) { func(); return; } // No code signing certificate, nothing to do.
  2929. // Setup the domain is specified
  2930. var objx = domain, suffix = '';
  2931. if (domain.id == '') { objx = obj; } else { suffix = '-' + domain.id; objx.meshAgentBinaries = {}; }
  2932. // Generate the agent signature description and URL
  2933. const serverSignedAgentsPath = obj.path.join(obj.datapath, 'signedagents' + suffix);
  2934. const signDesc = (domain.title ? domain.title : agentSignCertInfo.cert.subject.hash);
  2935. const httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
  2936. var httpsHost = ((domain.dns != null) ? domain.dns : obj.certificates.CommonName);
  2937. if (obj.args.agentaliasdns != null) { httpsHost = obj.args.agentaliasdns; }
  2938. var signUrl = 'https://' + httpsHost;
  2939. if (httpsPort != 443) { signUrl += ':' + httpsPort; }
  2940. var xdomain = (domain.dns == null) ? domain.id : '';
  2941. if (xdomain != '') xdomain += '/';
  2942. signUrl += '/' + xdomain;
  2943. // If requested, lock the agent to this server
  2944. if (obj.config.settings.agentsignlock) { signUrl += '?ServerID=' + obj.certificateOperations.getPublicKeyHash(obj.certificates.agent.cert).toUpperCase(); }
  2945. // Setup the time server
  2946. var timeStampUrl = 'http://timestamp.comodoca.com/authenticode';
  2947. if (obj.args.agenttimestampserver === false) { timeStampUrl = null; }
  2948. else if (typeof obj.args.agenttimestampserver == 'string') { timeStampUrl = obj.args.agenttimestampserver; }
  2949. // Setup the time server proxy
  2950. var timeStampProxy = null;
  2951. if (typeof obj.args.agenttimestampproxy == 'string') { timeStampProxy = obj.args.agenttimestampproxy; }
  2952. else if ((obj.args.agenttimestampproxy !== false) && (typeof obj.args.npmproxy == 'string')) { timeStampProxy = obj.args.npmproxy; }
  2953. // Setup the pending operations counter
  2954. var pendingOperations = 1;
  2955. for (var archid in obj.meshAgentsArchitectureNumbers) {
  2956. if (obj.meshAgentsArchitectureNumbers[archid].codesign !== true) continue;
  2957. var agentpath;
  2958. if (domain.id == '') {
  2959. // Load all agents when processing the default domain
  2960. agentpath = obj.path.join(__dirname, 'agents' + suffix, obj.meshAgentsArchitectureNumbers[archid].localname);
  2961. var agentpath2 = obj.path.join(obj.datapath, 'agents' + suffix, obj.meshAgentsArchitectureNumbers[archid].localname);
  2962. if (obj.fs.existsSync(agentpath2)) { agentpath = agentpath2; delete obj.meshAgentsArchitectureNumbers[archid].codesign; } // If the agent is present in "meshcentral-data/agents", use that one instead.
  2963. } else {
  2964. // When processing an extra domain, only load agents that are specific to that domain
  2965. agentpath = obj.path.join(obj.datapath, 'agents' + suffix, obj.meshAgentsArchitectureNumbers[archid].localname);
  2966. if (obj.fs.existsSync(agentpath)) { delete obj.meshAgentsArchitectureNumbers[archid].codesign; } else { continue; } // If the agent is not present in "meshcentral-data/agents" skip.
  2967. }
  2968. // Open the original agent with authenticode
  2969. const signeedagentpath = obj.path.join(serverSignedAgentsPath, obj.meshAgentsArchitectureNumbers[archid].localname);
  2970. const originalAgent = require('./authenticode.js').createAuthenticodeHandler(agentpath);
  2971. if (originalAgent != null) {
  2972. // Check if the agent is already signed correctly
  2973. const destinationAgent = require('./authenticode.js').createAuthenticodeHandler(signeedagentpath);
  2974. var destinationAgentOk = (
  2975. (destinationAgent != null) &&
  2976. (destinationAgent.fileHashSigned != null) &&
  2977. (Buffer.compare(destinationAgent.fileHashSigned, destinationAgent.fileHashActual) == 0) &&
  2978. (destinationAgent.signingAttribs.indexOf(signUrl) >= 0) &&
  2979. (destinationAgent.signingAttribs.indexOf(signDesc) >= 0)
  2980. );
  2981. if (destinationAgent != null) {
  2982. // If the agent is signed correctly, look to see if the resources in the destination agent are correct
  2983. var orgVersionStrings = originalAgent.getVersionInfo();
  2984. if (destinationAgentOk == true) {
  2985. const versionStrings = destinationAgent.getVersionInfo();
  2986. const versionProperties = ['FileDescription', 'FileVersion', 'InternalName', 'LegalCopyright', 'OriginalFilename', 'ProductName', 'ProductVersion'];
  2987. for (var i in versionProperties) {
  2988. const prop = versionProperties[i], propl = prop.toLowerCase();
  2989. if ((domain.agentfileinfo != null) && (typeof domain.agentfileinfo == 'object') && (typeof domain.agentfileinfo[propl] == 'string')) {
  2990. if (domain.agentfileinfo[propl] != versionStrings[prop]) { destinationAgentOk = false; break; } // If the resource we want is not the same as the destination executable, we need to re-sign the agent.
  2991. } else {
  2992. if (orgVersionStrings[prop] != versionStrings[prop]) { destinationAgentOk = false; break; } // if the resource of the orginal agent not the same as the destination executable, we need to re-sign the agent.
  2993. }
  2994. }
  2995. // Check file version number
  2996. if (destinationAgentOk == true) {
  2997. if ((domain.agentfileinfo != null) && (typeof domain.agentfileinfo == 'object') && (typeof domain.agentfileinfo['fileversionnumber'] == 'string')) {
  2998. if (domain.agentfileinfo['fileversionnumber'] != versionStrings['~FileVersion']) { destinationAgentOk = false; } // If the resource we want is not the same as the destination executable, we need to re-sign the agent.
  2999. } else {
  3000. if (orgVersionStrings['~FileVersion'] != versionStrings['~FileVersion']) { destinationAgentOk = false; } // if the resource of the orginal agent not the same as the destination executable, we need to re-sign the agent.
  3001. }
  3002. }
  3003. // Check product version number
  3004. if (destinationAgentOk == true) {
  3005. if ((domain.agentfileinfo != null) && (typeof domain.agentfileinfo == 'object') && (typeof domain.agentfileinfo['productversionnumber'] == 'string')) {
  3006. if (domain.agentfileinfo['productversionnumber'] != versionStrings['~ProductVersion']) { destinationAgentOk = false; } // If the resource we want is not the same as the destination executable, we need to re-sign the agent.
  3007. } else {
  3008. if (orgVersionStrings['~ProductVersion'] != versionStrings['~ProductVersion']) { destinationAgentOk = false; } // if the resource of the orginal agent not the same as the destination executable, we need to re-sign the agent.
  3009. }
  3010. }
  3011. // Check the agent icon
  3012. if (destinationAgentOk == true) {
  3013. if ((domain.agentfileinfo != null) && (domain.agentfileinfo.icon != null)) {
  3014. // Check if the destination agent matches the icon we want
  3015. const agentIconGroups = destinationAgent.getIconInfo();
  3016. if (agentIconGroups != null) {
  3017. const agentIconGroupNames = Object.keys(agentIconGroups);
  3018. if (agentIconGroupNames.length > 0) {
  3019. const agentMainIconGroup = agentIconGroups[agentIconGroupNames[0]];
  3020. if (agentMainIconGroup.resCount != domain.agentfileinfo.icon.resCount) {
  3021. destinationAgentOk = false; // The icon image count is different, don't bother hashing to see if the icons are different.
  3022. } else {
  3023. const agentMainIconGroupHash = require('./authenticode.js').hashObject(agentMainIconGroup);
  3024. const iconHash = require('./authenticode.js').hashObject(domain.agentfileinfo.icon);
  3025. if (agentMainIconGroupHash != iconHash) { destinationAgentOk = false; } // If the existing agent icon does not match the desired icon, we need to re-sign the agent.
  3026. }
  3027. }
  3028. }
  3029. } else {
  3030. // Check if the destination agent has the default icon
  3031. const agentIconGroups1 = destinationAgent.getIconInfo();
  3032. const agentIconGroups2 = originalAgent.getIconInfo();
  3033. if (agentIconGroups1.resCount != agentIconGroups2.resCount) {
  3034. destinationAgentOk = false; // The icon image count is different, don't bother hashing to see if the icons are different.
  3035. } else {
  3036. const iconHash1 = require('./authenticode.js').hashObject(agentIconGroups1);
  3037. const iconHash2 = require('./authenticode.js').hashObject(agentIconGroups2);
  3038. if (iconHash1 != iconHash2) { destinationAgentOk = false; } // If the existing agent icon does not match the desired icon, we need to re-sign the agent.
  3039. }
  3040. }
  3041. }
  3042. // Check the agent logo
  3043. if (destinationAgentOk == true) {
  3044. if ((domain.agentfileinfo != null) && (domain.agentfileinfo.logo != null)) {
  3045. // Check if the destination agent matches the logo we want
  3046. const agentBitmaps = destinationAgent.getBitmapInfo();
  3047. if (agentBitmaps != null) {
  3048. const agentBitmapNames = Object.keys(agentBitmaps);
  3049. if (agentBitmapNames.length > 0) {
  3050. const agentMainBitmap = agentBitmaps[agentBitmapNames[0]];
  3051. const agentMainBitmapHash = require('./authenticode.js').hashObject(agentMainBitmap);
  3052. const bitmapHash = require('./authenticode.js').hashObject(domain.agentfileinfo.logo);
  3053. if (agentMainBitmapHash != bitmapHash) { destinationAgentOk = false; } // If the existing agent logo does not match the desired logo, we need to re-sign the agent.
  3054. }
  3055. }
  3056. } else {
  3057. // Check if the destination agent has the default icon
  3058. const agentBitmaps1 = destinationAgent.getBitmapInfo();
  3059. const agentBitmaps2 = originalAgent.getBitmapInfo();
  3060. const agentBitmapNames = Object.keys(agentBitmaps1);
  3061. if (agentBitmapNames.length == 0) {
  3062. destinationAgentOk = false;
  3063. } else {
  3064. const iconHash1 = require('./authenticode.js').hashObject(agentBitmaps1[agentBitmapNames[0]]);
  3065. const iconHash2 = require('./authenticode.js').hashObject(agentBitmaps2[agentBitmapNames[0]]);
  3066. if (iconHash1 != iconHash2) { destinationAgentOk = false; } // If the existing agent icon does not match the desired icon, we need to re-sign the agent.
  3067. }
  3068. }
  3069. }
  3070. }
  3071. // If everything looks ok, runs a hash of the original and destination agent .text, .data and .rdata sections. If different, sign the agent again.
  3072. if ((destinationAgentOk == true) && (originalAgent.getHashOfSection('sha384', '.text').compare(destinationAgent.getHashOfSection('sha384', '.text')) != 0)) { destinationAgentOk = false; }
  3073. if ((destinationAgentOk == true) && (originalAgent.getHashOfSection('sha384', '.data').compare(destinationAgent.getHashOfSection('sha384', '.data')) != 0)) { destinationAgentOk = false; }
  3074. if ((destinationAgentOk == true) && (originalAgent.getHashOfSection('sha384', '.rdata').compare(destinationAgent.getHashOfSection('sha384', '.rdata')) != 0)) { destinationAgentOk = false; }
  3075. // We are done comparing the destination agent, close it.
  3076. destinationAgent.close();
  3077. }
  3078. if (destinationAgentOk == false) {
  3079. // If not signed correctly, sign it. First, create the server signed agent folder if needed
  3080. try { obj.fs.mkdirSync(serverSignedAgentsPath); } catch (ex) { }
  3081. const xagentSignedFunc = function agentSignedFunc(err, size) {
  3082. if (err == null) {
  3083. // Agent was signed succesfuly
  3084. console.log(obj.common.format('Code signed {0}.', agentSignedFunc.objx.meshAgentsArchitectureNumbers[agentSignedFunc.archid].localname));
  3085. } else {
  3086. // Failed to sign agent
  3087. addServerWarning('Failed to sign \"' + agentSignedFunc.objx.meshAgentsArchitectureNumbers[agentSignedFunc.archid].localname + '\": ' + err, 22, [agentSignedFunc.objx.meshAgentsArchitectureNumbers[agentSignedFunc.archid].localname, err]);
  3088. }
  3089. if (--pendingOperations === 0) { agentSignedFunc.func(); }
  3090. }
  3091. pendingOperations++;
  3092. xagentSignedFunc.func = func;
  3093. xagentSignedFunc.objx = objx;
  3094. xagentSignedFunc.archid = archid;
  3095. xagentSignedFunc.signeedagentpath = signeedagentpath;
  3096. // Parse the resources in the executable and make any required changes
  3097. var resChanges = false, versionStrings = null;
  3098. if ((domain.agentfileinfo != null) && (typeof domain.agentfileinfo == 'object')) {
  3099. versionStrings = originalAgent.getVersionInfo();
  3100. var versionProperties = ['FileDescription', 'FileVersion', 'InternalName', 'LegalCopyright', 'OriginalFilename', 'ProductName', 'ProductVersion'];
  3101. // Change the agent string properties
  3102. for (var i in versionProperties) {
  3103. const prop = versionProperties[i], propl = prop.toLowerCase();
  3104. if (domain.agentfileinfo[propl] && (domain.agentfileinfo[propl] != versionStrings[prop])) { versionStrings[prop] = domain.agentfileinfo[propl]; resChanges = true; }
  3105. }
  3106. // Change the agent file version
  3107. if (domain.agentfileinfo['fileversionnumber'] && (domain.agentfileinfo['fileversionnumber'] != versionStrings['~FileVersion'])) {
  3108. versionStrings['~FileVersion'] = domain.agentfileinfo['fileversionnumber']; resChanges = true;
  3109. }
  3110. // Change the agent product version
  3111. if (domain.agentfileinfo['productversionnumber'] && (domain.agentfileinfo['productversionnumber'] != versionStrings['~ProductVersion'])) {
  3112. versionStrings['~ProductVersion'] = domain.agentfileinfo['productversionnumber']; resChanges = true;
  3113. }
  3114. if (resChanges == true) { originalAgent.setVersionInfo(versionStrings); }
  3115. // Change the agent icon
  3116. if (domain.agentfileinfo.icon != null) {
  3117. const agentIconGroups = originalAgent.getIconInfo();
  3118. if (agentIconGroups != null) {
  3119. const agentIconGroupNames = Object.keys(agentIconGroups);
  3120. if (agentIconGroupNames.length > 0) {
  3121. const agentMainIconGroupName = agentIconGroupNames[0];
  3122. agentIconGroups[agentIconGroupNames[0]] = domain.agentfileinfo.icon;
  3123. originalAgent.setIconInfo(agentIconGroups);
  3124. }
  3125. }
  3126. }
  3127. // Change the agent logo
  3128. if (domain.agentfileinfo.logo != null) {
  3129. const agentBitmaps = originalAgent.getBitmapInfo();
  3130. if (agentBitmaps != null) {
  3131. const agentBitmapNames = Object.keys(agentBitmaps);
  3132. if (agentBitmapNames.length > 0) {
  3133. agentBitmaps[agentBitmapNames[0]] = domain.agentfileinfo.logo;
  3134. originalAgent.setBitmapInfo(agentBitmaps);
  3135. }
  3136. }
  3137. }
  3138. }
  3139. const signingArguments = { out: signeedagentpath, desc: signDesc, url: signUrl, time: timeStampUrl, proxy: timeStampProxy }; // Shallow clone
  3140. obj.debug('main', "Code signing with arguments: " + JSON.stringify(signingArguments));
  3141. if (resChanges == false) {
  3142. // Sign the agent the simple way, without changing any resources.
  3143. originalAgent.sign(agentSignCertInfo, signingArguments, xagentSignedFunc);
  3144. } else {
  3145. // Change the agent resources and sign the agent, this is a much more involved process.
  3146. // NOTE: This is experimental and could corupt the agent.
  3147. originalAgent.writeExecutable(signingArguments, agentSignCertInfo, xagentSignedFunc);
  3148. }
  3149. } else {
  3150. // Signed agent is already ok, use it.
  3151. originalAgent.close();
  3152. }
  3153. }
  3154. }
  3155. if (--pendingOperations === 0) { func(); }
  3156. }
  3157. // Update the list of available mesh agents
  3158. obj.updateMeshAgentsTable = function (domain, func) {
  3159. // Check if a custom agent signing certificate is available
  3160. var agentSignCertInfo = require('./authenticode.js').loadCertificates([obj.path.join(obj.datapath, 'agentsigningcert.pem')]);
  3161. // If not using a custom signing cert, get agent code signature certificate ready with the full cert chain
  3162. if ((agentSignCertInfo == null) && (obj.certificates.codesign != null)) {
  3163. agentSignCertInfo = {
  3164. cert: obj.certificateOperations.forge.pki.certificateFromPem(obj.certificates.codesign.cert),
  3165. key: obj.certificateOperations.forge.pki.privateKeyFromPem(obj.certificates.codesign.key),
  3166. extraCerts: [obj.certificateOperations.forge.pki.certificateFromPem(obj.certificates.root.cert)]
  3167. }
  3168. }
  3169. // Setup the domain is specified
  3170. var objx = domain, suffix = '';
  3171. if (domain.id == '') { objx = obj; } else { suffix = '-' + domain.id; objx.meshAgentBinaries = {}; }
  3172. // Load agent information file. This includes the data & time of the agent.
  3173. const agentInfo = [];
  3174. try { agentInfo = JSON.parse(obj.fs.readFileSync(obj.path.join(__dirname, 'agents', 'hashagents.json'), 'utf8')); } catch (ex) { }
  3175. var archcount = 0;
  3176. for (var archid in obj.meshAgentsArchitectureNumbers) {
  3177. var agentpath;
  3178. if (domain.id == '') {
  3179. // Load all agents when processing the default domain
  3180. agentpath = obj.path.join(__dirname, 'agents' + suffix, obj.meshAgentsArchitectureNumbers[archid].localname);
  3181. if (obj.meshAgentsArchitectureNumbers[archid].unsigned !== true) {
  3182. const agentpath2 = obj.path.join(obj.datapath, 'signedagents' + suffix, obj.meshAgentsArchitectureNumbers[archid].localname);
  3183. if (obj.fs.existsSync(agentpath2)) { agentpath = agentpath2; } // If the agent is present in "meshcentral-data/signedagents", use that one instead.
  3184. const agentpath3 = obj.path.join(obj.datapath, 'agents' + suffix, obj.meshAgentsArchitectureNumbers[archid].localname);
  3185. if (obj.fs.existsSync(agentpath3)) { agentpath = agentpath3; } // If the agent is present in "meshcentral-data/agents", use that one instead.
  3186. }
  3187. } else {
  3188. // When processing an extra domain, only load agents that are specific to that domain
  3189. agentpath = obj.path.join(obj.datapath, 'agents' + suffix, obj.meshAgentsArchitectureNumbers[archid].localname);
  3190. if (obj.fs.existsSync(agentpath)) { delete obj.meshAgentsArchitectureNumbers[archid].codesign; } else { continue; } // If the agent is not present in "meshcentral-data/agents" skip.
  3191. }
  3192. // Fetch agent binary information
  3193. var stats = null;
  3194. try { stats = obj.fs.statSync(agentpath); } catch (ex) { }
  3195. if ((stats == null)) continue; // If this agent does not exist, skip it.
  3196. // Setup agent information
  3197. archcount++;
  3198. objx.meshAgentBinaries[archid] = Object.assign({}, obj.meshAgentsArchitectureNumbers[archid]);
  3199. objx.meshAgentBinaries[archid].path = agentpath;
  3200. objx.meshAgentBinaries[archid].url = 'http://' + obj.certificates.CommonName + ':' + ((typeof obj.args.aliasport == 'number') ? obj.args.aliasport : obj.args.port) + '/meshagents?id=' + archid;
  3201. objx.meshAgentBinaries[archid].size = stats.size;
  3202. if ((agentInfo[archid] != null) && (agentInfo[archid].mtime != null)) { objx.meshAgentBinaries[archid].mtime = new Date(agentInfo[archid].mtime); } // Set agent time if available
  3203. // If this is a windows binary, pull binary information
  3204. if (obj.meshAgentsArchitectureNumbers[archid].platform == 'win32') {
  3205. try { objx.meshAgentBinaries[archid].pe = obj.exeHandler.parseWindowsExecutable(agentpath); } catch (ex) { }
  3206. }
  3207. // If agents must be stored in RAM or if this is a Windows 32/64 agent, load the agent in RAM.
  3208. if ((obj.args.agentsinram === true) || (((archid == 3) || (archid == 4)) && (obj.args.agentsinram !== false))) {
  3209. if ((archid == 3) || (archid == 4)) {
  3210. // Load the agent with a random msh added to it.
  3211. const outStream = new require('stream').Duplex();
  3212. outStream.meshAgentBinary = objx.meshAgentBinaries[archid];
  3213. if (agentSignCertInfo) { outStream.meshAgentBinary.randomMsh = agentSignCertInfo.cert.subject.hash; } else { outStream.meshAgentBinary.randomMsh = obj.crypto.randomBytes(16).toString('hex'); }
  3214. outStream.bufferList = [];
  3215. outStream._write = function (chunk, encoding, callback) { this.bufferList.push(chunk); if (callback) callback(); }; // Append the chuck.
  3216. outStream._read = function (size) { }; // Do nothing, this is not going to be called.
  3217. outStream.on('finish', function () {
  3218. // Merge all chunks
  3219. this.meshAgentBinary.data = Buffer.concat(this.bufferList);
  3220. this.meshAgentBinary.size = this.meshAgentBinary.data.length;
  3221. delete this.bufferList;
  3222. // Hash the uncompressed binary
  3223. const hash = obj.crypto.createHash('sha384').update(this.meshAgentBinary.data);
  3224. this.meshAgentBinary.fileHash = hash.digest('binary');
  3225. this.meshAgentBinary.fileHashHex = Buffer.from(this.meshAgentBinary.fileHash, 'binary').toString('hex');
  3226. // Compress the agent using ZIP
  3227. const archive = require('archiver')('zip', { level: 9 }); // Sets the compression method.
  3228. const onZipData = function onZipData(buffer) { onZipData.x.zacc.push(buffer); }
  3229. const onZipEnd = function onZipEnd() {
  3230. // Concat all the buffer for create compressed zip agent
  3231. const concatData = Buffer.concat(onZipData.x.zacc);
  3232. delete onZipData.x.zacc;
  3233. // Hash the compressed binary
  3234. const hash = obj.crypto.createHash('sha384').update(concatData);
  3235. onZipData.x.zhash = hash.digest('binary');
  3236. onZipData.x.zhashhex = Buffer.from(onZipData.x.zhash, 'binary').toString('hex');
  3237. // Set the agent
  3238. onZipData.x.zdata = concatData;
  3239. onZipData.x.zsize = concatData.length;
  3240. }
  3241. const onZipError = function onZipError() { delete onZipData.x.zacc; }
  3242. this.meshAgentBinary.zacc = [];
  3243. onZipData.x = this.meshAgentBinary;
  3244. onZipEnd.x = this.meshAgentBinary;
  3245. onZipError.x = this.meshAgentBinary;
  3246. archive.on('data', onZipData);
  3247. archive.on('end', onZipEnd);
  3248. archive.on('error', onZipError);
  3249. // Starting with NodeJS v16, passing in a buffer at archive.append() will result a compressed file with zero byte length. To fix this, we pass in the buffer as a stream.
  3250. // archive.append(this.meshAgentBinary.data, { name: 'meshagent' }); // This is the version that does not work on NodeJS v16.
  3251. const ReadableStream = require('stream').Readable;
  3252. const zipInputStream = new ReadableStream();
  3253. zipInputStream.push(this.meshAgentBinary.data);
  3254. zipInputStream.push(null);
  3255. archive.append(zipInputStream, { name: 'meshagent' });
  3256. archive.finalize();
  3257. })
  3258. obj.exeHandler.streamExeWithMeshPolicy(
  3259. {
  3260. platform: 'win32',
  3261. sourceFileName: agentpath,
  3262. destinationStream: outStream,
  3263. randomPolicy: true, // Indicates that the msh policy is random data.
  3264. msh: outStream.meshAgentBinary.randomMsh,
  3265. peinfo: objx.meshAgentBinaries[archid].pe
  3266. });
  3267. } else {
  3268. // Load the agent as-is
  3269. objx.meshAgentBinaries[archid].data = obj.fs.readFileSync(agentpath);
  3270. // Compress the agent using ZIP
  3271. const archive = require('archiver')('zip', { level: 9 }); // Sets the compression method.
  3272. const onZipData = function onZipData(buffer) { onZipData.x.zacc.push(buffer); }
  3273. const onZipEnd = function onZipEnd() {
  3274. // Concat all the buffer for create compressed zip agent
  3275. const concatData = Buffer.concat(onZipData.x.zacc);
  3276. delete onZipData.x.zacc;
  3277. // Hash the compressed binary
  3278. const hash = obj.crypto.createHash('sha384').update(concatData);
  3279. onZipData.x.zhash = hash.digest('binary');
  3280. onZipData.x.zhashhex = Buffer.from(onZipData.x.zhash, 'binary').toString('hex');
  3281. // Set the agent
  3282. onZipData.x.zdata = concatData;
  3283. onZipData.x.zsize = concatData.length;
  3284. //console.log('Packed', onZipData.x.size, onZipData.x.zsize);
  3285. }
  3286. const onZipError = function onZipError() { delete onZipData.x.zacc; }
  3287. objx.meshAgentBinaries[archid].zacc = [];
  3288. onZipData.x = objx.meshAgentBinaries[archid];
  3289. onZipEnd.x = objx.meshAgentBinaries[archid];
  3290. onZipError.x = objx.meshAgentBinaries[archid];
  3291. archive.on('data', onZipData);
  3292. archive.on('end', onZipEnd);
  3293. archive.on('error', onZipError);
  3294. archive.append(objx.meshAgentBinaries[archid].data, { name: 'meshagent' });
  3295. archive.finalize();
  3296. }
  3297. }
  3298. // Hash the binary
  3299. const hashStream = obj.crypto.createHash('sha384');
  3300. hashStream.archid = archid;
  3301. hashStream.on('data', function (data) {
  3302. objx.meshAgentBinaries[this.archid].hash = data.toString('binary');
  3303. objx.meshAgentBinaries[this.archid].hashhex = data.toString('hex');
  3304. if ((--archcount == 0) && (func != null)) { func(); }
  3305. });
  3306. const options = { sourcePath: agentpath, targetStream: hashStream, platform: obj.meshAgentsArchitectureNumbers[archid].platform };
  3307. if (objx.meshAgentBinaries[archid].pe != null) { options.peinfo = objx.meshAgentBinaries[archid].pe; }
  3308. obj.exeHandler.hashExecutableFile(options);
  3309. // If we are not loading Windows binaries to RAM, compute the RAW file hash of the signed binaries here.
  3310. if ((obj.args.agentsinram === false) && ((archid == 3) || (archid == 4))) {
  3311. const hash = obj.crypto.createHash('sha384').update(obj.fs.readFileSync(agentpath));
  3312. objx.meshAgentBinaries[archid].fileHash = hash.digest('binary');
  3313. objx.meshAgentBinaries[archid].fileHashHex = Buffer.from(objx.meshAgentBinaries[archid].fileHash, 'binary').toString('hex');
  3314. }
  3315. }
  3316. };
  3317. // Generate a time limited user login token
  3318. obj.getLoginToken = function (userid, func) {
  3319. if ((userid == null) || (typeof userid != 'string')) { func('Invalid userid.'); return; }
  3320. const x = userid.split('/');
  3321. if (x == null || x.length != 3 || x[0] != 'user') { func('Invalid userid.'); return; }
  3322. obj.db.Get(userid, function (err, docs) {
  3323. if (err != null || docs == null || docs.length == 0) {
  3324. func('User ' + userid + ' not found.'); return;
  3325. } else {
  3326. // Load the login cookie encryption key from the database
  3327. obj.db.Get('LoginCookieEncryptionKey', function (err, docs) {
  3328. if ((docs.length > 0) && (docs[0].key != null) && (obj.args.logintokengen == null) && (docs[0].key.length >= 160)) {
  3329. // Key is present, use it.
  3330. obj.loginCookieEncryptionKey = Buffer.from(docs[0].key, 'hex');
  3331. func(obj.encodeCookie({ u: userid, a: 3 }, obj.loginCookieEncryptionKey));
  3332. } else {
  3333. // Key is not present, generate one.
  3334. obj.loginCookieEncryptionKey = obj.generateCookieKey();
  3335. obj.db.Set({ _id: 'LoginCookieEncryptionKey', key: obj.loginCookieEncryptionKey.toString('hex'), time: Date.now() }, function () { func(obj.encodeCookie({ u: userid, a: 3 }, obj.loginCookieEncryptionKey)); });
  3336. }
  3337. });
  3338. }
  3339. });
  3340. };
  3341. // Show the user login token generation key
  3342. obj.showLoginTokenKey = function (func) {
  3343. // Load the login cookie encryption key from the database
  3344. obj.db.Get('LoginCookieEncryptionKey', function (err, docs) {
  3345. if ((docs.length > 0) && (docs[0].key != null) && (obj.args.logintokengen == null) && (docs[0].key.length >= 160)) {
  3346. // Key is present, use it.
  3347. func(docs[0].key);
  3348. } else {
  3349. // Key is not present, generate one.
  3350. obj.loginCookieEncryptionKey = obj.generateCookieKey();
  3351. obj.db.Set({ _id: 'LoginCookieEncryptionKey', key: obj.loginCookieEncryptionKey.toString('hex'), time: Date.now() }, function () { func(obj.loginCookieEncryptionKey.toString('hex')); });
  3352. }
  3353. });
  3354. };
  3355. // Load the list of Intel AMT UUID and passwords from "amtactivation.log"
  3356. obj.loadAmtActivationLogPasswords = function (func) {
  3357. const amtlogfilename = obj.path.join(obj.datapath, 'amtactivation.log');
  3358. obj.fs.readFile(amtlogfilename, 'utf8', function (err, data) {
  3359. const amtPasswords = {}; // UUID --> [Passwords]
  3360. if ((err == null) && (data != null)) {
  3361. const lines = data.split('\n');
  3362. for (var i in lines) {
  3363. const line = lines[i];
  3364. if (line.startsWith('{')) {
  3365. var j = null;
  3366. try { j = JSON.parse(line); } catch (ex) { }
  3367. if ((j != null) && (typeof j == 'object')) {
  3368. if ((typeof j.amtUuid == 'string') && (typeof j.password == 'string')) {
  3369. if (amtPasswords[j.amtUuid] == null) {
  3370. amtPasswords[j.amtUuid] = [j.password]; // Add password to array
  3371. } else {
  3372. amtPasswords[j.amtUuid].unshift(j.password); // Add password at the start of the array
  3373. }
  3374. }
  3375. }
  3376. }
  3377. }
  3378. // Remove all duplicates and only keep the 3 last passwords for any given device
  3379. for (var i in amtPasswords) {
  3380. amtPasswords[i] = [...new Set(amtPasswords[i])];
  3381. while (amtPasswords[i].length > 3) { amtPasswords[i].pop(); }
  3382. }
  3383. }
  3384. func(obj.common.sortObj(amtPasswords)); // Sort by UUID
  3385. });
  3386. }
  3387. // Encrypt session data
  3388. obj.encryptSessionData = function (data, key) {
  3389. if (data == null) return null;
  3390. if (key == null) { key = obj.loginCookieEncryptionKey; }
  3391. try {
  3392. const iv = Buffer.from(obj.crypto.randomBytes(12), 'binary'), cipher = obj.crypto.createCipheriv('aes-256-gcm', key.slice(0, 32), iv);
  3393. const crypted = Buffer.concat([cipher.update(JSON.stringify(data), 'utf8'), cipher.final()]);
  3394. return Buffer.concat([iv, cipher.getAuthTag(), crypted]).toString(obj.args.cookieencoding ? obj.args.cookieencoding : 'base64').replace(/\+/g, '@').replace(/\//g, '$');
  3395. } catch (ex) { return null; }
  3396. }
  3397. // Decrypt the session data
  3398. obj.decryptSessionData = function (data, key) {
  3399. if ((typeof data != 'string') || (data.length < 13)) return {};
  3400. if (key == null) { key = obj.loginCookieEncryptionKey; }
  3401. try {
  3402. const buf = Buffer.from(data.replace(/\@/g, '+').replace(/\$/g, '/'), obj.args.cookieencoding ? obj.args.cookieencoding : 'base64');
  3403. const decipher = obj.crypto.createDecipheriv('aes-256-gcm', key.slice(0, 32), buf.slice(0, 12));
  3404. decipher.setAuthTag(buf.slice(12, 28));
  3405. return JSON.parse(decipher.update(buf.slice(28), 'binary', 'utf8') + decipher.final('utf8'));
  3406. } catch (ex) { return {}; }
  3407. }
  3408. // Generate a cryptographic key used to encode and decode cookies
  3409. obj.generateCookieKey = function () {
  3410. return Buffer.from(obj.crypto.randomBytes(80), 'binary');
  3411. //return Buffer.alloc(80, 0); // Sets the key to zeros, debug only.
  3412. };
  3413. // Encode an object as a cookie using a key using AES-GCM. (key must be 32 bytes or more)
  3414. obj.encodeCookie = function (o, key) {
  3415. try {
  3416. if (key == null) { key = obj.serverKey; }
  3417. o.time = Math.floor(Date.now() / 1000); // Add the cookie creation time
  3418. const iv = Buffer.from(obj.crypto.randomBytes(12), 'binary'), cipher = obj.crypto.createCipheriv('aes-256-gcm', key.slice(0, 32), iv);
  3419. const crypted = Buffer.concat([cipher.update(JSON.stringify(o), 'utf8'), cipher.final()]);
  3420. const r = Buffer.concat([iv, cipher.getAuthTag(), crypted]).toString(obj.args.cookieencoding ? obj.args.cookieencoding : 'base64').replace(/\+/g, '@').replace(/\//g, '$');
  3421. obj.debug('cookie', 'Encoded AESGCM cookie: ' + JSON.stringify(o));
  3422. return r;
  3423. } catch (ex) { obj.debug('cookie', 'ERR: Failed to encode AESGCM cookie due to exception: ' + ex); return null; }
  3424. };
  3425. // Decode a cookie back into an object using a key using AES256-GCM or AES128-CBC/HMAC-SHA384. Return null if it's not a valid cookie. (key must be 32 bytes or more)
  3426. obj.decodeCookie = function (cookie, key, timeout) {
  3427. if (cookie == null) return null;
  3428. var r = obj.decodeCookieAESGCM(cookie, key, timeout);
  3429. if (r === -1) { r = obj.decodeCookieAESSHA(cookie, key, timeout); } // If decodeCookieAESGCM() failed to decode, try decodeCookieAESSHA()
  3430. if ((r == null) && (obj.args.cookieencoding == null) && (cookie.length != 64) && ((cookie == cookie.toLowerCase()) || (cookie == cookie.toUpperCase()))) {
  3431. obj.debug('cookie', 'Upper/Lowercase cookie, try "CookieEncoding":"hex" in settings section of config.json.');
  3432. console.log('Upper/Lowercase cookie, try "CookieEncoding":"hex" in settings section of config.json.');
  3433. }
  3434. if ((r != null) && (typeof r.once == 'string') && (r.once.length > 0)) {
  3435. // This cookie must only be used once.
  3436. if (timeout == null) { timeout = 2; }
  3437. if (obj.cookieUseOnceTable[r.once] == null) {
  3438. const ctimeout = (((r.expire) == null || (typeof r.expire != 'number')) ? (r.time + ((timeout + 3) * 60000)) : (r.time + ((r.expire + 3) * 60000)));
  3439. // Store the used cookie in RAM
  3440. obj.cookieUseOnceTable[r.once] = ctimeout;
  3441. // Store the used cookie in the database
  3442. // TODO
  3443. // Send the used cookie to peer servers
  3444. // TODO
  3445. // Clean up the used table
  3446. if (++obj.cookieUseOnceTableCleanCounter > 20) {
  3447. const now = Date.now();
  3448. for (var i in obj.cookieUseOnceTable) { if (obj.cookieUseOnceTable[i] < now) { delete obj.cookieUseOnceTable[i]; } }
  3449. obj.cookieUseOnceTableCleanCounter = 0;
  3450. }
  3451. } else { return null; }
  3452. }
  3453. return r;
  3454. }
  3455. // Decode a cookie back into an object using a key using AES256-GCM. Return null if it's not a valid cookie. (key must be 32 bytes or more)
  3456. obj.decodeCookieAESGCM = function (cookie, key, timeout) {
  3457. try {
  3458. if (key == null) { key = obj.serverKey; }
  3459. cookie = Buffer.from(cookie.replace(/\@/g, '+').replace(/\$/g, '/'), obj.args.cookieencoding ? obj.args.cookieencoding : 'base64');
  3460. const decipher = obj.crypto.createDecipheriv('aes-256-gcm', key.slice(0, 32), cookie.slice(0, 12));
  3461. decipher.setAuthTag(cookie.slice(12, 28));
  3462. const o = JSON.parse(decipher.update(cookie.slice(28), 'binary', 'utf8') + decipher.final('utf8'));
  3463. if ((o.time == null) || (o.time == null) || (typeof o.time != 'number')) { obj.debug('cookie', 'ERR: Bad cookie due to invalid time'); return null; }
  3464. o.time = o.time * 1000; // Decode the cookie creation time
  3465. o.dtime = Date.now() - o.time; // Decode how long ago the cookie was created (in milliseconds)
  3466. if ((o.expire) == null || (typeof o.expire != 'number')) {
  3467. // Use a fixed cookie expire time
  3468. if (timeout == null) { timeout = 2; }
  3469. if ((o.dtime > (timeout * 60000)) || (o.dtime < -30000)) { obj.debug('cookie', 'ERR: Bad cookie due to timeout'); return null; } // The cookie is only valid 120 seconds, or 30 seconds back in time (in case other server's clock is not quite right)
  3470. } else {
  3471. // An expire time is included in the cookie (in minutes), use this.
  3472. if ((o.expire !== 0) && ((o.dtime > (o.expire * 60000)) || (o.dtime < -30000))) { obj.debug('cookie', 'ERR: Bad cookie due to timeout'); return null; } // The cookie is only valid 120 seconds, or 30 seconds back in time (in case other server's clock is not quite right)
  3473. }
  3474. obj.debug('cookie', 'Decoded AESGCM cookie: ' + JSON.stringify(o));
  3475. return o;
  3476. } catch (ex) { obj.debug('cookie', 'ERR: Bad AESGCM cookie due to exception: ' + ex); return -1; }
  3477. };
  3478. // Decode a cookie back into an object using a key using AES256 / HMAC-SHA384. Return null if it's not a valid cookie. (key must be 80 bytes or more)
  3479. // We do this because poor .NET does not support AES256-GCM.
  3480. obj.decodeCookieAESSHA = function (cookie, key, timeout) {
  3481. try {
  3482. if (key == null) { key = obj.serverKey; }
  3483. if (key.length < 80) { return null; }
  3484. cookie = Buffer.from(cookie.replace(/\@/g, '+').replace(/\$/g, '/'), obj.args.cookieencoding ? obj.args.cookieencoding : 'base64');
  3485. const decipher = obj.crypto.createDecipheriv('aes-256-cbc', key.slice(48, 80), cookie.slice(0, 16));
  3486. const rawmsg = decipher.update(cookie.slice(16), 'binary', 'binary') + decipher.final('binary');
  3487. const hmac = obj.crypto.createHmac('sha384', key.slice(0, 48));
  3488. hmac.update(rawmsg.slice(48));
  3489. if (Buffer.compare(hmac.digest(), Buffer.from(rawmsg.slice(0, 48))) == false) { return null; }
  3490. const o = JSON.parse(rawmsg.slice(48).toString('utf8'));
  3491. if ((o.time == null) || (o.time == null) || (typeof o.time != 'number')) { obj.debug('cookie', 'ERR: Bad cookie due to invalid time'); return null; }
  3492. o.time = o.time * 1000; // Decode the cookie creation time
  3493. o.dtime = Date.now() - o.time; // Decode how long ago the cookie was created (in milliseconds)
  3494. if ((o.expire) == null || (typeof o.expire != 'number')) {
  3495. // Use a fixed cookie expire time
  3496. if (timeout == null) { timeout = 2; }
  3497. if ((o.dtime > (timeout * 60000)) || (o.dtime < -30000)) { obj.debug('cookie', 'ERR: Bad cookie due to timeout'); return null; } // The cookie is only valid 120 seconds, or 30 seconds back in time (in case other server's clock is not quite right)
  3498. } else {
  3499. // An expire time is included in the cookie (in minutes), use this.
  3500. if ((o.expire !== 0) && ((o.dtime > (o.expire * 60000)) || (o.dtime < -30000))) { obj.debug('cookie', 'ERR: Bad cookie due to timeout'); return null; } // The cookie is only valid 120 seconds, or 30 seconds back in time (in case other server's clock is not quite right)
  3501. }
  3502. obj.debug('cookie', 'Decoded AESSHA cookie: ' + JSON.stringify(o));
  3503. return o;
  3504. } catch (ex) { obj.debug('cookie', 'ERR: Bad AESSHA cookie due to exception: ' + ex); return null; }
  3505. };
  3506. // Debug
  3507. obj.debug = function (source, ...args) {
  3508. // Send event to console
  3509. if ((obj.debugSources != null) && ((obj.debugSources == '*') || (obj.debugSources.indexOf(source) >= 0))) { console.log(source.toUpperCase() + ':', ...args); }
  3510. // Send event to log file
  3511. if (obj.config.settings && obj.config.settings.log) {
  3512. if (typeof obj.args.log == 'string') { obj.args.log = obj.args.log.split(','); }
  3513. if ((obj.args.log.indexOf(source) >= 0) || (obj.args.log[0] == '*')) {
  3514. const d = new Date();
  3515. if (obj.xxLogFile == null) {
  3516. try {
  3517. obj.xxLogFile = obj.fs.openSync(obj.getConfigFilePath('log.txt'), 'a+', 0o666);
  3518. obj.fs.writeSync(obj.xxLogFile, '---- Log start at ' + new Date().toLocaleString() + ' ----\r\n');
  3519. obj.xxLogDateStr = d.toLocaleDateString();
  3520. } catch (ex) { }
  3521. }
  3522. if (obj.xxLogFile != null) {
  3523. try {
  3524. if (obj.xxLogDateStr != d.toLocaleDateString()) { obj.xxLogDateStr = d.toLocaleDateString(); obj.fs.writeSync(obj.xxLogFile, '---- ' + d.toLocaleDateString() + ' ----\r\n'); }
  3525. const formattedArgs = args.map(function (arg) { return (typeof arg === 'object' && arg !== null) ? JSON.stringify(arg) : arg; });
  3526. obj.fs.writeSync(obj.xxLogFile, new Date().toLocaleTimeString() + ' - ' + source + ': ' + formattedArgs.join(', ') + '\r\n');
  3527. } catch (ex) { }
  3528. }
  3529. }
  3530. }
  3531. // Send the event to logged in administrators
  3532. if ((obj.debugRemoteSources != null) && ((obj.debugRemoteSources == '*') || (obj.debugRemoteSources.indexOf(source) >= 0))) {
  3533. var sendcount = 0;
  3534. for (var sessionid in obj.webserver.wssessions2) {
  3535. const ws = obj.webserver.wssessions2[sessionid];
  3536. if ((ws != null) && (ws.userid != null)) {
  3537. const user = obj.webserver.users[ws.userid];
  3538. if ((user != null) && (user.siteadmin == 4294967295)) {
  3539. try { ws.send(JSON.stringify({ action: 'trace', source: source, args: args, time: Date.now() })); sendcount++; } catch (ex) { }
  3540. }
  3541. }
  3542. }
  3543. if (sendcount == 0) { obj.debugRemoteSources = null; } // If there are no listeners, remove debug sources.
  3544. }
  3545. };
  3546. // Update server state. Writes a server state file.
  3547. const meshServerState = {};
  3548. obj.updateServerState = function (name, val) {
  3549. //console.log('updateServerState', name, val);
  3550. try {
  3551. if ((name != null) && (val != null)) {
  3552. var changed = false;
  3553. if ((name != null) && (meshServerState[name] != val)) { if ((val == null) && (meshServerState[name] != null)) { delete meshServerState[name]; changed = true; } else { if (meshServerState[name] != val) { meshServerState[name] = val; changed = true; } } }
  3554. if (changed == false) return;
  3555. }
  3556. var r = 'time=' + Date.now() + '\r\n';
  3557. for (var i in meshServerState) { r += (i + '=' + meshServerState[i] + '\r\n'); }
  3558. try {
  3559. obj.fs.writeFileSync(obj.getConfigFilePath('serverstate.txt'), r); // Try to write the server state, this may fail if we don't have permission.
  3560. } catch (ex) { obj.serverSelfWriteAllowed = false; }
  3561. } catch (ex) { } // Do nothing since this is not a critical feature.
  3562. };
  3563. // Read a list of IP addresses from a file
  3564. function readIpListFromFile(arg) {
  3565. if ((typeof arg != 'string') || (!arg.startsWith('file:'))) return arg;
  3566. var lines = null;
  3567. try { lines = obj.fs.readFileSync(obj.path.join(obj.datapath, arg.substring(5))).toString().split(/\r?\n/).join('\r').split('\r'); } catch (ex) { }
  3568. if (lines == null) return null;
  3569. const validLines = [];
  3570. for (var i in lines) { if ((lines[i].length > 0) && (((lines[i].charAt(0) > '0') && (lines[i].charAt(0) < '9')) || (lines[i].charAt(0) == ':'))) validLines.push(lines[i]); }
  3571. return validLines;
  3572. }
  3573. // Logging funtions
  3574. function logException(e) { e += ''; logErrorEvent(e); }
  3575. function logInfoEvent(msg) { if (obj.servicelog != null) { obj.servicelog.info(msg); } console.log(msg); }
  3576. function logWarnEvent(msg) { if (obj.servicelog != null) { obj.servicelog.warn(msg); } console.log(msg); }
  3577. function logErrorEvent(msg) { if (obj.servicelog != null) { obj.servicelog.error(msg); } console.error(msg); }
  3578. obj.getServerWarnings = function () { return serverWarnings; }
  3579. obj.addServerWarning = function (msg, id, args, print) { serverWarnings.push({ msg: msg, id: id, args: args }); if (print !== false) { console.log("WARNING: " + msg); } }
  3580. // auth.log functions
  3581. obj.authLog = function (server, msg, args) {
  3582. if (typeof msg != 'string') return;
  3583. var str = msg;
  3584. if (args != null) {
  3585. if (typeof args.sessionid == 'string') { str += ', SessionID: ' + args.sessionid; }
  3586. if (typeof args.useragent == 'string') { const userAgentInfo = obj.webserver.getUserAgentInfo(args.useragent); str += ', Browser: ' + userAgentInfo.browserStr + ', OS: ' + userAgentInfo.osStr; }
  3587. }
  3588. obj.debug('authlog', str);
  3589. if (obj.syslogauth != null) { try { obj.syslogauth.log(obj.syslogauth.LOG_INFO, str); } catch (ex) { } }
  3590. if (obj.authlogfile != null) { // Write authlog to file
  3591. try {
  3592. const d = new Date(), month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][d.getMonth()];
  3593. str = month + ' ' + d.getDate() + ' ' + obj.common.zeroPad(d.getHours(), 2) + ':' + obj.common.zeroPad(d.getMinutes(), 2) + ':' + d.getSeconds() + ' meshcentral ' + server + '[' + process.pid + ']: ' + msg + ((obj.platform == 'win32') ? '\r\n' : '\n');
  3594. obj.fs.write(obj.authlogfile, str, function (err, written, string) { if (err) { console.error(err); } });
  3595. } catch (ex) { console.error(ex); }
  3596. }
  3597. }
  3598. // Return the path of a file into the meshcentral-data path
  3599. obj.getConfigFilePath = function (filename) {
  3600. if ((obj.config != null) && (obj.config.configfiles != null) && (obj.config.configfiles[filename] != null) && (typeof obj.config.configfiles[filename] == 'string')) {
  3601. //console.log('getConfigFilePath(\"' + filename + '\") = ' + obj.config.configfiles[filename]);
  3602. return obj.config.configfiles[filename];
  3603. }
  3604. //console.log('getConfigFilePath(\"' + filename + '\") = ' + obj.path.join(obj.datapath, filename));
  3605. return obj.path.join(obj.datapath, filename);
  3606. };
  3607. return obj;
  3608. }
  3609. // Resolve a list of names, call back with list of failed resolves.
  3610. function checkResolveAll(names, func) {
  3611. const dns = require('dns'), state = { func: func, count: names.length, err: null };
  3612. for (var i in names) {
  3613. dns.lookup(names[i], { all: true }, function (err, records) {
  3614. if (err != null) { if (this.state.err == null) { this.state.err = [this.name]; } else { this.state.err.push(this.name); } }
  3615. if (--this.state.count == 0) { this.state.func(this.state.err); }
  3616. }.bind({ name: names[i], state: state }))
  3617. }
  3618. }
  3619. // Return the server configuration
  3620. function getConfig(createSampleConfig) {
  3621. // Figure out the datapath location
  3622. var i, datapath = null;
  3623. const fs = require('fs'), path = require('path'), args = require('minimist')(process.argv.slice(2));
  3624. if ((__dirname.endsWith('/node_modules/meshcentral')) || (__dirname.endsWith('\\node_modules\\meshcentral')) || (__dirname.endsWith('/node_modules/meshcentral/')) || (__dirname.endsWith('\\node_modules\\meshcentral\\'))) {
  3625. datapath = path.join(__dirname, '../../meshcentral-data');
  3626. } else {
  3627. datapath = path.join(__dirname, '../meshcentral-data');
  3628. }
  3629. if (args.datapath) { datapath = args.datapath; }
  3630. try { fs.mkdirSync(datapath); } catch (ex) { }
  3631. // Read configuration file if present and change arguments.
  3632. var config = {}, configFilePath = path.join(datapath, 'config.json');
  3633. if (args.configfile) { configFilePath = common.joinPath(datapath, args.configfile); }
  3634. if (fs.existsSync(configFilePath)) {
  3635. // Load and validate the configuration file
  3636. try { config = require(configFilePath); } catch (ex) { console.log('ERROR: Unable to parse ' + configFilePath + '.'); return null; }
  3637. if (config.domains == null) { config.domains = {}; }
  3638. for (i in config.domains) { if ((i.split('/').length > 1) || (i.split(' ').length > 1)) { console.log("ERROR: Error in config.json, domain names can't have spaces or /."); return null; } }
  3639. } else {
  3640. if (createSampleConfig === true) {
  3641. // Copy the "sample-config.json" to give users a starting point
  3642. const sampleConfigPath = path.join(__dirname, 'sample-config.json');
  3643. if (fs.existsSync(sampleConfigPath)) { fs.createReadStream(sampleConfigPath).pipe(fs.createWriteStream(configFilePath)); }
  3644. }
  3645. }
  3646. // Set the command line arguments to the config file if they are not present
  3647. if (!config.settings) { config.settings = {}; }
  3648. for (i in args) { config.settings[i] = args[i]; }
  3649. // Lower case all keys in the config file
  3650. try {
  3651. require('./common.js').objKeysToLower(config, ['ldapoptions', 'defaultuserwebstate', 'forceduserwebstate', 'httpheaders', 'telegram/proxy']);
  3652. } catch (ex) {
  3653. console.log('CRITICAL ERROR: Unable to access the file \"./common.js\".\r\nCheck folder & file permissions.');
  3654. process.exit();
  3655. }
  3656. return config;
  3657. }
  3658. // Check if a list of modules are present and install any missing ones
  3659. function InstallModules(modules, args, func) {
  3660. var missingModules = [];
  3661. if (modules.length > 0) {
  3662. const dependencies = require('./package.json').dependencies;
  3663. for (var i in modules) {
  3664. // Modules may contain a version tag (foobar@1.0.0), remove it so the module can be found using require
  3665. const moduleNameAndVersion = modules[i];
  3666. const moduleInfo = moduleNameAndVersion.split('@', 3);
  3667. var moduleName = null;
  3668. var moduleVersion = null;
  3669. if(moduleInfo.length == 1){ // normal package without version
  3670. moduleName = moduleInfo[0];
  3671. } else if (moduleInfo.length == 2) { // normal package with a version OR custom repo package with no version
  3672. moduleName = moduleInfo[0] === '' ? moduleNameAndVersion : moduleInfo[0];
  3673. moduleVersion = moduleInfo[0] === '' ? null : moduleInfo[1];
  3674. } else if (moduleInfo.length == 3) { // custom repo package and package with a version
  3675. moduleName = "@" + moduleInfo[1];
  3676. moduleVersion = moduleInfo[2];
  3677. }
  3678. try {
  3679. // Does the module need a specific version?
  3680. if (moduleVersion) {
  3681. if (require(`./node_modules/${moduleName}/package.json`).version != moduleVersion) { throw new Error(); }
  3682. } else {
  3683. // For all other modules, do the check here.
  3684. // Is the module in package.json? Install exact version.
  3685. if (typeof dependencies[moduleName] != null) { moduleVersion = dependencies[moduleName]; }
  3686. require(moduleName);
  3687. }
  3688. } catch (ex) {
  3689. missingModules.push(moduleNameAndVersion);
  3690. }
  3691. }
  3692. if (missingModules.length > 0) { if (args.debug) { console.log('Missing Modules: ' + missingModules.join(', ')); } InstallModuleEx(missingModules, args, func); } else { func(); }
  3693. }
  3694. }
  3695. // Install all missing modules at once. We will be running "npm install" once, with a full list of all modules we need, no matter if they area already installed or not,
  3696. // this is to make sure NPM gives us exactly what we need. Also, we install the meshcentral with current version, so that NPM does not update it - which it will do if obmitted.
  3697. function InstallModuleEx(modulenames, args, func) {
  3698. var names = modulenames.join(' ');
  3699. console.log('Installing modules', modulenames);
  3700. const child_process = require('child_process');
  3701. var parentpath = __dirname;
  3702. function getCurrentVersion() { try { return JSON.parse(require('fs').readFileSync(require('path').join(__dirname, 'package.json'), 'utf8')).version; } catch (ex) { } return null; } // Fetch server version
  3703. //const meshCentralVersion = getCurrentVersion();
  3704. //if ((meshCentralVersion != null) && (args.dev == null)) { names = 'meshcentral@' + getCurrentVersion() + ' ' + names; }
  3705. // Get the working directory
  3706. if ((__dirname.endsWith('/node_modules/meshcentral')) || (__dirname.endsWith('\\node_modules\\meshcentral')) || (__dirname.endsWith('/node_modules/meshcentral/')) || (__dirname.endsWith('\\node_modules\\meshcentral\\'))) { parentpath = require('path').join(__dirname, '../..'); }
  3707. if (args.debug) { console.log('NPM Command Line: ' + npmpath + ` install --save-exact --no-audit --omit=optional --no-fund ${names}`); }
  3708. // always use --save-exact - https://stackoverflow.com/a/64507176/1210734
  3709. child_process.exec(npmpath + ` install --save-exact --no-audit --no-optional --omit=optional ${names}`, { maxBuffer: 512000, timeout: 300000, cwd: parentpath }, function (error, stdout, stderr) {
  3710. if ((error != null) && (error != '')) {
  3711. var mcpath = __dirname;
  3712. if (mcpath.endsWith('\\node_modules\\meshcentral') || mcpath.endsWith('/node_modules/meshcentral')) { mcpath = require('path').join(mcpath, '..', '..'); }
  3713. console.log('ERROR: Unable to install required modules. MeshCentral may not have access to npm, or npm may not have suffisent rights to load the new module. To manualy install this module try:\r\n\r\n cd "' + mcpath + '"\r\n npm install --no-audit --no-optional --omit=optional ' + names + '\r\n node node_modules' + ((require('os').platform() == 'win32') ? '\\' : '/') + 'meshcentral');
  3714. process.exit();
  3715. return;
  3716. }
  3717. func();
  3718. return;
  3719. });
  3720. }
  3721. // Detect CTRL-C on Linux and stop nicely
  3722. process.on('SIGINT', function () { if (meshserver != null) { meshserver.Stop(); meshserver = null; } console.log('Server Ctrl-C exit...'); process.exit(); });
  3723. // Add a server warning, warnings will be shown to the administrator on the web application
  3724. const serverWarnings = [];
  3725. function addServerWarning(msg, id, args, print) { serverWarnings.push({ msg: msg, id: id, args: args }); if (print !== false) { console.log("WARNING: " + msg); } }
  3726. /*
  3727. var ServerWarnings = {
  3728. 1: "",
  3729. 2: "Missing WebDAV parameters.",
  3730. 3: "Unrecognized configuration option \"{0}\".",
  3731. 4: "WebSocket compression is disabled, this feature is broken in NodeJS v11.11 to v12.15 and v13.2",
  3732. 5: "Unable to load Intel AMT TLS root certificate for default domain.",
  3733. 6: "Unable to load Intel AMT TLS root certificate for domain {0}.",
  3734. 7: "CIRA local FQDN's ignored when server in LAN-only or WAN-only mode.",
  3735. 8: "Can't have more than 4 CIRA local FQDN's. Ignoring value.",
  3736. 9: "Agent hash checking is being skipped, this is unsafe.",
  3737. 10: "Missing Let's Encrypt email address.",
  3738. 11: "Invalid Let's Encrypt host names.",
  3739. 12: "Invalid Let's Encrypt names, can't contain a *.",
  3740. 13: "Unable to setup Let's Encrypt module.",
  3741. 14: "Invalid Let's Encrypt names, unable to resolve: {0}",
  3742. 15: "Invalid Let's Encrypt email address, unable to resolve: {0}",
  3743. 16: "Unable to load CloudFlare trusted proxy IPv6 address list.",
  3744. 17: "SendGrid server has limited use in LAN mode.",
  3745. 18: "SMTP server has limited use in LAN mode.",
  3746. 19: "SMS gateway has limited use in LAN mode.",
  3747. 20: "Invalid \"LoginCookieEncryptionKey\" in config.json.",
  3748. 21: "Backup path can't be set within meshcentral-data folder, backup settings ignored.",
  3749. 22: "Failed to sign agent {0}: {1}",
  3750. 23: "Unable to load agent icon file: {0}.",
  3751. 24: "Unable to load agent logo file: {0}.",
  3752. 25: "This NodeJS version does not support OpenID.",
  3753. 26: "This NodeJS version does not support Discord.js."
  3754. };
  3755. */
  3756. // Load the really basic modules
  3757. var npmpath = 'npm';
  3758. var meshserver = null;
  3759. var childProcess = null;
  3760. var previouslyInstalledModules = {};
  3761. function mainStart() {
  3762. // Check the NodeJS is version 16 or better.
  3763. if (Number(process.version.match(/^v(\d+\.\d+)/)[1]) < 16) { console.log("MeshCentral requires Node v16 or above, current version is " + process.version + "."); return; }
  3764. // If running within the node_modules folder, move working directory to the parent of the node_modules folder.
  3765. if (__dirname.endsWith('\\node_modules\\meshcentral') || __dirname.endsWith('/node_modules/meshcentral')) { process.chdir(require('path').join(__dirname, '..', '..')); }
  3766. // Check for any missing modules.
  3767. InstallModules(['minimist'], {}, function () {
  3768. // Parse inbound arguments
  3769. const args = require('minimist')(process.argv.slice(2));
  3770. // Setup the NPM path
  3771. if (args.npmpath == null) {
  3772. try {
  3773. var xnodepath = process.argv[0];
  3774. var xnpmpath = require('path').join(require('path').dirname(process.argv[0]), 'npm');
  3775. if (require('fs').existsSync(xnodepath) && require('fs').existsSync(xnpmpath)) {
  3776. if (xnodepath.indexOf(' ') >= 0) { xnodepath = '"' + xnodepath + '"'; }
  3777. if (xnpmpath.indexOf(' ') >= 0) { xnpmpath = '"' + xnpmpath + '"'; }
  3778. if (require('os').platform() == 'win32') { npmpath = xnpmpath; } else { npmpath = (xnodepath + ' ' + xnpmpath); }
  3779. }
  3780. } catch (ex) { console.log(ex); }
  3781. } else {
  3782. npmpath = args.npmpath;
  3783. }
  3784. // Get the server configuration
  3785. var config = getConfig(false);
  3786. if (config == null) { process.exit(); }
  3787. // Lowercase the auth value if present
  3788. for (var i in config.domains) { if (typeof config.domains[i].auth == 'string') { config.domains[i].auth = config.domains[i].auth.toLowerCase(); } }
  3789. // Get the current node version
  3790. const verSplit = process.version.substring(1).split('.');
  3791. var nodeVersion = parseInt(verSplit[0]) + (parseInt(verSplit[1]) / 100);
  3792. // Check if RDP support if present
  3793. var mstsc = true;
  3794. try { require('./rdp') } catch (ex) { mstsc = false; }
  3795. // Check if Windows SSPI, LDAP, Passport and YubiKey OTP will be used
  3796. var sspi = false;
  3797. var ldap = false;
  3798. var passport = null;
  3799. var allsspi = true;
  3800. var yubikey = false;
  3801. var ssh = false;
  3802. var sessionRecording = false;
  3803. var domainCount = 0;
  3804. var wildleek = false;
  3805. var nodemailer = false;
  3806. var sendgrid = false;
  3807. var captcha = false;
  3808. if (require('os').platform() == 'win32') { for (var i in config.domains) { domainCount++; if (config.domains[i].auth == 'sspi') { sspi = true; } else { allsspi = false; } } } else { allsspi = false; }
  3809. if (domainCount == 0) { allsspi = false; }
  3810. for (var i in config.domains) {
  3811. if (i.startsWith('_')) continue;
  3812. if (((config.domains[i].smtp != null) && (config.domains[i].smtp.name != 'console')) || (config.domains[i].sendmail != null)) { nodemailer = true; }
  3813. if (config.domains[i].sendgrid != null) { sendgrid = true; }
  3814. if (config.domains[i].yubikey != null) { yubikey = true; }
  3815. if (config.domains[i].auth == 'ldap') { ldap = true; }
  3816. if (mstsc == false) { config.domains[i].mstsc = false; }
  3817. if (config.domains[i].ssh == true) { ssh = true; }
  3818. if ((typeof config.domains[i].authstrategies == 'object')) {
  3819. if (passport == null) { passport = ['passport','connect-flash']; } // Passport v0.6.0 requires a patch, see https://github.com/jaredhanson/passport/issues/904 and include connect-flash here to display errors
  3820. if ((typeof config.domains[i].authstrategies.twitter == 'object') && (typeof config.domains[i].authstrategies.twitter.clientid == 'string') && (typeof config.domains[i].authstrategies.twitter.clientsecret == 'string') && (passport.indexOf('passport-twitter') == -1)) { passport.push('passport-twitter'); }
  3821. if ((typeof config.domains[i].authstrategies.google == 'object') && (typeof config.domains[i].authstrategies.google.clientid == 'string') && (typeof config.domains[i].authstrategies.google.clientsecret == 'string') && (passport.indexOf('passport-google-oauth20') == -1)) { passport.push('passport-google-oauth20'); }
  3822. if ((typeof config.domains[i].authstrategies.github == 'object') && (typeof config.domains[i].authstrategies.github.clientid == 'string') && (typeof config.domains[i].authstrategies.github.clientsecret == 'string') && (passport.indexOf('passport-github2') == -1)) { passport.push('passport-github2'); }
  3823. if ((typeof config.domains[i].authstrategies.azure == 'object') && (typeof config.domains[i].authstrategies.azure.clientid == 'string') && (typeof config.domains[i].authstrategies.azure.clientsecret == 'string') && (typeof config.domains[i].authstrategies.azure.tenantid == 'string') && (passport.indexOf('passport-azure-oauth2') == -1)) { passport.push('passport-azure-oauth2'); passport.push('jwt-simple'); }
  3824. if ((typeof config.domains[i].authstrategies.oidc == 'object') && (passport.indexOf('openid-client') == -1)) {
  3825. if ((nodeVersion >= 17)
  3826. || ((Math.floor(nodeVersion) == 16) && (nodeVersion >= 16.13))
  3827. || ((Math.floor(nodeVersion) == 14) && (nodeVersion >= 14.15))
  3828. || ((Math.floor(nodeVersion) == 12) && (nodeVersion >= 12.19))) {
  3829. passport.push('openid-client@5.7.0');
  3830. } else {
  3831. addServerWarning('This NodeJS version does not support OpenID Connect on MeshCentral.', 25);
  3832. delete config.domains[i].authstrategies.oidc;
  3833. }
  3834. }
  3835. if ((typeof config.domains[i].authstrategies.saml == 'object') || (typeof config.domains[i].authstrategies.jumpcloud == 'object')) { passport.push('passport-saml'); }
  3836. }
  3837. if (config.domains[i].sessionrecording != null) { sessionRecording = true; }
  3838. if ((config.domains[i].passwordrequirements != null) && (config.domains[i].passwordrequirements.bancommonpasswords == true)) { wildleek = true; }
  3839. if ((config.domains[i].newaccountscaptcha != null) && (config.domains[i].newaccountscaptcha !== false)) { captcha = true; }
  3840. }
  3841. // Build the list of required modules
  3842. // NOTE: ALL MODULES MUST HAVE A VERSION NUMBER AND THE VERSION MUST MATCH THAT USED IN Dockerfile
  3843. var modules = ['archiver@7.0.1', 'body-parser@1.20.3', 'cbor@5.2.0', 'compression@1.7.4', 'cookie-session@2.1.0', 'express@4.21.1', 'express-handlebars@7.1.3', 'express-ws@5.0.2', 'ipcheck@0.1.0', 'minimist@1.2.8', 'multiparty@4.2.3', '@yetzt/nedb', 'node-forge@1.3.1', 'ua-parser-js@1.0.39', 'ws@8.18.0', 'yauzl@2.10.0'];
  3844. if (require('os').platform() == 'win32') { modules.push('node-windows@0.1.14'); modules.push('loadavg-windows@1.1.1'); if (sspi == true) { modules.push('node-sspi@0.2.10'); } } // Add Windows modules
  3845. if (ldap == true) { modules.push('ldapauth-fork@5.0.5'); }
  3846. if (ssh == true) { modules.push('ssh2@1.16.0'); }
  3847. if (passport != null) { modules.push(...passport); }
  3848. if (captcha == true) { modules.push('svg-captcha@1.4.0'); }
  3849. if (sessionRecording == true) { modules.push('image-size@1.1.1'); } // Need to get the remote desktop JPEG sizes to index the recodring file.
  3850. if (config.letsencrypt != null) { modules.push('acme-client@4.2.5'); } // Add acme-client module. We need to force v4.2.4 or higher since olver versions using SHA-1 which is no longer supported by Let's Encrypt.
  3851. if (config.settings.mqtt != null) { modules.push('aedes@0.39.0'); } // Add MQTT Modules
  3852. if (config.settings.mysql != null) { modules.push('mysql2@3.6.2'); } // Add MySQL.
  3853. //if (config.settings.mysql != null) { modules.push('@mysql/xdevapi@8.0.33'); } // Add MySQL, official driver (https://dev.mysql.com/doc/dev/connector-nodejs/8.0/)
  3854. if (config.settings.mongodb != null) { modules.push('mongodb@4.13.0'); modules.push('saslprep@1.0.3'); } // Add MongoDB, official driver.
  3855. if (config.settings.postgres != null) { modules.push('pg@8.7.1'); modules.push('pgtools@0.3.2'); } // Add Postgres, Postgres driver.
  3856. if (config.settings.mariadb != null) { modules.push('mariadb@3.2.2'); } // Add MariaDB, official driver.
  3857. if (config.settings.acebase != null) { modules.push('acebase@1.29.5'); } // Add AceBase, official driver.
  3858. if (config.settings.sqlite3 != null) { modules.push('sqlite3@5.1.6'); } // Add sqlite3, official driver.
  3859. if (config.settings.vault != null) { modules.push('node-vault@0.10.2'); } // Add official HashiCorp's Vault module.
  3860. if (config.settings.plugins != null) { modules.push('semver@7.5.4'); } // Required for version compat testing and update checks
  3861. if ((config.settings.plugins != null) && (config.settings.plugins.proxy != null)) { modules.push('https-proxy-agent@7.0.2'); } // Required for HTTP/HTTPS proxy support
  3862. else if (config.settings.xmongodb != null) { modules.push('mongojs@3.1.0'); } // Add MongoJS, old driver.
  3863. if (nodemailer || ((config.smtp != null) && (config.smtp.name != 'console')) || (config.sendmail != null)) { modules.push('nodemailer@6.9.15'); } // Add SMTP support
  3864. if (sendgrid || (config.sendgrid != null)) { modules.push('@sendgrid/mail'); } // Add SendGrid support
  3865. if ((args.translate || args.dev) && (Number(process.version.match(/^v(\d+\.\d+)/)[1]) >= 16)) { modules.push('jsdom@22.1.0'); modules.push('esprima@4.0.1'); modules.push('html-minifier@4.0.0'); } // Translation support
  3866. if (typeof config.settings.crowdsec == 'object') { modules.push('@crowdsec/express-bouncer@0.1.0'); } // Add CrowdSec bounser module (https://www.npmjs.com/package/@crowdsec/express-bouncer)
  3867. if (typeof config.settings.autobackup == 'object') {
  3868. // Setup encrypted zip support if needed
  3869. if (config.settings.autobackup.zippassword) { modules.push('archiver-zip-encrypted@2.0.0'); }
  3870. // Enable Google Drive Support
  3871. if (typeof config.settings.autobackup.googledrive == 'object') { modules.push('googleapis@128.0.0'); }
  3872. // Enable WebDAV Support
  3873. if (typeof config.settings.autobackup.webdav == 'object') {
  3874. if ((typeof config.settings.autobackup.webdav.url != 'string') || (typeof config.settings.autobackup.webdav.username != 'string') || (typeof config.settings.autobackup.webdav.password != 'string')) { addServerWarning("Missing WebDAV parameters.", 2, null, !args.launch); } else { modules.push('webdav@4.11.3'); }
  3875. }
  3876. // Enable S3 Support
  3877. if (typeof config.settings.autobackup.s3 == 'object') { modules.push('minio@8.0.1'); }
  3878. }
  3879. // Setup common password blocking
  3880. if (wildleek == true) { modules.push('wildleek@2.0.0'); }
  3881. // Setup 2nd factor authentication
  3882. if (config.settings.no2factorauth !== true) {
  3883. // Setup YubiKey OTP if configured
  3884. if (yubikey == true) { modules.push('yubikeyotp@0.2.0'); } // Add YubiKey OTP support
  3885. if (allsspi == false) { modules.push('otplib@10.2.3'); } // Google Authenticator support (v10 supports older NodeJS versions).
  3886. }
  3887. // Desktop multiplexor support
  3888. if (config.settings.desktopmultiplex === true) { modules.push('image-size@1.1.1'); }
  3889. // SMS support
  3890. if (config.sms != null) {
  3891. if (config.sms.provider == 'twilio') { modules.push('twilio@4.19.0'); }
  3892. if (config.sms.provider == 'plivo') { modules.push('plivo@4.58.0'); }
  3893. if (config.sms.provider == 'telnyx') { modules.push('telnyx@1.25.5'); }
  3894. }
  3895. // Messaging support
  3896. if (config.messaging != null) {
  3897. if (config.messaging.telegram != null) { modules.push('telegram@2.19.8'); modules.push('input@1.0.1'); }
  3898. if (config.messaging.discord != null) { if (nodeVersion >= 17) { modules.push('discord.js@14.6.0'); } else { delete config.messaging.discord; addServerWarning('This NodeJS version does not support Discord.js.', 26); } }
  3899. if (config.messaging.xmpp != null) { modules.push('@xmpp/client@0.13.1'); }
  3900. if (config.messaging.pushover != null) { modules.push('node-pushover@1.0.0'); }
  3901. if (config.messaging.zulip != null) { modules.push('zulip@0.1.0'); }
  3902. }
  3903. // Setup web based push notifications
  3904. if ((typeof config.settings.webpush == 'object') && (typeof config.settings.webpush.email == 'string')) { modules.push('web-push@3.6.6'); }
  3905. // Firebase Support
  3906. // Avoid 0.1.8 due to bugs: https://github.com/guness/node-xcs/issues/43
  3907. if (config.firebase != null) { modules.push('node-xcs@0.1.7'); }
  3908. // Syslog support
  3909. if ((require('os').platform() != 'win32') && (config.settings.syslog || config.settings.syslogjson)) { modules.push('modern-syslog@1.2.0'); }
  3910. if (config.settings.syslogtcp) { modules.push('syslog@0.1.1-1'); }
  3911. // Setup heapdump support if needed, useful for memory leak debugging
  3912. // https://www.arbazsiddiqui.me/a-practical-guide-to-memory-leaks-in-nodejs/
  3913. if (config.settings.heapdump === true) { modules.push('heapdump@0.3.15'); }
  3914. // Install any missing modules and launch the server
  3915. InstallModules(modules, args, function () {
  3916. if (require('os').platform() == 'win32') { try { require('node-windows'); } catch (ex) { console.log("Module node-windows can't be loaded. Restart MeshCentral."); process.exit(); return; } }
  3917. meshserver = CreateMeshCentralServer(config, args);
  3918. meshserver.Start();
  3919. });
  3920. // On exit, also terminate the child process if applicable
  3921. process.on('exit', function () { if (childProcess) { childProcess.kill(); childProcess = null; } });
  3922. // If our parent exits, we also exit
  3923. if (args.launch) {
  3924. process.stderr.on('end', function () { process.exit(); });
  3925. process.stdout.on('end', function () { process.exit(); });
  3926. process.stdin.on('end', function () { process.exit(); });
  3927. process.stdin.on('data', function (data) { });
  3928. }
  3929. });
  3930. }
  3931. if (require.main === module) {
  3932. mainStart(); // Called directly, launch normally.
  3933. } else {
  3934. module.exports.mainStart = mainStart; // Required as a module, useful for winservice.js
  3935. }