diff --git a/bob.json b/bob.json
new file mode 100644
index 00000000..36250a0d
--- /dev/null
+++ b/bob.json
@@ -0,0 +1 @@
+[{"type":"mesh","_id":"mesh//3pEOKJ0MOr85ssJt89qpFvosSnhB87G3GlS7sXuE6OVYrll1FSeoP6dBP4jEJXwh","name":"Lab-IntelAMT","mtype":"1","desc":"","domain":"","links":{"user//admin":{"name":"admin","rights":4294967295},"user//ro":{"name":"ro","rights":256}}},{"type":"mesh","_id":"mesh//542dea438e6065bffa9318c149e9982440dba4ce6ba3ba2b577e0e127b6a7f24","name":"test","domain":"","mtype":2,"links":{"user//bryan":{"rights":4294967295}}},{"type":"mesh","_id":"mesh//7b4b43cdad850135f36ab31124b52e47c167fba055ce800267a4dc89fe0e581c","name":"Lab-Computers","domain":"","mtype":2,"links":{"user//admin":{"rights":4294967295},"user//bcpd":{"rights":4294967295},"user//demo":{"rights":4294967295},"user//redge":{"rights":4294967295},"user//ylian":{"rights":4294967295},"user//ro":{"name":"ro","rights":256},"user//b":{"name":"b","rights":4294967295},"user//c":{"name":"c","rights":4294967295},"user//robert":{"name":"Robert","rights":4294967295},"user//joe%2Ebob":{"name":"joe.bob","rights":4294967295}},"desc":"","flags":0},{"type":"mesh","_id":"mesh//994a35a3ffcc0b3968080ec73add4bdb5f1317152e7db0b0c3caf7144eb1556e","name":"test","domain":"","mtype":2,"links":{"user//redge":{"rights":4294967295}}},{"type":"mesh","_id":"mesh//UE0kxwDwTXkSUtSvrF8W24iOkO@dRUOU5LMtPKA@powM68T7omPqSJM6gSXqLDKq","name":"MyHomeComputers","mtype":"2","desc":"","domain":"","links":{"user//c":{"name":"c","rights":4294967295}}},{"type":"mesh","_id":"mesh//lm2AD7PVRyTkGqXXqVPpWCYthR8WjQC5v2r$sS39tT207MI190MqeXQfdMpgUxvW","name":"SampleMesh","mtype":"2","desc":"","domain":"","links":{"user//test":{"name":"test","rights":4294967295}}},{"type":"mesh","_id":"mesh//oRdanQLDwNTP9BXftlic0g2u0QW59$y4xY15aqpZ$O9oUfjwpTDu5OLe@5lQzEQ1","name":"MyHomeComputers","mtype":"2","desc":"","domain":"","links":{}},{"type":"mesh","_id":"mesh//xYMpQG$jfLh6nPzpCbdHwoiT6aXWaki2O7DJY0LLuNold2Cz71sq@kFtxqS9Hxa8","name":"MyHomeComputers","mtype":"2","desc":"","domain":"","links":{"user//b":{"name":"b","rights":4294967295}}},{"type":"mesh","_id":"mesh/devtest/8CvC1wI7ivS289DcIUcMwXivGD2nKuj5jpUQ0qBd5Xd@R@jGkKSAA24A6UA9YRTe","name":"Test","mtype":"2","desc":"","domain":"devtest","links":{"user/devtest/a":{"name":"a","rights":4294967295}}},{"type":"node","mtype":2,"_id":"node//5CY6T$dcePOUyo4YxZfpii0awTQp6l32vNxwK75nkTjah1kqPN5Mg8vRhNB1bZ3i","icon":5,"meshid":"mesh//7b4b43cdad850135f36ab31124b52e47c167fba055ce800267a4dc89fe0e581c","name":"Raspberry Pi 2","rname":"raspberrypi","domain":"","agent":{"ver":0,"id":25,"caps":30,"core":"MeshCore v6"},"host":"192.168.2.125","osdesc":"Raspbian GNU/Linux 9 (stretch)"},{"type":"node","mtype":2,"_id":"node//8MvKOVHjBmvbERotepDOazSKFEczzT0lD6pkR0Y2kY6rVT5iByWCLjbXVsffjUub","icon":5,"meshid":"mesh//7b4b43cdad850135f36ab31124b52e47c167fba055ce800267a4dc89fe0e581c","name":"Raspberry Pi 3","rname":"raspberrypi","domain":"","agent":{"ver":0,"id":25,"caps":31,"core":"MeshCore v6"},"host":"192.168.2.146","osdesc":"Raspbian GNU/Linux 9 (stretch)"},{"type":"node","mtype":2,"_id":"node//9h5XPDmWPFf9VKv8uwNjK@hUx8oAPRpeUjbL3bRM7067dn1nkyvYIfwXsufDrXSS","icon":2,"meshid":"mesh//7b4b43cdad850135f36ab31124b52e47c167fba055ce800267a4dc89fe0e581c","name":"DellWin7","rname":"Dell-DEMO-PC","domain":"","agent":{"ver":0,"id":4,"caps":31,"core":"MeshCore v6"},"host":"192.168.2.111","users":["VPRODEMO\\Administrator"],"osdesc":"Microsoft Windows [Version 6.1.7601]","intelamt":{"ver":"9.0.21","state":2,"flags":4,"uuid":"4c4c4544-0057-3310-8054-c4c04f4b3132","tls":1}},{"type":"node","mtype":2,"_id":"node//@YOCeQU1FX5edxWN092QAoowH1l@7HW29OAjxAFziNB2kmLpOgzlunJga$NYgqAP","icon":1,"meshid":"mesh//7b4b43cdad850135f36ab31124b52e47c167fba055ce800267a4dc89fe0e581c","name":"AmtMachine7","rname":"AmtMachine7","domain":"","agent":{"ver":0,"id":4,"caps":31,"core":"MeshCore v6"},"host":"192.168.2.106","osdesc":"Microsoft Windows [Version 10.0.17134.523]","users":["AMTMACHINE7\\Default"],"intelamt":{"ver":"7.1.91","state":2,"flags":2,"uuid":"2cdf2ab0-7eb7-e111-a30f-001320e77720","tls":0}},{"type":"node","mtype":2,"_id":"node//ECAI7NO893JoN3ntK7@mbniyDq0qriG82wqGKQF4s8SpXs3NdnvuHR76Bzq14Pik","icon":1,"meshid":"mesh//7b4b43cdad850135f36ab31124b52e47c167fba055ce800267a4dc89fe0e581c","name":"MeshLabRight","rname":"MeshLabRight-x","domain":"","agent":{"ver":0,"id":3,"caps":31,"core":"MeshCore v6"},"host":"192.168.2.147","osdesc":"Microsoft Windows [Version 10.0.16299.192]","users":["MESHLABRIGHT-X\\Default"],"intelamt":{"ver":"10.0.56","state":2,"flags":2,"uuid":"41646200-ac01-11e4-b405-b8aeed733245","tls":0},"publicip":"192.55.64.246","iploc":"37.3541,-121.9552,1547225972"},{"type":"node","mtype":2,"_id":"node//YRGm4AQVRR38Ypisuo40KhvBGhDl2pE5YCp4j4eIbLaX3kmH3tmumOUbxb44A@Rh","icon":1,"meshid":"mesh//7b4b43cdad850135f36ab31124b52e47c167fba055ce800267a4dc89fe0e581c","name":"Coffee","rname":"Coffee-x","domain":"","agent":{"ver":0,"id":4,"caps":31,"core":"MeshCore v6"},"host":"192.168.2.139","osdesc":"Microsoft Windows [Version 10.0.17627.1000]","users":["COFFEE-X\\Administrator"],"intelamt":{"ver":"12.0.20","state":2,"flags":4,"uuid":"03fa4990-de4a-4990-fa03-4ade9049fa03","tls":0}},{"type":"node","mtype":2,"_id":"node//bCQgn$j077x65muhUe1$OAaMgEmseq0trqfGqx0@Doo7sCwvlaoKvAubiMxqAqUg","icon":5,"meshid":"mesh//7b4b43cdad850135f36ab31124b52e47c167fba055ce800267a4dc89fe0e581c","name":"Latte Panda","rname":"lattepanda","domain":"","agent":{"ver":0,"id":6,"caps":31,"core":"MeshCore v6"},"host":"192.168.2.137","osdesc":"Ubuntu 16.04.2 LTS","users":[]},{"type":"node","mtype":2,"_id":"node//ggifepc5wqK7sCVnOIjOZy9i9kaJizalIarz7Qwe5bJ4icpLD69zWYpjAaU@sfY$","icon":1,"meshid":"mesh//7b4b43cdad850135f36ab31124b52e47c167fba055ce800267a4dc89fe0e581c","name":"MeshLabTop","rname":"MeshLabTop-x","domain":"","agent":{"ver":0,"id":4,"caps":31,"core":"MeshCore v6"},"host":"192.168.2.133","osdesc":"Microsoft Windows [Version 10.0.17134.320]","users":["MESHLABTOP-X\\Default"],"intelamt":{"ver":"10.0.56","state":2,"flags":4,"uuid":"12246900-c162-11e5-86bc-b8aeed7f5469","tls":0}},{"type":"node","mtype":2,"_id":"node//hPIs53zBqqPkyKaLTi2oegl3O1FNRnC4YdheQGx@uFIq33PWHLb0Ru9ghRa3qQ7h","icon":2,"meshid":"mesh//7b4b43cdad850135f36ab31124b52e47c167fba055ce800267a4dc89fe0e581c","name":"Yoga","rname":"Yoga","domain":"","agent":{"ver":0,"id":4,"caps":31,"core":"MeshCore v6"},"host":"192.168.2.117","osdesc":"Microsoft Windows [Version 10.0.10240]","users":["YOGA\\Demo"]},{"type":"node","mtype":2,"_id":"node//hfbJ7zAgwZK@LQfsZkr1cqTSp6mjjZ3MjGC$v4X8E7HM1cZEnlGBgcorELu1hZWe","icon":1,"meshid":"mesh//7b4b43cdad850135f36ab31124b52e47c167fba055ce800267a4dc89fe0e581c","name":"AmtMachine11","rname":"DESKTOP-8737VKT","domain":"","agent":{"ver":0,"id":4,"caps":31,"core":"MeshCore v6"},"host":"192.168.2.122","osdesc":"Microsoft Windows [Version 10.0.17134.1]","users":["DESKTOP-8737VKT\\Default"]},{"type":"node","mtype":2,"_id":"node//tyR7l2j5@wOjDeRbOQNfjU7xB$ss6VZQPDkFsALPzJ4zbTI4IamV$OdwHeqiXV0K","icon":1,"meshid":"mesh//7b4b43cdad850135f36ab31124b52e47c167fba055ce800267a4dc89fe0e581c","name":"MeshLabLeft","rname":"meshLabLeft-x","domain":"","agent":{"ver":0,"id":4,"caps":31,"core":"MeshCore v6"},"host":"192.168.2.138","osdesc":"Microsoft Windows [Version 10.0.16299.192]","users":["MESHLABLEFT-X\\Default"],"intelamt":{"ver":"10.0.56","state":2,"flags":4,"uuid":"972a0380-d871-11dd-81b6-b8aeed73319d","tls":1}},{"type":"node","mtype":"2","_id":"node/devtest/@yw$s5jLUivpzZ49laprt4T0sBaOKImbDAiniothQwccZPukCB696$BvPWAW0Bg2","icon":1,"meshid":"mesh/devtest/8CvC1wI7ivS289DcIUcMwXivGD2nKuj5jpUQ0qBd5Xd@R@jGkKSAA24A6UA9YRTe","name":"AmtMachine7","domain":"devtest","agent":{"ver":0,"id":3,"caps":31,"core":"MeshCore v4"},"host":"192.168.2.106"},{"type":"user","_id":"user//DEVBOX\\Default","name":"DEVBOX\\Default","domain":"","sid":"S-1-5-21-3031010259-3226202738-568380285-1001","email":"ylian.saint-hilaire@intel.com","emailVerified":false},{"type":"user","_id":"user//aa","name":"aa","creation":1532639794468,"domain":"","email":"aa@aa.com","salt":"p87mfYAfroJ7pGqj7mnaUd4mySZGL6+dvs1xDeIb/3T7tbp70jUUj6syJW+KjZzo+lV1f0cnLvrUyJirIpxfWUgoDTXgmeD27ruyW53T/hxEtJ82bubYwVViYWkEqQ1Ie8MzcqfZF6y5SsyJXrbwlxo3E69s+65vLdXkpzB3AeY=","hash":"vpIWG65H9XHoKh365pGqolrir0nwJCFU0pHySXw9gV/CiBw/IlVvHIekIsWAKDGHrNZ8yMzFJ2A7met4cSW+sgM3OF07IfIPLzcjaQ+66OzsboZ1P06Ac+YIFWsdI/tb47HNq+2jMJ47HHcFi0JRIss9ij+V5UfjppGWHFgcPS4=","links":{},"login":1547668743516,"siteadmin":8},{"type":"user","_id":"user//admin","name":"admin","domain":"","creation":1417814230,"salt":"u79wclp+GmR93N8hYonUv45pTkOvRfFqe0FB2xr1+HUVdzwKBTjd5ZUOeuduJZ+8mJp8aOnN1oc1am+ZRzV1+nZml0JToQCPdYE6rtS10383NCLYq1EE3QUx80XQQnTqvAgWw0Q7c9mxkrJhHfUCpcczP+zCO+/zzSkXlidx+U4=","hash":"cIF5xyfke9NDy/YE7awAyHCLXObSD8gwDQFFU8k+GDntsKW6dBR1e/C6UANMXucGfnmqYHovEu7vGqH5rCNQF0hqkUt9IOVtOb8LUUDlpDy43/Z9ONl9p0nwQnF+90mci8MWY/fUzwg2mPt0NTc7X16Iyvde15A6V7Z7skuw90w=","siteadmin":4294967295,"links":{"mesh//1bc82505ef750af28b19292eaf2260eae83bc2939a367f1a885efe865b729193":{"name":"admin","rights":4294967295},"mesh//7b4b43cdad850135f36ab31124b52e47c167fba055ce800267a4dc89fe0e581c":{"name":"admin","rights":4294967295},"mesh//93c713c5cda428bb06777a7e7cd8075527f8a2f305945ac241852b37b1f30739":{"name":"admin","rights":4294967295},"mesh//acf0460d17df45ff977d2ceb3fcc84ced94a68bc67ef513bbdb1fc59a591bd5d":{"name":"admin","rights":4294967295},"mesh//3pEOKJ0MOr85ssJt89qpFvosSnhB87G3GlS7sXuE6OVYrll1FSeoP6dBP4jEJXwh":{"rights":4294967295}},"login":1547682871802,"email":"ylian.sain-hilaire@intel.com","emailVerified":false,"passchange":1545343751536,"passhint":""},{"type":"user","_id":"user//b","name":"b","email":"b@b.com","creation":1541571127127,"login":1541571437127,"domain":"","passhint":"b","salt":"S691HvbnvwqMlafjTtoNSrh7O0zhGgUj3vbiYF/EolvjQqsQ+k1fAZ2fpAqPTFWpwqlJbcZeB4kJzgPhwRhNmRfWHjXkmTsCNdmBobYz40AXysKGrxa/6jrR7MKIkI2/pBkzNLcx3cALB/B0zia/YSAP0/lGpFprB2IeEri0gS4=","hash":"cskKkLN8JOOxzytDb63bNN6yCdz34euXIuTF/EiClmXvHda4JcanyPTrUvrM+u9P7pD4wdapr0BYV7X86fkCFtl6A3nxh45LM34GTdSTey3i/kh7geSLM5uZIkm1pGeXaXpP0o7obCPnf0SRbq1X/k4yPY6BBUve6bOwsQX+3FU=","links":{"mesh//xYMpQG$jfLh6nPzpCbdHwoiT6aXWaki2O7DJY0LLuNold2Cz71sq@kFtxqS9Hxa8":{"rights":4294967295},"mesh//7b4b43cdad850135f36ab31124b52e47c167fba055ce800267a4dc89fe0e581c":{"rights":4294967295}},"siteadmin":0},{"type":"user","_id":"user//bob","name":"bob","creation":1523394756757,"domain":"","email":"bob@bob.com","salt":"53GmyHZq0DyCEqX8ndsU8gnv/2QfmRb3F/a03Rorl+X/wH7mG5GvvYtXL4aS+NWsr43Mm4jFx38qTmQi961ufGy8RWEKjo3FsAPFq3fnYJvBpmWCDPz8svPe5UtU6yGi8SPUWQD3d5fb4FZ4Qub49agVG52lMAoI7+XPee1aodw=","hash":"CtvD7IX3ytdJXmlbYEMRhSq0GuHVmis/a6qKGeJpb6MDvcB1MOPVuR3KMguPHxC2VQci0nkYosft/0V4RFxB1Y0OBCEo480Lprpce/TbJ1OvbUGxTpt0EJ3W8GFDrHW2MEyJlG0DTqrz5fM4cNS4zgYoM6XltEN4YYOcDvoeYFc=","login":1523394761510,"links":{},"siteadmin":0},{"type":"user","_id":"user//bryan","name":"Bryan","domain":"","creation":1373413118,"passtype":1,"salt":"GoMdgNplNW5Tq1LI38Fi0Q==","hash":"HSelSEKY8BJWdAV6D0PD4Gwiazg=","links":{"mesh//542dea438e6065bffa9318c149e9982440dba4ce6ba3ba2b577e0e127b6a7f24":{"name":"Bryan","rights":4294967295}},"siteadmin":0},{"type":"user","_id":"user//c","name":"c","email":"c@c.com","creation":1541619628129,"login":1541619628129,"domain":"","passhint":"c","salt":"bbzXmO1b9JROvnyId80zwUkIOSCbclSlhbdHUNmHSqPHsQ2jMVbUcXU5W/zrTD21414Nov6he1aUW3UGeyo55myriAnnWWU6nSQAUSdlvIW86apxkdGDZlL0fs+5YWKwfSIjXWtINzs6F0I1G78GgS0qb5sKOXiiToXxuDhddfI=","hash":"ZLW5y40Q6vE4Qod149d1UZM3I94LxnLGAxekZX6t8qugl+iz6dHU13Ed/I0aXOFYiYZBzicyMlwesnPvo9B6YBZf+sQ3KBmFDCz8KDRbzHQePRQEha6wz8OFH6LMwEJqG+nmk/XW2TsheTkMeTIf77O8L0wmYScTkCqPDvfPp28=","links":{"mesh//UE0kxwDwTXkSUtSvrF8W24iOkO@dRUOU5LMtPKA@powM68T7omPqSJM6gSXqLDKq":{"rights":4294967295},"mesh//7b4b43cdad850135f36ab31124b52e47c167fba055ce800267a4dc89fe0e581c":{"rights":4294967295}},"siteadmin":0},{"type":"user","_id":"user//demo","name":"demo","domain":"","creation":1428090949,"salt":"sGIIlqmi5YN8+BEBOYI2Ub+TGV0nAcE7UdM7itv80HGriQKtAeajer18WdJIsfEvXwEMjqftvEmNE1QLafb7edZ4LVTSe90+mluRxQ6na+GauoXIazTHCola+Ke3ySpedWuHvnKPrwQEWtTFDAp6UjppfwFtd8W6yAPyzYJwpfM=","hash":"0YfvLDm9NIMOHcbK9lQon+dH70vlQRnTW+b1BdQCBIcx4HyM6NpPeEs+nU6wqDk6nXwBp50OpBNmPLlCslpZAFg6R4sQzIJEcirb8GZnRaABDus8kq3Gq23DWp45j/uwQMAA6Z4orqXmr0OHEMB9Cr+p2jg+c01QTM2FU+TUvBc=","links":{"mesh//7b4b43cdad850135f36ab31124b52e47c167fba055ce800267a4dc89fe0e581c":{"name":"demo","rights":4294967295}},"login":1513299316831,"siteadmin":0},{"type":"user","_id":"user//devbox\\default","name":"devbox\\default","domain":"","sid":"S-1-5-21-3031010259-3226202738-568380285-1001","siteadmin":0},{"type":"user","_id":"user//devbox\\guest","name":"DevBox\\Guest","domain":"","creation":1426274108,"passtype":1,"salt":"ZhjNOb+BulO6xjq6cMgaDg==","hash":"7xbj8VbGL1bKC+5sQ57M06j80VA=","links":{},"siteadmin":0},{"type":"user","_id":"user//empty","name":"Empty","domain":"","creation":1416594156,"passtype":1,"salt":"lDetzH6OFHHLffpSkZBcKQ==","hash":"O+Ao6Lq2o9rSHqnHhm1Tcbxz8sw=","links":{}},{"type":"user","_id":"user//empty2","name":"empty2","domain":"","creation":1427217822,"passtype":1,"salt":"/ra8pIDX8XVadXyDAwUgZw==","hash":"Kw9CQLnXbzJWorjIhgWRoTkUeS8=","links":{}},{"type":"user","_id":"user//guest","name":"Guest","creation":1523035127813,"domain":"","salt":"TTWikAnKFo3VG954TtbGnuZmdt8PGsASQuyaofZCc6IxWdvhnbbDstO/vHG01ypi2V7lBWvRjh6gJxJKt+SHvTVbsfNofMjSzpiHjO5cAFeGsPfBUaXOfrF6/38KjxKUuEehDEvLU2YlHGCOvRKO8XDXfq8GWg3Wg7+5J6OPkn4=","hash":"gxcTbHlIlULsf/N1By4/qTPP5awPvs77qZnKj9fDSKJJjDzdQVwRBiAllKdm6wz6QLk5bwool0WCQejyzLtmzsub7y/z2vtHojDvfvr4XIyw9feJ9UJW5rodRu4AOGFbNqe9cdAyTmr2ASUVgSSk14IxFx/+4r5EHz5pXDfwGE4=","login":1524153238163,"email":"guest@meshcentral.com","emailVerified":true,"links":{}},{"type":"user","_id":"user//joe.bob","name":"joe.bob","email":"bob@bob.com","creation":1531530565489,"login":1531534497220,"domain":"","passhint":"bob","salt":"6P9m1LSHS+S1L7+HJO23pDBHn6eiZmAKXVckawjGNFPz8Py0zu6jDCcbP89lgnqLN8KjyWc7NO4dOPyL26QEkKOA2PghljDwjaWaCnX1EYkYDiJV3b4muHrQ0hVsdbYP/52gtRZAE5f0qM3xZjxfHz2dVyrRCFb+iNvCB/OiikQ=","hash":"yMsV9ATYiOcVOAjaH8Gxc6+jM28JwxsK0+GaA6b6Y8kL871sENd8/5nqi0QWYRKKahkpFe+xAaTdze/+/7vXt3Ly/WJsU7q8asZgORxqxWU0wsekFP2tnAjdiC/Grhj5euAXqft84p5aQHQt2PE/CoT+FOIVvUiM5gD/Iq1ev1M=","links":{"mesh//1fHXfQIiCOOOFw8lkIlUnXSQ3A75kFrKw2PvGQFkaAsGCM7mz@3$Mrr5NbPNTmxy":{"rights":4294967295},"mesh//PleIaEpGvRNLCx6rLcsKOogtaKfEDI7Tm2YSPGUurBLqNzRgLPd4ZurifZonL$7X":{"rights":4294967295},"mesh//8kWnSpFr$cIj2YvibBdMkBxBlcuNZp0yBZQSoWV12DTZV2f@34wJi5CIPMSUoOou":{"rights":4294967295},"mesh//CWsMAU4u63zNLLvdmoZP8uPcg$lzLnVcC25buVmqI05r1Wxk3ngk1OEgyHoFg7Oa":{"rights":4294967295},"mesh//Gt0Eo1GNpJmQ1WRwvKeIhibHBQ2j@XOV@omqmvsf8ZY6k3oKswvDdjzPn1KA4IQf":{"rights":4294967295},"mesh//GXbvFkWNsVKJCBbTvhBHCgFAY5fayzcStTJeZ5TdDfLC0sDL5TDs1ALFm8hMFoQ9":{"rights":4294967295},"mesh//7z0GW8pppHVGDw@Rz$fb1E29GnA9yExEKASjAf$r$hyBMP0$BSH$rVqWKCwxQ7D6":{"rights":4294967295},"mesh//7b4b43cdad850135f36ab31124b52e47c167fba055ce800267a4dc89fe0e581c":{"rights":4294967295}},"subscriptions":["user//joe.bob","server-global","mesh//1fHXfQIiCOOOFw8lkIlUnXSQ3A75kFrKw2PvGQFkaAsGCM7mz@3$Mrr5NbPNTmxy","mesh//PleIaEpGvRNLCx6rLcsKOogtaKfEDI7Tm2YSPGUurBLqNzRgLPd4ZurifZonL$7X","mesh//8kWnSpFr$cIj2YvibBdMkBxBlcuNZp0yBZQSoWV12DTZV2f@34wJi5CIPMSUoOou","mesh//CWsMAU4u63zNLLvdmoZP8uPcg$lzLnVcC25buVmqI05r1Wxk3ngk1OEgyHoFg7Oa","mesh//Gt0Eo1GNpJmQ1WRwvKeIhibHBQ2j@XOV@omqmvsf8ZY6k3oKswvDdjzPn1KA4IQf","mesh//GXbvFkWNsVKJCBbTvhBHCgFAY5fayzcStTJeZ5TdDfLC0sDL5TDs1ALFm8hMFoQ9","mesh//7z0GW8pppHVGDw@Rz$fb1E29GnA9yExEKASjAf$r$hyBMP0$BSH$rVqWKCwxQ7D6","mesh//7b4b43cdad850135f36ab31124b52e47c167fba055ce800267a4dc89fe0e581c"]},{"type":"user","_id":"user//mytestuser","name":"MyTestUser","email":"a@a.com","creation":1541494376182,"login":1541494376182,"domain":"","passhint":"a","salt":"3bURHy0OVDLNGJrEyQGs2UBhktEoJao9p4dIPS+uZwWdzPCcCZr4r7/dWlsjFGyKqCoWoStR6XzIFGElAAW5eUq2dM7lG9GXvrp59TzizKwTUAJWyCTfCmoP2jbTBjFLr5umBNsapiBY1HXXJxoRpJJJDGCt0ysETgIYjJfWUN8=","hash":"VzMdJ9paS12tH3KM3yUsVJXYt6tB/LJ8jX3665otE4AmQgaxBOXiEVr5Ff8oYd6MPonr6D93ACUems0R0NiX4EjGcOveT1zNijjaVYxFRu2PzkKayxqeupZQvniz6HdrtptEa96GpWXqXJ0dzsVbMcpfEKz1Tjk5cFku3swfZYw="},{"type":"user","_id":"user//redge","name":"redge","domain":"","creation":1387230750,"passtype":1,"salt":"cZD5MVAsa/6m18D/jw4Nkg==","hash":"dsmvqMuupJfedwcuFbZy8sC/1yc=","links":{"mesh//7b4b43cdad850135f36ab31124b52e47c167fba055ce800267a4dc89fe0e581c":{"name":"redge","rights":4294967295},"mesh//994a35a3ffcc0b3968080ec73add4bdb5f1317152e7db0b0c3caf7144eb1556e":{"name":"redge","rights":4294967295}}},{"type":"user","_id":"user//rick","name":"rick","domain":"","creation":1386192546,"passtype":1,"salt":"pi9Q9Mwkj4Ji7IMI1oMfjg==","hash":"oxlmQOVRvW2MF059xkCay+Yyxew=","links":{}},{"type":"user","_id":"user//ro","name":"ro","email":"ro@ro.com","creation":1541197742367,"login":1541204002597,"domain":"","passhint":"ro","salt":"dUJClqkl3WSaL0myQK0ejStXIWJzTXKMxVm+au0C9kg9UNAOYFC7E/kAxsr8jBCgk/cWMJCO6MNJNfYrE9x3I6sI3qSZe5t0+UqGg/A7DSUI1gisxHJG2Uxph02Wwso8nhZzElZtlHkfQp1kybuL7NgvvysyULDWv5t5F7K3tSA=","hash":"joJ713/kI1G/kAEvif4qE0TsaO3oJbfj09iAtHgGHQziDErJP0u8j8fFgreMgW2Qur9/pwAFbd1DA+0ADfp+tSOEqCoxLQbGKFC0I6rSLUU2psU+kOI1YYtBR9bywLM2e9pBQJMnJE6OGMAwqDwkRchZp+n/IaLkP8pI5DAzNBQ=","links":{"mesh//7b4b43cdad850135f36ab31124b52e47c167fba055ce800267a4dc89fe0e581c":{"rights":256},"mesh//3pEOKJ0MOr85ssJt89qpFvosSnhB87G3GlS7sXuE6OVYrll1FSeoP6dBP4jEJXwh":{"rights":256}},"siteadmin":0},{"type":"user","_id":"user//robert","name":"Robert","email":"robert@bob.com","creation":1543864529458,"login":1543864529458,"domain":"","passhint":"robert","salt":"/brZFf8ljdG2eZAFgBl3CaMnY5FGYPB93S3Y97wym/kUAVkF5NHlDVVNfmghC97VzZuHnlIoFlt4IUHbZHSc4/KAOc4pX/ZcyicsacMXyW+4CtDgSllnXeejK0nkVV/AdJSwKMQZ3K5+77TvydXbwLFuGoZ0NVOzCto/CgaWtsQ=","hash":"X7OlXcp514ay/HXV2qDsAQVerr2jWGdCUAVnl6FVsvw4XN+jgBsq8E7f0RnuAZp/BFIlqkLaj8TqYtpFPR87LCJdcg8LZ3Y7ygyiZp0rdpztyxBAJ3YyiFRrSvJB/u4JM0GJ88WPCRK6CbHwBlhg4i+VRSMPetn1XiemubK1Qv8=","links":{"mesh//7b4b43cdad850135f36ab31124b52e47c167fba055ce800267a4dc89fe0e581c":{"rights":4294967295}}},{"type":"user","_id":"user//test","name":"test","domain":"","creation":1443471004,"salt":"oIjWYPLCbUaAFW6OtG6ByA5/cQGXLtlszVrbS+gKZsz+Hi0Gt1EENRJtD/cWTfVdZhhjyevSTL96+29434R1Qr9BRnyRqBiBIDO5vps0+JOdiHr5UqyjRBgxCqoel6jA8JMFCaRtuIg7N7kgZvVaUaEgviPxxYYsj5Et6nCGx88=","hash":"381jAwP+/+rcxF2i4Jd6RJepNF3Ukz5kfRkWgf1C1bMPmH4535Atq9LABsgyw4fKG6dafV4sHU1WFh2dHzqnN7lGuZV2unT3z2/P0SWwroT5vIBcZId8ZuJ/7FQBXQUHwRAM+EvF7BSCvabSfXSq0+1JrbeGvdYHRAa/NhYKdJw=","links":{"mesh//lm2AD7PVRyTkGqXXqVPpWCYthR8WjQC5v2r$sS39tT207MI190MqeXQfdMpgUxvW":{"rights":4294967295}},"login":1516996988273},{"type":"user","_id":"user//test1","name":"test1","domain":"","creation":1374174496.79,"passtype":0,"hash":"test2","links":{}},{"type":"user","_id":"user//testuser","name":"TestUser","creation":1523658499596,"domain":"","email":"TestUser@meshcentral.com","salt":"KsM9DOWsz3Ub8Q1m5ex/Ge7XP6fBJ+MMA8LqjXmSRhLE0HWfv4uWAarWjZYYYM4/kWKZZSjkQWMMzCeZul3krBTogZRqMUar+VgKgZbjW0vX6NvMFn32Y2m+K/YSyl0XQoN09nmU5EN/RA95bkTz3RK/pXsOYVNdCcojs1uISj0=","hash":"uMO6etn7jwT1BBFqIY+DSqozcMIlpIAu9d4ruhxNmu/5smo/8M7Pvwa42tCza0bNsBNybPB6xOEYa4z+2W30WQ2dnN1AaHvRltdllxrPjmW0PjuD/4cNG+Z6Lb3v6R6QIUxYpUwo7gC87rM4+KyGiMFUq83VG67T4AcBxtKhzm4=","siteadmin":32},{"type":"user","_id":"user//ylian","name":"ylian","domain":"","creation":1398981122,"passtype":1,"salt":"WQYTOkQAKM1mK571bkPrEQ==","hash":"X8U/zslu/T6NbgsunM1tDiHZXgA=","links":{"mesh//7b4b43cdad850135f36ab31124b52e47c167fba055ce800267a4dc89fe0e581c":{"name":"ylian","rights":4294967295}}},{"type":"user","_id":"user//ylian2","name":"ylian2","domain":"","creation":1499729724,"passtype":1,"salt":"jS2CiOuElkqXe7yIqQtSUg==","hash":"HDWFHgTJIx7wR4XMPjgKNb5dbIs=","links":{}},{"type":"user","_id":"user//πele","name":"πele","domain":"","creation":1449084349,"passtype":1,"salt":"jGvowGbDG+/HdWysKE5EbA==","hash":"cR1ieHW5UuKJiSzpYBfzocjfKbM=","links":{}},{"type":"user","_id":"user/devtest/a","name":"a","email":"ylian.saint-hilaire@intel.com","creation":1514930104972,"login":1515724646486,"domain":"devtest","passhint":"a","siteadmin":4294967295,"salt":"g4aZeKUV0aPjzW8YpALfO7SYcyY03vdJVGNSowLqOlWZrFmnActIo+AvDb7ydC+DpSCWBq5fTHBj4Eol++PXeqyjcbVAKfMoZzGA6gdqNJ+UgabpzcddGm3oYEWcuEbvIzlLXSkZA+2LeXwRM9LbiRwH/dY0TrHVO2pwg8Hc3NU=","hash":"1+l9Wi2xEmoKNEH1jNkQGfI+F1gSId/SL7qAjxfe/lu5QvoFE4X9d3kajaC2GtWn73ilB5qmThOFqbL3nqut84EgHDmGWlxEJfGFytZwN0JlJSrid9OSKbfvS1E4P6jPWnbOLyiiKMWYktFwX2aNHRJZktrwcx07xpNuK7T5zFw=","links":{"mesh/devtest/8CvC1wI7ivS289DcIUcMwXivGD2nKuj5jpUQ0qBd5Xd@R@jGkKSAA24A6UA9YRTe":{"rights":4294967295}},"emailVerified":true}]
\ No newline at end of file
diff --git a/meshagent.js b/meshagent.js
index 1fcd306a..17418c97 100644
--- a/meshagent.js
+++ b/meshagent.js
@@ -156,7 +156,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
else if (cmdid == 12) { // MeshCommand_AgentHash
if ((msg.length == 52) && (obj.agentExeInfo != null) && (obj.agentExeInfo.update == true)) {
var agenthash = obj.common.rstr2hex(msg.substring(4)).toLowerCase();
- if (agenthash != obj.agentExeInfo.hash) {
+ if ((agenthash != obj.agentExeInfo.hash) && (agenthash != '000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')) {
// Mesh agent update required
if (obj.nodeid != null) { obj.parent.parent.debug(1, 'Agent update required, NodeID=0x' + obj.nodeid.substring(0, 16) + ', ' + obj.agentExeInfo.desc); }
obj.fs.open(obj.agentExeInfo.path, 'r', function (err, fd) {
@@ -193,7 +193,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
// Check the mesh core, if the agent is capable of running one
if (((obj.agentInfo.capabilities & 16) != 0) && (obj.parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].core != null)) {
obj.send(obj.common.ShortToStr(11) + obj.common.ShortToStr(0)); // Command 11, ask for mesh core hash.
- }
+ }
}
}
}
diff --git a/meshcentral.js b/meshcentral.js
index 038135dd..7d37ed96 100644
--- a/meshcentral.js
+++ b/meshcentral.js
@@ -90,7 +90,7 @@ function CreateMeshCentralServer(config, args) {
try { require('./pass').hash('test', function () { }); } catch (e) { console.log('Old version of node, must upgrade.'); return; } // TODO: Not sure if this test works or not.
// Check for invalid arguments
- var validArguments = ['_', 'notls', 'user', 'port', 'aliasport', 'mpsport', 'mpsaliasport', 'redirport', 'cert', 'mpscert', 'deletedomain', 'deletedefaultdomain', 'showall', 'showusers', 'shownodes', 'showmeshes', 'showevents', 'showpower', 'clearpower', 'showiplocations', 'help', 'exactports', 'install', 'uninstall', 'start', 'stop', 'restart', 'debug', 'filespath', 'datapath', 'noagentupdate', 'launch', 'noserverbackup', 'mongodb', 'mongodbcol', 'wanonly', 'lanonly', 'nousers', 'mpsdebug', 'mpspass', 'ciralocalfqdn', 'dbexport', 'dbimport', 'selfupdate', 'tlsoffload', 'userallowedip', 'swarmallowedip', 'fastcert', 'swarmport', 'swarmdebug', 'logintoken', 'logintokenkey', 'logintokengen', 'logintokengen', 'mailtokengen', 'admin', 'unadmin', 'sessionkey', 'sessiontime', 'minify', 'minifycore'];
+ var validArguments = ['_', 'notls', 'user', 'port', 'aliasport', 'mpsport', 'mpsaliasport', 'redirport', 'cert', 'mpscert', 'deletedomain', 'deletedefaultdomain', 'showall', 'showusers', 'shownodes', 'showmeshes', 'showevents', 'showpower', 'clearpower', 'showiplocations', 'help', 'exactports', 'install', 'uninstall', 'start', 'stop', 'restart', 'debug', 'filespath', 'datapath', 'noagentupdate', 'launch', 'noserverbackup', 'mongodb', 'mongodbcol', 'wanonly', 'lanonly', 'nousers', 'mpsdebug', 'mpspass', 'ciralocalfqdn', 'dbexport', 'dbexportmin', 'dbimport', 'selfupdate', 'tlsoffload', 'userallowedip', 'swarmallowedip', 'fastcert', 'swarmport', 'swarmdebug', 'logintoken', 'logintokenkey', 'logintokengen', 'logintokengen', 'mailtokengen', 'admin', 'unadmin', 'sessionkey', 'sessiontime', 'minify', 'minifycore'];
for (var arg in obj.args) { obj.args[arg.toLocaleLowerCase()] = obj.args[arg]; if (validArguments.indexOf(arg.toLocaleLowerCase()) == -1) { console.log('Invalid argument "' + arg + '", use --help.'); return; } }
if (obj.args.mongodb == true) { console.log('Must specify: --mongodb [connectionstring] \r\nSee https://docs.mongodb.com/manual/reference/connection-string/ for MongoDB connection string.'); return; }
for (i in obj.config.settings) { obj.args[i] = obj.config.settings[i]; } // Place all settings into arguments, arguments have already been placed into settings so arguments take precedence.
@@ -279,6 +279,16 @@ function CreateMeshCentralServer(config, args) {
});
return;
}
+ if (obj.args.dbexportmin) {
+ // Export a minimal database to a JSON file. Export only users, meshes and nodes.
+ // This is a useful command to look at the database.
+ if (obj.args.dbexportmin == true) { obj.args.dbexportmin = obj.getConfigFilePath('meshcentral.db.json'); }
+ obj.db.GetAllType({ $in: ['user', 'node', 'mesh'] }, function (err, docs) {
+ obj.fs.writeFileSync(obj.args.dbexportmin, JSON.stringify(docs));
+ console.log('Exported ' + docs.length + ' objects(s) to ' + obj.args.dbexportmin + '.'); process.exit();
+ });
+ return;
+ }
if (obj.args.dbimport) {
// Import the entire database from a JSON file
if (obj.args.dbimport == true) { obj.args.dbimport = obj.getConfigFilePath('meshcentral.db.json'); }
diff --git a/package.json b/package.json
index d75d67e0..d9346ac2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "meshcentral",
- "version": "0.2.6-q",
+ "version": "0.2.6-t",
"keywords": [
"Remote Management",
"Intel AMT",
diff --git a/views/default.handlebars b/views/default.handlebars
index 2a8c139d..176985a8 100644
--- a/views/default.handlebars
+++ b/views/default.handlebars
@@ -1755,7 +1755,6 @@
var deviceHeaders = {};
var oldviewmode = 0;
function updateDevices() {
- if (xxcurrentView != 1) return;
var r = '', c = 0, current = null, count = 0, displayedMeshes = {}, view = Q('viewselect').value, groups = {}, groupCount = {};
QV('xdevices', view < 4);
QV('xdevicesmap', view == 4);
diff --git a/webserver.js b/webserver.js
index 005d9104..40103bf2 100644
--- a/webserver.js
+++ b/webserver.js
@@ -175,35 +175,6 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
function EscapeHtml(x) { if (typeof x == "string") return x.replace(/&/g, '&').replace(/>/g, '>').replace(//g, '>').replace(/').replace(/\n/g, '').replace(/\t/g, ' '); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }
- if (obj.args.notls || obj.args.tlsoffload) {
- // Setup the HTTP server without TLS
- obj.expressWs = require('express-ws')(obj.app);
- } else {
- // Setup the HTTP server with TLS, use only TLS 1.2 and higher with perfect forward secrecy (PFS).
- const tlsOptions = { cert: obj.certificates.web.cert, key: obj.certificates.web.key, ca: obj.certificates.web.ca, rejectUnauthorized: true, ciphers: "HIGH:!aNULL:!eNULL:!EXPORT:!RSA:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA", secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE | constants.SSL_OP_NO_TLSv1 | constants.SSL_OP_NO_TLSv1_1 };
- if (obj.tlsSniCredentials != null) { tlsOptions.SNICallback = TlsSniCallback; } // We have multiple web server certificate used depending on the domain name
- obj.tlsServer = require('https').createServer(tlsOptions, obj.app);
- obj.tlsServer.on('secureConnection', function () { /*console.log('tlsServer secureConnection');*/ });
- obj.tlsServer.on('error', function () { console.log('tlsServer error'); });
- obj.tlsServer.on('newSession', function (id, data, cb) { if (tlsSessionStoreCount > 1000) { tlsSessionStoreCount = 0; tlsSessionStore = {}; } tlsSessionStore[id.toString('hex')] = data; tlsSessionStoreCount++; cb(); });
- obj.tlsServer.on('resumeSession', function (id, cb) { cb(null, tlsSessionStore[id.toString('hex')] || null); });
- obj.expressWs = require('express-ws')(obj.app, obj.tlsServer);
- }
-
- // Setup middleware
- obj.app.engine('handlebars', obj.exphbs({})); // defaultLayout: 'main'
- obj.app.set('view engine', 'handlebars');
- if (obj.args.tlsoffload) { obj.app.set('trust proxy', obj.args.tlsoffload); } // Reverse proxy should add the "X-Forwarded-*" headers
- obj.app.use(obj.bodyParser.urlencoded({ extended: false }));
- var sessionOptions = {
- name: 'xid', // Recommended security practice to not use the default cookie name
- httpOnly: true,
- keys: [obj.args.sessionkey], // If multiple instances of this server are behind a load-balancer, this secret must be the same for all instances
- secure: (obj.args.notls != true) // Use this cookie only over TLS (Check this: https://expressjs.com/en/guide/behind-proxies.html)
- }
- if (obj.args.sessiontime != null) { sessionOptions.maxAge = (obj.args.sessiontime * 60 * 1000); }
- obj.app.use(obj.session(sessionOptions));
-
// Session-persisted message middleware
obj.app.use(function (req, res, next) {
var err = null, msg = null, passhint = null;
@@ -234,10 +205,16 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
console.log('Server ' + ((i == '') ? '' : (i + ' ')) + 'has no users, next new account will be site administrator.');
}
}
- });
- // Fetch all meshes from the database, keep this in memory
- obj.db.GetAllType('mesh', function (err, docs) { obj.common.unEscapeAllLinksFieldName(docs); for (var i in docs) { obj.meshes[docs[i]._id] = docs[i]; } });
+ // Fetch all meshes from the database, keep this in memory
+ obj.db.GetAllType('mesh', function (err, docs) {
+ obj.common.unEscapeAllLinksFieldName(docs);
+ for (var i in docs) { obj.meshes[docs[i]._id] = docs[i]; }
+
+ // We loaded the users and mesh state, start the server
+ serverStart();
+ });
+ });
// Authenticate the user
obj.authenticate = function (name, pass, domain, fn) {
@@ -764,7 +741,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
user = obj.users[req.session.userid];
if ((user == null) || (user.sid != req.session.usersid)) {
// Create the domain user
- var usercount = 0, user2 = { type: 'user', _id: req.session.userid, name: req.connection.user, domain: domain.id, sid: req.session.usersid };
+ var usercount = 0, user2 = { type: 'user', _id: req.session.userid, name: req.connection.user, domain: domain.id, sid: req.session.usersid, creation: Date.now() };
for (var i in obj.users) { if (obj.users[i].domain == domain.id) { usercount++; } }
if (usercount == 0) { user2.siteadmin = 0xFFFFFFFF; } // If this is the first user, give the account site admin.
obj.users[req.session.userid] = user2;
@@ -1873,128 +1850,164 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
res.send(meshsettings);
};
- // Add HTTP security headers to all responses
- obj.app.use(function (req, res, next) {
- res.removeHeader("X-Powered-By");
- var domain = req.xdomain = getDomain(req);
-
- // Detect if this is a file sharing domain, if so, just share files.
- if ((domain != null) && (domain.share != null)) {
- var rpath;
- if (domain.dns == null) { rpath = req.url.split('/'); rpath.splice(1, 1); rpath = rpath.join('/'); } else { rpath = req.url; }
- if ((res.headers != null) && (res.headers.upgrade)) {
- // If this is a websocket, stop here.
- res.sendStatus(404);
- } else {
- // Check if the file exists, if so, serve it.
- obj.fs.exists(obj.path.join(domain.share, rpath), function (exists) { if (exists == true) { res.sendfile(rpath, { root: domain.share }); } else { res.sendStatus(404); } });
- }
+ // Starts the HTTPS server, this should be called after the user/mesh tables are loaded
+ function serverStart() {
+ // Start the server, only after users and meshes are loaded from the database.
+ if (obj.args.notls || obj.args.tlsoffload) {
+ // Setup the HTTP server without TLS
+ obj.expressWs = require('express-ws')(obj.app);
} else {
- // Two more headers to take a look at:
- // 'Public-Key-Pins': 'pin-sha256="X3pGTSOuJeEVw989IJ/cEtXUEmy52zs1TZQrU06KUKg="; max-age=10'
- // 'strict-transport-security': 'max-age=31536000; includeSubDomains'
- /*
- var headers = null;
- if (obj.args.notls) {
- // Default headers if no TLS is used
- headers = { 'Referrer-Policy': 'no-referrer', 'x-frame-options': 'SAMEORIGIN', 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', 'Content-Security-Policy': "default-src http: ws: data: 'self';script-src http: 'unsafe-inline';style-src http: 'unsafe-inline'" };
- } else {
- // Default headers if TLS is used
- headers = { 'Referrer-Policy': 'no-referrer', 'x-frame-options': 'SAMEORIGIN', 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', 'Content-Security-Policy': "default-src https: wss: data: 'self';script-src https: 'unsafe-inline';style-src https: 'unsafe-inline'" };
- }
- if (parent.config.settings.accesscontrolalloworigin != null) { headers['Access-Control-Allow-Origin'] = parent.config.settings.accesscontrolalloworigin; }
- res.set(headers);
- */
- return next();
+ // Setup the HTTP server with TLS, use only TLS 1.2 and higher with perfect forward secrecy (PFS).
+ const tlsOptions = { cert: obj.certificates.web.cert, key: obj.certificates.web.key, ca: obj.certificates.web.ca, rejectUnauthorized: true, ciphers: "HIGH:!aNULL:!eNULL:!EXPORT:!RSA:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA", secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE | constants.SSL_OP_NO_TLSv1 | constants.SSL_OP_NO_TLSv1_1 };
+ if (obj.tlsSniCredentials != null) { tlsOptions.SNICallback = TlsSniCallback; } // We have multiple web server certificate used depending on the domain name
+ obj.tlsServer = require('https').createServer(tlsOptions, obj.app);
+ obj.tlsServer.on('secureConnection', function () { /*console.log('tlsServer secureConnection');*/ });
+ obj.tlsServer.on('error', function () { console.log('tlsServer error'); });
+ obj.tlsServer.on('newSession', function (id, data, cb) { if (tlsSessionStoreCount > 1000) { tlsSessionStoreCount = 0; tlsSessionStore = {}; } tlsSessionStore[id.toString('hex')] = data; tlsSessionStoreCount++; cb(); });
+ obj.tlsServer.on('resumeSession', function (id, cb) { cb(null, tlsSessionStore[id.toString('hex')] || null); });
+ obj.expressWs = require('express-ws')(obj.app, obj.tlsServer);
}
- });
- // Setup all HTTP handlers
- obj.app.get('/backup.zip', handleBackupRequest);
- obj.app.post('/restoreserver.ashx', handleRestoreRequest);
- if (parent.multiServer != null) { obj.app.ws('/meshserver.ashx', function (ws, req) { parent.multiServer.CreatePeerInServer(parent.multiServer, ws, req); }); }
- for (var i in parent.config.domains) {
- if (parent.config.domains[i].dns != null) { continue; } // This is a subdomain with a DNS name, no added HTTP bindings needed.
- var url = parent.config.domains[i].url;
- obj.app.get(url, handleRootRequest);
- obj.app.get(url + 'terms', handleTermsRequest);
- obj.app.post(url + 'login', handleLoginRequest);
- obj.app.post(url + 'tokenlogin', handleLoginRequest);
- obj.app.get(url + 'logout', handleLogoutRequest);
- obj.app.get(url + 'MeshServerRootCert.cer', handleRootCertRequest);
- obj.app.get(url + 'mescript.ashx', handleMeScriptRequest);
- obj.app.post(url + 'changepassword', handlePasswordChangeRequest);
- obj.app.post(url + 'deleteaccount', handleDeleteAccountRequest);
- obj.app.post(url + 'createaccount', handleCreateAccountRequest);
- obj.app.post(url + 'resetaccount', handleResetAccountRequest);
- obj.app.get(url + 'checkmail', handleCheckMailRequest);
- obj.app.post(url + 'amtevents.ashx', obj.handleAmtEventRequest);
- obj.app.get(url + 'meshagents', obj.handleMeshAgentRequest);
- obj.app.get(url + 'messenger', handleMessengerRequest);
- obj.app.get(url + 'meshosxagent', obj.handleMeshOsxAgentRequest);
- obj.app.get(url + 'meshsettings', obj.handleMeshSettingsRequest);
- obj.app.get(url + 'downloadfile.ashx', handleDownloadFile);
- obj.app.post(url + 'uploadfile.ashx', handleUploadFile);
- obj.app.post(url + 'uploadmeshcorefile.ashx', handleUploadMeshCoreFile);
- obj.app.get(url + 'userfiles/*', handleDownloadUserFiles);
- obj.app.ws(url + 'echo.ashx', handleEchoWebSocket);
- obj.app.ws(url + 'meshrelay.ashx', function (ws, req) { PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie) { obj.meshRelayHandler.CreateMeshRelay(obj, ws1, req1, domain, user, cookie); }); });
- obj.app.get(url + 'webrelay.ashx', function (req, res) { res.send('Websocket connection expected'); });
- obj.app.ws(url + 'webrelay.ashx', function (ws, req) { PerformWSSessionAuth(ws, req, false, handleRelayWebSocket); });
- obj.app.ws(url + 'control.ashx', function (ws, req) { PerformWSSessionAuth(ws, req, false, function (ws1, req1, domain, user, cookie) { obj.meshUserHandler.CreateMeshUser(obj, obj.db, ws1, req1, obj.args, domain, user); }); });
- obj.app.get(url + 'logo.png', handleLogoRequest);
+ // Setup middleware
+ obj.app.engine('handlebars', obj.exphbs({})); // defaultLayout: 'main'
+ obj.app.set('view engine', 'handlebars');
+ if (obj.args.tlsoffload) { obj.app.set('trust proxy', obj.args.tlsoffload); } // Reverse proxy should add the "X-Forwarded-*" headers
+ obj.app.use(obj.bodyParser.urlencoded({ extended: false }));
+ var sessionOptions = {
+ name: 'xid', // Recommended security practice to not use the default cookie name
+ httpOnly: true,
+ keys: [obj.args.sessionkey], // If multiple instances of this server are behind a load-balancer, this secret must be the same for all instances
+ secure: (obj.args.notls != true) // Use this cookie only over TLS (Check this: https://expressjs.com/en/guide/behind-proxies.html)
+ }
+ if (obj.args.sessiontime != null) { sessionOptions.maxAge = (obj.args.sessiontime * 60 * 1000); }
+ obj.app.use(obj.session(sessionOptions));
- // Server picture
- obj.app.get(url + 'serverpic.ashx', function (req, res) {
- // Check if we have "server.png" in the data folder, if so, use that.
- var p = obj.path.join(obj.parent.datapath, 'server.jpg');
- if (obj.fs.existsSync(p)) {
- // Use the data folder server picture
- try { res.sendFile(p); } catch (e) { res.sendStatus(404); }
+ // Add HTTP security headers to all responses
+ obj.app.use(function (req, res, next) {
+ res.removeHeader("X-Powered-By");
+ var domain = req.xdomain = getDomain(req);
+
+ // Detect if this is a file sharing domain, if so, just share files.
+ if ((domain != null) && (domain.share != null)) {
+ var rpath;
+ if (domain.dns == null) { rpath = req.url.split('/'); rpath.splice(1, 1); rpath = rpath.join('/'); } else { rpath = req.url; }
+ if ((res.headers != null) && (res.headers.upgrade)) {
+ // If this is a websocket, stop here.
+ res.sendStatus(404);
+ } else {
+ // Check if the file exists, if so, serve it.
+ obj.fs.exists(obj.path.join(domain.share, rpath), function (exists) { if (exists == true) { res.sendfile(rpath, { root: domain.share }); } else { res.sendStatus(404); } });
+ }
} else {
- // Use the default server picture
- try { res.sendFile(obj.path.join(__dirname, 'public/images/server-200.jpg')); } catch (e) { res.sendStatus(404); }
+ // Two more headers to take a look at:
+ // 'Public-Key-Pins': 'pin-sha256="X3pGTSOuJeEVw989IJ/cEtXUEmy52zs1TZQrU06KUKg="; max-age=10'
+ // 'strict-transport-security': 'max-age=31536000; includeSubDomains'
+ /*
+ var headers = null;
+ if (obj.args.notls) {
+ // Default headers if no TLS is used
+ headers = { 'Referrer-Policy': 'no-referrer', 'x-frame-options': 'SAMEORIGIN', 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', 'Content-Security-Policy': "default-src http: ws: data: 'self';script-src http: 'unsafe-inline';style-src http: 'unsafe-inline'" };
+ } else {
+ // Default headers if TLS is used
+ headers = { 'Referrer-Policy': 'no-referrer', 'x-frame-options': 'SAMEORIGIN', 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', 'Content-Security-Policy': "default-src https: wss: data: 'self';script-src https: 'unsafe-inline';style-src https: 'unsafe-inline'" };
+ }
+ if (parent.config.settings.accesscontrolalloworigin != null) { headers['Access-Control-Allow-Origin'] = parent.config.settings.accesscontrolalloworigin; }
+ res.set(headers);
+ */
+ return next();
}
});
- // Receive mesh agent connections
- obj.app.ws(url + 'agent.ashx', function (ws, req) {
- //console.log(++obj.agentConnCount);
- /*
- var ip, port, type;
- if (req.connection) { ip = req.connection.remoteAddress; port = req.connection.remotePort; type = 1; } // HTTP(S) request
- else if (req._socket) { ip = req._socket.remoteAddress; port = req._socket.remotePort; type = 2; } // WebSocket request
- console.log('AgentConnect', ip, port, type);
- */
- try { obj.meshAgentHandler.CreateMeshAgent(obj, obj.db, ws, req, obj.args, getDomain(req)); } catch (e) { console.log(e); }
- });
+ // Setup all HTTP handlers
+ obj.app.get('/backup.zip', handleBackupRequest);
+ obj.app.post('/restoreserver.ashx', handleRestoreRequest);
+ if (parent.multiServer != null) { obj.app.ws('/meshserver.ashx', function (ws, req) { parent.multiServer.CreatePeerInServer(parent.multiServer, ws, req); }); }
+ for (var i in parent.config.domains) {
+ if (parent.config.domains[i].dns != null) { continue; } // This is a subdomain with a DNS name, no added HTTP bindings needed.
+ var url = parent.config.domains[i].url;
+ obj.app.get(url, handleRootRequest);
+ obj.app.get(url + 'terms', handleTermsRequest);
+ obj.app.post(url + 'login', handleLoginRequest);
+ obj.app.post(url + 'tokenlogin', handleLoginRequest);
+ obj.app.get(url + 'logout', handleLogoutRequest);
+ obj.app.get(url + 'MeshServerRootCert.cer', handleRootCertRequest);
+ obj.app.get(url + 'mescript.ashx', handleMeScriptRequest);
+ obj.app.post(url + 'changepassword', handlePasswordChangeRequest);
+ obj.app.post(url + 'deleteaccount', handleDeleteAccountRequest);
+ obj.app.post(url + 'createaccount', handleCreateAccountRequest);
+ obj.app.post(url + 'resetaccount', handleResetAccountRequest);
+ obj.app.get(url + 'checkmail', handleCheckMailRequest);
+ obj.app.post(url + 'amtevents.ashx', obj.handleAmtEventRequest);
+ obj.app.get(url + 'meshagents', obj.handleMeshAgentRequest);
+ obj.app.get(url + 'messenger', handleMessengerRequest);
+ obj.app.get(url + 'meshosxagent', obj.handleMeshOsxAgentRequest);
+ obj.app.get(url + 'meshsettings', obj.handleMeshSettingsRequest);
+ obj.app.get(url + 'downloadfile.ashx', handleDownloadFile);
+ obj.app.post(url + 'uploadfile.ashx', handleUploadFile);
+ obj.app.post(url + 'uploadmeshcorefile.ashx', handleUploadMeshCoreFile);
+ obj.app.get(url + 'userfiles/*', handleDownloadUserFiles);
+ obj.app.ws(url + 'echo.ashx', handleEchoWebSocket);
+ obj.app.ws(url + 'meshrelay.ashx', function (ws, req) { PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie) { obj.meshRelayHandler.CreateMeshRelay(obj, ws1, req1, domain, user, cookie); }); });
+ obj.app.get(url + 'webrelay.ashx', function (req, res) { res.send('Websocket connection expected'); });
+ obj.app.ws(url + 'webrelay.ashx', function (ws, req) { PerformWSSessionAuth(ws, req, false, handleRelayWebSocket); });
+ obj.app.ws(url + 'control.ashx', function (ws, req) { PerformWSSessionAuth(ws, req, false, function (ws1, req1, domain, user, cookie) { obj.meshUserHandler.CreateMeshUser(obj, obj.db, ws1, req1, obj.args, domain, user); }); });
+ obj.app.get(url + 'logo.png', handleLogoRequest);
- // Creates a login token using the user/pass that is passed in as URL arguments.
- // For example: https://localhost/createLoginToken.ashx?user=admin&pass=admin&a=3
- // It's not advised to use this to create login tokens since the URL is often logged and you got credentials in the URL.
- // However, people want it so here it is.
- obj.app.get(url + 'createLoginToken.ashx', function (req, res) {
- // A web socket session can be authenticated in many ways (Default user, session, user/pass and cookie). Check authentication here.
- if ((req.query.user != null) && (req.query.pass != null)) {
- // A user/pass is provided in URL arguments
- obj.authenticate(req.query.user, req.query.pass, getDomain(req), function (err, userid) {
- if ((err == null) && (obj.users[userid])) {
- // User is authenticated, create a token
- var x = { a: 3 }; for (var i in req.query) { if ((i != 'user') && (i != 'pass')) { x[i] = obj.common.toNumber(req.query[i]); } } x.u = userid;
- res.send(obj.parent.encodeCookie(x, obj.parent.loginCookieEncryptionKey));
- } else {
- res.sendStatus(404);
- }
- });
- } else {
- res.sendStatus(404);
- }
- });
+ // Server picture
+ obj.app.get(url + 'serverpic.ashx', function (req, res) {
+ // Check if we have "server.png" in the data folder, if so, use that.
+ var p = obj.path.join(obj.parent.datapath, 'server.jpg');
+ if (obj.fs.existsSync(p)) {
+ // Use the data folder server picture
+ try { res.sendFile(p); } catch (e) { res.sendStatus(404); }
+ } else {
+ // Use the default server picture
+ try { res.sendFile(obj.path.join(__dirname, 'public/images/server-200.jpg')); } catch (e) { res.sendStatus(404); }
+ }
+ });
- obj.app.get(url + 'stop', function (req, res) { res.send('Stopping Server, click here to login.'); setTimeout(function () { parent.Stop(); }, 500); });
+ // Receive mesh agent connections
+ obj.app.ws(url + 'agent.ashx', function (ws, req) {
+ //console.log(++obj.agentConnCount);
+ /*
+ var ip, port, type;
+ if (req.connection) { ip = req.connection.remoteAddress; port = req.connection.remotePort; type = 1; } // HTTP(S) request
+ else if (req._socket) { ip = req._socket.remoteAddress; port = req._socket.remotePort; type = 2; } // WebSocket request
+ console.log('AgentConnect', ip, port, type);
+ */
+ try { obj.meshAgentHandler.CreateMeshAgent(obj, obj.db, ws, req, obj.args, getDomain(req)); } catch (e) { console.log(e); }
+ });
- // Indicates to ExpressJS that the public folder should be used to serve static files.
- obj.app.use(url, obj.express.static(obj.path.join(__dirname, 'public')));
+ // Creates a login token using the user/pass that is passed in as URL arguments.
+ // For example: https://localhost/createLoginToken.ashx?user=admin&pass=admin&a=3
+ // It's not advised to use this to create login tokens since the URL is often logged and you got credentials in the URL.
+ // However, people want it so here it is.
+ obj.app.get(url + 'createLoginToken.ashx', function (req, res) {
+ // A web socket session can be authenticated in many ways (Default user, session, user/pass and cookie). Check authentication here.
+ if ((req.query.user != null) && (req.query.pass != null)) {
+ // A user/pass is provided in URL arguments
+ obj.authenticate(req.query.user, req.query.pass, getDomain(req), function (err, userid) {
+ if ((err == null) && (obj.users[userid])) {
+ // User is authenticated, create a token
+ var x = { a: 3 }; for (var i in req.query) { if ((i != 'user') && (i != 'pass')) { x[i] = obj.common.toNumber(req.query[i]); } } x.u = userid;
+ res.send(obj.parent.encodeCookie(x, obj.parent.loginCookieEncryptionKey));
+ } else {
+ res.sendStatus(404);
+ }
+ });
+ } else {
+ res.sendStatus(404);
+ }
+ });
+
+ obj.app.get(url + 'stop', function (req, res) { res.send('Stopping Server, click here to login.'); setTimeout(function () { parent.Stop(); }, 500); });
+
+ // Indicates to ExpressJS that the public folder should be used to serve static files.
+ obj.app.use(url, obj.express.static(obj.path.join(__dirname, 'public')));
+ }
+
+ // Start server on a free port
+ CheckListenPort(obj.args.port, StartWebServer);
}
// Authenticates a session and forwards
@@ -2131,8 +2144,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
agent.send(obj.common.ShortToStr(11) + obj.common.ShortToStr(0)); // Command 11, ask for mesh core hash.
} else if (coretype == 'custom') {
agent.agentCoreCheck = 1000; // Tell the agent object we are using a custom core.
- const hash = obj.crypto.createHash('sha384').update(Buffer.from(core, 'binary')).digest().toString('binary'); // Perform a SHA384 hash on the core module
- agent.send(obj.common.ShortToStr(10) + obj.common.ShortToStr(0) + hash + core); // Send the code module to the agent
+ const hash = obj.crypto.createHash('sha384').update(Buffer.from(coredata, 'binary')).digest().toString('binary'); // Perform a SHA384 hash on the core module
+ agent.send(obj.common.ShortToStr(10) + obj.common.ShortToStr(0) + hash + coredata); // Send the code module to the agent
}
}
};
@@ -2165,9 +2178,6 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
else if (arguments.length == 7) { console.log(arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6]); }
}
- // Start server on a free port
- CheckListenPort(obj.args.port, StartWebServer);
-
/*
obj.wssessions = {}; // UserId --> Array Of Sessions
obj.wssessions2 = {}; // "UserId + SessionRnd" --> Session (Note that the SessionId is the UserId + / + SessionRnd)