meshcentral.js 322 KB

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