From 5d7fabfc2164d025436b899776a658fc80dc7111 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Thu, 25 Aug 2022 20:11:47 -0700 Subject: [PATCH] Added guest web sharing of HTTP/HTTPS (#4413) --- agents/MeshCmd-signed.exe | Bin 4507224 -> 4498856 bytes agents/MeshCmd64-signed.exe | Bin 4120152 -> 4111784 bytes agents/meshcore.js | 1234 ++++++++++++++++++++++------------- apprelays.js | 9 +- meshcentral.js | 2 +- meshuser.js | 5 +- views/default.handlebars | 25 +- webrelayserver.js | 114 ++-- webserver.js | 215 +++--- 9 files changed, 1014 insertions(+), 590 deletions(-) diff --git a/agents/MeshCmd-signed.exe b/agents/MeshCmd-signed.exe index 8923c07a3f165efe619ae35756994d244efae694..e3e4df3d42d67b5a2cc788555e7cca1d9ea6d81c 100644 GIT binary patch delta 72213 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 delta 80362 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 WRr|M%z4wJNpIx-6^6}Epx&I4)o<{%x diff --git a/agents/MeshCmd64-signed.exe b/agents/MeshCmd64-signed.exe index 6469acb4d88b9db4bdfff1cfbf5e4bc5809475c4..642df48c0301a2e45cbda8c4e766804a53d5b021 100644 GIT binary patch delta 72199 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 delta 80396 zcmcG%33!}Yc_#YU-EDWfo3`DSZFw)rTb1iqS#^F{ma6pIt$kl?$?_r#b#0PLOO;%U z7R#a_EFI_oodA6@+#!J^10*ESJ$VvB1_C6IjU+5L6G8|}7Vf|>a3Pr_%)kxz{m$|) zrBZLnbU$TPo&9|K`OZ1tdH)ap_qMF4d*05USEQo)sxNfl)JbT+pV%O%^zfW%l(IGJ6^ETl8@dDYSMN^&ZlT(#1LJ)2Cm7X1BzbK%B|7ca8#NMj&amKO{MT3W&t z(izg50zq#}K{K#ncbW0`^AG(UJ67`dU%JE^0%zET3kH2hbN2Tp`L&-CTy+;Qs&xY7 zpmn?UcC9gB0aUkZgZ+WfVyuvu^`8%g&d&slq3FtNdLiW>vIau=+4OY54`d7mtP;qN%(K^7u;gT}_y;^g>5l>-{(eBZ zzhyvCL9{;@6#dSYfiF<0yE%7v=h=nEVEFunFbjkVSvhWhn7^ry?R5=e=A{f`&Ob2G zKUt~gdFmPTttm5IzKLUG`9w4s zs8X7Lpp89!4WcHI&c*VDSRrMlL%l=2W4)~dQ7W-)tzYfz=)F31ZK!`}`1(+!-jKgX zhX*2I^}T<5Fv8UL-j2?z5fd_#kGHevcKm&t`un+d)^b=hnztsxjm^z~0{%58MOyO1 z$2!hRcb`|!Y2=hdsj zSJ8KW=gq0k8|^)3F|^!dh>=qNlz~FMUA>}eGx`zZ zrEUf=oe1cq?dDYL$jCr%d+QjIBjK2bK|5|1~14Y_C8$%^ZUP+!UH`iNCPf1ZH{ zXn-S^>Q&X_+bz+=@zbIFLK^aMBp}D)a6VXHvJL^oKf&q=C~tX!RUhAw%9&c*NC>q( zj57XVXN(8=%rD#|DokX#S%yP=cm{aUF?Vd)N-8fd4lcQ;p&#RJi!i! zL_>O1z7GGOp^T`0t8a982&f|aEIqvjwG*w{8Y=28Kq-DI4yVOjW{(&hV{#o?*`3D1m;IzN7ArKu1 zt>hpZ05`Nh@k(}j&md8fL)>Zp^efqc15TekuDOUl>yja@1+I2pxz;&4Mm=fE5E?u> z=TnKD923-L74Nf&X}Hu@Fi_PAq^@ z7eTpY)gB*Yhc9ku>kY74bI7{$tOY-ml+c%o=58IF*#$*QVUxP3d ztc)MF9~z70^DEh0@^&pHPD?Pm=RM?}_d5sJ#St%PLqZcNpiPZJ<2Yb$$I$!r(1$Vf zLSiYsRHsB0x+Ez!C=k$_40R6-x3vyTx!~`f0ET9SL1r{^7fBJ80#Q9k;C-Hdt(zTh z>Brn6FjA4}K}%ULWdrUa-s=~Xd^{QI@9Z3bDjn~1kBOEz)XSHKSnXb$h&w^Vs5&63 z9`;rp8MsNr)Gw;NbAV)+ypQxkY=dwb`|ScF=-ZWaB*0IM->4GRPL zQMoXVo(Li=K8*7jnIyi7lp#`~=oVYC#3mb_-MmoO|b z`FS23VrNchy|R>_Po%mQuDQv!8c;A87{$cVUhwKF2xIm*92%pQuAaiFv_>?RuY zST?a68aX+_>Kchc1JO=7B1y3J>gYnH+Tb|U{niKzo|kpi!pAPGjnUze&LP1FqgDV`noZ273O;loDUJ-y)e8M;L-1B$eIuD#NEK4B&1`3uyO-`*W>VO)m9vp+^-KPD z#;%YxO3b9O%fHgW4#WDsHO@|zY|^=?vo${3vDWGo2)$-AFXq)c(iQP}%lM!hu1Qnc zJ@jjnAsTwu?8oEGUs?h6fM|#)?4IezpcuvlnKPh-tdz0M zMspDQO3{%EKGy_d`ZZ!aBoV}Vgs{@Dv~pWStHeLNIK=+sZ@*LcSR!`1fAvI zyUD6bSEcKeV0Bemt8nAyQ?Q8~KhOo^LhuTldvaAnxJLb}8t$|#>&s(lmdzA$*#-Yq zs1QvH!7I^ZYC2Y2D2Rcz4|YU|poU0Vr}Mr{mcQW^Tz}6i`08CYUJll*kSN2}dSWpd zNrp7Fl7y1W;sH!@kW8Rq^803{#1d$*Y&tM^FheCFN+>2OT_7`P*ez&8ax*1M`V&oG zl*N>&Q)0|=cfrEJ0fYV_TYlIr6h(tNhuR#niY1Jfp>J6@n@i86GgB3t#Ylqv1`~ha z-9^)?G|5oev?~vC!7@#%5**T0GwfKMt;!Y)@%Id|!)34(jtM9zVUgL80-z`X9!z_U zLCWCi9(K~P@$R~9yzR*l$)rP-8-j1sk|Mi0Iz&+7fNO!Z<}_sdtX0bw z##oiWMI5o7z0qee2FuP}3GevjGn)6|{iAC0jS+oJr-F?NJszRx^Z^0ntu z@E)`-7OtiivxQVIi~_)qrrSo_No}E}T(*!+WEU(GKjLeJRDLXr;+U8E*@cZ2ose6d zB1cuWSg_1`{D(3HCy9Q}!oO9Bqfd(=zeGvPsaziWQdSt>u|AL@w+&X)C;=#wNQu7C zrssDWBTZ+fv$}XH6N|%>XHCbjRz^#d4@V3bpZb_y$Sy6d>9UC=#!Nqk7wo*NsaSH& zt}RMG=!r`~9q zlmdBfQz6r6KtXIV81<^mY+0GGA#iNbxeDNOZ?ItJ0R<<@$oqpeLw)1|I8d~k;yCkb92m;-`n{E z^K4!Mpu^Lw1WyA-FTP=NZK2gw9$Po}@9q{dqfKvu@%ap^>FpN&&2F(EZVQbTvP&@O zAX<=O!k9q{$O}otCX&>=BFJg(PcrOCG75IJx7IXC8f_s#Yk`dy=C0@Eh?F%k8ExYO zS?pN0g&al<7{Ylymc@3?4ZoWo%X4hj3>ff0+Z7ah21t5i@@-H`HX{%eTtEE;*tSqf za@%E;mES_7Q7&C-4n(jaRbC^kK|tjNEL8Dun93{YW|zw3yA517RBzYP<`G|5Vh@%8 zP`T%>F^8V913>F;(mqaez`a+L1+8xFC=nATp(I$I3et~mx!90Dl<|yVzyib+P{07? zJR+*9O;!FcORVNGk*8Y5q)|{Pzmjx~iv+EvNSgFmo&k5R`UL`dTn-wdMz^G_u}EeC zdxU8kNCT{H_dvYBl6znLx9_lhpc#j}uU;EdxjcV;vj_Z zPl#MqM(!Z=0KI~HQdqsVXBQV^8Axz-P{H40^clV65JTgA8UuZn0(fj;A)A0NR~)kF zw*3B{OBd?NgWqdeg>;8!zAqKi9z0~efGr?fM z=o3;x(b4MaC@t%tuid;F#ICSB0D_tDpMD)Xc+{CfAB?YFquc0_lK@P3IM3P+^%_l5 z96@{zLIB$f}#VXPkR&meSypMkscQb zLtu$M(EA4ptghtOnPNlNuzy~<)D%E(eV4;c^^q_-BvD_Fp{Q~=uz>;?3N(mGBXf;T zO$UkiA7i9(c4JVQRS0wn!~DYqR&&|z_2mtMzo*C!@n`dF$8L9T1eBH{JHDd_Oo;hD zf$u?ox9X!?%}~ybhFX@@L(Lw2NEi{=Apy45*kS&aBHMLHGze+}FvTID^=|kXZ`W?L z@gElmMW;$0LVodDf=MO-!FAzXY`j43L^ofxnD86f zwMbC(gQrSpb%HgEMZc%C->k>V$yGy|M#6Lw2A(#g;7^*%<|KPgG)-a}PEI;w=b#H$ z&h(0^HB91WB3xgd>#7+Jdopv7G4bh~kXS-RX_9Um?lU51MOHlIJ6X9SJNL0JZWh<&s zSQqkzgFwL+r7$zuR4E+9K`xTtiWZ5^q|>GGqNLdhD7alK7IJX8*ih7~lO|QUDFXO9 z!ouI!Y&&&yk!G~N%Y~JJ629eYTQ!_kR)25;s+N zRVW=0!9kMtYy2yB+0LU_F-z!93yJHg$p7Uowq~e=f|&AAT{Jh1Gz|OF!{UR^P-)~t z<$4}4+IozmW36M?MyF_re!HO-XBKesP3`NzVI;&%n1(QP1~ed=aTZViM`-7gO*?gtvlV`w&m%|quall4;AbJ%QAWtlTy5(@X;DeAS%F6pJ-gAlwEQ;G}LMThb-p zS;El7>Vktm+~CzxZlH7j+t}F$pv0hS$von3d>cD)Mp!g@NL1Z+rh4i^h9BTxdK=rF zkRX9(-OvI&DyUD>L;Xfno1Q+LeZ-XQqY5Jes#l_lYx*4htfY)4vrjw;SK9SgvB%EA zv7}1F{FFS_s0u4*gqAjBGgh~UVQTdo7n%&`%wAns+V`?N!Ew!!-UbL^wX~9{1Pkav zY6wLTl{N>(ERi$iLqhF|GtQI)Qb3!9fytU*Y9*J*aw<6FgTD;z}Ya zM7hRW#fM(Qc8>s3m>2-SCM~Pcq-76HnwExg8PILj8$ED+y%ZX3t2mSmG!^Z!!r=NB zNklkZSBaLptqY!ZwxWYtzVs99{vIze2$O);?PJ-VES#qOV%22098>T)VRaMYq)fDj!$(B43CW5#DF3AT+>q8L1E?i zqI-}Ipf&QJZ;Ya}XNXRHg{T)8VtYr&I9Vt52rFSU!9Q7o&zw+t$b(X^etD9eK1v&5 zr2MhEaEX!uAxZ?MXRZXwL<&3ve#NVon<5PEkx6($jdEgWARR%v7``_!8Vf-E*oKef zyz=23x*4v2A=T5sBEocNXne`d3th9L#VQq?|ad(?$!`j2m9k95dQ#&~g>c3`pJ-77X7 z7xUBfgN{cTK5UrbZV&y&0pSWNOp3w%N~ps}rNzCkF3fko2^*YVXqQ-RxS4?MHZbIn zY1vy&5mLGEyKiEFFk#wWFv}^T;@Fn8jAv$H0)81)Yn`fF^kA2Ll(#;`4o!#(8*XmI zDwYs&(-O8$jRnG@-*WvYtZGSZ6|v=k!4q$0)y+6i+tu5BjSk%p4+-MCX$+B(kD_p! zlAmA;SO__|`={7rB|-X*oTr}0g$+qGFeDlox8{U-f?r{}EO!%XZm0#vuLA4i)~wEA zP8JS_P}ZW(GMy|_W-aKf5*9^Hlkihe#d)0z-jSZ7d8m2|0>#yPH*YV`#3 zEkJFIOX*^R*t<&Awjl7?(1b+_coF|J&_B(xOGPSSX%se0Vlhnj5-}LZvsHR4tA0Ru zVT7SlQk(?8u95>nhh2jXLp31)y@rkw>w)!a7PGlS3rfeR@f6!~!kN@L_g>~TTmr)~ zsDhNm!ljMB;VmrhlZ!u>+G-LW3}=Ez-pWqh4|bRK$r)Gs^;9Ql_XxvRp4}&3nCqap0VmRo17c zaG9Fk&=R<$=d|wljC9B2mj8N-#hRMx1DDq#t(rGh+`>!%yHbVK$X$22M&Vc3T(A_l zoQvdj(|6oem{=)M6&TfEw1Ov-{Dqo0ky&(SLdAMAmu04h>m{eQbN)JZ02Y}bB{_`e z=V5n0AkeJ}w{J2h!WYRIR%WM2tyF7txQkb^hwqLO?0nFAXT9Ij3jvZ2`nx6}+|SiXY9qHfFuFnTr22#& zsxN3vR`=M6qV^^NL}hrKlrNH|6wGwa=#0d?FORg=2 zoZOnh(R*OZ-gi-{8;Lp$B_6~-)CQ0#Pmjv|Sa=Di^Ed|upWj+0;ZKI9(+j1P@Zj`< zvZ9-)I%fE>yIvnak#1VHV=NeqSXEDJZ&zTQhAbhg=7(a_+FDWDihlCc3z#T)$O`G{ zG2GUdJG69{jRxIkrSu)~XwvEn2~+^geK1#-0qVXG&_Up)dH{JJu8-4Jhvu5LjVKSt z*}m=m7=Lw~?cd{%(as|6G8!@Zmxto4dYjDLD^d)n$Rxk>_fFjaW!QJ(@g3S)G~`r} zZHj99RimfM8x`%ltD;H1Ex`_#;zmq6W&QDqm~@fhOE5g?Fu%Q6x#2G>k7B~9MpeD# zX{}RMcv8m?pfCZlkNqlW0R2+_Ieg_GOhA^F*KTRSsCC7P^@8T+L2MqfxoI5KGoa(N zjeAS+2WU!B9(X(JxFm>qVKIf!36OR~B=mz$j!tE#r}HT~N2XJ8JUSW6=+k19%76Cl ztZ92{m4E*2?4UfNg>%Wc7j#wJ305~m-~gk-?i_iI#%i=P?7I|p9oa8cvh&pa2WGl!^=3_{fZoUm+`zE~#LZ5vIL=!lL6H#U&%To#+%IxT z$BHjX@rk0xajuCn5)y$57AoG!#)iN&Xa5(3&`e-`CNxEmXrE0b=6e@~l0$d~d;Bz# znNT@6shYxorerw)JK2SpjSX>^L)%{dHc5la7pahIXZ&`Du$BwgU*P?6yI%+ z#ss4bmNODsp)2~8cr24G;Kp#J9FonH&J+KU)l{bpT#d&x#Wiocy)$R{bML~D0d*=W z8G=Mu5X2>{I#D>4%VlvTTw!-=V6ZbZCDOE=>#8O1v$)D&*20oW=)xF6Lb}5AQ7iys z$fI8B!u7%`O@y?1QNts!=_FeBO16qyuE0S6D!4E4E>=?~>0)X)PAAcOJHVf)k4v(G zNNI>xTnRE9Clp{fDtFlJ0&1Spj^tl^7xU{z1rTT`IA@KsLr#r>!BLBTi6kEWId*g} z;4bq8&@712kza(*AxU@5`1;SWhFY;-sLFuj+aPL2TQ& z?scP5-@NDD?2*TbYhLWcT-|6;-Q{n3H}(`(%0bf_#E~m%yJvGwxIVZdXqm=2aTi;R zw)~J7rnGgjzEdS8df;-B?lP@S3ZzZMMDy81CvFEqqfmddf@(rYq_l~G*sh&L>>;h9 zEi6WLJyk?rt%LpjEUP+On1oRd=5o5$t1P+*VW;w+eh;g8Sb!SC#Q=HhM{%z2i|=8N zmU=)q2nprN4E1!j_ey$3)5LymGmzGmDp_%*4F@zFT0p>&RK~wfRh(6vJ(BVQ#?@JD zXX1`|m|YCj)zP#ApiYezybi;1)M|I zJGx?v>4i1?v>{x#xgi0GkPtbkB&KAaWU8SaARXMys?g(2lr=|eqiA+pBk<{Q?Bfb2``hU7|c5M)3ERnDI_+ez-EBdwk!!Ja+D zo?^6$4f*UZvC{`Dv`1{r-~CIh_7&dppZz6v?)l1B8_I~L?%h2pOUXXEB0QSBi$mPgnOshs)FDF&ojFX0SeRW*Ph1%-09fTugrqfEi z?tQGfu?|iHX{@<>`v>2FlX`u?oD?Yld**rOYL9eK>SWAh3H#*;(bGnk|D^A7Q87XpJ)x{b z#G?D&IE^jMv%ks?tBYH2`c)QHR9XU`i7Gat)3!}iM!MRYhd@rGhSIo8B}P~_D?1%= z58cAfV{q7oN+?Cxn++xDPLMKN)UQRC2_<`%89~dFGmG0+(&d1tGJ^8+mbfC?gUh0- zWr>Wu8Y{LaWlN!w@=L-cQ?HM1wMokxqgq{D7@@nSIc-moQPXDG$sv(@J&$WMeuO}g zH_)@Vflek-n_Y>(1wE`+b$ab&v>CG(LfIt>3Ys^j78dhEVpTPkrD&l3HmuzUb<>-K z&9ozI#-h_@_)C^4(`je-+yT_3Mw+fL0n3^PLyPA9w>PysD|HjKU*4(HvQ0gb5T^rC zC(puVd5K3C$5rv4`w-g+^8}1{jDPM~wzDL*+_#b*gB7?B(DtxY8A;k)Uq+B+T&N7& zBxldEeX;(@e>1_Wpc-Cw0l`;*!N)$t9=RWOumD4K$%%kb2@LZ@#RX9=K<<{2-4-DtSA06)Ug2RQAt!UXYED+nky$d zv2vxH;^rKsesi9j5>~*i!LT|3cn5h>RkQ|K80%ofT*$eC&PoN|>uEtWAj%1pbX3T^ zfJQ`SySx47V9sbQ1re7a7wp*7NoR$2WJy~o+tOOgXD>z5>XSgxWd+5uD4i5zSQeLm zX?c@{FQk;fP2K2n2;m&zGVa1RY3R=3o7Uu35kIsnC*PkIj@G)2W)Zn#$S<^D!ET)l z4jE}zUqouB@{8Dmp6HsimZQ})!ZI=@Y$)>mMhDxA9m>s;ZLX5I>mn&vE(04NQpp~g z=8yj-J7nO7p2s$VBovDa+o-fHKQPn5-+=9xGG+}p4JEy$qH`X-PcG0oXJH&7PZAPr z$Il!s06Y*E02j%jQK6}=IaIJ0p0JvzCv4~8ZuuZqr{UZht|+Hsi@;OTQ7TRTW`M<& zAk4uq0FdN=^&9NSiA{O3Pl0GERP~$e+;;p0R8E$rf!66377Yfmdk8r;OP-(Jk!2hL zhcnjcRdfOXsw8-$BN433^&VtdK|tLp=vv4JHp-eT`Df%hUBFMaf<02{f_1D=;Yn0l zW0Su{R@J$neZdXxDibaUObV#$1x1y#mPKLZ4fV%)`ok==OZjAzR)W9h!|Zs5?nBjy z15w&z-{MaO#X&7~bU<9}B^-KOk+6B{aFZO-7xI7?yq->^oO(E8df5^(hYoqG5a*17>eG9@NF_>FgMa!`W83LuSBRVOSdV6 zn@a!gTni22b~xg^!fz_G?EYPom35t>B9szb@-n=1f`9Y3nC~>UT=iygd}A@2EFz?K zYL%Qqd5dHc`N5;y_y}XlD4zNVGw#DE`BNWZhqnjT`FlTtu&rn&#y|fNc6oJVbmr7rn4DR;7n+_Cj^v{c;WB$o2obtx4`0m!O!oXG&eZ0ihmeSki31VwMJ zug66Ec=)Oo^D{U+Nc%zfq1-u0kYP=`;fj)$XqQE%s3JL+LwX4mtHL(6*>1L(sWgSX z&BPY8l;@xPFKmC*A26mc%LpOB5WeL0Y%x=a;I@uaB{YQ%+Tzm4SWmCL?cC_AQk1Qp1>1|Y1qDF4t$*@^po-Tof_ zxsNj64(X}lpZpzGO}HBQ7(4t>iZCcPGSz{nLsTkecixO=Y4TbBCf+RQ|VY7r2@DR&VeI|R`wE(Dd&rQ}VC+WBrEUpEsDYkxFm@9dfA9BLZKJph zDU_&|8sd1J5C%rC)gPi81qQ;-wWYIp)p?|sANv$*y>H0s<*`pO|0AMXI!ZXu(#wDP zQ+S*JfuM&&s(5~40Q*QCseB@r7A}bhLPLu%Q&wrX7+ebd);gD-Y*6=gG58TP@Qeb1ymR7iBAOww*LZ}XG z^tnVBZUt@_T{y$$x+dC5cVne-p~FIucTEHu$8MgRSK!-K;zh`{bV&qE5#6l!fFH2! zL*VQcUu_I^0bE@z-E}%(N3;u3N!EMG*nnKq7DjKdM_PeE{WW@~#>RVoOT2?M1pjP_ z_&@r6RvUB$U{UJAK|E~%L*P??6P^A(-ugMVk4IkFucb(5k*zXplEQr z4#FAdAavWS{v6Qwi+qrp5wf6N9R8wgRtbY^r&z4WsmddpRkkXUl=Da;#A(D#mrXo-u{AG<#m6`YU{U{xC=oYI3f+@nMz^+K-3rhlo{Ll_>cdTo!O}n z#n1j3J2$kkg?P~X)RD=L;@n2sS@-zTTMhmN$Ns55W3{C%#xBicDfDa$Fx&`BLLrQl zk~2Y7Q0qB{(5bFHhb@SaW*$hnT6um6nfWZy=={r`?q@#BYR}Si?YFOBK|ucXl?#v| zB_Dm5sR-EKc=F&)W6wW!aa#72lA@;;s2XG za@3c40XjUol|4eN%#Z){rRu#RG>0O_UZMhOfB$wo*LLpDnQy#L+|O`K1R(>api~J4 zoe-pJqj)UOkz#Qu3cC10rk!3&VEGIrXrqgGR`t;%`l!?8B^Y{bz;w|H!ddmwEk8^@ z|L5%FQQzVV%)EN5ne#9HIXiDv6gX$jC<&qu{Wc<=j3Zvw>7|57JM68?|COD*kG`bv zrCC;biONnO;H|-y>az~)7W{ylc13s=7EFoJ%a~PvIX}|Eg7^^;4&A5ez>hs{&nl#5 zFaPB4v3=2t43Ql)JNo+YAjqIGA{cV0Tnb?kvVq9YI*`ziU`I09hC|8O#8RgU6D!k? zBVLZWUOXf?aUzW6slMJe%)j*)tQPBqN505T;ZOLBbbMEO(T5Q(EnyAup)X<|UrK$^ zWL1fJ(&4H7{sH@L2fdSm7Z`#fO?mFXSp=vI9YiHValq zvJ5D6()$K3K9Wl>k|lx)YyPoiAOF~w*h5EsYcHFtM!&>rnc^ykWwcf+(?pIq;qUtr zJ8{(4`m+1o`ByKm--*9sfup|Vm)-BDUuM6DzRb=Y^(9_*zpwi;l*dblvk=>m+Wa*? zYaZafx|SXMM_(q4=U(~xjOuenYu!D1f9 z=%4*HI{;%;ta^W7f3M;vzv841eueGs={It*m55j7IyR5pprU+8<*jU>cYlTXj{34M zF#iEaTzF?I$y`iY9j+`aE{$TV11n5*-e<~5@wmzBzRHeb3%mcTaGVr*>Z{B;f`9=K zzldxq1+Z-j4ZzX!n_oqM@m~HH^3Olx&s?u<-Gm#lhitknTyVLzDc~8_-WGaPPnq|2 zwy<%%CFIouWgu#~aOjpJ)$aPcFjO6U)!*f8_xK$jsmbV`yz{#O0TSU0^J~1|Sbi8d z5$!+sPc27UDV!_-iMz%y3rPC)IXw=*eXkA(4Cv^?t12QohzM>?Y$tGtuR}Em=c`Az zfqBU+xf>!#f0aM?5w`C!#1g^2F7EOOgUg;}mAg8IXUoMyb*_k`K7s%)^ui7iBOP$t z%>7ma9)=+w5Jker;OJ7SWxNu22VG+Uhc5frkFv|AZb{G)TP1l6`95Drp{MWz14hB&Kzw`PH0fe5S-}bXs z&V!oKIRB?_;Qkp3B-1l^k>P48wy-!zk5uZ0@d^V?oE`_A!37l(Mkw7uFDBc}kIPQ> z(RO+~88=-7rK$%F1ne+#ZN>VN#LFHyO;~C$4e4QNi-}#G>tC;MDvtF@JZnd z%^F81ywjgkzq>#usoWCCrC`k>tqhW&$r*k9$i`4SgchkJ0v`eY>*6{>Qa(F1ZESwf zLdN6d;s3_=AC8MRM{&7q6z`R$aIgl_EnQgKFyj0PU1TZxq4VPu8Scory>_L z2QsIVnAlCm6jz5c^5_UHl5u{;Q%v2z^psPNp_Y{tyQpO?Iwe;hTXi89*Mva$yyKg= zl?Q|pFQlv_pZg|jYy|xL67ViSJ7@hWWYLLk{@HJ`;BYDs1?^Jsc^m`4^+qi7<#vFC z5t`mqUGIZ$&PYXh%ePqVPMqk*`ceS9im!f)JwhiaG?a98)eUkIXU-&`jKS%65;uY8 z?SXasL4w=0;#KB4^}=r_;%*@>7;xYQj&x4AIFjP5ibB{1L(iqF)H>)0m2!pA?1GY2 zVpy7Yh0t_W^3>^HUaVXPB6O=dpgVaxKe{c>L;sx}nt@e=E^9Klq3LA;I?|%{VMs*3 zD2QC`Y##?%1~Y2Ul{U=9+cg;xzW)l^!PH%|qh6h#6r8bq6!su|kg@J?;ruDK|2X+Ke3eE1|UOQT|Fhi!TIG_3-SdGivcrzW~a!szi!uaGZ-kHrg zDtxZE*GrztGys@x!F$ZKxt5fcP@I4Nf3W=ziq2E${L$~Q!)I}bZq7p2WCTEz!T`jV zqh%2Uy!u;OmR|%>WG&cZQ;W$9e*IVCQI%a)f2HmwuMV|Dr)}u( zQczdI?KgVa9ceb!VM^14>836j0GfDKLzB|Y7t}9=I#qO;dHtnLf63}EB1WH}ko8XB zcv+`I;Zbk17T>+hj#%Ml3Y3HYXApqP9bUCl1|{jV1D&8yS&=yZ{P)>mMc&@ptIzxc z+rMpRhA|Rt;NMLzZ`xK$v#JP)E zPvgr}C9{kw8@4)`bu4frf-7dqS-7E%H?0U-I60xd(GESOw7PQd+PQa-E5yN3 z{@U;2l@%d@wM`0X6T~w+^tL=~e1FdKtK0Ld%kv8_t-0#m_58X+zi_6KTEwD^w45`S z9a8sl-~Y?b-tuONH;u~l6xqyP0{|l^{fAt^Pi8_s&*2O|U9}0~#0u)m{Rw}B~OH9`SOQ^Z7)?WQ*_4)DHzhR-U_WB-h3AuU|GR@MuB4Vx+TzOP3pobKDFRDiJo2Os4v|CDBMZx) z+qL&$=6uh;;P_h7{rsTfacv#zv)6L06ltw|q`IWEqsuKx@xp)tgonk1?iI7=fB$VL z^bh_E+b1oME!bX9G7!XyQLOAW>}Ux|=KcTr7k1)^=tQlmg;KQ5A0=Npf57%tmCF~g zm|6G%I}(MtM5I3h7m$&%u)3Y42=Xv~rtvhZ?Mb27N6D5{G)b1wjJAY6_5*hAoIY&i z(g`yz#c8`IFyOuaiya%X;U_dWhX0~aFK!;GwTH5D1TBeY+DcfF2{?!~iDluZO^Y&# zR?mxNGE1zy3s``Os@|o%z9UB~S}oxnRUXFgt0iY=)!W~T7i z`~%;>RSY>)iEU?y@;AbWfYh^VWI(&!EK;0ra&hSDi3Pm82)%%ndTc?;Hm@&)np3s+ ziATd74~d|{Xa6^A*p31IKkDz%AF>O_qZ0nUxa&}yt$t$sLx0DPb^ArAvH$`p+dTYu=Hsl^e)HIP<9yQI5#74PdfG;Kz z8`YvfY*gu7d6Sg_u?elR!LR+0T|OSyMC*g?NKQUjczMLiqwQ&k@9(^_p=LWajsN_I ztYHUntcsG;v?)H+G|s_`P&dL~oWzTvi!P6x_zk%kn zA><&B|KX2te0kQ0!yToV%#nN2>TJP*P0emJu4!@+f(@WXY1foA11?hFyNPR#`n!le ztI{tZz3F0r-0krRJlu2!wmSk&A<`l_j|#Dwl11af#im7iHA4hqt zPF*!2&@@&&A`@nWeP&Jy0~CNlm$AlD7L{+DJV$YVgZO$;E{76$MnaZr2$X2tt^?TU zF^jv;bge6IO{8dKdSiD+erjdUK)PZtB>l4n?#6<( z1p44(&Bs%?iPV4v&Wv;5kD?r+vN!A$eu@1Mg$^J65Jo(_k)iz3f(V~gN61Ij8Kp#p z>BnF5uk84?8UF5n#qE$8tp70rcct@{DiO95@}>a|Gu6Aa+(5^{{4?|p3bct#MN~A= zqCrXo9j6fcvhm)%)Ft5AGt!i0vbt2Xqz?&EoUJ&NPFyU%Eq@ZB`f#Hv7*r}}9rO@^ z`fxpWT!+X~w0w=NR?0{szA2_<1J)Ta7VrcT0l!l)-V_H20Lt&8vl-Fy43NYB_{V@0 zcvb=d5?6s2Tlu0bePM}~-l{pj4UYr?NCh=?B?9a!8lfu@2M?s(=}I#xtv+DCZ6(Gj z35G~zsJI9TS;#r2i!EG0)fG-2AWh3$EJWpL1f-RnJcvtiCHu)IVj0uvfG!CtXsBDe za=4ms{m`>QI=th@=7C4VSA_`v$G5JceUym94>tt^UpSCjOVa9@YU7*2JObTxLCcn}E z7=gPOg2^nT3Mt>rhJlr>xO~R<+-L5`*3aqt%CPq#ZU@9v z&@ve!EC!I1D_%NfVz9I1>$xBZaFr?>SbyezbN`s$Eg`?VT!o6YLrgjUgta2poRp&9 z(N*6aa@*AK5g(0EnqwYo%-wqx>G1CSDj|d2F&l=Z~DOGG$=hsT)b1W^~Y!i zqFc{0p{x-vt&R>4)X}O!PG9e#iTiYZv=&u@C$RA#1tOR-_Zy_2^X>`dC+#WG9Z?1Acz@sB@X9z23B#fFu;q)0(rzwbX_)|60y zcC9@5NRW%2nB4YqXKro~e(IRq40FoC`%GZPzL=ASXP?UZYM9)<5#v9-!#sfXU;=w? zwBsgG2V9rK&#H$FvpKz`60pgTLCC^c#0~`1QX_O{Nk{k~D+jdaU=d1~`*xc5mlPL_ zq;gPyi-b^Y<;#)LxNMg$f)K(T;%!5pm6)*0Lw}1Uqqm&c%J&qKtbjiGTX&iV4+=Uj zn>%jY@=xr9n27PcJI#G^pps05Z4t-zt9wE!<|F{_ol6{qV!))Le;EB^(mxIG(b*-3 zQmX};xgU&RCrVJ*JQ@{1q0LdvXKuPCOHIlgZ)$ThwhdzZ=N>kXV;-M*#5^Ie4qv9S zC5XjqVBsc(bC`b3EM()c1+kagwGf-h(<6vwybO1Q*zie0Y2Ln_yL~&O{;gFaLvfh0 z$^>-DgFlv?Pu-8bs2IQakm={g9x@-UiV5F72*3D?8I=BWqLn24gfH2>Jc=CMP>p)^{7;50qPCilQ03}8CI#`N@X0ioh4|Nee?-z9ejC?UcyT_QUJ}KfJ_J#{SWOm@PIqVaJ*4-rH zfCz*?-6kqBI^`*N*c@dWCbrplJZ75qW0PuB&0#S zl_&f79rMU8Oj8CwmWrW8Z#yTpPzZPiTA?Uz;7w|@?wC7#dAE52hUky)H>>%t?KXq0 zOV+v_tQIos`siTm5N5VR2CeCP}$Y#9=9?Hb0a^)ZIbh>6-lgRpzbxHa3>X z;@Dq{i-`pzXDk~~l>CIx2fNB*_q>p5o(FCsNn0_8NBuYoXi* zK?8b!iG#QfpY$cM@1-CfYL8_SDY9L!6jQl$DvvBG_}oGBPzj|7?0F0Op|O0S*S_H| zTozL7_yJxV@W3lEaH$pJa!0_Q$7U|kQ|D?bF^W`NFpfJ87GK7H*C-h6KvSEsX0#eB zcwl8!c2*QfGM4cN>dROWKMBTpi^ZEvT~{Ad}n4kwUAv(p|}JyKY!f3wH>H^>bQAmAC5E^k-h>nEGsP2 zEUxGY6ko&y?o0Rq>l5agb1!-vE4=QwdD*yq)~1e;qH{hE_jQPEbKsqVvEmxmS^iNU zxKaUk*%{k`3cv*v`1k6~xA8x#F^^%4EBw1PSc$}`ZJaQ8u^_`3>_N6;9}#CTwULH9 zajO7b4ji^jGJ%Mo2=?wy6^3xBi=O$rmRsasjt^X0bIv@dM^JcC@c%G>SHRp; zLSNLFmT7~+-gmm2^RIyvav)n0W_Q)p2|2YkN{SSa8zL3LCpS@qV_r%z{A|tTVrz7B zk^YKItn{^~wvDQlsD{R-@W+7&1U4ySqD>;youSmG#J@WuNHqlh-KlVjOKB}L}pMejZ3Cotp>a7Kw4ah@ulb&=^4?Kq1^jOruLJvB+4K2=XPdwW5d> z!4Esc6bk&ab>_hX=teRDV%||lD(^>i<_W(TnLUM01XFQFKU+pBudF~I6@~bB>dlYx z-#CwlZ@k1Y0v+x&zW_m-2XFu)0ETF>m9N*E)w-7#khC90FK^yfqS~(gzPNt{!i-3% zX64?!qPB3PN)}d*RNR|n5{wPXA@$lK00gC>g-K2d6DXWb!lVQmRQ(m{I_>aEW+Vsg zk}DwA5cZl1_IbS9wNq12mlk|3-sHzss#xh4(iuFefZ-P*jO6Hb*czWq!dSGpMa2-g z2kc7f2!dTu`^V0lp`;1O|JR%}_no<23zk_H0#MQd$bzWOrCd>-Kk}H_y3<{;3Kh|a zw55b8g#^C$q8WH-)p8ds$%3Phfr&ykH=0899jd7l@QW-LFMHFmJzV3y;d)8>Iwx(nGyId)`WQL<#0 z+>oUtLz9t%De49F@`k1oUyQ6;;t2dHV+4BE7S^OKit~;ruE~|ta~|}JSjQ#X+9k@x zMj{*%HW3zrR^Daqh5^*Gl6!I!d0+$#2I*f`JPY3^zEXUrg$4BE z3?1PSg`Fx~!eCgG(Z_9cA>T5USPu1O@&z*Edpof0@~u5)WZN=7wb!hBNSLQIdH(&F zdEmT|2{d&%hMMKgQ+Lmizhkd?@?oOjWlT!573>kPRe^tbuX)5D1t1_vJUpceD=w1+ z%HJ6?FYI(T^E^yewA=+>7Lye>)2W9@<*>_Gqv&EX`u?Jrta3}5$q*(=Fj>K7vcvq{ z``{E@M?IS5hOB?y84B^QD1P8EP2>elNK5=R%%rGn?`LKzBBQ*?lLD9OFOzd?LU_1j zQeAj5;%t7yL#09lVztfR(q*2yj2$})Lr5$$51)E^AvpwZ92U*<)>NUG%Zz2o449W^ zRc9=FElREoM3CjrwV74NupKDZo*-WGLeCPtNN=wy!p&}T?@6LmN|u}{x6id0lo3Lc zJl1XQ>z)=u5IQQ3-9Kkk6VuqFRLk6nxQuB^#`Yu*dKKWjYlnWoRTx3>F+I9N7#$g| za%^H`6x~s0KY{F6#3>-ghyw00V%W6dpn;>Pi2O0L@WnV8n7cD5p=0N)S$?I*#MLac z;h(FaWiqxeaGnp5$Q6okG=mzNb;*)?!u=kNqi8$m}Qwh%) zF%<23$A)C$LAq5%??3Tx_L%;jc_X3y5)un8v!blg8EMBRj9J>G)Yy{e)v#dIQ9j3aG<{+C`2AJlWceEK7|`U0s$sLBFd-Y+=EN zMtl;B2*o$ zpkt;|lGdlvOq~{bR7{mT^JQi#%1blVUGaG|=&a=~cnvxjy=jB;#hIzIE)5!^E$tR9 zmZC9|(9`^#Fo-XJ2yDZYzDu3?{C26Vx@%S|$yuc&r)?#9VnP4`6e9O>%57+x-KOzT zqUI_?zI!KlnL&1Ur}-M;Lg9Qv}PFhkGSB{yU#X@-Ix-Dc=Ck2IU7OY-;*Z3Wxw zKf2v3lrrLMt5=8-Ls=E11Fp`A3Mocig~g36u(^|@mAw?qae3TIwwy}Mm&Ey0M+!@J z7q>V?qDRu=AgnE}71$K_V#s=cXw*GI$=$B;ETMoO8*y?zEwN;hWI=ujR~?;tLJBx@ zkaa?t{20BoIWaSdhw3m%{@sXq4wv1o zJx4DG;*vClRf17qOC~y%Pl<9=Nt>i*C{(1YQjNC~VFNedL{<9)8r*MW<AaEwI zzpQOYK&T>kgVD6OLUcjE2A@f40rs&(kgD`nCXnjb)zLJ2WaE7^e9rXls%m zu}q^|%2gbkr+4BA$(9v^7EHQ8YsGP5Utxl@xFkP1P;VO?3F^c52u3YMziyte3Yg$v zpR0L7oH$kiee5(5f94W6O|>8fsDbHBRQuL6{KdaL(S!4Z|rCfNe#Ze=3LA7d-Ut=8R%?4(^4>#=~*X0?)muMeWamk=-(tTrVI{0k(uF5naw)&)S#0@6vi zTt+cLW;%iP%>;SGXee;5S_}O1kDJF2sEXM}KAM<(<0h=y)I5=v$On76_Dc1dMu=_{A+T%JYL`<35Qmnx?BKrq1VW;> znFp`g^)ODw^^z9#5TwBMj$_M#sCd-;*IP|iL&F2dFG}`bCt{8)7`AMPTUy@kGP$%B=8hD!v(HUYo3i6t=;fhKe=l$(o{JV_Q_ zq(X@X=s!nhTwV+zeiDgCF?p;n!J&ajS?VJHY`b}A+aj;&f(oN)%AQroyL+maDJ)}g2qJ^(7@=tb{ReNywC1E)l78eUU%!7Ewb&-zlJ5a%iZhNN~ z@rm^vESeT+T+L;qY^kehH(|!DX)p)6DI3`|sF28p8d$;8F;2&GgcxgjK!Rtsw` zoF+DG@H?w4Nq4k!j_%suuP8hnirziq!1bu$1R3M*mziugCYA+2xzHwa0~=E7!rp*tZVqT zG>vnyd}-CJ;~(iY=cq-wt2YIQ_ zgu9DymqBIvOV{WJdE&NtD(d%kq^RK>3j~oPPgFY11%hhK;y{TwR(ZmcRDS+6CjCG;3=KanT7>mj)-7g;o|2do^-2xO z&IZX9V6PqU><0CC)41WCI?AX?<264uYfsOj+gmiL%^g!R`V;-yy?V|e458PX1pgRD zRt9wxbi=zu07ut1UDHkq#9;_#!<0gr0hYi97RQ7NunvGL7)_>b2ws=A=^%f47H3|R0DyK!4VSYTa?Y*` zo+6fB$Bbfebv(}_z1rk(l8SsqoV?TNSW&{=>aJ%DFE*G1m+0906-}N zwk-*9A-Y^kHTeD^NE1{h3E)v(v;g=LEK-|bQ6ixxp+O}#K?4zX#nR~By;x#1JZQWQ zBs}xi%{JCIY-(5A+BbaIR`sD_+QJC+7E+6H;o?b|xKYWGt(&+-=6ZSg!1_&TvXy5{ z+#;h;taMa~>hdDe$crTeN_R=i=(?qTSHf1(gRQiolq+4G6R4OWRLl}8<_Hxy2&w$- zg6>=t1BfYnNeS#Mgp&{mg5gRO%G@z->R?$^WZFx34a&Zn3b%FmqUW7ufw=DuEXTSKR8?r4nlOy!B89REKvblWW&h0N#a`TO<@mSM(CTHj@1^8 zqf{%2X;E-}L+Ds3UA53gtY1Jt*|2lYro*^~sI2`C#dm z6^n(`G>n^o4h@rFcgtC=f@+WfQzClHy5(Au0An}(w_F?vu9I8^GYSS&EDaz`#IW^< z5lVf58zJg~@MYqy$nDs)pm8`OREc~9w1C|i3S=9b73&eS6V@JUaa<;bTVM_KVQh`v zs1fTuTxdd|CmfKMU+~-{{0VZJr8*1L=7MUOW&?a^n1VX>0*i`xASX*e$Kfu60B9_M z8gWUE3cqf13{WTA^cFo5}Pd2zsbJcN7fipAXKad*i! z5^Wn%^*CYoguB$5sDuW`xj0TGyilG+HsZEqxc?DS$e!u(b}B#^gyX>{TmH5{o!6 zKv1k>#$Dcn_TItDg~JsJ_l}HKF3hO#1~i-)B!6lihsYJG&zu?8tE6$k8t{|JFF3SL zem}8i>U}+Yb3R?Xh9R;@!4APm;wE;5X6)_n@s0a18XjA~vF(L*bJq?S6U0GZea%ia?t*!B`vU-MbdY~OV;IAuAyFhxoZez&ymm%Yg5WqFHU{`*~l{+~>)ERna`85r-E1{L@9V=Ar9= zA0bXPfbEnvD;_+!gTQKe}uBBx(58&9jOr2iDDV4}z&At0UtO|HC(% zr>n0UDg*#7O{8&K^M;}j{^#rFNmKxn5C%kr>U*GJA|a#^VYl2fPwgU3a<}^0dua8h z(S<9l;ENtaNnA)dt$yO3d0vyHX!Fqx(|AymC2!j>>mDKQB#kQMiYWB<4KwuUEn_r2 zlYwKlKeZOQ$v?MY9yoZzXm{S0$w%yV{y%DmR>?O>TF?ak>Q!c_nJz8Lhc}>@TZ>6t zVokO#E%kOp#wCN}+f8F6i?EbyWAv)*Ek$$uU9U33n;Nu2FR_*9UTYraQzi4rZknN5 z<4FpCt$CXNTFE@sbJOS$!JkKPqkJWs14fMgR4$&%W%Jh%p=LClgfwaucQApn#k2^X zkMWCO(J-T8ddQ+P_G+_6!}|{P@v2vwXB>E=%rX8)uQsb4Orw;a?|qGV$c<^y*3fIr zQ~C)1B2dsC>O~d%AUmSO?Q6{od!?o&a?$EyV)jImQ_u;ZahxwOYXCB=p#zu>CXhs6xsn?sws%{vPIH6F8$>5CxlcC|` zJpcMK6b1N4U++cH*IsX4av1#kPon>0&paQ4zx54fqb3be{0DC^4?A!~w!yoeG#~Yp zdGbl~xKjpw*7DCj>EHwH3_00o;xu3QNwZ2qPIU6zPn!D_OhxT)|D<`aY}gmyXdd-l z2OH*L0;MP%mHkeYEl5HcyV=2cId2emzb78-Yw;-}mUn)AT!KR<0&Ik5A+!}Rg*K5g!C{cU_Ow_pR>VH6fs%&wHQu z`G}cc-(6i@T~%FOMY|Kv($IA|&OaD&$gV9XiLAc)1~ysmn<|TEZooFs84@AO48GC2 zMP;ed8(E^2ipp}g-DnP1miqgRETzwIm)yk8)u$xOTz(T9A5+FIUc3nmHjxnxswl_A z^qbimV(HefW!b~#nF>3!8w2eQcX)sBO5t!Gn+oKN~LH(Q+81su@v>6TjoR}!`*=e6)wRy>lj)PRtg zC=T4pCg~tUeu@~d+X6CKWaVy9WsQB7MRxCIv#nhuia+l*MG3>GOjg%#A#yOoou2G66DPmrFXK)#(ae=bH$x3Zfu|1G9e59`c5_{ zz6x{>zPC!lL}KOc)Rluo&nDc(QqARGaTm)3=2zpwrQ4Klz#vH4=lU6l+f#SpXwjDX z{kzz5gEAs_(I^@QahejDknI$EP-!wwxKKLwKt;$+$gZ>W@BWNAM%y-9kA-n}v#dE) z$(k62JKjwJ@(m#07HDK%u_`WcYPu&;Tz5B%KUW?rpeRzI4~q|#_1@iV-kBv1+da&M zbAsf5IoRrZ*rGV&B-Z2;sR2-1bN{{A3SWKe89UA7+vrkCku$}c{`VeMF<_mjyN^vL z5iEQ!n>w#1d2uj6miGujfj^-7ApeUOT0ZriD9}a0*nZmN#P2%)?;1`N@7za1y&CA9 zdswE(*n_QO?Oc`_Auiivo;aZ|x63MS__P z)um^YVunEL2qEV_%1U@O_}DNwAfHCOa3V|KgX}54hZ<3l1W?D=`yXX?bJXF-fv^93 zl+6G=k6Ab|>oHx;BFof2#?H~siO6GYu0B|q_tayA3pzXdR^RM|$4Lg#vll$h?3UUY zX3pNnnM1)_w)(;$Hc{(w^b;)El2h{po1*1xe1dRWUjtR|iL8OC6yi|nTi`LgUOHqXe36T6-=fb#fLggllx#)%is3Y3BaI#<$o zfY~+BUVp&a%5w+Ud7RpeBrBQ{=j6Lk&D_@^Lpj4gNxhq$o zZWEMypVNUMtNracgQ^WXt^-5nlpHrUaNxKV7&7#$8;8Me~gj zOc$R#58axa#!s7uif`9^@4o9kuAM5SsAQT9SI- zSZEjxCA`EuI_oRPd(}%=QJwh|WjAGPZL=UdReXYPefOe;6(-7+zO18M@Rv}4-|{j$SF5=9Wu0uvoX=loq}q)4W3hcbS)b_sMB60R+X@s(IA;l7$y?1Z)?P^CEGjL>L}OxUO485 zi3DuS8qVCimrVNg?(sp!EO=m`4S=peX=B3D3beqZ<@;gM9^GuCi%vWQB6B&f$Lxa? zyz*T%83JieWKe(}ub8eikWlOOuJ0j(GuW%a*bHW;ReFT0CiwP(-3@Hi;2*TX+v(5b z+AD=B{dGaq+w3W`LoAe!7wq0<^(e^OoX_1kJSQ{Dos$7G9vJ&{FY(iU(L813R>5Ph zx|;)s(;4ZI*K$?08Cs(DnW=r2HZ|c0N2M&i5xN;gS{;-X+S_~L1mOzRa*m0q;%O8*>H5!8lP|<-GYnd&PGow z?9^3VbN*Ogm87Pd?2THQmzlCn3F-u+O?06KVswZt6>baT>K}UvMm;j_atrKf$N*GF zkX4bANCyl(U3?A4Od z6=7_XM?~IYZw%<{Ej-Dt8YmW=WJ3mY_5S*8_JQ`%(cAkT%O8Yo-us_Z?1BNb-`9P> z{KMo;d%H&*{eZnURDMKy@A?B9JV1SH5PLpk?x?ZtS~YVfMT~0jYAi!pDM|Y`H^Y}s zY>LA0C=l?`u0ar3f=IQ+LeSP(OO}7@)wEP5lTF#lED#N6$wzNg$TO4zP{Y&2$TBuB zGlGh4B0~C0vwkXgGC9hlRVpf{f z04&zT%$PD_$G8EP%N!tPN&xk=;Ablkx&yJ_q$H7gej;>;`%C<`U5H$W{KkcDmE|LPY?Pa z3Ijp(i$}N8UdXoD+iAniS7VFcXN!a>xu3cUXtY`;buoG*XBaRw#j$8>mliO#fOxf< z&6!hC?U2oPBKBCj-=|H}*jL&F4^^`j!!&0J0v~a38ltUIdJcexzAd1(B8L2}0yoK@ zw5!RcS5()q6ee-ZTqC0*g83K~DYUU%Y9f}X_(%;~^MfiPrl+)wbf!%zV*a3>ZhUs7E@DAOTA#nXjCf(Sj55qu(T;O97%~(uD0*1 z#aI7fGxB97?Pa6WYI1XuOeFhS0Zu%@QUma9MEyT;AWlW|NgfJ^ULO&j(0~>m$Pz12KuZBvfwOhG(Q? zL~gjv#J434QknzKbSH+5jjDoV3o>p#NdS+Hu zmRmb-sJIqXi|T?Y(whMu8k{J}LNUEL;W4#OJ5)8M(d?)kkENNdGUEyYFU^MRa7RL973RaRL}Yb`2QSv{vl$9Mq_%N;*n`orYk zFfXq*d!aqA^#Z3R`fH^s5+IRLtwfH>+A45_+9WKM1CXvrMkWCnLJh5P6fa7&cLd%R z5Cn)4wGe>uU`th3RFED9L&9i-S+@tpAi+WSG_e_PK!{X$eY%t_MVmF!-rnycM*(`4 z6xlS<$5V_NQB}p7u0od9NSRn`F($ja(gbj241oLV1wx?}?Apt9ER_OvRKdE6NC9^D z8Crl?+2-k%=B>)ORgoFP!j(SV4w%aXaQ6)ttx|?2QieDjZ7#i@$6IUpDp~@Ov@NQ{ zPGs3*@`z$MoJf*Wr?Rvk^=|-F%h5P5{j+EZ(PMnY3`HIqgqq$~PbCC>8kANb8tH(+ zwTc>_nVjkLwp#EUF-96Zl-7Tvpf+g8YD!xaUJ?m;17Q!5y$t0ALiUN+k7l3fas^&f z$W9v(t~nVIo6)cAP5$h^I~?{gZPX=7S_$_77-dHPb#QQc+r*xKvstqBj2>@5j*T84 zp)u09nRsmX2B3dvK286C_MIM1==XX3R4l((tHbf;bBt($tPa_ z0uSnuuDo#Q$_I(2P(9RJ%oL$i(@G&EBaRA5BR~aoDOIN#(7l0X^q6$25Z(dk2!Q}& zhQ{2sjXvo(adbyD@k)7eJOns* zxh8Zxb@)NH5pe^o9vMym~>#vJxaQfHif;C;tkN}=icqiRF_z&$TM|P9rd`Sva5bqX zv0Qa4EUpK)ZHx8QEDJ$C05LsdeKW3Hx0vr^=~1;1w5hy}N~XdroU$qNjCD9Gjl7;7 zDvXIMr+76W)--QzCAsVMLhach}RJ(&n45lQ?&uoDsj1&tpIQJniui< z2=nAA1e_z{TiF+)H2`SM4~AzYP9g$l3^X-Z8+p3#M!*@0CX2UzDq)2)apQCIO?SRF z;-k?xum<4K9Mq$dc({Sh9N1HB`aF!%S!V&Cg+Ew9o0g_sg_g=m$x#iK0L9f$#MRYN ztS1~ktI}gV|NlE%gId;6kzDd$zjyN47Mhi=%Ev*_Z&2^pWkhPjFPl(Lm zC6WBCGlH@uO7X0dw5m#^t#?lvS`ALf;S}lX-4mnw*ix9oS>A%{TlHwr5{8({Zi>UzHB;5|Q=| zo3q3SK^gIF4EpVJgK&tyCB@g$*giMemWY`aYJMT{05#GTA#?;ngO-Nj_FdaB(l?mc z`wh!Xkbb>DIHg>oqU*WpTLM1#BT}17eD@7vpvWY|!lZ2j2TqiQ1%tx``elTlY=B2x zaMBU*yAqg7r^~l=#6J6yth}Hzn^^+r;)Fw?1B}pYXt>1zT&;|D1{9iU~K@=yk z<}I@xZ+txs%B>`Rw|cw=p4u8VHo4(Xs)Jl>DlE2p)pj7+(u=jn&QL$`AXWbfU9Q;# zj;2oklI4rbs12H30}=ZYI6Txu3%q2GkXL2xB$msrVkYZpa7BrkllwdHi12+&t@u3M zk@j4QKn%`K)EDr{QHsXZljn173HUsXc)9HLtp$!S_oAhC>`i#M4$xFQ#+({8o-du^ zScCQQxwf9EL?)g;%MzVuDB(^^UV_-_)JXH0ituDNH1;%*7C8{Te|I)AoN%KH2#8a! zU?b65V>T=klgr6TN}}Ic_?^!1Je@wW@sZ8cZ^SxBA#ywzLg+N*mKmKy+KET&BzMvc z9yd?RQ&G+u@^x+oF-|rIpxRS@1-lz@!)8m%#UYa(&6Cd0@??@bBLjiewAO0zwO;d& z<^=sy<$V4oFQOY;iqTpj1iJ>{2Dm)KKPOTl#yav&?>F773-178@d7@6ve_xs@%d%c zU!Q1Oz^7SCcSP{UqXRzv2&)jtQJ<$j->lE{r~d$wIJAJzjK<_C&flNxcVYqICAtqb||0Y34>LT;O4mUd@bIu#$Ij0r{TFRU>9D0nyL`b7L9K7ESOoY7qd{Jr@^ z=^}1JAWsn7EVJbtLm^86nEHhCi9WB}nK%7UbRa>(~e|2a6_ z^rg^`7k@IeqmlpxJWb-&r95#>UO-YM9B5JLhf1E?COjVqPiO3hr$l!k_2t8FPVZ>=h~srQj7+E zq&em{<{OtHQHuSSxaS%-z$zS<5SPm z#2Ffkq0(?%x$- z{Jmjk#7o7zD#g~}$CDdaI^oEWJia!51w|=LHaJ|Zkw6&yxL8oa6NgB`B^pcko7&I3 zRs4FyM+TgLu)6d@cbZC62R;yj!pjrh(_-+tXkc8mX@1RcIHDM?8VPY!L~>(?syGJ? zDLON%A|3chbcQ_~gqh*wsK(}VHj2Vho|QlxF~l=+1nQzEMcCOP+B};BVTLDmxShSd zl+Od)UM%GW_)}2EXNbfyFiG-vMhR`-{u+?dAT=NXRNv?gN5UXv!N7%nGSczrsEL?Y zZHm@}J#FGx8BZFuC2b)s9nCHxR#x)3sQle7wW$#t*arE*c^}FTo1`G5TH53Z6CKh; ze08(ZnOj$9X*r14MHYA1Do-mw8J6X)8;bDls460>dFJRXY2=uK;Ezb0VnDTf0;~n~ zLr8AiP1=A8pv0z0bf7DE$J+MY1jx5eBBy8iFOZtiUah4@P6{+gEf3Y zR3g|0L)Mgljk8u%Z7$H0hzgYFZIZBnmota?sfh`f>)m zG!R?WRbH#>@dDlx>IphAJ2PFpQO?Iim3W6C;5dSm7|i#?4z&XZmr({9`_oJH5a~6W zb-3y~9BKpufl7m+Ly|%)lu7VGbVvF?9@a+9IqX-$@#LRc+rsGKL)U_6H*Q-c- zv(+Q5j}F}LgAc9S1{Iy7UVKo&Q#r7Hu*78%;<2A7_NEd?hx-(@Z|H_ z;HldMhgfp0+Yzp+E27wa>|coz*zLEi;2kq0;zTsWEBo!FH%d;BxPKNfeDIEGf-U1uapqji-oRl{^__uD+U2 z&X9mmnbKD~?F71{3}ZTS6X>bZ*Bkju16qi4s`yO3$=5IBiSeBpJ;VrMkft0|SCj8U z0Gdsb4gw&tpLBq9$V58m-M@sUh_B&e$K&Qm@y=LSwNXSt4WH3pdhiVCyK8tnc1YiL zT;34keQTZR9~28Nwm$%RMS1Bem)etormglcyx~igXn|6g!f7^kpH>Y=jIIX_>$Aty z1YgI;r`lSyot1#uXaX7z^oY$T?NU`tE<>rZK}6Q^MQXLW#AEAtM;)zXn?V?Zxa9@K z!TZ48sC!RZPeNraZXsi0nA}Xj62NKB@CJ|Vi(yC%9H&o04g7$BhV+USl8$8#OeD}+q-n^m#;gqD+bIpjVrLcj0kaB< zXV-Jb5;ZGrQX!g@F(*m6JR$AygCZ0E%!yQraz;6rkvcfrVAtOV!)cmGY>&#&5kZA9%2{W~I6rGcSUALpg#3Nl z0QJpppi$!w*qYerFqO|$f%a&W!Qr*g=Flb-85i)xT4If7%eVpx#X#j{0TaiNiJ|lL z^GT#pEy-;~Hu!`u{xBIEBKRb1C9Gw_;)VFpo7nYQYh()yb%ZZ3ULbXMtfe)J(vh;-Anr&sLK*05r z3pm_+YXe_BWDpK-@z-WPb8gv^<@JSl4OCpZY8jMpnQ}0C;*!#}i;96LhC_l7JT=Lf6;hJy5ydS$ zX<&m#)C=b%n~!u~lHSTYDxccY@{Xzz{6K?vsD;m53L(C+eWM41hp!D%;6k>=l>lHS zGb&&<8|*KarcPogKm`5M=m3vHNHeq~*+EFSIKwpc0K&RyoP{d8BEV-3Xb^z_U+Faa z`2KqD2KbC*SwUkW9kLE})lXmOT>Siid&X2v?hJXlF}h9!-ciB|^z^NKk~)@KXmN_r;8u4*&^Tj|CA2g$${z^M-Bw3%RNB_Hx+GID8wCzS@p*d zFUOzUFi#nePN30j1R*L!ur3c2I3gP6xj@*1_~Vp@xU@I4LW^-pxDkptD(EWlht#YP zH3;>lM|hrAFKX1&%OHG^{u97dK$e>hsj_F8Prt0$8gXk-JSN~>45e`qym{(Zcv@09 zT%D73r*=1Q6Jt8~^l`yHd!R)u=-?^CB{V1qF!VAL#ra))SdrY_GCiEE1bMnsrc_Xs zV&9ASFw; ze8va{H!qTJxwr;b3$2!cQeMP617~kdU}!_Fr{B|yJ1=vjpD!hkqs%?Of+ND8>`Ub;`3wNnhX_LZZdx!7~ z=U8`pGU;b21fHbF?G}0*06mu5wiE1&j?`@q@lhwAGqO?FuQbqdC5Z)He9n5g6vi@0 zv#kce%F{sM7h{$|yKJK?Hg(@tm#8BiS(cF!pSH3AV_&UL^mg&{qKzszk$S_2V+6>;{znj3Zd&S7jeELG%lo77jyCtM@{mH;ed4y@FgUluc zyllwk@92n-N?GTDd=9a0GoNz~tUc7B=U1%3|3^5^5p7%egtGv%`u_nieFiBtvDGWS z+9EIMDNhnq1Ce2PBficd2a7`!ogG6LsEFz1s z-NfO@>f){+^Z3-Wl~%o2P(IKk#s!>{N7HQuM%S%&*PT5e4IOoLWN&5CA7}-<`3PmJ zY|QWqPs;L=7%3ID-iT&OtOYk$s87WlskmiNac41CdImA{g@G-8pB?sXCP$C3U)p&# zA#9Tu5A|Iw@V4v?b|MZMDf8x^9abQaCafC12UauAS~{VJx9FW&Kt>?)3V_Hy41XRz4-qYo%s#aAwPz5?WuQA2fp5XB>efcuh+A_izu4XmtM5Pt1bytI$;I7avA> z(n`8L(Sqk9E%xZ5SIvsTW9yP4wM|-bNNuJB4CtLn+2yR`_^rAsu%irShiwp_|5nbnPQWu>KS zEZs~NFI@^&?cGcHatMYyE`w;Cga7HjJ1&#rCDK<92O2A^JarkLcn-d#$xT|W_m<01 z!7Jh~=W!KYZ1J)+rI3G1U~V8!cGv_dvoy-nynN_+I=o;lX>gdhdQe(HEheE?TyZ&H zIt`cx>n38S{T=!pHjSr&gyxxI&=q{#U^)TBomX&AiVj||_9QRKS0`Rq6CVF=hF09u zDbcJWNp?GpI*VIg8L?E%2KF6v~1@SlB^|N-i>6(0OhT}zaHij z4bmt$CXCL`q7q0-I&HRM-VVNSFzHo9Xa|oQlGp00U#@2V;0~!EIJkosjHBeGaQGlG z-P4-aDn?w%XAe_^G(n`{;~cMXS==M4ujI4hP$?3|@&9AoRq87N_!B^`y^=deNdS`a zpHhxo$=7q4LL-2&M*VVK{OSow>t2_C3)#PPdRk)+D)GG`62Tj$LiUJSqd^7W5>w0Y_H{7?R! z89nvY#k+Uz$Qb|0!96*>IeQAi$@r_xZrtjaQBF2b|xIx@qWZ*Jr$VxO3-u zDPxWezudX9=BBdCe?4p2U)wKv_xB&)E8cvn;Ns{{GEUxo*cnb3wDQ=4C$q|Kv0e9% zjvY^IzG?qITR(1jKEAx=%Yze|nm*1rvQJ!n%bss@#ytAwz~LWWSa;_ww*R<3e(b-a z4y7JDK7D%6&;Bs5aer=a`SEKeebGDd*`Zm5`Y8JUKSL z(*Dd}ULI9Ba&YGOA@8rd)|1ls#Qc*tJQx2-61%JQs@wDzb~(N`q`Z$zslWu`#FO@pYZn= zW>5X+HE*XqH*nQBQGKxB^Jp5wyYplSboJ9qX&!_obDc+Hu~g+54!CS z6b?Jce(xyldMf_;{A<5mc6HAi<3kTr-rM>6qxT*8wz~GaOe0Ox}{Z}2h z`k|4#vpehVj0}A`?bTPdgtIC>iF^^n<0EhE`qP0yPi)vZD7fcmZ=RDn zaM|szWH#LO*3`$BZ@2sQzVXzbicUUp{O*c--b(sz_1pV)*SzuB*56)w{o%>7g z;AJ-qyyl%RnoE0owm*8!>)p<4e{=kIH8Z0Re17eTU2O|5xuwzj=7MS2!}CUe;2+r0 z`R|{P_U$}a^XdE#ulb(?Z(j4=A94rmyW+i$Szn|s8b5c#zG3TKCwun)<}dpS?VoLU zbJx)q?@UTez5Cs7uG_cn-><)R^{IskuYPTR`wnkG-HAW^y{PAce{B5px2@A|n0kDv z_p|$6d++m>A6=OF*z_%b-sycBF2Ck~u>0X-*PXxeaP7JK#~j{r^^Ru?9$9m0-Mz<` zd{O+c^ZDHCzj~nK!<%>frfJoIp=34-Xek&e=QYp}7Mt{p;Fc d9ohA-NBqCL^!mM?f-9owlmGQ%IPPoqe*m!8pTYnD diff --git a/agents/meshcore.js b/agents/meshcore.js index 9d3cd688..d342653e 100644 --- a/agents/meshcore.js +++ b/agents/meshcore.js @@ -23,8 +23,7 @@ if (process.platform == 'win32' && require('user-sessions').getDomain == null) { }; } -// NOTE: This seems to cause big problems, don't enable the debugger in the server's meshcore. -//attachDebugger({ webport: 9999, wait: 1 }).then(function (prt) { console.log('Point Browser for Debug to port: ' + prt); }); +var promise = require('promise'); // Mesh Rights var MNG_ERROR = 65; @@ -1043,6 +1042,35 @@ function server_check_consentTimer(id) { return false; } +function tunnel_finalized() +{ + console.info1('Tunnel Request Finalized'); +} +function tunnel_checkServerIdentity(certs) +{ + /* + try { sendConsoleText("certs[0].digest: " + certs[0].digest); } catch (ex) { sendConsoleText(ex); } + try { sendConsoleText("certs[0].fingerprint: " + certs[0].fingerprint); } catch (ex) { sendConsoleText(ex); } + try { sendConsoleText("control-digest: " + require('MeshAgent').ServerInfo.ControlChannelCertificate.digest); } catch (ex) { sendConsoleText(ex); } + try { sendConsoleText("control-fingerprint: " + require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint); } catch (ex) { sendConsoleText(ex); } + */ + + // Check if this is an old agent, no certificate checks are possible in this situation. Display a warning. + if ((require('MeshAgent').ServerInfo == null) || (require('MeshAgent').ServerInfo.ControlChannelCertificate == null) || (certs[0].digest == null)) { sendAgentMessage("This agent is using insecure tunnels, consider updating.", 3, 119, true); return; } + + // If the tunnel certificate matches the control channel certificate, accept the connection + if (require('MeshAgent').ServerInfo.ControlChannelCertificate.digest == certs[0].digest) return; // Control channel certificate matches using full cert hash + if ((certs[0].fingerprint != null) && (require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint == certs[0].fingerprint)) return; // Control channel certificate matches using public key hash + + // Check that the certificate is the one expected by the server, fail if not. + if ((tunnel_checkServerIdentity.servertlshash != null) && (tunnel_checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase())) { throw new Error('BadCert') } +} + +function tunnel_onError() +{ + sendConsoleText("ERROR: Unable to connect relay tunnel to: " + this.url + ", " + JSON.stringify(e)); +} + // Handle a mesh agent command function handleServerCommand(data) { if (typeof data == 'object') { @@ -1062,7 +1090,8 @@ function handleServerCommand(data) { } break; } - case 'tunnel': { + case 'tunnel': + { if (data.value != null) { // Process a new tunnel connection request // Create a new tunnel object var xurl = getServerTargetUrlEx(data.value); @@ -1074,31 +1103,15 @@ function handleServerCommand(data) { // Perform manual server TLS certificate checking based on the certificate hash given by the server. woptions.rejectUnauthorized = 0; - woptions.checkServerIdentity = function checkServerIdentity(certs) { - /* - try { sendConsoleText("certs[0].digest: " + certs[0].digest); } catch (ex) { sendConsoleText(ex); } - try { sendConsoleText("certs[0].fingerprint: " + certs[0].fingerprint); } catch (ex) { sendConsoleText(ex); } - try { sendConsoleText("control-digest: " + require('MeshAgent').ServerInfo.ControlChannelCertificate.digest); } catch (ex) { sendConsoleText(ex); } - try { sendConsoleText("control-fingerprint: " + require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint); } catch (ex) { sendConsoleText(ex); } - */ - - // Check if this is an old agent, no certificate checks are possible in this situation. Display a warning. - if ((require('MeshAgent').ServerInfo == null) || (require('MeshAgent').ServerInfo.ControlChannelCertificate == null) || (certs[0].digest == null)) { sendAgentMessage("This agent is using insecure tunnels, consider updating.", 3, 119, true); return; } - - // If the tunnel certificate matches the control channel certificate, accept the connection - if (require('MeshAgent').ServerInfo.ControlChannelCertificate.digest == certs[0].digest) return; // Control channel certificate matches using full cert hash - if ((certs[0].fingerprint != null) && (require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint == certs[0].fingerprint)) return; // Control channel certificate matches using public key hash - - // Check that the certificate is the one expected by the server, fail if not. - if ((checkServerIdentity.servertlshash != null) && (checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase())) { throw new Error('BadCert') } - } + woptions.checkServerIdentity = tunnel_checkServerIdentity; woptions.checkServerIdentity.servertlshash = data.servertlshash; //sendConsoleText(JSON.stringify(woptions)); //sendConsoleText('TUNNEL: ' + JSON.stringify(data, null, 2)); + var tunnel = http.request(woptions); tunnel.upgrade = onTunnelUpgrade; - tunnel.on('error', function (e) { sendConsoleText("ERROR: Unable to connect relay tunnel to: " + this.url + ", " + JSON.stringify(e)); }); + tunnel.on('error', tunnel_onError); tunnel.sessionid = data.sessionid; tunnel.rights = data.rights; tunnel.consent = data.consent; @@ -1122,11 +1135,13 @@ function handleServerCommand(data) { tunnel.tcpport = data.tcpport; tunnel.udpaddr = data.udpaddr; tunnel.udpport = data.udpport; - tunnel.end(); + // Put the tunnel in the tunnels list var index = nextTunnelIndex++; tunnel.index = index; tunnels[index] = tunnel; + tunnel.once('~', tunnel_finalized); + tunnel.end(); //sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid); } @@ -1355,7 +1370,9 @@ function handleServerCommand(data) { this._dispatcher.on('connection', function (c) { this._c = c; this._c.root = this.parent; - this._c.on('end', function () { + this._c.on('end', function () + { + this.root._dispatcher.close(); this.root._dispatcher = null; this.root = null; mesh.SendCommand({ action: 'msg', type: 'setclip', sessionid: data.sessionid, success: true }); @@ -1840,20 +1857,34 @@ function getDirectoryInfo(reqpath) { return response; } +function tunnel_s_finalized() +{ + console.info1('Tunnel Socket Finalized'); +} + + +function tunnel_onIdleTimeout() +{ + this.ping(); + this.setTimeout(require('MeshAgent').idleTimeout * 1000); +} + // Tunnel callback operations -function onTunnelUpgrade(response, s, head) { +function onTunnelUpgrade(response, s, head) +{ + this.s = s; + s.once('~', tunnel_s_finalized); s.httprequest = this; s.end = onTunnelClosed; s.tunnel = this; s.descriptorMetadata = "MeshAgent_relayTunnel"; - if (require('MeshAgent').idleTimeout != null) { + + if (require('MeshAgent').idleTimeout != null) + { s.setTimeout(require('MeshAgent').idleTimeout * 1000); - s.on('timeout', function () { - this.ping(); - this.setTimeout(require('MeshAgent').idleTimeout * 1000); - }); + s.on('timeout', tunnel_onIdleTimeout); } //sendConsoleText('onTunnelUpgrade - ' + this.tcpport + ' - ' + this.udpport); @@ -1937,7 +1968,25 @@ function onTcpRelayServerTunnelData(data) { } } -function onTunnelClosed() { +function onTunnelClosed() +{ + if (this.httprequest._dispatcher != null && this.httprequest.term == null) + { + // Windows Dispatcher was created to spawn a child connection, but the child didn't connect yet, so we have to shutdown the dispatcher, otherwise the child may end up hanging + if (this.httprequest._dispatcher.close) { this.httprequest._dispatcher.close(); } + this.httprequest._dispatcher = null; + } + + if (this.tunnel) + { + if (tunnels[this.httprequest.index] == null) + { + this.tunnel.s = null; + this.tunnel = null; + return; + } + } + var tunnel = tunnels[this.httprequest.index]; if (tunnel == null) return; // Stop duplicate calls. @@ -1987,7 +2036,7 @@ function onTunnelClosed() { } catch (ex) { } //sendConsoleText("Tunnel #" + this.httprequest.index + " closed. Sent -> " + this.bytesSent_uncompressed + ' bytes (uncompressed), ' + this.bytesSent_actual + ' bytes (actual), ' + this.bytesSent_ratio + '% compression', this.httprequest.sessionid); - if (this.httprequest.index) { delete tunnels[this.httprequest.index]; } + /* // Close the watcher if required @@ -2014,11 +2063,523 @@ function onTunnelClosed() { } // Clean up WebSocket + delete tunnels[this.httprequest.index]; + tunnel = null; + this.tunnel.s = null; + this.tunnel = null; this.removeAllListeners('data'); } function onTunnelSendOk() { /*sendConsoleText("Tunnel #" + this.index + " SendOK.", this.sessionid);*/ } -function onTunnelData(data) { - //console.log("OnTunnelData"); + +function terminal_onconnection (c) +{ + if (this.httprequest.connectionPromise.completed) + { + c.end(); + } + else + { + this.httprequest.connectionPromise._res(c); + } +} +function terminal_user_onconnection(c) +{ + console.info1('completed-2: ' + this.connectionPromise.completed); + + if (this.connectionPromise.completed) + { + c.end(); + } + else + { + this.connectionPromise._res(c); + } +} +function terminal_stderr_ondata(c) +{ + this.stdout.write(c); +} +function terminal_onend() +{ + this.httprequest.process.kill(); +} + +function terminal_onexit() +{ + this.tunnel.end(); +} +function terminal_onfinalized() +{ + this.httprequest = null; + console.info1('Dispatcher Finalized'); +} +function terminal_end() +{ + if (this.httprequest == null) { return; } + if (this.httprequest.tpromise._consent) { this.httprequest.tpromise._consent.close(); } + if (this.httprequest.connectionPromise) { this.httprequest.connectionPromise._rej('Closed'); } + + // Remove the terminal session to the count to update the server + if (this.httprequest.userid != null) + { + var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest); + if (tunnelUserCount.terminal[userid] != null) { tunnelUserCount.terminal[userid]--; if (tunnelUserCount.terminal[userid] <= 0) { delete tunnelUserCount.terminal[userid]; } } + try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (ex) { } + broadcastSessionsToRegisteredApps(); + } + + if (process.platform == 'win32') + { + // Unpipe the web socket + this.unpipe(this.httprequest._term); + if (this.httprequest._term) { this.httprequest._term.unpipe(this); } + + // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends). + if (this.rtcchannel) + { + this.rtcchannel.unpipe(this.httprequest._term); + if (this.httprequest._term) { this.httprequest._term.unpipe(this.rtcchannel); } + } + + // Clean up + if (this.httprequest._term) { this.httprequest._term.end(); } + this.httprequest._term = null; + this.httprequest._dispatcher = null; + } + + this.httprequest = null; + +} + +function terminal_promise_connection_rejected(e) +{ + // FAILED to connect terminal + this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 })); + this.ws.end(); +} + +function terminal_promise_connection_resolved(term) +{ + this._internal.completedArgs = []; + + // SUCCESS + var stdoutstream; + var stdinstream; + if (process.platform == 'win32') + { + this.ws.httprequest._term = term; + this.ws.httprequest._term.tunnel = this.ws; + stdoutstream = stdinstream = term; + } + else + { + term.descriptorMetadata = 'Remote Terminal'; + this.ws.httprequest.process = term; + this.ws.httprequest.process.tunnel = this.ws; + term.stderr.stdout = term.stdout; + term.stderr.on('data', terminal_stderr_ondata); + stdoutstream = term.stdout; + stdinstream = term.stdin; + this.ws.prependListener('end', terminal_onend); + term.prependListener('exit', terminal_onexit); + } + + this.ws.removeAllListeners('data'); + this.ws.on('data', onTunnelControlData); + + stdoutstream.pipe(this.ws, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. + this.ws.pipe(stdinstream, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. + + // Add the terminal session to the count to update the server + if (this.ws.httprequest.userid != null) + { + var userid = getUserIdAndGuestNameFromHttpRequest(this.ws.httprequest); + if (tunnelUserCount.terminal[userid] == null) { tunnelUserCount.terminal[userid] = 1; } else { tunnelUserCount.terminal[userid]++; } + try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (ex) { } + broadcastSessionsToRegisteredApps(); + } + + // Toast Notification, if required + if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 2)) + { + // User Notifications is required + var notifyMessage = currentTranslation['terminalNotify'].replace('{0}', this.ws.httprequest.username); + var notifyTitle = "MeshCentral"; + if (this.ws.httprequest.soptions != null) + { + if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; } + if (this.ws.httprequest.soptions.notifyMsgTerminal != null) { notifyMessage = this.ws.httprequest.soptions.notifyMsgTerminal.replace('{0}', this.ws.httprequest.realname).replace('{1}', this.ws.httprequest.username); } + } + try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (ex) { } + } + this.ws = null; +} +function terminal_promise_consent_rejected(e) +{ + // DO NOT start terminal + this.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 })); + this.that.end(); + + this.that = null; + this.httprequest = null; +} +function promise_init(res, rej) { this._res = res; this._rej = rej; } +function terminal_userpromise_resolved(u) +{ + + var that = this.that; + if (u.Active.length > 0) + { + var tmp; + var username = '"' + u.Active[0].Domain + '\\' + u.Active[0].Username + '"'; + + + if (require('win-virtual-terminal').supported) + { + // ConPTY PseudoTerminal + tmp = require('win-dispatcher').dispatch({ user: username, modules: [{ name: 'win-virtual-terminal', script: getJSModule('win-virtual-terminal') }], launch: { module: 'win-virtual-terminal', method: (that.httprequest.protocol == 9 ? 'StartPowerShell' : 'Start'), args: [this.cols, this.rows] } }); + } + else + { + // Legacy Terminal + tmp = require('win-dispatcher').dispatch({ user: username, modules: [{ name: 'win-terminal', script: getJSModule('win-terminal') }], launch: { module: 'win-terminal', method: (that.httprequest.protocol == 9 ? 'StartPowerShell' : 'Start'), args: [this.cols, this.rows] } }); + } + that.httprequest._dispatcher = tmp; + that.httprequest._dispatcher.connectionPromise = that.httprequest.connectionPromise; + that.httprequest._dispatcher.on('connection', terminal_user_onconnection); + that.httprequest._dispatcher.on('~', terminal_onfinalized); + } + this.that = null; + that = null; +} + +function terminal_promise_consent_resolved() +{ + this.httprequest.connectionPromise = new promise(promise_init); + this.httprequest.connectionPromise.ws = this.that; + + // Start Terminal + if (process.platform == 'win32') + { + try + { + var cols = 80, rows = 25; + if (this.httprequest.xoptions) + { + if (this.httprequest.xoptions.rows) { rows = this.httprequest.xoptions.rows; } + if (this.httprequest.xoptions.cols) { cols = this.httprequest.xoptions.cols; } + } + + if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6)) + { + // Admin Terminal + if (require('win-virtual-terminal').supported) + { + // ConPTY PseudoTerminal + // this.httprequest._term = require('win-virtual-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](80, 25); + + // The above line is commented out, because there is a bug with ClosePseudoConsole() API, so this is the workaround + this.httprequest._dispatcher = require('win-dispatcher').dispatch({ modules: [{ name: 'win-virtual-terminal', script: getJSModule('win-virtual-terminal') }], launch: { module: 'win-virtual-terminal', method: (this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'), args: [cols, rows] } }); + this.httprequest._dispatcher.httprequest = this.httprequest; + this.httprequest._dispatcher.on('connection', terminal_onconnection); + this.httprequest._dispatcher.on('~', terminal_onfinalized); + } + else + { + // Legacy Terminal + this.httprequest.connectionPromise._res(require('win-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](cols, rows)); + } + } + else + { + // Logged in user + var userPromise = require('user-sessions').enumerateUsers(); + userPromise.that = this; + userPromise.cols = cols; + userPromise.rows = rows; + userPromise.then(terminal_userpromise_resolved); + } + } catch (ex) + { + this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + ex.toString()); + } + } + else + { + try + { + var bash = fs.existsSync('/bin/bash') ? '/bin/bash' : false; + var sh = fs.existsSync('/bin/sh') ? '/bin/sh' : false; + var login = process.platform == 'linux' ? '/bin/login' : '/usr/bin/login'; + + var env = { HISTCONTROL: 'ignoreboth' }; + if (process.env['LANG']) { env['LANG'] = process.env['LANG']; } + if (process.env['PATH']) { env['PATH'] = process.env['PATH']; } + if (this.httprequest.xoptions) + { + if (this.httprequest.xoptions.rows) { env.LINES = ('' + this.httprequest.xoptions.rows); } + if (this.httprequest.xoptions.cols) { env.COLUMNS = ('' + this.httprequest.xoptions.cols); } + } + var options = { type: childProcess.SpawnTypes.TERM, uid: (this.httprequest.protocol == 8) ? require('user-sessions').consoleUid() : null, env: env }; + if (this.httprequest.xoptions && this.httprequest.xoptions.requireLogin) + { + if (!require('fs').existsSync(login)) { throw ('Unable to spawn login process'); } + this.httprequest.connectionPromise._res(childProcess.execFile(login, ['login'], options)); // Start login shell + } + else if (bash) + { + var p = childProcess.execFile(bash, ['bash'], options); // Start bash + // Spaces at the beginning of lines are needed to hide commands from the command history + if ((obj.serverInfo.termlaunchcommand != null) && (typeof obj.serverInfo.termlaunchcommand[process.platform] == 'string')) + { + if (obj.serverInfo.termlaunchcommand[process.platform] != '') { p.stdin.write(obj.serverInfo.termlaunchcommand[process.platform]); } + } else if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); } + this.httprequest.connectionPromise._res(p); + } + else if (sh) + { + var p = childProcess.execFile(sh, ['sh'], options); // Start sh + // Spaces at the beginning of lines are needed to hide commands from the command history + if ((obj.serverInfo.termlaunchcommand != null) && (typeof obj.serverInfo.termlaunchcommand[process.platform] == 'string')) + { + if (obj.serverInfo.termlaunchcommand[process.platform] != '') { p.stdin.write(obj.serverInfo.termlaunchcommand[process.platform]); } + } else if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); } + this.httprequest.connectionPromise._res(p); + } + else + { + this.httprequest.connectionPromise._rej('Failed to start remote terminal session, no shell found'); + } + } catch (ex) + { + this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + ex.toString()); + } + } + + this.httprequest.connectionPromise.then(terminal_promise_connection_resolved, terminal_promise_connection_rejected); + this.that = null; + this.httprequest = null; +} +function tunnel_kvm_end() +{ + --this.desktop.kvm.connectionCount; + + // Remove ourself from the list of remote desktop session + var i = this.desktop.kvm.tunnels.indexOf(this); + if (i >= 0) { this.desktop.kvm.tunnels.splice(i, 1); } + + // Send a metadata update to all desktop sessions + var users = {}; + if (this.httprequest.desktop.kvm.tunnels != null) + { + for (var i in this.httprequest.desktop.kvm.tunnels) + { + try + { + var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest.desktop.kvm.tunnels[i].httprequest); + if (users[userid] == null) { users[userid] = 1; } else { users[userid]++; } + } catch (ex) { sendConsoleText(ex); } + } + for (var i in this.httprequest.desktop.kvm.tunnels) + { + try { this.httprequest.desktop.kvm.tunnels[i].write(JSON.stringify({ ctrlChannel: '102938', type: 'metadata', users: users })); } catch (ex) { } + } + tunnelUserCount.desktop = users; + try { mesh.SendCommand({ action: 'sessions', type: 'kvm', value: users }); } catch (ex) { } + broadcastSessionsToRegisteredApps(); + } + + // Unpipe the web socket + try + { + this.unpipe(this.httprequest.desktop.kvm); + this.httprequest.desktop.kvm.unpipe(this); + } catch (ex) { } + + // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends). + if (this.rtcchannel) + { + try + { + this.rtcchannel.unpipe(this.httprequest.desktop.kvm); + this.httprequest.desktop.kvm.unpipe(this.rtcchannel); + } + catch (ex) { } + } + + // Place wallpaper back if needed + // TODO + + if (this.desktop.kvm.connectionCount == 0) + { + // Display a toast message. This may not be supported on all platforms. + // try { require('toaster').Toast('MeshCentral', 'Remote Desktop Control Ended.'); } catch (ex) { } + + this.httprequest.desktop.kvm.end(); + if (this.httprequest.desktop.kvm.connectionBar) + { + this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close'); + this.httprequest.desktop.kvm.connectionBar.close(); + this.httprequest.desktop.kvm.connectionBar = null; + } + } else + { + for (var i in this.httprequest.desktop.kvm.users) + { + if ((this.httprequest.desktop.kvm.users[i] == this.httprequest.username) && this.httprequest.desktop.kvm.connectionBar) + { + for (var j in this.httprequest.desktop.kvm.rusers) { if (this.httprequest.desktop.kvm.rusers[j] == this.httprequest.realname) { this.httprequest.desktop.kvm.rusers.splice(j, 1); break; } } + this.httprequest.desktop.kvm.users.splice(i, 1); + this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close'); + this.httprequest.desktop.kvm.connectionBar.close(); + this.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.httprequest.privacybartext.replace('{0}', this.httprequest.desktop.kvm.rusers.join(', ')).replace('{1}', this.httprequest.desktop.kvm.users.join(', ')), require('MeshAgent')._tsid, color_options); + this.httprequest.desktop.kvm.connectionBar.httprequest = this.httprequest; + this.httprequest.desktop.kvm.connectionBar.on('close', function () + { + MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest); + for (var i in this.httprequest.desktop.kvm._pipedStreams) + { + this.httprequest.desktop.kvm._pipedStreams[i].end(); + } + this.httprequest.desktop.kvm.end(); + }); + break; + } + } + } + + if(this.httprequest.desktop.kvm.connectionBar) + { + console.info1('Setting ConnectionBar request to NULL'); + this.httprequest.desktop.kvm.connectionBar.httprequest = null; + } + + this.httprequest = null; + this.desktop.tunnel = null; +} + +function kvm_tunnel_consentpromise_closehandler() +{ + if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); } +} + +function kvm_consentpromise_rejected(e) +{ + // User Consent Denied/Failed + this.ws._consentpromise = null; + MeshServerLogEx(34, null, "Failed to start remote desktop after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); + this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 })); + this.ws = null; +} +function kvm_consentpromise_resolved(always) +{ + if (always && process.platform=='win32') { server_set_consentTimer(this.ws.httprequest.userid); } + + // Success + this.ws._consentpromise = null; + MeshServerLogEx(30, null, "Starting remote desktop after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); + this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 })); + if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 1)) + { + // User Notifications is required + var notifyMessage = currentTranslation['desktopNotify'].replace('{0}', this.ws.httprequest.realname); + var notifyTitle = "MeshCentral"; + if (this.ws.httprequest.soptions != null) + { + if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; } + if (this.ws.httprequest.soptions.notifyMsgDesktop != null) { notifyMessage = this.ws.httprequest.soptions.notifyMsgDesktop.replace('{0}', this.ws.httprequest.realname).replace('{1}', this.ws.httprequest.username); } + } + try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (ex) { } + } + if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 0x40)) + { + // Connection Bar is required + if (this.ws.httprequest.desktop.kvm.connectionBar) + { + this.ws.httprequest.desktop.kvm.connectionBar.removeAllListeners('close'); + this.ws.httprequest.desktop.kvm.connectionBar.close(); + } + try + { + this.ws.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.ws.httprequest.privacybartext.replace('{0}', this.ws.httprequest.desktop.kvm.rusers.join(', ')).replace('{1}', this.ws.httprequest.desktop.kvm.users.join(', ')), require('MeshAgent')._tsid, color_options); + MeshServerLogEx(31, null, "Remote Desktop Connection Bar Activated/Updated (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); + } catch (ex) + { + if (process.platform != 'darwin') + { + MeshServerLogEx(32, null, "Remote Desktop Connection Bar Failed or Not Supported (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); + } + } + if (this.ws.httprequest.desktop.kvm.connectionBar) + { + this.ws.httprequest.desktop.kvm.connectionBar.state = + { + userid: this.ws.httprequest.userid, + xuserid: this.ws.httprequest.xuserid, + username: this.ws.httprequest.username, + sessionid: this.ws.httprequest.sessionid, + remoteaddr: this.ws.httprequest.remoteaddr, + guestname: this.ws.httprequest.guestname, + desktop: this.ws.httprequest.desktop + }; + this.ws.httprequest.desktop.kvm.connectionBar.on('close', function () + { + MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.state.remoteaddr + ")", state); + for (var i in this.state.desktop.kvm._pipedStreams) + { + this.state.desktop.kvm._pipedStreams[i].end(); + } + this.state.desktop.kvm.end(); + }); + } + } + this.ws.httprequest.desktop.kvm.pipe(this.ws, { dataTypeSkip: 1 }); + if (this.ws.httprequest.autolock) + { + destopLockHelper_pipe(this.ws.httprequest); + } + this.ws.resume(); + this.ws = null; +} + +function files_consentpromise_resolved(always) +{ + if (always && process.platform == 'win32') { server_set_consentTimer(this.ws.httprequest.userid); } + + // Success + this.ws._consentpromise = null; + MeshServerLogEx(40, null, "Starting remote files after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); + this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null })); + if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 4)) + { + // User Notifications is required + var notifyMessage = currentTranslation['fileNotify'].replace('{0}', this.ws.httprequest.realname); + var notifyTitle = "MeshCentral"; + if (this.ws.httprequest.soptions != null) + { + if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; } + if (this.ws.httprequest.soptions.notifyMsgFiles != null) { notifyMessage = this.ws.httprequest.soptions.notifyMsgFiles.replace('{0}', this.ws.httprequest.realname).replace('{1}', this.ws.httprequest.username); } + } + try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (ex) { } + } + this.ws.resume(); + this.ws = null; +} +function files_consentpromise_rejected(e) +{ + // User Consent Denied/Failed + this.ws._consentpromise = null; + MeshServerLogEx(41, null, "Failed to start remote files after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); + this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 })); + this.ws = null; +} +function files_tunnel_endhandler() +{ + if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); } +} + +function onTunnelData(data) +{ //sendConsoleText('OnTunnelData, ' + data.length + ', ' + typeof data + ', ' + data); // If this is upload data, save it to file @@ -2080,7 +2641,8 @@ function onTunnelData(data) { // // Check user access rights for terminal - if (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NOTERMINAL) != 0))) { + if (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NOTERMINAL) != 0))) + { // Disengage this tunnel, user does not have the rights to do this!! this.httprequest.protocol = 999999; this.httprequest.s.end(); @@ -2090,7 +2652,8 @@ function onTunnelData(data) { this.descriptorMetadata = "Remote Terminal"; - if (process.platform == 'win32') { + if (process.platform == 'win32') + { if (!require('win-terminal').PowerShellCapable() && (this.httprequest.protocol == 6 || this.httprequest.protocol == 9)) { this.httprequest.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'PowerShell is not supported on this version of windows', msgid: 1 })); this.httprequest.s.end(); @@ -2099,53 +2662,29 @@ function onTunnelData(data) { } var prom = require('promise'); - this.httprequest.tpromise = new prom(function (res, rej) { this._res = res; this._rej = rej; }); + this.httprequest.tpromise = new prom(promise_init); this.httprequest.tpromise.that = this; this.httprequest.tpromise.httprequest = this.httprequest; - - this.end = function () { - if (this.httprequest.tpromise._consent) { this.httprequest.tpromise._consent.close(); } - if (this.httprequest.connectionPromise) { this.httprequest.connectionPromise._rej('Closed'); } - - // Remove the terminal session to the count to update the server - if (this.httprequest.userid != null) { - var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest); - if (tunnelUserCount.terminal[userid] != null) { tunnelUserCount.terminal[userid]--; if (tunnelUserCount.terminal[userid] <= 0) { delete tunnelUserCount.terminal[userid]; } } - try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (ex) { } - broadcastSessionsToRegisteredApps(); - } - - if (process.platform == 'win32') { - // Unpipe the web socket - this.unpipe(this.httprequest._term); - if (this.httprequest._term) { this.httprequest._term.unpipe(this); } - - // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends). - if (this.rtcchannel) { - this.rtcchannel.unpipe(this.httprequest._term); - if (this.httprequest._term) { this.httprequest._term.unpipe(this.rtcchannel); } - } - - // Clean up - if (this.httprequest._term) { this.httprequest._term.end(); } - this.httprequest._term = null; - } - }; + this.end = terminal_end; // Perform User-Consent if needed. - if (this.httprequest.consent && (this.httprequest.consent & 16)) { + if (this.httprequest.consent && (this.httprequest.consent & 16)) + { this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 })); var consentMessage = currentTranslation['terminalConsent'].replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); var consentTitle = 'MeshCentral'; - if (this.httprequest.soptions != null) { + if (this.httprequest.soptions != null) + { if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; } if (this.httprequest.soptions.consentMsgTerminal != null) { consentMessage = this.httprequest.soptions.consentMsgTerminal.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); } } - if (process.platform == 'win32') { + if (process.platform == 'win32') + { var enhanced = false; try { require('win-userconsent'); enhanced = true; } catch (ex) { } - if (enhanced) { + if (enhanced) + { var ipr = server_getUserImage(this.httprequest.userid); ipr.consentTitle = consentTitle; ipr.consentMessage = consentMessage; @@ -2153,21 +2692,25 @@ function onTunnelData(data) { ipr.consentAutoAccept = this.httprequest.consentAutoAccept; ipr.username = this.httprequest.realname; ipr.translations = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage }; - this.httprequest.tpromise._consent = ipr.then(function (img) { + this.httprequest.tpromise._consent = ipr.then(function (img) + { this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translations, background: color_options.background, foreground: color_options.foreground }); this.__childPromise.close = this.consent.close.bind(this.consent); return (this.consent); }); - } else { - this.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, this.consentTimeout); + } else + { + this.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout); } - } else { - this.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, this.consentTimeout); + } else + { + this.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout); } this.httprequest.tpromise._consent.retPromise = this.httprequest.tpromise; this.httprequest.tpromise._consent.then( - function (always) { - if (always) { server_set_consentTimer(this.retPromise.httprequest.userid); } + function (always) + { + if (always && process.platform == 'win32') { server_set_consentTimer(this.retPromise.httprequest.userid); } // Success MeshServerLogEx(27, null, "Local user accepted remote terminal request (" + this.retPromise.httprequest.remoteaddr + ")", this.retPromise.that.httprequest); @@ -2179,174 +2722,21 @@ function onTunnelData(data) { // Denied MeshServerLogEx(28, null, "Local user rejected remote terminal request (" + this.retPromise.that.httprequest.remoteaddr + ")", this.retPromise.that.httprequest); this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 })); + this.retPromise._consent = null; this.retPromise._rej(e.toString()); }); } - else { + else + { // User-Consent is not required, so just resolve this promise this.httprequest.tpromise._res(); } - this.httprequest.tpromise.then( - function () { - this.httprequest.connectionPromise = new prom(function (res, rej) { this._res = res; this._rej = rej; }); - this.httprequest.connectionPromise.ws = this.that; - - // Start Terminal - if (process.platform == 'win32') { - try { - var cols = 80, rows = 25; - if (this.httprequest.xoptions) { - if (this.httprequest.xoptions.rows) { rows = this.httprequest.xoptions.rows; } - if (this.httprequest.xoptions.cols) { cols = this.httprequest.xoptions.cols; } - } - - if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6)) { - // Admin Terminal - if (require('win-virtual-terminal').supported) { - // ConPTY PseudoTerminal - // this.httprequest._term = require('win-virtual-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](80, 25); - - // The above line is commented out, because there is a bug with ClosePseudoConsole() API, so this is the workaround - this.httprequest._dispatcher = require('win-dispatcher').dispatch({ modules: [{ name: 'win-virtual-terminal', script: getJSModule('win-virtual-terminal') }], launch: { module: 'win-virtual-terminal', method: (this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'), args: [cols, rows] } }); - this.httprequest._dispatcher.httprequest = this.httprequest; - this.httprequest._dispatcher.on('connection', function (c) { if (this.httprequest.connectionPromise.completed) { c.end(); } else { this.httprequest.connectionPromise._res(c); } }); - } - else { - // Legacy Terminal - this.httprequest.connectionPromise._res(require('win-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](cols, rows)); - } - } - else { - // Logged in user - var userPromise = require('user-sessions').enumerateUsers(); - userPromise.that = this; - userPromise.then(function (u) { - var that = this.that; - if (u.Active.length > 0) { - var username = '"' + u.Active[0].Domain + '\\' + u.Active[0].Username + '"'; - //sendConsoleText('Terminal: ' + username); - if (require('win-virtual-terminal').supported) { - // ConPTY PseudoTerminal - that.httprequest._dispatcher = require('win-dispatcher').dispatch({ user: username, modules: [{ name: 'win-virtual-terminal', script: getJSModule('win-virtual-terminal') }], launch: { module: 'win-virtual-terminal', method: (that.httprequest.protocol == 9 ? 'StartPowerShell' : 'Start'), args: [cols, rows] } }); - } - else { - // Legacy Terminal - that.httprequest._dispatcher = require('win-dispatcher').dispatch({ user: username, modules: [{ name: 'win-terminal', script: getJSModule('win-terminal') }], launch: { module: 'win-terminal', method: (that.httprequest.protocol == 9 ? 'StartPowerShell' : 'Start'), args: [cols, rows] } }); - } - that.httprequest._dispatcher.ws = that; - that.httprequest._dispatcher.on('connection', function (c) { if (this.ws.httprequest.connectionPromise.completed) { c.end(); } else { this.ws.httprequest.connectionPromise._res(c); } }); - } - }); - } - } catch (ex) { - this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + ex.toString()); - } - } - else { - try { - var bash = fs.existsSync('/bin/bash') ? '/bin/bash' : false; - var sh = fs.existsSync('/bin/sh') ? '/bin/sh' : false; - var login = process.platform == 'linux' ? '/bin/login' : '/usr/bin/login'; - - var env = { HISTCONTROL: 'ignoreboth' }; - if (process.env['LANG']) { env['LANG'] = process.env['LANG']; } - if (process.env['PATH']) { env['PATH'] = process.env['PATH']; } - if (this.httprequest.xoptions) { - if (this.httprequest.xoptions.rows) { env.LINES = ('' + this.httprequest.xoptions.rows); } - if (this.httprequest.xoptions.cols) { env.COLUMNS = ('' + this.httprequest.xoptions.cols); } - } - var options = { type: childProcess.SpawnTypes.TERM, uid: (this.httprequest.protocol == 8) ? require('user-sessions').consoleUid() : null, env: env }; - if (this.httprequest.xoptions && this.httprequest.xoptions.requireLogin) { - if (!require('fs').existsSync(login)) { throw ('Unable to spawn login process'); } - this.httprequest.connectionPromise._res(childProcess.execFile(login, ['login'], options)); // Start login shell - } - else if (bash) { - var p = childProcess.execFile(bash, ['bash'], options); // Start bash - // Spaces at the beginning of lines are needed to hide commands from the command history - if ((obj.serverInfo.termlaunchcommand != null) && (typeof obj.serverInfo.termlaunchcommand[process.platform] == 'string')) { - if (obj.serverInfo.termlaunchcommand[process.platform] != '') { p.stdin.write(obj.serverInfo.termlaunchcommand[process.platform]); } - } else if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); } - this.httprequest.connectionPromise._res(p); - } - else if (sh) { - var p = childProcess.execFile(sh, ['sh'], options); // Start sh - // Spaces at the beginning of lines are needed to hide commands from the command history - if ((obj.serverInfo.termlaunchcommand != null) && (typeof obj.serverInfo.termlaunchcommand[process.platform] == 'string')) { - if (obj.serverInfo.termlaunchcommand[process.platform] != '') { p.stdin.write(obj.serverInfo.termlaunchcommand[process.platform]); } - } else if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); } - this.httprequest.connectionPromise._res(p); - } - else { - this.httprequest.connectionPromise._rej('Failed to start remote terminal session, no shell found'); - } - } catch (ex) { - this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + ex.toString()); - } - } - - this.httprequest.connectionPromise.then( - function (term) { - // SUCCESS - var stdoutstream; - var stdinstream; - if (process.platform == 'win32') { - this.ws.httprequest._term = term; - this.ws.httprequest._term.tunnel = this.ws; - stdoutstream = stdinstream = term; - } - else { - term.descriptorMetadata = 'Remote Terminal'; - this.ws.httprequest.process = term; - this.ws.httprequest.process.tunnel = this.ws; - term.stderr.stdout = term.stdout; - term.stderr.on('data', function (c) { this.stdout.write(c); }); - stdoutstream = term.stdout; - stdinstream = term.stdin; - this.ws.prependListener('end', function () { this.httprequest.process.kill(); }); - term.prependListener('exit', function () { this.tunnel.end(); }); - } - - this.ws.removeAllListeners('data'); - this.ws.on('data', onTunnelControlData); - - stdoutstream.pipe(this.ws, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. - this.ws.pipe(stdinstream, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. - - // Add the terminal session to the count to update the server - if (this.ws.httprequest.userid != null) { - var userid = getUserIdAndGuestNameFromHttpRequest(this.ws.httprequest); - if (tunnelUserCount.terminal[userid] == null) { tunnelUserCount.terminal[userid] = 1; } else { tunnelUserCount.terminal[userid]++; } - try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (ex) { } - broadcastSessionsToRegisteredApps(); - } - - // Toast Notification, if required - if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 2)) { - // User Notifications is required - var notifyMessage = currentTranslation['terminalNotify'].replace('{0}', this.ws.httprequest.username); - var notifyTitle = "MeshCentral"; - if (this.ws.httprequest.soptions != null) { - if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; } - if (this.ws.httprequest.soptions.notifyMsgTerminal != null) { notifyMessage = this.ws.httprequest.soptions.notifyMsgTerminal.replace('{0}', this.ws.httprequest.realname).replace('{1}', this.ws.httprequest.username); } - } - try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (ex) { } - } - }, - function (e) { - // FAILED to connect terminal - this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 })); - this.ws.end(); - }); - }, - function (e) { - // DO NOT start terminal - this.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 })); - this.that.end(); - }); + this.httprequest.tpromise.then(terminal_promise_consent_resolved, terminal_promise_consent_rejected); } - else if (this.httprequest.protocol == 2) { + else if (this.httprequest.protocol == 2) + { // // Remote Desktop // @@ -2378,14 +2768,17 @@ function onTunnelData(data) { // Send a metadata update to all desktop sessions var users = {}; - if (this.httprequest.desktop.kvm.tunnels != null) { - for (var i in this.httprequest.desktop.kvm.tunnels) { + if (this.httprequest.desktop.kvm.tunnels != null) + { + for (var i in this.httprequest.desktop.kvm.tunnels) + { try { var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest.desktop.kvm.tunnels[i].httprequest); if (users[userid] == null) { users[userid] = 1; } else { users[userid]++; } } catch (ex) { sendConsoleText(ex); } } - for (var i in this.httprequest.desktop.kvm.tunnels) { + for (var i in this.httprequest.desktop.kvm.tunnels) + { try { this.httprequest.desktop.kvm.tunnels[i].write(JSON.stringify({ ctrlChannel: '102938', type: 'metadata', users: users })); } catch (ex) { } } tunnelUserCount.desktop = users; @@ -2393,79 +2786,8 @@ function onTunnelData(data) { broadcastSessionsToRegisteredApps(); } - this.end = function () { - --this.desktop.kvm.connectionCount; + this.end = tunnel_kvm_end; - // Remove ourself from the list of remote desktop session - var i = this.desktop.kvm.tunnels.indexOf(this); - if (i >= 0) { this.desktop.kvm.tunnels.splice(i, 1); } - - // Send a metadata update to all desktop sessions - var users = {}; - if (this.httprequest.desktop.kvm.tunnels != null) { - for (var i in this.httprequest.desktop.kvm.tunnels) { - try { - var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest.desktop.kvm.tunnels[i].httprequest); - if (users[userid] == null) { users[userid] = 1; } else { users[userid]++; } - } catch (ex) { sendConsoleText(ex); } - } - for (var i in this.httprequest.desktop.kvm.tunnels) { - try { this.httprequest.desktop.kvm.tunnels[i].write(JSON.stringify({ ctrlChannel: '102938', type: 'metadata', users: users })); } catch (ex) { } - } - tunnelUserCount.desktop = users; - try { mesh.SendCommand({ action: 'sessions', type: 'kvm', value: users }); } catch (ex) { } - broadcastSessionsToRegisteredApps(); - } - - // Unpipe the web socket - try { - this.unpipe(this.httprequest.desktop.kvm); - this.httprequest.desktop.kvm.unpipe(this); - } catch (ex) { } - - // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends). - if (this.rtcchannel) { - try { - this.rtcchannel.unpipe(this.httprequest.desktop.kvm); - this.httprequest.desktop.kvm.unpipe(this.rtcchannel); - } - catch (ex) { } - } - - // Place wallpaper back if needed - // TODO - - if (this.desktop.kvm.connectionCount == 0) { - // Display a toast message. This may not be supported on all platforms. - // try { require('toaster').Toast('MeshCentral', 'Remote Desktop Control Ended.'); } catch (ex) { } - - this.httprequest.desktop.kvm.end(); - if (this.httprequest.desktop.kvm.connectionBar) { - this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close'); - this.httprequest.desktop.kvm.connectionBar.close(); - this.httprequest.desktop.kvm.connectionBar = null; - } - } else { - for (var i in this.httprequest.desktop.kvm.users) { - if ((this.httprequest.desktop.kvm.users[i] == this.httprequest.username) && this.httprequest.desktop.kvm.connectionBar) { - for (var j in this.httprequest.desktop.kvm.rusers) { if (this.httprequest.desktop.kvm.rusers[j] == this.httprequest.realname) { this.httprequest.desktop.kvm.rusers.splice(j, 1); break; } } - this.httprequest.desktop.kvm.users.splice(i, 1); - this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close'); - this.httprequest.desktop.kvm.connectionBar.close(); - this.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.httprequest.privacybartext.replace('{0}', this.httprequest.desktop.kvm.rusers.join(', ')).replace('{1}', this.httprequest.desktop.kvm.users.join(', ')), require('MeshAgent')._tsid, color_options); - this.httprequest.desktop.kvm.connectionBar.httprequest = this.httprequest; - this.httprequest.desktop.kvm.connectionBar.on('close', function () { - MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest); - for (var i in this.httprequest.desktop.kvm._pipedStreams) { - this.httprequest.desktop.kvm._pipedStreams[i].end(); - } - this.httprequest.desktop.kvm.end(); - }); - break; - } - } - } - }; if (this.httprequest.desktop.kvm.hasOwnProperty('connectionCount')) { this.httprequest.desktop.kvm.connectionCount++; this.httprequest.desktop.kvm.rusers.push(this.httprequest.realname); @@ -2478,31 +2800,38 @@ function onTunnelData(data) { this.httprequest.desktop.kvm.users = [this.httprequest.username]; } - if ((this.httprequest.desktopviewonly != true) && ((this.httprequest.rights == 0xFFFFFFFF) || (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) != 0) && ((this.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0)))) { + if ((this.httprequest.desktopviewonly != true) && ((this.httprequest.rights == 0xFFFFFFFF) || (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) != 0) && ((this.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0)))) + { // If we have remote control rights, pipe the KVM input this.pipe(this.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. Pipe the Browser --> KVM input. - } else { + } + else + { // We need to only pipe non-mouse & non-keyboard inputs. // sendConsoleText('Warning: No Remote Desktop Input Rights.'); // TODO!!! } // Perform notification if needed. Toast messages may not be supported on all platforms. - if (this.httprequest.consent && (this.httprequest.consent & 8)) { + if (this.httprequest.consent && (this.httprequest.consent & 8)) + { // User Consent Prompt is required // Send a console message back using the console channel, "\n" is supported. this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 })); var consentMessage = currentTranslation['desktopConsent'].replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); var consentTitle = 'MeshCentral'; - if (this.httprequest.soptions != null) { + if (this.httprequest.soptions != null) + { if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; } if (this.httprequest.soptions.consentMsgDesktop != null) { consentMessage = this.httprequest.soptions.consentMsgDesktop.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); } } var pr; - if (process.platform == 'win32') { + if (process.platform == 'win32') + { var enhanced = false; try { require('win-userconsent'); enhanced = true; } catch (ex) { } - if (enhanced) { + if (enhanced) + { var ipr = server_getUserImage(this.httprequest.userid); ipr.consentTitle = consentTitle; ipr.consentMessage = consentMessage; @@ -2511,85 +2840,33 @@ function onTunnelData(data) { ipr.tsid = tsid; ipr.username = this.httprequest.realname; ipr.translation = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage }; - pr = ipr.then(function (img) { + pr = ipr.then(function (img) + { this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), uid: this.tsid, timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translation, background: color_options.background, foreground: color_options.foreground }); this.__childPromise.close = this.consent.close.bind(this.consent); return (this.consent); }); } - else { - pr = require('message-box').create(consentTitle, consentMessage, this.consentTimeout, null, tsid); + else + { + pr = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout, null, tsid); } } - else { - pr = require('message-box').create(consentTitle, consentMessage, this.consentTimeout, null, tsid); + else + { + pr = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout, null, tsid); } pr.ws = this; this.pause(); this._consentpromise = pr; - this.prependOnceListener('end', function () { - if (this._consentpromise && this._consentpromise.close) { - this._consentpromise.close(); - } - }); - pr.then( - function (always) { - if (always) { server_set_consentTimer(this.ws.httprequest.userid); } - - // Success - this.ws._consentpromise = null; - MeshServerLogEx(30, null, "Starting remote desktop after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); - this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 })); - if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 1)) { - // User Notifications is required - var notifyMessage = currentTranslation['desktopNotify'].replace('{0}', this.ws.httprequest.realname); - var notifyTitle = "MeshCentral"; - if (this.ws.httprequest.soptions != null) { - if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; } - if (this.ws.httprequest.soptions.notifyMsgDesktop != null) { notifyMessage = this.ws.httprequest.soptions.notifyMsgDesktop.replace('{0}', this.ws.httprequest.realname).replace('{1}', this.ws.httprequest.username); } - } - try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (ex) { } - } - if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 0x40)) { - // Connection Bar is required - if (this.ws.httprequest.desktop.kvm.connectionBar) { - this.ws.httprequest.desktop.kvm.connectionBar.removeAllListeners('close'); - this.ws.httprequest.desktop.kvm.connectionBar.close(); - } - try { - this.ws.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.ws.httprequest.privacybartext.replace('{0}', this.ws.httprequest.desktop.kvm.rusers.join(', ')).replace('{1}', this.ws.httprequest.desktop.kvm.users.join(', ')), require('MeshAgent')._tsid, color_options); - MeshServerLogEx(31, null, "Remote Desktop Connection Bar Activated/Updated (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); - } catch (ex) { - if (process.platform != 'darwin') { - MeshServerLogEx(32, null, "Remote Desktop Connection Bar Failed or Not Supported (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); - } - } - if (this.ws.httprequest.desktop.kvm.connectionBar) { - this.ws.httprequest.desktop.kvm.connectionBar.httprequest = this.ws.httprequest; - this.ws.httprequest.desktop.kvm.connectionBar.on('close', function () { - MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest); - for (var i in this.httprequest.desktop.kvm._pipedStreams) { - this.httprequest.desktop.kvm._pipedStreams[i].end(); - } - this.httprequest.desktop.kvm.end(); - }); - } - } - this.ws.httprequest.desktop.kvm.pipe(this.ws, { dataTypeSkip: 1 }); - if (this.ws.httprequest.autolock) { - destopLockHelper_pipe(this.ws.httprequest); - } - this.ws.resume(); - }, - function (e) { - // User Consent Denied/Failed - this.ws._consentpromise = null; - MeshServerLogEx(34, null, "Failed to start remote desktop after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); - this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 })); - }); - } else { + this.prependOnceListener('end', kvm_tunnel_consentpromise_closehandler); + pr.then(kvm_consentpromise_resolved, kvm_consentpromise_rejected); + } + else + { // User Consent Prompt is not required - if (this.httprequest.consent && (this.httprequest.consent & 1)) { + if (this.httprequest.consent && (this.httprequest.consent & 1)) + { // User Notifications is required MeshServerLogEx(35, null, "Started remote desktop with toast notification (" + this.httprequest.remoteaddr + ")", this.httprequest); var notifyMessage = currentTranslation['desktopNotify'].replace('{0}', this.httprequest.realname); @@ -2599,34 +2876,52 @@ function onTunnelData(data) { if (this.httprequest.soptions.notifyMsgDesktop != null) { notifyMessage = this.httprequest.soptions.notifyMsgDesktop.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); } } try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (ex) { } - } else { + } else + { MeshServerLogEx(36, null, "Started remote desktop without notification (" + this.httprequest.remoteaddr + ")", this.httprequest); } - if (this.httprequest.consent && (this.httprequest.consent & 0x40)) { + if (this.httprequest.consent && (this.httprequest.consent & 0x40)) + { // Connection Bar is required - if (this.httprequest.desktop.kvm.connectionBar) { + if (this.httprequest.desktop.kvm.connectionBar) + { this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close'); this.httprequest.desktop.kvm.connectionBar.close(); } - try { + try + { this.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.httprequest.privacybartext.replace('{0}', this.httprequest.desktop.kvm.rusers.join(', ')).replace('{1}', this.httprequest.desktop.kvm.users.join(', ')), require('MeshAgent')._tsid, color_options); MeshServerLogEx(31, null, "Remote Desktop Connection Bar Activated/Updated (" + this.httprequest.remoteaddr + ")", this.httprequest); } catch (ex) { MeshServerLogEx(32, null, "Remote Desktop Connection Bar Failed or not Supported (" + this.httprequest.remoteaddr + ")", this.httprequest); } - if (this.httprequest.desktop.kvm.connectionBar) { - this.httprequest.desktop.kvm.connectionBar.httprequest = this.httprequest; - this.httprequest.desktop.kvm.connectionBar.on('close', function () { - MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest); - for (var i in this.httprequest.desktop.kvm._pipedStreams) { - this.httprequest.desktop.kvm._pipedStreams[i].end(); + if (this.httprequest.desktop.kvm.connectionBar) + { + this.httprequest.desktop.kvm.connectionBar.state = + { + userid: this.httprequest.userid, + xuserid: this.httprequest.xuserid, + username: this.httprequest.username, + sessionid: this.httprequest.sessionid, + remoteaddr: this.httprequest.remoteaddr, + guestname: this.httprequest.guestname, + desktop: this.httprequest.desktop + }; + this.httprequest.desktop.kvm.connectionBar.on('close', function () + { + console.info1('Connection Bar Forcefully closed'); + MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.state.remoteaddr + ")", this.state); + for (var i in this.state.desktop.kvm._pipedStreams) + { + this.state.desktop.kvm._pipedStreams[i].end(); } - this.httprequest.desktop.kvm.end(); + this.state.desktop.kvm.end(); }); } } this.httprequest.desktop.kvm.pipe(this, { dataTypeSkip: 1 }); - if (this.httprequest.autolock) { + if (this.httprequest.autolock) + { destopLockHelper_pipe(this.httprequest); } } @@ -2634,7 +2929,6 @@ function onTunnelData(data) { this.removeAllListeners('data'); this.on('data', onTunnelControlData); //this.write('MeshCore KVM Hello!1'); - } else if (this.httprequest.protocol == 5) { // // Remote Files @@ -2659,7 +2953,8 @@ function onTunnelData(data) { broadcastSessionsToRegisteredApps(); } - this.end = function () { + this.end = function () + { // Remove the files session from the count to update the server if (this.httprequest.userid != null) { var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest); @@ -2670,22 +2965,26 @@ function onTunnelData(data) { }; // Perform notification if needed. Toast messages may not be supported on all platforms. - if (this.httprequest.consent && (this.httprequest.consent & 32)) { + if (this.httprequest.consent && (this.httprequest.consent & 32)) + { // User Consent Prompt is required // Send a console message back using the console channel, "\n" is supported. this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 })); var consentMessage = currentTranslation['fileConsent'].replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); var consentTitle = 'MeshCentral'; - if (this.httprequest.soptions != null) { + if (this.httprequest.soptions != null) + { if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; } if (this.httprequest.soptions.consentMsgFiles != null) { consentMessage = this.httprequest.soptions.consentMsgFiles.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); } } var pr; - if (process.platform == 'win32') { + if (process.platform == 'win32') + { var enhanced = false; try { require('win-userconsent'); enhanced = true; } catch (ex) { } - if (enhanced) { + if (enhanced) + { var ipr = server_getUserImage(this.httprequest.userid); ipr.consentTitle = consentTitle; ipr.consentMessage = consentMessage; @@ -2693,49 +2992,29 @@ function onTunnelData(data) { ipr.consentAutoAccept = this.httprequest.consentAutoAccept; ipr.username = this.httprequest.realname; ipr.translations = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage }; - pr = ipr.then(function (img) { + pr = ipr.then(function (img) + { this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translations, background: color_options.background, foreground: color_options.foreground }); this.__childPromise.close = this.consent.close.bind(this.consent); return (this.consent); }); - } else { - pr = require('message-box').create(consentTitle, consentMessage, this.consentTimeout, null); + } else + { + pr = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout, null); } - } else { - pr = require('message-box').create(consentTitle, consentMessage, this.consentTimeout, null); + } + else + { + pr = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout, null); } pr.ws = this; this.pause(); this._consentpromise = pr; - this.prependOnceListener('end', function () { if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); } }); - pr.then( - function (always) { - if (always) { server_set_consentTimer(this.ws.httprequest.userid); } - - // Success - this.ws._consentpromise = null; - MeshServerLogEx(40, null, "Starting remote files after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); - this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null })); - if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 4)) { - // User Notifications is required - var notifyMessage = currentTranslation['fileNotify'].replace('{0}', this.ws.httprequest.realname); - var notifyTitle = "MeshCentral"; - if (this.ws.httprequest.soptions != null) { - if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; } - if (this.ws.httprequest.soptions.notifyMsgFiles != null) { notifyMessage = this.ws.httprequest.soptions.notifyMsgFiles.replace('{0}', this.ws.httprequest.realname).replace('{1}', this.ws.httprequest.username); } - } - try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (ex) { } - } - this.ws.resume(); - }, - function (e) { - // User Consent Denied/Failed - this.ws._consentpromise = null; - MeshServerLogEx(41, null, "Failed to start remote files after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest); - this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 })); - }); + this.prependOnceListener('end', files_tunnel_endhandler); + pr.then(files_consentpromise_resolved, files_consentpromise_rejected); } - else { + else + { // User Consent Prompt is not required if (this.httprequest.consent && (this.httprequest.consent & 4)) { // User Notifications is required @@ -3062,6 +3341,44 @@ function onTunnelWebRTCControlData(data) { } } +function tunnel_webrtc_onEnd() +{ + // The WebRTC channel closed, unpipe the KVM now. This is also done when the web socket closes. + //sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC data channel closed'); + if (this.websocket.desktop && this.websocket.desktop.kvm) + { + try + { + this.unpipe(this.websocket.desktop.kvm); + this.websocket.httprequest.desktop.kvm.unpipe(this); + } catch (ex) { } + } + this.httprequest = null; + this.websocket = null; +} +function tunnel_webrtc_DataChannel_OnFinalized() +{ + console.info1('WebRTC DataChannel Finalized'); +} +function tunnel_webrtc_OnDataChannel(rtcchannel) +{ + //sendConsoleText('WebRTC Datachannel open, protocol: ' + this.websocket.httprequest.protocol); + //rtcchannel.maxFragmentSize = 32768; + rtcchannel.xrtc = this; + rtcchannel.websocket = this.websocket; + this.rtcchannel = rtcchannel; + this.rtcchannel.once('~', tunnel_webrtc_DataChannel_OnFinalized); + this.websocket.rtcchannel = rtcchannel; + this.websocket.rtcchannel.on('data', onTunnelWebRTCControlData); + this.websocket.rtcchannel.on('end', tunnel_webrtc_onEnd); + this.websocket.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc0\"}'); // Indicate we are ready for WebRTC switch-over. +} + +function tunnel_webrtc_OnFinalized() +{ + console.info1('WebRTC Connection Finalized'); +} + // Called when receiving control data on websocket function onTunnelControlData(data, ws) { var obj; @@ -3135,7 +3452,8 @@ function onTunnelControlData(data, ws) { break; } case 'webrtc0': { // Browser indicates we can start WebRTC switch-over. - if (ws.httprequest.protocol == 1) { // Terminal + if (ws.httprequest.protocol == 1) + { // Terminal // This is a terminal data stream, unpipe the terminal now and indicate to the other side that terminal data will no longer be received over WebSocket if (process.platform == 'win32') { ws.httprequest._term.unpipe(ws); @@ -3146,7 +3464,8 @@ function onTunnelControlData(data, ws) { } else if (ws.httprequest.protocol == 2) { // Desktop // This is a KVM data stream, unpipe the KVM now and indicate to the other side that KVM data will no longer be received over WebSocket ws.httprequest.desktop.kvm.unpipe(ws); - } else { + } else + { // Switch things around so all WebRTC data goes to onTunnelData(). ws.rtcchannel.httprequest = ws.httprequest; ws.rtcchannel.removeAllListeners('data'); @@ -3155,8 +3474,10 @@ function onTunnelControlData(data, ws) { ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"webrtc1\"}"); // End of data marker break; } - case 'webrtc1': { - if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal + case 'webrtc1': + { + if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) + { // Terminal // Switch the user input from websocket to webrtc at this point. if (process.platform == 'win32') { ws.unpipe(ws.httprequest._term); @@ -3166,7 +3487,9 @@ function onTunnelControlData(data, ws) { ws.rtcchannel.pipe(ws.httprequest.process.stdin, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text. } ws.resume(); // Resume the websocket to keep receiving control data - } else if (ws.httprequest.protocol == 2) { // Desktop + } + else if (ws.httprequest.protocol == 2) + { // Desktop // Switch the user input from websocket to webrtc at this point. ws.unpipe(ws.httprequest.desktop.kvm); try { ws.webrtc.rtcchannel.pipe(ws.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); } catch (ex) { sendConsoleText('EX2'); } // 0 = Binary, 1 = Text. @@ -3193,29 +3516,12 @@ function onTunnelControlData(data, ws) { // This is a WebRTC offer. if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) return; // TODO: Terminal is currently broken with WebRTC. Reject WebRTC upgrade for now. ws.webrtc = rtc.createConnection(); + ws.webrtc.once('~', tunnel_webrtc_OnFinalized); ws.webrtc.websocket = ws; - ws.webrtc.on('connected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC connected');*/ }); - ws.webrtc.on('disconnected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC disconnected');*/ }); - ws.webrtc.on('dataChannel', function (rtcchannel) { - //sendConsoleText('WebRTC Datachannel open, protocol: ' + this.websocket.httprequest.protocol); - //rtcchannel.maxFragmentSize = 32768; - rtcchannel.xrtc = this; - rtcchannel.websocket = this.websocket; - this.rtcchannel = rtcchannel; - this.websocket.rtcchannel = rtcchannel; - this.websocket.rtcchannel.on('data', onTunnelWebRTCControlData); - this.websocket.rtcchannel.on('end', function () { - // The WebRTC channel closed, unpipe the KVM now. This is also done when the web socket closes. - //sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC data channel closed'); - if (this.websocket.desktop && this.websocket.desktop.kvm) { - try { - this.unpipe(this.websocket.desktop.kvm); - this.websocket.httprequest.desktop.kvm.unpipe(this); - } catch (ex) { } - } - }); - this.websocket.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc0\"}'); // Indicate we are ready for WebRTC switch-over. - }); + //ws.webrtc.on('connected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC connected');*/ }); + //ws.webrtc.on('disconnected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC disconnected');*/ }); + ws.webrtc.on('dataChannel', tunnel_webrtc_OnDataChannel); + var sdp = null; try { sdp = ws.webrtc.setOffer(obj.sdp); } catch (ex) { } if (sdp != null) { ws.write({ type: 'answer', ctrlChannel: '102938', sdp: sdp }); } @@ -4040,7 +4346,9 @@ function processConsoleCommand(cmd, args, rights, sessionid) { this._dispatcher.on('connection', function (c) { this._c = c; this._c.root = this.parent; - this._c.on('end', function () { + this._c.on('end', function () + { + this.root._dispatcher.close(); this.root._dispatcher = null; this.root = null; }); diff --git a/apprelays.js b/apprelays.js index 569c63b0..3eb87a03 100644 --- a/apprelays.js +++ b/apprelays.js @@ -69,7 +69,7 @@ function SerialTunnel(options) { } // Construct a Web relay object -module.exports.CreateWebRelaySession = function (parent, db, req, args, domain, userid, nodeid, addr, port, appid, sessionid) { +module.exports.CreateWebRelaySession = function (parent, db, req, args, domain, userid, nodeid, addr, port, appid, sessionid, expire) { const obj = {}; obj.parent = parent; obj.lastOperation = Date.now(); @@ -80,6 +80,7 @@ module.exports.CreateWebRelaySession = function (parent, db, req, args, domain, obj.port = port; obj.appid = appid; obj.sessionid = sessionid; + obj.expireTimer = null; var pendingRequests = []; var nextTunnelId = 1; var tunnels = {}; @@ -90,6 +91,9 @@ module.exports.CreateWebRelaySession = function (parent, db, req, args, domain, // Any HTTP cookie set by the device is going to be shared between all tunnels to that device. obj.webCookies = {}; + // Setup an expire time if needed + if (expire != null) { var timeout = (expire - Date.now()); if (timeout < 10) { timeout = 10; } obj.expireTimer = setTimeout(close, timeout); } + // Events obj.closed = false; obj.onclose = null; @@ -202,6 +206,9 @@ module.exports.CreateWebRelaySession = function (parent, db, req, args, domain, parent.parent.debug('webrelay', 'tunnel-close'); obj.closed = true; + // Clear the time if present + if (obj.expireTimer != null) { clearTimeout(obj.expireTimer); delete obj.expireTimer; } + // Close all tunnels for (var i in tunnels) { tunnels[i].close(); } tunnels = null; diff --git a/meshcentral.js b/meshcentral.js index 0dd31e92..44b6ee71 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -466,7 +466,7 @@ function CreateMeshCentralServer(config, args) { const npmproxy = ((typeof obj.args.npmproxy == 'string') ? (' --proxy ' + obj.args.npmproxy) : ''); const env = Object.assign({}, process.env); // Shallow clone if (typeof obj.args.npmproxy == 'string') { env['HTTP_PROXY'] = env['HTTPS_PROXY'] = env['http_proxy'] = env['https_proxy'] = obj.args.npmproxy; } - const xxprocess = child_process.exec(npmpath + ' install meshcentral' + version + npmproxy, { maxBuffer: Infinity, cwd: obj.parentpath, env: env }, function (error, stdout, stderr) { + const xxprocess = child_process.exec(npmpath + ' install --no-package-lock meshcentral' + version + npmproxy, { maxBuffer: Infinity, cwd: obj.parentpath, env: env }, function (error, stdout, stderr) { if ((error != null) && (error != '')) { console.log('Update failed: ' + error); } }); xxprocess.data = ''; diff --git a/meshuser.js b/meshuser.js index 463faf0c..8972acf3 100644 --- a/meshuser.js +++ b/meshuser.js @@ -4137,8 +4137,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use else if ((command.start != null) && (typeof command.start != 'number')) { err = 'Invalid start time'; } // Check the start time in UTC seconds else if ((command.end != null) && (typeof command.end != 'number')) { err = 'Invalid end time'; } // Check the end time in UTC seconds else if (common.validateInt(command.consent, 0, 256) == false) { err = 'Invalid flags'; } // Check the flags - else if (common.validateInt(command.p, 1, 7) == false) { err = 'Invalid protocol'; } // Check the protocol, 1 = Terminal, 2 = Desktop, 4 = Files + 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 else if ((command.recurring != null) && (common.validateInt(command.recurring, 1, 2) == false)) { err = 'Invalid recurring value'; } // Check the recurring value, 1 = Daily, 2 = Weekly + else if ((command.port != null) && (common.validateInt(command.port, 1, 65535) == false)) { err = 'Invalid port value'; } // Check the port if present else if ((command.recurring != null) && ((command.end != null) || (command.start == null) || (command.expire == null))) { err = 'Invalid recurring command'; } 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 else { @@ -4238,7 +4239,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use try { ws.send(JSON.stringify(command)); } catch (ex) { } // Create a device sharing database entry - 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, url: url }; + 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 }; if ((startTime != null) && (expireTime != null)) { shareEntry.startTime = startTime; shareEntry.expireTime = expireTime; } else if ((startTime != null) && (duration != null)) { shareEntry.startTime = startTime; shareEntry.duration = duration; } if (command.recurring) { shareEntry.recurring = command.recurring; } diff --git a/views/default.handlebars b/views/default.handlebars index ecdbce87..345ff633 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -3650,7 +3650,7 @@ if (message.consent & 0x0040) { y.push("Privacy bar"); } if (y.length == 0) { y.push("None"); } x += addHtmlValue("User Consent", y.join(', ')); - var type = ['', "Remote Terminal Link", "Remote Desktop Link", "Remote Desktop + Terminal Link", "Remote Files Link", "Remote Terminal + Files Link", "Remote Desktop + Files Link", "Remote Desktop + Terminal + Files Link"][message.p]; + var type = ''; if (message.p <= 7) { type = ['', "Remote Terminal Link", "Remote Desktop Link", "Remote Desktop + Terminal Link", "Remote Files Link", "Remote Terminal + Files Link", "Remote Desktop + Files Link", "Remote Desktop + Terminal + Files Link"][message.p]; } else if (message.p == 8) { type = format("HTTP/{0} link", message.port); } else if (message.p == 16) { type = format("HTTPS/{0}", message.port); } x += ''; setDialogMode(2, "Share Device", 1, null, x); break; @@ -7588,7 +7588,7 @@ var dshare = deviceShares[i], trash = ''; if (dshare.url != null) { trash += ' '; } trash += ''; - var type = ['', "Terminal", "Desktop", "Desktop + Terminal", "Files", "Terminal + Files", "Desktop + Files", "Desktop + Terminal + Files"][dshare.p]; + var type = ''; if (dshare.p <= 7) { type = ['', "Terminal", "Desktop", "Desktop + Terminal", "Files", "Terminal + Files", "Desktop + Files", "Desktop + Terminal + Files"][dshare.p]; } else if (dshare.p == 8) { type = "HTTP/" + dshare.port; } else if (dshare.p == 16) { type = "HTTPS/" + dshare.port; } var details = type; if ((dshare.startTime != null) && (dshare.expireTime != null)) { details = format("{0}, {1} to {2}", type, printFlexDateTime(new Date(dshare.startTime)), printFlexDateTime(new Date(dshare.expireTime))); } if ((dshare.startTime != null) && (dshare.duration != null)) { @@ -7896,6 +7896,11 @@ if ((rights != 0xFFFFFFFF) && ((rights & 0x600) != 0)) { termFiles = ''; } var allFeatures = ''; if ((rights != 0xFFFFFFFF) && ((rights & 0x700) != 0)) { allFeatures = ''; } + var httpFeature = ''; + if (webRelayPort != 0) { + httpFeature = ''; + if ((rights != 0xFFFFFFFF) && ((rights & 8) != 0)) { httpFeature = ''; } + } var y = '', z = ''; if ((currentNode.agent.caps & 1) == 1) { y += (deskFull + ''); } // Agent is desktop capable @@ -7904,6 +7909,7 @@ if ((currentNode.agent.caps & 5) == 5) { y += deskFiles; } // Agent is desktop + files capable if ((currentNode.agent.caps & 6) == 6) { y += termFiles; } // Agent is terminal + files capable if ((currentNode.agent.caps & 7) == 7) { y += allFeatures; } // Agent is desktop + terminal + files capable + y += httpFeature; x += addHtmlValue("Type", ''); var options = { 1 : "1 minute", 5 : "5 minutes", 10 : "10 minutes", 15 : "15 minutes", 30 : "30 minutes", 45 : "45 minutes", 60 : "60 minutes", 120 : "2 hours", 240 : "4 hours", 480 : "8 hours", 720 : "12 hours", 960 : "16 hours", 1440 : "24 hours", 2880 : "2 days", 5760 : "4 days", 0 : "Unlimited" } @@ -7925,7 +7931,9 @@ x += addHtmlValue("Start Time", ''); x += addHtmlValue("Duration", ''); x += ''; - if (currentNode.agent.caps & 1) { x += addHtmlValue("User Consent", ''); } + if (currentNode.agent.caps & 1) { x += '
' + addHtmlValue("User Consent", '') + '
'; } + x += '
' + addHtmlValue("Port", '') + '
'; + x += '
' + addHtmlValue("Port", '') + '
'; setDialogMode(2, "Share Device", 3, showShareDeviceEx, x); showShareDeviceValidate(); var tomorrow = new Date(); @@ -7936,18 +7944,25 @@ } function showShareDeviceValidate() { + if (currentNode.agent.caps & 1) { QV('d2userConsentSelector', Q('d2shareType').value < 8); } + QV('d2httpPortSelector', Q('d2shareType').value == 8); + QV('d2httpsPortSelector', Q('d2shareType').value == 9); QV('d2modenow', Q('d2timeRange').value == 0); QV('d2moderange', Q('d2timeRange').value == 1); QV('d2moderecurring', Q('d2timeRange').value >= 2); var ok = true; + if (Q('d2shareType').value == 8) { var port = parseInt(Q('d2httpPort').value); if ((Q('d2httpPort').value != port) || (port < 1) || (port > 65535)) { ok = false; } } + if (Q('d2shareType').value == 9) { var port = parseInt(Q('d2httpsPort').value); if ((Q('d2httpsPort').value != port) || (port < 1) || (port > 65535)) { ok = false; } } if (Q('d2inviteName').value.trim().length == 0) { ok = false; } QE('idx_dlgOkButton', ok); } function showShareDeviceEx(b, tag) { var consent = 0, p = parseInt(Q('d2shareType').value), viewOnly = false, q = 0; + if (p == 8) { meshserver.send({ action: 'createDeviceShareLink', nodeid: currentNode._id, guestname: Q('d2inviteName').value.trim(), p: 8, expire: parseInt(Q('d2inviteExpire').value), port: parseInt(Q('d2httpPort').value), consent: 0 }); return; } + if (p == 9) { meshserver.send({ action: 'createDeviceShareLink', nodeid: currentNode._id, guestname: Q('d2inviteName').value.trim(), p: 16, expire: parseInt(Q('d2inviteExpire').value), port: parseInt(Q('d2httpsPort').value), consent: 0 }); return; } if (p == 3) { viewOnly = true; } - var q = [0, 1, 2, 2, 4, 6, 5, 7][p]; // Protocol flags: 1 = Terminal, 2 = Desktop, 4 = Files. + var q = [0, 1, 2, 2, 4, 6, 5, 7][p]; // Protocol flags: 1 = Terminal, 2 = Desktop, 4 = Files, 8 = HTTP, 16 = HTTPS. if (q & 1) { consent |= 0x0002; // Terminal notify @@ -12641,7 +12656,7 @@ var dshare = deviceShares[i], trash = ''; if (dshare.url != null) { trash += ' '; } trash += ''; - var type = ['', "Terminal", "Desktop", "Desktop + Terminal", "Files", "Terminal + Files", "Desktop + Files", "Desktop + Terminal + Files"][dshare.p]; + var type = ''; if (dshare.p <= 7) { type = ['', "Terminal", "Desktop", "Desktop + Terminal", "Files", "Terminal + Files", "Desktop + Files", "Desktop + Terminal + Files"][dshare.p]; } else if (dshare.p == 8) { type = "HTTP/" + dshare.port; } else if (dshare.p == 16) { type = "HTTPS/" + dshare.port; } var details = type; if ((dshare.startTime != null) && (dshare.expireTime != null)) { details = format("{0}, {1} to {2}", type, printFlexDateTime(new Date(dshare.startTime)), printFlexDateTime(new Date(dshare.expireTime))); } if ((dshare.startTime != null) && (dshare.duration != null)) { diff --git a/webrelayserver.js b/webrelayserver.js index e9f70353..07aa3349 100644 --- a/webrelayserver.js +++ b/webrelayserver.js @@ -124,8 +124,11 @@ module.exports.CreateWebRelayServer = function (parent, db, args, certificates, return next(); } else { // If this is a normal request (GET, POST, etc) handle it here - if ((req.session.userid != null) && (req.session.x != null) && (parent.webserver.destroyedSessions[req.session.userid + '/' + req.session.x] == null)) { - var relaySession = relaySessions[req.session.userid + '/' + req.session.x]; + var webSessionId = null; + if ((req.session.userid != null) && (req.session.x != null)) { webSessionId = req.session.userid + '/' + req.session.x; } + else if (req.session.z != null) { webSessionId = req.session.z; } + if ((webSessionId != null) && (parent.webserver.destroyedSessions[webSessionId] == null)) { + var relaySession = relaySessions[webSessionId]; if (relaySession != null) { // The web relay session is valid, use it relaySession.handleRequest(req, res); @@ -157,8 +160,11 @@ module.exports.CreateWebRelayServer = function (parent, db, args, certificates, // Handle incoming web socket calls obj.app.ws('/*', function (ws, req) { - if ((req.session.userid != null) && (req.session.x != null) && (parent.webserver.destroyedSessions[req.session.userid + '/' + req.session.x] == null)) { - var relaySession = relaySessions[req.session.userid + '/' + req.session.x]; + var webSessionId = null; + if ((req.session.userid != null) && (req.session.x != null)) { webSessionId = req.session.userid + '/' + req.session.x; } + else if (req.session.z != null) { webSessionId = req.session.z; } + if ((webSessionId != null) && (parent.webserver.destroyedSessions[webSessionId] == null)) { + var relaySession = relaySessions[webSessionId]; if (relaySession != null) { // The multi-tunnel session is valid, use it relaySession.handleWebSocket(ws, req); @@ -178,55 +184,85 @@ module.exports.CreateWebRelayServer = function (parent, db, args, certificates, parent.debug('webrelay', 'webRelaySetup'); // Decode the relay cookie - if (req.query.c != null) { - // Decode and check if this relay cookie is valid - const urlCookie = obj.parent.decodeCookie(req.query.c, parent.loginCookieEncryptionKey); - if ((urlCookie != null) && (urlCookie.ruserid != null) && (urlCookie.x != null) && (parent.webserver.destroyedSessions[urlCookie.ruserid + '/' + urlCookie.x] == null)) { - if (req.session.x != urlCookie.x) { req.session.x = urlCookie.x; } // Set the sessionid if missing - if (req.session.userid != urlCookie.ruserid) { req.session.userid = urlCookie.ruserid; } // Set the session userid if missing - } + if (req.query.c == null) { res.sendStatus(404); return; } + + // Decode and check if this relay cookie is valid + var userid, domainid, domain, nodeid, addr, port, appid, webSessionId, expire; + const urlCookie = obj.parent.decodeCookie(req.query.c, parent.loginCookieEncryptionKey); + if (urlCookie == null) { res.sendStatus(404); return; } + + // Decode the incomign cookie + if ((urlCookie.ruserid != null) && (urlCookie.x != null)) { + if (parent.webserver.destroyedSessions[urlCookie.ruserid + '/' + urlCookie.x] != null) { res.sendStatus(404); return; } + + // This is a standard user, figure out what our web relay will be. + if (req.session.x != urlCookie.x) { req.session.x = urlCookie.x; } // Set the sessionid if missing + if (req.session.userid != urlCookie.ruserid) { req.session.userid = urlCookie.ruserid; } // Set the session userid if missing + if (req.session.z) { delete req.session.z; } // Clear the web relay guest session + userid = req.session.userid; + domainid = userid.split('/')[1]; + domain = parent.config.domains[domainid]; + nodeid = ((req.query.relayid != null) ? req.query.relayid : req.query.n); + addr = (req.query.addr != null) ? req.query.addr : '127.0.0.1'; + port = parseInt(req.query.p); + appid = parseInt(req.query.appid); + webSessionId = req.session.userid + '/' + req.session.x; + + // Check that all the required arguments are present + if ((req.session.userid == null) || (req.session.x == null) || (req.query.n == null) || (req.query.p == null) || (parent.webserver.destroyedSessions[webSessionId] != null) || ((req.query.appid != 1) && (req.query.appid != 2))) { res.redirect('/'); return; } + } else if (urlCookie.r == 8) { + // This is a guest user, figure out what our web relay will be. + userid = urlCookie.userid; + domainid = userid.split('/')[1]; + domain = parent.config.domains[domainid]; + nodeid = urlCookie.nid; + addr = (urlCookie.addr != null) ? urlCookie.addr : '127.0.0.1'; + port = urlCookie.port; + appid = (urlCookie.p == 16) ? 2 : 1; // appid: 1 = HTTP, 2 = HTTPS + webSessionId = userid + '/' + urlCookie.pid; + if (req.session.x) { delete req.session.x; } // Clear the web relay sessionid + if (req.session.userid) { delete req.session.userid; } // Clear the web relay userid + if (req.session.z != webSessionId) { req.session.z = webSessionId; } // Set the web relay guest session + expire = urlCookie.expire; } - // Check that all the required arguments are present - if ((req.session.userid == null) || (req.session.x == null) || (req.query.n == null) || (req.query.p == null) || (parent.webserver.destroyedSessions[req.session.userid + '/' + req.session.x] != null) || ((req.query.appid != 1) && (req.query.appid != 2))) { res.redirect('/'); return; } - - // Get the user and domain information - const userid = req.session.userid; - const domainid = userid.split('/')[1]; - const domain = parent.config.domains[domainid]; - const nodeid = ((req.query.relayid != null) ? req.query.relayid : req.query.n); - const addr = (req.query.addr != null) ? req.query.addr : '127.0.0.1'; - const port = parseInt(req.query.p); - const appid = parseInt(req.query.appid); + // No session identifier was setup, exit now + if (webSessionId == null) { res.sendStatus(404); return; } // Check to see if we already have a multi-relay session that matches exactly this device and port for this user - const xrelaySession = relaySessions[req.session.userid + '/' + req.session.x]; + const xrelaySession = relaySessions[webSessionId]; if ((xrelaySession != null) && (xrelaySession.domain.id == domain.id) && (xrelaySession.userid == userid) && (xrelaySession.nodeid == nodeid) && (xrelaySession.addr == addr) && (xrelaySession.port == port) && (xrelaySession.appid == appid)) { // We found an exact match, we are all setup already, redirect to root res.redirect('/'); return; } - // There is a relay session, but it's not correct, close it. - if (xrelaySession != null) { xrelaySession.close(); delete relaySessions[req.session.userid + '/' + req.session.x]; } + // Check that the user has rights to access this device + parent.webserver.GetNodeWithRights(domain, userid, nodeid, function (node, rights, visible) { + // If there is no remote control rights, reject this web relay + if ((rights & 8) == 0) { res.sendStatus(404); return; } - // Create a web relay session - const relaySession = require('./apprelays.js').CreateWebRelaySession(obj, db, req, args, domain, userid, nodeid, addr, port, appid, xrelaySession); - relaySession.onclose = function (sessionId) { - // Remove the relay session - delete relaySessions[sessionId]; - // If there are not more relay sessions, clear the cleanup timer - if ((Object.keys(relaySessions).length == 0) && (obj.cleanupTimer != null)) { clearInterval(obj.cleanupTimer); obj.cleanupTimer = null; } - } + // There is a relay session, but it's not correct, close it. + if (xrelaySession != null) { xrelaySession.close(); delete relaySessions[webSessionId]; } - // Set the multi-tunnel session - relaySessions[userid + '/' + req.session.x] = relaySession; + // Create a web relay session + const relaySession = require('./apprelays.js').CreateWebRelaySession(obj, db, req, args, domain, userid, nodeid, addr, port, appid, xrelaySession, expire); + relaySession.onclose = function (sessionId) { + // Remove the relay session + delete relaySessions[sessionId]; + // If there are not more relay sessions, clear the cleanup timer + if ((Object.keys(relaySessions).length == 0) && (obj.cleanupTimer != null)) { clearInterval(obj.cleanupTimer); obj.cleanupTimer = null; } + } - // Setup the cleanup timer if needed - if (obj.cleanupTimer == null) { obj.cleanupTimer = setInterval(checkTimeout, 10000); } + // Set the multi-tunnel session + relaySessions[webSessionId] = relaySession; - // Redirect to root - res.redirect('/'); + // Setup the cleanup timer if needed + if (obj.cleanupTimer == null) { obj.cleanupTimer = setInterval(checkTimeout, 10000); } + + // Redirect to root + res.redirect('/'); + }); }); } diff --git a/webserver.js b/webserver.js index 05f65e27..d4d8d54b 100644 --- a/webserver.js +++ b/webserver.js @@ -3848,7 +3848,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF } // Generate an old style cookie from the information in the database - var cookie = { a: 5, p: doc.p, gn: doc.guestName, nid: doc.nodeid, cf: doc.consent, pid: doc.publicid, k: doc.extrakey }; + var cookie = { a: 5, p: doc.p, gn: doc.guestName, nid: doc.nodeid, cf: doc.consent, pid: doc.publicid, k: doc.extrakey ? doc.extrakey : null, port: doc.port }; if (doc.userid) { cookie.uid = doc.userid; } if ((cookie.userid == null) && (cookie.pid.startsWith('AS:node/'))) { cookie.nouser = 1; } if (doc.startTime != null) { @@ -3870,7 +3870,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF // Check the public id obj.db.GetAllTypeNodeFiltered([c.nid], domain.id, 'deviceshare', null, function (err, docs) { - // Check if any desktop sharing links are present, expire message. + // Check if any sharing links are present, expire message. if ((err != null) || (docs.length == 0)) { render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 12, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; } // Search for the device share public identifier, expire message. @@ -3886,22 +3886,43 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF // Check the start time, not yet valid message. if ((c.start != null) && (c.expire != null) && ((c.start > Date.now()) || (c.start > c.expire))) { render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 11, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; } - // Looks good, let's create the outbound session cookies. - // Consent flags are 1 = Notify, 8 = Prompt, 64 = Privacy Bar. - const authCookieData = { userid: c.uid, domainid: domain.id, nid: c.nid, ip: req.clientIp, p: c.p, gn: c.gn, cf: c.cf, r: 8, expire: c.expire, pid: c.pid, vo: c.vo }; - if ((authCookieData.userid == null) && (authCookieData.pid.startsWith('AS:node/'))) { authCookieData.nouser = 1; } - if (c.k != null) { authCookieData.k = c.k; } - const authCookie = obj.parent.encodeCookie(authCookieData, obj.parent.loginCookieEncryptionKey); + // If this is a web relay share, check if this feature is active + if ((c.p == 8) || (c.p == 16)) { + // This is a HTTP or HTTPS share + 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)); + if (webRelayPort == 0) { res.sendStatus(404); return; } - // Server features - var features2 = 0; - if (obj.args.allowhighqualitydesktop !== false) { features2 += 1; } // Enable AllowHighQualityDesktop (Default true) + // Create the authentication cookie + const authCookieData = { userid: c.uid, domainid: domain.id, nid: c.nid, ip: req.clientIp, p: c.p, gn: c.gn, r: 8, expire: c.expire, pid: c.pid, port: c.port }; + if ((authCookieData.userid == null) && (authCookieData.pid.startsWith('AS:node/'))) { authCookieData.nouser = 1; } + const authCookie = obj.parent.encodeCookie(authCookieData, obj.parent.loginCookieEncryptionKey); - // Lets respond by sending out the desktop viewer. - var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified - parent.debug('web', 'handleSharingRequest: Sending guest sharing page for \"' + c.uid + '\", guest \"' + c.gn + '\".'); - res.set({ 'Cache-Control': 'no-store' }); - render(req, res, getRenderPage('sharing', req, domain), getRenderArgs({ authCookie: authCookie, authRelayCookie: '', domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27'), nodeid: c.nid, serverDnsName: obj.getWebServerName(domain, req), serverRedirPort: args.redirport, serverPublicPort: httpsPort, expire: c.expire, viewOnly: (c.vo == 1) ? 1 : 0, nodeName: encodeURIComponent(node.name).replace(/'/g, '%27'), features: c.p, features2: features2 }, req, domain)); + // Redirect to a URL + var webRelayDns = (args.relaydns != null) ? args.relaydns[0] : obj.getWebServerName(domain, req); + var url = 'https://' + webRelayDns + ':' + webRelayPort + '/control-redirect.ashx?n=' + c.nid + '&p=' + c.port + '&appid=' + c.p + '&c=' + authCookie; + if (c.addr != null) { url += '&addr=' + c.addr; } + if (c.pid != null) { url += '&relayid=' + c.pid; } + parent.debug('web', 'handleSharingRequest: Redirecting guest to HTTP relay page for \"' + c.uid + '\", guest \"' + c.gn + '\".'); + res.redirect(url); + } else { + // Looks good, let's create the outbound session cookies. + // This is a desktop, terminal or files share. We need to display the sharing page. + // Consent flags are 1 = Notify, 8 = Prompt, 64 = Privacy Bar. + const authCookieData = { userid: c.uid, domainid: domain.id, nid: c.nid, ip: req.clientIp, p: c.p, gn: c.gn, cf: c.cf, r: 8, expire: c.expire, pid: c.pid, vo: c.vo }; + if ((authCookieData.userid == null) && (authCookieData.pid.startsWith('AS:node/'))) { authCookieData.nouser = 1; } + if (c.k != null) { authCookieData.k = c.k; } + const authCookie = obj.parent.encodeCookie(authCookieData, obj.parent.loginCookieEncryptionKey); + + // Server features + var features2 = 0; + if (obj.args.allowhighqualitydesktop !== false) { features2 += 1; } // Enable AllowHighQualityDesktop (Default true) + + // Lets respond by sending out the desktop viewer. + var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified + parent.debug('web', 'handleSharingRequest: Sending guest sharing page for \"' + c.uid + '\", guest \"' + c.gn + '\".'); + res.set({ 'Cache-Control': 'no-store' }); + render(req, res, getRenderPage('sharing', req, domain), getRenderArgs({ authCookie: authCookie, authRelayCookie: '', domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27'), nodeid: c.nid, serverDnsName: obj.getWebServerName(domain, req), serverRedirPort: args.redirport, serverPublicPort: httpsPort, expire: c.expire, viewOnly: (c.vo == 1) ? 1 : 0, nodeName: encodeURIComponent(node.name).replace(/'/g, '%27'), features: c.p, features2: features2 }, req, domain)); + } }); }); } @@ -6549,32 +6570,56 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF parent.debug('web', 'webRelaySetup'); // Decode the relay cookie - if (req.query.c != null) { - // Decode and check if this relay cookie is valid - const urlCookie = obj.parent.decodeCookie(req.query.c, obj.parent.loginCookieEncryptionKey); - if ((urlCookie != null) && (urlCookie.ruserid != null) && (urlCookie.x != null)) { - if (req.session.x != urlCookie.x) { req.session.x = urlCookie.x; } // Set the sessionid if missing - if (req.session.userid != urlCookie.ruserid) { req.session.userid = urlCookie.ruserid; } // Set the session userid if missing - } + if (req.query.c == null) { res.sendStatus(404); return; } + + // Decode and check if this relay cookie is valid + var userid, domainid, domain, nodeid, addr, port, appid, webSessionId, expire; + const urlCookie = obj.parent.decodeCookie(req.query.c, parent.loginCookieEncryptionKey); + if (urlCookie == null) { res.sendStatus(404); return; } + + // Decode the incomign cookie + if ((urlCookie.ruserid != null) && (urlCookie.x != null)) { + if (parent.webserver.destroyedSessions[urlCookie.ruserid + '/' + urlCookie.x] != null) { res.sendStatus(404); return; } + + // This is a standard user, figure out what our web relay will be. + if (req.session.x != urlCookie.x) { req.session.x = urlCookie.x; } // Set the sessionid if missing + if (req.session.userid != urlCookie.ruserid) { req.session.userid = urlCookie.ruserid; } // Set the session userid if missing + if (req.session.z) { delete req.session.z; } // Clear the web relay guest session + userid = req.session.userid; + domainid = userid.split('/')[1]; + domain = parent.config.domains[domainid]; + nodeid = ((req.query.relayid != null) ? req.query.relayid : req.query.n); + addr = (req.query.addr != null) ? req.query.addr : '127.0.0.1'; + port = parseInt(req.query.p); + appid = parseInt(req.query.appid); + webSessionId = req.session.userid + '/' + req.session.x; + + // Check that all the required arguments are present + if ((req.session.userid == null) || (req.session.x == null) || (req.query.n == null) || (req.query.p == null) || (parent.webserver.destroyedSessions[webSessionId] != null) || ((req.query.appid != 1) && (req.query.appid != 2))) { res.redirect('/'); return; } + } else if (urlCookie.r == 8) { + // This is a guest user, figure out what our web relay will be. + userid = urlCookie.userid; + domainid = userid.split('/')[1]; + domain = parent.config.domains[domainid]; + nodeid = urlCookie.nid; + addr = (urlCookie.addr != null) ? urlCookie.addr : '127.0.0.1'; + port = urlCookie.port; + appid = (urlCookie.p == 16) ? 2 : 1; // appid: 1 = HTTP, 2 = HTTPS + webSessionId = userid + '/' + urlCookie.pid; + if (req.session.x) { delete req.session.x; } // Clear the web relay sessionid + if (req.session.userid) { delete req.session.userid; } // Clear the web relay userid + if (req.session.z != webSessionId) { req.session.z = webSessionId; } // Set the web relay guest session + expire = urlCookie.expire; } - // Check that all the required arguments are present - if ((req.session.userid == null) || (req.session.x == null) || (req.query.n == null) || (req.query.p == null) || ((obj.destroyedSessions[req.session.userid + '/' + req.session.x] != null)) || ((req.query.appid != 1) && (req.query.appid != 2))) { res.redirect('/'); return; } - - // Get the user and domain information - const userid = req.session.userid; - const domainid = userid.split('/')[1]; - const domain = parent.config.domains[domainid]; - const nodeid = ((req.query.relayid != null) ? req.query.relayid : req.query.n); - const addr = (req.query.addr != null) ? req.query.addr : '127.0.0.1'; - const port = parseInt(req.query.p); - const appid = parseInt(req.query.appid); + // No session identifier was setup, exit now + if (webSessionId == null) { res.sendStatus(404); return; } // Check that we have an exact session on any of the relay DNS names var xrelaySessionId, xrelaySession, freeRelayHost, oldestRelayTime, oldestRelayHost; for (var hostIndex in obj.args.relaydns) { const host = obj.args.relaydns[hostIndex]; - xrelaySessionId = req.session.userid + '/' + req.session.x + '/' + host; + xrelaySessionId = webSessionId + '/' + host; xrelaySession = webRelaySessions[xrelaySessionId]; if (xrelaySession == null) { // We found an unused hostname, save this as it could be useful. @@ -6609,49 +6654,55 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF } } - // Check if there is a free relay DNS name we can use - var selectedHost = null; - if (freeRelayHost != null) { - // There is a free one, use it. - selectedHost = freeRelayHost; - } else { - // No free ones, close the oldest one - selectedHost = oldestRelayHost; - } - xrelaySessionId = req.session.userid + '/' + req.session.x + '/' + selectedHost; + // Check that the user has rights to access this device + parent.webserver.GetNodeWithRights(domain, userid, nodeid, function (node, rights, visible) { + // If there is no remote control rights, reject this web relay + if ((rights & 8) == 0) { res.sendStatus(404); return; } - if (selectedHost == req.hostname) { - // If this web relay session id is not free, close it now - xrelaySession = webRelaySessions[xrelaySessionId]; - if (xrelaySession != null) { xrelaySession.close(); delete webRelaySessions[xrelaySessionId]; } - - // Create a web relay session - const relaySession = require('./apprelays.js').CreateWebRelaySession(obj, db, req, args, domain, userid, nodeid, addr, port, appid, xrelaySessionId); - relaySession.onclose = function (sessionId) { - // Remove the relay session - delete webRelaySessions[sessionId]; - // If there are not more relay sessions, clear the cleanup timer - if ((Object.keys(webRelaySessions).length == 0) && (obj.cleanupTimer != null)) { clearInterval(webRelayCleanupTimer); obj.cleanupTimer = null; } - } - - // Set the multi-tunnel session - webRelaySessions[xrelaySessionId] = relaySession; - - // Setup the cleanup timer if needed - if (obj.cleanupTimer == null) { webRelayCleanupTimer = setInterval(checkWebRelaySessionsTimeout, 10000); } - - // Redirect to root. - res.redirect('/'); - } else { - if (req.query.noredirect != null) { - // No redirects allowed, fail here. This is important to make sure there is no redirect cascades - res.sendStatus(404); + // Check if there is a free relay DNS name we can use + var selectedHost = null; + if (freeRelayHost != null) { + // There is a free one, use it. + selectedHost = freeRelayHost; } else { - // Request was made to a different host, redirect using the full URL so an HTTP cookie can be created on the other DNS name. - const httpport = ((args.aliasport != null) ? args.aliasport : args.port); - res.redirect('https://' + selectedHost + ((httpport != 443) ? (':' + httpport) : '') + req.url + '&noredirect=1'); + // No free ones, close the oldest one + selectedHost = oldestRelayHost; } - } + xrelaySessionId = webSessionId + '/' + selectedHost; + + if (selectedHost == req.hostname) { + // If this web relay session id is not free, close it now + xrelaySession = webRelaySessions[xrelaySessionId]; + if (xrelaySession != null) { xrelaySession.close(); delete webRelaySessions[xrelaySessionId]; } + + // Create a web relay session + const relaySession = require('./apprelays.js').CreateWebRelaySession(obj, db, req, args, domain, userid, nodeid, addr, port, appid, xrelaySessionId, expire); + relaySession.onclose = function (sessionId) { + // Remove the relay session + delete webRelaySessions[sessionId]; + // If there are not more relay sessions, clear the cleanup timer + if ((Object.keys(webRelaySessions).length == 0) && (obj.cleanupTimer != null)) { clearInterval(webRelayCleanupTimer); obj.cleanupTimer = null; } + } + + // Set the multi-tunnel session + webRelaySessions[xrelaySessionId] = relaySession; + + // Setup the cleanup timer if needed + if (obj.cleanupTimer == null) { webRelayCleanupTimer = setInterval(checkWebRelaySessionsTimeout, 10000); } + + // Redirect to root. + res.redirect('/'); + } else { + if (req.query.noredirect != null) { + // No redirects allowed, fail here. This is important to make sure there is no redirect cascades + res.sendStatus(404); + } else { + // Request was made to a different host, redirect using the full URL so an HTTP cookie can be created on the other DNS name. + const httpport = ((args.aliasport != null) ? args.aliasport : args.port); + res.redirect('https://' + selectedHost + ((httpport != 443) ? (':' + httpport) : '') + req.url + '&noredirect=1'); + } + } + }); }); // Handle all incoming requests as web relays @@ -6956,8 +7007,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF // Handle an incoming request as a web relay function handleWebRelayRequest(req, res) { - if ((req.session.userid != null) && (req.session.x != null) && (obj.destroyedSessions[req.session.userid + '/' + req.session.x] == null)) { - var relaySession = webRelaySessions[req.session.userid + '/' + req.session.x + '/' + req.hostname]; + var webRelaySessionId = null; + if ((req.session.userid != null) && (req.session.x != null)) { webRelaySessionId = req.session.userid + '/' + req.session.x; } + else if (req.session.z != null) { webRelaySessionId = req.session.z; } + if ((webRelaySessionId != null) && (obj.destroyedSessions[webRelaySessionId] == null)) { + var relaySession = webRelaySessions[webRelaySessionId + '/' + req.hostname]; if (relaySession != null) { // The web relay session is valid, use it relaySession.handleRequest(req, res); @@ -6973,8 +7027,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF // Handle an incoming websocket connection as a web relay function handleWebRelayWebSocket(ws, req) { - if ((req.session.userid != null) && (req.session.x != null) && (obj.destroyedSessions[req.session.userid + '/' + req.session.x] == null)) { - var relaySession = webRelaySessions[req.session.userid + '/' + req.session.x + '/' + req.hostname]; + var webRelaySessionId = null; + if ((req.session.userid != null) && (req.session.x != null)) { webRelaySessionId = req.session.userid + '/' + req.session.x; } + else if (req.session.z != null) { webRelaySessionId = req.session.z; } + if ((webRelaySessionId != null) && (obj.destroyedSessions[webRelaySessionId] == null)) { + var relaySession = webRelaySessions[webRelaySessionId + '/' + req.hostname]; if (relaySession != null) { // The multi-tunnel session is valid, use it relaySession.handleWebSocket(ws, req);