meshuser.js 575 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411
  1. /**
  2. * @description MeshCentral MeshAgent
  3. * @author Ylian Saint-Hilaire & Bryan Roe
  4. * @copyright Intel Corporation 2018-2022
  5. * @license Apache-2.0
  6. * @version v0.0.1
  7. */
  8. /*jslint node: true */
  9. /*jshint node: true */
  10. /*jshint strict:false */
  11. /*jshint -W097 */
  12. /*jshint esversion: 6 */
  13. "use strict";
  14. // Construct a MeshAgent object, called upon connection
  15. module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, user) {
  16. const fs = require('fs');
  17. const path = require('path');
  18. const common = parent.common;
  19. // Cross domain messages, for cross-domain administrators only.
  20. const allowedCrossDomainMessages = ['accountcreate', 'accountremove', 'accountchange', 'createusergroup', 'deleteusergroup', 'usergroupchange'];
  21. // User Consent Flags
  22. const USERCONSENT_DesktopNotifyUser = 1;
  23. const USERCONSENT_TerminalNotifyUser = 2;
  24. const USERCONSENT_FilesNotifyUser = 4;
  25. const USERCONSENT_DesktopPromptUser = 8;
  26. const USERCONSENT_TerminalPromptUser = 16;
  27. const USERCONSENT_FilesPromptUser = 32;
  28. const USERCONSENT_ShowConnectionToolbar = 64;
  29. // Mesh Rights
  30. const MESHRIGHT_EDITMESH = 0x00000001; // 1
  31. const MESHRIGHT_MANAGEUSERS = 0x00000002; // 2
  32. const MESHRIGHT_MANAGECOMPUTERS = 0x00000004; // 4
  33. const MESHRIGHT_REMOTECONTROL = 0x00000008; // 8
  34. const MESHRIGHT_AGENTCONSOLE = 0x00000010; // 16
  35. const MESHRIGHT_SERVERFILES = 0x00000020; // 32
  36. const MESHRIGHT_WAKEDEVICE = 0x00000040; // 64
  37. const MESHRIGHT_SETNOTES = 0x00000080; // 128
  38. const MESHRIGHT_REMOTEVIEWONLY = 0x00000100; // 256
  39. const MESHRIGHT_NOTERMINAL = 0x00000200; // 512
  40. const MESHRIGHT_NOFILES = 0x00000400; // 1024
  41. const MESHRIGHT_NOAMT = 0x00000800; // 2048
  42. const MESHRIGHT_DESKLIMITEDINPUT = 0x00001000; // 4096
  43. const MESHRIGHT_LIMITEVENTS = 0x00002000; // 8192
  44. const MESHRIGHT_CHATNOTIFY = 0x00004000; // 16384
  45. const MESHRIGHT_UNINSTALL = 0x00008000; // 32768
  46. const MESHRIGHT_NODESKTOP = 0x00010000; // 65536
  47. const MESHRIGHT_REMOTECOMMAND = 0x00020000; // 131072
  48. const MESHRIGHT_RESETOFF = 0x00040000; // 262144
  49. const MESHRIGHT_GUESTSHARING = 0x00080000; // 524288
  50. const MESHRIGHT_DEVICEDETAILS = 0x00100000; // 1048576
  51. const MESHRIGHT_RELAY = 0x00200000; // 2097152
  52. const MESHRIGHT_ADMIN = 0xFFFFFFFF;
  53. // Site rights
  54. const SITERIGHT_SERVERBACKUP = 0x00000001; // 1
  55. const SITERIGHT_MANAGEUSERS = 0x00000002; // 2
  56. const SITERIGHT_SERVERRESTORE = 0x00000004; // 4
  57. const SITERIGHT_FILEACCESS = 0x00000008; // 8
  58. const SITERIGHT_SERVERUPDATE = 0x00000010; // 16
  59. const SITERIGHT_LOCKED = 0x00000020; // 32
  60. const SITERIGHT_NONEWGROUPS = 0x00000040; // 64
  61. const SITERIGHT_NOMESHCMD = 0x00000080; // 128
  62. const SITERIGHT_USERGROUPS = 0x00000100; // 256
  63. const SITERIGHT_RECORDINGS = 0x00000200; // 512
  64. const SITERIGHT_LOCKSETTINGS = 0x00000400; // 1024
  65. const SITERIGHT_ALLEVENTS = 0x00000800; // 2048
  66. const SITERIGHT_NONEWDEVICES = 0x00001000; // 4096
  67. const SITERIGHT_ADMIN = 0xFFFFFFFF;
  68. // Protocol Numbers
  69. const PROTOCOL_TERMINAL = 1;
  70. const PROTOCOL_DESKTOP = 2;
  71. const PROTOCOL_FILES = 5;
  72. const PROTOCOL_AMTWSMAN = 100;
  73. const PROTOCOL_AMTREDIR = 101;
  74. const PROTOCOL_MESSENGER = 200;
  75. const PROTOCOL_WEBRDP = 201;
  76. const PROTOCOL_WEBSSH = 202;
  77. const PROTOCOL_WEBSFTP = 203;
  78. const PROTOCOL_WEBVNC = 204;
  79. // MeshCentral Satellite
  80. const SATELLITE_PRESENT = 1; // This session is a MeshCentral Satellite session
  81. const SATELLITE_802_1x = 2; // This session supports 802.1x profile checking and creation
  82. // Events
  83. /*
  84. var eventsMessageId = {
  85. 1: "Account login",
  86. 2: "Account logout",
  87. 3: "Changed language from {1} to {2}",
  88. 4: "Joined desktop multiplex session",
  89. 5: "Left the desktop multiplex session",
  90. 6: "Started desktop multiplex session",
  91. 7: "Finished recording session, {0} second(s)",
  92. 8: "Closed desktop multiplex session, {0} second(s)"
  93. };
  94. */
  95. var obj = {};
  96. obj.user = user;
  97. obj.domain = domain;
  98. obj.ws = ws;
  99. // Information related to the current page the user is looking at
  100. obj.deviceSkip = 0; // How many devices to skip
  101. obj.deviceLimit = 0; // How many devices to view
  102. obj.visibleDevices = null; // An object of visible nodeid's if the user is in paging mode
  103. if (domain.maxdeviceview != null) { obj.deviceLimit = domain.maxdeviceview; }
  104. // Check if we are a cross-domain administrator
  105. if (parent.parent.config.settings.managecrossdomain && (parent.parent.config.settings.managecrossdomain.indexOf(user._id) >= 0)) { obj.crossDomain = true; }
  106. // Server side Intel AMT stack
  107. const WsmanComm = require('./amt/amt-wsman-comm.js');
  108. const Wsman = require('./amt/amt-wsman.js');
  109. const Amt = require('./amt/amt.js');
  110. // If this session has an expire time, setup a timer now.
  111. if ((req.session != null) && (typeof req.session.expire == 'number')) {
  112. var delta = (req.session.expire - Date.now());
  113. if (delta <= 0) { req.session = {}; try { ws.close(); } catch (ex) { } return; } // Session is already expired, close now.
  114. obj.expireTimer = setTimeout(function () { for (var i in req.session) { delete req.session[i]; } obj.close(); }, delta);
  115. }
  116. // Send data through the websocket
  117. obj.send = function (object) { try { ws.send(JSON.stringify(object)); } catch(ex) {} }
  118. // Clean a IPv6 address that encodes a IPv4 address
  119. function cleanRemoteAddr(addr) { if (addr.startsWith('::ffff:')) { return addr.substring(7); } else { return addr; } }
  120. // Send a PING/PONG message
  121. function sendPing() { try { obj.ws.send('{"action":"ping"}'); } catch (ex) { } }
  122. function sendPong() { try { obj.ws.send('{"action":"pong"}'); } catch (ex) { } }
  123. // Setup the agent PING/PONG timers
  124. if ((typeof args.browserping == 'number') && (obj.pingtimer == null)) { obj.pingtimer = setInterval(sendPing, args.browserping * 1000); }
  125. else if ((typeof args.browserpong == 'number') && (obj.pongtimer == null)) { obj.pongtimer = setInterval(sendPong, args.browserpong * 1000); }
  126. // Disconnect this user
  127. obj.close = function (arg) {
  128. obj.ws.xclosed = 1; // This is for testing. Will be displayed when running "usersessions" server console command.
  129. if ((arg == 1) || (arg == null)) { try { obj.ws.close(); parent.parent.debug('user', 'Soft disconnect'); } catch (ex) { console.log(ex); } } // Soft close, close the websocket
  130. if (arg == 2) { try { obj.ws._socket._parent.end(); parent.parent.debug('user', 'Hard disconnect'); } catch (ex) { console.log(ex); } } // Hard close, close the TCP socket
  131. obj.ws.xclosed = 2; // DEBUG
  132. // Perform timer cleanup
  133. if (obj.pingtimer) { clearInterval(obj.pingtimer); delete obj.pingtimer; }
  134. if (obj.pongtimer) { clearInterval(obj.pongtimer); delete obj.pongtimer; }
  135. obj.ws.xclosed = 3; // DEBUG
  136. // Clear expire timeout
  137. if (obj.expireTimer != null) { clearTimeout(obj.expireTimer); delete obj.expireTimer; }
  138. obj.ws.xclosed = 4; // DEBUG
  139. // Perform cleanup
  140. parent.parent.RemoveAllEventDispatch(obj.ws);
  141. if (obj.serverStatsTimer != null) { clearInterval(obj.serverStatsTimer); delete obj.serverStatsTimer; }
  142. if (req.session && req.session.ws && req.session.ws == obj.ws) { delete req.session.ws; }
  143. if (parent.wssessions2[ws.sessionId]) { delete parent.wssessions2[ws.sessionId]; }
  144. obj.ws.xclosed = 5; // DEBUG
  145. if ((obj.user != null) && (parent.wssessions[obj.user._id])) {
  146. obj.ws.xclosed = 6; // DEBUG
  147. var i = parent.wssessions[obj.user._id].indexOf(obj.ws);
  148. if (i >= 0) {
  149. obj.ws.xclosed = 7; // DEBUG
  150. parent.wssessions[obj.user._id].splice(i, 1);
  151. var user = parent.users[obj.user._id];
  152. if (user) {
  153. obj.ws.xclosed = 8; // DEBUG
  154. if (parent.parent.multiServer == null) {
  155. var targets = ['*', 'server-users'];
  156. if (obj.user.groups) { for (var i in obj.user.groups) { targets.push('server-users:' + i); } }
  157. parent.parent.DispatchEvent(targets, obj, { action: 'wssessioncount', userid: user._id, username: user.name, count: parent.wssessions[obj.user._id].length, nolog: 1, domain: domain.id });
  158. } else {
  159. parent.recountSessions(ws.sessionId); // Recount sessions
  160. }
  161. }
  162. if (parent.wssessions[obj.user._id].length == 0) { delete parent.wssessions[obj.user._id]; }
  163. }
  164. }
  165. obj.ws.xclosed = 9; // DEBUG
  166. // If we have peer servers, inform them of the disconnected session
  167. if (parent.parent.multiServer != null) { parent.parent.multiServer.DispatchMessage({ action: 'sessionEnd', sessionid: ws.sessionId }); }
  168. obj.ws.xclosed = 10; // DEBUG
  169. // Update user last access time
  170. if (obj.user != null) {
  171. const timeNow = Math.floor(Date.now() / 1000);
  172. if (obj.user.access < (timeNow - 300)) { // Only update user access time if longer than 5 minutes
  173. obj.user.access = timeNow;
  174. parent.db.SetUser(user);
  175. // Event the change
  176. var message = { etype: 'user', userid: obj.user._id, username: obj.user.name, account: parent.CloneSafeUser(obj.user), action: 'accountchange', domain: domain.id, nolog: 1 };
  177. if (parent.db.changeStream) { message.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  178. var targets = ['*', 'server-users', obj.user._id];
  179. if (obj.user.groups) { for (var i in obj.user.groups) { targets.push('server-users:' + i); } }
  180. parent.parent.DispatchEvent(targets, obj, message);
  181. }
  182. }
  183. // Aggressive cleanup
  184. delete obj.user;
  185. delete obj.domain;
  186. delete obj.ws.userid;
  187. delete obj.ws.domainid;
  188. delete obj.ws.clientIp;
  189. delete obj.ws.sessionId;
  190. delete obj.ws.HandleEvent;
  191. obj.ws.removeAllListeners(['message', 'close', 'error']);
  192. obj.ws.xclosed = 11; // DEBUG
  193. };
  194. // Convert a mesh path array into a real path on the server side
  195. function meshPathToRealPath(meshpath, user) {
  196. if (common.validateArray(meshpath, 1) == false) return null;
  197. var splitid = meshpath[0].split('/');
  198. if (splitid[0] == 'user') {
  199. // Check user access
  200. if (meshpath[0] != user._id) return null; // Only allow own user folder
  201. } else if (splitid[0] == 'mesh') {
  202. // Check mesh access
  203. if ((parent.GetMeshRights(user, meshpath[0]) & MESHRIGHT_SERVERFILES) == 0) return null; // This user must have mesh rights to "server files"
  204. } else return null;
  205. var rootfolder = meshpath[0], rootfoldersplit = rootfolder.split('/'), domainx = 'domain';
  206. if (rootfoldersplit[1].length > 0) domainx = 'domain-' + rootfoldersplit[1];
  207. var path = parent.path.join(parent.filespath, domainx, rootfoldersplit[0] + '-' + rootfoldersplit[2]);
  208. for (var i = 1; i < meshpath.length; i++) { if (common.IsFilenameValid(meshpath[i]) == false) { path = null; break; } path += ("/" + meshpath[i]); }
  209. return path;
  210. }
  211. // Copy a file using the best technique available
  212. function copyFile(src, dest, func, tag) {
  213. if (fs.copyFile) {
  214. // NodeJS v8.5 and higher
  215. fs.copyFile(src, dest, function (err) { func(tag); })
  216. } else {
  217. // Older NodeJS
  218. try {
  219. var ss = fs.createReadStream(src), ds = fs.createWriteStream(dest);
  220. ss.on('error', function () { func(tag); });
  221. ds.on('error', function () { func(tag); });
  222. ss.pipe(ds);
  223. ds.ss = ss;
  224. if (arguments.length == 3 && typeof arguments[2] === 'function') { ds.on('close', arguments[2]); }
  225. else if (arguments.length == 4 && typeof arguments[3] === 'function') { ds.on('close', arguments[3]); }
  226. ds.on('close', function () { func(tag); });
  227. } catch (ex) { }
  228. }
  229. }
  230. // Route a command to a target node
  231. function routeCommandToNode(command, requiredRights, requiredNonRights, func, options) {
  232. if (common.validateString(command.nodeid, 8, 128) == false) { if (func) { func(false); } return false; }
  233. var splitnodeid = command.nodeid.split('/');
  234. // Check that we are in the same domain and the user has rights over this node.
  235. if ((splitnodeid[0] == 'node') && (splitnodeid[1] == domain.id)) {
  236. // See if the node is connected
  237. var agent = parent.wsagents[command.nodeid];
  238. if (agent != null) {
  239. // Check if we have permission to send a message to that node
  240. parent.GetNodeWithRights(domain, user, agent.dbNodeKey, function (node, rights, visible) {
  241. var mesh = parent.meshes[agent.dbMeshKey];
  242. if ((node != null) && (mesh != null) && ((rights & MESHRIGHT_REMOTECONTROL) || (rights & MESHRIGHT_REMOTEVIEWONLY))) { // 8 is remote control permission, 256 is desktop read only
  243. if ((requiredRights != null) && ((rights & requiredRights) == 0)) { if (func) { func(false); return; } } // Check Required Rights
  244. if ((requiredNonRights != null) && (rights != MESHRIGHT_ADMIN) && ((rights & requiredNonRights) != 0)) { if (func) { func(false); return; } } // Check Required None Rights
  245. command.sessionid = ws.sessionId; // Set the session id, required for responses
  246. command.rights = rights; // Add user rights flags to the message
  247. if ((options != null) && (options.removeViewOnlyLimitation === true) && (command.rights != 0xFFFFFFFF) && ((command.rights & 0x100) != 0)) { command.rights -= 0x100; } // Since the multiplexor will enforce view-only, remove MESHRIGHT_REMOTEVIEWONLY
  248. command.consent = 0;
  249. if (typeof domain.userconsentflags == 'number') { command.consent |= domain.userconsentflags; } // Add server required consent flags
  250. if (typeof mesh.consent == 'number') { command.consent |= mesh.consent; } // Add device group user consent
  251. if (typeof node.consent == 'number') { command.consent |= node.consent; } // Add node user consent
  252. if (typeof user.consent == 'number') { command.consent |= user.consent; } // Add user consent
  253. // If desktop is viewonly, add this here.
  254. if ((typeof domain.desktop == 'object') && (domain.desktop.viewonly == true)) { command.desktopviewonly = true; }
  255. // Check if we need to add consent flags because of a user group link
  256. if ((user.links != null) && (user.links[mesh._id] == null) && (user.links[node._id] == null)) {
  257. // This user does not have a direct link to the device group or device. Find all user groups the would cause the link.
  258. for (var i in user.links) {
  259. var ugrp = parent.userGroups[i];
  260. if ((ugrp != null) && (ugrp.consent != null) && (ugrp.links != null) && ((ugrp.links[mesh._id] != null) || (ugrp.links[node._id] != null))) {
  261. command.consent |= ugrp.consent; // Add user group consent flags
  262. }
  263. }
  264. }
  265. command.username = user.name; // Add user name
  266. command.realname = user.realname; // Add real name
  267. command.userid = user._id; // Add user id
  268. command.remoteaddr = req.clientIp; // User's IP address
  269. if (typeof domain.desktopprivacybartext == 'string') { command.privacybartext = domain.desktopprivacybartext; } // Privacy bar text
  270. delete command.nodeid; // Remove the nodeid since it's implied
  271. try { agent.send(JSON.stringify(command)); } catch (ex) { }
  272. } else { if (func) { func(false); } }
  273. });
  274. } else {
  275. // Check if a peer server is connected to this agent
  276. var routing = parent.parent.GetRoutingServerIdNotSelf(command.nodeid, 1); // 1 = MeshAgent routing type
  277. if (routing != null) {
  278. // Check if we have permission to send a message to that node
  279. parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
  280. if ((requiredRights != null) && ((rights & requiredRights) == 0)) { if (func) { func(false); return; } } // Check Required Rights
  281. if ((requiredNonRights != null) && (rights != MESHRIGHT_ADMIN) && ((rights & requiredNonRights) != 0)) { if (func) { func(false); return; } } // Check Required None Rights
  282. var mesh = parent.meshes[routing.meshid];
  283. if ((node != null) && (mesh != null) && ((rights & MESHRIGHT_REMOTECONTROL) || (rights & MESHRIGHT_REMOTEVIEWONLY))) { // 8 is remote control permission
  284. command.fromSessionid = ws.sessionId; // Set the session id, required for responses
  285. command.rights = rights; // Add user rights flags to the message
  286. if ((options != null) && (options.removeViewOnlyLimitation === true) && (command.rights != 0xFFFFFFFF) && ((command.rights & 0x100) != 0)) { command.rights -= 0x100; } // Since the multiplexor will enforce view-only, remove MESHRIGHT_REMOTEVIEWONLY
  287. command.consent = 0;
  288. if (typeof domain.userconsentflags == 'number') { command.consent |= domain.userconsentflags; } // Add server required consent flags
  289. if (typeof mesh.consent == 'number') { command.consent |= mesh.consent; } // Add device group user consent
  290. if (typeof node.consent == 'number') { command.consent |= node.consent; } // Add node user consent
  291. if (typeof user.consent == 'number') { command.consent |= user.consent; } // Add user consent
  292. // Check if we need to add consent flags because of a user group link
  293. if ((user.links != null) && (user.links[mesh._id] == null) && (user.links[node._id] == null)) {
  294. // This user does not have a direct link to the device group or device. Find all user groups the would cause the link.
  295. for (var i in user.links) {
  296. var ugrp = parent.userGroups[i];
  297. if ((ugrp != null) && (ugrp.consent != null) && (ugrp.links != null) && ((ugrp.links[mesh._id] != null) || (ugrp.links[node._id] != null))) {
  298. command.consent |= ugrp.consent; // Add user group consent flags
  299. }
  300. }
  301. }
  302. command.username = user.name; // Add user name
  303. command.realname = user.realname; // Add real name
  304. command.userid = user._id; // Add user id
  305. command.remoteaddr = req.clientIp; // User's IP address
  306. if (typeof domain.desktopprivacybartext == 'string') { command.privacybartext = domain.desktopprivacybartext; } // Privacy bar text
  307. parent.parent.multiServer.DispatchMessageSingleServer(command, routing.serverid);
  308. } else { if (func) { func(false); } }
  309. });
  310. } else { if (func) { func(false); } return false; }
  311. }
  312. } else { if (func) { func(false); } return false; }
  313. if (func) { func(true); }
  314. return true;
  315. }
  316. // Route a command to all targets in a mesh
  317. function routeCommandToMesh(meshid, command) {
  318. // If we have peer servers, inform them of this command to send to all agents of this device group
  319. if (parent.parent.multiServer != null) { parent.parent.multiServer.DispatchMessage({ action: 'agentMsgByMeshId', meshid: meshid, command: command }); }
  320. // See if the node is connected
  321. for (var nodeid in parent.wsagents) {
  322. var agent = parent.wsagents[nodeid];
  323. if (agent.dbMeshKey == meshid) { try { agent.send(JSON.stringify(command)); } catch (ex) { } }
  324. }
  325. return true;
  326. }
  327. try {
  328. // Check if the user is logged in
  329. if (user == null) { try { ws.close(); } catch (e) { } return; }
  330. // Check if we have exceeded the user session limit
  331. if ((typeof domain.limits.maxusersessions == 'number') || (typeof domain.limits.maxsingleusersessions == 'number')) {
  332. // Count the number of user sessions for this domain
  333. var domainUserSessionCount = 0, selfUserSessionCount = 0;
  334. for (var i in parent.wssessions2) {
  335. if (parent.wssessions2[i].domainid == domain.id) {
  336. domainUserSessionCount++; if (parent.wssessions2[i].userid == user._id) { selfUserSessionCount++; }
  337. }
  338. }
  339. // Check if we have too many user sessions
  340. if (((typeof domain.limits.maxusersessions == 'number') && (domainUserSessionCount >= domain.limits.maxusersessions)) || ((typeof domain.limits.maxsingleusersessions == 'number') && (selfUserSessionCount >= domain.limits.maxsingleusersessions))) {
  341. try { ws.send(JSON.stringify({ action: 'stopped', msg: 'Session count exceed' })); } catch (ex) { }
  342. try { ws.close(); } catch (e) { }
  343. return;
  344. }
  345. }
  346. // Associate this websocket session with the web session
  347. ws.userid = user._id;
  348. ws.domainid = domain.id;
  349. ws.clientIp = req.clientIp;
  350. // Create a new session id for this user.
  351. parent.crypto.randomBytes(20, function (err, randombuf) {
  352. ws.sessionId = user._id + '/' + randombuf.toString('hex');
  353. // Add this web socket session to session list
  354. parent.wssessions2[ws.sessionId] = ws;
  355. if (!parent.wssessions[user._id]) { parent.wssessions[user._id] = [ws]; } else { parent.wssessions[user._id].push(ws); }
  356. if (parent.parent.multiServer == null) {
  357. var targets = ['*', 'server-users'];
  358. if (obj.user.groups) { for (var i in obj.user.groups) { targets.push('server-users:' + i); } }
  359. parent.parent.DispatchEvent(targets, obj, { action: 'wssessioncount', userid: user._id, username: user.name, count: parent.wssessions[user._id].length, nolog: 1, domain: domain.id });
  360. } else {
  361. parent.recountSessions(ws.sessionId); // Recount sessions
  362. }
  363. // If we have peer servers, inform them of the new session
  364. if (parent.parent.multiServer != null) { parent.parent.multiServer.DispatchMessage({ action: 'sessionStart', sessionid: ws.sessionId }); }
  365. // Handle events
  366. ws.HandleEvent = function (source, event, ids, id) {
  367. // If this session is logged in using a loginToken and the token is removed, disconnect.
  368. if ((req.session.loginToken != null) && (typeof event == 'object') && (event.action == 'loginTokenChanged') && (event.removed != null) && (event.removed.indexOf(req.session.loginToken) >= 0)) { delete req.session; obj.close(); return; }
  369. // If this user is not viewing all devices and paging, check if this event is in the current page
  370. if (isEventWithinPage(ids) == false) return;
  371. // Normally, only allow this user to receive messages from it's own domain.
  372. // If the user is a cross domain administrator, allow some select messages from different domains.
  373. if ((event.domain == null) || (event.domain == domain.id) || ((obj.crossDomain === true) && (allowedCrossDomainMessages.indexOf(event.action) >= 0))) {
  374. try {
  375. if (event == 'close') { try { delete req.session; } catch (ex) { } obj.close(); return; }
  376. else if (event == 'resubscribe') { user.subscriptions = parent.subscribe(user._id, ws); }
  377. else if (event == 'updatefiles') { updateUserFiles(user, ws, domain); }
  378. else {
  379. // If updating guest device shares, if we are updating a user that is not creator of the share, remove the URL.
  380. if (((event.action == 'deviceShareUpdate') && (Array.isArray(event.deviceShares))) || ((event.action == 'changenode') && (event.node != null) && ((event.node.rdp != null) || (event.node.ssh != null)))) {
  381. event = common.Clone(event);
  382. if ((event.action == 'deviceShareUpdate') && (Array.isArray(event.deviceShares))) {
  383. for (var i in event.deviceShares) { if (event.deviceShares[i].userid != user._id) { delete event.deviceShares[i].url; } }
  384. }
  385. if ((event.action == 'changenode') && (event.node != null) && ((event.node.rdp != null) || (event.node.ssh != null))) {
  386. // Clean up RDP & SSH credentials
  387. if ((event.node.rdp != null) && (typeof event.node.rdp[user._id] == 'number')) { event.node.rdp = event.node.rdp[user._id]; } else { delete event.node.rdp; }
  388. if ((event.node.ssh != null) && (typeof event.node.ssh[user._id] == 'number')) { event.node.ssh = event.node.ssh[user._id]; } else { delete event.node.ssh; }
  389. }
  390. }
  391. // This is a MeshCentral Satellite message
  392. if (event.action == 'satellite') { if ((obj.ws.satelliteFlags & event.satelliteFlags) != 0) { try { ws.send(JSON.stringify(event)); } catch (ex) { } return; } }
  393. // Because of the device group "Show Self Events Only", we need to do more checks here.
  394. if (id.startsWith('mesh/')) {
  395. // Check if we have rights to get this message. If we have limited events on this mesh, don't send the event to the user.
  396. var meshrights = parent.GetMeshRights(user, id);
  397. if ((meshrights === MESHRIGHT_ADMIN) || ((meshrights & MESHRIGHT_LIMITEVENTS) == 0) || (ids.indexOf(user._id) >= 0)) {
  398. // We have the device group rights to see this event or we are directly targetted by the event
  399. try { ws.send(JSON.stringify({ action: 'event', event: event })); } catch (ex) { }
  400. } else {
  401. // Check if no other users are targeted by the event, if not, we can get this event.
  402. var userTarget = false;
  403. for (var i in ids) { if (ids[i].startsWith('user/')) { userTarget = true; } }
  404. if (userTarget == false) { ws.send(JSON.stringify({ action: 'event', event: event })); }
  405. }
  406. } else if (event.ugrpid != null) {
  407. if ((user.siteadmin & SITERIGHT_USERGROUPS) != 0) {
  408. // If we have the rights to see users in a group, send the group as is.
  409. try { ws.send(JSON.stringify({ action: 'event', event: event })); } catch (ex) { }
  410. } else {
  411. // We don't have the rights to see otehr users in the user group, remove the links that are not for ourselves.
  412. var links = {};
  413. if (event.links) { for (var i in event.links) { if ((i == user._id) || i.startsWith('mesh/') || i.startsWith('node/')) { links[i] = event.links[i]; } } }
  414. try { ws.send(JSON.stringify({ action: 'event', event: { ugrpid: event.ugrpid, domain: event.domain, time: event.time, name: event.name, action: event.action, username: event.username, links: links, h: event.h } })); } catch (ex) { }
  415. }
  416. } else {
  417. // This is not a device group event, we can get this event.
  418. try { ws.send(JSON.stringify({ action: 'event', event: event })); } catch (ex) { }
  419. }
  420. }
  421. } catch (ex) { console.log(ex); }
  422. }
  423. };
  424. user.subscriptions = parent.subscribe(user._id, ws); // Subscribe to events
  425. try { ws._socket.setKeepAlive(true, 240000); } catch (ex) { } // Set TCP keep alive
  426. // Send current server statistics
  427. obj.SendServerStats = function () {
  428. // Take a look at server stats
  429. var os = require('os');
  430. var stats = { action: 'serverstats', totalmem: os.totalmem(), freemem: os.freemem() };
  431. try { stats.cpuavg = os.loadavg(); } catch (ex) { }
  432. if (parent.parent.platform != 'win32') {
  433. try { stats.availablemem = 1024 * Number(/MemAvailable:[ ]+(\d+)/.exec(fs.readFileSync('/proc/meminfo', 'utf8'))[1]); } catch (ex) { }
  434. }
  435. // Count the number of device groups that are not deleted
  436. var activeDeviceGroups = 0;
  437. for (var i in parent.meshes) { if (parent.meshes[i].deleted == null) { activeDeviceGroups++; } } // This is not ideal for performance, we want to dome something better.
  438. var serverStats = {
  439. UserAccounts: Object.keys(parent.users).length,
  440. DeviceGroups: activeDeviceGroups,
  441. AgentSessions: Object.keys(parent.wsagents).length,
  442. ConnectedUsers: Object.keys(parent.wssessions).length,
  443. UsersSessions: Object.keys(parent.wssessions2).length,
  444. RelaySessions: parent.relaySessionCount,
  445. RelayCount: Object.keys(parent.wsrelays).length,
  446. ConnectedIntelAMT: 0
  447. };
  448. if (parent.relaySessionErrorCount != 0) { serverStats.RelayErrors = parent.relaySessionErrorCount; }
  449. if (parent.parent.mpsserver != null) {
  450. serverStats.ConnectedIntelAMTCira = 0;
  451. for (var i in parent.parent.mpsserver.ciraConnections) { serverStats.ConnectedIntelAMTCira += parent.parent.mpsserver.ciraConnections[i].length; }
  452. }
  453. for (var i in parent.parent.connectivityByNode) {
  454. const node = parent.parent.connectivityByNode[i];
  455. if (node && typeof node.connectivity !== 'undefined' && node.connectivity === 4) { serverStats.ConnectedIntelAMT++; }
  456. }
  457. // Take a look at agent errors
  458. var agentstats = parent.getAgentStats();
  459. var errorCounters = {}, errorCountersCount = 0;
  460. if (agentstats.meshDoesNotExistCount > 0) { errorCountersCount++; errorCounters.UnknownGroup = agentstats.meshDoesNotExistCount; }
  461. if (agentstats.invalidPkcsSignatureCount > 0) { errorCountersCount++; errorCounters.InvalidPKCSsignature = agentstats.invalidPkcsSignatureCount; }
  462. if (agentstats.invalidRsaSignatureCount > 0) { errorCountersCount++; errorCounters.InvalidRSAsignature = agentstats.invalidRsaSignatureCount; }
  463. if (agentstats.invalidJsonCount > 0) { errorCountersCount++; errorCounters.InvalidJSON = agentstats.invalidJsonCount; }
  464. if (agentstats.unknownAgentActionCount > 0) { errorCountersCount++; errorCounters.UnknownAction = agentstats.unknownAgentActionCount; }
  465. if (agentstats.agentBadWebCertHashCount > 0) { errorCountersCount++; errorCounters.BadWebCertificate = agentstats.agentBadWebCertHashCount; }
  466. if ((agentstats.agentBadSignature1Count + agentstats.agentBadSignature2Count) > 0) { errorCountersCount++; errorCounters.BadSignature = (agentstats.agentBadSignature1Count + agentstats.agentBadSignature2Count); }
  467. if (agentstats.agentMaxSessionHoldCount > 0) { errorCountersCount++; errorCounters.MaxSessionsReached = agentstats.agentMaxSessionHoldCount; }
  468. if ((agentstats.invalidDomainMeshCount + agentstats.invalidDomainMesh2Count) > 0) { errorCountersCount++; errorCounters.UnknownDeviceGroup = (agentstats.invalidDomainMeshCount + agentstats.invalidDomainMesh2Count); }
  469. if ((agentstats.invalidMeshTypeCount + agentstats.invalidMeshType2Count) > 0) { errorCountersCount++; errorCounters.InvalidDeviceGroupType = (agentstats.invalidMeshTypeCount + agentstats.invalidMeshType2Count); }
  470. //if (agentstats.duplicateAgentCount > 0) { errorCountersCount++; errorCounters.DuplicateAgent = agentstats.duplicateAgentCount; }
  471. // Send out the stats
  472. stats.values = { ServerState: serverStats }
  473. if (errorCountersCount > 0) { stats.values.AgentErrorCounters = errorCounters; }
  474. try { ws.send(JSON.stringify(stats)); } catch (ex) { }
  475. }
  476. // When data is received from the web socket
  477. ws.on('message', processWebSocketData);
  478. // If error, do nothing
  479. ws.on('error', function (err) { console.log(err); obj.close(0); });
  480. // If the web socket is closed
  481. ws.on('close', function (req) { obj.close(0); });
  482. // Figure out the MPS port, use the alias if set
  483. var mpsport = ((args.mpsaliasport != null) ? args.mpsaliasport : args.mpsport);
  484. var httpport = ((args.aliasport != null) ? args.aliasport : args.port);
  485. // Build server information object
  486. const allFeatures = parent.getDomainUserFeatures(domain, user, req);
  487. var serverinfo = { domain: domain.id, name: domain.dns ? domain.dns : parent.certificates.CommonName, mpsname: parent.certificates.AmtMpsName, mpsport: mpsport, mpspass: args.mpspass, port: httpport, emailcheck: ((domain.mailserver != null) && (domain.auth != 'sspi') && (domain.auth != 'ldap') && (args.lanonly != true) && (parent.certificates.CommonName != null) && (parent.certificates.CommonName.indexOf('.') != -1) && (user._id.split('/')[2].startsWith('~') == false)), domainauth: (domain.auth == 'sspi'), serverTime: Date.now(), features: allFeatures.features, features2: allFeatures.features2 };
  488. serverinfo.languages = parent.renderLanguages;
  489. serverinfo.tlshash = Buffer.from(parent.webCertificateFullHashs[domain.id], 'binary').toString('hex').toUpperCase(); // SHA384 of server HTTPS certificate
  490. serverinfo.agentCertHash = parent.agentCertificateHashBase64;
  491. if (typeof domain.sessionrecording == 'object') {
  492. if (domain.sessionrecording.onlyselectedusers === true) { serverinfo.usersSessionRecording = 1; } // Allow enabling of session recording for users
  493. if (domain.sessionrecording.onlyselectedusergroups === true) { serverinfo.userGroupsSessionRecording = 1; } // Allow enabling of session recording for user groups
  494. if (domain.sessionrecording.onlyselecteddevicegroups === true) { serverinfo.devGroupSessionRecording = 1; } // Allow enabling of session recording for device groups
  495. }
  496. if ((parent.parent.config.domains[domain.id].amtacmactivation != null) && (parent.parent.config.domains[domain.id].amtacmactivation.acmmatch != null)) {
  497. var matchingDomains = [];
  498. for (var i in parent.parent.config.domains[domain.id].amtacmactivation.acmmatch) {
  499. var cn = parent.parent.config.domains[domain.id].amtacmactivation.acmmatch[i].cn;
  500. if ((cn != '*') && (matchingDomains.indexOf(cn) == -1)) { matchingDomains.push(cn); }
  501. }
  502. if (matchingDomains.length > 0) { serverinfo.amtAcmFqdn = matchingDomains; }
  503. }
  504. if (typeof domain.devicemeshrouterlinks == 'object') { serverinfo.devicemeshrouterlinks = domain.devicemeshrouterlinks; }
  505. if ((typeof domain.altmessenging == 'object') && (typeof domain.altmessenging.name == 'string') && (typeof domain.altmessenging.url == 'string')) { serverinfo.altmessenging = [{ name: domain.altmessenging.name, url: domain.altmessenging.url, localurl: domain.altmessenging.localurl, type: domain.altmessenging.type }]; }
  506. if (Array.isArray(domain.altmessenging)) { serverinfo.altmessenging = []; for (var i in domain.altmessenging) { if ((typeof domain.altmessenging[i] == 'object') && (typeof domain.altmessenging[i].name == 'string') && (typeof domain.altmessenging[i].url == 'string')) { serverinfo.altmessenging.push({ name: domain.altmessenging[i].name, url: domain.altmessenging[i].url, type: domain.altmessenging[i].type }); } } }
  507. serverinfo.https = true;
  508. serverinfo.redirport = args.redirport;
  509. if (parent.parent.webpush != null) { serverinfo.vapidpublickey = parent.parent.webpush.vapidPublicKey; } // Web push public key
  510. if (parent.parent.amtProvisioningServer != null) { serverinfo.amtProvServerMeshId = parent.parent.amtProvisioningServer.meshid; } // Device group that allows for bare-metal Intel AMT activation
  511. if ((typeof domain.autoremoveinactivedevices == 'number') && (domain.autoremoveinactivedevices > 0)) { serverinfo.autoremoveinactivedevices = domain.autoremoveinactivedevices; } // Default number of days before inactive devices are removed
  512. if (domain.passwordrequirements) {
  513. if (domain.passwordrequirements.lock2factor == true) { serverinfo.lock2factor = true; } // Indicate 2FA change are not allowed
  514. if (typeof domain.passwordrequirements.maxfidokeys == 'number') { serverinfo.maxfidokeys = domain.passwordrequirements.maxfidokeys; }
  515. }
  516. if (parent.parent.msgserver != null) { // Setup messaging providers information
  517. serverinfo.userMsgProviders = parent.parent.msgserver.providers;
  518. if (parent.parent.msgserver.discordUrl != null) { serverinfo.discordUrl = parent.parent.msgserver.discordUrl; }
  519. }
  520. if ((typeof parent.parent.config.messaging == 'object') && (typeof parent.parent.config.messaging.ntfy == 'object') && (typeof parent.parent.config.messaging.ntfy.userurl == 'string')) { // nfty user url
  521. serverinfo.userMsgNftyUrl = parent.parent.config.messaging.ntfy.userurl;
  522. }
  523. // Build the mobile agent URL, this is used to connect mobile devices
  524. var agentServerName = parent.getWebServerName(domain, req);
  525. if (typeof parent.args.agentaliasdns == 'string') { agentServerName = parent.args.agentaliasdns; }
  526. var xdomain = (domain.dns == null) ? domain.id : '';
  527. var agentHttpsPort = ((parent.args.aliasport == null) ? parent.args.port : parent.args.aliasport); // Use HTTPS alias port is specified
  528. if (parent.args.agentport != null) { agentHttpsPort = parent.args.agentport; } // If an agent only port is enabled, use that.
  529. if (parent.args.agentaliasport != null) { agentHttpsPort = parent.args.agentaliasport; } // If an agent alias port is specified, use that.
  530. serverinfo.magenturl = 'mc://' + agentServerName + ((agentHttpsPort != 443) ? (':' + agentHttpsPort) : '') + ((xdomain != '') ? ('/' + xdomain) : '');
  531. serverinfo.domainsuffix = xdomain;
  532. if (domain.guestdevicesharing === false) { serverinfo.guestdevicesharing = false; } else {
  533. if (typeof domain.guestdevicesharing == 'object') {
  534. if (typeof domain.guestdevicesharing.maxsessiontime == 'number') { serverinfo.guestdevicesharingmaxtime = domain.guestdevicesharing.maxsessiontime; }
  535. }
  536. }
  537. if (typeof domain.userconsentflags == 'number') { serverinfo.consent = domain.userconsentflags; }
  538. if ((typeof domain.usersessionidletimeout == 'number') && (domain.usersessionidletimeout > 0)) {serverinfo.timeout = (domain.usersessionidletimeout * 60 * 1000); }
  539. if (typeof domain.logoutonidlesessiontimeout == 'boolean') {
  540. serverinfo.logoutonidlesessiontimeout = domain.logoutonidlesessiontimeout;
  541. } else {
  542. // Default
  543. serverinfo.logoutonidlesessiontimeout = true;
  544. }
  545. if (user.siteadmin === SITERIGHT_ADMIN) {
  546. if (parent.parent.config.settings.managealldevicegroups.indexOf(user._id) >= 0 || (user.links && Object.keys(user.links).some(key => parent.parent.config.settings.managealldevicegroups.indexOf(key) >= 0))) { serverinfo.manageAllDeviceGroups = true; }
  547. if (obj.crossDomain === true) { serverinfo.crossDomain = []; for (var i in parent.parent.config.domains) { serverinfo.crossDomain.push(i); } }
  548. if (typeof parent.webCertificateExpire[domain.id] == 'number') { serverinfo.certExpire = parent.webCertificateExpire[domain.id]; }
  549. }
  550. if (typeof domain.terminal == 'object') { // Settings used for remote terminal feature
  551. if ((typeof domain.terminal.linuxshell == 'string') && (domain.terminal.linuxshell != 'any')) { serverinfo.linuxshell = domain.terminal.linuxshell; }
  552. }
  553. if (Array.isArray(domain.preconfiguredremoteinput)) { serverinfo.preConfiguredRemoteInput = domain.preconfiguredremoteinput; }
  554. if (Array.isArray(domain.preconfiguredscripts)) {
  555. const r = [];
  556. for (var i in domain.preconfiguredscripts) {
  557. const types = ['', 'bat', 'ps1', 'sh', 'agent']; // 1 = Windows Command, 2 = Windows PowerShell, 3 = Linux, 4 = Agent
  558. const script = domain.preconfiguredscripts[i];
  559. if ((typeof script.name == 'string') && (script.name.length <= 32) && (typeof script.type == 'string') && ((typeof script.file == 'string') || (typeof script.cmd == 'string'))) {
  560. const s = { name: script.name, type: types.indexOf(script.type.toLowerCase()) };
  561. if (s.type > 0) { r.push(s); }
  562. }
  563. }
  564. serverinfo.preConfiguredScripts = r;
  565. }
  566. if (domain.maxdeviceview != null) { serverinfo.maxdeviceview = domain.maxdeviceview; } // Maximum number of devices a user can view at any given time
  567. // Send server information
  568. try { ws.send(JSON.stringify({ action: 'serverinfo', serverinfo: serverinfo })); } catch (ex) { }
  569. // Send user information to web socket, this is the first thing we send
  570. try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: parent.CloneSafeUser(parent.users[user._id]) })); } catch (ex) { }
  571. if (user.siteadmin === SITERIGHT_ADMIN) {
  572. // Check if tracing is allowed for this domain
  573. if ((domain.myserver !== false) && ((domain.myserver == null) || (domain.myserver.trace === true))) {
  574. // Send server tracing information
  575. try { ws.send(JSON.stringify({ action: 'traceinfo', traceSources: parent.parent.debugRemoteSources })); } catch (ex) { }
  576. }
  577. // Send any server warnings if any
  578. var serverWarnings = parent.parent.getServerWarnings();
  579. if (serverWarnings.length > 0) { try { ws.send(JSON.stringify({ action: 'serverwarnings', warnings: serverWarnings })); } catch (ex) { } }
  580. }
  581. // See how many times bad login attempts where made since the last login
  582. const lastLoginTime = parent.users[user._id].pastlogin;
  583. if (lastLoginTime != null) {
  584. db.GetFailedLoginCount(user._id, user.domain, new Date(lastLoginTime * 1000), function (count) {
  585. if (count > 0) { try { ws.send(JSON.stringify({ action: 'msg', type: 'notify', title: "Security Warning", tag: 'ServerNotify', id: Math.random(), value: "There has been " + count + " failed login attempts on this account since the last login.", titleid: 3, msgid: 12, args: [count] })); } catch (ex) { } delete user.pastlogin; }
  586. });
  587. }
  588. // If we are site administrator and Google Drive backup is setup, send out the status.
  589. if ((user.siteadmin === SITERIGHT_ADMIN) && (domain.id == '') && (typeof parent.parent.config.settings.autobackup == 'object') && (typeof parent.parent.config.settings.autobackup.googledrive == 'object')) {
  590. db.Get('GoogleDriveBackup', function (err, docs) {
  591. if (err != null) return;
  592. if (docs.length == 0) { try { ws.send(JSON.stringify({ action: 'serverBackup', service: 'googleDrive', state: 1 })); } catch (ex) { } }
  593. else { try { ws.send(JSON.stringify({ action: 'serverBackup', service: 'googleDrive', state: docs[0].state })); } catch (ex) { } }
  594. });
  595. }
  596. // We are all set, start receiving data
  597. ws._socket.resume();
  598. if (parent.parent.pluginHandler != null) parent.parent.pluginHandler.callHook('hook_userLoggedIn', user);
  599. });
  600. } catch (ex) { console.log(ex); }
  601. // Process incoming web socket data from the browser
  602. function processWebSocketData(msg) {
  603. var command, i = 0, mesh = null, meshid = null, nodeid = null, meshlinks = null, change = 0;
  604. try { command = JSON.parse(msg.toString('utf8')); } catch (e) { return; }
  605. if (common.validateString(command.action, 3, 32) == false) return; // Action must be a string between 3 and 32 chars
  606. var commandHandler = serverCommands[command.action];
  607. if (commandHandler != null) {
  608. try { commandHandler(command); return; }
  609. catch (e) {
  610. console.log('Unhandled error while processing ' + command.action + ' for user ' + user.name + ':\n' + e);
  611. parent.parent.logError(e.stack); return; // todo: remove returns when switch is gone
  612. }
  613. } else { }
  614. // console.log('Unknown action from user ' + user.name + ': ' + command.action + '.');
  615. // pass through to switch statement until refactoring complete
  616. switch (command.action) {
  617. case 'nodes':
  618. {
  619. // If in paging mode, look to set the skip and limit values
  620. if (domain.maxdeviceview != null) {
  621. if ((typeof command.skip == 'number') && (command.skip >= 0)) { obj.deviceSkip = command.skip; }
  622. if ((typeof command.limit == 'number') && (command.limit > 0)) { obj.deviceLimit = command.limit; }
  623. if (obj.deviceLimit > domain.maxdeviceview) { obj.deviceLimit = domain.maxdeviceview; }
  624. }
  625. var links = [], extraids = null, err = null;
  626. // Resolve the device group name if needed
  627. if ((typeof command.meshname == 'string') && (command.meshid == null)) {
  628. for (var i in parent.meshes) {
  629. var m = parent.meshes[i];
  630. if ((m.mtype == 2) && (m.name == command.meshname) && parent.IsMeshViewable(user, m)) {
  631. if (command.meshid == null) { command.meshid = m._id; } else { err = 'Duplicate device groups found'; }
  632. }
  633. }
  634. if (command.meshid == null) { err = 'Invalid group id'; }
  635. }
  636. if (err == null) {
  637. try {
  638. if (command.meshid == null) {
  639. // Request a list of all meshes this user as rights to
  640. links = parent.GetAllMeshIdWithRights(user);
  641. // Add any nodes with direct rights or any nodes with user group direct rights
  642. extraids = getUserExtraIds();
  643. } else {
  644. // Request list of all nodes for one specific meshid
  645. meshid = command.meshid;
  646. if (common.validateString(meshid, 0, 128) == false) { err = 'Invalid group id'; } else {
  647. if (meshid.split('/').length == 1) { meshid = 'mesh/' + domain.id + '/' + command.meshid; }
  648. if (parent.IsMeshViewable(user, meshid)) { links.push(meshid); } else { err = 'Invalid group id'; }
  649. }
  650. }
  651. } catch (ex) { err = 'Validation exception: ' + ex; }
  652. }
  653. // Handle any errors
  654. if (err != null) {
  655. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'nodes', responseid: command.responseid, result: err })); } catch (ex) { } }
  656. break;
  657. }
  658. // Request a list of all nodes
  659. db.GetAllTypeNoTypeFieldMeshFiltered(links, extraids, domain.id, 'node', command.id, obj.deviceSkip, obj.deviceLimit, function (err, docs) {
  660. //console.log(err, docs, links, extraids, domain.id, 'node', command.id);
  661. if (docs == null) { docs = []; }
  662. parent.common.unEscapeAllLinksFieldName(docs);
  663. var r = {}, nodeCount = docs.length;
  664. if (domain.maxdeviceview != null) { obj.visibleDevices = {}; }
  665. for (i in docs) {
  666. // Check device links, if a link points to an unknown user, remove it.
  667. parent.cleanDevice(docs[i]); // TODO: This will make the total device count incorrect and will affect device paging.
  668. // If we are paging, add the device to the page here
  669. if (domain.maxdeviceview != null) { obj.visibleDevices[docs[i]._id] = 1; }
  670. // Remove any connectivity and power state information, that should not be in the database anyway.
  671. // TODO: Find why these are sometimes saved in the db.
  672. if (docs[i].conn != null) { delete docs[i].conn; }
  673. if (docs[i].pwr != null) { delete docs[i].pwr; }
  674. if (docs[i].agct != null) { delete docs[i].agct; }
  675. if (docs[i].cict != null) { delete docs[i].cict; }
  676. // Add the connection state
  677. var state = parent.parent.GetConnectivityState(docs[i]._id);
  678. if (state) {
  679. docs[i].conn = state.connectivity;
  680. docs[i].pwr = state.powerState;
  681. if ((state.connectivity & 1) != 0) { var agent = parent.wsagents[docs[i]._id]; if (agent != null) { docs[i].agct = agent.connectTime; } }
  682. // Use the connection time of the CIRA/Relay connection
  683. if ((state.connectivity & 2) != 0) {
  684. var ciraConnection = parent.parent.mpsserver.GetConnectionToNode(docs[i]._id, null, true);
  685. if ((ciraConnection != null) && (ciraConnection.tag != null)) { docs[i].cict = ciraConnection.tag.connectTime; }
  686. }
  687. }
  688. // Compress the meshid's
  689. meshid = docs[i].meshid;
  690. if (!r[meshid]) { r[meshid] = []; }
  691. delete docs[i].meshid;
  692. // Remove push messaging token if present
  693. if (docs[i].pmt != null) { docs[i].pmt = 1; }
  694. // Remove SSH credentials if present
  695. if (docs[i].ssh != null) {
  696. if ((docs[i].ssh[user._id] != null) && (docs[i].ssh[user._id].u)) {
  697. if (docs[i].ssh.k && docs[i].ssh[user._id].kp) { docs[i].ssh = 2; } // Username, key and password
  698. else if (docs[i].ssh[user._id].k) { docs[i].ssh = 3; } // Username and key. No password.
  699. else if (docs[i].ssh[user._id].p) { docs[i].ssh = 1; } // Username and password
  700. else { delete docs[i].ssh; }
  701. } else {
  702. delete docs[i].ssh;
  703. }
  704. }
  705. // Remove RDP credentials if present, only set to 1 if our userid has RDP credentials
  706. if ((docs[i].rdp != null) && (docs[i].rdp[user._id] != null)) { docs[i].rdp = 1; } else { delete docs[i].rdp; }
  707. // Remove Intel AMT credential if present
  708. if (docs[i].intelamt != null) {
  709. if (docs[i].intelamt.pass != null) { docs[i].intelamt.pass = 1; }
  710. if (docs[i].intelamt.mpspass != null) { docs[i].intelamt.mpspass = 1; }
  711. }
  712. // If GeoLocation not enabled, remove any node location information
  713. if (domain.geolocation != true) {
  714. if (docs[i].iploc != null) { delete docs[i].iploc; }
  715. if (docs[i].wifiloc != null) { delete docs[i].wifiloc; }
  716. if (docs[i].gpsloc != null) { delete docs[i].gpsloc; }
  717. if (docs[i].userloc != null) { delete docs[i].userloc; }
  718. }
  719. // Add device sessions
  720. const xagent = parent.wsagents[docs[i]._id];
  721. if ((xagent != null) && (xagent.sessions != null)) { docs[i].sessions = xagent.sessions; }
  722. // Add IP-KVM sessions
  723. if (parent.parent.ipKvmManager != null) {
  724. const xipkvmport = parent.parent.ipKvmManager.managedPorts[docs[i]._id];
  725. if ((xipkvmport != null) && (xipkvmport.sessions != null)) { docs[i].sessions = xipkvmport.sessions; }
  726. }
  727. // Patch node links with names, like meshes links with names
  728. for (var a in docs[i].links) {
  729. if (!docs[i].links[a].name) {
  730. if (parent.users[a] && parent.users[a].realname) { docs[i].links[a].name = parent.users[a].realname; }
  731. else if (parent.users[a] && parent.users[a].name) { docs[i].links[a].name = parent.users[a].name; }
  732. }
  733. }
  734. r[meshid].push(docs[i]);
  735. }
  736. const response = { action: 'nodes', responseid: command.responseid, nodes: r, tag: command.tag };
  737. if (domain.maxdeviceview != null) {
  738. // If in paging mode, report back the skip and limit values
  739. response.skip = obj.deviceSkip;
  740. response.limit = obj.deviceLimit;
  741. // Add total device count
  742. // Only set response.totalcount if we need to be in paging mode
  743. if (nodeCount < response.limit) {
  744. if (obj.deviceSkip > 0) { response.totalcount = obj.deviceSkip + nodeCount; } else { obj.visibleDevices = null; }
  745. try { ws.send(JSON.stringify(response)); } catch (ex) { }
  746. } else {
  747. // Ask the database for the total device count
  748. if (db.CountAllTypeNoTypeFieldMeshFiltered) {
  749. db.CountAllTypeNoTypeFieldMeshFiltered(links, extraids, domain.id, 'node', command.id, function (err, count) {
  750. if ((err != null) || (typeof count != 'number') || ((obj.deviceSkip == 0) && (count < obj.deviceLimit))) {
  751. obj.visibleDevices = null;
  752. } else {
  753. response.totalcount = count;
  754. }
  755. try { ws.send(JSON.stringify(response)); } catch (ex) { }
  756. });
  757. } else {
  758. // The database does not support device counting
  759. obj.visibleDevices = null; // We are not in paging mode
  760. try { ws.send(JSON.stringify(response)); } catch (ex) { }
  761. }
  762. }
  763. } else {
  764. obj.visibleDevices = null; // We are not in paging mode
  765. try { ws.send(JSON.stringify(response)); } catch (ex) { }
  766. }
  767. });
  768. break;
  769. }
  770. case 'fileoperation':
  771. {
  772. // Check permissions
  773. if ((user.siteadmin & 8) != 0) {
  774. // Perform a file operation (Create Folder, Delete Folder, Delete File...)
  775. if (common.validateString(command.fileop, 3, 16) == false) return;
  776. var sendUpdate = true, path = meshPathToRealPath(command.path, user); // This will also check access rights
  777. if (path == null) break;
  778. if ((command.fileop == 'createfolder') && (common.IsFilenameValid(command.newfolder) == true)) {
  779. // Create a new folder
  780. try { fs.mkdirSync(parent.path.join(path, command.newfolder)); } catch (ex) {
  781. try { fs.mkdirSync(path); } catch (ex) { }
  782. try { fs.mkdirSync(parent.path.join(path, command.newfolder)); } catch (ex) { }
  783. }
  784. }
  785. else if (command.fileop == 'delete') {
  786. // Delete a file
  787. if (common.validateArray(command.delfiles, 1) == false) return;
  788. for (i in command.delfiles) {
  789. if (common.IsFilenameValid(command.delfiles[i]) == true) {
  790. var fullpath = parent.path.join(path, command.delfiles[i]);
  791. if (command.rec == true) {
  792. try { deleteFolderRecursive(fullpath); } catch (ex) { } // TODO, make this an async function
  793. } else {
  794. try { fs.rmdirSync(fullpath); } catch (ex) { try { fs.unlinkSync(fullpath); } catch (xe) { } }
  795. }
  796. }
  797. }
  798. // If we deleted something in the mesh root folder and the entire mesh folder is empty, remove it.
  799. if (command.path.length == 1) {
  800. try {
  801. if (command.path[0].startsWith('mesh//')) {
  802. path = meshPathToRealPath([command.path[0]], user);
  803. fs.readdir(path, function (err, dir) { if ((err == null) && (dir.length == 0)) { fs.rmdir(path, function (err) { }); } });
  804. }
  805. } catch (ex) { }
  806. }
  807. }
  808. else if ((command.fileop == 'rename') && (common.IsFilenameValid(command.oldname) === true) && (common.IsFilenameValid(command.newname) === true)) {
  809. // Rename
  810. try { fs.renameSync(parent.path.join(path, command.oldname), parent.path.join(path, command.newname)); } catch (e) { }
  811. }
  812. else if ((command.fileop == 'copy') || (command.fileop == 'move')) {
  813. // Copy or move of one or many files
  814. if (common.validateArray(command.names, 1) == false) return;
  815. var scpath = meshPathToRealPath(command.scpath, user); // This will also check access rights
  816. if (scpath == null) break;
  817. // TODO: Check quota if this is a copy
  818. for (i in command.names) {
  819. if (common.IsFilenameValid(command.names[i]) === true) {
  820. var s = parent.path.join(scpath, command.names[i]), d = parent.path.join(path, command.names[i]);
  821. sendUpdate = false;
  822. try { fs.mkdirSync(path); } catch (ex) { } // try to create folder first incase folder is missing
  823. copyFile(s, d, function (op) { if (op != null) { fs.unlink(op, function (err) { parent.parent.DispatchEvent([user._id], obj, 'updatefiles'); }); } else { parent.parent.DispatchEvent([user._id], obj, 'updatefiles'); } }, ((command.fileop == 'move') ? s : null));
  824. }
  825. }
  826. } else if (command.fileop == 'get') {
  827. // Get a short file and send it back on the web socket
  828. if (common.validateString(command.file, 1, 4096) == false) return;
  829. const scpath = meshPathToRealPath(command.path, user); // This will also check access rights
  830. if ((scpath == null) || (command.file !== parent.path.basename(command.file))) break;
  831. const filePath = parent.path.join(scpath, command.file);
  832. fs.stat(filePath, function (err, stat) {
  833. if ((err != null) || (stat == null) || (stat.size >= 204800)) return;
  834. fs.readFile(filePath, function (err, data) {
  835. if ((err != null) || (data == null)) return;
  836. command.data = data.toString('base64');
  837. ws.send(JSON.stringify(command)); // Send the file data back, base64 encoded.
  838. });
  839. });
  840. } else if (command.fileop == 'set') {
  841. // Set a short file transfered on the web socket
  842. if (common.validateString(command.file, 1, 4096) == false) return;
  843. if (typeof command.data != 'string') return;
  844. const scpath = meshPathToRealPath(command.path, user); // This will also check access rights
  845. if ((scpath == null) || (command.file !== parent.path.basename(command.file))) break;
  846. const filePath = parent.path.join(scpath, command.file);
  847. var data = null;
  848. try { data = Buffer.from(command.data, 'base64'); } catch (ex) { return; }
  849. fs.writeFile(filePath, data, function (err) { if (err == null) { parent.parent.DispatchEvent([user._id], obj, 'updatefiles'); } });
  850. }
  851. if (sendUpdate == true) { parent.parent.DispatchEvent([user._id], obj, 'updatefiles'); } // Fire an event causing this user to update this files
  852. }
  853. break;
  854. }
  855. case 'msg':
  856. {
  857. // Check the nodeid
  858. if (common.validateString(command.nodeid, 1, 1024) == false) {
  859. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'msg', result: 'Unable to route', tag: command.tag, responseid: command.responseid })); } catch (ex) { } }
  860. return;
  861. }
  862. // Rights check
  863. var requiredRights = null, requiredNonRights = null, routingOptions = null;
  864. // Complete the nodeid if needed
  865. if (command.nodeid.indexOf('/') == -1) { command.nodeid = 'node/' + domain.id + '/' + command.nodeid; }
  866. // Check if getting / setting clipboard data is allowed
  867. if ((command.type == 'getclip') && (domain.clipboardget == false)) { console.log('CG-EXIT'); break; }
  868. if ((command.type == 'setclip') && (domain.clipboardset == false)) { console.log('CS-EXIT'); break; }
  869. // Before routing this command, let's do some security checking.
  870. // If this is a tunnel request, we need to make sure the NodeID in the URL matches the NodeID in the command.
  871. if (command.type == 'tunnel') {
  872. if ((typeof command.value != 'string') || (typeof command.nodeid != 'string')) break;
  873. var url = null;
  874. try { url = require('url').parse(command.value, true); } catch (ex) { }
  875. if (url == null) break; // Bad URL
  876. if (url.query && url.query.nodeid && (url.query.nodeid != command.nodeid)) break; // Bad NodeID in URL query string
  877. // Check rights
  878. if (url.query.p == '1') { requiredNonRights = MESHRIGHT_NOTERMINAL; }
  879. else if ((url.query.p == '4') || (url.query.p == '5')) { requiredNonRights = MESHRIGHT_NOFILES; }
  880. // If we are using the desktop multiplexor, remove the VIEWONLY limitation. The multiplexor will take care of enforcing that limitation when needed.
  881. if (((parent.parent.config.settings.desktopmultiplex === true) || (domain.desktopmultiplex === true)) && (url.query.p == '2')) { routingOptions = { removeViewOnlyLimitation: true }; }
  882. // Add server TLS cert hash
  883. var tlsCertHash = null;
  884. if ((parent.parent.args.ignoreagenthashcheck == null) || (parent.parent.args.ignoreagenthashcheck === false)) { // TODO: If ignoreagenthashcheck is an array of IP addresses, not sure how to handle this.
  885. tlsCertHash = parent.webCertificateFullHashs[domain.id];
  886. if (tlsCertHash != null) { command.servertlshash = Buffer.from(tlsCertHash, 'binary').toString('hex'); }
  887. }
  888. // Add user consent messages
  889. command.soptions = {};
  890. if (typeof domain.consentmessages == 'object') {
  891. if (typeof domain.consentmessages.title == 'string') { command.soptions.consentTitle = domain.consentmessages.title; }
  892. if (typeof domain.consentmessages.desktop == 'string') { command.soptions.consentMsgDesktop = domain.consentmessages.desktop; }
  893. if (typeof domain.consentmessages.terminal == 'string') { command.soptions.consentMsgTerminal = domain.consentmessages.terminal; }
  894. if (typeof domain.consentmessages.files == 'string') { command.soptions.consentMsgFiles = domain.consentmessages.files; }
  895. if ((typeof domain.consentmessages.consenttimeout == 'number') && (domain.consentmessages.consenttimeout > 0)) { command.soptions.consentTimeout = domain.consentmessages.consenttimeout; }
  896. if (domain.consentmessages.autoacceptontimeout === true) { command.soptions.consentAutoAccept = true; }
  897. if (domain.consentmessages.autoacceptifnouser === true) { command.soptions.consentAutoAcceptIfNoUser = true; }
  898. if (domain.consentmessages.autoacceptifdesktopnouser === true) { command.soptions.consentAutoAcceptIfDesktopNoUser = true; }
  899. if (domain.consentmessages.autoacceptifterminalnouser === true) { command.soptions.consentAutoAcceptIfTerminalNoUser = true; }
  900. if (domain.consentmessages.autoacceptiffilenouser === true) { command.soptions.consentAautoAcceptIfFileNoUser = true; }
  901. if (domain.consentmessages.autoacceptiflocked === true) { command.soptions.consentAutoAcceptIfLocked = true; }
  902. if (domain.consentmessages.autoacceptifdesktoplocked === true) { command.soptions.consentAutoAcceptIfDesktopLocked = true; }
  903. if (domain.consentmessages.autoacceptifterminallocked === true) { command.soptions.consentAutoAcceptIfTerminalLocked = true; }
  904. if (domain.consentmessages.autoacceptiffilelocked === true) { command.soptions.consentAutoAcceptIfFileLocked = true; }
  905. if (domain.consentmessages.oldstyle === true) { command.soptions.oldStyle = true; }
  906. }
  907. if (typeof domain.notificationmessages == 'object') {
  908. if (typeof domain.notificationmessages.title == 'string') { command.soptions.notifyTitle = domain.notificationmessages.title; }
  909. if (typeof domain.notificationmessages.desktop == 'string') { command.soptions.notifyMsgDesktop = domain.notificationmessages.desktop; }
  910. if (typeof domain.notificationmessages.terminal == 'string') { command.soptions.notifyMsgTerminal = domain.notificationmessages.terminal; }
  911. if (typeof domain.notificationmessages.files == 'string') { command.soptions.notifyMsgFiles = domain.notificationmessages.files; }
  912. }
  913. // Add userid
  914. command.userid = user._id;
  915. // Add tunnel pre-message deflate
  916. if (typeof parent.parent.config.settings.agentwscompression == 'boolean') { command.perMessageDeflate = parent.parent.config.settings.agentwscompression; }
  917. }
  918. // If a response is needed, set a callback function
  919. var func = null;
  920. if (command.responseid != null) { func = function (r) { try { ws.send(JSON.stringify({ action: 'msg', result: r ? 'OK' : 'Unable to route', tag: command.tag, responseid: command.responseid })); } catch (ex) { } } }
  921. // Route this command to a target node
  922. routeCommandToNode(command, requiredRights, requiredNonRights, func, routingOptions);
  923. break;
  924. }
  925. case 'events':
  926. {
  927. // User filtered events
  928. if ((command.userid != null) && ((user.siteadmin & SITERIGHT_MANAGEUSERS) != 0)) {
  929. const userSplit = command.userid.split('/');
  930. if ((userSplit.length != 3) || (userSplit[1] != domain.id)) return;
  931. // TODO: Add the meshes command.userid has access to (???)
  932. var filter = [command.userid];
  933. var actionfilter = null;
  934. if (command.filter != null) {
  935. if (['agentlog','batchupload','changenode','manual','relaylog','removenode','runcommands'].includes(command.filter)) actionfilter = command.filter;
  936. }
  937. if ((command.limit == null) || (typeof command.limit != 'number')) {
  938. // Send the list of all events for this session
  939. db.GetUserEvents(filter, domain.id, command.userid, actionfilter, function (err, docs) {
  940. if (err != null) return;
  941. try { ws.send(JSON.stringify({ action: 'events', events: docs, userid: command.userid, tag: command.tag })); } catch (ex) { }
  942. });
  943. } else {
  944. // Send the list of most recent events for this session, up to 'limit' count
  945. db.GetUserEventsWithLimit(filter, domain.id, command.userid, command.limit, actionfilter, function (err, docs) {
  946. if (err != null) return;
  947. try { ws.send(JSON.stringify({ action: 'events', events: docs, userid: command.userid, tag: command.tag })); } catch (ex) { }
  948. });
  949. }
  950. } else if (command.nodeid != null) { // Device filtered events
  951. // Check that the user has access to this nodeid
  952. const nodeSplit = command.nodeid.split('/');
  953. if (nodeSplit.length == 1) { command.nodeid = 'node/' + domain.id + '/' + command.nodeid; }
  954. parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
  955. if (node == null) { try { ws.send(JSON.stringify({ action: 'events', events: [], nodeid: command.nodeid, tag: command.tag })); } catch (ex) { } return; }
  956. // Put a limit on the number of returned entries if present
  957. var limit = 10000;
  958. if (common.validateInt(command.limit, 1, 1000000) == true) { limit = command.limit; }
  959. var filter = null;
  960. if (command.filter != null) {
  961. if (['agentlog','batchupload','changenode','manual','relaylog','removenode','runcommands'].includes(command.filter)) filter = command.filter;
  962. }
  963. if (((rights & MESHRIGHT_LIMITEVENTS) != 0) && (rights != MESHRIGHT_ADMIN)) {
  964. // Send the list of most recent events for this nodeid that only apply to us, up to 'limit' count
  965. db.GetNodeEventsSelfWithLimit(node._id, domain.id, user._id, limit, filter, function (err, docs) {
  966. if (err != null) return;
  967. try { ws.send(JSON.stringify({ action: 'events', events: docs, nodeid: node._id, tag: command.tag })); } catch (ex) { }
  968. });
  969. } else {
  970. // Send the list of most recent events for this nodeid, up to 'limit' count
  971. db.GetNodeEventsWithLimit(node._id, domain.id, limit, filter, function (err, docs) {
  972. if (err != null) return;
  973. try { ws.send(JSON.stringify({ action: 'events', events: docs, nodeid: node._id, tag: command.tag })); } catch (ex) { }
  974. });
  975. }
  976. });
  977. } else {
  978. // Create a filter for device groups
  979. if ((obj.user == null) || (obj.user.links == null)) return;
  980. // All events
  981. var exGroupFilter2 = [], filter = [], filter2 = user.subscriptions;
  982. // Add all meshes for groups this user is part of
  983. // TODO (UserGroups)
  984. // Remove MeshID's that we do not have rights to see events for
  985. for (var link in obj.user.links) { if (((obj.user.links[link].rights & MESHRIGHT_LIMITEVENTS) != 0) && ((obj.user.links[link].rights != MESHRIGHT_ADMIN))) { exGroupFilter2.push(link); } }
  986. for (var i in filter2) { if (exGroupFilter2.indexOf(filter2[i]) == -1) { filter.push(filter2[i]); } }
  987. var actionfilter = null;
  988. if (command.filter != null) {
  989. if (['agentlog','batchupload','changenode','manual','relaylog','removenode','runcommands'].includes(command.filter)) actionfilter = command.filter;
  990. }
  991. if ((command.limit == null) || (typeof command.limit != 'number')) {
  992. // Send the list of all events for this session
  993. db.GetEvents(filter, domain.id, actionfilter, function (err, docs) {
  994. if (err != null) return;
  995. try { ws.send(JSON.stringify({ action: 'events', events: docs, user: command.user, tag: command.tag })); } catch (ex) { }
  996. });
  997. } else {
  998. // Send the list of most recent events for this session, up to 'limit' count
  999. db.GetEventsWithLimit(filter, domain.id, command.limit, actionfilter, function (err, docs) {
  1000. if (err != null) return;
  1001. try { ws.send(JSON.stringify({ action: 'events', events: docs, user: command.user, tag: command.tag })); } catch (ex) { }
  1002. });
  1003. }
  1004. }
  1005. break;
  1006. }
  1007. case 'recordings': {
  1008. if (((user.siteadmin & SITERIGHT_RECORDINGS) == 0) || (domain.sessionrecording == null)) return; // Check if recordings is enabled and we have rights to do this.
  1009. var recordingsPath = null;
  1010. if (domain.sessionrecording.filepath) { recordingsPath = domain.sessionrecording.filepath; } else { recordingsPath = parent.parent.recordpath; }
  1011. if (recordingsPath == null) return;
  1012. fs.readdir(recordingsPath, function (err, files) {
  1013. if (err != null) { try { ws.send(JSON.stringify({ action: 'recordings', error: 1, tag: command.tag })); } catch (ex) { } return; }
  1014. if ((command.limit == null) || (typeof command.limit != 'number')) {
  1015. // Send the list of all recordings
  1016. db.GetEvents(['recording'], domain.id, null, function (err, docs) {
  1017. if (err != null) { try { ws.send(JSON.stringify({ action: 'recordings', error: 2, tag: command.tag })); } catch (ex) { } return; }
  1018. for (var i in docs) {
  1019. delete docs[i].action; delete docs[i].etype; delete docs[i].msg; // TODO: We could make a more specific query in the DB and never have these.
  1020. if (files.indexOf(docs[i].filename) >= 0) { docs[i].present = 1; }
  1021. }
  1022. try { ws.send(JSON.stringify({ action: 'recordings', events: docs, tag: command.tag })); } catch (ex) { }
  1023. });
  1024. } else {
  1025. // Send the list of most recent recordings, up to 'limit' count
  1026. db.GetEventsWithLimit(['recording'], domain.id, command.limit, null, function (err, docs) {
  1027. if (err != null) { try { ws.send(JSON.stringify({ action: 'recordings', error: 2, tag: command.tag })); } catch (ex) { } return; }
  1028. for (var i in docs) {
  1029. delete docs[i].action; delete docs[i].etype; delete docs[i].msg; // TODO: We could make a more specific query in the DB and never have these.
  1030. if (files.indexOf(docs[i].filename) >= 0) { docs[i].present = 1; }
  1031. }
  1032. try { ws.send(JSON.stringify({ action: 'recordings', events: docs, tag: command.tag })); } catch (ex) { }
  1033. });
  1034. }
  1035. });
  1036. break;
  1037. }
  1038. case 'wssessioncount':
  1039. {
  1040. // Request a list of all web socket user session count
  1041. var wssessions = {};
  1042. if ((user.siteadmin & 2) == 0) { try { ws.send(JSON.stringify({ action: 'wssessioncount', wssessions: {}, tag: command.tag })); } catch (ex) { } break; }
  1043. if (parent.parent.multiServer == null) {
  1044. // No peering, use simple session counting
  1045. for (i in parent.wssessions) {
  1046. if ((obj.crossDomain === true) || (parent.wssessions[i][0].domainid == domain.id)) {
  1047. if ((user.groups == null) || (user.groups.length == 0)) {
  1048. // No user groups, count everything
  1049. wssessions[i] = parent.wssessions[i].length;
  1050. } else {
  1051. // Only count if session is for a user in our user groups
  1052. var sessionUser = parent.users[parent.wssessions[i][0].userid];
  1053. if ((sessionUser != null) && findOne(sessionUser.groups, user.groups)) {
  1054. wssessions[i] = parent.wssessions[i].length;
  1055. }
  1056. }
  1057. }
  1058. }
  1059. } else {
  1060. // We have peer servers, use more complex session counting
  1061. for (i in parent.sessionsCount) {
  1062. if ((obj.crossDomain === true) || (i.split('/')[1] == domain.id)) {
  1063. if ((user.groups == null) || (user.groups.length == 0)) {
  1064. // No user groups, count everything
  1065. wssessions[i] = parent.sessionsCount[i];
  1066. } else {
  1067. // Only count if session is for a user in our user groups
  1068. var sessionUser = parent.users[i];
  1069. if ((sessionUser != null) && findOne(sessionUser.groups, user.groups)) {
  1070. wssessions[i] = parent.sessionsCount[i];
  1071. }
  1072. }
  1073. }
  1074. }
  1075. }
  1076. try { ws.send(JSON.stringify({ action: 'wssessioncount', wssessions: wssessions, tag: command.tag })); } catch (ex) { } // wssessions is: userid --> count
  1077. break;
  1078. }
  1079. case 'deleteuser':
  1080. {
  1081. // Delete a user account
  1082. var err = null, delusersplit, deluserid, deluser, deluserdomain;
  1083. try {
  1084. if ((user.siteadmin & 2) == 0) { err = 'Permission denied'; }
  1085. else if (common.validateString(command.userid, 1, 2048) == false) { err = 'Invalid userid'; }
  1086. else {
  1087. if (command.userid.indexOf('/') < 0) { command.userid = 'user/' + domain.id + '/' + command.userid; }
  1088. delusersplit = command.userid.split('/');
  1089. deluserid = command.userid;
  1090. deluser = parent.users[deluserid];
  1091. if (deluser == null) { err = 'User does not exists'; }
  1092. else if ((obj.crossDomain !== true) && ((delusersplit.length != 3) || (delusersplit[1] != domain.id))) { err = 'Invalid domain'; } // Invalid domain, operation only valid for current domain
  1093. else if ((deluser.siteadmin === SITERIGHT_ADMIN) && (user.siteadmin != SITERIGHT_ADMIN)) { err = 'Permission denied'; } // Need full admin to remote another administrator
  1094. else if ((obj.crossDomain !== true) && (user.groups != null) && (user.groups.length > 0) && ((deluser.groups == null) || (findOne(deluser.groups, user.groups) == false))) { err = 'Invalid user group'; } // Can only perform this operation on other users of our group.
  1095. }
  1096. } catch (ex) { err = 'Validation exception: ' + ex; }
  1097. // Get domain
  1098. deluserdomain = domain;
  1099. if (obj.crossDomain === true) { deluserdomain = parent.parent.config.domains[delusersplit[1]]; }
  1100. if (deluserdomain == null) { err = 'Invalid domain'; }
  1101. // Handle any errors
  1102. if (err != null) {
  1103. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'deleteuser', responseid: command.responseid, result: err })); } catch (ex) { } }
  1104. break;
  1105. }
  1106. // Remove all links to this user
  1107. if (deluser.links != null) {
  1108. for (var i in deluser.links) {
  1109. if (i.startsWith('mesh/')) {
  1110. // Get the device group
  1111. mesh = parent.meshes[i];
  1112. if (mesh) {
  1113. // Remove user from the mesh
  1114. if (mesh.links[deluser._id] != null) { delete mesh.links[deluser._id]; parent.db.Set(mesh); }
  1115. // Notify mesh change
  1116. change = 'Removed user ' + deluser.name + ' from device group ' + mesh.name;
  1117. var event = { etype: 'mesh', userid: user._id, username: user.name, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msgid: 72, msgArgs: [deluser.name, mesh.name], msg: change, domain: deluserdomain.id, invite: mesh.invite };
  1118. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
  1119. parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(mesh, [deluser._id, user._id]), obj, event);
  1120. }
  1121. } else if (i.startsWith('node/')) {
  1122. // Get the node and the rights for this node
  1123. parent.GetNodeWithRights(deluserdomain, deluser, i, function (node, rights, visible) {
  1124. if ((node == null) || (node.links == null) || (node.links[deluser._id] == null)) return;
  1125. // Remove the link and save the node to the database
  1126. delete node.links[deluser._id];
  1127. if (Object.keys(node.links).length == 0) { delete node.links; }
  1128. db.Set(parent.cleanDevice(node));
  1129. // Event the node change
  1130. var event;
  1131. if (command.rights == 0) {
  1132. event = { etype: 'node', userid: user._id, username: user.name, action: 'changenode', nodeid: node._id, domain: deluserdomain.id, msgid: 60, msgArgs: [node.name], msg: 'Removed user device rights for ' + node.name, node: parent.CloneSafeNode(node) }
  1133. } else {
  1134. event = { etype: 'node', userid: user._id, username: user.name, action: 'changenode', nodeid: node._id, domain: deluserdomain.id, msgid: 61, msgArgs: [node.name], msg: 'Changed user device rights for ' + node.name, node: parent.CloneSafeNode(node) }
  1135. }
  1136. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
  1137. parent.parent.DispatchEvent(parent.CreateNodeDispatchTargets(node.meshid, node._id), obj, event);
  1138. });
  1139. } else if (i.startsWith('ugrp/')) {
  1140. // Get the device group
  1141. var ugroup = parent.userGroups[i];
  1142. if (ugroup) {
  1143. // Remove user from the user group
  1144. if (ugroup.links[deluser._id] != null) { delete ugroup.links[deluser._id]; parent.db.Set(ugroup); }
  1145. // Notify user group change
  1146. change = 'Removed user ' + deluser.name + ' from user group ' + ugroup.name;
  1147. var event = { etype: 'ugrp', userid: user._id, username: user.name, ugrpid: ugroup._id, name: ugroup.name, desc: ugroup.desc, action: 'usergroupchange', links: ugroup.links, msgid: 62, msgArgs: [deluser.name, ugroup.name], msg: 'Removed user ' + deluser.name + ' from user group ' + ugroup.name, addUserDomain: deluserdomain.id };
  1148. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user group. Another event will come.
  1149. parent.parent.DispatchEvent(['*', ugroup._id, user._id, deluser._id], obj, event);
  1150. }
  1151. }
  1152. }
  1153. }
  1154. db.Remove('ws' + deluser._id); // Remove user web state
  1155. db.Remove('nt' + deluser._id); // Remove notes for this user
  1156. db.Remove('ntp' + deluser._id); // Remove personal notes for this user
  1157. db.Remove('im' + deluser._id); // Remove image for this user
  1158. // Delete any login tokens
  1159. parent.parent.db.GetAllTypeNodeFiltered(['logintoken-' + deluser._id], domain.id, 'logintoken', null, function (err, docs) {
  1160. if ((err == null) && (docs != null)) { for (var i = 0; i < docs.length; i++) { parent.parent.db.Remove(docs[i]._id, function () { }); } }
  1161. });
  1162. // Delete all files on the server for this account
  1163. try {
  1164. var deluserpath = parent.getServerRootFilePath(deluser);
  1165. if (deluserpath != null) { parent.deleteFolderRec(deluserpath); }
  1166. } catch (e) { }
  1167. db.Remove(deluserid);
  1168. delete parent.users[deluserid];
  1169. var targets = ['*', 'server-users'];
  1170. if (deluser.groups) { for (var i in deluser.groups) { targets.push('server-users:' + i); } }
  1171. parent.parent.DispatchEvent(targets, obj, { etype: 'user', userid: deluserid, username: deluser.name, action: 'accountremove', msgid: 63, msg: 'Account removed', domain: deluserdomain.id });
  1172. parent.parent.DispatchEvent([deluserid], obj, 'close');
  1173. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'deleteuser', responseid: command.responseid, result: 'ok' })); } catch (ex) { } }
  1174. // Log in the auth log
  1175. if (parent.parent.authlog) { parent.parent.authLog('https', 'User ' + user.name + ' deleted user account ' + deluser.name); }
  1176. break;
  1177. }
  1178. case 'userbroadcast':
  1179. {
  1180. var err = null;
  1181. try {
  1182. // Broadcast a message to all currently connected users.
  1183. if ((user.siteadmin & 2) == 0) { err = "Permission denied"; }
  1184. else if (common.validateString(command.msg, 1, 512) == false) { err = "Message is too long"; } // Notification message is between 1 and 256 characters
  1185. } catch (ex) { err = "Validation exception: " + ex; }
  1186. // Handle any errors
  1187. if (err != null) {
  1188. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'userbroadcast', responseid: command.responseid, result: err })); } catch (ex) { } }
  1189. break;
  1190. }
  1191. // Create the notification message
  1192. var notification = { action: 'msg', type: 'notify', domain: domain.id, value: command.msg, title: user.name, icon: 0, tag: 'broadcast', id: Math.random() };
  1193. if ((typeof command.maxtime == 'number') && (command.maxtime > 0)) { notification.maxtime = command.maxtime; }
  1194. // Send the notification on all user sessions for this server
  1195. for (var i in parent.wssessions2) {
  1196. try {
  1197. if (parent.wssessions2[i].domainid == domain.id) {
  1198. var sessionUser = parent.users[parent.wssessions2[i].userid];
  1199. if ((command.userid != null) && (command.userid != sessionUser._id) && (command.userid != sessionUser._id.split('/')[2])) { continue; }
  1200. if ((command.target == null) || ((sessionUser.links) != null && (sessionUser.links[command.target] != null))) {
  1201. if ((user.groups == null) || (user.groups.length == 0)) {
  1202. // We are part of no user groups, send to everyone.
  1203. parent.wssessions2[i].send(JSON.stringify(notification));
  1204. } else {
  1205. // We are part of user groups, only send to sessions of users in our groups.
  1206. if ((sessionUser != null) && findOne(sessionUser.groups, user.groups)) {
  1207. parent.wssessions2[i].send(JSON.stringify(notification));
  1208. }
  1209. }
  1210. }
  1211. }
  1212. } catch (ex) { }
  1213. }
  1214. // TODO: Notify all sessions on other peers.
  1215. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'userbroadcast', responseid: command.responseid, result: 'ok' })); } catch (ex) { } }
  1216. break;
  1217. }
  1218. case 'edituser':
  1219. {
  1220. // Must be user administrator or edit self.
  1221. if (((user.siteadmin & 2) == 0) && (user._id != command.id)) break;
  1222. // User the username as userid if needed
  1223. if ((typeof command.username == 'string') && (command.userid == null)) { command.userid = command.username; }
  1224. if ((typeof command.id == 'string') && (command.userid == null)) { command.userid = command.id; }
  1225. // Edit a user account
  1226. var err = null, editusersplit, edituserid, edituser, edituserdomain;
  1227. try {
  1228. if ((user.siteadmin & 2) == 0) { err = 'Permission denied'; }
  1229. else if (common.validateString(command.userid, 1, 2048) == false) { err = 'Invalid userid'; }
  1230. else {
  1231. if (command.userid.indexOf('/') < 0) { command.userid = 'user/' + domain.id + '/' + command.userid; }
  1232. editusersplit = command.userid.split('/');
  1233. edituserid = command.userid;
  1234. edituser = parent.users[edituserid];
  1235. if (edituser == null) { err = 'User does not exists'; }
  1236. else if ((obj.crossDomain !== true) && ((editusersplit.length != 3) || (editusersplit[1] != domain.id))) { err = 'Invalid domain'; } // Invalid domain, operation only valid for current domain
  1237. else if ((edituser.siteadmin === SITERIGHT_ADMIN) && (user.siteadmin != SITERIGHT_ADMIN)) { err = 'Permission denied'; } // Need full admin to remote another administrator
  1238. else if ((obj.crossDomain !== true) && (user.groups != null) && (user.groups.length > 0) && ((edituser.groups == null) || (findOne(edituser.groups, user.groups) == false))) { err = 'Invalid user group'; } // Can only perform this operation on other users of our group.
  1239. }
  1240. } catch (ex) { err = 'Validation exception: ' + ex; }
  1241. // Handle any errors
  1242. if (err != null) {
  1243. if (command.responseid != null) {
  1244. try { ws.send(JSON.stringify({ action: 'edituser', responseid: command.responseid, result: err })); } catch (ex) { }
  1245. }
  1246. break;
  1247. }
  1248. // Edit a user account, may involve changing email or administrator permissions
  1249. var chguser = parent.users[edituserid];
  1250. change = 0;
  1251. if (chguser) {
  1252. // If the target user is admin and we are not admin, no changes can be made.
  1253. if ((chguser.siteadmin === SITERIGHT_ADMIN) && (user.siteadmin != SITERIGHT_ADMIN)) return;
  1254. // Can only perform this operation on other users of our group.
  1255. if (user.siteadmin != SITERIGHT_ADMIN) {
  1256. if ((user.groups != null) && (user.groups.length > 0) && ((chguser.groups == null) || (findOne(chguser.groups, user.groups) == false))) return;
  1257. }
  1258. // Fetch and validate the user domain
  1259. var edituserdomainid = edituserid.split('/')[1];
  1260. if ((obj.crossDomain !== true) && (edituserdomainid != domain.id)) break;
  1261. var edituserdomain = parent.parent.config.domains[edituserdomainid];
  1262. if (edituserdomain == null) break;
  1263. // Validate and change email
  1264. if (edituserdomain.usernameisemail !== true) {
  1265. if (common.validateString(command.email, 0, 1024) && (chguser.email != command.email)) {
  1266. if (command.email == '') { command.emailVerified = false; delete chguser.email; } else { chguser.email = command.email.toLowerCase(); }
  1267. change = 1;
  1268. }
  1269. }
  1270. // Validate and change real name
  1271. if (common.validateString(command.realname, 0, 256) && (chguser.realname != command.realname)) {
  1272. if (command.realname == '') { delete chguser.realname; } else { chguser.realname = command.realname; }
  1273. change = 1;
  1274. }
  1275. // Make changes
  1276. if ((command.emailVerified === true || command.emailVerified === false) && (chguser.emailVerified != command.emailVerified)) { chguser.emailVerified = command.emailVerified; change = 1; }
  1277. if ((common.validateInt(command.quota, 0) || command.quota == null) && (command.quota != chguser.quota)) { chguser.quota = command.quota; if (chguser.quota == null) { delete chguser.quota; } change = 1; }
  1278. if (command.resetNextLogin === true) { chguser.passchange = -1; }
  1279. if ((command.consent != null) && (typeof command.consent == 'number')) { if (command.consent == 0) { delete chguser.consent; } else { chguser.consent = command.consent; } change = 1; }
  1280. if ((command.phone != null) && (typeof command.phone == 'string') && ((command.phone == '') || isPhoneNumber(command.phone))) { if (command.phone == '') { delete chguser.phone; } else { chguser.phone = command.phone; } change = 1; }
  1281. if ((command.msghandle != null) && (typeof command.msghandle == 'string')) {
  1282. if (command.msghandle.startsWith('callmebot:http')) { const h = parent.parent.msgserver.callmebotUrlToHandle(command.msghandle.substring(10)); if (h) { command.msghandle = h; } else { command.msghandle = ''; } }
  1283. if (command.msghandle == '') { delete chguser.msghandle; } else { chguser.msghandle = command.msghandle; }
  1284. change = 1;
  1285. }
  1286. if ((command.flags != null) && (typeof command.flags == 'number')) {
  1287. // Flags: 1 = Account Image, 2 = Session Recording
  1288. if ((command.flags == 0) && (chguser.flags != null)) { delete chguser.flags; change = 1; } else { if (command.flags !== chguser.flags) { chguser.flags = command.flags; change = 1; } }
  1289. }
  1290. if ((command.removeRights != null) && (typeof command.removeRights == 'number')) {
  1291. if (command.removeRights == 0) {
  1292. if (chguser.removeRights != null) { delete chguser.removeRights; change = 1; }
  1293. } else {
  1294. if (command.removeRights !== chguser.removeRights) { chguser.removeRights = command.removeRights; change = 1; }
  1295. }
  1296. }
  1297. // Site admins can change any server rights, user managers can only change AccountLock, NoMeshCmd and NoNewGroups
  1298. if (common.validateInt(command.siteadmin) && (chguser._id !== user._id) && (chguser.siteadmin != command.siteadmin)) { // We can't change our own siteadmin permissions.
  1299. var chgusersiteadmin = chguser.siteadmin ? chguser.siteadmin : 0;
  1300. if (user.siteadmin === SITERIGHT_ADMIN) { chguser.siteadmin = command.siteadmin; change = 1; }
  1301. else if (user.siteadmin & 2) {
  1302. var mask = 0xFFFFFF1D; // Mask: 2 (User Mangement) + 32 (Account locked) + 64 (No New Groups) + 128 (No Tools)
  1303. if ((user.siteadmin & 256) != 0) { mask -= 256; } // Mask: Manage User Groups
  1304. if ((user.siteadmin & 512) != 0) { mask -= 512; } // Mask: Manage Recordings
  1305. if (((chgusersiteadmin ^ command.siteadmin) & mask) == 0) { chguser.siteadmin = command.siteadmin; change = 1; }
  1306. }
  1307. }
  1308. // When sending a notification about a group change, we need to send to all the previous and new groups.
  1309. var allTargetGroups = chguser.groups;
  1310. if ((Array.isArray(command.groups)) && ((user._id != command.id) || (user.siteadmin === SITERIGHT_ADMIN))) {
  1311. if (command.groups.length == 0) {
  1312. // Remove the user groups
  1313. if (chguser.groups != null) { delete chguser.groups; change = 1; }
  1314. } else {
  1315. // Arrange the user groups
  1316. var groups2 = [];
  1317. for (var i in command.groups) {
  1318. if (typeof command.groups[i] == 'string') {
  1319. var gname = command.groups[i].trim().toLowerCase();
  1320. if ((gname.length > 0) && (gname.length <= 64) && (groups2.indexOf(gname) == -1)) { groups2.push(gname); }
  1321. }
  1322. }
  1323. groups2.sort();
  1324. // Set the user groups (Realms)
  1325. if (chguser.groups != groups2) { chguser.groups = groups2; change = 1; }
  1326. // Add any missing groups in the target list
  1327. if (allTargetGroups == null) { allTargetGroups = []; }
  1328. for (var i in groups2) { if (allTargetGroups.indexOf(i) == -1) { allTargetGroups.push(i); } }
  1329. }
  1330. }
  1331. if (change == 1) {
  1332. // Update the user
  1333. db.SetUser(chguser);
  1334. parent.parent.DispatchEvent([chguser._id], obj, 'resubscribe');
  1335. var targets = ['*', 'server-users', user._id, chguser._id];
  1336. if (allTargetGroups) { for (var i in allTargetGroups) { targets.push('server-users:' + i); } }
  1337. var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(chguser), action: 'accountchange', msgid: 66, msgArgs: [chguser.name], msg: 'Account changed: ' + chguser.name, domain: edituserdomain.id };
  1338. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  1339. parent.parent.DispatchEvent(targets, obj, event);
  1340. }
  1341. if ((chguser.siteadmin) && (chguser.siteadmin !== SITERIGHT_ADMIN) && (chguser.siteadmin & 32)) {
  1342. // If the user is locked out of this account, disconnect now
  1343. parent.parent.DispatchEvent([chguser._id], obj, 'close'); // Disconnect all this user's sessions
  1344. }
  1345. }
  1346. // OK Response
  1347. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'edituser', responseid: command.responseid, result: 'ok' })); } catch (ex) { } }
  1348. break;
  1349. }
  1350. case 'usergroups':
  1351. {
  1352. // Return only groups in the same administrative domain
  1353. if ((user.siteadmin & SITERIGHT_USERGROUPS) == 0) {
  1354. // We are not user group administrator, return a list with limited data for our domain.
  1355. var groups = {}, groupCount = 0;
  1356. for (var i in parent.userGroups) { if (parent.userGroups[i].domain == domain.id) { groupCount++; groups[i] = { name: parent.userGroups[i].name }; } }
  1357. try { ws.send(JSON.stringify({ action: 'usergroups', ugroups: groupCount ? groups : null, tag: command.tag })); } catch (ex) { }
  1358. } else {
  1359. // We are user group administrator, return a full user group list for our domain.
  1360. var groups = {}, groupCount = 0;
  1361. for (var i in parent.userGroups) { if ((obj.crossDomain == true) || (parent.userGroups[i].domain == domain.id)) { groupCount++; groups[i] = parent.userGroups[i]; } }
  1362. try { ws.send(JSON.stringify({ action: 'usergroups', ugroups: groupCount ? groups : null, tag: command.tag })); } catch (ex) { }
  1363. }
  1364. break;
  1365. }
  1366. case 'createusergroup':
  1367. {
  1368. var ugrpdomain, err = null;
  1369. try {
  1370. // Check if we are in a mode that does not allow manual user group creation
  1371. if (
  1372. (typeof domain.authstrategies == 'object') &&
  1373. (typeof domain.authstrategies['oidc'] == 'object') &&
  1374. (typeof domain.authstrategies['oidc'].groups == 'object') &&
  1375. ((domain.authstrategies['oidc'].groups.sync == true) || ((typeof domain.authstrategies['oidc'].groups.sync == 'object') && (domain.authstrategies['oidc'].groups.sync.enabled == true)))
  1376. ) {
  1377. err = "Not allowed in OIDC mode with user group sync.";
  1378. }
  1379. // Check if we have new group restriction
  1380. if ((user.siteadmin & SITERIGHT_USERGROUPS) == 0) { err = "Permission denied"; }
  1381. // Create user group validation
  1382. else if (common.validateString(command.name, 1, 64) == false) { err = "Invalid group name"; } // User group name is between 1 and 64 characters
  1383. else if ((command.desc != null) && (common.validateString(command.desc, 0, 1024) == false)) { err = "Invalid group description"; } // User group description is between 0 and 1024 characters
  1384. // If we are cloning from an existing user group, check that.
  1385. if (command.clone) {
  1386. if (common.validateString(command.clone, 1, 256) == false) { err = "Invalid clone groupid"; }
  1387. else {
  1388. var clonesplit = command.clone.split('/');
  1389. if ((clonesplit.length != 3) || (clonesplit[0] != 'ugrp') || ((command.domain == null) && (clonesplit[1] != domain.id))) { err = "Invalid clone groupid"; }
  1390. else if (parent.userGroups[command.clone] == null) { err = "Invalid clone groupid"; }
  1391. }
  1392. if (err == null) {
  1393. // Get new user group domain
  1394. ugrpdomain = parent.parent.config.domains[clonesplit[1]];
  1395. if (ugrpdomain == null) { err = "Invalid domain"; }
  1396. }
  1397. } else {
  1398. // Get new user group domain
  1399. ugrpdomain = domain;
  1400. if ((obj.crossDomain === true) && (command.domain != null)) { ugrpdomain = parent.parent.config.domains[command.domain]; }
  1401. if (ugrpdomain == null) { err = "Invalid domain"; }
  1402. }
  1403. // In some situations, we need a verified email address to create a device group.
  1404. if ((err == null) && (domain.mailserver != null) && (ugrpdomain.auth != 'sspi') && (ugrpdomain.auth != 'ldap') && (user.emailVerified !== true) && (user.siteadmin != SITERIGHT_ADMIN)) { err = "Email verification required"; } // User must verify it's email first.
  1405. } catch (ex) { err = "Validation exception: " + ex; }
  1406. // Handle any errors
  1407. if (err != null) {
  1408. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'createusergroup', responseid: command.responseid, result: err })); } catch (ex) { } }
  1409. break;
  1410. }
  1411. // We only create Agent-less Intel AMT mesh (Type1), or Agent mesh (Type2)
  1412. parent.crypto.randomBytes(48, function (err, buf) {
  1413. // Create new device group identifier
  1414. var ugrpid = 'ugrp/' + ugrpdomain.id + '/' + buf.toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
  1415. // Create the new device group
  1416. var ugrp = { type: 'ugrp', _id: ugrpid, name: command.name, desc: command.desc, domain: ugrpdomain.id, links: {} };
  1417. // Clone the existing group if required
  1418. var pendingDispatchEvents = [];
  1419. if (command.clone != null) {
  1420. var cgroup = parent.userGroups[command.clone];
  1421. if (cgroup.links) {
  1422. for (var i in cgroup.links) {
  1423. if (i.startsWith('user/')) {
  1424. var xuser = parent.users[i];
  1425. if ((xuser != null) && (xuser.links != null)) {
  1426. ugrp.links[i] = { rights: cgroup.links[i].rights };
  1427. xuser.links[ugrpid] = { rights: cgroup.links[i].rights };
  1428. db.SetUser(xuser);
  1429. parent.parent.DispatchEvent([xuser._id], obj, 'resubscribe');
  1430. // Notify user change
  1431. var targets = ['*', 'server-users', user._id, xuser._id];
  1432. var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(xuser), action: 'accountchange', msgid: 67, msgArgs: [xuser.name], msg: 'User group membership changed: ' + xuser.name, domain: ugrpdomain.id };
  1433. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  1434. //parent.parent.DispatchEvent(targets, obj, event);
  1435. pendingDispatchEvents.push([targets, obj, event]);
  1436. }
  1437. } else if (i.startsWith('mesh/')) {
  1438. var xmesh = parent.meshes[i];
  1439. if (xmesh && xmesh.links) {
  1440. ugrp.links[i] = { rights: cgroup.links[i].rights };
  1441. xmesh.links[ugrpid] = { rights: cgroup.links[i].rights };
  1442. db.Set(xmesh);
  1443. // Notify mesh change
  1444. var event = { etype: 'mesh', userid: user._id, username: user.name, meshid: xmesh._id, name: xmesh.name, mtype: xmesh.mtype, desc: xmesh.desc, action: 'meshchange', links: xmesh.links, msgid: 68, msgArgs: [ugrp.name, xmesh.name], msg: 'Added user group ' + ugrp.name + ' to device group ' + xmesh.name, domain: ugrpdomain.id, invite: xmesh.invite };
  1445. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
  1446. //parent.parent.DispatchEvent(['*', xmesh._id, user._id], obj, event);
  1447. pendingDispatchEvents.push([parent.CreateMeshDispatchTargets(xmesh, [user._id]), obj, event]);
  1448. }
  1449. }
  1450. }
  1451. }
  1452. }
  1453. // Save the new group
  1454. db.Set(ugrp);
  1455. if (db.changeStream == false) { parent.userGroups[ugrpid] = ugrp; }
  1456. // Event the user group creation
  1457. var event = { etype: 'ugrp', userid: user._id, username: user.name, ugrpid: ugrpid, name: ugrp.name, desc: ugrp.desc, action: 'createusergroup', links: ugrp.links, msgid: 69, msgArgv: [ugrp.name], msg: 'User group created: ' + ugrp.name, ugrpdomain: domain.id };
  1458. parent.parent.DispatchEvent(['*', ugrpid, user._id], obj, event); // Even if DB change stream is active, this event must be acted upon.
  1459. // Event any pending events, these must be sent out after the group creation event is dispatched.
  1460. for (var i in pendingDispatchEvents) { var ev = pendingDispatchEvents[i]; parent.parent.DispatchEvent(ev[0], ev[1], ev[2]); }
  1461. // Log in the auth log
  1462. if (parent.parent.authlog) { parent.parent.authLog('https', 'User ' + user.name + ' created user group ' + ugrp.name); }
  1463. try { ws.send(JSON.stringify({ action: 'createusergroup', responseid: command.responseid, result: 'ok', ugrpid: ugrpid, links: ugrp.links })); } catch (ex) { }
  1464. });
  1465. break;
  1466. }
  1467. case 'deleteusergroup':
  1468. {
  1469. var err = null;
  1470. if ((user.siteadmin & SITERIGHT_USERGROUPS) == 0) { err = "Permission denied"; }
  1471. // Change the name or description of a user group
  1472. else if (common.validateString(command.ugrpid, 1, 1024) == false) { err = "Invalid group id"; } // Check the user group id
  1473. else {
  1474. var ugroupidsplit = command.ugrpid.split('/');
  1475. if ((ugroupidsplit.length != 3) || (ugroupidsplit[0] != 'ugrp') || ((obj.crossDomain !== true) && (ugroupidsplit[1] != domain.id))) { err = "Invalid domain id"; }
  1476. }
  1477. // Get the domain
  1478. var delGroupDomain;
  1479. if (ugroupidsplit != null) {
  1480. delGroupDomain = parent.parent.config.domains[ugroupidsplit[1]];
  1481. if (delGroupDomain == null) { err = "Invalid domain id"; }
  1482. }
  1483. // Handle any errors
  1484. if (err != null) {
  1485. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'deleteusergroup', responseid: command.responseid, result: err })); } catch (ex) { } }
  1486. break;
  1487. }
  1488. db.Get(command.ugrpid, function (err, groups) {
  1489. if ((err != null) || (groups.length != 1)) {
  1490. try { ws.send(JSON.stringify({ action: 'deleteusergroup', responseid: command.responseid, result: 'Unknown device group' })); } catch (ex) { }
  1491. return;
  1492. }
  1493. var group = groups[0];
  1494. // If this user group is an externally managed user group, it can't be deleted unless there are no users in it.
  1495. if (group.membershipType != null) {
  1496. var userCount = 0;
  1497. if (group.links != null) { for (var i in group.links) { if (i.startsWith('user/')) { userCount++; } } }
  1498. if (userCount > 0) return;
  1499. }
  1500. // Unlink any user and meshes that have a link to this group
  1501. if (group.links) {
  1502. for (var i in group.links) {
  1503. if (i.startsWith('user/')) {
  1504. var xuser = parent.users[i];
  1505. if ((xuser != null) && (xuser.links != null)) {
  1506. delete xuser.links[group._id];
  1507. db.SetUser(xuser);
  1508. parent.parent.DispatchEvent([xuser._id], obj, 'resubscribe');
  1509. // Notify user change
  1510. var targets = ['*', 'server-users', user._id, xuser._id];
  1511. var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(xuser), action: 'accountchange', msgid: 67, msgArgs: [xuser.name], msg: 'User group membership changed: ' + xuser.name, delGroupDomain: domain.id };
  1512. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  1513. parent.parent.DispatchEvent(targets, obj, event);
  1514. }
  1515. } else if (i.startsWith('mesh/')) {
  1516. var xmesh = parent.meshes[i];
  1517. if (xmesh && xmesh.links) {
  1518. delete xmesh.links[group._id];
  1519. db.Set(xmesh);
  1520. // Notify mesh change
  1521. var event = { etype: 'mesh', userid: user._id, username: user.name, meshid: xmesh._id, name: xmesh.name, mtype: xmesh.mtype, desc: xmesh.desc, action: 'meshchange', links: xmesh.links, msgid: 70, msgArgs: [group.name, xmesh.name], msg: 'Removed user group ' + group.name + ' from device group ' + xmesh.name, domain: delGroupDomain.id };
  1522. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
  1523. parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(xmesh, [user._id]), obj, event);
  1524. }
  1525. }
  1526. }
  1527. }
  1528. // Remove the user group from the database
  1529. db.Remove(group._id);
  1530. if (db.changeStream == false) { delete parent.userGroups[group._id]; }
  1531. // Event the user group being removed
  1532. var event = { etype: 'ugrp', userid: user._id, username: user.name, ugrpid: group._id, action: 'deleteusergroup', msg: change, domain: delGroupDomain.id };
  1533. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
  1534. parent.parent.DispatchEvent(['*', group._id, user._id], obj, event);
  1535. // Log in the auth log
  1536. if (parent.parent.authlog) { parent.parent.authLog('https', 'User ' + user.name + ' deleted user group ' + group.name); }
  1537. try { ws.send(JSON.stringify({ action: 'deleteusergroup', responseid: command.responseid, result: 'ok', ugrpid: group._id })); } catch (ex) { }
  1538. });
  1539. break;
  1540. }
  1541. case 'editusergroup':
  1542. {
  1543. if ((user.siteadmin & SITERIGHT_USERGROUPS) == 0) { return; }
  1544. // Change the name or description of a user group
  1545. if (common.validateString(command.ugrpid, 1, 1024) == false) break; // Check the user group id
  1546. var ugroupidsplit = command.ugrpid.split('/');
  1547. if ((ugroupidsplit.length != 3) || (ugroupidsplit[0] != 'ugrp') || (ugroupidsplit[1] != domain.id)) break;
  1548. // Get the user group
  1549. change = '';
  1550. var group = parent.userGroups[command.ugrpid];
  1551. if (group != null) {
  1552. // If this user group is an externally managed user group, the name of the user group can't be edited
  1553. if ((group.membershipType == null) && (common.validateString(command.name, 1, 64) == true) && (command.name != group.name)) { change = 'User group name changed from "' + group.name + '" to "' + command.name + '"'; group.name = command.name; }
  1554. if ((common.validateString(command.desc, 0, 1024) == true) && (command.desc != group.desc)) { if (change != '') change += ' and description changed'; else change += 'User group "' + group.name + '" description changed'; group.desc = command.desc; }
  1555. if ((typeof command.consent == 'number') && (command.consent != group.consent)) { if (change != '') change += ' and consent changed'; else change += 'User group "' + group.name + '" consent changed'; group.consent = command.consent; }
  1556. if ((command.flags != null) && (typeof command.flags == 'number')) {
  1557. // Flags: 2 = Session Recording
  1558. if ((command.flags == 0) && (group.flags != null)) { delete group.flags; } else { if (command.flags !== group.flags) { group.flags = command.flags; } }
  1559. if (change == '') { change = 'User group features changed.'; }
  1560. }
  1561. if (change != '') {
  1562. db.Set(group);
  1563. var event = { etype: 'ugrp', userid: user._id, username: user.name, ugrpid: group._id, name: group.name, desc: group.desc, consent: ((group.consent == null) ? 0 : group.consent), action: 'usergroupchange', links: group.links, flags: group.flags, msg: change, domain: domain.id };
  1564. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
  1565. parent.parent.DispatchEvent(['*', group._id, user._id], obj, event);
  1566. }
  1567. }
  1568. break;
  1569. }
  1570. case 'changemeshnotify':
  1571. {
  1572. if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return; // If this account is settings locked, return here.
  1573. // 2 = WebPage device connections
  1574. // 4 = WebPage device disconnections
  1575. // 8 = WebPage device desktop and serial events
  1576. // 16 = Email device connections
  1577. // 32 = Email device disconnections
  1578. // 64 = Email device help request
  1579. // 128 = Messaging device connections
  1580. // 256 = Messaging device disconnections
  1581. // 512 = Messaging device help request
  1582. var err = null;
  1583. try {
  1584. // Change the current user's notification flags for a meshid
  1585. if (common.validateString(command.meshid, 8, 134) == false) { err = 'Invalid group identifier'; } // Check the meshid
  1586. else if (command.meshid.indexOf('/') == -1) { command.meshid = 'mesh/' + domain.id + '/' + command.meshid; }
  1587. if (common.validateInt(command.notify) == false) { err = 'Invalid notification flags'; }
  1588. if (parent.IsMeshViewable(user, command.meshid) == false) err = 'Access denied';
  1589. } catch (ex) { err = 'Validation exception: ' + ex; }
  1590. // Handle any errors
  1591. if (err != null) { if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'changemeshnotify', responseid: command.responseid, result: err })); } catch (ex) { } } break; }
  1592. // Change the device group notification
  1593. if (user.links == null) { user.links = {}; }
  1594. if (user.links[command.meshid]) {
  1595. // The user has direct rights for this device group
  1596. if (command.notify == 0) {
  1597. delete user.links[command.meshid].notify;
  1598. } else {
  1599. user.links[command.meshid].notify = command.notify;
  1600. }
  1601. }
  1602. // Change user notification if needed, this is needed then a user has device rights thru a user group
  1603. if ((command.notify == 0) && (user.notify != null) && (user.notify[command.meshid] != null)) { delete user.notify[command.meshid]; }
  1604. if ((command.notify != 0) && (user.links[command.meshid] == null)) { if (user.notify == null) { user.notify = {} } user.notify[command.meshid] = command.notify; }
  1605. // Save the user
  1606. parent.db.SetUser(user);
  1607. // Notify change
  1608. var targets = ['*', 'server-users', user._id];
  1609. if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
  1610. var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msgid: 73, msg: 'Device group notification changed', domain: domain.id };
  1611. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  1612. parent.parent.DispatchEvent(targets, obj, event);
  1613. break;
  1614. }
  1615. case 'changeusernotify':
  1616. {
  1617. if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return; // If this account is settings locked, return here.
  1618. // 2 = WebPage device connections
  1619. // 4 = WebPage device disconnections
  1620. // 8 = WebPage device desktop and serial events
  1621. // 16 = Email device connections
  1622. // 32 = Email device disconnections
  1623. // 64 = Email device help request
  1624. // 128 = Messaging device connections
  1625. // 256 = Messaging device disconnections
  1626. // 512 = Messaging device help request
  1627. var err = null;
  1628. try {
  1629. // Change the current user's notification flags for a meshid
  1630. if (common.validateString(command.nodeid, 1, 1024) == false) { err = 'Invalid device identifier'; } // Check the meshid
  1631. else if (command.nodeid.indexOf('/') == -1) { command.nodeid = 'node/' + domain.id + '/' + command.nodeid; }
  1632. if (common.validateInt(command.notify) == false) { err = 'Invalid notification flags'; }
  1633. //if (parent.IsMeshViewable(user, command.nodeid) == false) err = 'Access denied';
  1634. } catch (ex) { err = 'Validation exception: ' + ex; }
  1635. // Handle any errors
  1636. if (err != null) { if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'changeusernotify', responseid: command.responseid, result: err })); } catch (ex) { } } break; }
  1637. // Check if nothing has changed
  1638. if ((user.notify == null) && (command.notify == 0)) return;
  1639. if ((user.notify != null) && (user.notify[command.nodeid] == command.notify)) return;
  1640. // Change the notification
  1641. if (user.notify == null) { user.notify = {}; }
  1642. if (command.notify == 0) { delete user.notify[command.nodeid]; } else { user.notify[command.nodeid] = command.notify; }
  1643. if (Object.keys(user.notify).length == 0) { delete user.notify; }
  1644. // Save the user
  1645. parent.db.SetUser(user);
  1646. // Notify change
  1647. var targets = ['*', 'server-users', user._id];
  1648. if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
  1649. var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msgid: 130, msg: 'User notifications changed', domain: domain.id };
  1650. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  1651. parent.parent.DispatchEvent(targets, obj, event);
  1652. break;
  1653. }
  1654. case 'changepassword':
  1655. {
  1656. // Do not allow this command when logged in using a login token
  1657. if (req.session.loginToken != null) break;
  1658. // If this account is settings locked, return here.
  1659. if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return;
  1660. // Do not allow change password if sspi or ldap
  1661. if ((domain.auth == 'sspi') || (domain.auth == 'ldap')) return;
  1662. // Change our own password
  1663. if (common.validateString(command.oldpass, 1, 256) == false) break;
  1664. if (common.validateString(command.newpass, 1, 256) == false) break;
  1665. if ((command.hint != null) && (common.validateString(command.hint, 0, 256) == false)) break;
  1666. if (common.checkPasswordRequirements(command.newpass, domain.passwordrequirements) == false) break; // Password does not meet requirements
  1667. // Start by checking the old password
  1668. parent.checkUserPassword(domain, user, command.oldpass, function (result) {
  1669. if (result == true) {
  1670. parent.checkOldUserPasswords(domain, user, command.newpass, function (result) {
  1671. if (result == 1) {
  1672. // Send user notification of error
  1673. displayNotificationMessage("Error, unable to change to previously used password.", "Account Settings", 'ServerNotify', 4, 17);
  1674. } else if (result == 2) {
  1675. // Send user notification of error
  1676. displayNotificationMessage("Error, unable to change to commonly used password.", "Account Settings", 'ServerNotify', 4, 18);
  1677. } else {
  1678. // Update the password
  1679. require('./pass').hash(command.newpass, function (err, salt, hash, tag) {
  1680. if (err) {
  1681. // Send user notification of error
  1682. displayNotificationMessage("Error, password not changed.", "Account Settings", 'ServerNotify', 4, 19);
  1683. } else {
  1684. const nowSeconds = Math.floor(Date.now() / 1000);
  1685. // Change the password
  1686. if (domain.passwordrequirements != null) {
  1687. // Save password hint if this feature is enabled
  1688. if ((domain.passwordrequirements.hint === true) && (command.hint != null)) { var hint = command.hint; if (hint.length > 250) { hint = hint.substring(0, 250); } user.passhint = hint; } else { delete user.passhint; }
  1689. // Save previous password if this feature is enabled
  1690. if ((typeof domain.passwordrequirements.oldpasswordban == 'number') && (domain.passwordrequirements.oldpasswordban > 0)) {
  1691. if (user.oldpasswords == null) { user.oldpasswords = []; }
  1692. user.oldpasswords.push({ salt: user.salt, hash: user.hash, start: user.passchange, end: nowSeconds });
  1693. const extraOldPasswords = user.oldpasswords.length - domain.passwordrequirements.oldpasswordban;
  1694. if (extraOldPasswords > 0) { user.oldpasswords.splice(0, extraOldPasswords); }
  1695. }
  1696. }
  1697. user.salt = salt;
  1698. user.hash = hash;
  1699. user.passchange = nowSeconds;
  1700. delete user.passtype;
  1701. db.SetUser(user);
  1702. var targets = ['*', 'server-users'];
  1703. if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
  1704. var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msgid: 74, msgArgs: [user.name], msg: 'Account password changed: ' + user.name, domain: domain.id };
  1705. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  1706. parent.parent.DispatchEvent(targets, obj, event);
  1707. // Send user notification of password change
  1708. displayNotificationMessage("Password changed.", "Account Settings", 'ServerNotify', 4, 20);
  1709. // Log in the auth log
  1710. if (parent.parent.authlog) { parent.parent.authLog('https', 'User ' + user.name + ' changed this password'); }
  1711. }
  1712. }, 0);
  1713. }
  1714. });
  1715. } else {
  1716. // Send user notification of error
  1717. displayNotificationMessage("Current password not correct.", "Account Settings", 'ServerNotify', 4, 21);
  1718. }
  1719. });
  1720. break;
  1721. }
  1722. case 'changeuserpass':
  1723. {
  1724. // Change a user's password
  1725. if ((user.siteadmin & 2) == 0) break;
  1726. if (common.validateString(command.userid, 1, 256) == false) break;
  1727. if (common.validateString(command.pass, 0, 256) == false) break;
  1728. if ((command.hint != null) && (common.validateString(command.hint, 0, 256) == false)) break;
  1729. if (typeof command.removeMultiFactor != 'boolean') break;
  1730. if ((command.pass != '') && (common.checkPasswordRequirements(command.pass, domain.passwordrequirements) == false)) break; // Password does not meet requirements
  1731. var chguser = parent.users[command.userid];
  1732. if (chguser) {
  1733. // If we are not full administrator, we can't change anything on a different full administrator
  1734. if ((user.siteadmin != SITERIGHT_ADMIN) & (chguser.siteadmin === SITERIGHT_ADMIN)) break;
  1735. // Can only perform this operation on other users of our group.
  1736. if ((user.groups != null) && (user.groups.length > 0) && ((chguser.groups == null) || (findOne(chguser.groups, user.groups) == false))) break;
  1737. // Compute the password hash & save it
  1738. require('./pass').hash(command.pass, function (err, salt, hash, tag) {
  1739. if (!err) {
  1740. if (command.pass != '') { chguser.salt = salt; chguser.hash = hash; }
  1741. if ((domain.passwordrequirements != null) && (domain.passwordrequirements.hint === true) && (command.hint != null)) {
  1742. var hint = command.hint;
  1743. if (hint.length > 250) { hint = hint.substring(0, 250); }
  1744. chguser.passhint = hint;
  1745. }
  1746. if (command.resetNextLogin === true) { chguser.passchange = -1; } else { chguser.passchange = Math.floor(Date.now() / 1000); }
  1747. delete chguser.passtype; // Remove the password type if one was present.
  1748. if (command.removeMultiFactor === true) {
  1749. delete chguser.otpkeys; // One time backup codes
  1750. delete chguser.otpsecret; // OTP Google Authenticator
  1751. delete chguser.otphkeys; // FIDO keys
  1752. delete chguser.otpekey; // Email 2FA
  1753. delete chguser.phone; // SMS 2FA
  1754. delete chguser.otpdev; // Push notification 2FA
  1755. delete chguser.otpduo; // Duo 2FA
  1756. }
  1757. db.SetUser(chguser);
  1758. var targets = ['*', 'server-users', user._id, chguser._id];
  1759. if (chguser.groups) { for (var i in chguser.groups) { targets.push('server-users:' + i); } }
  1760. var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(chguser), action: 'accountchange', msgid: 75, msg: 'Changed account credentials', domain: domain.id };
  1761. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  1762. parent.parent.DispatchEvent(targets, obj, event);
  1763. // Log in the auth log
  1764. if (parent.parent.authlog) { parent.parent.authLog('https', 'User ' + user.name + ' changed account password of user ' + chguser.name); }
  1765. } else {
  1766. // Report that the password change failed
  1767. // TODO
  1768. }
  1769. }, 0);
  1770. }
  1771. break;
  1772. }
  1773. case 'notifyuser':
  1774. {
  1775. // Send a notification message to a user
  1776. if ((user.siteadmin & 2) == 0) break;
  1777. if (common.validateString(command.userid, 1, 2048) == false) break;
  1778. if (common.validateString(command.msg, 1, 4096) == false) break;
  1779. // Can only perform this operation on other users of our group.
  1780. var chguser = parent.users[command.userid];
  1781. if (chguser == null) break; // This user does not exists
  1782. if ((user.groups != null) && (user.groups.length > 0) && ((chguser.groups == null) || (findOne(chguser.groups, user.groups) == false))) break;
  1783. // Create the notification message
  1784. var notification = { action: 'msg', type: 'notify', id: Math.random(), value: command.msg, title: user.name, icon: 8, userid: user._id, username: user.name };
  1785. if (typeof command.url == 'string') { notification.url = command.url; }
  1786. if ((typeof command.maxtime == 'number') && (command.maxtime > 0)) { notification.maxtime = command.maxtime; }
  1787. if (command.msgid == 11) { notification.value = "Chat Request, Click here to accept."; notification.msgid = 11; } // Chat request
  1788. // Get the list of sessions for this user
  1789. var sessions = parent.wssessions[command.userid];
  1790. if (sessions != null) { for (i in sessions) { try { sessions[i].send(JSON.stringify(notification)); } catch (ex) { } } }
  1791. if (parent.parent.multiServer != null) {
  1792. // TODO: Add multi-server support
  1793. }
  1794. // If the user is not connected, use web push if available.
  1795. if ((parent.wssessions[chguser._id] == null) && (parent.sessionsCount[chguser._id] == null)) {
  1796. // Perform web push notification
  1797. var payload = { body: command.msg, icon: 8 }; // Icon 8 is the user icon.
  1798. if (command.url) { payload.url = command.url; }
  1799. if (domain.title != null) { payload.title = domain.title; } else { payload.title = "MeshCentral"; }
  1800. payload.title += ' - ' + user.name;
  1801. parent.performWebPush(domain, chguser, payload, { TTL: 60 }); // For now, 1 minute TTL
  1802. }
  1803. break;
  1804. }
  1805. case 'meshmessenger':
  1806. {
  1807. // Setup a user-to-user session
  1808. if (common.validateString(command.userid, 1, 2048)) {
  1809. // Send a notification message to a user
  1810. if ((user.siteadmin & 2) == 0) break;
  1811. // Can only perform this operation on other users of our group.
  1812. var chguser = parent.users[command.userid];
  1813. if (chguser == null) break; // This user does not exists
  1814. if ((user.groups != null) && (user.groups.length > 0) && ((chguser.groups == null) || (findOne(chguser.groups, user.groups) == false))) break;
  1815. // Create the notification message
  1816. var notification = {
  1817. 'action': 'msg', 'type': 'notify', id: Math.random(), 'value': "Chat Request, Click here to accept.", 'title': user.name, 'userid': user._id, 'username': user.name, 'tag': 'meshmessenger/' + encodeURIComponent(command.userid) + '/' + encodeURIComponent(user._id), msgid: 11
  1818. };
  1819. // Get the list of sessions for this user
  1820. var sessions = parent.wssessions[command.userid];
  1821. if (sessions != null) { for (i in sessions) { try { sessions[i].send(JSON.stringify(notification)); } catch (ex) { } } }
  1822. if (parent.parent.multiServer != null) {
  1823. // TODO: Add multi-server support
  1824. }
  1825. // If the user is not connected, use web push if available.
  1826. if ((parent.wssessions[chguser._id] == null) && (parent.sessionsCount[chguser._id] == null)) {
  1827. // Create the server url
  1828. var httpsPort = ((args.aliasport == null) ? args.port : args.aliasport); // Use HTTPS alias port is specified
  1829. var xdomain = (domain.dns == null) ? domain.id : '';
  1830. if (xdomain != '') xdomain += "/";
  1831. var url = "https://" + parent.getWebServerName(domain, req) + ":" + httpsPort + "/" + xdomain + "messenger?id=meshmessenger/" + encodeURIComponent(command.userid) + "/" + encodeURIComponent(user._id);
  1832. // Perform web push notification
  1833. var payload = { body: "Chat Request, Click here to accept.", icon: 8, url: url }; // Icon 8 is the user icon.
  1834. if (domain.title != null) { payload.title = domain.title; } else { payload.title = "MeshCentral"; }
  1835. payload.title += ' - ' + user.name;
  1836. parent.performWebPush(domain, chguser, payload, { TTL: 60 }); // For now, 1 minute TTL
  1837. }
  1838. return;
  1839. }
  1840. // User-to-device chat is not support in LAN-only mode yet. We need the agent to replace the IP address of the server??
  1841. if (args.lanonly == true) { return; }
  1842. // Setup a user-to-node session
  1843. parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
  1844. // Check if this user has rights to do this
  1845. if ((rights & MESHRIGHT_CHATNOTIFY) == 0) return;
  1846. // Create the server url
  1847. var httpsPort = ((args.aliasport == null) ? args.port : args.aliasport); // Use HTTPS alias port is specified
  1848. var xdomain = (domain.dns == null) ? domain.id : '';
  1849. if (xdomain != '') xdomain += "/";
  1850. var url = "https://" + parent.getWebServerName(domain, req) + ":" + httpsPort + "/" + xdomain + "messenger?id=meshmessenger/" + encodeURIComponent(command.nodeid) + "/" + encodeURIComponent(user._id);
  1851. // Open a web page on the remote device
  1852. routeCommandToNode({ 'action': 'openUrl', 'nodeid': command.nodeid, 'userid': user._id, 'username': user.name, 'url': url });
  1853. });
  1854. break;
  1855. }
  1856. case 'createmesh':
  1857. {
  1858. var err = null;
  1859. try {
  1860. // Support for old web pages that sent the meshtype as a string.
  1861. if (typeof command.meshtype == 'string') { command.meshtype = parseInt(command.meshtype); }
  1862. // Check if we have new group restriction
  1863. if ((user.siteadmin != SITERIGHT_ADMIN) && ((user.siteadmin & 64) != 0)) { err = 'Permission denied'; }
  1864. // In some situations, we need a verified email address to create a device group.
  1865. else if ((domain.mailserver != null) && (domain.auth != 'sspi') && (domain.auth != 'ldap') && (user.emailVerified !== true) && (user.siteadmin != SITERIGHT_ADMIN)) { err = 'Email verification required'; } // User must verify it's email first.
  1866. // Create mesh
  1867. else if (common.validateString(command.meshname, 1, 128) == false) { err = 'Invalid group name'; } // Meshname is between 1 and 128 characters
  1868. else if ((command.desc != null) && (common.validateString(command.desc, 0, 1024) == false)) { err = 'Invalid group description'; } // Mesh description is between 0 and 1024 characters
  1869. else if ((command.meshtype < 1) || (command.meshtype > 4)) { err = 'Invalid group type'; } // Device group types are 1 = AMT, 2 = Agent, 3 = Local
  1870. else if (((command.meshtype == 3) || (command.meshtype == 4)) && (parent.args.wanonly == true) && (typeof command.relayid != 'string')) { err = 'Invalid group type'; } // Local device group type wihtout relay is not allowed in WAN mode
  1871. else if (((command.meshtype == 3) || (command.meshtype == 4)) && (parent.args.lanonly == true) && (typeof command.relayid == 'string')) { err = 'Invalid group type'; } // Local device group type with relay is not allowed in WAN mode
  1872. else if ((domain.ipkvm == null) && (command.meshtype == 4)) { err = 'Invalid group type'; } // IP KVM device group type is not allowed unless enabled
  1873. if ((err == null) && (command.meshtype == 4)) {
  1874. if ((command.kvmmodel < 1) || (command.kvmmodel > 2)) { err = 'Invalid KVM model'; }
  1875. else if (common.validateString(command.kvmhost, 1, 128) == false) { err = 'Invalid KVM hostname'; }
  1876. else if (common.validateString(command.kvmuser, 1, 128) == false) { err = 'Invalid KVM username'; }
  1877. else if (common.validateString(command.kvmpass, 1, 128) == false) { err = 'Invalid KVM password'; }
  1878. }
  1879. } catch (ex) { err = 'Validation exception: ' + ex; }
  1880. // Handle any errors
  1881. if (err != null) {
  1882. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'createmesh', responseid: command.responseid, result: err })); } catch (ex) { } }
  1883. break;
  1884. }
  1885. // We only create Agent-less Intel AMT mesh (Type1), or Agent mesh (Type2)
  1886. parent.crypto.randomBytes(48, function (err, buf) {
  1887. // Create new device group identifier
  1888. meshid = 'mesh/' + domain.id + '/' + buf.toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
  1889. // Create the new device group
  1890. var links = {};
  1891. links[user._id] = { name: user.name, rights: 4294967295 };
  1892. mesh = { type: 'mesh', _id: meshid, name: command.meshname, mtype: command.meshtype, desc: command.desc, domain: domain.id, links: links, creation: Date.now(), creatorid: user._id, creatorname: user.name };
  1893. // Add flags and consent if present
  1894. if (typeof command.flags == 'number') { mesh.flags = command.flags; }
  1895. if (typeof command.consent == 'number') { mesh.consent = command.consent; }
  1896. // Add KVM information if needed
  1897. if (command.meshtype == 4) { mesh.kvm = { model: command.kvmmodel, host: command.kvmhost, user: command.kvmuser, pass: command.kvmpass }; }
  1898. // If this is device group that requires a relay device, store that now
  1899. if ((parent.args.lanonly != true) && ((command.meshtype == 3) || (command.meshtype == 4)) && (typeof command.relayid == 'string')) {
  1900. // Check the relay id
  1901. var relayIdSplit = command.relayid.split('/');
  1902. if ((relayIdSplit[0] == 'node') && (relayIdSplit[1] == domain.id)) { mesh.relayid = command.relayid; }
  1903. }
  1904. // Save the new device group
  1905. db.Set(mesh);
  1906. parent.meshes[meshid] = mesh;
  1907. parent.parent.AddEventDispatch([meshid], ws);
  1908. // Change the user to make him administration of the new device group
  1909. if (user.links == null) user.links = {};
  1910. user.links[meshid] = { rights: 4294967295 };
  1911. user.subscriptions = parent.subscribe(user._id, ws);
  1912. db.SetUser(user);
  1913. // Event the user change
  1914. var targets = ['*', 'server-users', user._id];
  1915. if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
  1916. var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', domain: domain.id, nolog: 1 };
  1917. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  1918. parent.parent.DispatchEvent(targets, obj, event);
  1919. // Event the device group creation
  1920. var event = { etype: 'mesh', userid: user._id, username: user.name, meshid: meshid, mtype: command.meshtype, mesh: parent.CloneSafeMesh(mesh), action: 'createmesh', msgid: 76, msgArgs: [command.meshname], msg: 'Device group created: ' + command.meshname, domain: domain.id };
  1921. parent.parent.DispatchEvent(['*', 'server-createmesh', meshid, user._id], obj, event); // Even if DB change stream is active, this event must be acted upon.
  1922. // Log in the auth log
  1923. if (parent.parent.authlog) { parent.parent.authLog('https', 'User ' + user.name + ' created device group ' + mesh.name); }
  1924. try { ws.send(JSON.stringify({ action: 'createmesh', responseid: command.responseid, result: 'ok', meshid: meshid, links: links })); } catch (ex) { }
  1925. // If needed, event that a device is now a device group relay
  1926. if (mesh.relayid != null) {
  1927. // Get the node and the rights for this node
  1928. parent.GetNodeWithRights(domain, user, mesh.relayid, function (node, rights, visible) {
  1929. if (node == null) return;
  1930. var event = { etype: 'node', userid: user._id, username: user.name, action: 'changenode', nodeid: node._id, domain: domain.id, msg: 'Is a relay for ' + mesh.name + '.', msgid: 153, msgArgs: [mesh.name], node: parent.CloneSafeNode(node) };
  1931. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the node. Another event will come.
  1932. parent.parent.DispatchEvent(parent.CreateNodeDispatchTargets(node.meshid, node._id, [user._id]), obj, event);
  1933. });
  1934. }
  1935. });
  1936. break;
  1937. }
  1938. case 'deletemesh':
  1939. {
  1940. // Delete a mesh and all computers within it
  1941. var err = null;
  1942. // Resolve the device group name if needed
  1943. if ((typeof command.meshname == 'string') && (command.meshid == null)) {
  1944. for (var i in parent.meshes) {
  1945. var m = parent.meshes[i];
  1946. if ((m.mtype == 2) && (m.name == command.meshname) && parent.IsMeshViewable(user, m)) {
  1947. if (command.meshid == null) { command.meshid = m._id; } else { err = 'Duplicate device groups found'; }
  1948. }
  1949. }
  1950. }
  1951. // Validate input
  1952. try {
  1953. if (common.validateString(command.meshid, 8, 134) == false) { err = 'Invalid group identifier'; } // Check the meshid
  1954. else if (command.meshid.indexOf('/') == -1) { command.meshid = 'mesh/' + domain.id + '/' + command.meshid; }
  1955. } catch (ex) { err = 'Validation exception: ' + ex; }
  1956. // Handle any errors
  1957. if (err != null) { if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'deletemesh', responseid: command.responseid, result: err })); } catch (ex) { } } break; }
  1958. // Get the device group reference we are going to delete
  1959. var mesh = parent.meshes[command.meshid];
  1960. if (mesh == null) { if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'deletemesh', responseid: command.responseid, result: 'Unknown device group' })); } catch (ex) { } } return; }
  1961. // Check if this user has rights to do this
  1962. var err = null;
  1963. if (parent.GetMeshRights(user, mesh) != MESHRIGHT_ADMIN) { err = 'Access denied'; }
  1964. if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) { err = 'Invalid group'; } // Invalid domain, operation only valid for current domain
  1965. // Handle any errors
  1966. if (err != null) { if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'deletemesh', responseid: command.responseid, result: err })); } catch (ex) { } } return; }
  1967. // Fire the removal event first, because after this, the event will not route
  1968. var event = { etype: 'mesh', userid: user._id, username: user.name, mtype: mesh.mtype, meshid: command.meshid, name: command.meshname, action: 'deletemesh', msgid: 77, msgArgs: [command.meshname], msg: 'Device group deleted: ' + command.meshname, domain: domain.id };
  1969. parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(command.meshid, ['server-deletemesh']), obj, event); // Even if DB change stream is active, this event need to be acted on.
  1970. // Remove all user links to this mesh
  1971. for (var j in mesh.links) {
  1972. if (j.startsWith('user/')) {
  1973. var xuser = parent.users[j];
  1974. if (xuser && xuser.links) {
  1975. delete xuser.links[mesh._id];
  1976. db.SetUser(xuser);
  1977. parent.parent.DispatchEvent([xuser._id], obj, 'resubscribe');
  1978. // Notify user change
  1979. var targets = ['*', 'server-users', user._id, xuser._id];
  1980. var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(xuser), action: 'accountchange', msgid: 78, msgArgs: [xuser.name], msg: 'Device group membership changed: ' + xuser.name, domain: domain.id };
  1981. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  1982. parent.parent.DispatchEvent(targets, obj, event);
  1983. }
  1984. } else if (j.startsWith('ugrp/')) {
  1985. var xgroup = parent.userGroups[j];
  1986. if (xgroup && xgroup.links) {
  1987. delete xgroup.links[mesh._id];
  1988. db.Set(xgroup);
  1989. // Notify user group change
  1990. var targets = ['*', 'server-ugroups', user._id, xgroup._id];
  1991. var event = { etype: 'ugrp', userid: user._id, username: user.name, ugrpid: xgroup._id, name: xgroup.name, desc: xgroup.desc, action: 'usergroupchange', links: xgroup.links, msgid: 79, msgArgs: [xgroup.name], msg: 'User group changed: ' + xgroup.name, domain: domain.id };
  1992. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  1993. parent.parent.DispatchEvent(targets, obj, event);
  1994. }
  1995. }
  1996. }
  1997. // Delete any invitation codes
  1998. delete mesh.invite;
  1999. // Delete all files on the server for this mesh
  2000. try {
  2001. var meshpath = parent.getServerRootFilePath(mesh);
  2002. if (meshpath != null) { parent.deleteFolderRec(meshpath); }
  2003. } catch (e) { }
  2004. parent.parent.RemoveEventDispatchId(command.meshid); // Remove all subscriptions to this mesh
  2005. // Notify the devices that they have changed relay roles
  2006. if (mesh.relayid != null) {
  2007. // Get the node and the rights for this node
  2008. parent.GetNodeWithRights(domain, user, mesh.relayid, function (node, rights, visible) {
  2009. if (node == null) return;
  2010. var event = { etype: 'node', userid: user._id, username: user.name, action: 'changenode', nodeid: node._id, domain: domain.id, msg: 'No longer a relay for ' + mesh.name + '.', msgid: 152, msgArgs: [mesh.name], node: parent.CloneSafeNode(node) };
  2011. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the node. Another event will come.
  2012. parent.parent.DispatchEvent(parent.CreateNodeDispatchTargets(node.meshid, node._id, [user._id]), obj, event);
  2013. });
  2014. }
  2015. // Mark the mesh as deleted
  2016. mesh.deleted = new Date(); // Mark the time this mesh was deleted, we can expire it at some point.
  2017. db.Set(mesh); // We don't really delete meshes because if a device connects to is again, we will un-delete it.
  2018. // Delete all devices attached to this mesh in the database
  2019. db.RemoveMeshDocuments(command.meshid);
  2020. // TODO: We are possibly deleting devices that users will have links to. We need to clean up the broken links from on occasion.
  2021. // Log in the auth log
  2022. if (parent.parent.authlog) { parent.parent.authLog('https', 'User ' + user.name + ' deleted device group ' + mesh.name); }
  2023. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'deletemesh', responseid: command.responseid, result: 'ok' })); } catch (ex) { } }
  2024. break;
  2025. }
  2026. case 'editmesh':
  2027. {
  2028. // Change the name or description of a device group (mesh)
  2029. var err = null;
  2030. // Resolve the device group name if needed
  2031. if ((typeof command.meshidname == 'string') && (command.meshid == null)) {
  2032. for (var i in parent.meshes) {
  2033. var m = parent.meshes[i];
  2034. if ((m.mtype == 2) && (m.name == command.meshidname) && parent.IsMeshViewable(user, m)) {
  2035. if (command.meshid == null) { command.meshid = m._id; } else { err = 'Duplicate device groups found'; }
  2036. }
  2037. }
  2038. }
  2039. // Validate input
  2040. try {
  2041. if (common.validateString(command.meshid, 8, 134) == false) { err = 'Invalid group identifier'; } // Check the meshid
  2042. else if (command.meshid.indexOf('/') == -1) { command.meshid = 'mesh/' + domain.id + '/' + command.meshid; }
  2043. if (err == null) {
  2044. mesh = parent.meshes[command.meshid];
  2045. if (mesh == null) { err = 'Invalid group identifier '; }
  2046. }
  2047. } catch (ex) { err = 'Validation exception: ' + ex; }
  2048. // Handle any errors
  2049. if (err != null) { if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'editmesh', responseid: command.responseid, result: err })); } catch (ex) { } } break; }
  2050. change = '';
  2051. // Check if this user has rights to do this
  2052. if ((parent.GetMeshRights(user, mesh) & MESHRIGHT_EDITMESH) == 0) return;
  2053. if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
  2054. var changesids = [];
  2055. if ((common.validateString(command.meshname, 1, 128) == true) && (command.meshname != mesh.name)) { change = 'Device group name changed from "' + mesh.name + '" to "' + command.meshname + '"'; changesids.push(1); mesh.name = command.meshname; }
  2056. if ((common.validateString(command.desc, 0, 1024) == true) && (command.desc != mesh.desc)) { if (change != '') change += ' and description changed'; else change += 'Device group "' + mesh.name + '" description changed'; changesids.push(2); mesh.desc = command.desc; }
  2057. if ((common.validateInt(command.flags) == true) && (command.flags != mesh.flags)) { if (change != '') change += ' and flags changed'; else change += 'Device group "' + mesh.name + '" flags changed'; changesids.push(3); mesh.flags = command.flags; }
  2058. if ((common.validateInt(command.consent) == true) && (command.consent != mesh.consent)) { if (change != '') change += ' and consent changed'; else change += 'Device group "' + mesh.name + '" consent changed'; changesids.push(4); mesh.consent = command.consent; }
  2059. if ((common.validateInt(command.expireDevs, 0, 2000) == true) && (command.expireDevs != mesh.expireDevs)) { if (change != '') change += ' and auto-remove changed'; else change += 'Device group "' + mesh.name + '" auto-remove changed'; changesids.push(5); if (command.expireDevs == 0) { delete mesh.expireDevs; } else { mesh.expireDevs = command.expireDevs; } }
  2060. var oldRelayNodeId = null, newRelayNodeId = null;
  2061. if ((typeof command.relayid == 'string') && ((mesh.mtype == 3) || (mesh.mtype == 4)) && (mesh.relayid != null) && (command.relayid != mesh.relayid)) {
  2062. var relayIdSplit = command.relayid.split('/');
  2063. if ((relayIdSplit.length == 3) && (relayIdSplit[0] = 'node') && (relayIdSplit[1] == domain.id)) {
  2064. if (change != '') { change += ' and device relay changed'; } else { change = 'Device relay changed'; }
  2065. changesids.push(7);
  2066. oldRelayNodeId = mesh.relayid;
  2067. newRelayNodeId = mesh.relayid = command.relayid;
  2068. }
  2069. }
  2070. // See if we need to change device group invitation codes
  2071. if (mesh.mtype == 2) {
  2072. if (command.invite === '*') {
  2073. // Clear invite codes
  2074. if (mesh.invite != null) { delete mesh.invite; }
  2075. if (change != '') { change += ' and invite code changed'; } else { change += 'Device group "' + mesh.name + '" invite code changed'; }
  2076. changesids.push(6);
  2077. } else if ((typeof command.invite == 'object') && (Array.isArray(command.invite.codes)) && (typeof command.invite.flags == 'number')) {
  2078. // Set invite codes
  2079. if ((mesh.invite == null) || (mesh.invite.codes != command.invite.codes) || (mesh.invite.flags != command.invite.flags)) {
  2080. // Check if an invite code is not already in use.
  2081. var dup = null;
  2082. for (var i in command.invite.codes) {
  2083. for (var j in parent.meshes) {
  2084. if ((j != command.meshid) && (parent.meshes[j].deleted == null) && (parent.meshes[j].domain == domain.id) && (parent.meshes[j].invite != null) && (parent.meshes[j].invite.codes.indexOf(command.invite.codes[i]) >= 0)) { dup = command.invite.codes[i]; break; }
  2085. }
  2086. }
  2087. if (dup != null) {
  2088. // A duplicate was found, don't allow this change.
  2089. displayNotificationMessage("Error, invite code \"" + dup + "\" already in use.", "Invite Codes", null, 6, 22, [dup]);
  2090. return;
  2091. }
  2092. mesh.invite = { codes: command.invite.codes, flags: command.invite.flags };
  2093. if (typeof command.invite.ag == 'number') { mesh.invite.ag = command.invite.ag; }
  2094. if (change != '') { change += ' and invite code changed'; } else { change += 'Device group "' + mesh.name + '" invite code changed'; }
  2095. changesids.push(6);
  2096. }
  2097. }
  2098. }
  2099. if (change != '') {
  2100. db.Set(mesh);
  2101. var event = { etype: 'mesh', userid: user._id, username: user.name, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, flags: mesh.flags, consent: mesh.consent, action: 'meshchange', links: mesh.links, msgid: 142, msgArgs: [mesh.name, changesids], msg: change, domain: domain.id, invite: mesh.invite, expireDevs: command.expireDevs, relayid: mesh.relayid };
  2102. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
  2103. parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(mesh, [user._id, 'server-editmesh']), obj, event);
  2104. }
  2105. // Notify the devices that they have changed relay roles
  2106. if (oldRelayNodeId != null) {
  2107. // Get the node and the rights for this node
  2108. parent.GetNodeWithRights(domain, user, oldRelayNodeId, function (node, rights, visible) {
  2109. if (node == null) return;
  2110. var event = { etype: 'node', userid: user._id, username: user.name, action: 'changenode', nodeid: node._id, domain: domain.id, msg: 'No longer a relay for ' + mesh.name + '.', msgid: 152, msgArgs: [mesh.name], node: parent.CloneSafeNode(node) };
  2111. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the node. Another event will come.
  2112. parent.parent.DispatchEvent(parent.CreateNodeDispatchTargets(node.meshid, node._id, [user._id]), obj, event);
  2113. });
  2114. }
  2115. if (newRelayNodeId != null) {
  2116. // Get the node and the rights for this node
  2117. parent.GetNodeWithRights(domain, user, newRelayNodeId, function (node, rights, visible) {
  2118. if (node == null) return;
  2119. var event = { etype: 'node', userid: user._id, username: user.name, action: 'changenode', nodeid: node._id, domain: domain.id, msg: 'Is a relay for ' + mesh.name + '.', msgid: 153, msgArgs: [mesh.name], node: parent.CloneSafeNode(node) };
  2120. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the node. Another event will come.
  2121. parent.parent.DispatchEvent(parent.CreateNodeDispatchTargets(node.meshid, node._id, [user._id]), obj, event);
  2122. });
  2123. } else if ((mesh.relayid != null) && (changesids.indexOf(1) >= 0)) {
  2124. // Notify of node name change, get the node and the rights for this node, we just want to trigger a device update.
  2125. parent.GetNodeWithRights(domain, user, mesh.relayid, function (node, rights, visible) {
  2126. if (node == null) return;
  2127. var event = { etype: 'node', userid: user._id, username: user.name, action: 'changenode', nodeid: node._id, domain: domain.id, node: parent.CloneSafeNode(node), nolog: 1 };
  2128. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the node. Another event will come.
  2129. parent.parent.DispatchEvent(parent.CreateNodeDispatchTargets(node.meshid, node._id, [user._id]), obj, event);
  2130. });
  2131. }
  2132. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'editmesh', responseid: command.responseid, result: 'ok' })); } catch (ex) { } }
  2133. break;
  2134. }
  2135. case 'removemeshuser':
  2136. {
  2137. var xdomain, err = null;
  2138. // Resolve the device group name if needed
  2139. if ((typeof command.meshname == 'string') && (command.meshid == null)) {
  2140. for (var i in parent.meshes) {
  2141. var m = parent.meshes[i];
  2142. if ((m.mtype == 2) && (m.name == command.meshname) && parent.IsMeshViewable(user, m)) {
  2143. if (command.meshid == null) { command.meshid = m._id; } else { err = 'Duplicate device groups found'; }
  2144. }
  2145. }
  2146. }
  2147. try {
  2148. if (common.validateString(command.userid, 1, 1024) == false) { err = "Invalid userid"; } // Check userid
  2149. if (common.validateString(command.meshid, 8, 134) == false) { err = "Invalid groupid"; } // Check meshid
  2150. if (command.userid.indexOf('/') == -1) { command.userid = 'user/' + domain.id + '/' + command.userid; }
  2151. if (command.userid == obj.user._id) { err = "Can't remove self"; } // Can't add of modify self
  2152. if ((command.userid.split('/').length != 3) || ((obj.crossDomain !== true) && (command.userid.split('/')[1] != domain.id))) { err = "Invalid userid"; } // Invalid domain, operation only valid for current domain
  2153. else {
  2154. if (command.meshid.indexOf('/') == -1) { command.meshid = 'mesh/' + domain.id + '/' + command.meshid; }
  2155. mesh = parent.meshes[command.meshid];
  2156. var meshIdSplit = command.meshid.split('/');
  2157. if (mesh == null) { err = "Unknown device group"; }
  2158. else if ((parent.GetMeshRights(user, mesh) & MESHRIGHT_MANAGEUSERS) == 0) { err = "Permission denied"; }
  2159. else if (meshIdSplit.length != 3) { err = "Invalid domain"; } // Invalid domain, operation only valid for current domain
  2160. else {
  2161. xdomain = domain;
  2162. if (obj.crossDomain !== true) { xdomain = parent.parent.config.domains[meshIdSplit[1]]; }
  2163. if (xdomain == null) { err = "Invalid domain"; }
  2164. }
  2165. }
  2166. } catch (ex) { err = "Validation exception: " + ex; }
  2167. // Handle any errors
  2168. if (err != null) {
  2169. console.log(err);
  2170. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'removemeshuser', responseid: command.responseid, result: err })); } catch (ex) { } }
  2171. break;
  2172. }
  2173. // Check if the user exists - Just in case we need to delete a mesh right for a non-existant user, we do it this way. Technically, it's not possible, but just in case.
  2174. var deluserid = command.userid, deluser = null;
  2175. if (deluserid.startsWith('user/')) { deluser = parent.users[deluserid]; }
  2176. else if (deluserid.startsWith('ugrp/')) { deluser = parent.userGroups[deluserid]; }
  2177. // Search for a user name in that windows domain is the username starts with *\
  2178. if ((deluser == null) && (deluserid.startsWith('user/' + xdomain.id + '/*\\')) == true) {
  2179. var search = deluserid.split('/')[2].substring(1);
  2180. for (var i in parent.users) { if (i.endsWith(search) && (parent.users[i].domain == xdomain.id)) { deluser = parent.users[i]; command.userid = deluserid = deluser._id; break; } }
  2181. }
  2182. if (deluser != null) {
  2183. // Remove mesh from user
  2184. if (deluser.links != null && deluser.links[command.meshid] != null) {
  2185. var delmeshrights = deluser.links[command.meshid].rights;
  2186. if ((delmeshrights == MESHRIGHT_ADMIN) && (parent.GetMeshRights(user, mesh) != MESHRIGHT_ADMIN)) return; // A non-admin can't kick out an admin
  2187. delete deluser.links[command.meshid];
  2188. if (deluserid.startsWith('user/')) { db.SetUser(deluser); }
  2189. else if (deluserid.startsWith('ugrp/')) { db.Set(deluser); }
  2190. parent.parent.DispatchEvent([deluser._id], obj, 'resubscribe');
  2191. if (deluserid.startsWith('user/')) {
  2192. // Notify user change
  2193. var targets = ['*', 'server-users', user._id, deluser._id];
  2194. var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(deluser), action: 'accountchange', msgid: 78, msgArgs: [deluser.name], msg: 'Device group membership changed: ' + deluser.name, domain: xdomain.id };
  2195. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  2196. parent.parent.DispatchEvent(targets, obj, event);
  2197. } else if (deluserid.startsWith('ugrp/')) {
  2198. // Notify user group change
  2199. var targets = ['*', 'server-ugroups', user._id, deluser._id];
  2200. var event = { etype: 'ugrp', username: user.name, ugrpid: deluser._id, name: deluser.name, desc: deluser.desc, action: 'usergroupchange', links: deluser.links, msgid: 79, msgArgs: [deluser.name], msg: 'User group changed: ' + deluser.name, domain: xdomain.id };
  2201. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  2202. parent.parent.DispatchEvent(targets, obj, event);
  2203. }
  2204. }
  2205. }
  2206. // Remove user from the mesh
  2207. if (mesh.links[command.userid] != null) {
  2208. delete mesh.links[command.userid];
  2209. db.Set(mesh);
  2210. // Notify mesh change
  2211. var event;
  2212. if (deluser != null) {
  2213. event = { etype: 'mesh', username: user.name, userid: deluser.name, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msgid: 83, msgArgs: [deluser.name, mesh.name], msg: 'Removed user ' + deluser.name + ' from device group ' + mesh.name, domain: xdomain.id, invite: mesh.invite };
  2214. } else {
  2215. event = { etype: 'mesh', username: user.name, userid: (deluserid.split('/')[2]), meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msgid: 83, msgArgs: [(deluserid.split('/')[2]), mesh.name], msg: 'Removed user ' + (deluserid.split('/')[2]) + ' from device group ' + mesh.name, domain: xdomain.id, invite: mesh.invite };
  2216. }
  2217. parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(mesh, [user._id, command.userid]), obj, event);
  2218. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'removemeshuser', responseid: command.responseid, result: 'ok' })); } catch (ex) { } }
  2219. } else {
  2220. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'removemeshuser', responseid: command.responseid, result: 'User not in group' })); } catch (ex) { } }
  2221. }
  2222. break;
  2223. }
  2224. case 'meshamtpolicy':
  2225. {
  2226. // Change a mesh Intel AMT policy
  2227. if (common.validateString(command.meshid, 8, 134) == false) break; // Check the meshid
  2228. if (common.validateObject(command.amtpolicy) == false) break; // Check the amtpolicy
  2229. if (common.validateInt(command.amtpolicy.type, 0, 4) == false) break; // Check the amtpolicy.type
  2230. if (command.amtpolicy.type === 2) {
  2231. if ((command.amtpolicy.password != null) && (common.validateString(command.amtpolicy.password, 0, 32) == false)) break; // Check the amtpolicy.password
  2232. if ((command.amtpolicy.badpass != null) && common.validateInt(command.amtpolicy.badpass, 0, 1) == false) break; // Check the amtpolicy.badpass
  2233. if (common.validateInt(command.amtpolicy.cirasetup, 0, 2) == false) break; // Check the amtpolicy.cirasetup
  2234. } else if (command.amtpolicy.type === 3) {
  2235. if ((command.amtpolicy.password != null) && (common.validateString(command.amtpolicy.password, 0, 32) == false)) break; // Check the amtpolicy.password
  2236. if ((command.amtpolicy.badpass != null) && common.validateInt(command.amtpolicy.badpass, 0, 1) == false) break; // Check the amtpolicy.badpass
  2237. if ((command.amtpolicy.ccm != null) && common.validateInt(command.amtpolicy.ccm, 0, 2) == false) break; // Check the amtpolicy.ccm
  2238. if (common.validateInt(command.amtpolicy.cirasetup, 0, 2) == false) break; // Check the amtpolicy.cirasetup
  2239. }
  2240. mesh = parent.meshes[command.meshid];
  2241. if (mesh) {
  2242. // Check if this user has rights to do this
  2243. if ((parent.GetMeshRights(user, mesh) & MESHRIGHT_EDITMESH) == 0) return;
  2244. if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
  2245. // TODO: Check if this is a change from the existing policy
  2246. // Perform the Intel AMT policy change
  2247. var amtpolicy = { type: command.amtpolicy.type };
  2248. if ((command.amtpolicy.type === 2) || (command.amtpolicy.type === 3)) {
  2249. amtpolicy = { type: command.amtpolicy.type, badpass: command.amtpolicy.badpass, cirasetup: command.amtpolicy.cirasetup };
  2250. if (command.amtpolicy.type === 3) { amtpolicy.ccm = command.amtpolicy.ccm; }
  2251. if ((command.amtpolicy.password == null) && (mesh.amt != null) && (typeof mesh.amt.password == 'string')) { amtpolicy.password = mesh.amt.password; } // Keep the last password
  2252. if ((typeof command.amtpolicy.password == 'string') && (command.amtpolicy.password.length >= 8)) { amtpolicy.password = command.amtpolicy.password; } // Set a new password
  2253. }
  2254. mesh.amt = amtpolicy;
  2255. db.Set(mesh);
  2256. var amtpolicy2 = Object.assign({}, amtpolicy); // Shallow clone
  2257. if (amtpolicy2.password != null) { amtpolicy2.password = 1; }
  2258. var event = { etype: 'mesh', userid: user._id, username: user.name, meshid: mesh._id, amt: amtpolicy2, action: 'meshchange', links: mesh.links, msgid: 141, msg: "Intel(r) AMT policy change", domain: domain.id, invite: mesh.invite };
  2259. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
  2260. parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(mesh, [user._id]), obj, event);
  2261. // If we have peer servers, inform them of the new Intel AMT policy for this device group
  2262. if (parent.parent.multiServer != null) { parent.parent.multiServer.DispatchMessage({ action: 'newIntelAmtPolicy', meshid: command.meshid, amtpolicy: amtpolicy }); }
  2263. // See if any agents for the affected device group is connected, if so, update the Intel AMT policy
  2264. for (var nodeid in parent.wsagents) {
  2265. const agent = parent.wsagents[nodeid];
  2266. if (agent.dbMeshKey == command.meshid) { agent.sendUpdatedIntelAmtPolicy(amtpolicy); }
  2267. }
  2268. }
  2269. break;
  2270. }
  2271. case 'addlocaldevice':
  2272. {
  2273. var err = null;
  2274. // Perform input validation
  2275. try {
  2276. if (common.validateString(command.meshid, 8, 134) == false) { err = "Invalid device group id"; } // Check meshid
  2277. if (common.validateString(command.devicename, 1, 256) == false) { err = "Invalid devicename"; } // Check device name
  2278. if (common.validateString(command.hostname, 1, 256) == false) { err = "Invalid hostname"; } // Check hostname
  2279. if (typeof command.type != 'number') { err = "Invalid type"; } // Type must be a number
  2280. if ((command.type != 4) && (command.type != 6) && (command.type != 29)) { err = "Invalid type"; } // Check device type
  2281. else {
  2282. if (command.meshid.indexOf('/') == -1) { command.meshid = 'mesh/' + domain.id + '/' + command.meshid; }
  2283. mesh = parent.meshes[command.meshid];
  2284. if (mesh == null) { err = "Unknown device group"; }
  2285. if (mesh.mtype != 3) { err = "Local device agentless mesh only allowed" } // This operation is only allowed for mesh type 3, local device agentless mesh.
  2286. else if ((parent.GetMeshRights(user, mesh) & MESHRIGHT_MANAGECOMPUTERS) == 0) { err = "Permission denied"; }
  2287. else if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) { err = "Invalid domain"; } // Invalid domain, operation only valid for current domain
  2288. }
  2289. } catch (ex) { console.log(ex); err = "Validation exception: " + ex; }
  2290. // Handle any errors
  2291. if (err != null) {
  2292. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'changeDeviceMesh', responseid: command.responseid, result: err })); } catch (ex) { } }
  2293. break;
  2294. }
  2295. // Create a new nodeid
  2296. parent.crypto.randomBytes(48, function (err, buf) {
  2297. // Create the new node
  2298. nodeid = 'node/' + domain.id + '/' + buf.toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
  2299. var device = { type: 'node', _id: nodeid, meshid: command.meshid, mtype: 3, icon: 1, name: command.devicename, host: command.hostname, domain: domain.id, agent: { id: command.type, caps: 0 } };
  2300. db.Set(device);
  2301. // Event the new node
  2302. parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(command.meshid, [nodeid]), obj, { etype: 'node', userid: user._id, username: user.name, action: 'addnode', node: parent.CloneSafeNode(device), msgid: 84, msgArgs: [command.devicename, mesh.name], msg: 'Added device ' + command.devicename + ' to device group ' + mesh.name, domain: domain.id });
  2303. // Send response if required
  2304. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'addlocaldevice', responseid: command.responseid, result: 'ok' })); } catch (ex) { } }
  2305. });
  2306. break;
  2307. }
  2308. case 'addamtdevice':
  2309. {
  2310. if (args.wanonly == true) return; // This is a WAN-only server, local Intel AMT computers can't be added
  2311. var err = null;
  2312. // Perform input validation
  2313. try {
  2314. if (common.validateString(command.meshid, 8, 134) == false) { err = "Invalid device group id"; } // Check meshid
  2315. if (common.validateString(command.devicename, 1, 256) == false) { err = "Invalid devicename"; } // Check device name
  2316. if (common.validateString(command.hostname, 1, 256) == false) { err = "Invalid hostname"; } // Check hostname
  2317. if (common.validateString(command.amtusername, 0, 16) == false) { err = "Invalid amtusername"; } // Check username
  2318. if (common.validateString(command.amtpassword, 0, 16) == false) { err = "Invalid amtpassword"; } // Check password
  2319. if (command.amttls == '0') { command.amttls = 0; } else if (command.amttls == '1') { command.amttls = 1; } // Check TLS flag
  2320. if ((command.amttls != 1) && (command.amttls != 0)) { err = "Invalid amttls"; }
  2321. else {
  2322. if (command.meshid.indexOf('/') == -1) { command.meshid = 'mesh/' + domain.id + '/' + command.meshid; }
  2323. // Get the mesh
  2324. mesh = parent.meshes[command.meshid];
  2325. if (mesh == null) { err = "Unknown device group"; }
  2326. if (mesh.mtype != 1) { err = "Intel AMT agentless mesh only allowed"; } // This operation is only allowed for mesh type 1, Intel AMT agentless mesh.
  2327. // Check if this user has rights to do this
  2328. else if ((parent.GetMeshRights(user, mesh) & MESHRIGHT_MANAGECOMPUTERS) == 0) { err = "Permission denied"; }
  2329. else if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) { err = "Invalid domain"; } // Invalid domain, operation only valid for current domain
  2330. }
  2331. } catch (ex) { console.log(ex); err = "Validation exception: " + ex; }
  2332. // Handle any errors
  2333. if (err != null) {
  2334. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'changeDeviceMesh', responseid: command.responseid, result: err })); } catch (ex) { } }
  2335. break;
  2336. }
  2337. // If we are in WAN-only mode, hostname is not used
  2338. if ((args.wanonly == true) && (command.hostname)) { delete command.hostname; }
  2339. // Create a new nodeid
  2340. parent.crypto.randomBytes(48, function (err, buf) {
  2341. // Create the new node
  2342. nodeid = 'node/' + domain.id + '/' + buf.toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
  2343. var device = { type: 'node', _id: nodeid, meshid: command.meshid, mtype: 1, icon: 1, name: command.devicename, host: command.hostname, domain: domain.id, intelamt: { user: command.amtusername, pass: command.amtpassword, tls: command.amttls } };
  2344. // Add optional feilds
  2345. if (common.validateInt(command.state, 0, 3)) { device.intelamt.state = command.state; }
  2346. if (common.validateString(command.ver, 1, 16)) { device.intelamt.ver = command.ver; }
  2347. if (common.validateString(command.hash, 1, 256)) { device.intelamt.hash = command.hash; }
  2348. if (common.validateString(command.realm, 1, 256)) { device.intelamt.realm = command.realm; }
  2349. // Save the device to the database
  2350. db.Set(device);
  2351. // Event the new node
  2352. parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(command.meshid, [nodeid]), obj, { etype: 'node', userid: user._id, username: user.name, action: 'addnode', node: parent.CloneSafeNode(device), msgid: 84, msgArgs: [command.devicename, mesh.name], msg: 'Added device ' + command.devicename + ' to device group ' + mesh.name, domain: domain.id });
  2353. // Send response if required
  2354. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'addamtdevice', responseid: command.responseid, result: 'ok' })); } catch (ex) { } }
  2355. });
  2356. break;
  2357. }
  2358. case 'scanamtdevice':
  2359. {
  2360. if (args.wanonly == true) return; // This is a WAN-only server, this type of scanning is not allowed.
  2361. if (common.validateString(command.range, 1, 256) == false) break; // Check range string
  2362. // Ask the RMCP scanning to scan a range of IP addresses
  2363. if (parent.parent.amtScanner) {
  2364. if (parent.parent.amtScanner.performRangeScan(user._id, command.range) == false) {
  2365. parent.parent.DispatchEvent(['*', user._id], obj, { action: 'scanamtdevice', range: command.range, results: null, nolog: 1 });
  2366. }
  2367. }
  2368. break;
  2369. }
  2370. case 'changeDeviceMesh':
  2371. {
  2372. var err = null;
  2373. // Resolve the device group name if needed
  2374. if ((typeof command.meshname == 'string') && (command.meshid == null)) {
  2375. for (var i in parent.meshes) {
  2376. var m = parent.meshes[i];
  2377. if ((m.mtype == 2) && (m.name == command.meshname) && parent.IsMeshViewable(user, m)) {
  2378. if (command.meshid == null) { command.meshid = m._id; } else { err = 'Duplicate device groups found'; }
  2379. }
  2380. }
  2381. }
  2382. // Perform input validation
  2383. try {
  2384. if (common.validateStrArray(command.nodeids, 1, 256) == false) { err = "Invalid nodeids"; } // Check nodeids
  2385. if (common.validateString(command.meshid, 8, 134) == false) { err = "Invalid groupid"; } // Check meshid
  2386. else {
  2387. if (command.meshid.indexOf('/') == -1) { command.meshid = 'mesh/' + domain.id + '/' + command.meshid; }
  2388. mesh = parent.meshes[command.meshid];
  2389. if (mesh == null) { err = "Unknown device group"; }
  2390. else if ((parent.GetMeshRights(user, mesh) & MESHRIGHT_MANAGECOMPUTERS) == 0) { err = "Permission denied"; }
  2391. else if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) { err = "Invalid domain"; } // Invalid domain, operation only valid for current domain
  2392. }
  2393. } catch (ex) { console.log(ex); err = "Validation exception: " + ex; }
  2394. // Handle any errors
  2395. if (err != null) {
  2396. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'changeDeviceMesh', responseid: command.responseid, result: err })); } catch (ex) { } }
  2397. break;
  2398. }
  2399. // This is to change device guest sharing to the new device group
  2400. var changeDeviceShareMeshIdNodeCount = command.nodeids.length;
  2401. var changeDeviceShareMeshIdNodeList = [];
  2402. // For each nodeid, change the group
  2403. for (var i = 0; i < command.nodeids.length; i++) {
  2404. var xnodeid = command.nodeids[i];
  2405. if (xnodeid.indexOf('/') == -1) { xnodeid = 'node/' + domain.id + '/' + xnodeid; }
  2406. // Get the node and the rights for this node
  2407. parent.GetNodeWithRights(domain, user, xnodeid, function (node, rights, visible) {
  2408. // Check if we found this device
  2409. if (node == null) { if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'changeDeviceMesh', responseid: command.responseid, result: 'Device not found' })); } catch (ex) { } } changeDeviceShareMeshIdNodeCount--; return; }
  2410. // Check if already in the right mesh
  2411. if (node.meshid == command.meshid) { if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'changeDeviceMesh', responseid: command.responseid, result: 'Device already in correct group' })); } catch (ex) { } } changeDeviceShareMeshIdNodeCount--; return; }
  2412. // Make sure both source and target mesh are the same type
  2413. try { if (parent.meshes[node.meshid].mtype != parent.meshes[command.meshid].mtype) { changeDeviceShareMeshIdNodeCount--; return; } } catch (e) {
  2414. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'changeDeviceMesh', responseid: command.responseid, result: 'Device groups are of different types' })); } catch (ex) { } }
  2415. changeDeviceShareMeshIdNodeCount--;
  2416. return;
  2417. };
  2418. // Make sure that we have rights on both source and destination mesh
  2419. const targetMeshRights = parent.GetMeshRights(user, command.meshid);
  2420. if (((rights & MESHRIGHT_EDITMESH) == 0) || ((targetMeshRights & MESHRIGHT_EDITMESH) == 0)) {
  2421. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'changeDeviceMesh', responseid: command.responseid, result: 'Permission denied' })); } catch (ex) { } }
  2422. changeDeviceShareMeshIdNodeCount--;
  2423. return;
  2424. }
  2425. // Perform the switch, start by saving the node with the new meshid.
  2426. changeDeviceShareMeshIdNodeList.push(node._id);
  2427. changeDeviceShareMeshIdNodeCount--;
  2428. if (changeDeviceShareMeshIdNodeCount == 0) { changeDeviceShareMeshId(changeDeviceShareMeshIdNodeList, command.meshid); }
  2429. const oldMeshId = node.meshid;
  2430. node.meshid = command.meshid;
  2431. db.Set(parent.cleanDevice(node));
  2432. // If the device is connected on this server, switch it now.
  2433. var agentSession = parent.wsagents[node._id];
  2434. if (agentSession != null) {
  2435. agentSession.dbMeshKey = command.meshid; // Switch the agent mesh
  2436. agentSession.meshid = command.meshid.split('/')[2]; // Switch the agent mesh
  2437. agentSession.sendUpdatedIntelAmtPolicy(); // Send the new Intel AMT policy
  2438. }
  2439. // If any MQTT sessions are connected on this server, switch it now.
  2440. if (parent.parent.mqttbroker != null) { parent.parent.mqttbroker.changeDeviceMesh(node._id, command.meshid); }
  2441. // If any CIRA sessions are connected on this server, switch it now.
  2442. if (parent.parent.mpsserver != null) { parent.parent.mpsserver.changeDeviceMesh(node._id, command.meshid); }
  2443. // Add the connection state
  2444. const state = parent.parent.GetConnectivityState(node._id);
  2445. if (state) {
  2446. node.conn = state.connectivity;
  2447. node.pwr = state.powerState;
  2448. if ((state.connectivity & 1) != 0) { var agent = parent.wsagents[node._id]; if (agent != null) { node.agct = agent.connectTime; } }
  2449. // Uuse the connection time of the CIRA/Relay connection
  2450. if ((state.connectivity & 2) != 0) {
  2451. var ciraConnection = parent.parent.mpsserver.GetConnectionToNode(node._id, null, true);
  2452. if ((ciraConnection != null) && (ciraConnection.tag != null)) { node.cict = ciraConnection.tag.connectTime; }
  2453. }
  2454. }
  2455. // Update lastconnect meshid for this node
  2456. db.Get('lc' + node._id, function (err, xnodes) {
  2457. if ((xnodes != null) && (xnodes.length == 1) && (xnodes[0].meshid != command.meshid)) { xnodes[0].meshid = command.meshid; db.Set(xnodes[0]); }
  2458. });
  2459. // Event the node change
  2460. var newMesh = parent.meshes[command.meshid];
  2461. var event = { etype: 'node', userid: user._id, username: user.name, action: 'nodemeshchange', nodeid: node._id, node: node, oldMeshId: oldMeshId, newMeshId: command.meshid, msgid: 85, msgArgs: [node.name, newMesh.name], msg: 'Moved device ' + node.name + ' to group ' + newMesh.name, domain: domain.id };
  2462. // Even if change stream is enabled on this server, we still make the nodemeshchange actionable. This is because the DB can't send out a change event that will match this.
  2463. parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(command.meshid, [oldMeshId, node._id]), obj, event);
  2464. // Send response if required
  2465. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'changeDeviceMesh', responseid: command.responseid, result: 'ok' })); } catch (ex) { } }
  2466. });
  2467. }
  2468. break;
  2469. }
  2470. case 'removedevices':
  2471. {
  2472. if (common.validateArray(command.nodeids, 1) == false) break; // Check nodeid's
  2473. for (i in command.nodeids) {
  2474. var nodeid = command.nodeids[i], err = null;
  2475. // Argument validation
  2476. if (common.validateString(nodeid, 1, 1024) == false) { err = 'Invalid nodeid'; } // Check nodeid
  2477. else {
  2478. if (nodeid.indexOf('/') == -1) { nodeid = 'node/' + domain.id + '/' + nodeid; }
  2479. if ((nodeid.split('/').length != 3) || (nodeid.split('/')[1] != domain.id)) { err = 'Invalid domain'; } // Invalid domain, operation only valid for current domain
  2480. }
  2481. if (err != null) {
  2482. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'removedevices', responseid: command.responseid, result: err })); } catch (ex) { } }
  2483. continue;
  2484. }
  2485. // Get the node and the rights for this node
  2486. parent.GetNodeWithRights(domain, user, nodeid, function (node, rights, visible) {
  2487. // Check we have the rights to delete this device
  2488. if ((rights & MESHRIGHT_UNINSTALL) == 0) {
  2489. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'removedevices', responseid: command.responseid, result: 'Denied' })); } catch (ex) { } }
  2490. return;
  2491. }
  2492. // Delete this node including network interface information, events and timeline
  2493. db.Remove(node._id); // Remove node with that id
  2494. db.Remove('if' + node._id); // Remove interface information
  2495. db.Remove('nt' + node._id); // Remove notes
  2496. db.Remove('lc' + node._id); // Remove last connect time
  2497. db.Remove('si' + node._id); // Remove system information
  2498. db.Remove('al' + node._id); // Remove error log last time
  2499. if (db.RemoveSMBIOS) { db.RemoveSMBIOS(node._id); } // Remove SMBios data
  2500. db.RemoveAllNodeEvents(node._id); // Remove all events for this node
  2501. db.removeAllPowerEventsForNode(node._id); // Remove all power events for this node
  2502. if (typeof node.pmt == 'string') { db.Remove('pmt_' + node.pmt); } // Remove Push Messaging Token
  2503. db.Get('ra' + node._id, function (err, nodes) {
  2504. if ((nodes != null) && (nodes.length == 1)) { db.Remove('da' + nodes[0].daid); } // Remove diagnostic agent to real agent link
  2505. db.Remove('ra' + node._id); // Remove real agent to diagnostic agent link
  2506. });
  2507. // Remove any user node links
  2508. if (node.links != null) {
  2509. for (var i in node.links) {
  2510. if (i.startsWith('user/')) {
  2511. var cuser = parent.users[i];
  2512. if ((cuser != null) && (cuser.links != null) && (cuser.links[node._id] != null)) {
  2513. // Remove the user link & save the user
  2514. delete cuser.links[node._id];
  2515. if (Object.keys(cuser.links).length == 0) { delete cuser.links; }
  2516. db.SetUser(cuser);
  2517. // Notify user change
  2518. var targets = ['*', 'server-users', cuser._id];
  2519. var event = { etype: 'user', userid: cuser._id, username: cuser.name, action: 'accountchange', msgid: 86, msgArgs: [cuser.name], msg: 'Removed user device rights for ' + cuser.name, domain: domain.id, account: parent.CloneSafeUser(cuser) };
  2520. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  2521. parent.parent.DispatchEvent(targets, obj, event);
  2522. }
  2523. } else if (i.startsWith('ugrp/')) {
  2524. var cusergroup = parent.userGroups[i];
  2525. if ((cusergroup != null) && (cusergroup.links != null) && (cusergroup.links[node._id] != null)) {
  2526. // Remove the user link & save the user
  2527. delete cusergroup.links[node._id];
  2528. if (Object.keys(cusergroup.links).length == 0) { delete cusergroup.links; }
  2529. db.Set(cusergroup);
  2530. // Notify user change
  2531. var targets = ['*', 'server-users', cusergroup._id];
  2532. var event = { etype: 'ugrp', userid: user._id, username: user.name, ugrpid: cusergroup._id, name: cusergroup.name, desc: cusergroup.desc, action: 'usergroupchange', links: cusergroup.links, msgid: 163, msgArgs: [node.name, cusergroup.name], msg: 'Removed device ' + node.name + ' from user group ' + cusergroup.name };
  2533. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  2534. parent.parent.DispatchEvent(targets, obj, event);
  2535. }
  2536. }
  2537. }
  2538. }
  2539. // Event node deletion
  2540. var event = { etype: 'node', userid: user._id, username: user.name, action: 'removenode', nodeid: node._id, msgid: 87, msgArgs: [node.name, parent.meshes[node.meshid].name], msg: 'Removed device ' + node.name + ' from device group ' + parent.meshes[node.meshid].name, domain: domain.id };
  2541. // TODO: We can't use the changeStream for node delete because we will not know the meshid the device was in.
  2542. //if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to remove the node. Another event will come.
  2543. parent.parent.DispatchEvent(parent.CreateNodeDispatchTargets(node.meshid, node._id), obj, event);
  2544. // Disconnect all connections if needed
  2545. var state = parent.parent.GetConnectivityState(nodeid);
  2546. if ((state != null) && (state.connectivity != null)) {
  2547. if ((state.connectivity & 1) != 0) { parent.wsagents[nodeid].close(); } // Disconnect mesh agent
  2548. if ((state.connectivity & 2) != 0) { parent.parent.mpsserver.closeAllForNode(nodeid); } // Disconnect CIRA/Relay/LMS connections
  2549. }
  2550. // Send response if required
  2551. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'removedevices', responseid: command.responseid, result: 'ok' })); } catch (ex) { } }
  2552. });
  2553. }
  2554. break;
  2555. }
  2556. case 'wakedevices':
  2557. {
  2558. // TODO: We can optimize this a lot.
  2559. // - We should get a full list of all MAC's to wake first.
  2560. // - We should try to only have one agent per subnet (using Gateway MAC) send a wake-on-lan.
  2561. if (common.validateArray(command.nodeids, 1) == false) break; // Check nodeid's
  2562. // Event wakeup, this will cause Intel AMT wake operations on this and other servers.
  2563. parent.parent.DispatchEvent('*', obj, { action: 'wakedevices', userid: user._id, username: user.name, nodeids: command.nodeids, domain: domain.id, nolog: 1 });
  2564. // Perform wake-on-lan
  2565. for (i in command.nodeids) {
  2566. var nodeid = command.nodeids[i];
  2567. // Argument validation
  2568. if (common.validateString(nodeid, 8, 128) == false) { // Check the nodeid
  2569. if (command.nodeids.length == 1) { try { ws.send(JSON.stringify({ action: 'wakedevices', responseid: command.responseid, result: 'Invalid nodeid' })); } catch (ex) { } }
  2570. continue;
  2571. }
  2572. else if (nodeid.indexOf('/') == -1) { nodeid = 'node/' + domain.id + '/' + nodeid; }
  2573. else if ((nodeid.split('/').length != 3) || (nodeid.split('/')[1] != domain.id)) { // Invalid domain, operation only valid for current domain
  2574. if (command.nodeids.length == 1) { try { ws.send(JSON.stringify({ action: 'wakedevices', responseid: command.responseid, result: 'Invalid domain' })); } catch (ex) { } }
  2575. continue;
  2576. }
  2577. // Get the node and the rights for this node
  2578. parent.GetNodeWithRights(domain, user, nodeid, function (node, rights, visible) {
  2579. // Check we have the rights to wake this device
  2580. if ((node == null) || (visible == false) || (rights & MESHRIGHT_WAKEDEVICE) == 0) {
  2581. if (command.nodeids.length == 1) { try { ws.send(JSON.stringify({ action: 'wakedevices', responseid: command.responseid, result: 'Invalid nodeid' })); } catch (ex) { } }
  2582. return;
  2583. }
  2584. // If this device is connected on MQTT, send a wake action.
  2585. if (parent.parent.mqttbroker != null) { parent.parent.mqttbroker.publish(node._id, 'powerAction', 'wake'); }
  2586. // If this is a IP-KVM or Power Distribution Unit (PDU), dispatch an action event
  2587. if (node.mtype == 4) {
  2588. // Send out an event to perform turn off command on the port
  2589. const targets = parent.CreateNodeDispatchTargets(node.meshid, node._id, ['devport-operation', 'server-users', user._id]);
  2590. const event = { etype: 'node', userid: user._id, username: user.name, nodeid: node._id, action: 'turnon', domain: domain.id, portid: node.portid, porttype: node.porttype, portnum: node.portnum, meshid: node.meshid, mtype: node.mtype, msgid: 132, msg: "Turn on." };
  2591. parent.parent.DispatchEvent(targets, obj, event);
  2592. return;
  2593. }
  2594. // Get the device interface information
  2595. db.Get('if' + node._id, function (err, nodeifs) {
  2596. if ((nodeifs != null) && (nodeifs.length == 1)) {
  2597. var macs = [], nodeif = nodeifs[0];
  2598. if (nodeif.netif) {
  2599. for (var j in nodeif.netif) { if (nodeif.netif[j].mac && (nodeif.netif[j].mac != '00:00:00:00:00:00') && (macs.indexOf(nodeif.netif[j].mac) == -1)) { macs.push(nodeif.netif[j].mac); } }
  2600. } else if (nodeif.netif2) {
  2601. for (var j in nodeif.netif2) { for (var k in nodeif.netif2[j]) { if (nodeif.netif2[j][k].mac && (nodeif.netif2[j][k].mac != '00:00:00:00:00:00') && (macs.indexOf(nodeif.netif2[j][k].mac) == -1)) { macs.push(nodeif.netif2[j][k].mac); } } }
  2602. }
  2603. if (macs.length == 0) {
  2604. if (command.nodeids.length == 1) { try { ws.send(JSON.stringify({ action: 'wakedevices', responseid: command.responseid, result: 'No known MAC addresses for this device' })); } catch (ex) { } }
  2605. return;
  2606. }
  2607. // Have the server send a wake-on-lan packet (Will not work in WAN-only)
  2608. if (parent.parent.meshScanner != null) { parent.parent.meshScanner.wakeOnLan(macs, node.host); }
  2609. // Get the list of device groups this user as wake permissions on
  2610. var targets = [], targetDeviceGroups = parent.GetAllMeshWithRights(user, MESHRIGHT_WAKEDEVICE);
  2611. for (j in targetDeviceGroups) { targets.push(targetDeviceGroups[j]._id); }
  2612. for (j in user.links) { if ((j.startsWith('node/')) && (typeof user.links[j].rights == 'number') && ((user.links[j].rights & MESHRIGHT_WAKEDEVICE) != 0)) { targets.push(j); } }
  2613. // Go thru all the connected agents and send wake-on-lan on all the ones in the target mesh list
  2614. var wakeCount = 0;
  2615. for (j in parent.wsagents) {
  2616. var agent = parent.wsagents[j];
  2617. if ((agent.authenticated == 2) && ((targets.indexOf(agent.dbMeshKey) >= 0) || (targets.indexOf(agent.dbNodeKey) >= 0))) {
  2618. //console.log('Asking agent ' + agent.dbNodeKey + ' to wake ' + macs.join(','));
  2619. try { agent.send(JSON.stringify({ action: 'wakeonlan', macs: macs })); wakeCount++; } catch (ex) { }
  2620. }
  2621. }
  2622. if (command.nodeids.length == 1) { try { ws.send(JSON.stringify({ action: 'wakedevices', responseid: command.responseid, result: 'Used ' + wakeCount + ' device(s) to send wake packets' })); } catch (ex) { } }
  2623. } else {
  2624. if (command.nodeids.length == 1) { try { ws.send(JSON.stringify({ action: 'wakedevices', responseid: command.responseid, result: 'No network information for this device' })); } catch (ex) { } }
  2625. }
  2626. });
  2627. });
  2628. if (command.nodeids.length > 1) {
  2629. // If we are waking multiple devices, confirm we got the command.
  2630. try { ws.send(JSON.stringify({ action: 'wakedevices', responseid: command.responseid, result: 'ok' })); } catch (ex) { }
  2631. }
  2632. }
  2633. break;
  2634. }
  2635. case 'webrelay':
  2636. {
  2637. if (common.validateString(command.nodeid, 8, 128) == false) { err = 'Invalid node id'; } // Check the nodeid
  2638. else if (command.nodeid.indexOf('/') == -1) { command.nodeid = 'node/' + domain.id + '/' + command.nodeid; }
  2639. else if ((command.nodeid.split('/').length != 3) || (command.nodeid.split('/')[1] != domain.id)) { err = 'Invalid domain'; } // Invalid domain, operation only valid for current domain
  2640. else if ((command.port != null) && (common.validateInt(command.port, 1, 65535) == false)) { err = 'Invalid port value'; } // Check the port if present
  2641. else {
  2642. if (command.nodeid.split('/').length == 1) { command.nodeid = 'node/' + domain.id + '/' + command.nodeid; }
  2643. var snode = command.nodeid.split('/');
  2644. if ((snode.length != 3) || (snode[0] != 'node') || (snode[1] != domain.id)) { err = 'Invalid node id'; }
  2645. }
  2646. // Handle any errors
  2647. if (err != null) {
  2648. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'webrelay', responseid: command.responseid, result: err })); } catch (ex) { } }
  2649. break;
  2650. }
  2651. // Get the device rights
  2652. parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
  2653. // If node not found or we don't have remote control, reject.
  2654. if (node == null) {
  2655. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'webrelay', responseid: command.responseid, result: 'Invalid node id' })); } catch (ex) { } }
  2656. return;
  2657. }
  2658. var relayid = null;
  2659. var addr = null;
  2660. if (node.mtype == 3) { // Setup device relay if needed
  2661. var mesh = parent.meshes[node.meshid];
  2662. if (mesh && mesh.relayid) { relayid = mesh.relayid; addr = node.host; }
  2663. }
  2664. var webRelayDns = (args.relaydns != null) ? args.relaydns[0] : obj.getWebServerName(domain, req);
  2665. var webRelayPort = ((args.relaydns != null) ? ((typeof args.aliasport == 'number') ? args.aliasport : args.port) : ((parent.webrelayserver != null) ? ((typeof args.relayaliasport == 'number') ? args.relayaliasport : parent.webrelayserver.port) : 0));
  2666. if (webRelayPort == 0) { try { ws.send(JSON.stringify({ action: 'webrelay', responseid: command.responseid, result: 'WebRelay Disabled' })); return; } catch (ex) { } }
  2667. const authRelayCookie = parent.parent.encodeCookie({ ruserid: user._id, x: req.session.x }, parent.parent.loginCookieEncryptionKey);
  2668. var url = 'https://' + webRelayDns + ':' + webRelayPort + '/control-redirect.ashx?n=' + command.nodeid + '&p=' + command.port + '&appid=' + command.appid + '&c=' + authRelayCookie;
  2669. if (addr != null) { url += '&addr=' + addr; }
  2670. if (relayid != null) { url += '&relayid=' + relayid }
  2671. command.url = url;
  2672. if (command.responseid != null) { command.result = 'OK'; }
  2673. try { ws.send(JSON.stringify(command)); } catch (ex) { }
  2674. });
  2675. break;
  2676. }
  2677. case 'runcommands':
  2678. {
  2679. if (common.validateArray(command.nodeids, 1) == false) break; // Check nodeid's
  2680. if (typeof command.presetcmd != 'number') {
  2681. if (typeof command.type != 'number') break; // Check command type
  2682. if (typeof command.runAsUser != 'number') { command.runAsUser = 0; } // Check runAsUser
  2683. }
  2684. const processRunCommand = function (command) {
  2685. for (i in command.nodeids) {
  2686. var nodeid = command.nodeids[i], err = null;
  2687. // Argument validation
  2688. if (common.validateString(nodeid, 1, 1024) == false) { err = 'Invalid nodeid'; } // Check nodeid
  2689. else {
  2690. if (nodeid.indexOf('/') == -1) { nodeid = 'node/' + domain.id + '/' + nodeid; }
  2691. if ((nodeid.split('/').length != 3) || (nodeid.split('/')[1] != domain.id)) { err = 'Invalid domain'; } // Invalid domain, operation only valid for current domain
  2692. }
  2693. if (err != null) {
  2694. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: err })); } catch (ex) { } }
  2695. continue;
  2696. }
  2697. // Get the node and the rights for this node
  2698. parent.GetNodeWithRights(domain, user, nodeid, function (node, rights, visible) {
  2699. // Check if this node was found
  2700. if (node == null) {
  2701. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'Invalid nodeid' })); } catch (ex) { } }
  2702. return;
  2703. }
  2704. if (command.type == 4) {
  2705. // This is an agent console command
  2706. // Check we have the rights to run commands on this device, MESHRIGHT_REMOTECONTROL & MESHRIGHT_AGENTCONSOLE are needed
  2707. if ((rights & 24) != 24) {
  2708. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'Access denied' })); } catch (ex) { } }
  2709. return;
  2710. }
  2711. var theCommand = { action: 'msg', type: 'console', value: command.cmds, rights: rights, sessionid: ws.sessionId };
  2712. if (parent.parent.multiServer != null) { // peering setup
  2713. parent.parent.multiServer.DispatchMessage({ action: 'agentCommand', nodeid: node._id, command: theCommand});
  2714. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'OK' })); } catch (ex) { } }
  2715. } else {
  2716. // Send the commands to the agent
  2717. var agent = parent.wsagents[node._id];
  2718. if ((agent != null) && (agent.authenticated == 2) && (agent.agentInfo != null)) {
  2719. try { agent.send(JSON.stringify(theCommand)); } catch (ex) { }
  2720. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'OK' })); } catch (ex) { } }
  2721. } else {
  2722. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'Agent not connected' })); } catch (ex) { } }
  2723. }
  2724. }
  2725. } else {
  2726. // This is a standard (bash/shell/powershell) command.
  2727. // Check we have the rights to run commands on this device
  2728. if ((rights & MESHRIGHT_REMOTECOMMAND) == 0) {
  2729. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'Access denied' })); } catch (ex) { } }
  2730. return;
  2731. }
  2732. if (typeof command.reply != 'boolean') command.reply = false;
  2733. if (typeof command.responseid != 'string') command.responseid = null;
  2734. var msgid = 24; // "Running commands"
  2735. if (command.type == 1) { msgid = 99; } // "Running commands as user"
  2736. if (command.type == 2) { msgid = 100; } // "Running commands as user if possible"
  2737. // Check if this agent is correct for this command type
  2738. // command.type 1 = Windows Command, 2 = Windows PowerShell, 3 = Linux/BSD/macOS
  2739. var commandsOk = false;
  2740. if ((node.agent.id > 0) && (node.agent.id < 5) || (node.agent.id > 41 && node.agent.id < 44)) {
  2741. // Windows Agent
  2742. if ((command.type == 1) || (command.type == 2)) { commandsOk = true; }
  2743. else if (command.type === 0) { command.type = 1; commandsOk = true; } // Set the default type of this agent
  2744. } else {
  2745. // Non-Windows Agent
  2746. if (command.type == 3) { commandsOk = true; }
  2747. else if (command.type === 0) { command.type = 3; commandsOk = true; } // Set the default type of this agent
  2748. }
  2749. if (commandsOk == true) {
  2750. var theCommand = { action: 'runcommands', type: command.type, cmds: command.cmds, runAsUser: command.runAsUser, reply: command.reply, responseid: command.responseid };
  2751. var agent = parent.wsagents[node._id];
  2752. if ((agent != null) && (agent.authenticated == 2) && (agent.agentInfo != null)) {
  2753. // Send the commands to the agent
  2754. try { agent.send(JSON.stringify(theCommand)); } catch (ex) { }
  2755. if (command.responseid != null && command.reply == false) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'OK' })); } catch (ex) { } }
  2756. // Send out an event that these commands where run on this device
  2757. var targets = parent.CreateNodeDispatchTargets(node.meshid, node._id, ['server-users', user._id]);
  2758. var event = { etype: 'node', userid: user._id, username: user.name, nodeid: node._id, action: 'runcommands', msg: 'Running commands', msgid: msgid, cmds: command.cmds, cmdType: command.type, runAsUser: command.runAsUser, domain: domain.id };
  2759. parent.parent.DispatchEvent(targets, obj, event);
  2760. } else if (parent.parent.multiServer != null) { // peering setup
  2761. // Send the commands to the agent
  2762. parent.parent.multiServer.DispatchMessage({ action: 'agentCommand', nodeid: node._id, command: theCommand});
  2763. if (command.responseid != null && command.reply == false) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'OK' })); } catch (ex) { } }
  2764. // Send out an event that these commands where run on this device
  2765. var targets = parent.CreateNodeDispatchTargets(node.meshid, node._id, ['server-users', user._id]);
  2766. var event = { etype: 'node', userid: user._id, username: user.name, nodeid: node._id, action: 'runcommands', msg: 'Running commands', msgid: msgid, cmds: command.cmds, cmdType: command.type, runAsUser: command.runAsUser, domain: domain.id };
  2767. parent.parent.multiServer.DispatchEvent(targets, obj, event);
  2768. } else {
  2769. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'Agent not connected' })); } catch (ex) { } }
  2770. }
  2771. } else {
  2772. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'runcommands', responseid: command.responseid, result: 'Invalid command type' })); } catch (ex) { } }
  2773. }
  2774. }
  2775. });
  2776. }
  2777. }
  2778. if (typeof command.presetcmd == 'number') {
  2779. // If a pre-set command is used, load the command
  2780. if (Array.isArray(domain.preconfiguredscripts) == false) return;
  2781. const script = domain.preconfiguredscripts[command.presetcmd];
  2782. if (script == null) return;
  2783. delete command.presetcmd;
  2784. // Decode script type
  2785. const types = ['', 'bat', 'ps1', 'sh', 'agent']; // 1 = Windows Command, 2 = Windows PowerShell, 3 = Linux, 4 = Agent
  2786. if (typeof script.type == 'string') { const stype = types.indexOf(script.type.toLowerCase()); if (stype > 0) { command.type = stype; } }
  2787. if (command.type == null) return;
  2788. // Decode script runas
  2789. if (command.type != 4) {
  2790. const runAsModes = ['agent', 'userfirst', 'user']; // 0 = AsAgent, 1 = UserFirst, 2 = UserOnly
  2791. if (typeof script.runas == 'string') { const srunas = runAsModes.indexOf(script.runas.toLowerCase()); if (srunas >= 0) { command.runAsUser = srunas; } }
  2792. }
  2793. if (typeof script.file == 'string') {
  2794. // The pre-defined script commands are in a file, load it
  2795. const scriptPath = parent.common.joinPath(parent.parent.datapath, script.file);
  2796. fs.readFile(scriptPath, function (err, data) {
  2797. // If loaded correctly, run loaded commands
  2798. if ((err != null) || (data == null) || (data.length == 0) || (data.length > 65535)) return;
  2799. command.cmds = data.toString();
  2800. processRunCommand(command);
  2801. });
  2802. } else if (typeof script.cmd == 'string') {
  2803. // The pre-defined script commands are right in the config.json, use that
  2804. command.cmds = script.cmd;
  2805. processRunCommand(command);
  2806. }
  2807. } else if (typeof command.cmdpath == 'string') {
  2808. // If a server command path is used, load the script from the path
  2809. var file = parent.getServerFilePath(user, domain, command.cmdpath);
  2810. if (file != null) {
  2811. fs.readFile(file.fullpath, function (err, data) {
  2812. // If loaded correctly, run loaded commands
  2813. if ((err != null) || (data == null) || (data.length == 0) || (data.length > 65535)) return;
  2814. command.cmds = data.toString();
  2815. delete command.cmdpath;
  2816. processRunCommand(command);
  2817. });
  2818. }
  2819. } else if (typeof command.cmds == 'string') {
  2820. // Run provided commands
  2821. if (command.cmds.length > 65535) return;
  2822. processRunCommand(command);
  2823. }
  2824. break;
  2825. }
  2826. case 'uninstallagent':
  2827. {
  2828. if (common.validateArray(command.nodeids, 1) == false) break; // Check nodeid's
  2829. for (i in command.nodeids) {
  2830. // Get the node and the rights for this node
  2831. parent.GetNodeWithRights(domain, user, command.nodeids[i], function (node, rights, visible) {
  2832. // Check we have the rights to delete this device
  2833. if ((rights & MESHRIGHT_UNINSTALL) == 0) return;
  2834. // Send uninstall command to connected agent
  2835. const agent = parent.wsagents[node._id];
  2836. if (agent != null) {
  2837. //console.log('Asking agent ' + agent.dbNodeKey + ' to uninstall.');
  2838. try { agent.send(JSON.stringify({ action: 'uninstallagent' })); } catch (ex) { }
  2839. }
  2840. });
  2841. }
  2842. break;
  2843. }
  2844. case 'poweraction':
  2845. {
  2846. if (common.validateArray(command.nodeids, 1) == false) break; // Check nodeid's
  2847. if (common.validateInt(command.actiontype, 2, 401) == false) break; // Check actiontype
  2848. for (i in command.nodeids) {
  2849. var nodeid = command.nodeids[i];
  2850. // Argument validation
  2851. if (common.validateString(nodeid, 8, 128) == false) { continue; } // Check the nodeid
  2852. else if (nodeid.indexOf('/') == -1) { nodeid = 'node/' + domain.id + '/' + nodeid; }
  2853. else if ((nodeid.split('/').length != 3) || (nodeid.split('/')[1] != domain.id)) { continue; } // Invalid domain, operation only valid for current domain
  2854. // Get the node and the rights for this node
  2855. parent.GetNodeWithRights(domain, user, nodeid, function (node, rights, visible) {
  2856. if ((command.actiontype >= 400) && ((rights & MESHRIGHT_REMOTECONTROL) != 0)) {
  2857. // Flash and vibrate
  2858. if ((command.actiontype == 400) && common.validateInt(command.time, 1, 30000)) { routeCommandToNode({ action: 'msg', type: 'console', nodeid: node._id, value: 'flash ' + command.time }, MESHRIGHT_ADMIN, 0); }
  2859. if ((command.actiontype == 401) && common.validateInt(command.time, 1, 30000)) { routeCommandToNode({ action: 'msg', type: 'console', nodeid: node._id, value: 'vibrate ' + command.time }, MESHRIGHT_ADMIN, 0); }
  2860. } else {
  2861. // Check we have the rights to perform this operation
  2862. if ((command.actiontype == 302) && ((rights & MESHRIGHT_WAKEDEVICE) == 0)) return; // This is a Intel AMT power on operation, check if we have WAKE rights
  2863. if ((command.actiontype != 302) && ((rights & MESHRIGHT_RESETOFF) == 0)) return; // For all other operations, check that we have RESET/OFF rights
  2864. // If this device is connected on MQTT, send a power action.
  2865. if ((parent.parent.mqttbroker != null) && (command.actiontype >= 0) && (command.actiontype <= 4)) { parent.parent.mqttbroker.publish(node._id, 'powerAction', ['', '', 'poweroff', 'reset', 'sleep'][command.actiontype]); }
  2866. // If this is a IP-KVM or Power Distribution Unit (PDU), dispatch an action event
  2867. if (node.mtype == 4) {
  2868. // Send out an event to perform turn off command on the port
  2869. const targets = parent.CreateNodeDispatchTargets(node.meshid, node._id, ['devport-operation', 'server-users', user._id]);
  2870. const event = { etype: 'node', userid: user._id, username: user.name, nodeid: node._id, action: 'turnoff', domain: domain.id, portid: node.portid, porttype: node.porttype, portnum: node.portnum, meshid: node.meshid, mtype: node.mtype, msgid: 133, msg: "Turn off." };
  2871. parent.parent.DispatchEvent(targets, obj, event);
  2872. return;
  2873. }
  2874. if ((command.actiontype >= 300) && (command.actiontype < 400)) {
  2875. if ((command.actiontype != 302) && (command.actiontype != 308) && (command.actiontype < 310) && (command.actiontype > 316)) return; // Invalid action type.
  2876. // Intel AMT power command, actiontype: 2 = Power on, 8 = Power down, 10 = reset, 11 = Power on to BIOS, 12 = Reset to BIOS, 13 = Power on to BIOS with SOL, 14 = Reset to BIOS with SOL, 15 = Power on to PXE, 16 = Reset to PXE
  2877. parent.parent.DispatchEvent('*', obj, { action: 'amtpoweraction', userid: user._id, username: user.name, nodeids: [node._id], domain: domain.id, nolog: 1, actiontype: command.actiontype - 300 });
  2878. } else {
  2879. if ((command.actiontype < 2) && (command.actiontype > 4)) return; // Invalid action type.
  2880. // Mesh Agent power command, get this device and send the power command
  2881. const agent = parent.wsagents[node._id];
  2882. if (agent != null) {
  2883. try { agent.send(JSON.stringify({ action: 'poweraction', actiontype: command.actiontype, userid: user._id, username: user.name, remoteaddr: req.clientIp })); } catch (ex) { }
  2884. }
  2885. }
  2886. }
  2887. });
  2888. // Confirm we may be doing something (TODO)
  2889. if (command.responseid != null) {
  2890. try { ws.send(JSON.stringify({ action: 'poweraction', responseid: command.responseid, result: 'ok' })); } catch (ex) { }
  2891. } else {
  2892. try { ws.send(JSON.stringify({ action: 'poweraction' })); } catch (ex) { }
  2893. }
  2894. }
  2895. break;
  2896. }
  2897. case 'toast':
  2898. {
  2899. var err = null;
  2900. // Perform input validation
  2901. try {
  2902. if (common.validateStrArray(command.nodeids, 1, 256) == false) { err = "Invalid nodeids"; } // Check nodeids
  2903. else if (common.validateString(command.msg, 1, 4096) == false) { err = "Invalid message"; } // Check message
  2904. else {
  2905. var nodeids = [];
  2906. for (i in command.nodeids) { if (command.nodeids[i].indexOf('/') == -1) { nodeids.push('node/' + domain.id + '/' + command.nodeids[i]); } else { nodeids.push(command.nodeids[i]); } }
  2907. command.nodeids = nodeids;
  2908. }
  2909. } catch (ex) { console.log(ex); err = "Validation exception: " + ex; }
  2910. // Handle any errors
  2911. if (err != null) {
  2912. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'toast', responseid: command.responseid, result: err })); } catch (ex) { } }
  2913. break;
  2914. }
  2915. // Check the title, if needed, use a default one
  2916. if (common.validateString(command.title, 1, 512) == false) { delete command.title } // Check title
  2917. if ((command.title == null) && (typeof domain.notificationmessages == 'object') && (typeof domain.notificationmessages.title == 'string')) { command.title = domain.notificationmessages.title; }
  2918. if ((command.title == null) && (typeof domain.title == 'string')) { command.title = domain.title; }
  2919. if (command.title == null) { command.title = "MeshCentral"; }
  2920. for (i in command.nodeids) {
  2921. // Get the node and the rights for this node
  2922. parent.GetNodeWithRights(domain, user, command.nodeids[i], function (node, rights, visible) {
  2923. // Check we have the rights to notify this device
  2924. if ((rights & MESHRIGHT_CHATNOTIFY) == 0) {
  2925. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'toast', responseid: command.responseid, result: 'Access Denied' })); } catch (ex) { } }
  2926. return;
  2927. }
  2928. // Get this device and send toast command
  2929. const agent = parent.wsagents[node._id];
  2930. if (agent != null) {
  2931. try { agent.send(JSON.stringify({ action: 'toast', title: command.title, msg: command.msg, sessionid: ws.sessionId, username: user.name, userid: user._id })); } catch (ex) { }
  2932. }
  2933. });
  2934. }
  2935. // Send response if required
  2936. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'toast', responseid: command.responseid, result: 'ok' })); } catch (ex) { } }
  2937. break;
  2938. }
  2939. case 'changedevice':
  2940. {
  2941. var err = null;
  2942. // Argument validation
  2943. try {
  2944. if (common.validateString(command.nodeid, 1, 1024) == false) { err = "Invalid nodeid"; } // Check nodeid
  2945. else {
  2946. if (command.nodeid.indexOf('/') == -1) { command.nodeid = 'node/' + domain.id + '/' + command.nodeid; }
  2947. if ((command.nodeid.split('/').length != 3) || (command.nodeid.split('/')[1] != domain.id)) { err = "Invalid nodeid"; } // Invalid domain, operation only valid for current domain
  2948. else if ((command.userloc) && (command.userloc.length != 2) && (command.userloc.length != 0)) { err = "Invalid user location"; }
  2949. }
  2950. } catch (ex) { console.log(ex); err = "Validation exception: " + ex; }
  2951. // Handle any errors
  2952. if (err != null) {
  2953. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'changedevice', responseid: command.responseid, result: err })); } catch (ex) { } }
  2954. break;
  2955. }
  2956. // Get the node and the rights for this node
  2957. parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
  2958. if ((rights & MESHRIGHT_MANAGECOMPUTERS) == 0) {
  2959. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'changedevice', responseid: command.responseid, result: 'Access Denied' })); } catch (ex) { } }
  2960. return;
  2961. }
  2962. node = common.unEscapeLinksFieldName(node); // unEscape node data for rdp/ssh credentials
  2963. var mesh = parent.meshes[node.meshid], amtchange = 0;
  2964. // Ready the node change event
  2965. var changes = [], event = { etype: 'node', userid: user._id, username: user.name, action: 'changenode', nodeid: node._id, domain: domain.id };
  2966. change = 0;
  2967. event.msg = ': ';
  2968. // If we are in WAN-only mode, host is not used
  2969. if ((args.wanonly == true) && (command.host) && (node.mtype != 3) && (node.mtype != 4)) { delete command.host; }
  2970. // Look for a change
  2971. if ((typeof command.icon == 'number') && (command.icon != node.icon)) { change = 1; node.icon = command.icon; changes.push('icon'); }
  2972. if ((typeof command.name == 'string') && (command.name != node.name)) { change = 1; node.name = command.name; changes.push('name'); }
  2973. if ((typeof command.host == 'string') && (command.host != node.host)) { change = 1; node.host = command.host; changes.push('host'); }
  2974. if (typeof command.consent == 'number') {
  2975. var oldConsent = node.consent;
  2976. if (command.consent != node.consent) { node.consent = command.consent; }
  2977. if (command.consent == 0) { delete node.consent; }
  2978. if (oldConsent != node.consent) { change = 1; changes.push('consent'); }
  2979. }
  2980. if ((typeof command.rdpport == 'number') && (command.rdpport > 0) && (command.rdpport < 65536)) {
  2981. if ((command.rdpport == 3389) && (node.rdpport != null)) {
  2982. delete node.rdpport; change = 1; changes.push('rdpport'); // Delete the RDP port
  2983. } else {
  2984. node.rdpport = command.rdpport; change = 1; changes.push('rdpport'); // Set the RDP port
  2985. }
  2986. }
  2987. if ((typeof command.rfbport == 'number') && (command.rfbport > 0) && (command.rfbport < 65536)) {
  2988. if ((command.rfbport == 5900) && (node.rfbport != null)) {
  2989. delete node.rfbport; change = 1; changes.push('rfbport'); // Delete the RFB port
  2990. } else {
  2991. node.rfbport = command.rfbport; change = 1; changes.push('rfbport'); // Set the RFB port
  2992. }
  2993. }
  2994. if ((typeof command.sshport == 'number') && (command.sshport > 0) && (command.sshport < 65536)) {
  2995. if ((command.sshport == 22) && (node.sshport != null)) {
  2996. delete node.sshport; change = 1; changes.push('sshport'); // Delete the SSH port
  2997. } else {
  2998. node.sshport = command.sshport; change = 1; changes.push('sshport'); // Set the SSH port
  2999. }
  3000. }
  3001. if ((typeof command.httpport == 'number') && (command.httpport > 0) && (command.httpport < 65536)) {
  3002. if ((command.httpport == 80) && (node.httpport != null)) {
  3003. delete node.httpport; change = 1; changes.push('httpport'); // Delete the HTTP port
  3004. } else {
  3005. node.httpport = command.httpport; change = 1; changes.push('httpport'); // Set the HTTP port
  3006. }
  3007. }
  3008. if ((typeof command.httpsport == 'number') && (command.httpsport > 0) && (command.httpsport < 65536)) {
  3009. if ((command.httpsport == 443) && (node.httpsport != null)) {
  3010. delete node.httpsport; change = 1; changes.push('httpsport'); // Delete the HTTPS port
  3011. } else {
  3012. node.httpsport = command.httpsport; change = 1; changes.push('httpsport'); // Set the HTTPS port
  3013. }
  3014. }
  3015. if ((typeof command.ssh == 'number') && (command.ssh == 0)) {
  3016. if ((node.ssh != null) && (node.ssh[user._id] != null)) { delete node.ssh[user._id]; change = 1; changes.push('ssh'); } // Delete the SSH cendentials
  3017. }
  3018. if ((typeof command.rdp == 'number') && (command.rdp == 0)) {
  3019. if ((node.rdp != null) && (node.rdp[user._id] != null)) { delete node.rdp[user._id]; change = 1; changes.push('rdp'); } // Delete the RDP cendentials
  3020. }
  3021. // Clean up any legacy RDP and SSH credentials
  3022. if (node.rdp != null) { delete node.rdp.d; delete node.rdp.u; delete node.rdp.p; }
  3023. if (node.ssh != null) { delete node.ssh.u; delete node.ssh.p; delete node.ssh.k; delete node.ssh.kp; }
  3024. if (domain.geolocation && command.userloc && ((node.userloc == null) || (command.userloc[0] != node.userloc[0]) || (command.userloc[1] != node.userloc[1]))) {
  3025. change = 1;
  3026. if ((command.userloc.length == 0) && (node.userloc)) {
  3027. delete node.userloc;
  3028. changes.push('location removed');
  3029. } else {
  3030. command.userloc.push((Math.floor((new Date()) / 1000)));
  3031. node.userloc = command.userloc.join(',');
  3032. changes.push('location');
  3033. }
  3034. }
  3035. if (command.desc != null && (command.desc != node.desc)) { change = 1; node.desc = command.desc; changes.push('description'); }
  3036. if (command.intelamt != null) {
  3037. if ((parent.parent.amtManager == null) || (node.intelamt.pass == null) || (node.intelamt.pass == '') || ((node.intelamt.warn != null) && (((node.intelamt.warn) & 9) != 0))) { // Only allow changes to Intel AMT credentials if AMT manager is not running, or manager warned of unknown/trying credentials.
  3038. if ((command.intelamt.user != null) && (command.intelamt.pass != null) && ((command.intelamt.user != node.intelamt.user) || (command.intelamt.pass != node.intelamt.pass))) {
  3039. change = 1;
  3040. node.intelamt.user = command.intelamt.user;
  3041. node.intelamt.pass = command.intelamt.pass;
  3042. node.intelamt.warn |= 8; // Change warning to "Trying". Bit flags: 1 = Unknown credentials, 2 = Realm Mismatch, 4 = TLS Cert Mismatch, 8 = Trying credentials
  3043. changes.push('Intel AMT credentials');
  3044. amtchange = 1;
  3045. }
  3046. }
  3047. // Only allow the user to set Intel AMT TLS state if AMT Manager is not active. AMT manager will auto-detect TLS state.
  3048. if ((parent.parent.amtManager != null) && (command.intelamt.tls != null) && (command.intelamt.tls != node.intelamt.tls)) { change = 1; node.intelamt.tls = command.intelamt.tls; changes.push('Intel AMT TLS'); }
  3049. }
  3050. if (command.tags) { // Node grouping tag, this is a array of strings that can't be empty and can't contain a comma
  3051. var ok = true, group2 = [];
  3052. if (common.validateString(command.tags, 0, 4096) == true) { command.tags = command.tags.split(','); }
  3053. for (var i in command.tags) { var tname = command.tags[i].trim(); if ((tname.length > 0) && (tname.length < 64) && (group2.indexOf(tname) == -1)) { group2.push(tname); } }
  3054. group2.sort();
  3055. if (node.tags != group2) { node.tags = group2; change = 1; }
  3056. } else if ((command.tags === '') && node.tags) { delete node.tags; change = 1; }
  3057. if (change == 1) {
  3058. // Save the node
  3059. db.Set(parent.cleanDevice(node));
  3060. // Event the node change. Only do this if the database will not do it.
  3061. event.msg = 'Changed device ' + node.name + ' from group ' + mesh.name + ': ' + changes.join(', ');
  3062. event.node = parent.CloneSafeNode(node);
  3063. event.msgid = 140;
  3064. event.msgArgs = [ node.name, mesh.name, changes.join(', ') ];
  3065. if (amtchange == 1) { event.amtchange = 1; } // This will give a hint to the AMT Manager to reconnect using new AMT credentials
  3066. if (command.rdpport == 3389) { event.node.rdpport = 3389; }
  3067. if (command.rfbport == 5900) { event.node.rfbport = 5900; }
  3068. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the node. Another event will come.
  3069. parent.parent.DispatchEvent(parent.CreateNodeDispatchTargets(node.meshid, node._id, [user._id]), obj, event);
  3070. }
  3071. // Send response if required
  3072. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'changedevice', responseid: command.responseid, result: 'ok' })); } catch (ex) { } }
  3073. });
  3074. break;
  3075. }
  3076. case 'uploadagentcore':
  3077. {
  3078. if (common.validateString(command.type, 1, 40) == false) break; // Check path
  3079. if (common.validateArray(command.nodeids, 1) == false) break; // Check nodeid's
  3080. // Go thru all node identifiers and run the operation
  3081. for (var i in command.nodeids) {
  3082. var nodeid = command.nodeids[i];
  3083. if (typeof nodeid != 'string') return;
  3084. // Get the node and the rights for this node
  3085. parent.GetNodeWithRights(domain, user, nodeid, function (node, rights, visible) {
  3086. if ((node == null) || (((rights & MESHRIGHT_AGENTCONSOLE) == 0) && (user.siteadmin != SITERIGHT_ADMIN))) return;
  3087. // TODO: If we have peer servers, inform...
  3088. //if (parent.parent.multiServer != null) { parent.parent.multiServer.DispatchMessage({ action: 'uploadagentcore', sessionid: ws.sessionId }); }
  3089. if (command.type == 'default') {
  3090. // Send the default core to the agent
  3091. parent.parent.updateMeshCore(function () { parent.sendMeshAgentCore(user, domain, node._id, 'default'); });
  3092. } else if (command.type == 'clear') {
  3093. // Clear the mesh agent core on the mesh agent
  3094. parent.sendMeshAgentCore(user, domain, node._id, 'clear');
  3095. } else if (command.type == 'recovery') {
  3096. // Send the recovery core to the agent
  3097. parent.sendMeshAgentCore(user, domain, node._id, 'recovery');
  3098. } else if (command.type == 'tiny') {
  3099. // Send the tiny core to the agent
  3100. parent.sendMeshAgentCore(user, domain, node._id, 'tiny');
  3101. } else if ((command.type == 'custom') && (common.validateString(command.path, 1, 2048) == true)) {
  3102. // Send a mesh agent core to the mesh agent
  3103. var file = parent.getServerFilePath(user, domain, command.path);
  3104. if (file != null) {
  3105. fs.readFile(file.fullpath, 'utf8', function (err, data) {
  3106. if (err != null) {
  3107. data = common.IntToStr(0) + data; // Add the 4 bytes encoding type & flags (Set to 0 for raw)
  3108. parent.sendMeshAgentCore(user, domain, node._id, 'custom', data);
  3109. }
  3110. });
  3111. }
  3112. }
  3113. });
  3114. }
  3115. break;
  3116. }
  3117. case 'inviteAgent':
  3118. {
  3119. var err = null, mesh = null;
  3120. // Resolve the device group name if needed
  3121. if ((typeof command.meshname == 'string') && (command.meshid == null)) {
  3122. for (var i in parent.meshes) {
  3123. var m = parent.meshes[i];
  3124. if ((m.mtype == 2) && (m.name == command.meshname) && parent.IsMeshViewable(user, m)) {
  3125. if (command.meshid == null) { command.meshid = m._id; } else { err = 'Duplicate device groups found'; }
  3126. }
  3127. }
  3128. }
  3129. try {
  3130. if ((domain.mailserver == null) || (args.lanonly == true)) { err = 'Unsupported feature'; } // This operation requires the email server
  3131. else if ((parent.parent.certificates.CommonName == null) || (parent.parent.certificates.CommonName.indexOf('.') == -1)) { err = 'Unsupported feature'; } // Server name must be configured
  3132. else if (common.validateString(command.meshid, 8, 134) == false) { err = 'Invalid group identifier'; } // Check meshid
  3133. else {
  3134. if (command.meshid.split('/').length == 1) { command.meshid = 'mesh/' + domain.id + '/' + command.meshid; }
  3135. if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) { err = 'Invalid group identifier'; } // Invalid domain, operation only valid for current domain
  3136. else if (common.validateString(command.email, 4, 1024) == false) { err = 'Invalid email'; } // Check email
  3137. else if (command.email.split('@').length != 2) { err = 'Invalid email'; } // Check email
  3138. else {
  3139. mesh = parent.meshes[command.meshid];
  3140. if (mesh == null) { err = 'Unknown device group'; } // Check if the group exists
  3141. else if (mesh.mtype != 2) { err = 'Invalid group type'; } // Check if this is the correct group type
  3142. else if (parent.IsMeshViewable(user, mesh) == false) { err = 'Not allowed'; } // Check if this user has rights to do this
  3143. }
  3144. }
  3145. } catch (ex) { err = 'Validation exception: ' + ex; }
  3146. // Handle any errors
  3147. if (err != null) {
  3148. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'inviteAgent', responseid: command.responseid, result: err })); } catch (ex) { } }
  3149. break;
  3150. }
  3151. // Perform email invitation
  3152. domain.mailserver.sendAgentInviteMail(domain, (user.realname ? user.realname : user.name), command.email.toLowerCase(), command.meshid, command.name, command.os, command.msg, command.flags, command.expire, parent.getLanguageCodes(req), req.query.key);
  3153. // Send a response if needed
  3154. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'inviteAgent', responseid: command.responseid, result: 'ok' })); } catch (ex) { } }
  3155. break;
  3156. }
  3157. case 'setDeviceEvent':
  3158. {
  3159. // Argument validation
  3160. if (common.validateString(command.msg, 1, 4096) == false) break; // Check event
  3161. // Get the node and the rights for this node
  3162. parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
  3163. if (rights == 0) return;
  3164. // Add an event for this device
  3165. var targets = parent.CreateNodeDispatchTargets(node.meshid, node._id, ['server-users', user._id]);
  3166. var event = { etype: 'node', userid: user._id, username: user.name, nodeid: node._id, action: 'manual', msg: decodeURIComponent(command.msg), domain: domain.id };
  3167. parent.parent.DispatchEvent(targets, obj, event);
  3168. });
  3169. break;
  3170. }
  3171. case 'setNotes':
  3172. {
  3173. // Argument validation
  3174. if (common.validateString(command.id, 1, 1024) == false) break; // Check id
  3175. var splitid = command.id.split('/');
  3176. if ((splitid.length != 3) || (splitid[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
  3177. var idtype = splitid[0];
  3178. if ((idtype != 'puser') && (idtype != 'user') && (idtype != 'mesh') && (idtype != 'node')) return;
  3179. if (idtype == 'node') {
  3180. // Get the node and the rights for this node
  3181. parent.GetNodeWithRights(domain, user, command.id, function (node, rights, visible) {
  3182. if ((rights & MESHRIGHT_SETNOTES) != 0) {
  3183. // Set the id's notes
  3184. if (common.validateString(command.notes, 1) == false) {
  3185. db.Remove('nt' + node._id); // Delete the note for this node
  3186. } else {
  3187. db.Set({ _id: 'nt' + node._id, type: 'note', value: command.notes }); // Set the note for this node
  3188. }
  3189. }
  3190. });
  3191. } else if (idtype == 'mesh') {
  3192. // Get the mesh for this device
  3193. mesh = parent.meshes[command.id];
  3194. if (mesh) {
  3195. // Check if this user has rights to do this
  3196. if ((parent.GetMeshRights(user, mesh) & MESHRIGHT_EDITMESH) == 0) return; // Must have rights to edit the mesh
  3197. // Set the id's notes
  3198. if (common.validateString(command.notes, 1) == false) {
  3199. db.Remove('nt' + command.id); // Delete the note for this node
  3200. } else {
  3201. db.Set({ _id: 'nt' + command.id, type: 'note', value: command.notes }); // Set the note for this mesh
  3202. }
  3203. }
  3204. } else if ((idtype == 'user') && ((user.siteadmin & 2) != 0)) {
  3205. // Set the id's notes
  3206. if (common.validateString(command.notes, 1) == false) {
  3207. db.Remove('nt' + command.id); // Delete the note for this node
  3208. } else {
  3209. // Can only perform this operation on other users of our group.
  3210. var chguser = parent.users[command.id];
  3211. if (chguser == null) break; // This user does not exists
  3212. if ((user.groups != null) && (user.groups.length > 0) && ((chguser.groups == null) || (findOne(chguser.groups, user.groups) == false))) break;
  3213. db.Set({ _id: 'nt' + command.id, type: 'note', value: command.notes }); // Set the note for this user
  3214. }
  3215. } else if (idtype == 'puser') {
  3216. // Set the user's personal note, starts with 'ntp' + userid.
  3217. if (common.validateString(command.notes, 1) == false) {
  3218. db.Remove('ntp' + user._id); // Delete the note for this node
  3219. } else {
  3220. db.Set({ _id: 'ntp' + user._id, type: 'note', value: command.notes }); // Set the note for this user
  3221. }
  3222. }
  3223. break;
  3224. }
  3225. case 'otpemail':
  3226. {
  3227. // Do not allow this command if 2FA's are locked
  3228. if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return;
  3229. // Do not allow this command when logged in using a login token
  3230. if (req.session.loginToken != null) break;
  3231. if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return; // If this account is settings locked, return here.
  3232. // Check input
  3233. if (typeof command.enabled != 'boolean') return;
  3234. // See if we really need to change the state
  3235. if ((command.enabled === true) && (user.otpekey != null)) return;
  3236. if ((command.enabled === false) && (user.otpekey == null)) return;
  3237. // Change the email 2FA of this user
  3238. if (command.enabled === true) { user.otpekey = {}; } else { delete user.otpekey; }
  3239. parent.db.SetUser(user);
  3240. ws.send(JSON.stringify({ action: 'otpemail', success: true, enabled: command.enabled })); // Report success
  3241. // Notify change
  3242. var targets = ['*', 'server-users', user._id];
  3243. if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
  3244. var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msgid: command.enabled ? 88 : 89, msg: command.enabled ? "Enabled email two-factor authentication." : "Disabled email two-factor authentication.", domain: domain.id };
  3245. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  3246. parent.parent.DispatchEvent(targets, obj, event);
  3247. break;
  3248. }
  3249. case 'otpduo':
  3250. {
  3251. // Do not allow this command if 2FA's are locked
  3252. if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return;
  3253. // Do not allow if Duo is not supported
  3254. if ((typeof domain.duo2factor != 'object') || (typeof domain.duo2factor.integrationkey != 'string') || (typeof domain.duo2factor.secretkey != 'string') || (typeof domain.duo2factor.apihostname != 'string')) return;
  3255. // Do not allow if Duo is disabled
  3256. if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.duo2factor == false)) return;
  3257. // Do not allow this command when logged in using a login token
  3258. if (req.session.loginToken != null) break;
  3259. if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return; // If this account is settings locked, return here.
  3260. // Check input
  3261. if ((typeof command.enabled != 'boolean') || (command.enabled != false)) return;
  3262. // See if we really need to change the state
  3263. if ((command.enabled === false) && (user.otpduo == null)) return;
  3264. // Change the duo 2FA of this user
  3265. delete user.otpduo;
  3266. parent.db.SetUser(user);
  3267. ws.send(JSON.stringify({ action: 'otpduo', success: true, enabled: command.enabled })); // Report success
  3268. // Notify change
  3269. var targets = ['*', 'server-users', user._id];
  3270. if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
  3271. var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msgid: command.enabled ? 160 : 161, msg: command.enabled ? "Enabled duo two-factor authentication." : "Disabled duo two-factor authentication.", domain: domain.id };
  3272. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  3273. parent.parent.DispatchEvent(targets, obj, event);
  3274. break;
  3275. }
  3276. case 'otpauth-request':
  3277. {
  3278. // Do not allow this command if 2FA's are locked
  3279. if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) { ws.send(JSON.stringify({ action: 'otpauth-request', err: 1 })); return; }
  3280. // Do not allow this command when logged in using a login token
  3281. if (req.session.loginToken != null) { ws.send(JSON.stringify({ action: 'otpauth-request', err: 3 })); return; }
  3282. // Check of OTP 2FA is allowed
  3283. if ((domain.passwordrequirements) && (domain.passwordrequirements.otp2factor == false)) { ws.send(JSON.stringify({ action: 'otpauth-request', err: 4 })); return; }
  3284. if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) { ws.send(JSON.stringify({ action: 'otpauth-request', err: 5 })); return; } // If this account is settings locked, return here.
  3285. // Check if 2-step login is supported
  3286. const twoStepLoginSupported = ((parent.parent.config.settings.no2factorauth !== true) && (domain.auth != 'sspi') && (parent.parent.certificates.CommonName.indexOf('.') != -1) && (args.nousers !== true));
  3287. if (twoStepLoginSupported) {
  3288. // Request a one time password to be setup
  3289. var otplib = null;
  3290. try { otplib = require('otplib'); } catch (ex) { }
  3291. if (otplib == null) { ws.send(JSON.stringify({ action: 'otpauth-request', err: 6 })); return; }
  3292. const secret = otplib.authenticator.generateSecret(); // TODO: Check the random source of this value.
  3293. var domainName = parent.certificates.CommonName;
  3294. if (domain.dns != null) {
  3295. domainName = domain.dns;
  3296. } else if (domain.dns == null && domain.id != '') {
  3297. domainName += "/" + domain.id;
  3298. }
  3299. ws.send(JSON.stringify({ action: 'otpauth-request', secret: secret, url: otplib.authenticator.keyuri(user.name, domainName, secret) }));
  3300. }
  3301. break;
  3302. }
  3303. case 'otpauth-setup':
  3304. {
  3305. // Do not allow this command if 2FA's are locked
  3306. if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return;
  3307. // Do not allow this command when logged in using a login token
  3308. if (req.session.loginToken != null) break;
  3309. // Check of OTP 2FA is allowed
  3310. if ((domain.passwordrequirements) && (domain.passwordrequirements.otp2factor == false)) break;
  3311. if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return; // If this account is settings locked, return here.
  3312. // Check if 2-step login is supported
  3313. const twoStepLoginSupported = ((parent.parent.config.settings.no2factorauth !== true) && (domain.auth != 'sspi') && (parent.parent.certificates.CommonName.indexOf('.') != -1) && (args.nousers !== true));
  3314. if (twoStepLoginSupported) {
  3315. // Perform the one time password setup
  3316. var otplib = null;
  3317. try { otplib = require('otplib'); } catch (ex) { }
  3318. if (otplib == null) { break; }
  3319. otplib.authenticator.options = { window: 2 }; // Set +/- 1 minute window
  3320. if (otplib.authenticator.check(command.token, command.secret) === true) {
  3321. // Token is valid, activate 2-step login on this account.
  3322. user.otpsecret = command.secret;
  3323. parent.db.SetUser(user);
  3324. ws.send(JSON.stringify({ action: 'otpauth-setup', success: true })); // Report success
  3325. // Notify change
  3326. var targets = ['*', 'server-users', user._id];
  3327. if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
  3328. var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msgid: 90, msg: 'Added authentication application', domain: domain.id };
  3329. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  3330. parent.parent.DispatchEvent(targets, obj, event);
  3331. } else {
  3332. ws.send(JSON.stringify({ action: 'otpauth-setup', success: false })); // Report fail
  3333. }
  3334. }
  3335. break;
  3336. }
  3337. case 'otpauth-clear':
  3338. {
  3339. // Do not allow this command if 2FA's are locked
  3340. if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return;
  3341. // Do not allow this command when logged in using a login token
  3342. if (req.session.loginToken != null) break;
  3343. if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return; // If this account is settings locked, return here.
  3344. // Check if 2-step login is supported
  3345. const twoStepLoginSupported = ((parent.parent.config.settings.no2factorauth !== true) && (domain.auth != 'sspi') && (parent.parent.certificates.CommonName.indexOf('.') != -1) && (args.nousers !== true));
  3346. if (twoStepLoginSupported) {
  3347. // Clear the one time password secret
  3348. if (user.otpsecret) {
  3349. delete user.otpsecret;
  3350. parent.db.SetUser(user);
  3351. ws.send(JSON.stringify({ action: 'otpauth-clear', success: true })); // Report success
  3352. // Notify change
  3353. var targets = ['*', 'server-users', user._id];
  3354. if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
  3355. var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msgid: 91, msg: 'Removed authentication application', domain: domain.id };
  3356. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  3357. parent.parent.DispatchEvent(targets, obj, event);
  3358. } else {
  3359. ws.send(JSON.stringify({ action: 'otpauth-clear', success: false })); // Report fail
  3360. }
  3361. }
  3362. break;
  3363. }
  3364. case 'otpauth-getpasswords':
  3365. {
  3366. // Do not allow this command if 2FA's are locked
  3367. if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return;
  3368. // Do not allow this command if backup codes are not allowed
  3369. if ((domain.passwordrequirements) && (domain.passwordrequirements.backupcode2factor == false)) return;
  3370. // Do not allow this command when logged in using a login token
  3371. if (req.session.loginToken != null) break;
  3372. // Check if 2-step login is supported
  3373. const twoStepLoginSupported = ((parent.parent.config.settings.no2factorauth !== true) && (domain.auth != 'sspi') && (parent.parent.certificates.CommonName.indexOf('.') != -1) && (args.nousers !== true));
  3374. if (twoStepLoginSupported == false) break;
  3375. var actionTaken = false, actionText = null, actionId = 0;
  3376. if ((user.siteadmin == 0xFFFFFFFF) || ((user.siteadmin & 1024) == 0)) { // Don't allow generation of tokens if the account is settings locked
  3377. // Perform a sub-action
  3378. if (command.subaction == 1) { // Generate a new set of tokens
  3379. var randomNumbers = [], v;
  3380. for (var i = 0; i < 10; i++) { do { v = getRandomEightDigitInteger(); } while (randomNumbers.indexOf(v) >= 0); randomNumbers.push(v); }
  3381. user.otpkeys = { keys: [] };
  3382. for (var i = 0; i < 10; i++) { user.otpkeys.keys[i] = { p: randomNumbers[i], u: true } }
  3383. actionTaken = true;
  3384. actionId = 92;
  3385. actionText = "New 2FA backup codes generated";
  3386. } else if (command.subaction == 2) { // Clear all tokens
  3387. actionTaken = (user.otpkeys != null);
  3388. delete user.otpkeys;
  3389. if (actionTaken) {
  3390. actionId = 93;
  3391. actionText = "2FA backup codes cleared";
  3392. }
  3393. }
  3394. // Save the changed user
  3395. if (actionTaken) { parent.db.SetUser(user); }
  3396. }
  3397. // Return one time passwords for this user
  3398. if (count2factoraAuths() > 0) {
  3399. ws.send(JSON.stringify({ action: 'otpauth-getpasswords', passwords: user.otpkeys ? user.otpkeys.keys : null }));
  3400. }
  3401. // Notify change
  3402. if (actionText != null) {
  3403. var targets = ['*', 'server-users', user._id];
  3404. if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
  3405. var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msgid: actionId, msg: actionText, domain: domain.id };
  3406. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  3407. parent.parent.DispatchEvent(targets, obj, event);
  3408. }
  3409. break;
  3410. }
  3411. case 'otp-hkey-get':
  3412. {
  3413. // Do not allow this command if 2FA's are locked
  3414. if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return;
  3415. // Do not allow this command when logged in using a login token
  3416. if (req.session.loginToken != null) break;
  3417. // Check if 2-step login is supported
  3418. const twoStepLoginSupported = ((parent.parent.config.settings.no2factorauth !== true) && (domain.auth != 'sspi') && (parent.parent.certificates.CommonName.indexOf('.') != -1) && (args.nousers !== true));
  3419. if (twoStepLoginSupported == false) break;
  3420. // Send back the list of keys we have, just send the list of names and index
  3421. var hkeys = [];
  3422. if (user.otphkeys != null) { for (var i = 0; i < user.otphkeys.length; i++) { hkeys.push({ i: user.otphkeys[i].keyIndex, name: user.otphkeys[i].name, type: user.otphkeys[i].type }); } }
  3423. ws.send(JSON.stringify({ action: 'otp-hkey-get', keys: hkeys }));
  3424. break;
  3425. }
  3426. case 'otp-hkey-remove':
  3427. {
  3428. // Do not allow this command if 2FA's are locked
  3429. if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return;
  3430. // Do not allow this command when logged in using a login token
  3431. if (req.session.loginToken != null) break;
  3432. if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return; // If this account is settings locked, return here.
  3433. // Check if 2-step login is supported
  3434. const twoStepLoginSupported = ((parent.parent.config.settings.no2factorauth !== true) && (domain.auth != 'sspi') && (parent.parent.certificates.CommonName.indexOf('.') != -1) && (args.nousers !== true));
  3435. if (twoStepLoginSupported == false || command.index == null) break;
  3436. // Remove a key
  3437. var foundAtIndex = -1;
  3438. if (user.otphkeys != null) { for (var i = 0; i < user.otphkeys.length; i++) { if (user.otphkeys[i].keyIndex == command.index) { foundAtIndex = i; } } }
  3439. if (foundAtIndex != -1) {
  3440. user.otphkeys.splice(foundAtIndex, 1);
  3441. parent.db.SetUser(user);
  3442. }
  3443. // Notify change
  3444. var targets = ['*', 'server-users', user._id];
  3445. if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
  3446. var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msgid: 94, msg: 'Removed security key', domain: domain.id };
  3447. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  3448. parent.parent.DispatchEvent(targets, obj, event);
  3449. break;
  3450. }
  3451. case 'otp-hkey-yubikey-add':
  3452. {
  3453. // Do not allow this command if 2FA's are locked or max keys reached
  3454. if (domain.passwordrequirements) {
  3455. if (domain.passwordrequirements.lock2factor == true) return;
  3456. if ((typeof domain.passwordrequirements.maxfidokeys == 'number') && (user.otphkeys) && (user.otphkeys.length >= domain.passwordrequirements.maxfidokeys)) return;
  3457. }
  3458. // Do not allow this command when logged in using a login token
  3459. if (req.session.loginToken != null) break;
  3460. if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return; // If this account is settings locked, return here.
  3461. // Yubico API id and signature key can be requested from https://upgrade.yubico.com/getapikey/
  3462. var yub = null;
  3463. try { yub = require('yub'); } catch (ex) { }
  3464. // Check if 2-step login is supported
  3465. const twoStepLoginSupported = ((parent.parent.config.settings.no2factorauth !== true) && (domain.auth != 'sspi') && (parent.parent.certificates.CommonName.indexOf('.') != -1) && (args.nousers !== true));
  3466. if ((yub == null) || (twoStepLoginSupported == false) || (typeof command.otp != 'string')) {
  3467. ws.send(JSON.stringify({ action: 'otp-hkey-yubikey-add', result: false, name: command.name }));
  3468. break;
  3469. }
  3470. // Check if Yubikey support is present or OTP no exactly 44 in length
  3471. if ((typeof domain.yubikey != 'object') || (typeof domain.yubikey.id != 'string') || (typeof domain.yubikey.secret != 'string') || (command.otp.length != 44)) {
  3472. ws.send(JSON.stringify({ action: 'otp-hkey-yubikey-add', result: false, name: command.name }));
  3473. break;
  3474. }
  3475. // TODO: Check if command.otp is modhex encoded, reject if not.
  3476. // Query the YubiKey server to validate the OTP
  3477. yub.init(domain.yubikey.id, domain.yubikey.secret);
  3478. yub.verify(command.otp, function (err, results) {
  3479. if ((results != null) && (results.status == 'OK')) {
  3480. var keyIndex = parent.crypto.randomBytes(4).readUInt32BE(0);
  3481. var keyId = command.otp.substring(0, 12);
  3482. if (user.otphkeys == null) { user.otphkeys = []; }
  3483. // Check if this key was already registered, if so, remove it.
  3484. var foundAtIndex = -1;
  3485. for (var i = 0; i < user.otphkeys.length; i++) { if (user.otphkeys[i].keyid == keyId) { foundAtIndex = i; } }
  3486. if (foundAtIndex != -1) { user.otphkeys.splice(foundAtIndex, 1); }
  3487. // Add the new key and notify
  3488. user.otphkeys.push({ name: command.name, type: 2, keyid: keyId, keyIndex: keyIndex });
  3489. parent.db.SetUser(user);
  3490. ws.send(JSON.stringify({ action: 'otp-hkey-yubikey-add', result: true, name: command.name, index: keyIndex }));
  3491. // Notify change TODO: Should be done on all sessions/servers for this user.
  3492. var targets = ['*', 'server-users', user._id];
  3493. if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
  3494. var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msgid: 95, msg: 'Added security key', domain: domain.id };
  3495. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  3496. parent.parent.DispatchEvent(targets, obj, event);
  3497. } else {
  3498. ws.send(JSON.stringify({ action: 'otp-hkey-yubikey-add', result: false, name: command.name }));
  3499. }
  3500. });
  3501. break;
  3502. }
  3503. case 'otpdev-clear':
  3504. {
  3505. // Do not allow this command if 2FA's are locked
  3506. if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return;
  3507. // Do not allow this command when logged in using a login token
  3508. if (req.session.loginToken != null) break;
  3509. // Remove the authentication push notification device
  3510. if (user.otpdev != null) {
  3511. // Change the user
  3512. user.otpdev = obj.dbNodeKey;
  3513. parent.db.SetUser(user);
  3514. // Notify change
  3515. var targets = ['*', 'server-users', user._id];
  3516. if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
  3517. var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msgid: 114, msg: "Removed push notification authentication device", domain: domain.id };
  3518. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  3519. parent.parent.DispatchEvent(targets, obj, event);
  3520. }
  3521. break;
  3522. }
  3523. case 'otpdev-set':
  3524. {
  3525. // Do not allow this command if 2FA's are locked
  3526. if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return;
  3527. // Do not allow this command when logged in using a login token
  3528. if (req.session.loginToken != null) break;
  3529. // Attempt to add a authentication push notification device
  3530. // This will only send a push notification to the device, the device needs to confirm for the auth device to be added.
  3531. if (common.validateString(command.nodeid, 1, 1024) == false) break; // Check nodeid
  3532. parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
  3533. // Only allow use of devices with full rights
  3534. if ((node == null) || (visible == false) || (rights != 0xFFFFFFFF) || (node.agent == null) || (node.agent.id != 14) || (node.pmt == null)) return;
  3535. // Encode the cookie
  3536. const code = Buffer.from(user.name).toString('base64');
  3537. const authCookie = parent.parent.encodeCookie({ a: 'addAuth', c: code, u: user._id, n: node._id });
  3538. // Send out a push message to the device
  3539. var payload = { notification: { title: "MeshCentral", body: user.name + " authentication" }, data: { url: '2fa://auth?code=' + code + '&c=' + authCookie } };
  3540. var options = { priority: 'High', timeToLive: 60 }; // TTL: 1 minute
  3541. parent.parent.firebase.sendToDevice(node, payload, options, function (id, err, errdesc) {
  3542. if (err == null) {
  3543. parent.parent.debug('email', 'Successfully auth addition send push message to device ' + node.name);
  3544. } else {
  3545. parent.parent.debug('email', 'Failed auth addition push message to device ' + node.name + ', error: ' + errdesc);
  3546. }
  3547. });
  3548. });
  3549. break;
  3550. }
  3551. case 'webauthn-startregister':
  3552. {
  3553. // Do not allow this command if 2FA's are locked or max keys reached
  3554. if (domain.passwordrequirements) {
  3555. if (domain.passwordrequirements.lock2factor == true) return;
  3556. if ((typeof domain.passwordrequirements.maxfidokeys == 'number') && (user.otphkeys) && (user.otphkeys.length >= domain.passwordrequirements.maxfidokeys)) return;
  3557. }
  3558. // Do not allow this command when logged in using a login token
  3559. if (req.session.loginToken != null) break;
  3560. if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return; // If this account is settings locked, return here.
  3561. // Check if 2-step login is supported
  3562. const twoStepLoginSupported = ((parent.parent.config.settings.no2factorauth !== true) && (domain.auth != 'sspi') && (parent.parent.certificates.CommonName.indexOf('.') != -1) && (args.nousers !== true));
  3563. if ((twoStepLoginSupported == false) || (command.name == null)) break;
  3564. // Send the registration request
  3565. var registrationOptions = parent.webauthn.generateRegistrationChallenge("Anonymous Service", { id: Buffer.from(user._id, 'binary').toString('base64'), name: user._id, displayName: user._id.split('/')[2] });
  3566. //console.log('registrationOptions', registrationOptions);
  3567. registrationOptions.userVerification = (domain.passwordrequirements && domain.passwordrequirements.fidopininput) ? domain.passwordrequirements.fidopininput : 'preferred'; // Use the domain setting if it exists, otherwise use 'preferred'.
  3568. obj.webAuthnReqistrationRequest = { action: 'webauthn-startregister', keyname: command.name, request: registrationOptions };
  3569. ws.send(JSON.stringify(obj.webAuthnReqistrationRequest));
  3570. break;
  3571. }
  3572. case 'webauthn-endregister':
  3573. {
  3574. // Do not allow this command if 2FA's are locked or max keys reached
  3575. if (domain.passwordrequirements) {
  3576. if (domain.passwordrequirements.lock2factor == true) return;
  3577. if ((typeof domain.passwordrequirements.maxfidokeys == 'number') && (user.otphkeys) && (user.otphkeys.length >= domain.passwordrequirements.maxfidokeys)) return;
  3578. }
  3579. // Do not allow this command when logged in using a login token
  3580. if (req.session.loginToken != null) break;
  3581. if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return; // If this account is settings locked, return here.
  3582. const twoStepLoginSupported = ((parent.parent.config.settings.no2factorauth !== true) && (domain.auth != 'sspi') && (parent.parent.certificates.CommonName.indexOf('.') != -1) && (args.nousers !== true));
  3583. if ((twoStepLoginSupported == false) || (obj.webAuthnReqistrationRequest == null)) return;
  3584. // Figure out the origin
  3585. var httpport = ((args.aliasport != null) ? args.aliasport : args.port);
  3586. var origin = "https://" + (domain.dns ? domain.dns : parent.certificates.CommonName);
  3587. if (httpport != 443) { origin += ':' + httpport; }
  3588. // Use internal WebAuthn module to check the response
  3589. var regResult = null;
  3590. try { regResult = parent.webauthn.verifyAuthenticatorAttestationResponse(command.response.response); } catch (ex) { regResult = { verified: false, error: ex }; }
  3591. if (regResult.verified === true) {
  3592. // Since we are registering a WebAuthn/FIDO2 key, remove all U2F keys (Type 1).
  3593. var otphkeys2 = [];
  3594. if (user.otphkeys && Array.isArray(user.otphkeys)) { for (var i = 0; i < user.otphkeys.length; i++) { if (user.otphkeys[i].type != 1) { otphkeys2.push(user.otphkeys[i]); } } }
  3595. user.otphkeys = otphkeys2;
  3596. // Add the new WebAuthn/FIDO2 keys
  3597. var keyIndex = parent.crypto.randomBytes(4).readUInt32BE(0);
  3598. if (user.otphkeys == null) { user.otphkeys = []; }
  3599. user.otphkeys.push({ name: obj.webAuthnReqistrationRequest.keyname, type: 3, publicKey: regResult.authrInfo.publicKey, counter: regResult.authrInfo.counter, keyIndex: keyIndex, keyId: regResult.authrInfo.keyId });
  3600. parent.db.SetUser(user);
  3601. ws.send(JSON.stringify({ action: 'otp-hkey-setup-response', result: true, name: command.name, index: keyIndex }));
  3602. // Notify change
  3603. var targets = ['*', 'server-users', user._id];
  3604. if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
  3605. var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msgid: 95, msg: 'Added security key', domain: domain.id };
  3606. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  3607. parent.parent.DispatchEvent(targets, obj, event);
  3608. } else {
  3609. //console.log('webauthn-endregister-error', regResult.error);
  3610. ws.send(JSON.stringify({ action: 'otp-hkey-setup-response', result: false, error: regResult.error, name: command.name, index: keyIndex }));
  3611. }
  3612. delete obj.hardwareKeyRegistrationRequest;
  3613. break;
  3614. }
  3615. case 'userWebState': {
  3616. if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return; // If this account is settings locked, return here.
  3617. if (common.validateString(command.state, 1, 30000) == false) break; // Check state size, no more than 30k
  3618. command.state = parent.filterUserWebState(command.state); // Filter the state to remove anything bad
  3619. if ((command.state == null) || (typeof command.state !== 'string')) break; // If state did not validate correctly, quit here.
  3620. command.domain = domain.id;
  3621. db.Set({ _id: 'ws' + user._id, state: command.state });
  3622. parent.parent.DispatchEvent([user._id], obj, { action: 'userWebState', nolog: 1, domain: domain.id, state: command.state });
  3623. break;
  3624. }
  3625. case 'getNotes':
  3626. {
  3627. // Argument validation
  3628. if (common.validateString(command.id, 1, 1024) == false) break; // Check id
  3629. var splitid = command.id.split('/');
  3630. if ((splitid.length != 3) || (splitid[1] != domain.id)) return; // Invalid domain, operation only valid for current domain
  3631. var idtype = splitid[0];
  3632. if ((idtype != 'puser') && (idtype != 'user') && (idtype != 'mesh') && (idtype != 'node')) return;
  3633. if (idtype == 'node') {
  3634. // Get the node and the rights for this node
  3635. parent.GetNodeWithRights(domain, user, command.id, function (node, rights, visible) {
  3636. if (visible == false) return;
  3637. // Get the notes about this node
  3638. db.Get('nt' + command.id, function (err, notes) {
  3639. try {
  3640. if ((notes == null) || (notes.length != 1)) { ws.send(JSON.stringify({ action: 'getNotes', id: command.id, notes: null })); return; }
  3641. ws.send(JSON.stringify({ action: 'getNotes', id: command.id, notes: notes[0].value }));
  3642. } catch (ex) { }
  3643. });
  3644. });
  3645. } else if (idtype == 'mesh') {
  3646. // Get the mesh for this device
  3647. mesh = parent.meshes[command.id];
  3648. if (mesh) {
  3649. // Check if this user has rights to do this
  3650. if ((parent.GetMeshRights(user, mesh) & MESHRIGHT_EDITMESH) == 0) return; // Must have rights to edit the mesh
  3651. // Get the notes about this node
  3652. db.Get('nt' + command.id, function (err, notes) {
  3653. try {
  3654. if ((notes == null) || (notes.length != 1)) { ws.send(JSON.stringify({ action: 'getNotes', id: command.id, notes: null })); return; }
  3655. ws.send(JSON.stringify({ action: 'getNotes', id: command.id, notes: notes[0].value }));
  3656. } catch (ex) { }
  3657. });
  3658. }
  3659. } else if ((idtype == 'user') && ((user.siteadmin & 2) != 0)) {
  3660. // Get the notes about this node
  3661. db.Get('nt' + command.id, function (err, notes) {
  3662. try {
  3663. if ((notes == null) || (notes.length != 1)) { ws.send(JSON.stringify({ action: 'getNotes', id: command.id, notes: null })); return; }
  3664. ws.send(JSON.stringify({ action: 'getNotes', id: command.id, notes: notes[0].value }));
  3665. } catch (ex) { }
  3666. });
  3667. } else if (idtype == 'puser') {
  3668. // Get personal note, starts with 'ntp' + userid
  3669. db.Get('ntp' + user._id, function (err, notes) {
  3670. try {
  3671. if ((notes == null) || (notes.length != 1)) { ws.send(JSON.stringify({ action: 'getNotes', id: command.id, notes: null })); return; }
  3672. ws.send(JSON.stringify({ action: 'getNotes', id: command.id, notes: notes[0].value }));
  3673. } catch (ex) { }
  3674. });
  3675. }
  3676. break;
  3677. }
  3678. case 'createInviteLink': {
  3679. var err = null;
  3680. // Resolve the device group name if needed
  3681. if ((typeof command.meshname == 'string') && (command.meshid == null)) {
  3682. for (var i in parent.meshes) {
  3683. var m = parent.meshes[i];
  3684. if ((m.mtype == 2) && (m.name == command.meshname) && parent.IsMeshViewable(user, m)) {
  3685. if (command.meshid == null) { command.meshid = m._id; } else { err = 'Duplicate device groups found'; }
  3686. }
  3687. }
  3688. }
  3689. if (common.validateString(command.meshid, 8, 134) == false) { err = 'Invalid group id'; } // Check the meshid (Max length of a meshid is 134 bytes).
  3690. else if (common.validateInt(command.expire, 0, 99999) == false) { err = 'Invalid expire time'; } // Check the expire time in hours
  3691. else if (common.validateInt(command.flags, 0, 256) == false) { err = 'Invalid flags'; } // Check the flags
  3692. else {
  3693. if (command.meshid.split('/').length == 1) { command.meshid = 'mesh/' + domain.id + '/' + command.meshid; }
  3694. var smesh = command.meshid.split('/');
  3695. if ((smesh.length != 3) || (smesh[0] != 'mesh') || (smesh[1] != domain.id)) { err = 'Invalid group id'; }
  3696. mesh = parent.meshes[command.meshid];
  3697. if ((mesh == null) || (parent.IsMeshViewable(user, mesh) == false)) { err = 'Invalid group id'; }
  3698. }
  3699. var serverName = parent.getWebServerName(domain, req);
  3700. // Handle any errors
  3701. if (err != null) {
  3702. console.log(err, command.meshid);
  3703. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'createInviteLink', responseid: command.responseid, result: err })); } catch (ex) { } }
  3704. break;
  3705. }
  3706. const cookie = { a: 4, mid: command.meshid, f: command.flags, expire: command.expire * 60 };
  3707. if ((typeof command.agents == 'number') && (command.agents != 0)) { cookie.ag = command.agents; }
  3708. const inviteCookie = parent.parent.encodeCookie(cookie, parent.parent.invitationLinkEncryptionKey);
  3709. if (inviteCookie == null) { if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'createInviteLink', responseid: command.responseid, result: 'Unable to generate invitation cookie' })); } catch (ex) { } } break; }
  3710. // Create the server url
  3711. var httpsPort = ((args.aliasport == null) ? args.port : args.aliasport); // Use HTTPS alias port is specified
  3712. var xdomain = (domain.dns == null) ? domain.id : '';
  3713. if (xdomain != '') xdomain += '/';
  3714. var url = 'https://' + serverName + ':' + httpsPort + '/' + xdomain + 'agentinvite?c=' + inviteCookie;
  3715. if (serverName.split('.') == 1) { url = '/' + xdomain + 'agentinvite?c=' + inviteCookie; }
  3716. ws.send(JSON.stringify({ action: 'createInviteLink', meshid: command.meshid, url: url, expire: command.expire, cookie: inviteCookie, responseid: command.responseid, tag: command.tag }));
  3717. break;
  3718. }
  3719. case 'deviceMeshShares': {
  3720. if (domain.guestdevicesharing === false) return; // This feature is not allowed.
  3721. var err = null;
  3722. // Argument validation
  3723. if (common.validateString(command.meshid, 8, 134) == false) { err = 'Invalid device group id'; } // Check the meshid
  3724. else if (command.meshid.indexOf('/') == -1) { command.meshid = 'mesh/' + domain.id + '/' + command.meshid; }
  3725. else if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) { err = 'Invalid domain'; } // Invalid domain, operation only valid for current domain
  3726. else {
  3727. // Check if we have rights on this device group
  3728. mesh = parent.meshes[command.meshid];
  3729. if (mesh == null) { err = 'Invalid device group id'; } // Check the meshid
  3730. else if (parent.GetMeshRights(user, mesh) == 0) { err = 'Access denied'; }
  3731. }
  3732. // Handle any errors
  3733. if (err != null) {
  3734. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'deviceShares', responseid: command.responseid, result: err })); } catch (ex) { } }
  3735. break;
  3736. }
  3737. // Get all device shares
  3738. parent.db.GetAllTypeNoTypeField('deviceshare', domain.id, function (err, docs) {
  3739. if (err != null) return;
  3740. var now = Date.now(), okDocs = [];
  3741. for (var i = 0; i < docs.length; i++) {
  3742. const doc = docs[i];
  3743. if ((doc.expireTime != null) && (doc.expireTime < now)) {
  3744. // This share is expired.
  3745. parent.db.Remove(doc._id, function () { });
  3746. // Send device share update
  3747. var targets = parent.CreateNodeDispatchTargets(doc.xmeshid, doc.nodeid, ['server-users', user._id]);
  3748. parent.parent.DispatchEvent(targets, obj, { etype: 'node', meshid: doc.xmeshid, nodeid: doc.nodeid, action: 'deviceShareUpdate', domain: domain.id, deviceShares: okDocs, nolog: 1 });
  3749. } else {
  3750. if (doc.xmeshid == null) {
  3751. // This is an old share with missing meshid, fix it here.
  3752. const f = function fixShareMeshId(err, nodes) {
  3753. if (err != null) return;
  3754. if (nodes.length == 1) {
  3755. // Add the meshid to the device share
  3756. fixShareMeshId.xdoc.xmeshid = nodes[0].meshid;
  3757. fixShareMeshId.xdoc.type = 'deviceshare';
  3758. delete fixShareMeshId.xdoc.meshid;
  3759. parent.db.Set(fixShareMeshId.xdoc);
  3760. } else {
  3761. // This node no longer exists, remove the device share.
  3762. parent.db.Remove(fixShareMeshId.xdoc._id);
  3763. }
  3764. }
  3765. f.xdoc = doc;
  3766. db.Get(doc.nodeid, f);
  3767. } else if (doc.xmeshid == command.meshid) {
  3768. // This share is ok, remove extra data we don't need to send.
  3769. delete doc._id; delete doc.domain; delete doc.type; delete doc.xmeshid;
  3770. if (doc.userid != user._id) { delete doc.url; } // If this is not the user who created this link, don't give the link.
  3771. okDocs.push(doc);
  3772. }
  3773. }
  3774. }
  3775. try { ws.send(JSON.stringify({ action: 'deviceMeshShares', meshid: command.meshid, deviceShares: okDocs })); } catch (ex) { }
  3776. });
  3777. break;
  3778. }
  3779. case 'deviceShares': {
  3780. if (domain.guestdevicesharing === false) return; // This feature is not allowed.
  3781. var err = null;
  3782. // Argument validation
  3783. if (common.validateString(command.nodeid, 8, 128) == false) { err = 'Invalid node id'; } // Check the nodeid
  3784. else if (command.nodeid.indexOf('/') == -1) { command.nodeid = 'node/' + domain.id + '/' + command.nodeid; }
  3785. else if ((command.nodeid.split('/').length != 3) || (command.nodeid.split('/')[1] != domain.id)) { err = 'Invalid domain'; } // Invalid domain, operation only valid for current domain
  3786. // Handle any errors
  3787. if (err != null) {
  3788. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'deviceShares', responseid: command.responseid, result: err })); } catch (ex) { } }
  3789. break;
  3790. }
  3791. // Get the device rights
  3792. parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
  3793. // If node not found or we don't have remote control, reject.
  3794. if (node == null) {
  3795. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'deviceShares', responseid: command.responseid, result: 'Invalid node id' })); } catch (ex) { } }
  3796. return;
  3797. }
  3798. // If there is MESHRIGHT_DESKLIMITEDINPUT or we don't have MESHRIGHT_GUESTSHARING on this account, reject this request.
  3799. if (rights != MESHRIGHT_ADMIN) {
  3800. // If we don't have remote control, or have limited input, or don't have guest sharing permission, fail here.
  3801. if (((rights & MESHRIGHT_REMOTECONTROL) == 0) || ((rights & MESHRIGHT_DESKLIMITEDINPUT) != 0) || ((rights & MESHRIGHT_GUESTSHARING) == 0)) {
  3802. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'deviceShares', responseid: command.responseid, result: 'Access denied' })); } catch (ex) { } }
  3803. return;
  3804. }
  3805. }
  3806. parent.db.GetAllTypeNodeFiltered([command.nodeid], domain.id, 'deviceshare', null, function (err, docs) {
  3807. if (err != null) return;
  3808. var now = Date.now(), removed = false, okDocs = [];
  3809. for (var i = 0; i < docs.length; i++) {
  3810. const doc = docs[i];
  3811. if ((doc.expireTime != null) && (doc.expireTime < now)) {
  3812. // This share is expired.
  3813. parent.db.Remove(doc._id, function () { }); removed = true;
  3814. } else {
  3815. // This share is ok, remove extra data we don't need to send.
  3816. delete doc._id; delete doc.domain; delete doc.nodeid; delete doc.type; delete doc.xmeshid;
  3817. if (doc.userid != user._id) { delete doc.url; } // If this is not the user who created this link, don't give the link.
  3818. okDocs.push(doc);
  3819. }
  3820. }
  3821. try { ws.send(JSON.stringify({ action: 'deviceShares', nodeid: command.nodeid, deviceShares: okDocs })); } catch (ex) { }
  3822. // If we removed any shares, send device share update
  3823. if (removed == true) {
  3824. var targets = parent.CreateNodeDispatchTargets(node.meshid, node._id, ['server-users', user._id]);
  3825. parent.parent.DispatchEvent(targets, obj, { etype: 'node', nodeid: node._id, action: 'deviceShareUpdate', domain: domain.id, deviceShares: okDocs, nolog: 1 });
  3826. }
  3827. });
  3828. });
  3829. break;
  3830. }
  3831. case 'removeDeviceShare': {
  3832. if (domain.guestdevicesharing === false) return; // This feature is not allowed.
  3833. var err = null;
  3834. // Argument validation
  3835. if (common.validateString(command.nodeid, 8, 128) == false) { err = 'Invalid node id'; } // Check the nodeid
  3836. else if (command.nodeid.indexOf('/') == -1) { command.nodeid = 'node/' + domain.id + '/' + command.nodeid; }
  3837. else if ((command.nodeid.split('/').length != 3) || (command.nodeid.split('/')[1] != domain.id)) { err = 'Invalid domain'; } // Invalid domain, operation only valid for current domain
  3838. if (common.validateString(command.publicid, 1, 128) == false) { err = 'Invalid public id'; } // Check the public identifier
  3839. // Handle any errors
  3840. if (err != null) {
  3841. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'removeDeviceShare', responseid: command.responseid, result: err })); } catch (ex) { } }
  3842. break;
  3843. }
  3844. // Get the device rights
  3845. parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
  3846. // If node not found or we don't have remote control, reject.
  3847. if (node == null) {
  3848. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'deviceShares', responseid: command.responseid, result: 'Invalid node id' })); } catch (ex) { } }
  3849. return;
  3850. }
  3851. // If there is MESHRIGHT_DESKLIMITEDINPUT or we don't have MESHRIGHT_GUESTSHARING on this account, reject this request.
  3852. if (rights != MESHRIGHT_ADMIN) {
  3853. // If we don't have remote control, or have limited input, or don't have guest sharing permission, fail here.
  3854. if (((rights & MESHRIGHT_REMOTECONTROL) == 0) || ((rights & MESHRIGHT_DESKLIMITEDINPUT) != 0) || ((rights & MESHRIGHT_GUESTSHARING) == 0)) {
  3855. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'deviceShares', responseid: command.responseid, result: 'Access denied' })); } catch (ex) { } }
  3856. return;
  3857. }
  3858. }
  3859. parent.db.GetAllTypeNodeFiltered([command.nodeid], domain.id, 'deviceshare', null, function (err, docs) {
  3860. if (err != null) return;
  3861. // Remove device sharing
  3862. var now = Date.now(), removedExact = null, removed = false, okDocs = [];
  3863. for (var i = 0; i < docs.length; i++) {
  3864. const doc = docs[i];
  3865. if (doc.publicid == command.publicid) { parent.db.Remove(doc._id, function () { }); removedExact = doc; removed = true; }
  3866. else if (doc.expireTime < now) { parent.db.Remove(doc._id, function () { }); removed = true; } else {
  3867. // This share is ok, remove extra data we don't need to send.
  3868. delete doc._id; delete doc.domain; delete doc.nodeid; delete doc.type;
  3869. okDocs.push(doc);
  3870. }
  3871. }
  3872. // Confirm removal if requested
  3873. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'removeDeviceShare', responseid: command.responseid, nodeid: command.nodeid, publicid: command.publicid, removed: removedExact })); } catch (ex) { } }
  3874. // Event device share removal
  3875. if (removedExact != null) {
  3876. // Send out an event that we removed a device share
  3877. var targets = parent.CreateNodeDispatchTargets(node.meshid, node._id, ['server-users', 'server-shareremove', user._id]);
  3878. var event = { etype: 'node', userid: user._id, username: user.name, nodeid: node._id, action: 'removedDeviceShare', msg: 'Removed Device Share', msgid: 102, msgArgs: [removedExact.guestName], domain: domain.id, publicid: command.publicid };
  3879. parent.parent.DispatchEvent(targets, obj, event);
  3880. // If this is an agent self-sharing link, notify the agent
  3881. if (command.publicid.startsWith('AS:node/')) { routeCommandToNode({ action: 'msg', type: 'guestShare', nodeid: command.publicid.substring(3), flags: 0, url: null, viewOnly: false }); }
  3882. }
  3883. // If we removed any shares, send device share update
  3884. if (removed == true) {
  3885. var targets = parent.CreateNodeDispatchTargets(node.meshid, node._id, ['server-users', user._id]);
  3886. parent.parent.DispatchEvent(targets, obj, { etype: 'node', nodeid: node._id, action: 'deviceShareUpdate', domain: domain.id, deviceShares: okDocs, nolog: 1 });
  3887. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'removeDeviceShare', responseid: command.responseid, result: 'OK' })); } catch (ex) { } }
  3888. } else {
  3889. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'removeDeviceShare', responseid: command.responseid, result: 'Invalid device share identifier.' })); } catch (ex) { } }
  3890. }
  3891. });
  3892. });
  3893. break;
  3894. }
  3895. case 'createDeviceShareLink': {
  3896. if (domain.guestdevicesharing === false) return; // This feature is not allowed.
  3897. var err = null;
  3898. // Argument validation
  3899. if (common.validateString(command.nodeid, 8, 128) == false) { err = 'Invalid node id'; } // Check the nodeid
  3900. else if (command.nodeid.indexOf('/') == -1) { command.nodeid = 'node/' + domain.id + '/' + command.nodeid; }
  3901. else if ((command.nodeid.split('/').length != 3) || (command.nodeid.split('/')[1] != domain.id)) { err = 'Invalid domain'; } // Invalid domain, operation only valid for current domain
  3902. if (common.validateString(command.guestname, 1, 128) == false) { err = 'Invalid guest name'; } // Check the guest name
  3903. else if ((command.expire != null) && (typeof command.expire != 'number')) { err = 'Invalid expire time'; } // Check the expire time in minutes
  3904. else if ((command.start != null) && (typeof command.start != 'number')) { err = 'Invalid start time'; } // Check the start time in UTC seconds
  3905. else if ((command.end != null) && (typeof command.end != 'number')) { err = 'Invalid end time'; } // Check the end time in UTC seconds
  3906. else if (common.validateInt(command.consent, 0, 256) == false) { err = 'Invalid flags'; } // Check the flags
  3907. else if (common.validateInt(command.p, 1, 31) == false) { err = 'Invalid protocol'; } // Check the protocol, 1 = Terminal, 2 = Desktop, 4 = Files, 8 = HTTP, 16 = HTTPS
  3908. else if ((command.recurring != null) && (common.validateInt(command.recurring, 1, 2) == false)) { err = 'Invalid recurring value'; } // Check the recurring value, 1 = Daily, 2 = Weekly
  3909. else if ((command.port != null) && (common.validateInt(command.port, 1, 65535) == false)) { err = 'Invalid port value'; } // Check the port if present
  3910. else if ((command.recurring != null) && ((command.end != null) || (command.start == null) || (command.expire == null))) { err = 'Invalid recurring command'; }
  3911. else if ((command.expire == null) && ((command.start == null) || (command.end == null) || (command.start > command.end))) { err = 'No time specified'; } // Check that a time range is present
  3912. else {
  3913. if (command.nodeid.split('/').length == 1) { command.nodeid = 'node/' + domain.id + '/' + command.nodeid; }
  3914. var snode = command.nodeid.split('/');
  3915. if ((snode.length != 3) || (snode[0] != 'node') || (snode[1] != domain.id)) { err = 'Invalid node id'; }
  3916. }
  3917. // Handle any errors
  3918. if (err != null) {
  3919. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'createDeviceShareLink', responseid: command.responseid, result: err })); } catch (ex) { } }
  3920. break;
  3921. }
  3922. // Correct maximum session length if needed
  3923. if ((typeof domain.guestdevicesharing == 'object') && (typeof domain.guestdevicesharing.maxsessiontime == 'number') && (domain.guestdevicesharing.maxsessiontime > 0)) {
  3924. const maxtime = domain.guestdevicesharing.maxsessiontime;
  3925. if ((command.expire != null) && (command.expire > maxtime)) { command.expire = maxtime; }
  3926. if ((command.start != null) && (command.end != null)) { if ((command.end - command.start) > (maxtime * 60)) { command.end = (command.start + (maxtime * 60)); } }
  3927. }
  3928. // Get the device rights
  3929. parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
  3930. // If node not found or we don't have remote control, reject.
  3931. if (node == null) {
  3932. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'deviceShares', responseid: command.responseid, result: 'Invalid node id' })); } catch (ex) { } }
  3933. return;
  3934. }
  3935. // If there is MESHRIGHT_DESKLIMITEDINPUT or we don't have MESHRIGHT_GUESTSHARING on this account, reject this request.
  3936. if (rights != MESHRIGHT_ADMIN) {
  3937. // If we don't have remote control, or have limited input, or don't have guest sharing permission, fail here.
  3938. if (((rights & MESHRIGHT_REMOTECONTROL) == 0) || ((rights & MESHRIGHT_DESKLIMITEDINPUT) != 0) || ((rights & MESHRIGHT_GUESTSHARING) == 0)) {
  3939. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'deviceShares', responseid: command.responseid, result: 'Access denied' })); } catch (ex) { } }
  3940. return;
  3941. }
  3942. }
  3943. // If we are limited to no terminal, don't allow terminal sharing
  3944. if (((command.p & 1) != 0) && (rights != MESHRIGHT_ADMIN) && ((rights & MESHRIGHT_NOTERMINAL) != 0)) {
  3945. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'deviceShares', responseid: command.responseid, result: 'Access denied' })); } catch (ex) { } }
  3946. return;
  3947. }
  3948. // If we are limited to no desktop, don't allow desktop sharing
  3949. if (((command.p & 2) != 0) && (rights != MESHRIGHT_ADMIN) && ((rights & MESHRIGHT_NODESKTOP) != 0)) {
  3950. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'deviceShares', responseid: command.responseid, result: 'Access denied' })); } catch (ex) { } }
  3951. return;
  3952. }
  3953. // If we are limited to no files, don't allow file sharing
  3954. if (((command.p & 4) != 0) && (rights != MESHRIGHT_ADMIN) && ((rights & MESHRIGHT_NOFILES) != 0)) {
  3955. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'deviceShares', responseid: command.responseid, result: 'Access denied' })); } catch (ex) { } }
  3956. return;
  3957. }
  3958. // If we have view only remote desktop rights, force view-only on the guest share.
  3959. if ((rights != MESHRIGHT_ADMIN) && ((rights & MESHRIGHT_REMOTEVIEWONLY) != 0)) { command.viewOnly = true; }
  3960. // Create cookie
  3961. var publicid = getRandomPassword(), startTime = null, expireTime = null, duration = null;
  3962. if (command.recurring) {
  3963. // Recurring share
  3964. startTime = command.start * 1000;
  3965. duration = command.expire;
  3966. } else if (command.expire != null) {
  3967. if (command.expire !== 0) {
  3968. // Now until expire in hours
  3969. startTime = Date.now();
  3970. expireTime = Date.now() + (60000 * command.expire);
  3971. } else {
  3972. delete command.expire;
  3973. }
  3974. } else {
  3975. // Time range in seconds
  3976. startTime = command.start * 1000;
  3977. expireTime = command.end * 1000;
  3978. }
  3979. //var cookie = { a: 5, p: command.p, uid: user._id, gn: command.guestname, nid: node._id, cf: command.consent, pid: publicid }; // Old style sharing cookie
  3980. var cookie = { a: 6, pid: publicid }; // New style sharing cookie
  3981. if ((startTime != null) && (expireTime != null)) { command.start = startTime; command.expire = cookie.e = expireTime; }
  3982. else if ((startTime != null) && (duration != null)) { command.start = startTime; }
  3983. const inviteCookie = parent.parent.encodeCookie(cookie, parent.parent.invitationLinkEncryptionKey);
  3984. if (inviteCookie == null) { if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'createDeviceShareLink', responseid: command.responseid, result: 'Unable to generate shareing cookie' })); } catch (ex) { } } return; }
  3985. // Create the server url
  3986. var serverName = parent.getWebServerName(domain, req);
  3987. var httpsPort = ((args.aliasport == null) ? args.port : args.aliasport); // Use HTTPS alias port is specified
  3988. var xdomain = (domain.dns == null) ? domain.id : '';
  3989. if (xdomain != '') xdomain += '/';
  3990. var url = 'https://' + serverName + ':' + httpsPort + '/' + xdomain + 'sharing?c=' + inviteCookie;
  3991. if (serverName.split('.') == 1) { url = '/' + xdomain + page + '?c=' + inviteCookie; }
  3992. command.url = url;
  3993. command.publicid = publicid;
  3994. if (command.responseid != null) { command.result = 'OK'; }
  3995. try { ws.send(JSON.stringify(command)); } catch (ex) { }
  3996. // Create a device sharing database entry
  3997. var shareEntry = { _id: 'deviceshare-' + publicid, type: 'deviceshare', xmeshid: node.meshid, nodeid: node._id, p: command.p, domain: node.domain, publicid: publicid, userid: user._id, guestName: command.guestname, consent: command.consent, port: command.port, url: url };
  3998. if ((startTime != null) && (expireTime != null)) { shareEntry.startTime = startTime; shareEntry.expireTime = expireTime; }
  3999. else if ((startTime != null) && (duration != null)) { shareEntry.startTime = startTime; shareEntry.duration = duration; }
  4000. if (command.recurring) { shareEntry.recurring = command.recurring; }
  4001. if (command.viewOnly === true) { shareEntry.viewOnly = true; }
  4002. parent.db.Set(shareEntry);
  4003. // Send out an event that we added a device share
  4004. var targets = parent.CreateNodeDispatchTargets(node.meshid, node._id, ['server-users', user._id]);
  4005. var event;
  4006. if (command.recurring == 1) {
  4007. event = { etype: 'node', userid: user._id, username: user.name, meshid: node.meshid, nodeid: node._id, action: 'addedDeviceShare', msg: 'Added device share ' + command.guestname + ' recurring daily.', msgid: 138, msgArgs: [command.guestname], domain: domain.id };
  4008. } else if (command.recurring == 2) {
  4009. event = { etype: 'node', userid: user._id, username: user.name, meshid: node.meshid, nodeid: node._id, action: 'addedDeviceShare', msg: 'Added device share ' + command.guestname + ' recurring weekly.', msgid: 139, msgArgs: [command.guestname], domain: domain.id };
  4010. } else if ((startTime != null) && (expireTime != null)) {
  4011. event = { etype: 'node', userid: user._id, username: user.name, meshid: node.meshid, nodeid: node._id, action: 'addedDeviceShare', msg: 'Added device share: ' + command.guestname + '.', msgid: 101, msgArgs: [command.guestname, 'DATETIME:' + startTime, 'DATETIME:' + expireTime], domain: domain.id };
  4012. } else {
  4013. event = { etype: 'node', userid: user._id, username: user.name, meshid: node.meshid, nodeid: node._id, action: 'addedDeviceShare', msg: 'Added device share ' + command.guestname + ' with unlimited time.', msgid: 131, msgArgs: [command.guestname], domain: domain.id };
  4014. }
  4015. parent.parent.DispatchEvent(targets, obj, event);
  4016. // Send device share update
  4017. parent.db.GetAllTypeNodeFiltered([command.nodeid], domain.id, 'deviceshare', null, function (err, docs) {
  4018. if (err != null) return;
  4019. // Check device sharing
  4020. var now = Date.now();
  4021. for (var i = 0; i < docs.length; i++) {
  4022. const doc = docs[i];
  4023. if (doc.expireTime < now) { parent.db.Remove(doc._id, function () { }); delete docs[i]; } else {
  4024. // This share is ok, remove extra data we don't need to send.
  4025. delete doc._id; delete doc.domain; delete doc.nodeid; delete doc.type; delete doc.xmeshid;
  4026. }
  4027. }
  4028. // Send device share update
  4029. var targets = parent.CreateNodeDispatchTargets(node.meshid, node._id, ['server-users', user._id]);
  4030. parent.parent.DispatchEvent(targets, obj, { etype: 'node', nodeid: node._id, action: 'deviceShareUpdate', domain: domain.id, deviceShares: docs, nolog: 1 });
  4031. });
  4032. });
  4033. break;
  4034. }
  4035. case 'traceinfo': {
  4036. // Only accept if the tracing tab is allowed for this domain
  4037. if ((domain.myserver === false) || ((domain.myserver != null) && (domain.myserver !== true) && (domain.myserver.trace !== true))) break;
  4038. if ((user.siteadmin === SITERIGHT_ADMIN) && (typeof command.traceSources == 'object')) {
  4039. parent.parent.debugRemoteSources = command.traceSources;
  4040. parent.parent.DispatchEvent(['*'], obj, { action: 'traceinfo', userid: user._id, username: user.name, traceSources: command.traceSources, nolog: 1, domain: domain.id });
  4041. }
  4042. break;
  4043. }
  4044. case 'sendmqttmsg': {
  4045. if (parent.parent.mqttbroker == null) { err = 'MQTT not supported on this server'; }; // MQTT not available
  4046. if (common.validateArray(command.nodeids, 1) == false) { err = 'Invalid nodeids'; }; // Check nodeid's
  4047. if (common.validateString(command.topic, 1, 64) == false) { err = 'Invalid topic'; } // Check the topic
  4048. if (common.validateString(command.msg, 1, 4096) == false) { err = 'Invalid msg'; } // Check the message
  4049. // Handle any errors
  4050. if (err != null) {
  4051. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'sendmqttmsg', responseid: command.responseid, result: err })); } catch (ex) { } }
  4052. break;
  4053. }
  4054. // Send the MQTT message
  4055. for (i in command.nodeids) {
  4056. // Get the node and the rights for this node
  4057. parent.GetNodeWithRights(domain, user, command.nodeids[i], function (node, rights, visible) {
  4058. // If this device is connected on MQTT, send a wake action.
  4059. if (rights != 0) {
  4060. parent.parent.mqttbroker.publish(node._id, command.topic, command.msg);
  4061. }
  4062. });
  4063. }
  4064. break;
  4065. }
  4066. case 'getmqttlogin': {
  4067. var err = null;
  4068. if (parent.parent.mqttbroker == null) { err = 'MQTT not supported on this server'; }
  4069. if (common.validateString(command.nodeid, 1, 1024) == false) { err = 'Invalid nodeid'; } // Check the nodeid
  4070. // Handle any errors
  4071. if (err != null) { if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'getmqttlogin', responseid: command.responseid, result: err })); } catch (ex) { } } break; }
  4072. // Get the node and the rights for this node
  4073. parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
  4074. // Check if this user has rights to do this
  4075. if (rights == MESHRIGHT_ADMIN) {
  4076. var token = parent.parent.mqttbroker.generateLogin(node.meshid, node._id);
  4077. var r = { action: 'getmqttlogin', responseid: command.responseid, nodeid: node._id, user: token.user, pass: token.pass };
  4078. const serverName = parent.getWebServerName(domain, req);
  4079. // Add MPS URL
  4080. if (parent.parent.mpsserver != null) {
  4081. r.mpsCertHashSha384 = parent.parent.certificateOperations.getCertHash(parent.parent.mpsserver.certificates.mps.cert);
  4082. r.mpsCertHashSha1 = parent.parent.certificateOperations.getCertHashSha1(parent.parent.mpsserver.certificates.mps.cert);
  4083. r.mpsUrl = 'mqtts://' + serverName + ':' + ((args.mpsaliasport != null) ? args.mpsaliasport : args.mpsport) + '/';
  4084. }
  4085. // Add WS URL
  4086. var xdomain = (domain.dns == null) ? domain.id : '';
  4087. if (xdomain != '') xdomain += '/';
  4088. var httpsPort = ((args.aliasport == null) ? args.port : args.aliasport); // Use HTTPS alias port is specified
  4089. r.wsUrl = 'wss://' + serverName + ':' + httpsPort + '/' + xdomain + 'mqtt.ashx';
  4090. r.wsTrustedCert = parent.isTrustedCert(domain);
  4091. try { ws.send(JSON.stringify(r)); } catch (ex) { }
  4092. } else {
  4093. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'getmqttlogin', responseid: command.responseid, result: 'Unable to perform this operation' })); } catch (ex) { } }
  4094. }
  4095. });
  4096. break;
  4097. }
  4098. case 'amt': {
  4099. if (common.validateString(command.nodeid, 1, 1024) == false) break; // Check nodeid
  4100. if (common.validateInt(command.mode, 0, 3) == false) break; // Check connection mode
  4101. // Validate if communication mode is possible
  4102. if (command.mode == null || command.mode == 0) {
  4103. break; //unsupported
  4104. } else if (command.mode == 1) {
  4105. var state = parent.parent.GetConnectivityState(command.nodeid);
  4106. if ((state == null) || (state.connectivity & 4) == 0) break;
  4107. } else if (command.mode == 2) {
  4108. if (parent.parent.mpsserver.ciraConnections[command.nodeid] == null) break;
  4109. }
  4110. /*
  4111. else if (command.mode == 3) {
  4112. if (parent.parent.apfserver.apfConnections[command.nodeid] == null) break;
  4113. }
  4114. */
  4115. // Get the node and the rights for this node
  4116. parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
  4117. if ((rights & MESHRIGHT_REMOTECONTROL) == 0) return;
  4118. handleAmtCommand(command, node);
  4119. });
  4120. break;
  4121. }
  4122. case 'distributeCore': {
  4123. // This is only available when plugins are enabled since it could cause stress on the server
  4124. if ((user.siteadmin != SITERIGHT_ADMIN) || (parent.parent.pluginHandler == null)) break; // Must be full admin with plugins enabled
  4125. for (var i in command.nodes) {
  4126. parent.sendMeshAgentCore(user, domain, command.nodes[i]._id, 'default');
  4127. }
  4128. break;
  4129. }
  4130. case 'plugins': {
  4131. // Since plugin actions generally require a server restart, use the Full admin permission
  4132. if ((user.siteadmin != SITERIGHT_ADMIN) || (parent.parent.pluginHandler == null)) break; // Must be full admin with plugins enabled
  4133. parent.db.getPlugins(function(err, docs) {
  4134. try { ws.send(JSON.stringify({ action: 'updatePluginList', list: docs, result: err })); } catch (ex) { }
  4135. });
  4136. break;
  4137. }
  4138. case 'pluginLatestCheck': {
  4139. if ((user.siteadmin != SITERIGHT_ADMIN) || (parent.parent.pluginHandler == null)) break; // Must be full admin with plugins enabled
  4140. parent.parent.pluginHandler.getPluginLatest()
  4141. .then(function(latest) {
  4142. try { ws.send(JSON.stringify({ action: 'pluginVersionsAvailable', list: latest })); } catch (ex) { }
  4143. });
  4144. break;
  4145. }
  4146. case 'addplugin': {
  4147. if ((user.siteadmin != SITERIGHT_ADMIN) || (parent.parent.pluginHandler == null)) break; // Must be full admin with plugins enabled
  4148. try {
  4149. parent.parent.pluginHandler.getPluginConfig(command.url)
  4150. .then(parent.parent.pluginHandler.addPlugin)
  4151. .then(function(docs){
  4152. var targets = ['*', 'server-users'];
  4153. parent.parent.DispatchEvent(targets, obj, { action: 'updatePluginList', list: docs });
  4154. })
  4155. .catch(function(err) {
  4156. if (typeof err == 'object') err = err.message;
  4157. try { ws.send(JSON.stringify({ action: 'pluginError', msg: err })); } catch (er) { }
  4158. });
  4159. } catch(ex) { console.log('Cannot add plugin: ' + e); }
  4160. break;
  4161. }
  4162. case 'installplugin': {
  4163. if ((user.siteadmin != SITERIGHT_ADMIN) || (parent.parent.pluginHandler == null)) break; // Must be full admin with plugins enabled
  4164. parent.parent.pluginHandler.installPlugin(command.id, command.version_only, null, function(){
  4165. parent.db.getPlugins(function(err, docs) {
  4166. try { ws.send(JSON.stringify({ action: 'updatePluginList', list: docs, result: err })); } catch (ex) { }
  4167. });
  4168. var targets = ['*', 'server-users'];
  4169. parent.parent.DispatchEvent(targets, obj, { action: 'pluginStateChange' });
  4170. });
  4171. break;
  4172. }
  4173. case 'disableplugin': {
  4174. if ((user.siteadmin != SITERIGHT_ADMIN) || (parent.parent.pluginHandler == null)) break; // Must be full admin with plugins enabled
  4175. parent.parent.pluginHandler.disablePlugin(command.id, function(){
  4176. parent.db.getPlugins(function(err, docs) {
  4177. try { ws.send(JSON.stringify({ action: 'updatePluginList', list: docs, result: err })); } catch (ex) { }
  4178. var targets = ['*', 'server-users'];
  4179. parent.parent.DispatchEvent(targets, obj, { action: 'pluginStateChange' });
  4180. });
  4181. });
  4182. break;
  4183. }
  4184. case 'removeplugin': {
  4185. if ((user.siteadmin != SITERIGHT_ADMIN) || (parent.parent.pluginHandler == null)) break; // Must be full admin with plugins enabled
  4186. parent.parent.pluginHandler.removePlugin(command.id, function(){
  4187. parent.db.getPlugins(function(err, docs) {
  4188. try { ws.send(JSON.stringify({ action: 'updatePluginList', list: docs, result: err })); } catch (ex) { }
  4189. });
  4190. });
  4191. break;
  4192. }
  4193. case 'getpluginversions': {
  4194. if ((user.siteadmin != SITERIGHT_ADMIN) || (parent.parent.pluginHandler == null)) break; // Must be full admin with plugins enabled
  4195. parent.parent.pluginHandler.getPluginVersions(command.id)
  4196. .then(function (versionInfo) {
  4197. try { ws.send(JSON.stringify({ action: 'downgradePluginVersions', info: versionInfo, error: null })); } catch (ex) { }
  4198. })
  4199. .catch(function (e) {
  4200. try { ws.send(JSON.stringify({ action: 'pluginError', msg: e })); } catch (ex) { }
  4201. });
  4202. break;
  4203. }
  4204. case 'plugin': {
  4205. if (parent.parent.pluginHandler == null) break; // If the plugin's are not supported, reject this command.
  4206. command.userid = user._id;
  4207. if (command.routeToNode === true) {
  4208. routeCommandToNode(command);
  4209. } else {
  4210. try {
  4211. parent.parent.pluginHandler.plugins[command.plugin].serveraction(command, obj, parent);
  4212. } catch (ex) { console.log('Error loading plugin handler (' + ex + ')'); }
  4213. }
  4214. break;
  4215. }
  4216. case 'uicustomevent': {
  4217. if ((command.src != null) && (Array.isArray(command.src.selectedDevices))) {
  4218. // Contains a list of nodeid's, check that we have permissions for them.
  4219. parent.GetNodesWithRights(domain, user, command.src.selectedDevices, function (nodes) {
  4220. var nodeids = [];
  4221. for (var i in nodes) { nodeids.push(i); }
  4222. if (nodeids.length == 0) return;
  4223. // Event the custom UI action
  4224. var message = { etype: 'user', userid: user._id, username: user.name, action: 'uicustomevent', domain: domain.id, uisection: command.section, element: command.element };
  4225. if (nodeids.length == 1) { message.nodeid = nodeids[0]; }
  4226. if (command.selectedDevices != null) { message.selectedDevices = command.selectedDevices; }
  4227. if (command.src != null) { message.src = command.src; }
  4228. if (command.values != null) { message.values = command.values; }
  4229. if (typeof command.logmsg == 'string') { message.msg = command.logmsg; } else { message.nolog = 1; }
  4230. parent.parent.DispatchEvent(['*', user._id], obj, message);
  4231. });
  4232. } else {
  4233. // Event the custom UI action
  4234. var message = { etype: 'user', userid: user._id, username: user.name, action: 'uicustomevent', domain: domain.id, uisection: command.section, element: command.element };
  4235. if (command.selectedDevices != null) { message.selectedDevices = command.selectedDevices; }
  4236. if (command.src != null) { message.src = command.src; }
  4237. if (command.values != null) { message.values = command.values; }
  4238. if (typeof command.logmsg == 'string') { message.msg = command.logmsg; } else { message.nolog = 1; }
  4239. parent.parent.DispatchEvent(['*', user._id], obj, message);
  4240. }
  4241. if (parent.parent.pluginHandler != null) // If the plugin's are not supported, reject this command.
  4242. {
  4243. command.userid = user._id;
  4244. try {
  4245. for( var pluginName in parent.parent.pluginHandler.plugins)
  4246. if( typeof parent.parent.pluginHandler.plugins[pluginName].uiCustomEvent === 'function' )
  4247. parent.parent.pluginHandler.plugins[pluginName].uiCustomEvent(command, obj);
  4248. } catch (ex) { console.log('Error loading plugin handler (' + ex + ')'); }
  4249. }
  4250. break;
  4251. }
  4252. case 'serverBackup': {
  4253. // Do not allow this command when logged in using a login token
  4254. if (req.session.loginToken != null) break;
  4255. if ((user.siteadmin != SITERIGHT_ADMIN) || (typeof parent.parent.config.settings.autobackup.googledrive != 'object')) return;
  4256. if (command.service == 'googleDrive') {
  4257. if (command.state == 0) {
  4258. parent.db.Remove('GoogleDriveBackup', function () { try { ws.send(JSON.stringify({ action: 'serverBackup', service: 'googleDrive', state: 1 })); } catch (ex) { } });
  4259. } else if (command.state == 1) {
  4260. const {google} = require('googleapis');
  4261. obj.oAuth2Client = new google.auth.OAuth2(command.clientid, command.clientsecret, "urn:ietf:wg:oauth:2.0:oob");
  4262. obj.oAuth2Client.xxclientid = command.clientid;
  4263. obj.oAuth2Client.xxclientsecret = command.clientsecret;
  4264. const authUrl = obj.oAuth2Client.generateAuthUrl({ access_type: 'offline', scope: ['https://www.googleapis.com/auth/drive.file'] });
  4265. try { ws.send(JSON.stringify({ action: 'serverBackup', service: 'googleDrive', state: 2, url: authUrl })); } catch (ex) { }
  4266. } else if ((command.state == 2) && (obj.oAuth2Client != null)) {
  4267. obj.oAuth2Client.getToken(command.code, function (err, token) {
  4268. if (err != null) { console.log('GoogleDrive (getToken) error: ', err); return; }
  4269. parent.db.Set({ _id: 'GoogleDriveBackup', state: 3, clientid: obj.oAuth2Client.xxclientid, clientsecret: obj.oAuth2Client.xxclientsecret, token: token });
  4270. try { ws.send(JSON.stringify({ action: 'serverBackup', service: 'googleDrive', state: 3 })); } catch (ex) { }
  4271. });
  4272. }
  4273. }
  4274. break;
  4275. }
  4276. case 'twoFactorCookie': {
  4277. try {
  4278. // Do not allow this command when logged in using a login token
  4279. if (req.session.loginToken != null) break;
  4280. // Do not allows this command is 2FA cookie duration is set to zero
  4281. if (domain.twofactorcookiedurationdays === 0) break;
  4282. // Generate a two-factor cookie
  4283. var maxCookieAge = domain.twofactorcookiedurationdays;
  4284. if ((typeof maxCookieAge != 'number') || (maxCookieAge < 1)) { maxCookieAge = 30; }
  4285. const twoFactorCookie = parent.parent.encodeCookie({ userid: user._id, expire: maxCookieAge * 24 * 60 /*, ip: req.clientIp*/ }, parent.parent.loginCookieEncryptionKey);
  4286. try { ws.send(JSON.stringify({ action: 'twoFactorCookie', cookie: twoFactorCookie })); } catch (ex) { }
  4287. } catch (ex) { console.log(ex); }
  4288. break;
  4289. }
  4290. case 'amtsetupbin': {
  4291. if ((command.oldmebxpass != 'admin') && (common.validateString(command.oldmebxpass, 8, 16) == false)) break; // Check password
  4292. if (common.validateString(command.newmebxpass, 8, 16) == false) break; // Check password
  4293. if ((command.baremetal) && (parent.parent.amtProvisioningServer != null)) {
  4294. // Create bare metal setup.bin
  4295. var bin = parent.parent.certificateOperations.GetBareMetalSetupBinFile(domain.amtacmactivation, command.oldmebxpass, command.newmebxpass, domain, user);
  4296. try { ws.send(JSON.stringify({ action: 'amtsetupbin', file: Buffer.from(bin, 'binary').toString('base64') })); } catch (ex) { }
  4297. } else {
  4298. // Create standard setup.bin
  4299. var bin = parent.parent.certificateOperations.GetSetupBinFile(domain.amtacmactivation, command.oldmebxpass, command.newmebxpass, domain, user);
  4300. try { ws.send(JSON.stringify({ action: 'amtsetupbin', file: Buffer.from(bin, 'binary').toString('base64') })); } catch (ex) { }
  4301. }
  4302. break;
  4303. }
  4304. case 'meshToolInfo': {
  4305. if (typeof command.name != 'string') break;
  4306. var info = parent.parent.meshToolsBinaries[command.name];
  4307. var responseCmd = { action: 'meshToolInfo', name: command.name, hash: info.hash, size: info.size, url: info.url };
  4308. if (parent.webCertificateHashs[domain.id] != null) { responseCmd.serverhash = Buffer.from(parent.webCertificateHashs[domain.id], 'binary').toString('hex'); }
  4309. try { ws.send(JSON.stringify(responseCmd)); } catch (ex) { }
  4310. break;
  4311. }
  4312. case 'pushmessage': {
  4313. // Check if this user has rights on this nodeid
  4314. if (parent.parent.firebase == null) return;
  4315. if (common.validateString(command.nodeid, 1, 1024) == false) break; // Check nodeid
  4316. if (common.validateString(command.title, 1, 1024) == false) break; // Check title
  4317. if (common.validateString(command.msg, 1, 1024) == false) break; // Check message
  4318. db.Get(command.nodeid, function (err, nodes) { // TODO: Make a NodeRights(user) method that also does not do a db call if agent is connected (???)
  4319. if ((err == null) && (nodes.length == 1)) {
  4320. const node = nodes[0];
  4321. if (((parent.GetNodeRights(user, node.meshid, node._id) & MESHRIGHT_CHATNOTIFY) != 0) && (typeof node.pmt == 'string')) {
  4322. // Send out a push message to the device
  4323. var payload = { notification: { title: command.title, body: command.msg } };
  4324. var options = { priority: "Normal", timeToLive: 5 * 60 }; // TTL: 5 minutes
  4325. parent.parent.firebase.sendToDevice(node, payload, options, function (id, err, errdesc) {
  4326. if (err == null) {
  4327. parent.parent.debug('email', 'Successfully send push message to device ' + node.name + ', title: ' + command.title + ', msg: ' + command.msg);
  4328. } else {
  4329. parent.parent.debug('email', 'Failed to send push message to device ' + node.name + ', title: ' + command.title + ', msg: ' + command.msg + ', error: ' + errdesc);
  4330. }
  4331. });
  4332. }
  4333. }
  4334. });
  4335. break;
  4336. }
  4337. case 'pushconsole': {
  4338. // Check if this user has rights on this nodeid
  4339. if (parent.parent.firebase == null) return;
  4340. if (common.validateString(command.nodeid, 1, 1024) == false) break; // Check nodeid
  4341. if (common.validateString(command.console, 1, 3000) == false) break; // Check console command
  4342. db.Get(command.nodeid, function (err, nodes) { // TODO: Make a NodeRights(user) method that also does not do a db call if agent is connected (???)
  4343. if ((err == null) && (nodes.length == 1)) {
  4344. const node = nodes[0];
  4345. if ((parent.GetNodeRights(user, node.meshid, node._id) == MESHRIGHT_ADMIN) && (typeof node.pmt == 'string')) {
  4346. // Send out a push message to the device
  4347. var payload = { data: { con: command.console, s: ws.sessionId } };
  4348. var options = { priority: "Normal", timeToLive: 60 }; // TTL: 1 minutes, priority 'Normal' or 'High'
  4349. parent.parent.firebase.sendToDevice(node, payload, options, function (id, err, errdesc) {
  4350. if (err != null) {
  4351. try { ws.send(JSON.stringify({ action: 'msg', type: 'console', nodeid: node._id, value: 'Failed: ' + errdesc })); } catch (ex) { }
  4352. parent.parent.debug('email', 'Failed to send push console message to device ' + node.name + ', command: ' + command.console + ', error: ' + errdesc);
  4353. }
  4354. });
  4355. }
  4356. }
  4357. });
  4358. break;
  4359. }
  4360. case 'webpush': {
  4361. // Check if web push is enabled
  4362. if (parent.parent.webpush == null) break;
  4363. // Adds a web push session to the user. Start by sanitizing the input.
  4364. if ((typeof command.sub != 'object') && (typeof command.sub.keys != 'object') && (typeof command.sub.endpoint != 'string')) break;
  4365. if (common.validateString(command.sub.endpoint, 1, 1024) == false) break; // Check endpoint
  4366. if (common.validateString(command.sub.keys.auth, 1, 64) == false) break; // Check key auth
  4367. if (common.validateString(command.sub.keys.p256dh, 1, 256) == false) break; // Check key dh
  4368. var newWebPush = { endpoint: command.sub.endpoint, keys: { auth: command.sub.keys.auth, p256dh: command.sub.keys.p256dh } }
  4369. // See if we need to add this session
  4370. var changed = false;
  4371. if (user.webpush == null) {
  4372. changed = true;
  4373. user.webpush = [newWebPush];
  4374. } else {
  4375. var found = false;
  4376. for (var i in user.webpush) {
  4377. if ((user.webpush[i].endpoint == newWebPush.endpoint) && (user.webpush[i].keys.auth == newWebPush.keys.auth) && (user.webpush[i].keys.p256dh == newWebPush.keys.p256dh)) { found = true; }
  4378. }
  4379. if (found == true) break;
  4380. changed = true;
  4381. user.webpush.push(newWebPush);
  4382. while (user.webpush.length > 5) { user.webpush.shift(); }
  4383. }
  4384. // If we added the session, update the user
  4385. if (changed == true) {
  4386. // Update the database
  4387. parent.db.SetUser(user);
  4388. // Event the change
  4389. var message = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', domain: domain.id, nolog: 1 };
  4390. if (db.changeStream) { message.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  4391. var targets = ['*', 'server-users', user._id];
  4392. if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
  4393. parent.parent.DispatchEvent(targets, obj, message);
  4394. }
  4395. break;
  4396. }
  4397. case 'previousLogins': {
  4398. // TODO: Make a better database call to get filtered data.
  4399. if (command.userid == null) {
  4400. var splitUser = user._id.split('/');
  4401. // Get previous logins for self
  4402. if (db.GetUserLoginEvents) {
  4403. // New way
  4404. db.GetUserLoginEvents(domain.id, user._id, function (err, docs) {
  4405. if (err != null) return;
  4406. var e = [];
  4407. for (var i in docs) { e.push({ t: docs[i].time, m: docs[i].msgid, a: docs[i].msgArgs, tn: docs[i].tokenName }); }
  4408. try { ws.send(JSON.stringify({ action: 'previousLogins', events: e })); } catch (ex) { }
  4409. });
  4410. } else {
  4411. // Old way
  4412. db.GetUserEvents([user._id], domain.id, user._id, null, function (err, docs) {
  4413. if (err != null) return;
  4414. var e = [];
  4415. for (var i in docs) {
  4416. if ((docs[i].msgArgs) && (docs[i].userid == user._id) && ((docs[i].action == 'authfail') || (docs[i].action == 'login'))) {
  4417. e.push({ t: docs[i].time, m: docs[i].msgid, a: docs[i].msgArgs, tn: docs[i].tokenName });
  4418. }
  4419. }
  4420. try { ws.send(JSON.stringify({ action: 'previousLogins', events: e })); } catch (ex) { }
  4421. });
  4422. }
  4423. } else {
  4424. // Get previous logins for specific userid
  4425. if ((user.siteadmin & SITERIGHT_MANAGEUSERS) != 0) {
  4426. var splitUser = command.userid.split('/');
  4427. if ((obj.crossDomain === true) || (splitUser[1] === domain.id)) {
  4428. if (db.GetUserLoginEvents) {
  4429. // New way
  4430. db.GetUserLoginEvents(splitUser[1], command.userid, function (err, docs) {
  4431. if (err != null) return;
  4432. var e = [];
  4433. for (var i in docs) { e.push({ t: docs[i].time, m: docs[i].msgid, a: docs[i].msgArgs }); }
  4434. try { ws.send(JSON.stringify({ action: 'previousLogins', userid: command.userid, events: e })); } catch (ex) { }
  4435. });
  4436. } else {
  4437. // Old way
  4438. db.GetUserEvents([command.userid], domain.id, user._id, null, function (err, docs) {
  4439. if (err != null) return;
  4440. var e = [];
  4441. for (var i in docs) { if ((docs[i].msgArgs) && (docs[i].userid == command.userid) && ((docs[i].action == 'authfail') || (docs[i].action == 'login'))) { e.push({ t: docs[i].time, m: docs[i].msgid, a: docs[i].msgArgs }); } }
  4442. try { ws.send(JSON.stringify({ action: 'previousLogins', userid: command.userid, events: e })); } catch (ex) { }
  4443. });
  4444. }
  4445. }
  4446. }
  4447. }
  4448. break;
  4449. }
  4450. case 'oneclickrecovery': { // Intel(R) AMT One Click Recovery (OCR)
  4451. if (common.validateStrArray(command.nodeids, 1) == false) break; // Check nodeids
  4452. if (common.validateString(command.path, 1, 2048) == false) break; // Check file path
  4453. if (command.type != 'diskimage') break; // Make sure type is correct
  4454. var file = parent.getServerFilePath(user, domain, command.path);
  4455. if (file == null) return;
  4456. // For each nodeid, change the group
  4457. for (var i = 0; i < command.nodeids.length; i++) {
  4458. var xnodeid = command.nodeids[i];
  4459. if (xnodeid.indexOf('/') == -1) { xnodeid = 'node/' + domain.id + '/' + xnodeid; }
  4460. // Get the node and the rights for this node
  4461. parent.GetNodeWithRights(domain, user, xnodeid, function (node, rights, visible) {
  4462. // Check if we found this device and if we have full rights
  4463. if ((node == null) || (rights != 0xFFFFFFFF)) return;
  4464. // Event Intel AMT One Click Recovery, this will cause Intel AMT wake operations on this and other servers.
  4465. parent.parent.DispatchEvent('*', obj, { action: 'oneclickrecovery', userid: user._id, username: user.name, nodeids: [node._id], domain: domain.id, nolog: 1, file: file.fullpath });
  4466. });
  4467. }
  4468. break;
  4469. }
  4470. case 'loginTokens': { // Respond with the list of currently valid login tokens
  4471. if (req.session.loginToken != null) break; // Do not allow this command when logged in using a login token
  4472. if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.logintokens == false)) break; // Login tokens are not supported on this server
  4473. // If remove is an array or strings, we are going to be removing these and returning the results.
  4474. if (common.validateStrArray(command.remove, 1) == false) { delete command.remove; }
  4475. parent.db.GetAllTypeNodeFiltered(['logintoken-' + user._id], domain.id, 'logintoken', null, function (err, docs) {
  4476. if (err != null) return;
  4477. var now = Date.now(), removed = [], okDocs = [];
  4478. for (var i = 0; i < docs.length; i++) {
  4479. const doc = docs[i];
  4480. if (((doc.expire != 0) && (doc.expire < now)) || (doc.tokenUser == null) || ((command.remove != null) && (command.remove.indexOf(doc.tokenUser) >= 0))) {
  4481. // This share is expired.
  4482. parent.db.Remove(doc._id, function () { }); removed.push(doc.tokenUser);
  4483. } else {
  4484. // This share is ok, remove extra data we don't need to send.
  4485. delete doc._id; delete doc.domain; delete doc.nodeid; delete doc.type; delete doc.userid; delete doc.salt; delete doc.hash;
  4486. okDocs.push(doc);
  4487. }
  4488. }
  4489. try { ws.send(JSON.stringify({ action: 'loginTokens', loginTokens: okDocs })); } catch (ex) { }
  4490. // If any login tokens where removed, event the change.
  4491. if (removed.length > 0) {
  4492. // Dispatch the new event
  4493. var targets = ['*', 'server-users', user._id];
  4494. if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
  4495. var event = { etype: 'user', userid: user._id, username: user.name, action: 'loginTokenChanged', domain: domain.id, loginTokens: okDocs, removed: removed, nolog: 1 };
  4496. parent.parent.DispatchEvent(targets, obj, event);
  4497. }
  4498. });
  4499. break;
  4500. }
  4501. case 'createLoginToken': { // Create a new login token
  4502. var err = null;
  4503. if (req.session.loginToken != null) { err = "Access denied"; } // Do not allow this command when logged in using a login token
  4504. else if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.logintokens === false)) { err = "Not supported"; } // Login tokens are not supported on this server
  4505. else if ((typeof domain.passwordrequirements == 'object') && Array.isArray(domain.passwordrequirements.logintokens) && ((domain.passwordrequirements.logintokens.indexOf(user._id) < 0) && (user.links && Object.keys(user.links).some(key => domain.passwordrequirements.logintokens.indexOf(key) < 0)))) { err = "Not supported"; } // Login tokens are not supported by this user
  4506. else if (common.validateString(command.name, 1, 100) == false) { err = "Invalid name"; } // Check name
  4507. else if ((typeof command.expire != 'number') || (command.expire < 0)) { err = "Invalid expire value"; } // Check expire
  4508. // Handle any errors
  4509. if (err != null) {
  4510. if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'createLoginToken', responseid: command.responseid, result: err })); } catch (ex) { } }
  4511. break;
  4512. }
  4513. // Generate a token username. Don't have any + or / in the username or password
  4514. var tokenUser = '~t:' + Buffer.from(parent.parent.crypto.randomBytes(12), 'binary').toString('base64');
  4515. while ((tokenUser.indexOf('+') >= 0) || (tokenUser.indexOf('/') >= 0)) { tokenUser = '~t:' + Buffer.from(parent.parent.crypto.randomBytes(12), 'binary').toString('base64'); };
  4516. var tokenPass = Buffer.from(parent.parent.crypto.randomBytes(15), 'binary').toString('base64');
  4517. while ((tokenPass.indexOf('+') >= 0) || (tokenPass.indexOf('/') >= 0)) { tokenPass = Buffer.from(parent.parent.crypto.randomBytes(15), 'binary').toString('base64'); };
  4518. // Create a user, generate a salt and hash the password
  4519. require('./pass').hash(tokenPass, function (err, salt, hash, tag) {
  4520. if (err) throw err;
  4521. // Compute expire time
  4522. const created = Date.now();
  4523. var expire = 0;
  4524. if (command.expire > 0) { expire = created + (command.expire * 60000); }
  4525. // Generate the token password
  4526. const dbentry = { _id: 'logintoken-' + tokenUser, type: 'logintoken', nodeid: 'logintoken-' + user._id, userid: user._id, name: command.name, tokenUser: tokenUser, salt: salt, hash: hash, domain: domain.id, created: created, expire: expire };
  4527. parent.db.Set(dbentry);
  4528. // Send the token information back
  4529. try { ws.send(JSON.stringify({ action: 'createLoginToken', name: command.name, tokenUser: tokenUser, tokenPass: tokenPass, created: created, expire: expire })); } catch (ex) { }
  4530. // Dispatch the new event
  4531. var targets = ['*', 'server-users', user._id];
  4532. if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
  4533. var event = { etype: 'user', userid: user._id, username: user.name, action: 'loginTokenAdded', msgid: 115, msg: "Added login token", domain: domain.id, newToken: { name: command.name, tokenUser: tokenUser, created: created, expire: expire } };
  4534. parent.parent.DispatchEvent(targets, obj, event);
  4535. });
  4536. break;
  4537. }
  4538. case 'getDeviceDetails': {
  4539. if ((common.validateStrArray(command.nodeids, 1) == false) && (command.nodeids != null)) break; // Check nodeids
  4540. if (common.validateString(command.type, 3, 4) == false) break; // Check type
  4541. const links = parent.GetAllMeshIdWithRights(user);
  4542. const extraids = getUserExtraIds();
  4543. db.GetAllTypeNoTypeFieldMeshFiltered(links, extraids, domain.id, 'node', null, obj.deviceSkip, obj.deviceLimit, function (err, docs) {
  4544. if (docs == null) return;
  4545. const ids = [];
  4546. if (command.nodeids != null) {
  4547. // Create a list of node ids and query them for last device connection time
  4548. for (var i in command.nodeids) { ids.push('lc' + command.nodeids[i]); }
  4549. } else {
  4550. // Create a list of node ids for this user and query them for last device connection time
  4551. for (var i in docs) { ids.push('lc' + docs[i]._id); }
  4552. }
  4553. db.GetAllIdsOfType(ids, domain.id, 'lastconnect', function (err, docs) {
  4554. const lastConnects = {};
  4555. if (docs != null) { for (var i in docs) { lastConnects[docs[i]._id] = docs[i]; } }
  4556. getDeviceDetailedInfo(command.nodeids, command.type, function (results, type) {
  4557. for (var i = 0; i < results.length; i++) {
  4558. // Remove any device system and network information is we do not have details rights to this device
  4559. if ((parent.GetNodeRights(user, results[i].node.meshid, results[i].node._id) & MESHRIGHT_DEVICEDETAILS) == 0) {
  4560. delete results[i].sys; delete results[i].net;
  4561. }
  4562. // Merge any last connection information
  4563. const lc = lastConnects['lc' + results[i].node._id];
  4564. if (lc != null) { delete lc._id; delete lc.type; delete lc.meshid; delete lc.domain; results[i].lastConnect = lc; }
  4565. // Remove any connectivity and power state information, that should not be in the database anyway.
  4566. // TODO: Find why these are sometimes saved in the db.
  4567. if (results[i].node.conn != null) { delete results[i].node.conn; }
  4568. if (results[i].node.pwr != null) { delete results[i].node.pwr; }
  4569. if (results[i].node.agct != null) { delete results[i].node.agct; }
  4570. if (results[i].node.cict != null) { delete results[i].node.cict; }
  4571. // Add the connection state
  4572. var state = parent.parent.GetConnectivityState(results[i].node._id);
  4573. if (state) {
  4574. results[i].node.conn = state.connectivity;
  4575. results[i].node.pwr = state.powerState;
  4576. if ((state.connectivity & 1) != 0) { var agent = parent.wsagents[results[i].node._id]; if (agent != null) { results[i].node.agct = agent.connectTime; } }
  4577. // Use the connection time of the CIRA/Relay connection
  4578. if ((state.connectivity & 2) != 0) {
  4579. var ciraConnection = parent.parent.mpsserver.GetConnectionToNode(results[i].node._id, null, true);
  4580. if ((ciraConnection != null) && (ciraConnection.tag != null)) { results[i].node.cict = ciraConnection.tag.connectTime; }
  4581. }
  4582. }
  4583. }
  4584. var output = null;
  4585. if (type == 'csv') {
  4586. try {
  4587. // Create the CSV file
  4588. output = 'id,name,rname,host,icon,ip,osdesc,groupname,av,update,firewall,bitlocker,avdetails,tags,lastbootuptime,cpu,osbuild,biosDate,biosVendor,biosVersion,biosSerial,biosMode,boardName,boardVendor,boardVersion,boardSerial,chassisSerial,chassisAssetTag,chassisManufacturer,productUuid,tpmversion,tpmmanufacturer,tpmmanufacturerversion,tpmisactivated,tpmisenabled,tpmisowned,totalMemory,agentOpenSSL,agentCommitDate,agentCommitHash,agentCompileTime,netIfCount,macs,addresses,lastConnectTime,lastConnectAddr\r\n';
  4589. for (var i = 0; i < results.length; i++) {
  4590. const nodeinfo = results[i];
  4591. // Node information
  4592. if (nodeinfo.node != null) {
  4593. const n = nodeinfo.node;
  4594. output += csvClean(n._id) + ',' + csvClean(n.name) + ',' + csvClean(n.rname ? n.rname : '') + ',' + csvClean(n.host ? n.host : '') + ',' + (n.icon ? n.icon : 1) + ',' + (n.ip ? n.ip : '') + ',' + (n.osdesc ? csvClean(n.osdesc) : '') + ',' + csvClean(parent.meshes[n.meshid].name);
  4595. if (typeof n.wsc == 'object') {
  4596. output += ',' + csvClean(n.wsc.antiVirus ? n.wsc.antiVirus : '') + ',' + csvClean(n.wsc.autoUpdate ? n.wsc.autoUpdate : '') + ',' + csvClean(n.wsc.firewall ? n.wsc.firewall : '')
  4597. } else { output += ',,,'; }
  4598. if (typeof n.volumes == 'object') {
  4599. var bitlockerdetails = '', firstbitlocker = true;
  4600. for (var a in n.volumes) { if (typeof n.volumes[a].protectionStatus !== 'undefined') { if (firstbitlocker) { firstbitlocker = false; } else { bitlockerdetails += '|'; } bitlockerdetails += a + '/' + n.volumes[a].volumeStatus; } }
  4601. output += ',' + csvClean(bitlockerdetails);
  4602. } else {
  4603. output += ',';
  4604. }
  4605. if (typeof n.av == 'object') {
  4606. var avdetails = '', firstav = true;
  4607. for (var a in n.av) { if (typeof n.av[a].product == 'string') { if (firstav) { firstav = false; } else { avdetails += '|'; } avdetails += (n.av[a].product + '/' + ((n.av[a].enabled) ? 'enabled' : 'disabled') + '/' + ((n.av[a].updated) ? 'updated' : 'notupdated')); } }
  4608. output += ',' + csvClean(avdetails);
  4609. } else {
  4610. output += ',';
  4611. }
  4612. if (typeof n.tags == 'object') {
  4613. var tagsdetails = '', firsttags = true;
  4614. for (var a in n.tags) { if (firsttags) { firsttags = false; } else { tagsdetails += '|'; } tagsdetails += n.tags[a]; }
  4615. output += ',' + csvClean(tagsdetails);
  4616. } else {
  4617. output += ',';
  4618. }
  4619. if (typeof n.lastbootuptime == 'number') { output += ',' + n.lastbootuptime; } else { output += ','; }
  4620. } else {
  4621. output += ',,,,,,,,,,,,,,,,,,,,';
  4622. }
  4623. // System infomation
  4624. if ((nodeinfo.sys) && (nodeinfo.sys.hardware) && (nodeinfo.sys.hardware.windows)) {
  4625. // Windows
  4626. output += ',';
  4627. if (nodeinfo.sys.hardware.windows.cpu && (nodeinfo.sys.hardware.windows.cpu.length > 0) && (typeof nodeinfo.sys.hardware.windows.cpu[0].Name == 'string')) { output += csvClean(nodeinfo.sys.hardware.windows.cpu[0].Name); }
  4628. output += ',';
  4629. if (nodeinfo.sys.hardware.windows.osinfo && (nodeinfo.sys.hardware.windows.osinfo.BuildNumber)) { output += csvClean(nodeinfo.sys.hardware.windows.osinfo.BuildNumber); }
  4630. output += ',';
  4631. if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.bios_date)) { output += csvClean(nodeinfo.sys.hardware.identifiers.bios_date); }
  4632. output += ',';
  4633. if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.bios_vendor)) { output += csvClean(nodeinfo.sys.hardware.identifiers.bios_vendor); }
  4634. output += ',';
  4635. if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.bios_version)) { output += csvClean(nodeinfo.sys.hardware.identifiers.bios_version); }
  4636. output += ',';
  4637. if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.bios_serial)) { output += csvClean(nodeinfo.sys.hardware.identifiers.bios_serial); }
  4638. output += ',';
  4639. if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.bios_mode)) { output += csvClean(nodeinfo.sys.hardware.identifiers.bios_mode); }
  4640. output += ',';
  4641. if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.board_name)) { output += csvClean(nodeinfo.sys.hardware.identifiers.board_name); }
  4642. output += ',';
  4643. if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.board_vendor)) { output += csvClean(nodeinfo.sys.hardware.identifiers.board_vendor); }
  4644. output += ',';
  4645. if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.board_version)) { output += csvClean(nodeinfo.sys.hardware.identifiers.board_version); }
  4646. output += ',';
  4647. if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.board_serial)) { output += csvClean(nodeinfo.sys.hardware.identifiers.board_serial); }
  4648. output += ',';
  4649. if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.chassis_serial)) { output += csvClean(nodeinfo.sys.hardware.identifiers.chassis_serial); }
  4650. output += ',';
  4651. if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.chassis_assettag)) { output += csvClean(nodeinfo.sys.hardware.identifiers.chassis_assettag); }
  4652. output += ',';
  4653. if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.chassis_manufacturer)) { output += csvClean(nodeinfo.sys.hardware.identifiers.chassis_manufacturer); }
  4654. output += ',';
  4655. if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.product_uuid)) { output += csvClean(nodeinfo.sys.hardware.identifiers.product_uuid); }
  4656. output += ',';
  4657. if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.SpecVersion) { output += csvClean(nodeinfo.sys.hardware.tpm.SpecVersion); }
  4658. output += ',';
  4659. if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.ManufacturerId) { output += csvClean(nodeinfo.sys.hardware.tpm.ManufacturerId); }
  4660. output += ',';
  4661. if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.ManufacturerVersion) { output += csvClean(nodeinfo.sys.hardware.tpm.ManufacturerVersion); }
  4662. output += ',';
  4663. if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.IsActivated) { output += csvClean(nodeinfo.sys.hardware.tpm.IsActivated ? 'true' : 'false'); }
  4664. output += ',';
  4665. if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.IsEnabled) { output += csvClean(nodeinfo.sys.hardware.tpm.IsEnabled ? 'true' : 'false'); }
  4666. output += ',';
  4667. if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.IsOwned) { output += csvClean(nodeinfo.sys.hardware.tpm.IsOwned ? 'true' : 'false'); }
  4668. output += ',';
  4669. if (nodeinfo.sys.hardware.windows.memory) {
  4670. var totalMemory = 0;
  4671. for (var j in nodeinfo.sys.hardware.windows.memory) {
  4672. if (nodeinfo.sys.hardware.windows.memory[j].Capacity) {
  4673. if (typeof nodeinfo.sys.hardware.windows.memory[j].Capacity == 'number') { totalMemory += nodeinfo.sys.hardware.windows.memory[j].Capacity; }
  4674. if (typeof nodeinfo.sys.hardware.windows.memory[j].Capacity == 'string') { totalMemory += parseInt(nodeinfo.sys.hardware.windows.memory[j].Capacity); }
  4675. }
  4676. }
  4677. output += csvClean('' + totalMemory);
  4678. }
  4679. } else if ((nodeinfo.sys) && (nodeinfo.sys.hardware) && (nodeinfo.sys.hardware.mobile)) {
  4680. // Mobile
  4681. output += ',';
  4682. output += ',';
  4683. output += ',';
  4684. output += ',';
  4685. output += ',';
  4686. if (nodeinfo.sys.hardware.mobile && (nodeinfo.sys.hardware.mobile.bootloader)) { output += csvClean(nodeinfo.sys.hardware.mobile.bootloader); }
  4687. output += ',';
  4688. output += ',';
  4689. output += ',';
  4690. if (nodeinfo.sys.hardware.mobile && (nodeinfo.sys.hardware.mobile.model)) { output += csvClean(nodeinfo.sys.hardware.mobile.model); }
  4691. output += ',';
  4692. if (nodeinfo.sys.hardware.mobile && (nodeinfo.sys.hardware.mobile.brand)) { output += csvClean(nodeinfo.sys.hardware.mobile.brand); }
  4693. output += ',';
  4694. output += ',';
  4695. output += ',';
  4696. output += ',';
  4697. output += ',';
  4698. output += ',';
  4699. if (nodeinfo.sys.hardware.mobile && (nodeinfo.sys.hardware.mobile.id)) { output += csvClean(nodeinfo.sys.hardware.mobile.id); }
  4700. output += ',';
  4701. output += ',';
  4702. output += ',';
  4703. output += ',';
  4704. output += ',';
  4705. output += ',';
  4706. output += ',';
  4707. } else if ((nodeinfo.sys) && (nodeinfo.sys.hardware) && (nodeinfo.sys.hardware.linux)) {
  4708. // Linux
  4709. output += ',';
  4710. if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.cpu_name)) { output += csvClean(nodeinfo.sys.hardware.identifiers.cpu_name); }
  4711. output += ',,';
  4712. if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.bios_date)) { output += csvClean(nodeinfo.sys.hardware.linux.bios_date); }
  4713. output += ',';
  4714. if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.bios_vendor)) { output += csvClean(nodeinfo.sys.hardware.linux.bios_vendor); }
  4715. output += ',';
  4716. if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.bios_version)) { output += csvClean(nodeinfo.sys.hardware.linux.bios_version); }
  4717. output += ',';
  4718. if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.product_serial)) { output += csvClean(nodeinfo.sys.hardware.linux.product_serial); }
  4719. else if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.bios_serial)) { output += csvClean(nodeinfo.sys.hardware.identifiers.bios_serial); }
  4720. output += ',';
  4721. if (nodeinfo.sys.hardware.identifiers && (nodeinfo.sys.hardware.identifiers.bios_mode)) { output += csvClean(nodeinfo.sys.hardware.identifiers.bios_mode); }
  4722. output += ',';
  4723. if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.board_name)) { output += csvClean(nodeinfo.sys.hardware.linux.board_name); }
  4724. output += ',';
  4725. if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.board_vendor)) { output += csvClean(nodeinfo.sys.hardware.linux.board_vendor); }
  4726. output += ',';
  4727. if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.board_version)) { output += csvClean(nodeinfo.sys.hardware.linux.board_version); }
  4728. output += ',';
  4729. if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.board_serial)) { output += csvClean(nodeinfo.sys.hardware.linux.board_serial); }
  4730. output += ',';
  4731. if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.chassis_serial)) { output += csvClean(nodeinfo.sys.hardware.linux.chassis_serial); }
  4732. output += ',';
  4733. if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.chassis_assettag)) { output += csvClean(nodeinfo.sys.hardware.linux.chassis_assettag); }
  4734. output += ',';
  4735. if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.chassis_manufacturer)) { output += csvClean(nodeinfo.sys.hardware.linux.chassis_manufacturer); }
  4736. output += ',';
  4737. if (nodeinfo.sys.hardware.linux && (nodeinfo.sys.hardware.linux.product_uuid)) { output += csvClean(nodeinfo.sys.hardware.linux.product_uuid); }
  4738. output += ',';
  4739. if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.SpecVersion) { output += csvClean(nodeinfo.sys.hardware.tpm.SpecVersion); }
  4740. output += ',';
  4741. if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.ManufacturerId) { output += csvClean(nodeinfo.sys.hardware.tpm.ManufacturerId); }
  4742. output += ',';
  4743. if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.ManufacturerVersion) { output += csvClean(nodeinfo.sys.hardware.tpm.ManufacturerVersion); }
  4744. output += ',';
  4745. if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.IsActivated) { output += csvClean(nodeinfo.sys.hardware.tpm.IsActivated ? 'true' : 'false'); }
  4746. output += ',';
  4747. if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.IsEnabled) { output += csvClean(nodeinfo.sys.hardware.tpm.IsEnabled ? 'true' : 'false'); }
  4748. output += ',';
  4749. if (nodeinfo.sys.hardware.tpm && nodeinfo.sys.hardware.tpm.IsOwned) { output += csvClean(nodeinfo.sys.hardware.tpm.IsOwned ? 'true' : 'false'); }
  4750. output += ',';
  4751. if (nodeinfo.sys.hardware.linux.memory) {
  4752. if (nodeinfo.sys.hardware.linux.memory.Memory_Device) {
  4753. var totalMemory = 0;
  4754. for (var j in nodeinfo.sys.hardware.linux.memory.Memory_Device) {
  4755. if (nodeinfo.sys.hardware.linux.memory.Memory_Device[j].Size) {
  4756. if (typeof nodeinfo.sys.hardware.linux.memory.Memory_Device[j].Size == 'number') { totalMemory += nodeinfo.sys.hardware.linux.memory.Memory_Device[j].Size; }
  4757. if (typeof nodeinfo.sys.hardware.linux.memory.Memory_Device[j].Size == 'string') { totalMemory += parseInt(nodeinfo.sys.hardware.linux.memory.Memory_Device[j].Size); }
  4758. }
  4759. }
  4760. output += csvClean('' + (totalMemory * Math.pow(1024, 3)));
  4761. }
  4762. }
  4763. } else {
  4764. output += ',,,,,,,,,,,,,,,,,,,,,,';
  4765. }
  4766. // Agent information
  4767. if ((nodeinfo.sys) && (nodeinfo.sys.hardware) && (nodeinfo.sys.hardware.agentvers)) {
  4768. output += ',';
  4769. if (nodeinfo.sys.hardware.agentvers.openssl) { output += csvClean(nodeinfo.sys.hardware.agentvers.openssl); }
  4770. output += ',';
  4771. if (nodeinfo.sys.hardware.agentvers.commitDate) { output += csvClean(nodeinfo.sys.hardware.agentvers.commitDate); }
  4772. output += ',';
  4773. if (nodeinfo.sys.hardware.agentvers.commitHash) { output += csvClean(nodeinfo.sys.hardware.agentvers.commitHash); }
  4774. output += ',';
  4775. if (nodeinfo.sys.hardware.agentvers.compileTime) { output += csvClean(nodeinfo.sys.hardware.agentvers.compileTime); }
  4776. } else {
  4777. output += ',,,,';
  4778. }
  4779. // Network interfaces
  4780. if ((nodeinfo.net) && (nodeinfo.net.netif2)) {
  4781. output += ',';
  4782. output += Object.keys(nodeinfo.net.netif2).length; // Interface count
  4783. var macs = [], addresses = [];
  4784. for (var j in nodeinfo.net.netif2) {
  4785. if (Array.isArray(nodeinfo.net.netif2[j])) {
  4786. for (var k = 0; k < nodeinfo.net.netif2[j].length; k++) {
  4787. if (typeof nodeinfo.net.netif2[j][k].mac == 'string') { macs.push(nodeinfo.net.netif2[j][k].mac); }
  4788. if (typeof nodeinfo.net.netif2[j][k].address == 'string') { addresses.push(nodeinfo.net.netif2[j][k].address); }
  4789. }
  4790. }
  4791. }
  4792. output += ',';
  4793. output += csvClean(macs.join(' ')); // MACS
  4794. output += ',';
  4795. output += csvClean(addresses.join(' ')); // Addresses
  4796. } else {
  4797. output += ',,,';
  4798. }
  4799. // Last connection information
  4800. if (nodeinfo.lastConnect) {
  4801. output += ',';
  4802. if (nodeinfo.lastConnect.time) {
  4803. // Last connection time
  4804. if ((typeof command.l == 'string') && (typeof command.tz == 'string')) {
  4805. output += csvClean(new Date(nodeinfo.lastConnect.time).toLocaleString(command.l, { timeZone: command.tz }))
  4806. } else {
  4807. output += nodeinfo.lastConnect.time;
  4808. }
  4809. }
  4810. output += ',';
  4811. if (typeof nodeinfo.lastConnect.addr == 'string') { output += csvClean(nodeinfo.lastConnect.addr); } // Last connection address and port
  4812. } else {
  4813. output += ',,';
  4814. }
  4815. output += '\r\n';
  4816. }
  4817. } catch (ex) { console.log(ex); }
  4818. } else {
  4819. // Create the JSON file
  4820. // Add the device group name to each device
  4821. for (var i = 0; i < results.length; i++) {
  4822. const nodeinfo = results[i];
  4823. if (nodeinfo.node) {
  4824. const mesh = parent.meshes[nodeinfo.node.meshid];
  4825. if (mesh) { results[i].node.groupname = mesh.name; }
  4826. }
  4827. }
  4828. output = JSON.stringify(results);
  4829. }
  4830. try { ws.send(JSON.stringify({ action: 'getDeviceDetails', data: output, type: type })); } catch (ex) { }
  4831. });
  4832. });
  4833. });
  4834. break;
  4835. }
  4836. case 'endDesktopMultiplex': {
  4837. var err = null, xuser = null;
  4838. try {
  4839. if (command.xuserid.indexOf('/') < 0) { command.xuserid = 'user/' + domain.id + '/' + command.xuserid; }
  4840. if (common.validateString(command.nodeid, 1, 1024) == false) { err = 'Invalid device identifier'; } // Check the meshid
  4841. else if (command.nodeid.indexOf('/') == -1) { command.nodeid = 'node/' + domain.id + '/' + command.nodeid; }
  4842. const xusersplit = command.xuserid.split('/');
  4843. xuser = parent.users[command.xuserid];
  4844. if (xuser == null) { err = 'User does not exists'; }
  4845. else if ((obj.crossDomain !== true) && ((xusersplit.length != 3) || (xusersplit[1] != domain.id))) { err = 'Invalid domain'; } // Invalid domain, operation only valid for current domain
  4846. } catch (ex) { err = 'Validation exception: ' + ex; }
  4847. // Handle any errors
  4848. if (err != null) { if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'changeusernotify', responseid: command.responseid, result: err })); } catch (ex) { } } break; }
  4849. // Get the node and the rights for this node
  4850. parent.GetNodeWithRights(domain, xuser, command.nodeid, function (node, rights, visible) {
  4851. if ((rights != 0xFFFFFFFF) && (xuser._id != command.xuserid)) return;
  4852. const desktopRelay = parent.desktoprelays[command.nodeid];
  4853. if ((desktopRelay == null) || (desktopRelay === 1)) return; // If the desktopRelay is equal to 1, the relay is being constructed.
  4854. var viewersToClose = []; // Create a list of viewers to close. We don't want to close directly because it will change "desktopRelay.viewers" and we will not enumerate correctly.
  4855. for (var i = 0; i < desktopRelay.viewers.length; i++) {
  4856. const viewer = desktopRelay.viewers[i];
  4857. if ((viewer.user._id == command.xuserid) && (viewer.guestName == command.guestname)) { viewersToClose.push(viewer); } // Only close viewers that match the userid and guestname if present.
  4858. }
  4859. for (var i = 0; i < viewersToClose.length; i++) { viewersToClose[i].close(); } // Close any viewers we need closed.
  4860. // Log the desktop session disconnection
  4861. var targets = ['*', user._id, command.xuserid];
  4862. const splitxuser = command.xuserid.split('/');
  4863. var xusername = splitxuser[2];
  4864. if (command.guestname != null) { xusername += '/' + command.guestname; }
  4865. const event = { etype: 'user', userid: user._id, username: user.name, nodeid: command.nodeid, xuserid: command.xuserid, action: 'endsession', msgid: 134, msgArgs: [xusername], msg: 'Forcibly disconnected desktop session of user ' + xusername, domain: domain.id };
  4866. if (command.guestname != null) { event.guestname = command.guestname; }
  4867. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  4868. parent.parent.DispatchEvent(targets, obj, event);
  4869. });
  4870. break;
  4871. }
  4872. case 'satellite': {
  4873. // Command indicates this is a MeshCentral Satellite session and what featues it supports
  4874. if ((command.setFlags != null) && (typeof command.setFlags == 'number')) { obj.ws.satelliteFlags = command.setFlags; }
  4875. if ((command.reqid != null) && (typeof command.satelliteFlags == 'number')) {
  4876. const event = { action: 'satelliteResponse', subaction: command.subaction, reqid: command.reqid, response: command.response, satelliteFlags: command.satelliteFlags, nolog: 1 }
  4877. if (typeof command.nodeid == 'string') { event.nodeid = command.nodeid; }
  4878. parent.parent.DispatchEvent(['*'], obj, event);
  4879. }
  4880. break;
  4881. }
  4882. case 'importamtdevices': {
  4883. if ((command.amtdevices == null) || (command.meshid == null) || (typeof command.meshid != 'string') || (command.meshid.startsWith('mesh/' + domain.id + '/') == false)) return;
  4884. const mesh = parent.meshes[command.meshid];
  4885. if ((mesh == null) || (mesh.mtype != 1) || (parent.GetMeshRights(user, command.meshid) & MESHRIGHT_EDITMESH) == 0) return null; // This user must have mesh rights to edit the device group
  4886. var amtDevices = [];
  4887. // Decode a JSON file from the Intel SCS migration tool
  4888. if ((typeof command.amtdevices == 'object') && (typeof command.amtdevices.ApplicationData == 'object') && (command.amtdevices.ApplicationData.Application == 'Intel vPro(R) Manageability Migration Tool') && (typeof command.amtdevices['ManagedSystems'] == 'object') && (Array.isArray(command.amtdevices['ManagedSystems']['ManagedSystemsList']))) {
  4889. for (var i in command.amtdevices['ManagedSystems']['ManagedSystemsList']) {
  4890. const importDev = command.amtdevices['ManagedSystems']['ManagedSystemsList'][i];
  4891. var host = null;
  4892. if ((typeof importDev.Fqdn == 'string') && (importDev.Fqdn != '')) { host = importDev.Fqdn; }
  4893. if ((host == null) && (typeof importDev.IPv4 == 'string') && (importDev.IPv4 != '')) { host = importDev.IPv4; }
  4894. if (host != null) {
  4895. // Create a new Intel AMT device
  4896. const nodeid = 'node/' + domain.id + '/' + parent.crypto.randomBytes(48).toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
  4897. const device = { type: 'node', _id: nodeid, meshid: mesh._id, mtype: 1, icon: 1, name: host, host: host, domain: domain.id, intelamt: { user: 'admin', state: 2 } };
  4898. // Add optional fields
  4899. if (typeof importDev.AmtVersion == 'string') { device.intelamt.ver = importDev.AmtVersion; }
  4900. if (typeof importDev.ConfiguredPassword == 'string') { device.intelamt.pass = importDev.ConfiguredPassword; }
  4901. if (typeof importDev.Uuid == 'string') { device.intelamt.uuid = importDev.Uuid; }
  4902. if (importDev.ConnectionType == 'TLS') { device.intelamt.tls = 1; }
  4903. // Check if we are already adding a device with the same hostname, if so, skip it.
  4904. var skip = false;
  4905. for (var i in amtDevices) { if (amtDevices[i].host.toLowerCase() == device.host.toLowerCase()) { skip = true; } }
  4906. if (skip == false) { amtDevices.push(device); }
  4907. }
  4908. }
  4909. }
  4910. // Decode a JSON file from MeshCommander
  4911. if ((typeof command.amtdevices == 'object') && (typeof command.amtdevices.webappversion == 'string') && (Array.isArray(command.amtdevices.computers))) {
  4912. for (var i in command.amtdevices.computers) {
  4913. const importDev = command.amtdevices.computers[i];
  4914. if ((typeof importDev.host == 'string') && (importDev.host != '') && (importDev.host != '127.0.0.1') && (importDev.host.toLowerCase() != 'localhost')) {
  4915. // Create a new Intel AMT device
  4916. const nodeid = 'node/' + domain.id + '/' + parent.crypto.randomBytes(48).toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
  4917. const device = { type: 'node', _id: nodeid, meshid: mesh._id, mtype: 1, icon: 1, host: importDev.host, domain: domain.id, intelamt: { user: 'admin', state: 2 } };
  4918. if (typeof importDev.name == 'string') { device.name = importDev.name; } else { device.name = importDev.host; }
  4919. // Add optional fields
  4920. if (typeof importDev.user == 'string') { device.intelamt.user = importDev.user; }
  4921. if (typeof importDev.pass == 'string') { device.intelamt.pass = importDev.pass; }
  4922. if ((importDev.tls === true) || (importDev.tls === 1)) { device.intelamt.tls = 1; }
  4923. if (typeof importDev.digestrealm == 'string') { device.intelamt.realm = importDev.digestrealm; }
  4924. if (typeof importDev.ver == 'string') { device.intelamt.ver = importDev.ver; }
  4925. if (typeof importDev.uuid == 'string') { device.intelamt.uuid = importDev.uuid; }
  4926. if (typeof importDev.pstate == 'number') { device.intelamt.state = importDev.pstate; }
  4927. if (typeof importDev.tlscerthash == 'string') { device.intelamt.hash = importDev.tlscerthash; }
  4928. if (typeof importDev.icon == 'number') { device.icon = importDev.icon; }
  4929. if (typeof importDev.desc == 'string') { device.desc = importDev.desc; }
  4930. // Check if we are already adding a device with the same hostname, if so, skip it.
  4931. var skip = false;
  4932. for (var i in amtDevices) { if (amtDevices[i].host.toLowerCase() == device.host.toLowerCase()) { skip = true; } }
  4933. if (skip == false) { amtDevices.push(device); }
  4934. }
  4935. }
  4936. }
  4937. // Decode a JSON file in simple format
  4938. if (Array.isArray(command.amtdevices)) {
  4939. for (var i in command.amtdevices) {
  4940. const importDev = command.amtdevices[i];
  4941. if ((typeof importDev.fqdn == 'string') && (importDev.fqdn != '') && (importDev.fqdn != '127.0.0.1') && (importDev.fqdn.toLowerCase() != 'localhost')) {
  4942. // Create a new Intel AMT device
  4943. const nodeid = 'node/' + domain.id + '/' + parent.crypto.randomBytes(48).toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
  4944. const device = { type: 'node', _id: nodeid, meshid: mesh._id, mtype: 1, icon: 1, host: importDev.fqdn, domain: domain.id, intelamt: { user: 'admin', state: 2 } };
  4945. if (typeof importDev.name == 'string') { device.name = importDev.name; } else { device.name = importDev.fqdn; }
  4946. // Add optional fields
  4947. if (typeof importDev.username == 'string') { device.intelamt.user = importDev.username; }
  4948. if (typeof importDev.password == 'string') { device.intelamt.pass = importDev.password; }
  4949. if ((importDev.tls === true) || (importDev.tls === 1)) { device.intelamt.tls = 1; }
  4950. if (typeof importDev.version == 'string') { device.intelamt.ver = importDev.version; }
  4951. if (typeof importDev.digestrealm == 'string') { device.intelamt.realm = importDev.digestrealm; }
  4952. if (typeof importDev.uuid == 'string') { device.intelamt.uuid = importDev.uuid; }
  4953. if (typeof importDev.pstate == 'number') { device.intelamt.state = importDev.pstate; }
  4954. if (typeof importDev.tlscerthash == 'string') { device.intelamt.hash = importDev.tlscerthash; }
  4955. if (typeof importDev.icon == 'number') { device.icon = importDev.icon; }
  4956. if (typeof importDev.desc == 'string') { device.desc = importDev.desc; }
  4957. // Check if we are already adding a device with the same hostname, if so, skip it.
  4958. var skip = false;
  4959. for (var i in amtDevices) { if (amtDevices[i].host.toLowerCase() == device.host.toLowerCase()) { skip = true; } }
  4960. if (skip == false) { amtDevices.push(device); }
  4961. }
  4962. }
  4963. }
  4964. // Add all the correctly parsed devices to the database and event them
  4965. // TODO: We may want to remove any devices with duplicate hostnames
  4966. if (amtDevices.length == 0) return;
  4967. for (var i in amtDevices) {
  4968. // Save the device to the database
  4969. db.Set(amtDevices[i]);
  4970. // Event the new node
  4971. parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(command.meshid, [nodeid]), obj, { etype: 'node', userid: user._id, username: user.name, action: 'addnode', node: parent.CloneSafeNode(amtDevices[i]), msgid: 84, msgArgs: [amtDevices[i].name, mesh.name], msg: 'Added device ' + amtDevices[i].name + ' to device group ' + mesh.name, domain: domain.id });
  4972. }
  4973. break;
  4974. }
  4975. default: {
  4976. // Unknown user action
  4977. console.log('Unknown action from user ' + user.name + ': ' + command.action + '.');
  4978. break;
  4979. }
  4980. }
  4981. }
  4982. const serverCommands = {
  4983. 'adddeviceuser': serverCommandAddDeviceUser,
  4984. 'addmeshuser': serverCommandAddMeshUser,
  4985. 'adduser': serverCommandAddUser,
  4986. 'adduserbatch': serverCommandAddUserBatch,
  4987. 'addusertousergroup': serverCommandAddUserToUserGroup,
  4988. 'agentdisconnect': serverCommandAgentDisconnect,
  4989. 'authcookie': serverCommandAuthCookie,
  4990. 'changeemail': serverCommandChangeEmail,
  4991. 'changelang': serverCommandChangeLang,
  4992. 'close': serverCommandClose,
  4993. 'confirmPhone': serverCommandConfirmPhone,
  4994. 'confirmMessaging': serverCommandConfirmMessaging,
  4995. 'emailuser': serverCommandEmailUser,
  4996. 'files': serverCommandFiles,
  4997. 'getClip': serverCommandGetClip,
  4998. 'getcookie': serverCommandGetCookie,
  4999. 'getnetworkinfo': serverCommandGetNetworkInfo,
  5000. 'getsysinfo': serverCommandGetSysInfo,
  5001. 'intersession': serverCommandInterSession,
  5002. 'interuser': serverCommandInterUser,
  5003. 'lastconnect': serverCommandLastConnect,
  5004. 'lastconnects': serverCommandLastConnects,
  5005. 'logincookie': serverCommandLoginCookie,
  5006. 'meshes': serverCommandMeshes,
  5007. 'ping': serverCommandPing,
  5008. 'pong': serverCommandPong,
  5009. 'powertimeline': serverCommandPowerTimeline,
  5010. 'print': serverCommandPrint,
  5011. 'removePhone': serverCommandRemovePhone,
  5012. 'removeMessaging': serverCommandRemoveMessaging,
  5013. 'removeuserfromusergroup': serverCommandRemoveUserFromUserGroup,
  5014. 'report': serverCommandReport,
  5015. 'serverclearerrorlog': serverCommandServerClearErrorLog,
  5016. 'serverconsole': serverCommandServerConsole,
  5017. 'servererrors': serverCommandServerErrors,
  5018. 'serverconfig': serverCommandServerConfig,
  5019. 'serverstats': serverCommandServerStats,
  5020. 'servertimelinestats': serverCommandServerTimelineStats,
  5021. 'serverupdate': serverCommandServerUpdate,
  5022. 'serverversion': serverCommandServerVersion,
  5023. 'setClip': serverCommandSetClip,
  5024. 'smsuser': serverCommandSmsUser,
  5025. 'msguser': serverCommandMsgUser,
  5026. 'trafficdelta': serverCommandTrafficDelta,
  5027. 'trafficstats': serverCommandTrafficStats,
  5028. 'updateAgents': serverCommandUpdateAgents,
  5029. 'updateUserImage': serverCommandUpdateUserImage,
  5030. 'urlargs': serverCommandUrlArgs,
  5031. 'users': serverCommandUsers,
  5032. 'verifyemail': serverCommandVerifyEmail,
  5033. 'verifyPhone': serverCommandVerifyPhone,
  5034. 'verifyMessaging': serverCommandVerifyMessaging
  5035. };
  5036. const serverUserCommands = {
  5037. '2falock': [serverUserCommand2faLock, "Shows and changes the 2FA lock state"],
  5038. 'acceleratorsstats': [serverUserCommandAcceleratorsStats, "Show data on work being offloaded to other CPU's"],
  5039. 'agentissues': [serverUserCommandAgentIssues, ""],
  5040. 'agentstats': [serverUserCommandAgentStats, ""],
  5041. 'amtacm': [serverUserCommandAmtAcm, ""],
  5042. 'amtmanager': [serverUserCommandAmtManager, ""],
  5043. 'amtpasswords': [serverUserCommandAmtPasswords, ""],
  5044. 'amtstats': [serverUserCommandAmtStats, ""],
  5045. 'args': [serverUserCommandArgs, ""],
  5046. 'autobackup': [serverUserCommandAutoBackup, ""],
  5047. 'backupconfig': [serverUserCommandBackupConfig, ""],
  5048. 'badlogins': [serverUserCommandBadLogins, "Displays or resets the invalid login rate limiting table."],
  5049. 'bad2fa': [serverUserCommandBad2fa, "Displays or resets the invalid 2FA rate limiting table."],
  5050. 'certexpire': [serverUserCommandCertExpire, ""],
  5051. 'certhashes': [serverUserCommandCertHashes, ""],
  5052. 'closeusersessions': [serverUserCommandCloseUserSessions, "Disconnects all sessions for a specified user."],
  5053. 'cores': [serverUserCommandCores, ""],
  5054. 'dbcounters': [serverUserCommandDbCounters, ""],
  5055. 'dbstats': [serverUserCommandDbStats, ""],
  5056. 'dispatchtable': [serverUserCommandDispatchTable, ""],
  5057. 'dropallcira': [serverUserCommandDropAllCira, ""],
  5058. 'dupagents': [serverUserCommandDupAgents, ""],
  5059. 'email': [serverUserCommandEmail, ""],
  5060. 'emailnotifications': [serverUserCommandEmailNotifications, ""],
  5061. 'msgnotifications': [serverUserCommandMessageNotifications, ""],
  5062. 'firebase': [serverUserCommandFirebase, ""],
  5063. 'heapdump': [serverUserCommandHeapDump, ""],
  5064. 'heapdump2': [serverUserCommandHeapDump2, ""],
  5065. 'help': [serverUserCommandHelp, ""],
  5066. 'info': [serverUserCommandInfo, "Returns the most immidiatly useful information about this server, including MeshCentral and NodeJS versions. This is often information required to file a bug. Optionally use info h for human readable form."],
  5067. 'le': [serverUserCommandLe, ""],
  5068. 'lecheck': [serverUserCommandLeCheck, ""],
  5069. 'leevents': [serverUserCommandLeEvents, ""],
  5070. 'maintenance': [serverUserCommandMaintenance, ""],
  5071. 'migrationagents': [serverUserCommandMigrationAgents, ""],
  5072. 'mps': [serverUserCommandMps, ""],
  5073. 'mpsstats': [serverUserCommandMpsStats, ""],
  5074. 'nodeconfig': [serverUserCommandNodeConfig, ""],
  5075. 'print': [serverUserCommandPrint, ""],
  5076. 'relays': [serverUserCommandRelays, ""],
  5077. 'removeinactivedevices': [serverUserCommandRemoveInactiveDevices, ""],
  5078. 'resetserver': [serverUserCommandResetServer, "Causes the server to reset, this is sometimes useful is the config.json file was changed."],
  5079. 'serverupdate': [serverUserCommandServerUpdate, "Updates server to latest version. Optional version argument to install specific version. Example: serverupdate 0.8.49"],
  5080. 'setmaxtasks': [serverUserCommandSetMaxTasks, ""],
  5081. 'showpaths': [serverUserCommandShowPaths, ""],
  5082. 'sms': [serverUserCommandSMS, "Send a SMS message to a specified phone number"],
  5083. 'msg': [serverUserCommandMsg, "Send a user message to a user handle"],
  5084. 'swarmstats': [serverUserCommandSwarmStats, ""],
  5085. 'tasklimiter': [serverUserCommandTaskLimiter, "Returns the internal status of the tasklimiter. This is a system used to smooth out work done by the server. It's used by, for example, agent updates so that not all agents are updated at the same time."],
  5086. 'trafficdelta': [serverUserCommandTrafficDelta, ""],
  5087. 'trafficstats': [serverUserCommandTrafficStats, ""],
  5088. 'updatecheck': [serverUserCommandUpdateCheck, ""],
  5089. 'usersessions': [serverUserCommandUserSessions, "Returns a list of active sessions grouped by user."],
  5090. 'versions': [serverUserCommandVersions, "Returns all internal versions for NodeJS running this server."],
  5091. 'watchdog': [serverUserCommandWatchdog, ""],
  5092. 'webpush': [serverUserCommandWebPush, ""],
  5093. 'webstats': [serverUserCommandWebStats, ""]
  5094. };
  5095. function serverCommandAddDeviceUser(command) {
  5096. if (typeof command.userid == 'string') { command.userids = [command.userid]; }
  5097. var err = null;
  5098. try {
  5099. if (common.validateString(command.nodeid, 1, 1024) == false) { err = 'Invalid nodeid'; } // Check the nodeid
  5100. else if (common.validateInt(command.rights) == false) { err = 'Invalid rights'; } // Device rights must be an integer
  5101. else if ((command.rights & 7) != 0) { err = 'Invalid rights'; } // EDITMESH, MANAGEUSERS or MANAGECOMPUTERS rights can't be assigned to a user to device link
  5102. else if ((common.validateStrArray(command.usernames, 1, 128) == false) && (common.validateStrArray(command.userids, 1, 128) == false)) { err = 'Invalid usernames'; } // Username is between 1 and 128 characters
  5103. else {
  5104. if (command.nodeid.indexOf('/') == -1) { command.nodeid = 'node/' + domain.id + '/' + command.nodeid; }
  5105. else if ((command.nodeid.split('/').length != 3) || (command.nodeid.split('/')[1] != domain.id)) { err = 'Invalid domain'; } // Invalid domain, operation only valid for current domain
  5106. }
  5107. } catch (ex) { err = 'Validation exception: ' + ex; }
  5108. // Handle any errors
  5109. if (err != null) {
  5110. if (command.responseid != null) { obj.send({ action: 'adddeviceuser', responseid: command.responseid, result: err }); }
  5111. return;
  5112. }
  5113. // Convert user names to userid's
  5114. if (command.userids == null) {
  5115. command.userids = [];
  5116. for (var i in command.usernames) {
  5117. if (command.usernames[i] != null) {
  5118. if (parent.users['user/' + domain.id + '/' + command.usernames[i].toLowerCase()] != null) { command.userids.push('user/' + domain.id + '/' + command.usernames[i].toLowerCase()); }
  5119. else if (parent.users['user/' + domain.id + '/' + command.usernames[i]] != null) { command.userids.push('user/' + domain.id + '/' + command.usernames[i]); }
  5120. }
  5121. }
  5122. }
  5123. // Get the node and the rights for this node
  5124. parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
  5125. // Check if already in the right mesh
  5126. if ((node == null) || (node.meshid == command.meshid)) return;
  5127. var dispatchTargets = ['*', node.meshid, node._id];
  5128. // Check that we have rights to manage users on this device
  5129. if ((rights & MESHRIGHT_MANAGEUSERS) == 0) return;
  5130. // Add the new link to the users
  5131. var nodeChanged = false;
  5132. for (var i in command.userids) {
  5133. var newuserid = command.userids[i];
  5134. // Add a user
  5135. var newuser = null;
  5136. if (newuserid.startsWith('ugrp/')) { newuser = parent.userGroups[newuserid]; }
  5137. if (newuserid.startsWith('user/')) {
  5138. newuser = parent.users[newuserid];
  5139. // Search for a user name in that windows domain is the username starts with *\
  5140. if ((newuser == null) && (newuserid.startsWith('user/' + domain.id + '/*\\')) == true) {
  5141. var search = newuserid.split('/')[2].substring(1);
  5142. for (var i in parent.users) { if (i.endsWith(search) && (parent.users[i].domain == domain.id)) { newuser = parent.users[i]; command.userids[i] = newuserid = newuser._id; break; } }
  5143. }
  5144. }
  5145. // Check the the user and device are in the same domain
  5146. if (command.nodeid.split('/')[1] != newuserid.split('/')[1]) return; // Domain mismatch
  5147. if (newuser != null) {
  5148. // Add this user to the dispatch target list
  5149. dispatchTargets.push(newuser._id);
  5150. if (command.remove === true) {
  5151. // Remove link to this user
  5152. if (newuser.links != null) {
  5153. delete newuser.links[command.nodeid];
  5154. if (Object.keys(newuser.links).length == 0) { delete newuser.links; }
  5155. }
  5156. // Remove link to this device
  5157. if (node.links != null) {
  5158. delete node.links[newuserid];
  5159. nodeChanged = true;
  5160. if (Object.keys(node.links).length == 0) { delete node.links; }
  5161. }
  5162. } else {
  5163. // Add the new link to this user
  5164. if (newuser.links == null) { newuser.links = {}; }
  5165. newuser.links[command.nodeid] = { rights: command.rights };
  5166. // Add the new link to the device
  5167. if (node.links == null) { node.links = {}; }
  5168. node.links[newuserid] = { rights: command.rights };
  5169. nodeChanged = true;
  5170. }
  5171. // Save the user to the database
  5172. if (newuserid.startsWith('user/')) {
  5173. db.SetUser(newuser);
  5174. parent.parent.DispatchEvent([newuser], obj, 'resubscribe');
  5175. // Notify user change
  5176. var targets = ['*', 'server-users', newuserid];
  5177. var event;
  5178. if (command.rights == 0) {
  5179. event = { etype: 'user', userid: user._id, username: user.name, action: 'accountchange', msgid: 81, msgArgs: [newuser.name], msg: 'Removed user device rights for ' + newuser.name, domain: domain.id, account: parent.CloneSafeUser(newuser), nodeListChange: newuserid };
  5180. } else {
  5181. event = { etype: 'user', userid: user._id, username: user.name, action: 'accountchange', msgid: 82, msgArgs: [newuser.name], msg: 'Changed user device rights for ' + newuser.name, domain: domain.id, account: parent.CloneSafeUser(newuser), nodeListChange: newuserid };
  5182. }
  5183. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  5184. parent.parent.DispatchEvent(targets, obj, event);
  5185. } else if (newuserid.startsWith('ugrp/')) {
  5186. db.Set(newuser);
  5187. // Notify user group change
  5188. var targets = ['*', 'server-ugroups', newuser._id];
  5189. var event = { etype: 'ugrp', username: user.name, ugrpid: newuser._id, name: newuser.name, action: 'usergroupchange', links: newuser.links, msgid: 79, msgArgs: [newuser.name], msg: 'User group changed: ' + newuser.name, domain: domain.id };
  5190. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  5191. parent.parent.DispatchEvent(targets, obj, event);
  5192. }
  5193. }
  5194. }
  5195. // Save the device
  5196. if (nodeChanged == true) {
  5197. // Save the node to the database
  5198. db.Set(parent.cleanDevice(node));
  5199. // Event the node change
  5200. var event;
  5201. if (command.rights == 0) {
  5202. event = { etype: 'node', userid: user._id, username: user.name, action: 'changenode', nodeid: node._id, domain: domain.id, msgid: 81, msgArgs: [node.name], msg: 'Removed user device rights for ' + node.name, node: parent.CloneSafeNode(node) };
  5203. } else {
  5204. event = { etype: 'node', userid: user._id, username: user.name, action: 'changenode', nodeid: node._id, domain: domain.id, msgid: 82, msgArgs: [node.name], msg: 'Changed user device rights for ' + node.name, node: parent.CloneSafeNode(node) };
  5205. }
  5206. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
  5207. parent.parent.DispatchEvent(dispatchTargets, obj, event);
  5208. }
  5209. if (command.responseid != null) { obj.send({ action: 'adddeviceuser', responseid: command.responseid, result: 'ok' }); }
  5210. });
  5211. }
  5212. function serverCommandAddMeshUser(command) {
  5213. var err = null, mesh, meshIdSplit;
  5214. if (typeof command.userid == 'string') { command.userids = [command.userid]; }
  5215. // Resolve the device group name if needed
  5216. if ((typeof command.meshname == 'string') && (command.meshid == null)) {
  5217. for (var i in parent.meshes) {
  5218. var m = parent.meshes[i];
  5219. if ((m.mtype == 2) && (m.name == command.meshname) && parent.IsMeshViewable(user, m)) {
  5220. if (command.meshid == null) { command.meshid = m._id; } else { err = 'Duplicate device groups found'; }
  5221. }
  5222. }
  5223. }
  5224. var selfMeshRights = 0;
  5225. try {
  5226. if (common.validateString(command.meshid, 8, 134) == false) { err = 'Invalid groupid'; } // Check the meshid
  5227. else if (common.validateInt(command.meshadmin) == false) { err = 'Invalid group rights'; } // Mesh rights must be an integer
  5228. else if ((common.validateStrArray(command.usernames, 1, 128) == false) && (common.validateStrArray(command.userids, 1, 128) == false)) { err = 'Invalid usernames'; } // Username is between 1 and 128 characters
  5229. else {
  5230. if (command.meshid.indexOf('/') == -1) { command.meshid = 'mesh/' + domain.id + '/' + command.meshid; }
  5231. mesh = parent.meshes[command.meshid];
  5232. meshIdSplit = command.meshid.split('/');
  5233. if (mesh == null) { err = 'Unknown group'; }
  5234. else if (((selfMeshRights = parent.GetMeshRights(user, mesh)) & MESHRIGHT_MANAGEUSERS) == 0) { err = 'Permission denied'; }
  5235. else if ((meshIdSplit.length != 3) || (meshIdSplit[1] != domain.id)) { err = 'Invalid domain'; } // Invalid domain, operation only valid for current domain
  5236. }
  5237. } catch (ex) { err = 'Validation exception: ' + ex; }
  5238. // Handle any errors
  5239. if (err != null) {
  5240. if (command.responseid != null) { obj.send({ action: 'addmeshuser', responseid: command.responseid, result: err }); }
  5241. return;
  5242. }
  5243. // Convert user names to userid's
  5244. if (command.userids == null) {
  5245. command.userids = [];
  5246. for (var i in command.usernames) {
  5247. if (parent.users['user/' + domain.id + '/' + command.usernames[i].toLowerCase()] != null) { command.userids.push('user/' + domain.id + '/' + command.usernames[i].toLowerCase()); }
  5248. else if (parent.users['user/' + domain.id + '/' + command.usernames[i]] != null) { command.userids.push('user/' + domain.id + '/' + command.usernames[i]); }
  5249. }
  5250. }
  5251. var unknownUsers = [], successCount = 0, failCount = 0, msgs = [];
  5252. for (var i in command.userids) {
  5253. // Check if the user exists
  5254. var newuserid = command.userids[i], newuser = null;
  5255. if (newuserid.startsWith('user/')) { newuser = parent.users[newuserid]; }
  5256. else if (newuserid.startsWith('ugrp/')) { newuser = parent.userGroups[newuserid]; }
  5257. // Search for a user name in that windows domain is the username starts with *\
  5258. if ((newuser == null) && (newuserid.startsWith('user/' + domain.id + '/*\\')) == true) {
  5259. var search = newuserid.split('/')[2].substring(1);
  5260. for (var i in parent.users) { if (i.endsWith(search) && (parent.users[i].domain == domain.id)) { newuser = parent.users[i]; command.userids[i] = newuserid = parent.users[i]._id; break; } }
  5261. }
  5262. // Make sure this user is in the same domain as the device group
  5263. if (meshIdSplit[1] != newuserid.split('/')[1]) { msgs.push("Mismatch domains"); continue; }
  5264. if (newuser != null) {
  5265. // Can't add or modify self
  5266. if (newuserid == obj.user._id) { msgs.push("Can't change self"); continue; }
  5267. var targetMeshRights = 0;
  5268. if ((newuser.links != null) && (newuser.links[command.meshid] != null) && (newuser.links[command.meshid].rights != null)) { targetMeshRights = newuser.links[command.meshid].rights; }
  5269. if ((targetMeshRights === MESHRIGHT_ADMIN) && (selfMeshRights != MESHRIGHT_ADMIN)) { msgs.push("Can't change rights of device group administrator"); continue; } // A non-admin can't kick out an admin
  5270. if (command.remove === true) {
  5271. // Remove mesh from user or user group
  5272. delete newuser.links[command.meshid];
  5273. } else {
  5274. // Adjust rights since we can't add more rights that we have outself for MESHRIGHT_MANAGEUSERS
  5275. if ((selfMeshRights != MESHRIGHT_ADMIN) && (command.meshadmin == MESHRIGHT_ADMIN)) { msgs.push("Can't set device group administrator, if not administrator"); continue; }
  5276. if (((selfMeshRights & 2) == 0) && ((command.meshadmin & 2) != 0) && ((targetMeshRights & 2) == 0)) { command.meshadmin -= 2; }
  5277. // Add mesh to user or user group
  5278. if (newuser.links == null) { newuser.links = {}; }
  5279. if (newuser.links[command.meshid]) { newuser.links[command.meshid].rights = command.meshadmin; } else { newuser.links[command.meshid] = { rights: command.meshadmin }; }
  5280. }
  5281. if (newuserid.startsWith('user/')) { db.SetUser(newuser); }
  5282. else if (newuserid.startsWith('ugrp/')) { db.Set(newuser); }
  5283. parent.parent.DispatchEvent([newuser._id], obj, 'resubscribe');
  5284. if (newuserid.startsWith('user/')) {
  5285. // Notify user change
  5286. var targets = ['*', 'server-users', user._id, newuser._id];
  5287. var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(newuser), action: 'accountchange', msgid: 78, msgArgs: [newuser.name], msg: 'Device group membership changed: ' + newuser.name, domain: domain.id };
  5288. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  5289. parent.parent.DispatchEvent(targets, obj, event);
  5290. } else if (newuserid.startsWith('ugrp/')) {
  5291. // Notify user group change
  5292. var targets = ['*', 'server-ugroups', user._id, newuser._id];
  5293. var event = { etype: 'ugrp', username: user.name, ugrpid: newuser._id, name: newuser.name, desc: newuser.desc, action: 'usergroupchange', links: newuser.links, msgid: 79, msgArgs: [newuser.name], msg: 'User group changed: ' + newuser.name, domain: domain.id };
  5294. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  5295. parent.parent.DispatchEvent(targets, obj, event);
  5296. }
  5297. var event;
  5298. if (command.remove === true) {
  5299. // Remove userid from the mesh
  5300. delete mesh.links[newuserid];
  5301. db.Set(mesh);
  5302. event = { etype: 'mesh', username: newuser.name, userid: user._id, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msg: 'Removed user ' + newuser.name + ' from device group ' + mesh.name, domain: domain.id, invite: mesh.invite };
  5303. } else {
  5304. // Add userid to the mesh
  5305. mesh.links[newuserid] = { name: newuser.name, rights: command.meshadmin };
  5306. db.Set(mesh);
  5307. event = { etype: 'mesh', username: newuser.name, userid: user._id, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msg: 'Added user ' + newuser.name + ' to device group ' + mesh.name, domain: domain.id, invite: mesh.invite };
  5308. }
  5309. // Notify mesh change
  5310. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the mesh. Another event will come.
  5311. parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(mesh, [user._id, newuserid]), obj, event);
  5312. if (command.remove === true) { msgs.push("Removed user " + newuserid.split('/')[2]); } else { msgs.push("Added user " + newuserid.split('/')[2]); }
  5313. successCount++;
  5314. } else {
  5315. msgs.push("Unknown user " + newuserid.split('/')[2]);
  5316. unknownUsers.push(newuserid.split('/')[2]);
  5317. failCount++;
  5318. }
  5319. }
  5320. if ((successCount == 0) && (failCount == 0)) { msgs.push("Nothing done"); }
  5321. if (unknownUsers.length > 0) {
  5322. // Send error back, user not found.
  5323. displayNotificationMessage('User' + ((unknownUsers.length > 1) ? 's' : '') + ' ' + EscapeHtml(unknownUsers.join(', ')) + ' not found.', "Device Group", 'ServerNotify', 5, (unknownUsers.length > 1) ? 16 : 15, [EscapeHtml(unknownUsers.join(', '))]);
  5324. }
  5325. if (command.responseid != null) { obj.send({ action: 'addmeshuser', responseid: command.responseid, result: msgs.join(', '), success: successCount, failed: failCount }); }
  5326. }
  5327. function serverCommandAddUser(command) {
  5328. // If the email is the username, set this here.
  5329. if (domain.usernameisemail) { if (command.email) { command.username = command.email; } else { command.email = command.username; } }
  5330. // Randomize the password if needed
  5331. if (command.randomPassword === true) { command.pass = getRandomPassword(); }
  5332. // Add a new user account
  5333. var err = null, errid = 0, args = null, newusername, newuserid, newuserdomain;
  5334. try {
  5335. if ((user.siteadmin & MESHRIGHT_MANAGEUSERS) == 0) { err = "Permission denied"; errid = 1; }
  5336. else if (common.validateUsername(command.username, 1, 256) == false) { err = "Invalid username"; errid = 2; } // Username is between 1 and 64 characters, no spaces
  5337. else if ((command.username[0] == '~') || (command.username.indexOf('/') >= 0)) { err = "Invalid username"; errid = 2; } // Usernames cant' start with ~ and can't have '/'
  5338. else if (common.validateString(command.pass, 1, 256) == false) { err = "Invalid password"; errid = 3; } // Password is between 1 and 256 characters
  5339. else if ((command.randomPassword !== true) && (common.checkPasswordRequirements(command.pass, domain.passwordrequirements) == false)) { err = "Invalid password"; errid = 3; } // Password does not meet requirements
  5340. else if ((command.email != null) && (common.validateEmail(command.email, 1, 1024) == false)) { err = "Invalid email"; errid = 4; } // Check if this is a valid email address
  5341. else if ((obj.crossDomain === true) && (command.domain != null) && ((typeof command.domain != 'string') || (parent.parent.config.domains[command.domain] == null))) { err = "Invalid domain"; errid = 5; } // Check if this is a valid domain
  5342. else if ((domain.newaccountemaildomains != null) && Array.isArray(domain.newaccountemaildomains) && !common.validateEmailDomain(command.email, domain.newaccountemaildomains)) { err = "Email domain is not allowed. Only (" + domain.newaccountemaildomains.join(', ') + ") are allowed."; errid=30; args = [common.getEmailDomain(command.email), domain.newaccountemaildomains.join(', ')]; }
  5343. else {
  5344. newuserdomain = domain;
  5345. if ((obj.crossDomain === true) && (command.domain != null)) { newuserdomain = parent.parent.config.domains[command.domain]; }
  5346. newusername = command.username;
  5347. newuserid = 'user/' + newuserdomain.id + '/' + command.username.toLowerCase();
  5348. if (command.siteadmin != null) {
  5349. if ((typeof command.siteadmin != 'number') || (Number.isInteger(command.siteadmin) == false)) { err = "Invalid site permissions"; errid = 6; } // Check permissions
  5350. else if ((user.siteadmin != SITERIGHT_ADMIN) && ((command.siteadmin & (SITERIGHT_ADMIN - 224)) != 0)) { err = "Invalid site permissions"; errid = 6; }
  5351. }
  5352. if (parent.users[newuserid]) { err = "User already exists"; errid = 7; } // Account already exists
  5353. else if ((newuserdomain.auth == 'sspi') || (newuserdomain.auth == 'ldap')) { err = "Unable to add user in this mode"; errid = 8; }
  5354. }
  5355. } catch (ex) { err = "Validation exception"; errid = 9; }
  5356. // Handle any errors
  5357. if (err != null) {
  5358. if (command.responseid != null) {
  5359. obj.send({ action: 'adduser', responseid: command.responseid, result: err, msgid: errid });
  5360. } else {
  5361. // Send error back, user not found.
  5362. displayNotificationMessage(err, "New Account", 'ServerNotify', 1, errid, args);
  5363. }
  5364. return;
  5365. }
  5366. // Check if we exceed the maximum number of user accounts
  5367. db.isMaxType(newuserdomain.limits.maxuseraccounts, 'user', newuserdomain.id, function (maxExceed) {
  5368. if (maxExceed) {
  5369. // Account count exceed, do notification
  5370. if (command.responseid != null) {
  5371. // Respond privately if requested
  5372. obj.send({ action: 'adduser', responseid: command.responseid, result: 'maxUsersExceed' });
  5373. } else {
  5374. // Create the notification message
  5375. var notification = { action: 'msg', type: 'notify', id: Math.random(), value: "Account limit reached.", title: "Server Limit", userid: user._id, username: user.name, domain: newuserdomain.id, titleid: 2, msgid: 10 };
  5376. // Get the list of sessions for this user
  5377. var sessions = parent.wssessions[user._id];
  5378. if (sessions != null) { for (var i in sessions) { try { if (sessions[i].domainid == newuserdomain.id) { sessions[i].send(JSON.stringify(notification)); } } catch (ex) { } } }
  5379. // TODO: Notify all sessions on other peers.
  5380. }
  5381. } else {
  5382. // Remove any events for this userid
  5383. if (command.removeEvents === true) { db.RemoveAllUserEvents(newuserdomain.id, newuserid); }
  5384. // Create a new user
  5385. var newuser = { type: 'user', _id: newuserid, name: newusername, creation: Math.floor(Date.now() / 1000), domain: newuserdomain.id };
  5386. if (command.siteadmin != null) { newuser.siteadmin = command.siteadmin; }
  5387. else if (newuserdomain.newaccountsrights) { newuser.siteadmin = newuserdomain.newaccountsrights; }
  5388. if (command.email != null) { newuser.email = command.email.toLowerCase(); if (command.emailVerified === true) { newuser.emailVerified = true; } } // Email
  5389. if (command.resetNextLogin === true) { newuser.passchange = -1; } else { newuser.passchange = Math.floor(Date.now() / 1000); }
  5390. if (user.groups) { newuser.groups = user.groups; } // New accounts are automatically part of our groups (Realms).
  5391. if (common.validateString(command.realname, 1, 256)) { newuser.realname = command.realname; }
  5392. if ((command.consent != null) && (typeof command.consent == 'number')) { if (command.consent == 0) { delete chguser.consent; } else { newuser.consent = command.consent; } change = 1; }
  5393. if ((command.phone != null) && (typeof command.phone == 'string') && ((command.phone == '') || isPhoneNumber(command.phone))) { if (command.phone == '') { delete newuser.phone; } else { newuser.phone = command.phone; } change = 1; }
  5394. // Auto-join any user groups
  5395. if (typeof newuserdomain.newaccountsusergroups == 'object') {
  5396. for (var i in newuserdomain.newaccountsusergroups) {
  5397. var ugrpid = newuserdomain.newaccountsusergroups[i];
  5398. if (ugrpid.indexOf('/') < 0) { ugrpid = 'ugrp/' + newuserdomain.id + '/' + ugrpid; }
  5399. var ugroup = parent.userGroups[ugrpid];
  5400. if (ugroup != null) {
  5401. // Add group to the user
  5402. if (newuser.links == null) { newuser.links = {}; }
  5403. newuser.links[ugroup._id] = { rights: 1 };
  5404. // Add user to the group
  5405. ugroup.links[newuser._id] = { userid: newuser._id, name: newuser.name, rights: 1 };
  5406. db.Set(ugroup);
  5407. // Notify user group change
  5408. var event = { etype: 'ugrp', userid: user._id, username: user.name, ugrpid: ugroup._id, name: ugroup.name, desc: ugroup.desc, action: 'usergroupchange', links: ugroup.links, msgid: 80, msgArgs: [newuser.name, ugroup.name], msg: 'Added user ' + newuser.name + ' to user group ' + ugroup.name, addUserDomain: newuserdomain.id };
  5409. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user group. Another event will come.
  5410. parent.parent.DispatchEvent(['*', ugroup._id, user._id, newuser._id], obj, event);
  5411. }
  5412. }
  5413. }
  5414. parent.users[newuserid] = newuser;
  5415. // Create a user, generate a salt and hash the password
  5416. require('./pass').hash(command.pass, function (err, salt, hash, tag) {
  5417. if (err == null) {
  5418. newuser.salt = salt;
  5419. newuser.hash = hash;
  5420. db.SetUser(newuser);
  5421. var event, targets = ['*', 'server-users'];
  5422. if (newuser.groups) { for (var i in newuser.groups) { targets.push('server-users:' + i); } }
  5423. if (command.email == null) {
  5424. event = { etype: 'user', userid: newuser._id, username: newusername, account: parent.CloneSafeUser(newuser), action: 'accountcreate', msgid: 64, msgArgs: [command.username], msg: 'Account created, username is ' + command.username, domain: newuserdomain.id };
  5425. } else {
  5426. event = { etype: 'user', userid: newuser._id, username: newusername, account: parent.CloneSafeUser(newuser), action: 'accountcreate', msgid: 65, msgArgs: [command.email.toLowerCase()], msg: 'Account created, email is ' + command.email.toLowerCase(), domain: newuserdomain.id };
  5427. }
  5428. if (parent.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to create the user. Another event will come.
  5429. parent.parent.DispatchEvent(targets, obj, event);
  5430. // Perform email invitation
  5431. if ((command.emailInvitation == true) && (command.emailVerified == true) && command.email && domain.mailserver) {
  5432. domain.mailserver.sendAccountInviteMail(newuserdomain, (user.realname ? user.realname : user.name), newusername, command.email.toLowerCase(), command.pass, parent.getLanguageCodes(req), req.query.key);
  5433. }
  5434. // Log in the auth log
  5435. if (parent.parent.authlog) { parent.parent.authLog('https', 'User ' + user.name + ' created a user account ' + newuser.name); }
  5436. // OK Response
  5437. if (command.responseid != null) { obj.send({ action: 'adduser', responseid: command.responseid, result: 'ok' }); }
  5438. } else {
  5439. if (command.responseid != null) { obj.send({ action: 'adduser', responseid: command.responseid, result: 'passwordHashError' }); }
  5440. }
  5441. }, 0);
  5442. }
  5443. });
  5444. }
  5445. function serverCommandAddUserBatch(command) {
  5446. var err = null;
  5447. // Add many new user accounts
  5448. if ((user.siteadmin & 2) == 0) { err = 'Access denied'; }
  5449. else if ((domain.auth == 'sspi') || (domain.auth == 'ldap')) { err = 'Unable to create users when in SSPI or LDAP mode'; }
  5450. else if (!Array.isArray(command.users)) { err = 'Invalid users'; }
  5451. else {
  5452. var userCount = 0;
  5453. for (var i in command.users) {
  5454. if (domain.usernameisemail) { if (command.users[i].email) { command.users[i].user = command.users[i].email; } else { command.users[i].email = command.users[i].user; } } // If the email is the username, set this here.
  5455. if (common.validateUsername(command.users[i].user, 1, 256) == false) { err = 'Invalid username'; } // Username is between 1 and 64 characters, no spaces
  5456. if ((command.users[i].user[0] == '~') || (command.users[i].user.indexOf('/') >= 0)) { err = 'Invalid username'; } // This is a reserved user name or invalid name
  5457. if (common.validateString(command.users[i].pass, 1, 256) == false) { err = 'Invalid password'; } // Password is between 1 and 256 characters
  5458. if (common.checkPasswordRequirements(command.users[i].pass, domain.passwordrequirements) == false) { err = 'Invalid password'; } // Password does not meet requirements
  5459. if ((command.users[i].email != null) && (common.validateEmail(command.users[i].email, 1, 1024) == false)) { err = 'Invalid email'; } // Check if this is a valid email address
  5460. userCount++;
  5461. }
  5462. }
  5463. // Handle any errors
  5464. if (err != null) {
  5465. if (command.responseid != null) { obj.send({ action: 'adduserbatch', responseid: command.responseid, result: err }); }
  5466. return;
  5467. }
  5468. // Check if we exceed the maximum number of user accounts
  5469. db.isMaxType(domain.limits.maxuseraccounts + userCount, 'user', domain.id, function (maxExceed) {
  5470. if (maxExceed) {
  5471. // Account count exceed, do notification
  5472. // Create the notification message
  5473. var notification = { action: 'msg', type: 'notify', id: Math.random(), value: "Account limit reached.", title: "Server Limit", userid: user._id, username: user.name, domain: domain.id, titleid: 2, msgid: 10 };
  5474. // Get the list of sessions for this user
  5475. var sessions = parent.wssessions[user._id];
  5476. if (sessions != null) { for (var i in sessions) { try { if (sessions[i].domainid == domain.id) { sessions[i].send(JSON.stringify(notification)); } } catch (ex) { } } }
  5477. // TODO: Notify all sessions on other peers.
  5478. } else {
  5479. for (var i in command.users) {
  5480. // Check if this is an existing user
  5481. var newuserid = 'user/' + domain.id + '/' + command.users[i].user.toLowerCase();
  5482. var newuser = { type: 'user', _id: newuserid, name: command.users[i].user, creation: Math.floor(Date.now() / 1000), domain: domain.id };
  5483. if (domain.newaccountsrights) { newuser.siteadmin = domain.newaccountsrights; }
  5484. if (common.validateString(command.users[i].realname, 1, 256)) { newuser.realname = command.users[i].realname; }
  5485. if (command.users[i].email != null) { newuser.email = command.users[i].email.toLowerCase(); if (command.users[i].emailVerified === true) { newuser.emailVerified = true; } } // Email, always lowercase
  5486. if (command.users[i].resetNextLogin === true) { newuser.passchange = -1; } else { newuser.passchange = Math.floor(Date.now() / 1000); }
  5487. if (user.groups) { newuser.groups = user.groups; } // New accounts are automatically part of our groups (Realms).
  5488. if (parent.users[newuserid] == null) {
  5489. parent.users[newuserid] = newuser;
  5490. // Create a user, generate a salt and hash the password
  5491. require('./pass').hash(command.users[i].pass, function (err, salt, hash, newuser) {
  5492. if (err) throw err;
  5493. newuser.salt = salt;
  5494. newuser.hash = hash;
  5495. db.SetUser(newuser);
  5496. var event, targets = ['*', 'server-users'];
  5497. if (newuser.groups) { for (var i in newuser.groups) { targets.push('server-users:' + i); } }
  5498. if (newuser.email == null) {
  5499. event = { etype: 'user', userid: newuser._id, username: newuser.name, account: parent.CloneSafeUser(newuser), action: 'accountcreate', msgid: 64, msgArgs: [newuser.name], msg: 'Account created, username is ' + newuser.name, domain: domain.id };
  5500. } else {
  5501. event = { etype: 'user', userid: newuser._id, username: newuser.name, account: parent.CloneSafeUser(newuser), action: 'accountcreate', msgid: 65, msgArgs: [newuser.email], msg: 'Account created, email is ' + newuser.email, domain: domain.id };
  5502. }
  5503. if (parent.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to create the user. Another event will come.
  5504. parent.parent.DispatchEvent(targets, obj, event);
  5505. // Log in the auth log
  5506. if (parent.parent.authlog) { parent.parent.authLog('https', 'User ' + user.name + ' created user account ' + newuser.name); }
  5507. }, newuser);
  5508. }
  5509. }
  5510. }
  5511. });
  5512. }
  5513. function serverCommandAddUserToUserGroup(command) {
  5514. var err = null;
  5515. try {
  5516. if ((user.siteadmin & SITERIGHT_USERGROUPS) == 0) { err = 'Permission denied'; }
  5517. else if (common.validateString(command.ugrpid, 1, 1024) == false) { err = 'Invalid groupid'; } // Check the meshid
  5518. else if (common.validateStrArray(command.usernames, 1, 128) == false) { err = 'Invalid usernames'; } // Username is between 1 and 128 characters
  5519. else {
  5520. var ugroupidsplit = command.ugrpid.split('/');
  5521. if ((ugroupidsplit.length != 3) || (ugroupidsplit[0] != 'ugrp') || ((obj.crossDomain !== true) && (ugroupidsplit[1] != domain.id))) { err = 'Invalid groupid'; }
  5522. }
  5523. } catch (ex) { err = 'Validation exception: ' + ex; }
  5524. // Fetch the domain
  5525. var addUserDomain = domain;
  5526. if (obj.crossDomain === true) { addUserDomain = parent.parent.config.domains[ugroupidsplit[1]]; }
  5527. if (addUserDomain == null) { err = 'Invalid domain'; }
  5528. // Handle any errors
  5529. if (err != null) {
  5530. if (command.responseid != null) { obj.send({ action: 'addusertousergroup', responseid: command.responseid, result: err }); }
  5531. return;
  5532. }
  5533. // Get the user group
  5534. var group = parent.userGroups[command.ugrpid];
  5535. if (group != null) {
  5536. // If this user group is an externally managed user group, we can't add users to it.
  5537. if ((group != null) && (group.membershipType != null)) return;
  5538. if (group.links == null) { group.links = {}; }
  5539. var unknownUsers = [], addedCount = 0, failCount = 0, knownUsers = [];
  5540. for (var i in command.usernames) {
  5541. // Check if the user exists
  5542. var chguserid = 'user/' + addUserDomain.id + '/' + command.usernames[i].toLowerCase();
  5543. var chguser = parent.users[chguserid];
  5544. if (chguser == null) { chguserid = 'user/' + addUserDomain.id + '/' + command.usernames[i]; chguser = parent.users[chguserid]; }
  5545. if (chguser != null) {
  5546. // Add usr group to user
  5547. if (chguser.links == null) { chguser.links = {}; }
  5548. chguser.links[group._id] = { rights: 1 };
  5549. db.SetUser(chguser);
  5550. parent.parent.DispatchEvent([chguser._id], obj, 'resubscribe');
  5551. knownUsers.push(chguser);
  5552. // Notify user change
  5553. var targets = ['*', 'server-users', user._id, chguser._id];
  5554. var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(chguser), action: 'accountchange', msgid: 67, msgArgs: [chguser.name], msg: 'User group membership changed: ' + chguser.name, domain: addUserDomain.id };
  5555. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  5556. parent.parent.DispatchEvent(targets, obj, event);
  5557. // Add a user to the user group
  5558. group.links[chguserid] = { userid: chguser._id, name: chguser.name, rights: 1 };
  5559. addedCount++;
  5560. } else {
  5561. unknownUsers.push(command.usernames[i]);
  5562. failCount++;
  5563. }
  5564. }
  5565. if (addedCount > 0) {
  5566. // Save the new group to the database
  5567. db.Set(group);
  5568. // Notify user group change
  5569. var event = { etype: 'ugrp', userid: user._id, username: user.name, ugrpid: group._id, name: group.name, desc: group.desc, action: 'usergroupchange', links: group.links, msgid: 71, msgArgs: [knownUsers.map((u)=>u.name), group.name], msg: 'Added user(s) ' + knownUsers.map((u)=>u.name) + ' to user group ' + group.name, addUserDomain: domain.id };
  5570. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user group. Another event will come.
  5571. parent.parent.DispatchEvent(['*', group._id, user._id, chguserid], obj, event);
  5572. }
  5573. if (unknownUsers.length > 0) {
  5574. // Send error back, user not found.
  5575. displayNotificationMessage('User' + ((unknownUsers.length > 1) ? 's' : '') + ' ' + EscapeHtml(unknownUsers.join(', ')) + ' not found.', "Device Group", 'ServerNotify', 5, (unknownUsers.length > 1) ? 16 : 15, [EscapeHtml(unknownUsers.join(', '))]);
  5576. }
  5577. }
  5578. if (command.responseid != null) { obj.send({ action: 'addusertousergroup', responseid: command.responseid, result: 'ok', added: addedCount, failed: failCount }); }
  5579. }
  5580. function serverCommandAgentDisconnect(command) {
  5581. if (common.validateInt(command.disconnectMode) == false) return; // Check disconnect mode
  5582. // Get the node and the rights for this node
  5583. parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
  5584. if ((node == null) || (((rights & MESHRIGHT_AGENTCONSOLE) == 0) && (user.siteadmin != SITERIGHT_ADMIN))) return;
  5585. // Force mesh agent disconnection
  5586. parent.forceMeshAgentDisconnect(user, domain, node._id, command.disconnectMode);
  5587. });
  5588. }
  5589. function serverCommandAuthCookie(command) {
  5590. try {
  5591. ws.send(JSON.stringify({
  5592. action: 'authcookie',
  5593. cookie: parent.parent.encodeCookie({ userid: user._id, domainid: domain.id, ip: req.clientIp }, parent.parent.loginCookieEncryptionKey),
  5594. rcookie: parent.parent.encodeCookie({ ruserid: user._id, x: req.session.x }, parent.parent.loginCookieEncryptionKey)
  5595. }));
  5596. } catch (ex) { }
  5597. }
  5598. function serverCommandChangeEmail(command) {
  5599. // Do not allow this command when logged in using a login token
  5600. if (req.session.loginToken != null) return;
  5601. // If the email is the username, this command is not allowed.
  5602. if (domain.usernameisemail) return;
  5603. // If this account is settings locked, return here.
  5604. if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return;
  5605. // Change our own email address
  5606. if ((domain.auth == 'sspi') || (domain.auth == 'ldap')) return;
  5607. if (common.validateEmail(command.email, 1, 1024) == false) return;
  5608. // Always lowercase the email address
  5609. command.email = command.email.toLowerCase();
  5610. if (obj.user.email != command.email) {
  5611. // Check if this email is already validated on a different account
  5612. db.GetUserWithVerifiedEmail(domain.id, command.email, function (err, docs) {
  5613. if ((docs != null) && (docs.length > 0)) {
  5614. // Notify the duplicate email error
  5615. obj.send({ action: 'msg', type: 'notify', title: 'Account Settings', id: Math.random(), tag: 'ServerNotify', value: 'Failed to change email address, another account already using: ' + command.email + '.', titleid: 4, msgid: 13, args: [command.email] });
  5616. } else {
  5617. // Update the user's email
  5618. var oldemail = user.email;
  5619. user.email = command.email;
  5620. user.emailVerified = false;
  5621. parent.db.SetUser(user);
  5622. // Event the change
  5623. var message = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', domain: domain.id };
  5624. if (db.changeStream) { message.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  5625. if (oldemail != null) {
  5626. message.msg = 'Changed email of user ' + user.name + ' from ' + oldemail + ' to ' + user.email;
  5627. } else {
  5628. message.msg = 'Set email of user ' + user.name + ' to ' + user.email;
  5629. }
  5630. var targets = ['*', 'server-users', user._id];
  5631. if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
  5632. parent.parent.DispatchEvent(targets, obj, message);
  5633. // Log in the auth log
  5634. if (parent.parent.authlog) { parent.parent.authLog('https', 'User ' + user.name + ' changed email from ' + oldemail + ' to ' + user.email); }
  5635. // Send the verification email
  5636. if (domain.mailserver != null) { domain.mailserver.sendAccountCheckMail(domain, user.name, user._id, user.email, parent.getLanguageCodes(req), req.query.key); }
  5637. }
  5638. });
  5639. }
  5640. }
  5641. function serverCommandChangeLang(command) {
  5642. // Do not allow this command when logged in using a login token
  5643. if (req.session.loginToken != null) return;
  5644. // If this account is settings locked, return here.
  5645. if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return;
  5646. if (common.validateString(command.lang, 1, 6) == false) return;
  5647. // Always lowercase the language
  5648. command.lang = command.lang.toLowerCase();
  5649. // Update the user's language
  5650. var oldlang = user.lang;
  5651. if (command.lang == '*') { delete user.lang; } else { user.lang = command.lang; }
  5652. parent.db.SetUser(user);
  5653. // Event the change
  5654. var message = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', domain: domain.id, msgid: 3, msgArgs: ['', (oldlang ? oldlang : 'default'), (user.lang ? user.lang : 'default')] };
  5655. if (db.changeStream) { message.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  5656. message.msg = 'Changed language from ' + (oldlang ? oldlang : 'default') + ' to ' + (user.lang ? user.lang : 'default');
  5657. var targets = ['*', 'server-users', user._id];
  5658. if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
  5659. parent.parent.DispatchEvent(targets, obj, message);
  5660. }
  5661. function serverCommandClose(command) {
  5662. // Close the web socket session
  5663. try { if (obj.req.session.ws == ws) delete obj.req.session.ws; } catch (e) { }
  5664. try { ws.close(); } catch (e) { }
  5665. }
  5666. function serverCommandConfirmPhone(command) {
  5667. // Do not allow this command when logged in using a login token
  5668. if (req.session.loginToken != null) return;
  5669. if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return; // If this account is settings locked, return here.
  5670. if ((parent.parent.smsserver == null) || (typeof command.cookie != 'string') || (typeof command.code != 'string') || (obj.failedSmsCookieCheck == 1)) return; // Input checks
  5671. var cookie = parent.parent.decodeCookie(command.cookie);
  5672. if (cookie == null) return; // Invalid cookie
  5673. if (cookie.s != ws.sessionId) return; // Invalid session
  5674. if (cookie.c != command.code) {
  5675. obj.failedSmsCookieCheck = 1;
  5676. // Code does not match, delay the response to limit how many guesses we can make and don't allow more than 1 guess at any given time.
  5677. setTimeout(function () {
  5678. ws.send(JSON.stringify({ action: 'verifyPhone', cookie: command.cookie, success: true }));
  5679. delete obj.failedSmsCookieCheck;
  5680. }, 2000 + (parent.crypto.randomBytes(2).readUInt16BE(0) % 4095));
  5681. return;
  5682. }
  5683. // Set the user's phone
  5684. user.phone = cookie.p;
  5685. db.SetUser(user);
  5686. // Event the change
  5687. var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msgid: 96, msgArgs: [user.name], msg: 'Verified phone number of user ' + EscapeHtml(user.name), domain: domain.id };
  5688. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  5689. parent.parent.DispatchEvent(['*', 'server-users', user._id], obj, event);
  5690. }
  5691. function serverCommandConfirmMessaging(command) {
  5692. // Do not allow this command when logged in using a login token
  5693. if (req.session.loginToken != null) return;
  5694. if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return; // If this account is settings locked, return here.
  5695. if ((parent.parent.msgserver == null) || (typeof command.cookie != 'string') || (typeof command.code != 'string') || (obj.failedMsgCookieCheck == 1)) return; // Input checks
  5696. var cookie = parent.parent.decodeCookie(command.cookie);
  5697. if (cookie == null) return; // Invalid cookie
  5698. if (cookie.s != ws.sessionId) return; // Invalid session
  5699. if (cookie.c != command.code) {
  5700. obj.failedMsgCookieCheck = 1;
  5701. // Code does not match, delay the response to limit how many guesses we can make and don't allow more than 1 guess at any given time.
  5702. setTimeout(function () {
  5703. ws.send(JSON.stringify({ action: 'verifyMessaging', cookie: command.cookie, success: true }));
  5704. delete obj.failedMsgCookieCheck;
  5705. }, 2000 + (parent.crypto.randomBytes(2).readUInt16BE(0) % 4095));
  5706. return;
  5707. }
  5708. // Set the user's messaging handle
  5709. user.msghandle = cookie.p;
  5710. db.SetUser(user);
  5711. // Event the change
  5712. var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msgid: 156, msgArgs: [user.name], msg: 'Verified messaging account of user ' + EscapeHtml(user.name), domain: domain.id };
  5713. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  5714. parent.parent.DispatchEvent(['*', 'server-users', user._id], obj, event);
  5715. }
  5716. function serverCommandEmailUser(command) {
  5717. var errMsg = null, emailuser = null;
  5718. if (domain.mailserver == null) { errMsg = 'Email server not enabled'; }
  5719. else if ((user.siteadmin & 2) == 0) { errMsg = 'No user management rights'; }
  5720. else if (common.validateString(command.userid, 1, 2048) == false) { errMsg = 'Invalid userid'; }
  5721. else if (common.validateString(command.subject, 1, 1000) == false) { errMsg = 'Invalid subject message'; }
  5722. else if (common.validateString(command.msg, 1, 10000) == false) { errMsg = 'Invalid message'; }
  5723. else {
  5724. emailuser = parent.users[command.userid];
  5725. if (emailuser == null) { errMsg = 'Invalid userid'; }
  5726. else if (emailuser.email == null) { errMsg = 'No validated email address for this user'; }
  5727. else if (emailuser.emailVerified !== true) { errMsg = 'No validated email address for this user'; }
  5728. }
  5729. if (errMsg != null) { displayNotificationMessage(errMsg); return; }
  5730. domain.mailserver.sendMail(emailuser.email, command.subject, command.msg);
  5731. displayNotificationMessage("Email sent.", null, null, null, 14);
  5732. }
  5733. function serverCommandFiles(command) {
  5734. // Send the full list of server files to the browser app
  5735. updateUserFiles(user, ws, domain);
  5736. }
  5737. function serverCommandGetClip(command) {
  5738. if (common.validateString(command.nodeid, 1, 1024) == false) return; // Check nodeid
  5739. // Get the node and the rights for this node
  5740. parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
  5741. if ((rights & MESHRIGHT_REMOTECONTROL) == 0) return;
  5742. // Ask for clipboard data from agent
  5743. var agent = parent.wsagents[node._id];
  5744. if (agent != null) { try { agent.send(JSON.stringify({ action: 'getClip' })); } catch (ex) { } }
  5745. });
  5746. }
  5747. function serverCommandGetCookie(command) {
  5748. // Check if this user has rights on this nodeid
  5749. if (common.validateString(command.nodeid, 1, 1024) == false) return; // Check nodeid
  5750. parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
  5751. if ((node == null) || ((rights & MESHRIGHT_REMOTECONTROL) == 0) || (visible == false)) return; // Access denied.
  5752. // Add a user authentication cookie to a url
  5753. var cookieContent = { userid: user._id, domainid: user.domain };
  5754. if (command.nodeid) { cookieContent.nodeid = command.nodeid; }
  5755. if (command.tcpaddr) { cookieContent.tcpaddr = command.tcpaddr; } // Indicates the browser want the agent to TCP connect to a remote address
  5756. if (command.tcpport) { cookieContent.tcpport = command.tcpport; } // Indicates the browser want the agent to TCP connect to a remote port
  5757. if (command.tag == 'novnc') { cookieContent.p = 12; } // If tag is novnc we must encode a protocol for meshrelay logging
  5758. if (node.mtype == 3) { cookieContent.lc = 1; command.localRelay = true; } // Indicate this is for a local connection
  5759. command.cookie = parent.parent.encodeCookie(cookieContent, parent.parent.loginCookieEncryptionKey);
  5760. command.trustedCert = parent.isTrustedCert(domain);
  5761. obj.send(command);
  5762. });
  5763. }
  5764. function serverCommandGetNetworkInfo(command) {
  5765. if (!validNodeIdAndDomain(command)) return;
  5766. // Get the node and the rights for this node
  5767. parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
  5768. if ((visible == false) || ((rights & MESHRIGHT_DEVICEDETAILS) == 0)) { obj.send({ action: 'getnetworkinfo', nodeid: command.nodeid, tag: command.tag, noinfo: true, result: 'Invalid device id' }); return; }
  5769. // Get network information about this node
  5770. db.Get('if' + node._id, function (err, netinfos) {
  5771. if ((netinfos == null) || (netinfos.length != 1)) { obj.send({ action: 'getnetworkinfo', nodeid: node._id, netif: null, netif2: null }); return; }
  5772. var netinfo = netinfos[0];
  5773. // Unescape any field names that have special characters if needed
  5774. if (netinfo.netif2 != null) {
  5775. for (var i in netinfo.netif2) {
  5776. var esc = common.unEscapeFieldName(i);
  5777. if (esc !== i) { netinfo.netif2[esc] = netinfo.netif2[i]; delete netinfo.netif2[i]; }
  5778. }
  5779. }
  5780. obj.send({ action: 'getnetworkinfo', nodeid: node._id, updateTime: netinfo.updateTime, netif: netinfo.netif, netif2: netinfo.netif2 });
  5781. });
  5782. });
  5783. }
  5784. function serverCommandGetSysInfo(command) {
  5785. if (!validNodeIdAndDomain(command)) return;
  5786. // Get the node and the rights for this node
  5787. parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
  5788. if ((visible == false) || ((rights & MESHRIGHT_DEVICEDETAILS) == 0)) { obj.send({ action: 'getsysinfo', nodeid: command.nodeid, tag: command.tag, noinfo: true, result: 'Invalid device id' }); return; }
  5789. // Query the database system information
  5790. db.Get('si' + command.nodeid, function (err, docs) {
  5791. if ((docs != null) && (docs.length > 0)) {
  5792. var doc = docs[0];
  5793. doc.action = 'getsysinfo';
  5794. doc.nodeid = node._id;
  5795. doc.tag = command.tag;
  5796. delete doc.type;
  5797. delete doc.domain;
  5798. delete doc._id;
  5799. // If this is not a device group admin users, don't send any BitLocker recovery passwords
  5800. if ((rights != MESHRIGHT_ADMIN) && (doc.hardware) && (doc.hardware.windows) && (doc.hardware.windows.volumes)) {
  5801. for (var i in doc.hardware.windows.volumes) { delete doc.hardware.windows.volumes[i].recoveryPassword; }
  5802. }
  5803. if (command.nodeinfo === true) { doc.node = node; doc.rights = rights; }
  5804. obj.send(doc);
  5805. } else {
  5806. obj.send({ action: 'getsysinfo', nodeid: node._id, tag: command.tag, noinfo: true, result: 'Invalid device id' });
  5807. }
  5808. });
  5809. });
  5810. }
  5811. function serverCommandInterSession(command) {
  5812. // Sends data between sessions of the same user
  5813. var sessions = parent.wssessions[obj.user._id];
  5814. if (sessions == null) return;
  5815. // Create the notification message and send on all sessions except our own (no echo back).
  5816. var notification = JSON.stringify(command);
  5817. for (var i in sessions) { if (sessions[i] != obj.ws) { try { sessions[i].send(notification); } catch (ex) { } } }
  5818. // TODO: Send the message of user sessions connected to other servers.
  5819. }
  5820. function serverCommandInterUser(command) {
  5821. // Sends data between users only if allowed. Only a user in the "interUserMessaging": [] list, in the settings section of the config.json can receive and send inter-user messages from and to all users.
  5822. if ((parent.parent.config.settings.interusermessaging == null) || (parent.parent.config.settings.interusermessaging == false) || (command.data == null)) return;
  5823. if (typeof command.sessionid == 'string') { var userSessionId = command.sessionid.split('/'); if (userSessionId.length != 4) return; command.userid = userSessionId[0] + '/' + userSessionId[1] + '/' + userSessionId[2]; }
  5824. if (common.validateString(command.userid, 0, 2014) == false) return;
  5825. var userSplit = command.userid.split('/');
  5826. if (userSplit.length == 1) { command.userid = 'user/' + domain.id + '/' + command.userid; userSplit = command.userid.split('/'); }
  5827. if ((userSplit.length != 3) || (userSplit[0] != 'user') || (userSplit[1] != domain.id) || (parent.users[command.userid] == null)) return; // Make sure the target userid is valid and within the domain
  5828. const allowed = ((parent.parent.config.settings.interusermessaging === true) || (parent.parent.config.settings.interusermessaging.indexOf(obj.user._id) >= 0) || (parent.parent.config.settings.interusermessaging.indexOf(command.userid) >= 0));
  5829. if (allowed == false) return;
  5830. // Get sessions
  5831. var sessions = parent.wssessions[command.userid];
  5832. if (sessions == null) return;
  5833. // Create the notification message and send on all sessions except our own (no echo back).
  5834. var notification = JSON.stringify({ action: 'interuser', sessionid: ws.sessionId, data: command.data, scope: (command.sessionid != null)?'session':'user' });
  5835. for (var i in sessions) {
  5836. if ((command.sessionid != null) && (sessions[i].sessionId != command.sessionid)) continue; // Send to a specific session
  5837. if (sessions[i] != obj.ws) { try { sessions[i].send(notification); } catch (ex) { } }
  5838. }
  5839. // TODO: Send the message of user sessions connected to other servers.
  5840. }
  5841. function serverCommandLastConnect(command) {
  5842. if (!validNodeIdAndDomain(command)) return;
  5843. // Get the node and the rights for this node
  5844. parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
  5845. if (visible == false) { obj.send({ action: 'lastconnect', nodeid: command.nodeid, tag: command.tag, noinfo: true, result: 'Invalid device id' }); return; }
  5846. // Query the database for the last time this node connected
  5847. db.Get('lc' + command.nodeid, function (err, docs) {
  5848. if ((docs != null) && (docs.length > 0)) {
  5849. obj.send({ action: 'lastconnect', nodeid: command.nodeid, time: docs[0].time, addr: docs[0].addr });
  5850. } else {
  5851. obj.send({ action: 'lastconnect', nodeid: command.nodeid, tag: command.tag, noinfo: true, result: 'No data' });
  5852. }
  5853. });
  5854. });
  5855. }
  5856. function serverCommandLastConnects(command) {
  5857. if (obj.visibleDevices == null) {
  5858. // If we are not paging, get all devices visible to this user
  5859. const links = parent.GetAllMeshIdWithRights(user);
  5860. const extraids = getUserExtraIds();
  5861. db.GetAllTypeNoTypeFieldMeshFiltered(links, extraids, domain.id, 'node', null, obj.deviceSkip, obj.deviceLimit, function (err, docs) {
  5862. if (docs == null) return;
  5863. // Create a list of node ids for this user and query them for last device connection time
  5864. const ids = []
  5865. for (var i in docs) { ids.push('lc' + docs[i]._id); }
  5866. // Pull list of last connections only for device owned by this user
  5867. db.GetAllIdsOfType(ids, domain.id, 'lastconnect', function (err, docs) {
  5868. if (docs == null) return;
  5869. const response = {};
  5870. for (var j in docs) { response[docs[j]._id.substring(2)] = docs[j].time; }
  5871. obj.send({ action: 'lastconnects', lastconnects: response, tag: command.tag });
  5872. });
  5873. });
  5874. } else {
  5875. // If we are paging, we know what devices the user is look at
  5876. // Create a list of node ids for this user and query them for last device connection time
  5877. const ids = []
  5878. for (var i in obj.visibleDevices) { ids.push('lc' + i); }
  5879. // Pull list of last connections only for device owned by this user
  5880. db.GetAllIdsOfType(ids, domain.id, 'lastconnect', function (err, docs) {
  5881. if (docs == null) return;
  5882. const response = {};
  5883. for (var j in docs) { response[docs[j]._id.substring(2)] = docs[j].time; }
  5884. obj.send({ action: 'lastconnects', lastconnects: response, tag: command.tag });
  5885. });
  5886. }
  5887. }
  5888. function serverCommandLoginCookie(command) {
  5889. // If allowed, return a login cookie
  5890. if (parent.parent.config.settings.allowlogintoken === true) {
  5891. obj.send({ action: 'logincookie', cookie: parent.parent.encodeCookie({ u: user._id, a: 3 }, parent.parent.loginCookieEncryptionKey) });
  5892. }
  5893. }
  5894. function serverCommandMeshes(command) {
  5895. // Request a list of all meshes this user as rights to
  5896. obj.send({ action: 'meshes', meshes: parent.GetAllMeshWithRights(user).map(parent.CloneSafeMesh), tag: command.tag });
  5897. }
  5898. function serverCommandPing(command) { try { ws.send('{"action":"pong"}'); } catch (ex) { } }
  5899. function serverCommandPong(command) { } // NOP
  5900. function serverCommandPowerTimeline(command) {
  5901. // Get the node and the rights for this node
  5902. parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
  5903. if (visible == false) return;
  5904. // Query the database for the power timeline for a given node
  5905. // The result is a compacted array: [ startPowerState, startTimeUTC, powerState ] + many[ deltaTime, powerState ]
  5906. db.getPowerTimeline(node._id, function (err, docs) {
  5907. if ((err == null) && (docs != null) && (docs.length > 0)) {
  5908. var timeline = [], time = null, previousPower;
  5909. for (i in docs) {
  5910. var doc = docs[i], j = parseInt(i);
  5911. doc.time = Date.parse(doc.time);
  5912. if (time == null) { // First element
  5913. // Skip all starting power 0 events.
  5914. if ((doc.power == 0) && ((doc.oldPower == null) || (doc.oldPower == 0))) continue;
  5915. time = doc.time;
  5916. if (doc.oldPower) { timeline.push(doc.oldPower, time / 1000, doc.power); } else { timeline.push(0, time / 1000, doc.power); }
  5917. } else if (previousPower != doc.power) { // Delta element
  5918. // If this event is of a short duration (2 minutes or less), skip it.
  5919. if ((docs.length > (j + 1)) && ((Date.parse(docs[j + 1].time) - doc.time) < 120000)) continue;
  5920. timeline.push((doc.time - time) / 1000, doc.power);
  5921. time = doc.time;
  5922. }
  5923. previousPower = doc.power;
  5924. }
  5925. obj.send({ action: 'powertimeline', nodeid: node._id, timeline: timeline, tag: command.tag });
  5926. } else {
  5927. // No records found, send current state if we have it
  5928. var state = parent.parent.GetConnectivityState(command.nodeid);
  5929. if (state != null) { obj.send({ action: 'powertimeline', nodeid: node._id, timeline: [state.powerState, Date.now(), state.powerState], tag: command.tag }); }
  5930. }
  5931. });
  5932. });
  5933. }
  5934. function serverCommandPrint(command) { console.log(command.value); }
  5935. function serverCommandRemovePhone(command) {
  5936. // Do not allow this command when logged in using a login token
  5937. if (req.session.loginToken != null) return;
  5938. if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return; // If this account is settings locked, return here.
  5939. if (user.phone == null) return;
  5940. // Clear the user's phone
  5941. delete user.phone;
  5942. db.SetUser(user);
  5943. // Event the change
  5944. var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msgid: 97, msgArgs: [user.name], msg: 'Removed phone number of user ' + EscapeHtml(user.name), domain: domain.id };
  5945. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  5946. parent.parent.DispatchEvent(['*', 'server-users', user._id], obj, event);
  5947. }
  5948. function serverCommandRemoveMessaging(command) {
  5949. // Do not allow this command when logged in using a login token
  5950. if (req.session.loginToken != null) return;
  5951. if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return; // If this account is settings locked, return here.
  5952. if (user.msghandle == null) return;
  5953. // Clear the user's phone
  5954. delete user.msghandle;
  5955. db.SetUser(user);
  5956. // Event the change
  5957. var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msgid: 157, msgArgs: [user.name], msg: 'Removed messaging account of user ' + EscapeHtml(user.name), domain: domain.id };
  5958. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  5959. parent.parent.DispatchEvent(['*', 'server-users', user._id], obj, event);
  5960. }
  5961. function serverCommandRemoveUserFromUserGroup(command) {
  5962. var err = null;
  5963. try {
  5964. if ((user.siteadmin & SITERIGHT_USERGROUPS) == 0) { err = 'Permission denied'; }
  5965. else if (common.validateString(command.ugrpid, 1, 1024) == false) { err = 'Invalid groupid'; }
  5966. else if (common.validateString(command.userid, 1, 256) == false) { err = 'Invalid userid'; }
  5967. else {
  5968. var ugroupidsplit = command.ugrpid.split('/');
  5969. if ((ugroupidsplit.length != 3) || (ugroupidsplit[0] != 'ugrp') || ((obj.crossDomain !== true) && (ugroupidsplit[1] != domain.id))) { err = 'Invalid groupid'; }
  5970. }
  5971. } catch (ex) { err = 'Validation exception: ' + ex; }
  5972. // Fetch the domain
  5973. var removeUserDomain = domain;
  5974. if (obj.crossDomain !== true) { removeUserDomain = parent.parent.config.domains[ugroupidsplit[1]]; }
  5975. if (removeUserDomain == null) { err = 'Invalid domain'; }
  5976. // Handle any errors
  5977. if (err != null) {
  5978. if (command.responseid != null) { obj.send({ action: 'removeuserfromusergroup', responseid: command.responseid, result: err }); }
  5979. return;
  5980. }
  5981. // Check if the user exists
  5982. if (command.userid.startsWith('user/') == false) {
  5983. if (parent.users['user/' + removeUserDomain.id + '/' + command.userid.toLowerCase()] != null) { command.userid = 'user/' + removeUserDomain.id + '/' + command.userid.toLowerCase(); }
  5984. else if (parent.users['user/' + removeUserDomain.id + '/' + command.userid] != null) { command.userid = 'user/' + removeUserDomain.id + '/' + command.userid; }
  5985. }
  5986. var chguser = parent.users[command.userid];
  5987. if (chguser != null) {
  5988. // Get the user group
  5989. var group = parent.userGroups[command.ugrpid];
  5990. // If this user group is an externally managed user group, we can't remove a user from it.
  5991. if ((group != null) && (group.membershipType != null)) return;
  5992. if ((chguser.links != null) && (chguser.links[command.ugrpid] != null)) {
  5993. delete chguser.links[command.ugrpid];
  5994. // Notify user change
  5995. var targets = ['*', 'server-users', user._id, chguser._id];
  5996. var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(chguser), action: 'accountchange', msgid: 67, msgArgs: [chguser.name], msg: 'User group membership changed: ' + chguser.name, domain: removeUserDomain.id };
  5997. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  5998. parent.parent.DispatchEvent(targets, obj, event);
  5999. db.SetUser(chguser);
  6000. parent.parent.DispatchEvent([chguser._id], obj, 'resubscribe');
  6001. }
  6002. if (group != null) {
  6003. // Remove the user from the group
  6004. if ((group.links != null) && (group.links[command.userid] != null)) {
  6005. delete group.links[command.userid];
  6006. db.Set(group);
  6007. // Notify user group change
  6008. var event = { etype: 'ugrp', userid: user._id, username: user.name, ugrpid: group._id, name: group.name, desc: group.desc, action: 'usergroupchange', links: group.links, msgid: 72, msgArgs: [chguser.name, group.name], msg: 'Removed user ' + chguser.name + ' from user group ' + group.name, domain: removeUserDomain.id };
  6009. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user group. Another event will come.
  6010. parent.parent.DispatchEvent(['*', group._id, user._id, chguser._id], obj, event);
  6011. }
  6012. }
  6013. }
  6014. if (command.responseid != null) { obj.send({ action: 'removeuserfromusergroup', responseid: command.responseid, result: 'ok' }); }
  6015. }
  6016. function serverCommandReport(command) {
  6017. if (common.validateInt(command.type, 1, 4) == false) return; // Validate type
  6018. if (common.validateInt(command.groupBy, 1, 3) == false) return; // Validate groupBy: 1 = User, 2 = Device, 3 = Day
  6019. if ((typeof command.start != 'number') || (typeof command.end != 'number') || (command.start >= command.end)) return; // Validate start and end time
  6020. const manageAllDeviceGroups = ((user.siteadmin == 0xFFFFFFFF) && (parent.parent.config.settings.managealldevicegroups.indexOf(user._id) >= 0 || (user.links && Object.keys(user.links).some(key => parent.parent.config.settings.managealldevicegroups.indexOf(key) >= 0))));
  6021. if ((command.devGroup != null) && (manageAllDeviceGroups == false) && ((user.links == null) || (user.links[command.devGroup] == null))) return; // Asking for a device group that is not allowed
  6022. const msgIdFilter = [5, 10, 11, 12, 122, 123, 124, 125, 126, 144];
  6023. switch (command.type) {
  6024. case 1: {
  6025. remoteSessionReport(command, manageAllDeviceGroups, msgIdFilter);
  6026. break;
  6027. }
  6028. case 2: {
  6029. trafficUsageReport(command, msgIdFilter);
  6030. break;
  6031. }
  6032. case 3: {
  6033. userLoginReport(command);
  6034. break;
  6035. }
  6036. case 4: {
  6037. databaseRecordsReport(command);
  6038. break;
  6039. }
  6040. }
  6041. }
  6042. function serverCommandServerClearErrorLog(command) {
  6043. // Clear the server error log if user has site update permissions
  6044. if (userHasSiteUpdate()) { fs.unlink(parent.parent.getConfigFilePath('mesherrors.txt'), function (err) { }); }
  6045. }
  6046. function serverCommandServerConsole(command) {
  6047. // Do not allow this command when logged in using a login token
  6048. if (req.session.loginToken != null) return;
  6049. // This is a server console message, only process this if full administrator
  6050. if (user.siteadmin != SITERIGHT_ADMIN) return;
  6051. // Only accept if the console is allowed for this domain
  6052. if ((domain.myserver === false) || ((domain.myserver != null) && (domain.myserver !== true) && (domain.myserver.console !== true))) return;
  6053. var cmdargs = splitArgs(command.value);
  6054. if (cmdargs.length == 0) return;
  6055. const cmd = cmdargs[0].toLowerCase();
  6056. cmdargs = parseArgs(cmdargs);
  6057. var cmdData = { result: '', command: command, cmdargs: cmdargs };
  6058. // Find the command in the lookup table and run it.
  6059. var cmdTableEntry = serverUserCommands[cmd];
  6060. if (cmdTableEntry != null) { try { cmdTableEntry[0](cmdData); } catch (ex) { cmdData.result = '' + ex; }
  6061. } else { cmdData.result = 'Unknown command \"' + cmd + '\", type \"help\" for list of available commands.'; }
  6062. // Send back the command result
  6063. if (cmdData.result != '') { obj.send({ action: 'serverconsole', value: cmdData.result, tag: command.tag }); }
  6064. }
  6065. function serverCommandServerErrors(command) {
  6066. // Load the server error log
  6067. if (userHasSiteUpdate() && domainHasMyServerErrorLog())
  6068. fs.readFile(parent.parent.getConfigFilePath('mesherrors.txt'), 'utf8', function (err, data) { obj.send({ action: 'servererrors', data: data }); });
  6069. }
  6070. function serverCommandServerConfig(command) {
  6071. // Load the server config.json. This is a sensitive file so care must be taken to only send to trusted administrators.
  6072. if (userHasSiteUpdate() && (domain.myserver !== false) && ((domain.myserver == null) || (domain.myserver.config === true))) {
  6073. const configFilePath = common.joinPath(parent.parent.datapath, (parent.parent.args.configfile ? parent.parent.args.configfile : 'config.json'));
  6074. fs.readFile(configFilePath, 'utf8', function (err, data) { obj.send({ action: 'serverconfig', data: data }); });
  6075. }
  6076. }
  6077. function serverCommandServerStats(command) {
  6078. // Only accept if the "My Server" tab is allowed for this domain
  6079. if (domain.myserver === false) return;
  6080. if ((user.siteadmin & 21) == 0) return; // Only site administrators with "site backup" or "site restore" or "site update" permissions can use this.
  6081. if (common.validateInt(command.interval, 1000, 1000000) == false) {
  6082. // Clear the timer
  6083. if (obj.serverStatsTimer != null) { clearInterval(obj.serverStatsTimer); delete obj.serverStatsTimer; }
  6084. } else {
  6085. // Set the timer
  6086. obj.SendServerStats();
  6087. obj.serverStatsTimer = setInterval(obj.SendServerStats, command.interval);
  6088. }
  6089. }
  6090. function serverCommandServerTimelineStats(command) {
  6091. // Only accept if the "My Server" tab is allowed for this domain
  6092. if (domain.myserver === false) return;
  6093. if ((user.siteadmin & 21) == 0) return; // Only site administrators with "site backup" or "site restore" or "site update" permissions can use this.
  6094. if (common.validateInt(command.hours, 0, 24 * 30) == false) return;
  6095. db.GetServerStats(command.hours, function (err, docs) {
  6096. if (err == null) { obj.send({ action: 'servertimelinestats', events: docs }); }
  6097. });
  6098. }
  6099. function serverCommandServerUpdate(command) {
  6100. // Do not allow this command when logged in using a login token
  6101. if (req.session.loginToken != null) return;
  6102. // Perform server update
  6103. if (userHasSiteUpdate() && domainHasMyServerUpgrade() && !((command.version != null) && (typeof command.version != 'string')))
  6104. parent.parent.performServerUpdate(command.version);
  6105. }
  6106. function serverCommandServerVersion(command) {
  6107. // Do not allow this command when logged in using a login token
  6108. if (req.session.loginToken != null) return;
  6109. // Check the server version
  6110. if (userHasSiteUpdate() && domainHasMyServerUpgrade())
  6111. parent.parent.getServerTags(function (tags, err) { obj.send({ action: 'serverversion', tags: tags }); });
  6112. }
  6113. function serverCommandSetClip(command) {
  6114. if (common.validateString(command.data, 1, 65535) == false) return; // Check
  6115. // Get the node and the rights for this node
  6116. parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
  6117. if ((rights & MESHRIGHT_REMOTECONTROL) == 0) return;
  6118. // Send clipboard data to the agent
  6119. var agent = parent.wsagents[node._id];
  6120. if (agent != null) { try { agent.send(JSON.stringify({ action: 'setClip', data: command.data })); } catch (ex) { } }
  6121. });
  6122. }
  6123. function serverCommandSmsUser(command) {
  6124. var errMsg = null, smsuser = null;
  6125. if (parent.parent.smsserver == null) { errMsg = "SMS gateway not enabled"; }
  6126. else if ((user.siteadmin & 2) == 0) { errMsg = "No user management rights"; }
  6127. else if (common.validateString(command.userid, 1, 2048) == false) { errMsg = "Invalid username"; }
  6128. else if (common.validateString(command.msg, 1, 160) == false) { errMsg = "Invalid SMS message"; }
  6129. else {
  6130. smsuser = parent.users[command.userid];
  6131. if (smsuser == null) { errMsg = "Invalid username"; }
  6132. else if (smsuser.phone == null) { errMsg = "No phone number for this user"; }
  6133. }
  6134. if (errMsg != null) { displayNotificationMessage(errMsg); return; }
  6135. parent.parent.smsserver.sendSMS(smsuser.phone, command.msg, function (success, msg) {
  6136. if (success) {
  6137. displayNotificationMessage("SMS succesfuly sent.", null, null, null, 27);
  6138. } else {
  6139. if (typeof msg == 'string') { displayNotificationMessage("SMS error: " + msg, null, null, null, 29, [msg]); } else { displayNotificationMessage("SMS error", null, null, null, 28); }
  6140. }
  6141. });
  6142. }
  6143. function serverCommandMsgUser(command) {
  6144. var errMsg = null, msguser = null;
  6145. if ((parent.parent.msgserver == null) || (parent.parent.msgserver.providers == 0)) { errMsg = "Messaging server not enabled"; }
  6146. else if ((user.siteadmin & 2) == 0) { errMsg = "No user management rights"; }
  6147. else if (common.validateString(command.userid, 1, 2048) == false) { errMsg = "Invalid username"; }
  6148. else if (common.validateString(command.msg, 1, 160) == false) { errMsg = "Invalid message"; }
  6149. else {
  6150. msguser = parent.users[command.userid];
  6151. if (msguser == null) { errMsg = "Invalid username"; }
  6152. else if (msguser.msghandle == null) { errMsg = "No messaging service configured for this user"; }
  6153. }
  6154. if (errMsg != null) { displayNotificationMessage(errMsg); return; }
  6155. parent.parent.msgserver.sendMessage(msguser.msghandle, command.msg, domain, function (success, msg) {
  6156. if (success) {
  6157. displayNotificationMessage("Message succesfuly sent.", null, null, null, 32);
  6158. } else {
  6159. if (typeof msg == 'string') { displayNotificationMessage("Messaging error: " + msg, null, null, null, 34, [msg]); } else { displayNotificationMessage("Messaging error", null, null, null, 33); }
  6160. }
  6161. });
  6162. }
  6163. function serverCommandTrafficDelta(command) {
  6164. const stats = parent.getTrafficDelta(obj.trafficStats);
  6165. obj.trafficStats = stats.current;
  6166. obj.send({ action: 'trafficdelta', delta: stats.delta });
  6167. }
  6168. function serverCommandTrafficStats(command) {
  6169. obj.send({ action: 'trafficstats', stats: parent.getTrafficStats() });
  6170. }
  6171. function serverCommandUpdateAgents(command) {
  6172. // Update agents for selected devices
  6173. if (common.validateStrArray(command.nodeids, 1) == false) return; // Check nodeids
  6174. for (var i in command.nodeids) { routeCommandToNode({ action: 'msg', type: 'console', nodeid: command.nodeids[i], value: 'agentupdate' }, MESHRIGHT_ADMIN, 0); }
  6175. }
  6176. function serverCommandUpdateUserImage(command) {
  6177. if (req.session.loginToken != null) return; // Do not allow this command when logged in using a login token
  6178. var uid = user._id;
  6179. if ((typeof command.userid == 'string') && ((user.siteadmin & SITERIGHT_MANAGEUSERS) != 0)) { uid = command.userid; }
  6180. var chguser = parent.users[uid], flags = 0, change = 0;
  6181. if (chguser == null) return;
  6182. if (typeof chguser.flags == 'number') { flags = chguser.flags; }
  6183. if (command.image == 0) {
  6184. // Delete the image
  6185. db.Remove('im' + uid);
  6186. if ((flags & 1) != 0) { flags -= 1; change = 1; }
  6187. } else if ((typeof command.image == 'string') && (command.image.length < 600000) && ((command.image.startsWith('data:image/png;base64,') || (command.image.startsWith('data:image/jpeg;base64,'))))) {
  6188. // Save the new image
  6189. db.Set({ _id: 'im' + uid, image: command.image });
  6190. if ((flags & 1) == 0) { flags += 1; }
  6191. change = 1;
  6192. }
  6193. // Update the user if needed
  6194. if (change == 1) {
  6195. chguser.flags = flags;
  6196. db.SetUser(chguser);
  6197. // Event the change
  6198. var targets = ['*', 'server-users', user._id, chguser._id];
  6199. var allTargetGroups = chguser.groups;
  6200. if (allTargetGroups) { for (var i in allTargetGroups) { targets.push('server-users:' + i); } }
  6201. var event = { etype: 'user', userid: uid, username: chguser.name, account: parent.CloneSafeUser(chguser), action: 'accountchange', msgid: 66, msgArgs: [chguser.name], msg: 'Account changed: ' + chguser.name, domain: domain.id, accountImageChange: 1 };
  6202. if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
  6203. parent.parent.DispatchEvent(targets, obj, event);
  6204. }
  6205. }
  6206. function serverCommandUrlArgs(command) {
  6207. console.log(req.query);
  6208. console.log(command.args);
  6209. }
  6210. function serverCommandUsers(command) {
  6211. // Request a list of all users
  6212. if ((user.siteadmin & 2) == 0) { if (command.responseid != null) { obj.send({ action: 'users', responseid: command.responseid, result: 'Access denied' }); } return; }
  6213. var docs = [];
  6214. for (i in parent.users) {
  6215. if (((obj.crossDomain === true) || (parent.users[i].domain == domain.id)) && (parent.users[i].name != '~')) {
  6216. // If we are part of a user group, we can only see other members of our own group
  6217. if ((obj.crossDomain === true) || (user.groups == null) || (user.groups.length == 0) || ((parent.users[i].groups != null) && (findOne(parent.users[i].groups, user.groups)))) {
  6218. docs.push(parent.CloneSafeUser(parent.users[i]));
  6219. }
  6220. }
  6221. }
  6222. obj.send({ action: 'users', users: docs, tag: command.tag });
  6223. }
  6224. function serverCommandVerifyEmail(command) {
  6225. // Do not allow this command when logged in using a login token
  6226. if (req.session.loginToken != null) return;
  6227. // If this account is settings locked, return here.
  6228. if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return;
  6229. // Send a account email verification email
  6230. if ((domain.auth == 'sspi') || (domain.auth == 'ldap')) return;
  6231. if (common.validateString(command.email, 3, 1024) == false) return;
  6232. // Always lowercase the email address
  6233. command.email = command.email.toLowerCase();
  6234. if ((domain.mailserver != null) && (obj.user.email.toLowerCase() == command.email)) {
  6235. // Send the verification email
  6236. domain.mailserver.sendAccountCheckMail(domain, user.name, user._id, user.email, parent.getLanguageCodes(req), req.query.key);
  6237. }
  6238. }
  6239. function serverCommandVerifyPhone(command) {
  6240. // Do not allow this command when logged in using a login token
  6241. if (req.session.loginToken != null) return;
  6242. if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return; // If this account is settings locked, return here.
  6243. if (parent.parent.smsserver == null) return;
  6244. if (common.validateString(command.phone, 1, 18) == false) return; // Check phone length
  6245. if (isPhoneNumber(command.phone) == false) return; // Check phone
  6246. const code = common.zeroPad(getRandomSixDigitInteger(), 6);
  6247. const phoneCookie = parent.parent.encodeCookie({ a: 'verifyPhone', c: code, p: command.phone, s: ws.sessionId });
  6248. parent.parent.smsserver.sendPhoneCheck(domain, command.phone, code, parent.getLanguageCodes(req), function (success) {
  6249. ws.send(JSON.stringify({ action: 'verifyPhone', cookie: phoneCookie, success: success }));
  6250. });
  6251. }
  6252. function serverCommandVerifyMessaging(command) {
  6253. // Do not allow this command when logged in using a login token
  6254. if (req.session.loginToken != null) return;
  6255. if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return; // If this account is settings locked, return here.
  6256. if (parent.parent.msgserver == null) return;
  6257. if (common.validateString(command.handle, 1, 1024) == false) return; // Check handle length
  6258. // Setup the handle for the right messaging service
  6259. var handle = null;
  6260. if ((command.service == 1) && ((parent.parent.msgserver.providers & 1) != 0)) { handle = 'telegram:@' + command.handle; }
  6261. if ((command.service == 4) && ((parent.parent.msgserver.providers & 4) != 0)) { handle = 'discord:' + command.handle; }
  6262. if ((command.service == 8) && ((parent.parent.msgserver.providers & 8) != 0)) { handle = 'xmpp:' + command.handle; }
  6263. if ((command.service == 16) && ((parent.parent.msgserver.providers & 16) != 0)) { handle = parent.parent.msgserver.callmebotUrlToHandle(command.handle); }
  6264. if ((command.service == 32) && ((parent.parent.msgserver.providers & 32) != 0)) { handle = 'pushover:' + command.handle; }
  6265. if ((command.service == 64) && ((parent.parent.msgserver.providers & 64) != 0)) { handle = 'ntfy:' + command.handle; }
  6266. if ((command.service == 128) && ((parent.parent.msgserver.providers & 128) != 0)) { handle = 'zulip:' + command.handle; }
  6267. if ((command.service == 256) && ((parent.parent.msgserver.providers & 256) != 0)) { handle = 'slack:' + command.handle; }
  6268. if (handle == null) return;
  6269. // Send a verification message
  6270. const code = common.zeroPad(getRandomSixDigitInteger(), 6);
  6271. const messagingCookie = parent.parent.encodeCookie({ a: 'verifyMessaging', c: code, p: handle, s: ws.sessionId });
  6272. parent.parent.msgserver.sendMessagingCheck(domain, handle, code, parent.getLanguageCodes(req), function (success) {
  6273. ws.send(JSON.stringify({ action: 'verifyMessaging', cookie: messagingCookie, success: success }));
  6274. });
  6275. }
  6276. function serverUserCommandHelp(cmdData) {
  6277. var fin = '', f = '', availcommands = [];
  6278. for (var i in serverUserCommands) { availcommands.push(i); }
  6279. availcommands = availcommands.sort();
  6280. while (availcommands.length > 0) { if (f.length > 80) { fin += (f + ',\r\n'); f = ''; } f += (((f != '') ? ', ' : ' ') + availcommands.shift()); }
  6281. if (f != '') { fin += f; }
  6282. if (cmdData.cmdargs['_'].length == 0) {
  6283. cmdData.result = 'Available commands: \r\n' + fin + '\r\nType help <command> for details.';
  6284. } else {
  6285. var cmd2 = cmdData.cmdargs['_'][0].toLowerCase();
  6286. var cmdTableEntry = serverUserCommands[cmd2];
  6287. if (cmdTableEntry) {
  6288. if (cmdTableEntry[1] == '') {
  6289. cmdData.result = 'No help available for this command.';
  6290. } else {
  6291. cmdData.result = cmdTableEntry[1]; }
  6292. } else {
  6293. cmdData.result = "This command does not exist.";
  6294. }
  6295. }
  6296. }
  6297. function serverUserCommandCertExpire(cmdData) {
  6298. const now = Date.now();
  6299. for (var i in parent.webCertificateExpire) {
  6300. const domainName = (i == '') ? '[Default]' : i;
  6301. cmdData.result += domainName + ', expires in ' + Math.floor((parent.webCertificateExpire[i] - now) / 86400000) + ' day(s)\r\n';
  6302. }
  6303. }
  6304. function serverUserCommandWebPush(cmdData) {
  6305. if (parent.parent.webpush == null) {
  6306. cmdData.result = "Web push not supported.";
  6307. } else {
  6308. if (cmdData.cmdargs['_'].length != 1) {
  6309. cmdData.result = "Usage: WebPush \"Message\"";
  6310. } else {
  6311. const pushSubscription = { "endpoint": "https://updates.push.services.mozilla.com/wpush/v2/gAAAAABgIkO9hjXHWhMPiuk-ppNRw7r_pUZitddwCEK4ykdzeIxOIjFnYhIt_nr-qUca2mpZziwQsSEhYTUCiuYrhWnVDRweMtiUj16yJJq8V5jneaEaUYjEIe5jp3DOMNpoTm1aHgX74gCR8uTXSITcM97bNi-hRxcQ4f6Ie4WSAmoXpd89B_g", "keys": { "auth": "UB2sbLVK7ALnSHw5P1dahg", "p256dh": "BIoRbcNSxBuTjN39CCCUCHo1f4NxBJ1YDdu_k4MbPW_q3NK1_RufnydUzLPDp8ibBVItSI72-s48QJvOjQ_S8Ok" } }
  6312. parent.parent.webpush.sendNotification(pushSubscription, cmdData.cmdargs['_'][0]).then(
  6313. function (value) { try { ws.send(JSON.stringify({ action: 'OK', value: cmdData.result, tag: cmdData.command.tag })); } catch (ex) { } },
  6314. function (error) { try { ws.send(JSON.stringify({ action: 'Error', value: cmdData.result, tag: cmdData.command.tag })); } catch (ex) { } }
  6315. );
  6316. }
  6317. }
  6318. }
  6319. function serverUserCommandAmtManager(cmdData) {
  6320. if (parent.parent.amtManager == null) {
  6321. cmdData.result = 'Intel AMT Manager not active.';
  6322. } else {
  6323. cmdData.result = parent.parent.amtManager.getStatusString();
  6324. }
  6325. }
  6326. function serverUserCommandCertHashes(cmdData) {
  6327. cmdData.result += 'AgentCertHash: ' + parent.agentCertificateHashHex;
  6328. for (var i in parent.webCertificateHashs) { cmdData.result += '\r\nwebCertificateHash (' + i + '): ' + common.rstr2hex(parent.webCertificateHashs[i]); }
  6329. for (var i in parent.webCertificateFullHashs) { cmdData.result += '\r\nwebCertificateFullHash (' + i + '): ' + common.rstr2hex(parent.webCertificateFullHashs[i]); }
  6330. cmdData.result += '\r\ndefaultWebCertificateHash: ' + common.rstr2hex(parent.defaultWebCertificateHash);
  6331. cmdData.result += '\r\ndefaultWebCertificateFullHash: ' + common.rstr2hex(parent.defaultWebCertificateFullHash);
  6332. }
  6333. function serverUserCommandAmtAcm(cmdData) {
  6334. if ((domain.amtacmactivation == null) || (domain.amtacmactivation.acmmatch == null) || (domain.amtacmactivation.acmmatch.length == 0)) {
  6335. cmdData.result = 'No Intel AMT activation certificates.';
  6336. } else {
  6337. if (domain.amtacmactivation.log != null) { cmdData.result += '--- Activation Log ---\r\nFile : ' + domain.amtacmactivation.log + '\r\n'; }
  6338. for (var i in domain.amtacmactivation.acmmatch) {
  6339. var acmcert = domain.amtacmactivation.acmmatch[i];
  6340. cmdData.result += '--- Activation Certificate ' + (parseInt(i) + 1) + ' ---\r\nName : ' + acmcert.cn + '\r\nSHA1 : ' + acmcert.sha1 + '\r\nSHA256: ' + acmcert.sha256 + '\r\n';
  6341. }
  6342. }
  6343. }
  6344. function serverUserCommandHeapDump(cmdData) {
  6345. // Heapdump support, see example at:
  6346. // https://www.arbazsiddiqui.me/a-practical-guide-to-memory-leaks-in-nodejs/
  6347. if (parent.parent.config.settings.heapdump === true) {
  6348. var dumpFileName = parent.path.join(parent.parent.datapath, `heapDump-${Date.now()}.heapsnapshot`);
  6349. try { ws.send(JSON.stringify({ action: 'serverconsole', value: "Generating dump file at: " + dumpFileName, tag: cmdData.command.tag })); } catch (ex) { }
  6350. require('heapdump').writeSnapshot(dumpFileName, (err, filename) => {
  6351. try { ws.send(JSON.stringify({ action: 'serverconsole', value: "Done.", tag: cmdData.command.tag })); } catch (ex) { }
  6352. });
  6353. } else {
  6354. cmdData.result = "Heapdump not supported, add \"heapdump\":true to settings section of config.json.";
  6355. }
  6356. }
  6357. function serverUserCommandHeapDump2(cmdData) {
  6358. var heapdump = null;
  6359. try { heapdump = require('heapdump'); } catch (ex) { }
  6360. if (heapdump == null) {
  6361. cmdData.result = 'Heapdump module not installed, run "npm install heapdump".';
  6362. } else {
  6363. heapdump.writeSnapshot(function (err, filename) {
  6364. if (err != null) {
  6365. try { ws.send(JSON.stringify({ action: 'serverconsole', value: 'Unable to write heapdump: ' + err })); } catch (ex) { }
  6366. } else {
  6367. try { ws.send(JSON.stringify({ action: 'serverconsole', value: 'Wrote heapdump at ' + filename })); } catch (ex) { }
  6368. }
  6369. });
  6370. }
  6371. }
  6372. function serverUserCommandSMS(cmdData) {
  6373. if (parent.parent.smsserver == null) {
  6374. cmdData.result = "No SMS gateway in use.";
  6375. } else {
  6376. if (cmdData.cmdargs['_'].length != 2) {
  6377. cmdData.result = "Usage: SMS \"PhoneNumber\" \"Message\".";
  6378. } else {
  6379. parent.parent.smsserver.sendSMS(cmdData.cmdargs['_'][0], cmdData.cmdargs['_'][1], function (status, msg) {
  6380. if (typeof msg == 'string') {
  6381. try { ws.send(JSON.stringify({ action: 'serverconsole', value: status ? ('Success: ' + msg) : ('Failed: ' + msg), tag: cmdData.command.tag })); } catch (ex) { }
  6382. } else {
  6383. try { ws.send(JSON.stringify({ action: 'serverconsole', value: status ? 'Success' : 'Failed', tag: cmdData.command.tag })); } catch (ex) { }
  6384. }
  6385. });
  6386. }
  6387. }
  6388. }
  6389. function serverUserCommandMsg(cmdData) {
  6390. if ((parent.parent.msgserver == null) || (parent.parent.msgserver.providers == 0)) {
  6391. cmdData.result = "No messaging providers configured.";
  6392. } else {
  6393. if (cmdData.cmdargs['_'].length != 2) {
  6394. var r = [];
  6395. if ((parent.parent.msgserver.providers & 1) != 0) { r.push("Usage: MSG \"telegram:[@UserHandle]\" \"Message\"."); }
  6396. if ((parent.parent.msgserver.providers & 2) != 0) { r.push("Usage: MSG \"signal:[UserHandle]\" \"Message\"."); }
  6397. if ((parent.parent.msgserver.providers & 4) != 0) { r.push("Usage: MSG \"discord:[Username#0000]\" \"Message\"."); }
  6398. if ((parent.parent.msgserver.providers & 8) != 0) { r.push("Usage: MSG \"xmpp:[[email protected]]\" \"Message\"."); }
  6399. if ((parent.parent.msgserver.providers & 32) != 0) { r.push("Usage: MSG \"pushover:[userkey]\" \"Message\"."); }
  6400. if ((parent.parent.msgserver.providers & 64) != 0) { r.push("Usage: MSG \"ntfy:[topic]\" \"Message\"."); }
  6401. if ((parent.parent.msgserver.providers & 128) != 0) { r.push("Usage: MSG \"zulip:[topic]\" \"Message\"."); }
  6402. if ((parent.parent.msgserver.providers & 256) != 0) { r.push("Usage: MSG \"slack:[webhook]\" \"Message\"."); }
  6403. cmdData.result = r.join('\r\n');
  6404. } else {
  6405. parent.parent.msgserver.sendMessage(cmdData.cmdargs['_'][0], cmdData.cmdargs['_'][1], domain, function (status, msg) {
  6406. if (typeof msg == 'string') {
  6407. try { ws.send(JSON.stringify({ action: 'serverconsole', value: status ? ('Success: ' + msg) : ('Failed: ' + msg), tag: cmdData.command.tag })); } catch (ex) { }
  6408. } else {
  6409. try { ws.send(JSON.stringify({ action: 'serverconsole', value: status ? 'Success' : 'Failed', tag: cmdData.command.tag })); } catch (ex) { }
  6410. }
  6411. });
  6412. }
  6413. }
  6414. }
  6415. function serverUserCommandEmail(cmdData) {
  6416. if (domain.mailserver == null) {
  6417. cmdData.result = "No email service enabled.";
  6418. } else {
  6419. if (cmdData.cmdargs['_'].length != 3) {
  6420. cmdData.result = "Usage: email \"[email protected]\" \"Subject\" \"Message\".";
  6421. } else {
  6422. domain.mailserver.sendMail(cmdData.cmdargs['_'][0], cmdData.cmdargs['_'][1], cmdData.cmdargs['_'][2]);
  6423. cmdData.result = "Done.";
  6424. }
  6425. }
  6426. }
  6427. function serverUserCommandEmailNotifications(cmdData) {
  6428. if (domain.mailserver == null) {
  6429. cmdData.result = "No email service enabled.";
  6430. } else {
  6431. var x = '';
  6432. for (var userid in domain.mailserver.deviceNotifications) {
  6433. x += userid + '\r\n';
  6434. for (var nodeid in domain.mailserver.deviceNotifications[userid].nodes) {
  6435. const info = domain.mailserver.deviceNotifications[userid].nodes[nodeid];
  6436. x += ' ' + info.mn + ', ' + info.nn + ', c:' + (info.c ? info.c : 0) + ', d:' + (info.d ? info.d : 0) + '\r\n';
  6437. }
  6438. }
  6439. cmdData.result = ((x == '') ? 'None' : x);
  6440. }
  6441. }
  6442. function serverUserCommandMessageNotifications(cmdData) {
  6443. if (parent.parent.msgserver == null) {
  6444. cmdData.result = "No messaging service enabled.";
  6445. } else {
  6446. var x = '';
  6447. for (var userid in parent.parent.msgserver.deviceNotifications) {
  6448. x += userid + '\r\n';
  6449. for (var nodeid in parent.parent.msgserver.deviceNotifications[userid].nodes) {
  6450. const info = parent.parent.msgserver.deviceNotifications[userid].nodes[nodeid];
  6451. x += ' ' + info.mn + ', ' + info.nn + ', c:' + (info.c ? info.c : 0) + ', d:' + (info.d ? info.d : 0) + '\r\n';
  6452. }
  6453. }
  6454. cmdData.result = ((x == '') ? 'None' : x);
  6455. }
  6456. }
  6457. function serverUserCommandLe(cmdData) {
  6458. if (parent.parent.letsencrypt == null) {
  6459. cmdData.result = "Let's Encrypt not in use.";
  6460. } else {
  6461. cmdData.result = JSON.stringify(parent.parent.letsencrypt.getStats(), null, 4);
  6462. }
  6463. }
  6464. function serverUserCommandLeCheck(cmdData) {
  6465. if (parent.parent.letsencrypt == null) {
  6466. cmdData.result = "Let's Encrypt not in use.";
  6467. } else {
  6468. cmdData.result = ["CertOK", "Request:NoCert", "Request:Expire", "Request:MissingNames"][parent.parent.letsencrypt.checkRenewCertificate()];
  6469. }
  6470. }
  6471. function serverUserCommandLeEvents(cmdData) {
  6472. if (parent.parent.letsencrypt == null) {
  6473. cmdData.result = "Let's Encrypt not in use.";
  6474. } else {
  6475. cmdData.result = parent.parent.letsencrypt.events.join('\r\n');
  6476. }
  6477. }
  6478. function serverUserCommandBadLogins(cmdData) {
  6479. if (parent.parent.config.settings.maxinvalidlogin == false) {
  6480. cmdData.result = 'Bad login filter is disabled.';
  6481. } else {
  6482. if (cmdData.cmdargs['_'] == 'reset') {
  6483. // Reset bad login table
  6484. parent.badLoginTable = {};
  6485. parent.badLoginTableLastClean = 0;
  6486. cmdData.result = 'Done.';
  6487. } else if (cmdData.cmdargs['_'] == '') {
  6488. // Show current bad login table
  6489. if (typeof parent.parent.config.settings.maxinvalidlogin.coolofftime == 'number') {
  6490. cmdData.result = "Max is " + parent.parent.config.settings.maxinvalidlogin.count + " bad login(s) in " + parent.parent.config.settings.maxinvalidlogin.time + " minute(s), " + parent.parent.config.settings.maxinvalidlogin.coolofftime + " minute(s) cooloff.\r\n";
  6491. } else {
  6492. cmdData.result = "Max is " + parent.parent.config.settings.maxinvalidlogin.count + " bad login(s) in " + parent.parent.config.settings.maxinvalidlogin.time + " minute(s).\r\n";
  6493. }
  6494. var badLoginCount = 0;
  6495. parent.cleanBadLoginTable();
  6496. for (var i in parent.badLoginTable) {
  6497. badLoginCount++;
  6498. if (typeof parent.badLoginTable[i] == 'number') {
  6499. cmdData.result += (i + " - Cooloff for " + Math.floor((parent.badLoginTable[i] - Date.now()) / 60000) + " minute(s)\r\n");
  6500. } else {
  6501. cmdData.result += (i + ' - ' + parent.badLoginTable[i].length + " attempt(s) until Cooloff ban\r\n");
  6502. }
  6503. }
  6504. if (badLoginCount == 0) { cmdData.result += 'No bad logins.'; }
  6505. } else {
  6506. cmdData.result = 'Usage: badlogin [reset]';
  6507. }
  6508. }
  6509. }
  6510. function serverUserCommandBad2fa(cmdData) {
  6511. if (parent.parent.config.settings.maxinvalid2fa == false) {
  6512. cmdData.result = 'Bad 2FA filter is disabled.';
  6513. } else {
  6514. if (cmdData.cmdargs['_'] == 'reset') {
  6515. // Reset bad login table
  6516. parent.bad2faTable = {};
  6517. parent.bad2faTableLastClean = 0;
  6518. cmdData.result = 'Done.';
  6519. } else if (cmdData.cmdargs['_'] == '') {
  6520. // Show current bad login table
  6521. if (typeof parent.parent.config.settings.maxinvalid2fa.coolofftime == 'number') {
  6522. cmdData.result = "Max is " + parent.parent.config.settings.maxinvalid2fa.count + " bad 2FA(s) in " + parent.parent.config.settings.maxinvalid2fa.time + " minute(s), " + parent.parent.config.settings.maxinvalid2fa.coolofftime + " minute(s) cooloff.\r\n";
  6523. } else {
  6524. cmdData.result = "Max is " + parent.parent.config.settings.maxinvalid2fa.count + " bad 2FA(s) in " + parent.parent.config.settings.maxinvalid2fa.time + " minute(s).\r\n";
  6525. }
  6526. var bad2faCount = 0;
  6527. parent.cleanBad2faTable();
  6528. for (var i in parent.bad2faTable) {
  6529. bad2faCount++;
  6530. if (typeof parent.bad2faTable[i] == 'number') {
  6531. cmdData.result += "Cooloff for " + Math.floor((parent.bad2faTable[i] - Date.now()) / 60000) + " minute(s)\r\n";
  6532. } else {
  6533. if (parent.bad2faTable[i].length > 1) {
  6534. cmdData.result += (i + ' - ' + parent.bad2faTable[i].length + " records\r\n");
  6535. } else {
  6536. cmdData.result += (i + ' - ' + parent.bad2faTable[i].length + " record\r\n");
  6537. }
  6538. }
  6539. }
  6540. if (bad2faCount == 0) { cmdData.result += 'No bad 2FA.'; }
  6541. } else {
  6542. cmdData.result = 'Usage: bad2fa [reset]';
  6543. }
  6544. }
  6545. }
  6546. function serverUserCommandDispatchTable(cmdData) {
  6547. for (var i in parent.parent.eventsDispatch) {
  6548. cmdData.result += (i + ', ' + parent.parent.eventsDispatch[i].length + '\r\n');
  6549. }
  6550. }
  6551. function serverUserCommandDropAllCira(cmdData) {
  6552. if (parent.parent.mpsserver == null) { cmdData.result = 'MPS not setup.'; return; }
  6553. const dropCount = parent.parent.mpsserver.dropAllConnections();
  6554. cmdData.result = 'Dropped ' + dropCount + ' connection(s).';
  6555. }
  6556. function serverUserCommandDupAgents(cmdData) {
  6557. for (var i in parent.duplicateAgentsLog) {
  6558. cmdData.result += JSON.stringify(parent.duplicateAgentsLog[i]) + '\r\n';
  6559. }
  6560. if (cmdData.result == '') { cmdData.result = 'No duplicate agents in log.'; }
  6561. }
  6562. function serverUserCommandAgentStats(cmdData) {
  6563. var stats = parent.getAgentStats();
  6564. for (var i in stats) {
  6565. if (typeof stats[i] == 'object') { cmdData.result += (i + ': ' + JSON.stringify(stats[i]) + '\r\n'); } else { cmdData.result += (i + ': ' + stats[i] + '\r\n'); }
  6566. }
  6567. }
  6568. function serverUserCommandAgentIssues(cmdData) {
  6569. var stats = parent.getAgentIssues();
  6570. if (stats.length == 0) {
  6571. cmdData.result = "No agent issues.";
  6572. } else {
  6573. for (var i in stats) { cmdData.result += stats[i].join(', ') + '\r\n'; }
  6574. }
  6575. }
  6576. function serverUserCommandWebStats(cmdData) {
  6577. var stats = parent.getStats();
  6578. for (var i in stats) {
  6579. if (typeof stats[i] == 'object') { cmdData.result += (i + ': ' + JSON.stringify(stats[i]) + '\r\n'); } else { cmdData.result += (i + ': ' + stats[i] + '\r\n'); }
  6580. }
  6581. }
  6582. function serverUserCommandTrafficStats(cmdData) {
  6583. var stats = parent.getTrafficStats();
  6584. for (var i in stats) {
  6585. if (typeof stats[i] == 'object') { cmdData.result += (i + ': ' + JSON.stringify(stats[i]) + '\r\n'); } else { cmdData.result += (i + ': ' + stats[i] + '\r\n'); }
  6586. }
  6587. }
  6588. function serverUserCommandTrafficDelta(cmdData) {
  6589. const stats = parent.getTrafficDelta(obj.trafficStats);
  6590. obj.trafficStats = stats.current;
  6591. for (var i in stats.delta) {
  6592. if (typeof stats.delta[i] == 'object') { cmdData.result += (i + ': ' + JSON.stringify(stats.delta[i]) + '\r\n'); } else { cmdData.result += (i + ': ' + stats.delta[i] + '\r\n'); }
  6593. }
  6594. }
  6595. function serverUserCommandWatchdog(cmdData) {
  6596. if (parent.parent.watchdog == null) {
  6597. cmdData.result = 'Server watchdog not active.';
  6598. } else {
  6599. cmdData.result = 'Server watchdog active.\r\n';
  6600. if (parent.parent.watchdogmaxtime != null) { cmdData.result += 'Largest timeout was ' + parent.parent.watchdogmax + 'ms on ' + parent.parent.watchdogmaxtime + '\r\n'; }
  6601. for (var i in parent.parent.watchdogtable) { cmdData.result += parent.parent.watchdogtable[i] + '\r\n'; }
  6602. }
  6603. }
  6604. function serverUserCommand2faLock(cmdData) {
  6605. var arg = null;
  6606. if (cmdData.cmdargs['_'].length > 0) { arg = cmdData.cmdargs['_'][0]; }
  6607. if (domain.passwordrequirements == null) { domain.passwordrequirements = {}; }
  6608. if (arg == 'set') {
  6609. // TODO: Change 2FA lock for peer servers
  6610. domain.passwordrequirements.lock2factor = true;
  6611. cmdData.result = "2FA lock is set";
  6612. parent.parent.DispatchEvent(['server-allusers'], obj, { action: 'serverinfochange', lock2factor: true, nolog: 1, domain: domain.id });
  6613. } else if (arg == 'clear') {
  6614. // TODO: Change 2FA lock for peer servers
  6615. delete domain.passwordrequirements.lock2factor;
  6616. cmdData.result = "2FA lock is cleared";
  6617. parent.parent.DispatchEvent(['server-allusers'], obj, { action: 'serverinfochange', lock2factor: false, nolog: 1, domain: domain.id });
  6618. } else {
  6619. cmdData.result = (domain.passwordrequirements.lock2factor == true) ? "2FA lock is set" : "2FA lock is cleared";
  6620. cmdData.result += ", use '2falock [set/clear]' to change the lock state."
  6621. }
  6622. }
  6623. function serverUserCommandAcceleratorsStats(cmdData) {
  6624. var stats = parent.parent.certificateOperations.getAcceleratorStats();
  6625. for (var i in stats) {
  6626. if (typeof stats[i] == 'object') { cmdData.result += (i + ': ' + JSON.stringify(stats[i]) + '\r\n'); } else { cmdData.result += (i + ': ' + stats[i] + '\r\n'); }
  6627. }
  6628. }
  6629. function serverUserCommandMpsStats(cmdData) {
  6630. if (parent.parent.mpsserver == null) {
  6631. cmdData.result = 'MPS not enabled.';
  6632. } else {
  6633. var stats = parent.parent.mpsserver.getStats();
  6634. for (var i in stats) {
  6635. if (typeof stats[i] == 'object') { cmdData.result += (i + ': ' + JSON.stringify(stats[i]) + '\r\n'); } else { cmdData.result += (i + ': ' + stats[i] + '\r\n'); }
  6636. }
  6637. }
  6638. }
  6639. function serverUserCommandMps(cmdData) {
  6640. if (parent.parent.mpsserver == null) {
  6641. cmdData.result = 'MPS not enabled.';
  6642. } else {
  6643. const connectionTypes = ['CIRA', 'Relay', 'LMS'];
  6644. for (var nodeid in parent.parent.mpsserver.ciraConnections) {
  6645. cmdData.result += nodeid;
  6646. var connections = parent.parent.mpsserver.ciraConnections[nodeid];
  6647. for (var i in connections) { cmdData.result += ', ' + connectionTypes[connections[i].tag.connType]; }
  6648. cmdData.result += '\r\n';
  6649. }
  6650. if (cmdData.result == '') { cmdData.result = 'MPS has not connections.'; }
  6651. }
  6652. }
  6653. function serverUserCommandDbStats(cmdData) {
  6654. parent.parent.db.getDbStats(function (stats) {
  6655. var r2 = '';
  6656. for (var i in stats) { r2 += (i + ': ' + stats[i] + '\r\n'); }
  6657. try { ws.send(JSON.stringify({ action: 'serverconsole', value: r2, tag: cmdData.command.tag })); } catch (ex) { }
  6658. });
  6659. }
  6660. function serverUserCommandDbCounters(cmdData) {
  6661. try { ws.send(JSON.stringify({ action: 'serverconsole', value: JSON.stringify(parent.parent.db.dbCounters, null, 2), tag: cmdData.command.tag })); } catch (ex) { }
  6662. }
  6663. function serverUserCommandServerUpdate(cmdData) {
  6664. cmdData.result = 'Performing server update...';
  6665. var version = null;
  6666. if (cmdData.cmdargs['_'].length > 0) {
  6667. version = cmdData.cmdargs['_'][0];
  6668. // This call is SLOW. We only want to validate version if we have to
  6669. if (version != 'stable' && version != 'latest') {
  6670. parent.parent.getServerVersions((data) => {
  6671. var versions = JSON.parse(data);
  6672. if (versions.includes(version)) {
  6673. if (parent.parent.performServerUpdate(version) == false) {
  6674. try {
  6675. ws.send(JSON.stringify({ action: 'serverconsole',
  6676. value: 'Server self-update not possible.'}));
  6677. } catch (ex) { }
  6678. }
  6679. } else {
  6680. try {
  6681. ws.send(JSON.stringify({ action: 'serverconsole',
  6682. value: 'Invalid version. Aborting update'}));
  6683. } catch (ex) { }
  6684. }
  6685. });
  6686. } else {
  6687. if (parent.parent.performServerUpdate(version) == false) {
  6688. cmdData.result = 'Server self-update not possible.';
  6689. }
  6690. }
  6691. } else {
  6692. if (parent.parent.performServerUpdate(version) == false) {
  6693. cmdData.result = 'Server self-update not possible.';
  6694. }
  6695. }
  6696. }
  6697. function serverUserCommandPrint(cmdData) {
  6698. console.log(cmdData.cmdargs['_'][0]);
  6699. }
  6700. function serverUserCommandAmtPasswords(cmdData) {
  6701. if (parent.parent.amtPasswords == null) {
  6702. cmdData.result = "No Intel AMT password table."
  6703. } else {
  6704. for (var i in parent.parent.amtPasswords) { cmdData.result += (i + ' - ' + parent.parent.amtPasswords[i].join(', ') + '\r\n'); }
  6705. }
  6706. }
  6707. function serverUserCommandAmtStats(cmdData) {
  6708. parent.parent.db.GetAllType('node', function (err, docs) {
  6709. var r = '';
  6710. if (err != null) {
  6711. r = "Error occured.";
  6712. } else if ((docs == null) || (docs.length == 0)) {
  6713. r = "No devices in database"
  6714. } else {
  6715. var amtData = { total: 0, versions: {}, state: {} };
  6716. for (var i in docs) {
  6717. const node = docs[i];
  6718. if (node.intelamt != null) {
  6719. amtData['total']++;
  6720. if (node.intelamt.ver != null) { if (amtData.versions[node.intelamt.ver] == null) { amtData.versions[node.intelamt.ver] = 1; } else { amtData.versions[node.intelamt.ver]++; } }
  6721. if (node.intelamt.state != null) { if (amtData.state[node.intelamt.state] == null) { amtData.state[node.intelamt.state] = 1; } else { amtData.state[node.intelamt.state]++; } }
  6722. }
  6723. }
  6724. if (amtData.total == 0) {
  6725. r = "No Intel AMT devices found"
  6726. } else {
  6727. r = "Total Intel AMT devices: " + amtData['total'] + '\r\n';
  6728. r += "Un-provisionned: " + amtData['state'][0] + '\r\n';
  6729. r += "Provisionned: " + amtData['state'][2] + '\r\n';
  6730. r += "Versions: " + '\r\n';
  6731. // Sort the Intel AMT versions
  6732. var amtVersions = [];
  6733. for (var i in amtData.versions) { if (amtVersions.indexOf(i) == -1) { amtVersions.push(i); } }
  6734. var collator = new Intl.Collator([], { numeric: true });
  6735. amtVersions.sort((a, b) => collator.compare(a, b));
  6736. for (var i in amtVersions) { r += ' ' + amtVersions[i] + ': ' + amtData.versions[amtVersions[i]] + '\r\n'; }
  6737. }
  6738. }
  6739. try { ws.send(JSON.stringify({ action: 'serverconsole', value: r, tag: cmdData.command.tag })); } catch (ex) { }
  6740. });
  6741. }
  6742. function serverUserCommandUpdateCheck(cmdData) {
  6743. parent.parent.getServerTags(function (tags, error) {
  6744. var r2 = '';
  6745. if (error != null) { r2 += 'Exception: ' + error + '\r\n'; }
  6746. else { for (var i in tags) { r2 += i + ': ' + tags[i] + '\r\n'; } }
  6747. try { ws.send(JSON.stringify({ action: 'serverconsole', value: r2, tag: cmdData.command.tag })); } catch (ex) { }
  6748. });
  6749. cmdData.result = "Checking server update...";
  6750. }
  6751. function serverUserCommandMaintenance(cmdData) {
  6752. var arg = null, changed = false;
  6753. if ((cmdData.cmdargs['_'] != null) && (cmdData.cmdargs['_'][0] != null)) { arg = cmdData.cmdargs['_'][0].toLowerCase(); }
  6754. if (arg == 'enabled') { parent.parent.config.settings.maintenancemode = 1; changed = true; }
  6755. else if (arg == 'disabled') { delete parent.parent.config.settings.maintenancemode; changed = true; }
  6756. cmdData.result = 'Maintenance mode: ' + ((parent.parent.config.settings.maintenancemode == null) ? 'Disabled' : 'Enabled');
  6757. if (changed == false) { cmdData.result += '\r\nTo change type: maintenance [enabled|disabled]'; }
  6758. }
  6759. function serverUserCommandInfo(cmdData) {
  6760. function convertSeconds (s, form) {
  6761. if (!['long', 'shortprecise'].includes(form)) {
  6762. form = 'shortprecise';
  6763. }
  6764. let t = {}, r = '';
  6765. t.d = Math.floor(s / (24 * 3600));
  6766. s %= 24 * 3600;
  6767. t.h= Math.floor(s / 3600);
  6768. s %= 3600;
  6769. t.m = Math.floor(s / 60);
  6770. t.s =(s%60).toFixed(0);
  6771. if ( form == 'long') {
  6772. r = t.d + ((t.d == 1) ? ' day, ' : ' days, ') + t.h + ((t.h == 1) ? ' hour, ' : ' hours, ') + t.m + ((t.m == 1) ? ' minute, ' : ' minutes, ') + t.s+ ((t.s == 1) ? ' second' : ' seconds');
  6773. } else if (form == 'shortprecise') {
  6774. r = String(t.d).padStart(2, '0') + ':' + String(t.h).padStart(2, '0') + ':' + String(t.m).padStart(2, '0') + ':' + String((s%60).toFixed(2)).padStart(5, '0') + 's';
  6775. }
  6776. return r;
  6777. }
  6778. var info = {}, arg = null, t = {}, r = '';
  6779. if ((cmdData.cmdargs['_'] != null) && (cmdData.cmdargs['_'][0] != null)) { arg = cmdData.cmdargs['_'][0].toLowerCase(); }
  6780. try { info.meshVersion = 'v' + parent.parent.currentVer; } catch (ex) { }
  6781. try { info.nodeVersion = process.version; } catch (ex) { }
  6782. try { info.runMode = (["Hybrid (LAN + WAN) mode", "WAN mode", "LAN mode"][(args.lanonly ? 2 : (args.wanonly ? 1 : 0))]); } catch (ex) { }
  6783. try { info.productionMode = ((process.env.NODE_ENV != null) && (process.env.NODE_ENV == 'production')); } catch (ex) { }
  6784. try { info.database = ["Unknown", "NeDB", "MongoJS", "MongoDB", "MariaDB", "MySQL", "PostgreSQL", "AceBase", "SQLite"][parent.parent.db.databaseType]; } catch (ex) { }
  6785. try { if (parent.db.databaseType == 3) { info.dbChangeStream = parent.db.changeStream; info.dbBulkOperations = (parent.parent.config.settings.mongodbbulkoperations === true); } } catch (ex) { }
  6786. try { if (parent.parent.multiServer != null) { info.serverId = parent.parent.multiServer.serverid; } } catch (ex) { }
  6787. try { if (parent.parent.pluginHandler != null) { info.plugins = []; for (var i in parent.parent.pluginHandler.plugins) { info.plugins.push(i); } } } catch (ex) { }
  6788. try { info.platform = process.platform; } catch (ex) { }
  6789. try { info.arch = process.arch; } catch (ex) { }
  6790. try { info.pid = process.pid; } catch (ex) { }
  6791. if (arg == 'h') {
  6792. try {
  6793. info.uptime = convertSeconds(process.uptime(), 'long');
  6794. info.cpuUsage = {
  6795. system: (convertSeconds(process.cpuUsage().system /1000000)),
  6796. user: (convertSeconds(process.cpuUsage().user /1000000))
  6797. }
  6798. info.memoryUsage = {};
  6799. for (const [key,value] of Object.entries(process.memoryUsage())){
  6800. info.memoryUsage[key] = ([value]/1048576).toFixed(2) + 'Mb';
  6801. }
  6802. } catch (ex) { }
  6803. }
  6804. else {
  6805. try { info.uptime = process.uptime(); } catch (ex) { }
  6806. try { info.cpuUsage = process.cpuUsage(); } catch (ex) { }
  6807. try { info.memoryUsage = process.memoryUsage(); } catch (ex) { }
  6808. }
  6809. try { info.warnings = parent.parent.getServerWarnings(); } catch (ex) { console.log(ex); }
  6810. try { info.allDevGroupManagers = parent.parent.config.settings.managealldevicegroups; } catch (ex) { }
  6811. try { if (process.traceDeprecation == true) { info.traceDeprecation = true; } } catch (ex) { }
  6812. cmdData.result = JSON.stringify(info, null, 4);
  6813. }
  6814. function serverUserCommandNodeConfig(cmdData) {
  6815. cmdData.result = JSON.stringify(process.config, null, 4);
  6816. }
  6817. function serverUserCommandVersions(cmdData) {
  6818. cmdData.result = JSON.stringify(process.versions, null, 4);
  6819. }
  6820. function serverUserCommandArgs(cmdData) {
  6821. cmdData.result = 'args: ' + JSON.stringify(cmdData.cmdargs);
  6822. }
  6823. function serverUserCommandUserSessions(cmdData) {
  6824. var userSessionCount = 0;
  6825. var filter = null;
  6826. var arg = cmdData.cmdargs['_'][0];
  6827. if (typeof arg == 'string') { if (arg.indexOf('/') >= 0) { filter = arg; } else { filter = ('user/' + domain.id + '/' + arg); } }
  6828. for (var i in parent.wssessions) {
  6829. if ((filter == null) || (filter == i)) {
  6830. userSessionCount++;
  6831. cmdData.result += (i + ', ' + parent.wssessions[i].length + ' session' + ((parent.wssessions[i].length > 1) ? 's' : '') + '.\r\n');
  6832. for (var j in parent.wssessions[i]) {
  6833. var extras = "";
  6834. if (parent.wssessions[i][j].satelliteFlags) { extras += ', Satellite'; }
  6835. cmdData.result += ' ' + parent.wssessions[i][j].clientIp + ' --> ' + parent.wssessions[i][j].sessionId + extras + ((parent.wssessions[i][j].xclosed) ? (', CLOSED-' + parent.wssessions[i][j].xclosed):'') + '\r\n';
  6836. }
  6837. }
  6838. }
  6839. if (userSessionCount == 0) { cmdData.result = 'None.'; }
  6840. }
  6841. function serverUserCommandCloseUserSessions(cmdData) {
  6842. var userSessionCount = 0;
  6843. var filter = null;
  6844. var arg = cmdData.cmdargs['_'][0];
  6845. if (typeof arg == 'string') { if (arg.indexOf('/') >= 0) { filter = arg; } else { filter = ('user/' + domain.id + '/' + arg); } }
  6846. if (filter == null) {
  6847. cmdData.result += "Usage: closeusersessions <username>";
  6848. } else {
  6849. cmdData.result += "Closing user sessions for: " + filter + '\r\n';
  6850. for (var i in parent.wssessions) {
  6851. if (filter == i) {
  6852. userSessionCount++;
  6853. for (var j in parent.wssessions[i]) {
  6854. parent.wssessions[i][j].send(JSON.stringify({ action: 'stopped', msg: "Administrator forced disconnection" }));
  6855. parent.wssessions[i][j].close();
  6856. }
  6857. }
  6858. }
  6859. if (userSessionCount < 2) { cmdData.result += 'Disconnected ' + userSessionCount + ' session.'; } else { cmdData.result += 'Disconnected ' + userSessionCount + ' sessions.'; };
  6860. }
  6861. }
  6862. function serverUserCommandResetServer(cmdData) {
  6863. console.log("Server restart...");
  6864. process.exit(0);
  6865. }
  6866. function serverUserCommandTaskLimiter(cmdData) {
  6867. if (parent.parent.taskLimiter != null) {
  6868. //var obj = { maxTasks: maxTasks, maxTaskTime: (maxTaskTime * 1000), nextTaskId: 0, currentCount: 0, current: {}, pending: [[], [], []], timer: null };
  6869. const tl = parent.parent.taskLimiter;
  6870. cmdData.result += 'MaxTasks: ' + tl.maxTasks + ', NextTaskId: ' + tl.nextTaskId + '\r\n';
  6871. cmdData.result += 'MaxTaskTime: ' + (tl.maxTaskTime / 1000) + ' seconds, Timer: ' + (tl.timer != null) + '\r\n';
  6872. var c = [];
  6873. for (var i in tl.current) { c.push(i); }
  6874. cmdData.result += 'Current (' + tl.currentCount + '): [' + c.join(', ') + ']\r\n';
  6875. cmdData.result += 'Pending (High/Med/Low): ' + tl.pending[0].length + ', ' + tl.pending[1].length + ', ' + tl.pending[2].length + '\r\n';
  6876. }
  6877. }
  6878. function serverUserCommandSetMaxTasks(cmdData) {
  6879. if ((cmdData.cmdargs["_"].length != 1) || (parseInt(cmdData.cmdargs["_"][0]) < 1) || (parseInt(cmdData.cmdargs["_"][0]) > 1000)) {
  6880. cmdData.result = 'Usage: setmaxtasks [1 to 1000]';
  6881. } else {
  6882. parent.parent.taskLimiter.maxTasks = parseInt(cmdData.cmdargs["_"][0]);
  6883. cmdData.result = 'MaxTasks set to ' + parent.parent.taskLimiter.maxTasks + '.';
  6884. }
  6885. }
  6886. function serverUserCommandCores(cmdData) {
  6887. if (parent.parent.defaultMeshCores != null) {
  6888. for (var i in parent.parent.defaultMeshCores) {
  6889. cmdData.result += i + ': ' + parent.parent.defaultMeshCores[i].length + ' bytes\r\n';
  6890. }
  6891. }
  6892. }
  6893. function serverUserCommandShowPaths(cmdData) {
  6894. cmdData.result = 'Parent: ' + parent.parent.parentpath + '\r\n';
  6895. cmdData.result += 'Data: ' + parent.parent.datapath + '\r\n';
  6896. cmdData.result += 'Files: ' + parent.parent.filespath + '\r\n';
  6897. cmdData.result += 'Backup: ' + parent.parent.backuppath + '\r\n';
  6898. cmdData.result += 'Record: ' + parent.parent.recordpath + '\r\n';
  6899. cmdData.result += 'WebPublic: ' + parent.parent.webPublicPath + '\r\n';
  6900. cmdData.result += 'WebViews: ' + parent.parent.webViewsPath + '\r\n';
  6901. cmdData.result += 'WebEmails: ' + parent.parent.webEmailsPath + '\r\n';
  6902. if (parent.parent.webPublicOverridePath) { cmdData.result += 'XWebPublic: ' + parent.parent.webPublicOverridePath + '\r\n'; }
  6903. if (parent.parent.webViewsOverridePath) { cmdData.result += 'XWebViews: ' + parent.parent.webViewsOverridePath + '\r\n'; }
  6904. if (parent.parent.webEmailsOverridePath) { cmdData.result += 'XWebEmails: ' + parent.parent.webEmailsOverridePath + '\r\n'; }
  6905. if (domain.webpublicpath) { cmdData.result += 'DomainWebPublic: ' + domain.webpublicpath + '\r\n'; }
  6906. if (domain.webviewspath) { cmdData.result += 'DomainWebViews: ' + domain.webviewspath + '\r\n'; }
  6907. if (domain.webemailspath) { cmdData.result += 'DomainWebEmails: ' + domain.webemailspath + '\r\n'; }
  6908. }
  6909. function serverUserCommandMigrationAgents(cmdData) {
  6910. if (parent.parent.swarmserver == null) {
  6911. cmdData.result = 'Swarm server not running.';
  6912. } else {
  6913. for (var i in parent.parent.swarmserver.migrationAgents) {
  6914. var arch = parent.parent.swarmserver.migrationAgents[i];
  6915. for (var j in arch) { var agent = arch[j]; cmdData.result += 'Arch ' + agent.arch + ', Ver ' + agent.ver + ', Size ' + ((agent.binary == null) ? 0 : agent.binary.length) + '<br />'; }
  6916. }
  6917. }
  6918. }
  6919. function serverUserCommandSwarmStats(cmdData) {
  6920. if (parent.parent.swarmserver == null) {
  6921. cmdData.result = 'Swarm server not running.';
  6922. } else {
  6923. for (var i in parent.parent.swarmserver.stats) {
  6924. if (typeof parent.parent.swarmserver.stats[i] == 'object') {
  6925. cmdData.result += i + ': ' + JSON.stringify(parent.parent.swarmserver.stats[i]) + '\r\n';
  6926. } else {
  6927. cmdData.result += i + ': ' + parent.parent.swarmserver.stats[i] + '\r\n';
  6928. }
  6929. }
  6930. }
  6931. }
  6932. function serverUserCommandRelays(cmdData) {
  6933. for (var i in parent.wsrelays) {
  6934. cmdData.result += 'id: ' + i + ', ' + ((parent.wsrelays[i].state == 2) ? 'connected' : 'pending');
  6935. if (parent.wsrelays[i].peer1 != null) {
  6936. cmdData.result += ', ' + cleanRemoteAddr(parent.wsrelays[i].peer1.req.clientIp);
  6937. if (parent.wsrelays[i].peer1.user) { cmdData.result += ' (User:' + parent.wsrelays[i].peer1.user.name + ')' }
  6938. }
  6939. if (parent.wsrelays[i].peer2 != null) {
  6940. cmdData.result += ' to ' + cleanRemoteAddr(parent.wsrelays[i].peer2.req.clientIp);
  6941. if (parent.wsrelays[i].peer2.user) { cmdData.result += ' (User:' + parent.wsrelays[i].peer2.user.name + ')' }
  6942. }
  6943. cmdData.result += '\r\n';
  6944. }
  6945. if (cmdData.result == '') { cmdData.result = 'No relays.'; }
  6946. }
  6947. // removeinactivedevices showall|showremoved
  6948. function serverUserCommandRemoveInactiveDevices(cmdData) {
  6949. var arg = cmdData.cmdargs['_'][0];
  6950. if ((arg == null) && (arg != 'showremoved') && (arg != 'showall')) {
  6951. cmdData.result = 'Usage: removeinactivedevices [showremoved|showall]';
  6952. } else {
  6953. parent.db.removeInactiveDevices((arg == 'showall'), function (msg) { try { ws.send(JSON.stringify({ action: 'serverconsole', value: msg, tag: cmdData.command.tag })); } catch (ex) { } });
  6954. }
  6955. }
  6956. function serverUserCommandAutoBackup(cmdData) {
  6957. cmdData.result = parent.db.performBackup(function (msg) {
  6958. try { ws.send(JSON.stringify({ action: 'serverconsole', value: msg, tag: cmdData.command.tag })); } catch (ex) { }
  6959. });
  6960. }
  6961. function serverUserCommandBackupConfig(cmdData) {
  6962. cmdData.result = parent.db.getBackupConfig();
  6963. }
  6964. function serverUserCommandFirebase(cmdData) {
  6965. if (parent.parent.firebase == null) {
  6966. cmdData.result = "Firebase push messaging not supported";
  6967. } else {
  6968. cmdData.result = JSON.stringify(parent.parent.firebase.stats, null, 2);
  6969. }
  6970. }
  6971. function validNodeIdAndDomain(command) {
  6972. if (common.validateString(command.nodeid, 1, 1024) == false) return false; // Check nodeid
  6973. if (command.nodeid.indexOf('/') == -1) { command.nodeid = 'node/' + domain.id + '/' + command.nodeid; }
  6974. if ((command.nodeid.split('/').length != 3) || (command.nodeid.split('/')[1] != domain.id)) return false; // Invalid domain, operation only valid for current domain
  6975. return true;
  6976. }
  6977. function getUserExtraIds() {
  6978. var extraids = null;
  6979. if (obj.user.links != null) {
  6980. for (var i in obj.user.links) {
  6981. if (i.startsWith('node/')) { if (extraids == null) { extraids = []; } extraids.push(i); }
  6982. else if (i.startsWith('ugrp/')) {
  6983. const g = parent.userGroups[i];
  6984. if ((g != null) && (g.links != null)) {
  6985. for (var j in g.links) { if (j.startsWith('node/')) { if (extraids == null) { extraids = []; } extraids.push(j); } }
  6986. }
  6987. }
  6988. }
  6989. }
  6990. return extraids;
  6991. }
  6992. function userHasSiteUpdate() { return ((user.siteadmin & SITERIGHT_SERVERUPDATE) > 0); }
  6993. function domainHasMyServerErrorLog() { return !((domain.myserver === false) || ((domain.myserver != null) && (domain.myserver !== true) && (domain.myserver.errorlog !== true))); }
  6994. function domainHasMyServerUpgrade() { return !((domain.myserver === false) || ((domain.myserver != null) && (domain.myserver !== true) && (domain.myserver.upgrade !== true))); }
  6995. function csvClean(s) { return '\"' + s.split('\"').join('').split(',').join('').split('\r').join('').split('\n').join('') + '\"'; }
  6996. function remoteSessionReport(command, manageAllDeviceGroups, msgIdFilter) {
  6997. // If we are not user administrator on this site, only search for events with our own user id.
  6998. var ids = [user._id];
  6999. if ((user.siteadmin & SITERIGHT_MANAGEUSERS) != 0) {
  7000. if (command.devGroup != null) {
  7001. ids = [ user._id, command.devGroup ];
  7002. } else {
  7003. if (manageAllDeviceGroups) { ids = ['*']; } else if (user.links) { for (var i in user.links) { ids.push(i); } }
  7004. }
  7005. }
  7006. // Get the events in the time range
  7007. // MySQL or MariaDB query will ignore the MsgID filter.
  7008. db.GetEventsTimeRange(ids, domain.id, msgIdFilter, new Date(command.start * 1000), new Date(command.end * 1000), function (err, docs) {
  7009. if (err != null) return;
  7010. var data = { groups: {} };
  7011. var guestNamePresent = false;
  7012. // Columns
  7013. if (command.groupBy == 1) {
  7014. data.groupFormat = 'user';
  7015. data.columns = [{ id: 'time', title: "time", format: 'datetime' }, { id: 'nodeid', title: "device", format: 'node' }, { id: 'meshid', title: "devgroup", format: 'mesh' }, { id: 'guestname', title: "guest", align: 'center' }, { id: 'protocol', title: "session", format: 'protocol', align: 'center' }, { id: 'length', title: "length", format: 'seconds', align: 'center', sumBy: 'protocol' } ];
  7016. } else if (command.groupBy == 2) {
  7017. data.groupFormat = 'nodemesh';
  7018. data.columns = [{ id: 'time', title: "time", format: 'datetime' }, { id: 'userid', title: "user", format: 'user' }, { id: 'guestname', title: "guest", align: 'center' }, { id: 'protocol', title: "session", format: 'protocol', align: 'center' }, { id: 'length', title: "length", format: 'seconds', align: 'center', sumBy: 'protocol' } ];
  7019. } else if (command.groupBy == 3) {
  7020. data.columns = [{ id: 'time', title: "time", format: 'time' }, { id: 'nodeid', title: "device", format: 'node' }, { id: 'meshid', title: "devgroup", format: 'mesh' }, { id: 'guestname', title: "guest", align: 'center' }, { id: 'userid', title: "user", format: 'user' }, { id: 'protocol', title: "session", format: 'protocol', align: 'center' }, { id: 'length', title: "length", format: 'seconds', align: 'center', sumBy: 'protocol' } ];
  7021. }
  7022. // Add traffic columns
  7023. if (command.showTraffic) {
  7024. data.columns.push({ id: 'bytesin', title: "bytesin", format: 'bytes', align: 'center', sumBy: 'protocol' });
  7025. data.columns.push({ id: 'bytesout', title: "bytesout", format: 'bytes', align: 'center', sumBy: 'protocol' });
  7026. }
  7027. // Rows
  7028. for (var i in docs) {
  7029. // If MySQL or MariaDB query, we can't filter on MsgID, so we have to do it here.
  7030. if (msgIdFilter.indexOf(docs[i].msgid) < 0) continue;
  7031. if ((command.devGroup != null) && (docs[i].ids != null) && (docs[i].ids.indexOf(command.devGroup) == -1)) continue;
  7032. var entry = { time: docs[i].time.valueOf() };
  7033. // UserID
  7034. if (command.groupBy != 1) { entry.userid = docs[i].userid; }
  7035. if (command.groupBy != 2) { entry.nodeid = docs[i].nodeid; }
  7036. entry.protocol = docs[i].protocol;
  7037. // Device Group
  7038. if (docs[i].ids != null) { for (var j in docs[i].ids) { if (docs[i].ids[j].startsWith('mesh/')) { entry.meshid = docs[i].ids[j]; } } }
  7039. // Add traffic data
  7040. if (command.showTraffic) { entry.bytesin = docs[i].bytesin; entry.bytesout = docs[i].bytesout; }
  7041. // Add guest name if present
  7042. if (docs[i].guestname != null) { entry.guestname = docs[i].guestname; guestNamePresent = true; }
  7043. // Session length
  7044. if (((docs[i].msgid >= 10) && (docs[i].msgid <= 12)) && (docs[i].msgArgs != null) && (typeof docs[i].msgArgs == 'object') && (typeof docs[i].msgArgs[3] == 'number')) { entry.length = docs[i].msgArgs[3]; }
  7045. else if ((docs[i].msgid >= 122) && (docs[i].msgid <= 126) && (docs[i].msgArgs != null) && (typeof docs[i].msgArgs == 'object') && (typeof docs[i].msgArgs[0] == 'number')) { entry.length = docs[i].msgArgs[0]; }
  7046. else if ((docs[i].msgid == 144) && (docs[i].msgArgs != null) && (typeof docs[i].msgArgs == 'object') && (typeof docs[i].msgArgs[1] == 'number')) { entry.length = docs[i].msgArgs[1]; }
  7047. if (command.groupBy == 1) { // Add entry to per user
  7048. if (data.groups[docs[i].userid] == null) { data.groups[docs[i].userid] = { entries: [] }; }
  7049. data.groups[docs[i].userid].entries.push(entry);
  7050. } else if (command.groupBy == 2) { // Add entry to per mesh+device
  7051. if (entry.meshid != null) {
  7052. var k = docs[i].nodeid + '/' + entry.meshid;
  7053. if (data.groups[k] == null) { data.groups[k] = { entries: [] }; }
  7054. data.groups[k].entries.push(entry);
  7055. } else {
  7056. if (data.groups[docs[i].nodeid] == null) { data.groups[docs[i].nodeid] = { entries: [] }; }
  7057. data.groups[docs[i].nodeid].entries.push(entry);
  7058. }
  7059. } else if (command.groupBy == 3) { // Add entry to per day
  7060. var day;
  7061. if ((typeof command.l == 'string') && (typeof command.tz == 'string')) {
  7062. day = new Date(docs[i].time).toLocaleDateString(command.l, { timeZone: command.tz });
  7063. } else {
  7064. day = docs[i].time; // TODO
  7065. }
  7066. if (data.groups[day] == null) { data.groups[day] = { entries: [] }; }
  7067. data.groups[day].entries.push(entry);
  7068. }
  7069. }
  7070. // Remove guest column if not needed
  7071. if (guestNamePresent == false) {
  7072. if ((command.groupBy == 1) || (command.groupBy == 3)) {
  7073. data.columns.splice(3, 1);
  7074. } else if (command.groupBy == 2) {
  7075. data.columns.splice(2, 1);
  7076. }
  7077. }
  7078. try { ws.send(JSON.stringify({ action: 'report', data: data })); } catch (ex) { }
  7079. });
  7080. }
  7081. function trafficUsageReport(command, msgIdFilter) {
  7082. // If we are not user administrator on this site, only search for events with our own user id.
  7083. var ids = [user._id]; // If we are nto user administrator, only count our own traffic.
  7084. if ((user.siteadmin & SITERIGHT_MANAGEUSERS) != 0) { ids = ['*']; } // If user administrator, count traffic of all users.
  7085. // Get the events in the time range
  7086. // MySQL or MariaDB query will ignore the MsgID filter.
  7087. db.GetEventsTimeRange(ids, domain.id, msgIdFilter, new Date(command.start * 1000), new Date(command.end * 1000), function (err, docs) {
  7088. if (err != null) return;
  7089. var data = { groups: { 0: { entries: [] } } };
  7090. data.columns = [{ id: 'userid', title: "user", format: 'user' }, { id: 'length', title: "length", format: 'seconds', align: 'center', sumBy: true }, { id: 'bytesin', title: "bytesin", format: 'bytes', align: 'center', sumBy: true }, { id: 'bytesout', title: "bytesout", format: 'bytes', align: 'center', sumBy: true }];
  7091. var userEntries = {};
  7092. // Sum all entry logs for each user
  7093. for (var i in docs) {
  7094. // If MySQL or MariaDB query, we can't filter on MsgID, so we have to do it here.
  7095. if (msgIdFilter.indexOf(docs[i].msgid) < 0) continue;
  7096. if ((command.devGroup != null) && (docs[i].ids != null) && (docs[i].ids.indexOf(command.devGroup) == -1)) continue;
  7097. // Fetch or create the user entry
  7098. var userEntry = userEntries[docs[i].userid];
  7099. if (userEntry == null) { userEntry = { userid: docs[i].userid, length: 0, bytesin: 0, bytesout: 0 }; }
  7100. if (docs[i].bytesin) { userEntry.bytesin += docs[i].bytesin; }
  7101. if (docs[i].bytesout) { userEntry.bytesout += docs[i].bytesout; }
  7102. // Session length
  7103. if (((docs[i].msgid >= 10) && (docs[i].msgid <= 12)) && (docs[i].msgArgs != null) && (typeof docs[i].msgArgs == 'object') && (typeof docs[i].msgArgs[3] == 'number')) { userEntry.length += docs[i].msgArgs[3]; }
  7104. else if ((docs[i].msgid >= 122) && (docs[i].msgid <= 126) && (docs[i].msgArgs != null) && (typeof docs[i].msgArgs == 'object') && (typeof docs[i].msgArgs[0] == 'number')) { userEntry.length += docs[i].msgArgs[0]; }
  7105. else if ((docs[i].msgid == 144) && (docs[i].msgArgs != null) && (typeof docs[i].msgArgs == 'object') && (typeof docs[i].msgArgs[1] == 'number')) { userEntry.length += docs[i].msgArgs[1]; }
  7106. // Set the user entry
  7107. userEntries[docs[i].userid] = userEntry;
  7108. }
  7109. var userEntries2 = [];
  7110. for (var i in userEntries) { userEntries2.push(userEntries[i]); }
  7111. data.groups[0].entries = userEntries2;
  7112. try { ws.send(JSON.stringify({ action: 'report', data: data })); } catch (ex) { }
  7113. });
  7114. }
  7115. function userLoginReport(command) {
  7116. // If we are not user administrator on this site, only search for events with our own user id.
  7117. var ids = [user._id]; // If we are nto user administrator, only count our own traffic.
  7118. if ((user.siteadmin & SITERIGHT_MANAGEUSERS) != 0) { ids = ['*']; } // If user administrator, count traffic of all users.
  7119. var showInvalidLoginAttempts = true;
  7120. // Get the events in the time range
  7121. // MySQL or MariaDB query will ignore the MsgID filter.
  7122. var msgIdFilter = [107];
  7123. if (showInvalidLoginAttempts) { msgIdFilter = [107, 108, 109, 110]; } // Includes invalid login attempts
  7124. db.GetEventsTimeRange(ids, domain.id, msgIdFilter, new Date(command.start * 1000), new Date(command.end * 1000), function (err, docs) {
  7125. if (err != null) return;
  7126. // Columns
  7127. var data = { groups: {} };
  7128. if (command.groupBy == 1) {
  7129. data.groupFormat = 'user';
  7130. data.columns = [{ id: 'time', title: "time", format: 'datetime' }, { id: 'ip', title: "ip" }, { id: 'browser', title: "browser" }, { id: 'os', title: "os" }, { id: 'twofactor', title: "twofactor", format: '2fa' }];
  7131. } else if (command.groupBy == 3) {
  7132. data.columns = [{ id: 'time', title: "time", format: 'time' }, { id: 'userid', title: "user", format: 'user' }, { id: 'ip', title: "ip" }, { id: 'browser', title: "browser" }, { id: 'os', title: "os" }, { id: 'twofactor', title: "twofactor", format: '2fa' }];
  7133. }
  7134. if (showInvalidLoginAttempts) { data.columns.push({ id: 'msg', title: "msg", format: 'msg' }); }
  7135. // Add all log entries
  7136. var entries = [];
  7137. for (var i in docs) {
  7138. // If MySQL or MariaDB query, we can't filter on MsgID, so we have to do it here.
  7139. if (msgIdFilter.indexOf(docs[i].msgid) < 0) continue;
  7140. if (command.groupBy == 1) { // Add entry per user
  7141. if (data.groups[docs[i].userid] == null) { data.groups[docs[i].userid] = { entries: [] }; }
  7142. const entry = { time: docs[i].time.valueOf(), ip: docs[i].msgArgs[0], browser: docs[i].msgArgs[1], os: docs[i].msgArgs[2], twofactor: docs[i].twoFactorType ? docs[i].twoFactorType : '' };
  7143. if (showInvalidLoginAttempts) { entry.msg = docs[i].msgid }
  7144. data.groups[docs[i].userid].entries.push(entry);
  7145. } else if (command.groupBy == 3) { // Add entry per day
  7146. var day;
  7147. if ((typeof command.l == 'string') && (typeof command.tz == 'string')) {
  7148. day = new Date(docs[i].time).toLocaleDateString(command.l, { timeZone: command.tz });
  7149. } else {
  7150. day = docs[i].time; // TODO
  7151. }
  7152. if (data.groups[day] == null) { data.groups[day] = { entries: [] }; }
  7153. const entry = { time: docs[i].time.valueOf(), userid: docs[i].userid, ip: docs[i].msgArgs[0], browser: docs[i].msgArgs[1], os: docs[i].msgArgs[2], twofactor: docs[i].twoFactorType ? docs[i].twoFactorType : '' };
  7154. if (showInvalidLoginAttempts) { entry.msg = docs[i].msgid }
  7155. data.groups[day].entries.push(entry);
  7156. }
  7157. }
  7158. try { ws.send(JSON.stringify({ action: 'report', data: data })); } catch (ex) { }
  7159. });
  7160. }
  7161. function databaseRecordsReport(command) {
  7162. if (user.siteadmin != 0xFFFFFFFF) return; // This report is only available to full administrators
  7163. parent.parent.db.getDbStats(function (stats) {
  7164. var data = { groups: { 0: { entries: [] } } };
  7165. data.columns = [{ id: 'record', title: "Record", format: 'records' }, { id: 'recordcount', title: "Count", align: 'center', sumBy: true }];
  7166. for (var i in stats) { if ((i != 'total') && (stats[i] > 0)) { data.groups[0].entries.push({ record: i, recordcount: stats[i] }); } }
  7167. try { ws.send(JSON.stringify({ action: 'report', data: data })); } catch (ex) { }
  7168. });
  7169. }
  7170. // Return detailed information about an array of nodeid's
  7171. function getDeviceDetailedInfo(nodeids, type, func) {
  7172. if (nodeids == null) { getAllDeviceDetailedInfo(type, func); return; }
  7173. var results = [], resultPendingCount = 0;
  7174. for (var i in nodeids) {
  7175. // Fetch the node from the database
  7176. resultPendingCount++;
  7177. const getNodeFunc = function (node, rights, visible) {
  7178. if ((node != null) && (visible == true)) {
  7179. const getNodeSysInfoFunc = function (err, docs) {
  7180. const getNodeNetInfoFunc = function (err, docs) {
  7181. var netinfo = null;
  7182. if ((err == null) && (docs != null) && (docs.length == 1)) { netinfo = docs[0]; }
  7183. resultPendingCount--;
  7184. getNodeNetInfoFunc.results.push({ node: parent.CloneSafeNode(getNodeNetInfoFunc.node), sys: getNodeNetInfoFunc.sysinfo, net: netinfo });
  7185. if (resultPendingCount == 0) { func(getNodeFunc.results, type); }
  7186. }
  7187. getNodeNetInfoFunc.results = getNodeSysInfoFunc.results;
  7188. getNodeNetInfoFunc.nodeid = getNodeSysInfoFunc.nodeid;
  7189. getNodeNetInfoFunc.node = getNodeSysInfoFunc.node;
  7190. if ((err == null) && (docs != null) && (docs.length == 1)) { getNodeNetInfoFunc.sysinfo = docs[0]; }
  7191. // Query the database for network information
  7192. db.Get('if' + getNodeSysInfoFunc.nodeid, getNodeNetInfoFunc);
  7193. }
  7194. getNodeSysInfoFunc.results = getNodeFunc.results;
  7195. getNodeSysInfoFunc.nodeid = getNodeFunc.nodeid;
  7196. getNodeSysInfoFunc.node = node;
  7197. // Query the database for system information
  7198. db.Get('si' + getNodeFunc.nodeid, getNodeSysInfoFunc);
  7199. } else { resultPendingCount--; }
  7200. if (resultPendingCount == 0) { func(getNodeFunc.results.join('\r\n'), type); }
  7201. }
  7202. getNodeFunc.results = results;
  7203. getNodeFunc.nodeid = nodeids[i];
  7204. parent.GetNodeWithRights(domain, user, nodeids[i], getNodeFunc);
  7205. }
  7206. }
  7207. // Update all device shares for a nodeid list to a new meshid
  7208. // This is used when devices move to a new device group, changes are not evented.
  7209. function changeDeviceShareMeshId(nodes, meshid) {
  7210. parent.db.GetAllTypeNoTypeField('deviceshare', domain.id, function (err, docs) {
  7211. if (err != null) return;
  7212. for (var i = 0; i < docs.length; i++) {
  7213. const doc = docs[i];
  7214. if (nodes.indexOf(doc.nodeid) >= 0) {
  7215. doc.xmeshid = meshid;
  7216. doc.type = 'deviceshare';
  7217. db.Set(doc);
  7218. }
  7219. }
  7220. });
  7221. }
  7222. // Return detailed information about all nodes this user has access to
  7223. function getAllDeviceDetailedInfo(type, func) {
  7224. // If we are not paging, get all devices visible to this user
  7225. if (obj.visibleDevices == null) {
  7226. // Get all device groups this user has access to
  7227. var links = parent.GetAllMeshIdWithRights(user);
  7228. // Add any nodes with direct rights or any nodes with user group direct rights
  7229. var extraids = getUserExtraIds();
  7230. // Request a list of all nodes
  7231. db.GetAllTypeNoTypeFieldMeshFiltered(links, extraids, domain.id, 'node', null, obj.deviceSkip, obj.deviceLimit, function (err, docs) {
  7232. if (docs == null) { docs = []; }
  7233. parent.common.unEscapeAllLinksFieldName(docs);
  7234. var results = [], resultPendingCount = 0;
  7235. if (docs.length == 0) { // no results return blank array
  7236. func(docs, type);
  7237. } else {
  7238. for (i in docs) {
  7239. // Check device links, if a link points to an unknown user, remove it.
  7240. parent.cleanDevice(docs[i]);
  7241. // Fetch the node from the database
  7242. resultPendingCount++;
  7243. const getNodeFunc = function (node, rights, visible) {
  7244. if ((node != null) && (visible == true)) {
  7245. const getNodeSysInfoFunc = function (err, docs) {
  7246. const getNodeNetInfoFunc = function (err, docs) {
  7247. var netinfo = null;
  7248. if ((err == null) && (docs != null) && (docs.length == 1)) { netinfo = docs[0]; }
  7249. resultPendingCount--;
  7250. getNodeNetInfoFunc.results.push({ node: parent.CloneSafeNode(getNodeNetInfoFunc.node), sys: getNodeNetInfoFunc.sysinfo, net: netinfo });
  7251. if (resultPendingCount == 0) { func(getNodeFunc.results, type); }
  7252. }
  7253. getNodeNetInfoFunc.results = getNodeSysInfoFunc.results;
  7254. getNodeNetInfoFunc.nodeid = getNodeSysInfoFunc.nodeid;
  7255. getNodeNetInfoFunc.node = getNodeSysInfoFunc.node;
  7256. if ((err == null) && (docs != null) && (docs.length == 1)) { getNodeNetInfoFunc.sysinfo = docs[0]; }
  7257. // Query the database for network information
  7258. db.Get('if' + getNodeSysInfoFunc.nodeid, getNodeNetInfoFunc);
  7259. }
  7260. getNodeSysInfoFunc.results = getNodeFunc.results;
  7261. getNodeSysInfoFunc.nodeid = getNodeFunc.nodeid;
  7262. getNodeSysInfoFunc.node = node;
  7263. // Query the database for system information
  7264. db.Get('si' + getNodeFunc.nodeid, getNodeSysInfoFunc);
  7265. } else { resultPendingCount--; }
  7266. if (resultPendingCount == 0) { func(getNodeFunc.results.join('\r\n'), type); }
  7267. }
  7268. getNodeFunc.results = results;
  7269. getNodeFunc.nodeid = docs[i]._id;
  7270. parent.GetNodeWithRights(domain, user, docs[i]._id, getNodeFunc);
  7271. }
  7272. }
  7273. });
  7274. } else {
  7275. // If we are paging, we know what devices the user is look at
  7276. for (var id in obj.visibleDevices) {
  7277. // Fetch the node from the database
  7278. resultPendingCount++;
  7279. const getNodeFunc = function (node, rights, visible) {
  7280. if ((node != null) && (visible == true)) {
  7281. const getNodeSysInfoFunc = function (err, docs) {
  7282. const getNodeNetInfoFunc = function (err, docs) {
  7283. var netinfo = null;
  7284. if ((err == null) && (docs != null) && (docs.length == 1)) { netinfo = docs[0]; }
  7285. resultPendingCount--;
  7286. getNodeNetInfoFunc.results.push({ node: parent.CloneSafeNode(getNodeNetInfoFunc.node), sys: getNodeNetInfoFunc.sysinfo, net: netinfo });
  7287. if (resultPendingCount == 0) { func(getNodeFunc.results, type); }
  7288. }
  7289. getNodeNetInfoFunc.results = getNodeSysInfoFunc.results;
  7290. getNodeNetInfoFunc.nodeid = getNodeSysInfoFunc.nodeid;
  7291. getNodeNetInfoFunc.node = getNodeSysInfoFunc.node;
  7292. if ((err == null) && (docs != null) && (docs.length == 1)) { getNodeNetInfoFunc.sysinfo = docs[0]; }
  7293. // Query the database for network information
  7294. db.Get('if' + getNodeSysInfoFunc.nodeid, getNodeNetInfoFunc);
  7295. }
  7296. getNodeSysInfoFunc.results = getNodeFunc.results;
  7297. getNodeSysInfoFunc.nodeid = getNodeFunc.nodeid;
  7298. getNodeSysInfoFunc.node = node;
  7299. // Query the database for system information
  7300. db.Get('si' + getNodeFunc.nodeid, getNodeSysInfoFunc);
  7301. } else { resultPendingCount--; }
  7302. if (resultPendingCount == 0) { func(getNodeFunc.results.join('\r\n'), type); }
  7303. }
  7304. getNodeFunc.results = results;
  7305. getNodeFunc.nodeid = id;
  7306. parent.GetNodeWithRights(domain, user, id, getNodeFunc);
  7307. }
  7308. }
  7309. }
  7310. // Display a notification message for this session only.
  7311. function displayNotificationMessage(msg, title, tag, titleid, msgid, args) {
  7312. ws.send(JSON.stringify({ 'action': 'msg', 'type': 'notify', id: Math.random(), 'value': msg, 'title': title, 'userid': user._id, 'username': user.name, 'tag': tag, 'titleid': titleid, 'msgid': msgid, 'args': args }));
  7313. }
  7314. // Read the folder and all sub-folders and serialize that into json.
  7315. function readFilesRec(path) {
  7316. var r = {}, dir = fs.readdirSync(path);
  7317. for (var i in dir) {
  7318. var f = { t: 3, d: 111 }, stat = null;
  7319. try { stat = fs.statSync(path + '/' + dir[i]); } catch (ex) { }
  7320. if (stat != null) {
  7321. if ((stat.mode & 0x004000) == 0) { f.s = stat.size; f.d = stat.mtime.getTime(); } else { f.t = 2; f.f = readFilesRec(path + '/' + dir[i]); }
  7322. r[dir[i]] = f;
  7323. }
  7324. }
  7325. return r;
  7326. }
  7327. // Delete a directory with a files and directories within it
  7328. // TODO, make this an async function
  7329. function deleteFolderRecursive(path) {
  7330. if (fs.existsSync(path)) {
  7331. fs.readdirSync(path).forEach(function (file, index) {
  7332. var curPath = parent.path.join(path, file);;
  7333. if (fs.lstatSync(curPath).isDirectory()) { // recurse
  7334. deleteFolderRecursive(curPath);
  7335. } else { // delete file
  7336. fs.unlinkSync(curPath);
  7337. }
  7338. });
  7339. fs.rmdirSync(path);
  7340. }
  7341. };
  7342. function updateUserFiles(user, ws, domain) {
  7343. if ((user == null) || (user.siteadmin == null) || ((user.siteadmin & 8) == 0)) return;
  7344. // Request the list of server files
  7345. var files = { action: 'files', filetree: { n: 'Root', f: {} } };
  7346. // Add user files
  7347. files.filetree.f[user._id] = { t: 1, n: 'My Files', f: {} };
  7348. files.filetree.f[user._id].maxbytes = parent.getQuota(user._id, domain);
  7349. var usersplit = user._id.split('/'), domainx = 'domain';
  7350. if (usersplit[1].length > 0) domainx = 'domain-' + usersplit[1];
  7351. // Read all files recursively
  7352. try {
  7353. files.filetree.f[user._id].f = readFilesRec(parent.path.join(parent.filespath, domainx + '/user-' + usersplit[2]));
  7354. } catch (e) {
  7355. // TODO: We may want to fake this file structure until it's needed.
  7356. // Got an error, try to create all the folders and try again...
  7357. try { fs.mkdirSync(parent.filespath); } catch (e) { }
  7358. try { fs.mkdirSync(parent.path.join(parent.filespath, domainx)); } catch (e) { }
  7359. try { fs.mkdirSync(parent.path.join(parent.filespath, domainx + '/user-' + usersplit[2])); } catch (e) { }
  7360. try { fs.mkdirSync(parent.path.join(parent.filespath, domainx + '/user-' + usersplit[2] + '/Public')); } catch (e) { }
  7361. try { files.filetree.f[user._id].f = readFilesRec(parent.path.join(parent.filespath, domainx + '/user-' + usersplit[2])); } catch (e) { }
  7362. }
  7363. // Add files for each mesh
  7364. const meshes = parent.GetAllMeshWithRights(user, MESHRIGHT_SERVERFILES);
  7365. for (var i in meshes) {
  7366. const mesh = meshes[i];
  7367. var meshsplit = mesh._id.split('/');
  7368. files.filetree.f[mesh._id] = { t: 4, n: mesh.name, f: {} };
  7369. files.filetree.f[mesh._id].maxbytes = parent.getQuota(mesh._id, domain);
  7370. // Read all files recursively
  7371. try {
  7372. files.filetree.f[mesh._id].f = readFilesRec(parent.path.join(parent.filespath, domainx + '/mesh-' + meshsplit[2]));
  7373. } catch (e) {
  7374. files.filetree.f[mesh._id].f = {}; // Got an error, return empty folder. We will create the folder only when needed.
  7375. }
  7376. }
  7377. // Respond
  7378. try { ws.send(JSON.stringify(files)); } catch (ex) { }
  7379. }
  7380. function EscapeHtml(x) { if (typeof x == 'string') return x.replace(/&/g, '&amp;').replace(/>/g, '&gt;').replace(/</g, '&lt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;'); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }
  7381. //function EscapeHtmlBreaks(x) { if (typeof x == "string") return x.replace(/&/g, '&amp;').replace(/>/g, '&gt;').replace(/</g, '&lt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;').replace(/\r/g, '<br />').replace(/\n/g, '').replace(/\t/g, '&nbsp;&nbsp;'); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }
  7382. // Split a string taking into account the quoats. Used for command line parsing
  7383. function splitArgs(str) { var myArray = [], myRegexp = /[^\s"]+|"([^"]*)"/gi; do { var match = myRegexp.exec(str); if (match != null) { myArray.push(match[1] ? match[1] : match[0]); } } while (match != null); return myArray; }
  7384. function toNumberIfNumber(x) { if ((typeof x == 'string') && (+parseInt(x) === x)) { x = parseInt(x); } return x; }
  7385. function isPhoneNumber(x) {
  7386. var ok = true;
  7387. if (x.startsWith('+')) { x = x.substring(1); }
  7388. for (var i = 0; i < x.length; i++) { var c = x.charCodeAt(i); if (((c < 48) || (c > 57)) && (c != 32) && (c != 45) && (c != 46)) { ok = false; } }
  7389. return ok && (x.length >= 10);
  7390. }
  7391. function removeAllUnderScore(obj) {
  7392. if (typeof obj != 'object') return obj;
  7393. for (var i in obj) { if (i.startsWith('_')) { delete obj[i]; } else if (typeof obj[i] == 'object') { removeAllUnderScore(obj[i]); } }
  7394. return obj;
  7395. }
  7396. // Generate a 8 digit integer with even random probability for each value.
  7397. function getRandomEightDigitInteger() { var bigInt; do { bigInt = parent.crypto.randomBytes(4).readUInt32BE(0); } while (bigInt >= 4200000000); return bigInt % 100000000; }
  7398. function getRandomSixDigitInteger() { var bigInt; do { bigInt = parent.crypto.randomBytes(4).readUInt32BE(0); } while (bigInt >= 4200000000); return bigInt % 1000000; }
  7399. // Parse arguments string array into an object
  7400. function parseArgs(argv) {
  7401. var results = { '_': [] }, current = null;
  7402. for (var i = 1, len = argv.length; i < len; i++) {
  7403. var x = argv[i];
  7404. if (x.length > 2 && x[0] == '-' && x[1] == '-') {
  7405. if (current != null) { results[current] = true; }
  7406. current = x.substring(2);
  7407. } else {
  7408. if (current != null) { results[current] = toNumberIfNumber(x); current = null; } else { results['_'].push(toNumberIfNumber(x)); }
  7409. }
  7410. }
  7411. if (current != null) { results[current] = true; }
  7412. return results;
  7413. }
  7414. // Return true if at least one element of arr2 is in arr1
  7415. function findOne(arr1, arr2) { if ((arr1 == null) || (arr2 == null)) return false; return arr2.some(function (v) { return arr1.indexOf(v) >= 0; }); };
  7416. function getRandomPassword() { return Buffer.from(parent.crypto.randomBytes(9), 'binary').toString('base64').split('/').join('@'); }
  7417. function handleAmtCommand(cmd, node) {
  7418. if (cmd == null) return;
  7419. var host = cmd.nodeid;
  7420. if (cmd.mode == 1) { host = node.host; }
  7421. var tlsoptions = null;
  7422. var wsman = new Wsman(WsmanComm, host, node.intelamt.tls ? 16993 : 16992, node.intelamt.user, node.intelamt.pass,
  7423. node.intelamt.tls, tlsoptions, parent.parent, cmd.mode);
  7424. var amt = new Amt(wsman);
  7425. switch (cmd.command) {
  7426. case 'Get-GeneralSettings': {
  7427. amt.Get('AMT_GeneralSettings', function (obj, name, response, status) {
  7428. if (status == 200) {
  7429. var resp = { action: 'amt', nodeid: cmd.nodeid, command: 'Get-GeneralSettings', value: response.Body }
  7430. ws.send(JSON.stringify(resp));
  7431. } else {
  7432. ws.send(JSON.stringify({ 'error': error }));
  7433. }
  7434. });
  7435. break;
  7436. }
  7437. default: {
  7438. // Do nothing
  7439. }
  7440. }
  7441. }
  7442. // Return the number of 2nd factor for this account
  7443. function count2factoraAuths() {
  7444. var email2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.email2factor != false)) && (domain.mailserver != null));
  7445. var sms2fa = ((parent.parent.smsserver != null) && ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)));
  7446. var msg2fa = ((parent.parent.msgserver != null) && (parent.parent.msgserver.providers != 0) && ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.msg2factor != false)));
  7447. var duo2fa = ((typeof domain.passwordrequirements != 'object') || (typeof domain.passwordrequirements.duo2factor == 'object'));
  7448. var authFactorCount = 0;
  7449. if (typeof user.otpsecret == 'string') { authFactorCount++; } // Authenticator time factor
  7450. if (email2fa && (user.otpekey != null)) { authFactorCount++; } // EMail factor
  7451. if (sms2fa && (user.phone != null)) { authFactorCount++; } // SMS factor
  7452. if (msg2fa && (user.msghandle != null)) { authFactorCount++; } // Messaging factor
  7453. if (duo2fa && (user.otpduo != null)) { authFactorCount++; } // Duo authentication factor
  7454. if (user.otphkeys != null) { authFactorCount += user.otphkeys.length; } // FIDO hardware factor
  7455. if ((authFactorCount > 0) && (user.otpkeys != null)) { authFactorCount++; } // Backup keys
  7456. return authFactorCount;
  7457. }
  7458. // Return true if the event is for a device that is part of the currently visible page
  7459. function isEventWithinPage(ids) {
  7460. if (obj.visibleDevices == null) return true; // Add devices are visible
  7461. var r = true;
  7462. for (var i in ids) {
  7463. // If the event is for a visible device, return true
  7464. if (ids[i].startsWith('node/')) { r = false; if (obj.visibleDevices[ids[i]] != null) return true; }
  7465. }
  7466. return r; // If this event is not for any specific device, return true
  7467. }
  7468. return obj;
  7469. };