From 55cf7b1937c6aade27c83cb2a5a173ca5888d6ac Mon Sep 17 00:00:00 2001 From: Laan Tungir Date: Sun, 21 Sep 2025 15:55:06 -0400 Subject: [PATCH] true rng --- .gitignore | 1 + otp-arm64 | Bin 89696 -> 0 bytes otp-x86_64 | Bin 100680 -> 0 bytes otp.c | 2852 +++++++++++++++++++++++++++++++++++++++++++++++++++- otp.h | 166 +++ true_rng | 1 + 6 files changed, 3009 insertions(+), 11 deletions(-) delete mode 100755 otp-arm64 delete mode 100755 otp-x86_64 create mode 160000 true_rng diff --git a/.gitignore b/.gitignore index e6cb23b..a12cb66 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,6 @@ files/ Gemini.md TropicOfCancer-HenryMiller.txt .gitea_token +true_rng/ # Auto-generated files (none currently) diff --git a/otp-arm64 b/otp-arm64 deleted file mode 100755 index 0ce9cc6ee815e358cf9ff9a80ef2b37ab3268c55..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 89696 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 diff --git a/otp-x86_64 b/otp-x86_64 deleted file mode 100755 index 9290febdd8ea993397f5ac6b15c5a9a25d669c59..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 100680 zcmeFad3;pW`3HOhj3PoOs8L)h5jRlT6cI!;Rz?OD#R?Tv1Y{AEO-K+I5FGAL#_=jz zW3g46y3*DK7ZlN8g36$E30gI+TePTm2xvsCQIWjg@AI5<=gx#E_U-5W=Pj){_dMs> z&vVv$?wM<1XPnxqpun;JT018?%#H1;kP=g{cwrW&#OddBa7vuc&VkM@C~t%REM9(E z+X{vFXVbQtby_1OT_-;5AU#0Aj!ms}rlf1>U)#O4#HLQ6=1CXfV=@2WU$OiR(xG2y z-lkzXkyl6#_?MSXK1IPc4b#y#%g$H2WtZlrb6JRg#k*^2>F6#Ntu%Bi4V_IL<4>Df ze^N*O*U$JfKkbDw{QBgfn*p zXLH*TJlz7H<67WT-vXcQE$~US03Xx>|BqV0|5pogXSD$D+yejME#OaXLGE)cz~5+r z&%-Ui-)#ZDwgvv}Tfl#@1^DSL@af+I{>?4mZ)ySmx)$IUwZLc37Vw{H0iVOuj^cB5 z3-DjH!2g%PFUEiQ|856hhx}XM^L7jHeOidC87=T1+5#VTdOOl*UJHC~Y=O@!Ex?m4 z@LAsi{N5J$^lCxRcU#~SX@SpOE#SY>0{qJs_}tb4ys!m5j{^T<{FndliWc~cZvlQt z3-CE$yrX(N8u%TZ{hVTUQ21x(Bj@0`i$HS!+-2~4<>5bN;3au*XUve{L&r>*IBVj? zQ)ZV=oHcytDN|=mpE!KnMN=m_W5!%OZN~I5vrEU#DjhRMQFn+qb;iXq5 z6jbBqjvF&+%Jgwlr(8MFnRM~Q(%CE=KY5ljZQRtUGsZh*v!_m+IMbOld*Z}Pok`=T zmrix2O`JAu#+-?QID6)Fq+m5`;`DLTs0U^ltZs=c&*O$%L|*RA}P#ITTGvvnHy4W*fU83&xg?hgxFN?CIHp ziL>BNkQu8kpWuw2JPpQ7m@;Wn=@fVk62#|pgD48_Vk;{N4iiU=88c<}jPd>ZkC{Dt z{J81x)TxB@7! zM8u-1rg8q)*I}jU)8I3P{OXi3NB20ozhfVd37(D%o_hBQ9*@bEXMw%5sK;cXNB20E zZJ-tY*Bbw|k*8Kpfk_G1|3yr?O8~JYEu{s{u4r+#bhqHiLc*!aYZt1bG@bY^j<5WIZ|zc>U(00#fMhTy*lpjaP=;L#AgUkGm3mMks_ z!8?ZV;~{u>{2La6?-{}$8G>`2X#b53!HX;q>EsaHYDg-|$1YZ|| zcL~93Lhyq_@Y)dkkPy5s1V1zc_e1c*LhyzV{O}OGF$C`#f;*us> zgJ4VMpdinUfi0PiL7p1|TQUVfo*MyMGGA`D_HqLN_6K<`{9%8P=Yk*h2YD{^VSkY4 z0w4AVc`oc>e~{;b9`*-$F63cYJQvQeKge^z4EuvT7s{|d$a8@V`?p#9 zxiCik2YD`tVSkY4LKyZ3c`krqe~{tE?8lIkmo`b_6K<`P+|Y~*8UR7e`NFM9UeNJy#p4S z><^GH8&&wa0m;Ys6z9=u>Auk`v%m0A$ z5@Gwl&dYDk%YTxWe?Kq(R$l(qy!?xK`O3WfioEja4sr%FB1i%SZC^KMc;R|GfO> zy!lSH{Jgw;SvcQ&^N{5F z3**U;9Hq>v+N1`1r|ifP@x;K7c`z-W{G_yfJT>rBWc+>6&-oKD8CV-(w@@$|w1Irm zXD&w}A4Y&KzHn19=0C~GO&C7n$rW`-Cd5;14&|T~PsSR1SNZpy<2VUHD2U!#6@9WQ z8TtDeG}>(d_G-Mg0%qF(;e5w|(HEp5Pofl0-3>-OxfTwp-Y94*=?;GblmzTmh51F2 z6?Utd#2`}iggIpVz;`sJGh*0DMb1;+yZUEhT2;SKJXKDWQ?Ba&Hqbd8iKj+1PKY15 zmzYr+@drV`OV0;JCtg^PoOeS!S+*daiY7Dg`wkf5qf74-cy8M)kHyBQNkhE6;&uhD_#XdH&Ddq8%hk~OX-6B50SSzLz4Omo#8pH+7W zRDiSd>^UuqFhdZcE(6lPZm2}#3I5%9fHrl|rp_rn3XxG4PYqp=ilP2rgWyxoLioq- zfY>_f@!dCwrq~^s&6wrGDFyR)!QVFXyXLs&eP5JT*K`VudKxtGbgTjlJi92U8l8+SRan}8>}$r+(#(BW zO|B+_V%-I-%*}wY{-f^iQ2+{dL2Ny%xb%s89S#pU_ zAWffjeZ7>MUgHNSuXS}72MIM4!3tXgn+-FULg%W`NZH^lF8k%4S)tdf(8CNu@eY~! zSTbaev?AFnQMJRvs$B%tgoiC15-uGSDE0HvWTM^8N82m}aZjCREdwabo2vn({Mm5?y ztI?&RQQ23PRa1RNg>=qW{^_ z)bunxu!?_B&|eBzzxx@KgYTh)SQT1nmBWb<9-&V$D8;Gum|~fTP5XDR$6!iVg{KFb ziu2|`;Qp<+EA3AK8~^W=ohrKXqynfA`+Rjf<{{&iucXnH?FWKQhM*q^mWbfYbwG?xYnFt~*jsiNIaU>LY>Kvd8ukE0_^Bz*d@ntBgd zsN|(>C|M||_LT^Mq+0>{L?!Qw_Y=mVu(8gzS*tz8r$E2n-swE9ZxUp;Z5%6q53cp^t$>yW(1 z>eLf60(ld1ZiWY z3-$6yKk?J2tQR}ry~`p~FV}ylh7JxSqcO5xNWq9X5FTK}=q7@DFTjaP{^h#ND`y4J zIarp)lj~8Dr$VRII7v+RsV1+0b^Q@L!acMZZiQIlHE_YATvsVCnO9$UB2_DIz|4oG zKzceGyB~_T)Bd(=Wa7_$|Dg}mxJxz~x7J@oI+7>dDrlbaXbJWsn2CEU#F~`W4{WbH zjBR|GpaeiK!!}MJ<|JNZz7e~Nm66}Q&qG1Bdq2aw;}E9`!WM4g!RC0P{UOd7{ogD5 z0)SpS?Dv0V1l$L?kawoZ-o|XI==HwZ4xVDWQ?L8qL!zzUzr3$5xESc#Biu|ZcmWis zuKT52+QF?LV>>{BMTkD>EW%Xzo&pSQ=VE>yPgaxk@ldCceBB>)qj;Q~25pUYjlLDv zMZ|feA?XT`>TZ$L=mOxab-%D8Q3{>>Y};&LJfHz0)PxqLFK0{NU}@6z$vY*&Sn!%HcpwbxHRK=j=R$PO&&U$|#G6HZgpWk& z2Xr$!uPzAwH;&QZ2T~CH+ewJ>Tj~pz1Om-+|E(@umeB|5si2$x7KYM4ANgVx;4s)Nq(?? z3r(8`>s`V^+`AYR67o9@4F&7J`_V-$1ncGRp%7Jg>y<%28~cZXT0yLIGW9(}fP|t-Xc8017fI`w*>G4Xt`qwdxWY2@3I2M2L z0{p)w`jiuWs_Iaun8ue?V>ho3F~jAul13Lv45fzF#=Y3qRhWI*Hdy{I)KlymCmrKL z)Tuu>75jU9Y4piKk*&J4d;$o`H3D5cJhC+r`#To%7G+UY+3Vzp4)AKJ0ExKXa1(k* ztX!pJLbhZdDJknf1vxHc-k>ImR+2?)fQv#5x@Jq>mJ-7NLzQf27zS;!XpiJF*d*PS z=vJ-_+KC2^Q&NA9q5fMI=dxVt$Fn8pDs^F8YM0-UpoZ$|qmr$k67qW;8p_Tt<3=$K zdPXj4UEs{#6|26Zs_GBq;_hq!wnQ(e7EoN43~<+icyDahB z7vt4Ket3G3gSw-GLR%RHDzw;TK*GF_iGwXTFH#`ooFP8jr2(=dJy=dzECWFE{z z1Lw+*sAS`y(ntp5x@8>q;(KDAM0tT0YvUOFU#nvTl}&o?WFY&+h+&KbDNz}lAjw-_ ztWgqbMn&I7iqR5e)jp3za3jh5K3j%Y0(gMgHN)u#b zwh>AU)>cW{f9)MxaaG*ONO^*{V}V^*p{lS5yxs`h z2p%DU19Al(l`C*`An+^^sD>7ZH;f-kdxIODeJBRa)@WB{#pvJZXy_--(_%BN8uPt~ zCqaCGcrkiy3SFV)c0@B`;h}S~E+Br6JBR)gZ|7K1rbbz}O=8w7uhV*{V4}TTk44fL zINiP5Oq%{lPFWXUimVR4qdA%O%9o%Pu{!M~i7PBsnJ;iu0Oh72BxEu|ksH>(llap1 zZYh=KC7iz<(%S?6(tss~D*-IS?c5cvHup9kTx%A&uZp9Kw z$`>O^dW-NkZwN#!5z{H%hxH{-9pVcsiO=2N%ehzF-YECV8`i~gz|8YXzoDzj7cfb! zAPs6uC8qte2M1$oYr`NFFc>NWU3tUzWPrB_La8dtw$~$v!3cCxWQoCo<=RPgh5KZM zV4)mn|0Mxv)*zn-LVON(DYsCl-I@?DyzaS>n52YU|LsZ>n4vWM}&KDtm1c zGbkhNuB34MOS{X=lg=@g&|}zC1u*jzd(yO9B?vN)5XMIeF*;Bd$UGtp#sVB*bim6c zDVH34&Yp^W2z`Ef4TLB?dL67xh$>BRU!y8j^IsqumOE0K_D3*lYR~%g>>!Bl!AN%o zmi+z)Htg&iGHQprN2=D;S!r9+GUND$k%5BTi31Exh`gYUjP4BlZJk^Thr$ zh>)W=z^2`G;7y(DV`vQ`6+0G`hYijr3)yry4p`Va?11)`ZF=e#Ko=Lr6GFvZ8KE8^ z_RRV#+$la%XUKM%nTqs+CeT0Ei^r%XI>(%!Yff|r=IkDrGg=4FAE~>W7(b@&=(R6i zRj-}A}I+y2)73n&a zI*nqz$Z>{tFldpz&}92Di`_;lEs=h)7HA#)Q;|~!d3Vj;YqHy3A*=EV_K(;>5f-xA zIPg?CG66uM@P~n#-DffXL!xaG)VuK{AZh^pLem^m_mDK(Djc zf*-oYByp|sIB)opHSSeT2oja73#asOv_C$vp~HErnxdqZM%W07wXrXpOgVa#ROHY5 z%f}EWL!CEYQ4xoUI+1^v>^_?P$Yk4Ub|OY4=%cOV3BuCu+c2Ov?4Fc%KVUV3{b|ZR zj#rq`eK1maf)t$37(g#$QO9ZN#!40c02B;a*fy}R9%Ftg(umWnT>tFFRnmvN|c zIqrjf8N$VEId@=BK;LjFof%zp2MAMTART-;QM%tfA9~e_Q8&lp}mt)F6WHs=M&Gdy459Rw%O42Q403<4zU;_aYYnglr z&@YHSi31D3KcQXJ#m0BIh({Sv;$*4;PetC|SE`hzP5K)UZ7uymZMnQiZJ9xZQ$=%` z&9miRVj5e{c~)5Xg}Js|mbSJG=h|Hv(3Z)>OS+xZ7A6ipwMiL3*bW?6%z~{A=3TaN z%>K%(SeY@=w@LTEtV&%4Ekd#K-dd~FbGp1PAz@CF>IT!$eK6qW8zy~onfxwbawVAr zZCI2j;C}5)a_zrn&Us)m$}ss7RJJHdBx(8G_#}kVO$}FM4XtZ(#TEx*TZ``6yLm-P zRH~V}57jW~s@`Ct-Z;eQFimyP4ix)2FIsOrDw79QDOnAbNE@E4B;aT5R3nWqscWf^ z!DO{wmdkrqz|tJ26or2(Rn9Rfs*gpXoM2R{j4gFzSvgI;O%`!Zp-Lc+(ZbW~#@ zPL+#D&#eQ{%#z3A?UB*;k#)+z=p)uay>-v$X#2Egt+szbR=XsReTo@B>=C%F7(x=2 z{Ogn!!(`D$B6+e3(@0(cmFu|$q~&RUg9@=K#Bx>Wt67VDxMroP4U$*iQS)HbY}Pf{f}*UH?T8HR(9pG zI=P})lJZVSlHOkU+kBRzm}&p-c=3h^n@oyCC6i#5^RIT`iCNAMD8ej zlvj`xy#*6ps?USP(kd;KbHzrT2&m@=sP9B7?0m1<*5(Y##DZ0rxp8GJiRsqYoB2fl zH4+|{!|wwQ-9>Ac>IQSKXRSi$QfY>S!q?B!Wo9Pumt1zI2kbuJrixcyqME2UX#zv{ zkO*dn{E~9=?*mofqO`wDz~pu^Nx8x#Vdh>d4be;zrKPNx)t=gctZ!5nn_IOZ>rXPw zy|%#|YnA9VZhD~CTV@FCkgM19D(me6HLh1PCeOefs6iT6_=32}Uk9p?aoqwY&vG&x zXf1Um-NjgPHKR?v(K?|_q$tTTMqJX3PeLda^Qj_hq;$;H{juk)?lFo@R5He~Ymf`53WWq7j{=ZkFO~#o15s)ExVKTjlRS7aV#;FVDIS3yMgs)s}Ek9kB z0~*O*(Lq@OpU_v>L017x+rkI>4)Pn3>^ z?>kkc~X^lJ0o?dL}rw#6K6;{Ui_gvdhLflWMRq^{eWR%1|8+3EUG_jGB4Q9fs!H5 z?v-m1?KPcHbP?~u2WlrO(u0H>)twHqi@8P=zd zt`ZHb*X~AE=RwD|raWWBiV_i9IgE*^Qp~^5Bb)hG-O0iHtEaAr{m+!M=!NR-r>0uD z{d46W5y-7%W5df#m88^S=kJt(<>xB{!2|ou(`cw4<@o4EY`vSVa(~7s-X@VL-cBIN z_t6m5T79%hd0SmQ@PABW72?diLc?O(c%cB2u@7)OM6%~77hF&B z!AmP};V|OAg{n!rCr~^5s~?1>QUw>M1cc*R1vq8LQXEUg$A%u=A1_#k50Rh53{ST; zRR0Izs)V8@DoN3K{^MEMLNl3idI4W^i*5P~p+*C+s&6W(2Q_N7Xn2BsQ=x;+9UuV~U^ zLBO*;;S`QuSWp(?QV{0SCgAcgD=js`kMq1Rswo@F@cKYnnrLi;ix04`Y9@hg#eBW< z-~!r?w&&oOF0Mh4p*>i1^knM+Tj7adZ6ge153Ud7qrb&Tj}biaghh;0;c?{DSpjLk z=}G!o5bQCkuVj?ZJ*G>2LT+BTse&C?vU2h^dSb;`B+dL?E`^@^tl;EXJ(zM~b=1YX zYBpViJjWo@-qp70?x)E34DsF^WI5}Z-_|y-5YEMA_$JkIT@dDqsi-ue7ZkCe?AKYu zxnV@x*8X4-yJZmBuSq4$2;*n=m%s>LszX?IwzY$rDtuo!7qkC-aohofgYHto~ zHk11IOiTOfi5yOY0!~MQQ`)^6j9{8m4{3bnVfaN6`Uuz6q{;~@cq2ak4e8uaT(CZg zmr#*E2zQ=6*5JPrRhAxlfq$Bb4h!j=5i*%P)(brqRG5StR-p z3rJRIQ1+cbh$|#XpChaF^ArXO?!X$Z_+ zoE$GoX*p)5v@?6Md^CN5(NUn~;xHU~QXB$<-nfq_q8R;a`0gQc8AP$S|MoFqDLjxp zPqQ|p1_Ua1k!eK`YhB1u-3-0&6W2%|FhzA*N-Z#}L85znB~PlnWUjo8k6NEp>zKTN zgtFKmt@>VT_K$D{!j{VKWtg3i%j`}tgL_0aY?+Tz_4h>Ots#~652aI+dL|oFMM_M0 zLX{F=;J0nUF!0-{5WPkwCuzT0Wm&&<(QJiipZ536)nqHOs>!=L)3GYPD|jo63Z^23 z364=1Ug@J{V5Hp}upnin4N~?N zCp>U>AX6H3D)`nQi2|IFQqeR=4Zaq!U}hIznBZwrj#-m;QAO0`KT#W;cQVSop~zOb zV{(-{1X&@&c$ahS!;#<;8r%|_3NeSk@qjWMvmMH#wvFNMj(Qv)-sa#f@R9`r-m;*Ob(3&rH6o85K&uO3ETlQ+!PYv)BtCPKKu*? zAs_ySUOD+&>y;^p>|C$BMfC!&M6+JGgF%qzm0hK2;S(w22Ty&J?}uuV(hqz|Wc{$| zA=5H>M@j&Fn^1Zw#3iD<0pPncq~3;P}2UZe>Q=g*{J#mrqKh3 z(MXpPIjAn|hf%_u8__UDQPbaGooQ9WO!OVU7EA*wZM&(WW4;M2ur=b3p&587;~vC- zN>oyugY#U5uf&k5%A;7$3GKN5qzrS)5J&OEL!>UST;$EM_UdW>vt`Cg1iZ05FW^6c z%He>20oNe{(!hb5Bi!BQls4FEvqV7^#V4KK?bWAc< zp_lQrL$ohrRxYTkz`-$;AQXlWixnu@p=Tv8{ijuM!; zHr9Z!BHy&%@BkR4Ml2?tp+D;)|ly&{i(4Ys^}&g<~OBa}cU+S+#c3n`V$U%*8> zWE@nBTumZu=+Gpca(Iy%p-%g~s9Y*C%Agz>LV4eybO4GlV;c#X+ z<~J5|v|vhw8{f1OOhxgEhKLeUH|wQ#-4fM<3E!4*QjY-yIT4gIfCxBHOqe_f?i@g{ zpCJ0`ID*RjEv!WEYK}!s6}FMraCnq8y$fp^TTd#Q3F7i&^ciWl6EpaiN$N~>q~OR$ zu>1Uu)FQT=y7TA@e{U3RB)|gmr|DmO0~>XDRcrLg$Vf;%p=TM!XT|e|;gedqJx6=g zexeoqJPWIqTtB}8W=Z!DD6z1Tf16-P^hWN8WPJP~A3*I2&3~V=VRlv)zPv>>(FiN@ zr8B(9ix}IqYF9NAIZ8wpi%9;Z$oXg0U)_@WR|LEs$8~Sml=lB}FIr{fB8)PoHsD0MSSkzU8)+iG-~l&;FBAE6w-Xc#{rQ6f}gjvcR&nsmDoOW}HnfZGd@`nEV2%OW0(Wy+4cxO8JE zw_E9Zf<<&a<*&1k&icI@m?_mwp>2_c_8>Ug( z)~)|G?c_kDpLJ>*hW&SeQXHJ$U9CVtPItAu{f@e*Jw#2s|GQiKI1Ii;9Wx2KsHv>E zRJjycSF-86_m}+GyBRVW`(y&lpo>~1nBgw!V#OQ_Owm5C(=iN~XpkHS^Mg(Y;Gokv z5PD}jooW*yl>rvs^CCl>~<=G$efd+@mxmZIe ze0v+lQz{s~2cbK`gneyfUw|EdA-1z14Z#z>_tA@N+Z@*IbL6Po{cwiSEoY`$ybH@K z{coEud$B|X6thL$t@a|9EK^>bt7WiJ%|3uAY^goVpuEQ%BNOv5V}?vi{`^PikK?5E zm$48yLGRuK%hO>fuj&~OACmJsu}=I(B;l$79Z6SJ-X;QAZJ{e%(x2x(wi-!i_H65g7i8GvL`>> zG$AlEDw`G6zwXO-yi+qGdU0asP&H$aozd5h)TQ<#(!-&bd-fR4FS-n5C!uL$styO3Dp_ zmAeF`jxEjs` z_52-bDbrpnbtyo$o-y%fJrfkJ=QO-)eDKg8w4PrJVYXZ?$J~=&Egsd$R?9C`y9(8g zi6hk_061@Tp38SsE$yHZln$ycuUf=%u$rx`Y_&{4R;y(Q{9vlZ;_jo-qJF4kf|Wpx zR*S_s7z{GlaG=8$m%*liqk_3zbTi;0=7mLe*R6FO=*6Q^`CI0*pSzL;ke3@2(% zJaz=@A2HlodL>wirS%srsc&N}wT0d36`UecJ($4KbH5kH7Nbh3R z*oIVW1Mjk0%0^XAe*`!Bn@g|0M9rmRweT#G_n2f(0|g}DA>$`oKe7WF_f|tA1B&+4Rif-$Y zYvSYA#HUng)%(TC_0`{Y2DrN5r2+|o{;|5N%ONT(1Whg0*gTrB%~K`yi#U1pcUYdr z>aY!&sB0}VUn-s9>3G1rVSq*BYpNStCD$Zt3aWU*B36|wL>b~t_M~i_oG`Eq=bd9! zDM!H}tnDIAx3rpLDUpWl2TOMlwueBOys#}0{c-~p3hbVU)fUK%slI!Q(M-jI8@u0M z2t(Uw>-jIRyV0`qAzrJ(G^77WyOS(Cwx<90>>3srV;d6lHZ+y)fnTs_Ds7*bx2dTV zznRg{#63hz*%21^-)j1&45oF6gbmeod$4_?owjHDL_6Ij+fKCx`^Vm!*I8O&;f~sC zP4Z=YGb#a+#caD1n4Oxp{hHK>t&0(aXl-@T8@~F1-EUr`7dM-}B4qL16Z$*He*orz z0$0R*p;ycxBXC%DEHhd%;3(mdR;f#k@YpPtMyQx6qK$ zwkLRTfL=Bv7YIa;h@o^8Zmc<~O8dA0u?_Qgfsc`U?tH2j|1G}6F( zw@@3X9s5~o3mNa$>jAP^9iVc}AEgHB;OH#`<~1z&>rNa^3`GNlYkw1eF5u=ll;Ui5lst1HgCIzLc<^ zkR}3}wIB+!|`B@0~f$pU(xWfvX)neVevll&H zh^xIi4s8TgqnPS{c9W@SW>GjwuZgO$HC>a4y#l3Qq0-SC$2T=K(O=b%>J3~B4c>5d zVGK_Q3*)E4pP^Aoj9vGw);sLgv$T0znk?2DUhr%sM6hyrWQl z^YLWLj*9s?A)j^A|5wI;`7o8qH~w8fRoV4YuGg>OKG~A4+5)Q30`MOUw3T`Q)*H6| z7>!b?Jwi2&t-Q$e+d_lJVK-vH^CDxzussxZMi{o;6TE(5*hYmN5{A94u);8G4Pg>Y z==pi3tsq=_uTtzAhAmNwy628YyOTinmp57_oGTzO<86`FVR0zs;QDd7+9d<|nacGu z0|Oh!KVG@w{A$rZ1^#x3e`{>F>4jGRN{Ok@g-M%k5a|sdeXJSjI;f_k%aruCFzE|Q z3eOiMn~{0}=`@ns7B`Sv_j3+nK2>rQX5YNF{+uw^>EK#FG?(CV9PFfLhP7SkaEA<+ zI*Pl`3lknoLSD998isZjB_oTm1Y|$30Gjk=I(7qKa!vFN#wPy!9S7P-MQ=>N@l0(Ku^S5TGgh7FzZ3U{!fcm* z7nx=|WweAU?u1NK6~im6j8)j_6-#h`?};du^%92OoY&JYLere?vX~R8{Ob2cu>0}N zj@oVi;MPs)uDSsG1xoPCb&bW*+p0J<$P+LjS3vmvx6IlqeR+Akq7F&ESR9jLA$Et5 z=quP2Z_*~dNBa6EMiv`}oiPug*YT^9h6Z~!O0zJascuG7q%>9z_BNGfXh2iejOL@o zIdafRE6u(E&B@JZ9wLoya|O*|3IYHx__`Xn2Hl36Ll&J2D&363lnxClct=JPbsKxQ zFvMJkk^?_5Wu;7kD= zZE(}Tm5mIulawC}=YlX4Z8(o7Tho=b+*8IWMI_B*Knhi3_4A|aZ47+r<;Osbs^NSO z87hrY%{xYp!tzS3ZpMuM|7X1`yYq$$=V(M5c}egj*ugWTI@nOPCY5vn8P>^;sD6R< zhU~GR+jlZ+@W(&;6I$r{WQ{jaNU+^}k1`N>c;}QA`yrzR9OhW|FeM1H$7-zkThMvr zHi1*29PMT;qSx8DK@{QJygQbV3g>@a3>XKZ2N4&^fbz5!Npto$pd6*DOxkxB!CI&Z zLvuTh8-SNnmE6V0kkXHf^yCUTU7sxPid@n=1b_4E)hk~PsIY$eF!I;4hj`a53|y9` zTv8_8_wd(m+Yn5K+;zn9+?h;Z(}m~-;4RkDFO8X_sW+z%nIqXnc#(eAeiWqLGe9X; z;b$^1Nj8s-*+&N*sDZZ4J{D#lJ7ph>vyWY~kG-;w{j!fG*~fVHaai_oWcHDrxY1#9 z_HkzRkz=1C#1@#8+vzxo7yMS?Qj@PR=`xcpH>uWbI#y-!vf=}Mjmg)Vw9cfyq$!sb zYYz8jUXj6GYK*lp3jM)iw^pl&)kxr_TcyTMM^D*)2P7(4`3{FrcO|*VEGl|kDbW1yH)n%k7z(i@a5e4d)*x^Aa6B516Lz;2@u!Fr zYp`j)_|^!Y5j|s>*qx$z*9Jt3aTh5{@a9Ai+>emMfQwAh&EPrul!VWuq`Mh8NcM~d zk;!P#cFSk6`-O55-T5P;1Dyr96R|&1#d8}{uT;btxWI5e$MD>@p zH;2b4MMKR;uPX)%^Mb?+YI*jTJE&!M3mWQ}O{Q+>8n#rK(VHc>J5r2}tU%YrxLC0u zD<9YG8f_aD5zHHxT;LK1k#Zxw{}YDo8i(9EY0>9zh=k=6(PZ# zEY!A;*U#Mx1+)F!IoN8Niy2bR(Mi8IW<9aRK}cVQSd#7^LY&O2_#?$B)al5!@g&YS zCPy#BiU%v7k)4oVj{HjGi;?e&d=>KRFz?68bwo|7q#y1SPsJWg)P0?dJ-9aZl(RO5 z-zxE9V6#SsaBWDtt5kD(_q^2{d!$6n@^83?F97bq4=68qr7Z~2D~5}wp_nIS_?xt_ zB+U3RxK7`b1*f|)LCpSw_q8C6r$*<#fGFlP61@CeUX#EpU$ZUqso6H{UoP12O>@oO zL~(fW5<0I9U*VlNf1Vka`0{sucSTa;*b~e{(2q63O#Z|ZzjNfyBVSImMGosKlS3_( z)o8AvH@B|2=_+|_I?q}w-V2>A(KmHiiGwIPVx3f%$SxAuqRBedq$*erUBI!Ci#bI6 zSB&`38#@V-0gAv?<#(3>SLb$<6}7>C^+$)+kJ>a-O zQM=3o%7MR+ZV*jm-z61{L%-~$GIH%LKJg|RhD9dwiT4sd;rf?$E6F}Hirh>&ME#zQ zOrjQDY8_9aA?s60Zggn3GD0G`LqU~9LpUxQ;@=CoJCd|G+lRy{!#5iN3rJXe1;(U+v*j}4Dj+i>h92dhk}eJnDraNhK@?=e>Kw$? zI=VkuITFE%3H~ri%HxuFB}mS|QFSEY@Z!h_-va8Ca<_=s8fe4}0&Si2elD3LS)W8R zhqOMRF#Ngz65Mx#B-nuYtrAHQ4B13udG$MzG%jRsYFa0?RL)26lmdw=dBSzB++9jk zO#sniMUe!oPG)V1BGgg?OP(~aI6tydCtVKrNes79bu8Y~-TCiVvBB|YhMZY^*6?A) zL*vP|@gybyPsh$V=j6e$?%m_bcwxGv6Y$!tEja*9O{alg(TGLt?Zn;Y_1JW+O3R%$ z@KZBcqt24x!fzu0VpzxqNpUkSU4MkjQOadlK9`M&d|f_<&2YgEb@^0-P|Gm4`OqIu z?1EbTt-!3~1_GRPr$SoVos7SzE2n-GrrDIw^AqY~`2uGNVPevBDsw99)$aL>$X7;@ z#WACxOhP!8DB}Sl@ao$G?M1`K=7@%It+LpegW}Ikw_P(LUnGCNY6o)o@K{?0yTfi327?PgIg!EB>SxA6iG<$F!3dkzUdB zS5pD8`*hZH(sc$PNmMf7*Woy+2s#X;aWAs(6`{a5fE5B>_|eSW0LBG`^ zrD~KTn*DcFE$OzU0E12ygK5WkiZ11{kize?5roT2RVOC;#ro?DL=*p=*#R$h`dv${ zPKS$5=Ipsjp#s-8h>3G>oQ8YlwRDHKqD1Ia zGvEyTTEYTbjgfPVZUa;|;S7vBW?9`*oIj{GR62nIQ%zBKGHb_+oMd=>gR;zU!Vm!` zWh1~oA_5Ruxj#4hCgtKs)S#y>?zIA4AAk%#2s1I^l#PL)?T8&JaU%rjPm6*z0Y3l` z{%KK~i&0)mWpZ+Zc*Sjyf}($1LOv2l305;^z+K|0qRXG7If7*O(foTzh;sYWqQX+N zYstyfoI2HhB~FHMjO7A;xe+;(67YS%u`o>Q){9IrM*RcfAh*pdF@R;4**e%2lYu3i zdx`qq5;adMRp+CBdr=H#LGXFrzTk&rT#F#mII(~;n#i9J!r&#;UWcnLr$+RVjQjpq ztix+T0!OF-wxtn*Wl(CMmm1}i?q*^E_mg;$UyUcPwEqP*rJe?h;KGXo#ouJq*`Rbi zh^THqT7r++km!XM0sxBxz(U*W?wDf*>bhdO*4#7Sam@HTe4D7`U-T9mL#AT25P4GW z?WtaMFyPkRm&{isFG0nsgg>KOGxkK?P8!lv4b~yhBgYeSwLqP{zPaUz^q1OaOHCxe&n|9f!L#AsGe{|bP*%oW=BOAU5_lhc7)O9wi#CEuQ3Hy zvz^kx;Rt*FS(=w?a??CD`EoWJ-e^zjKn>L?QGa1j_XjGfp#~VV;~FGd z4dw6fQMz!{r1a!vIU4mg8tosdgc_sKCvbsi^sWIsG2L3YCp5ygH05yuutIw>uTB-v z(1d%nk^Ls@f~sfK1qXx#-e&lhT7h>Pz$p-j?u~jgbnl&J<^Q%dxZw2>3alCpn1c&q z7QgfhY2*{b@=J{TaMTSq;

#NhFzS|`s#U`YNUniVUdPEnvv8>pi~5a;y_Qf%Uh z(ryEjL?siPN@fH>Pw7TA;KIMX-3AQt+uJ7t)r2FKK~pDE7wHa58NK+%P1_|CrxYuA z0bsGNzm6?^Al5_R75>3tg1rvig_(4^`_Q+l19*y>_&q*q>lJ-+q>u|$q)a$$ycfe7CBW}XiJ-_qFoy2i7sT>vNz2*$XReeYwbuC4 zQXipeTCMlb)w(^khQ9D#?!t6W7@M8FV%A!x`yE^)^Tm@Sr& zorn98NPt<)_i!$yVvcGby>e%pQRZoMj<8j($pA8FEV6wBa-&5)F^i1z5yXftatXt` z1&TXTHyduNfyqoVw;NTr!C9PQoW!sWWG*1RbXMSuSBIew6V;_>`r%^4<|U+o6NRR2T2`ZF zwOUr8z&a!;cNzZbOXrmPES~ueK_1q+X=Dr5x@(cpl`1^E?f(pX{bN=A%kT{GVKF(f z503t_Cx}YT7jJT$3t&WU6U`$NjmXgunK@pj9K%7^F1jcdWI99_ z^$RlBaIH?1wo&(G^ks=kO2cW|AF#H@_x#8lDGKQ5rR^hCP9(YIuTMwKBsQXl()Nde zxL`du+;C9oQ0S3(h}Z>*hsX?A;csWvk|%X8r8Px=sLcaJ{JbtkGfBMe$*`#e1c9X8<-=!XF{xp+L6v- zsW(iJD*hTT-YIo6cxGOPphP9P3@Ynq967(IF&_;6HCvF?%+r;@6NbTqWRMIP#LJF1 z_0Hh>W0-Ic5aPrGq{vv6id<$`Od$(?!m2u)8g=_4)697ng_*sGL(__(!G@^qV%Y6R zcHc-yFg`j*Z`9QpGxMW2y^oTGm8{b3kP^L#??uD1ekxw@@_q;L@->W0xfH?Qqn9Y- zD}MItrXms&6!<((kkbB}TAc?%4gYrd zCMjYxl$xUM!4d6km)JV2UjNm!zug8VXQBZ>#tOW<4eQ+ zw?o)$uhuti|(YYEz(W&P;6>T220J3|C^RDtzjd^ z((k#w$fKZVi%gCAkHn8^CVma=uo~j~+UDXL;P1=_2m?e_e-W?8GrUMo&GFO($_~*S z540e+zvgN+r|%D4Im?oEeE=C(Y4-{9RDW?wp79l2>ed(K^oP}nrzh{8k?*zLbJczp z?ts_!%W_$yT;Qr+jF*EF!XFKbA}PiofJ&}XX*P2o1(rJz4<|u2#DS_D>8Cm4ksg{e z9yvsF;*qqtLVfUT$On9G!3TF?Jj{)pT^R$`O=*ASh1Lg_OV$TRu_eL>EP)U55L)!X z2(ZlYL4`za^Rz)?Ya=?I;`k}5c*7onN(Nn6}-OX?NveGy4yZdN61-6iCStJQ@L zFV6L5T_rb|Z;u8F?jt280_SjK>jz|ct{E5boT@yR5)+rdB5_Lh0I2{#y6FOt@}0PP zK!)`t(?V7p3Et?v#ZcTMNgY2nLZ(Ke{xH(6Y=i(!eALo@Uz~M zxRTSA94?;#%qhh>P{FdaQBL;OxK2AP!?q3JA{mZ*oN{ac;P;8nqden8-2Z^A^qTdP z(8XLlRUGPfCQTM;0co-<{zMv#;ZFV0pgG^r)aNojBbR2HrO6*820>I#gIJ==d0qId zkY!L(mPq*x;_>gH6>5NED{4RnP`=>!?ou`4JkcOBdzfZVw;E)g!DLq3$8o4|PCQ|o zjL_^ENuJw2z6533y#p9DnywXOPP9H%Svv0gnF39zS2Xt$%9Zvq6E>npI0I_ZENr;a zxpQrudOqBs}JvtOol>ggxdBXwfQTB4lKafgV{wduIjh`lEs-wk&9R zGeO%%K@cbSSwW9Z2`st`YQUl)JGN-snJrp$L^DCZg`j%Wt$NifIx8!spGq0LV=1?_ zEagLSN!V}QA%*<`os35D!AQ1rR%xP=f1T38h57*3M%_7dun7vU=)|#s-D_FJxE+RU z&_GEUT@=^qn2Q{0Embb1xcq{7*?bsA;ap2c6_X{1wDYnRw;lR`QVQ%w4m0S5Pv{YK z@6mdYV$72X?HP4FLSfUlp`8?$jPQz~6b@A}@m9GjsGvK1_(FDvT%OvAQ(Mu(r6KmR z7q&6ONo{%JHv$Q8Orcd;Y=?sfo=YC4S*88yN>xG5DD8=?_Buy{yQSGJm*)6fn(s%L zUMIf?X$7*J9z-r9O|(D6;djA#y$0LtWHH$n{%{{Q^jGElTIPj~3~o72{ci?I8V=<%(Jp07k^#IS495$|;au7%Fn81R1F*~SQ3-n4=C0{$?C1=1(^|}r zR8jXsjc#Xs7x-uU8S2Z=AzA$sMOS4=gBOzvP(yI2eCQ$(KyLdhH|^G@TXQq7!*4mh zYk!*c-M&Wt`x3MI0R!$<1xK9;6`S&Lfil;!y?K!?rXY?2WT(iRh_v5dv)7yK5t_Yj zh?t~$y-irE=&V19QJ+Fo@G|cz`Urg>zdAS~E;RmU=U6wi70E6s81SnRKm3VS{gpm< z9l&@6PR;ep9ikx=Wklm3^eK$aZJSe}3GNg|Eac<9*bKKsED3L$Jgo~P`u$GvCw`mQ zmr7yyUOHIS?`qYr$EQHjuf%a6Fm^KUHlibfA5stmUWv1V|04+|2N zOoEeUH)wP&0c=$%A6R@i_(SP*AvIaHr&#{Ekti$)}7) zTtYRn@&>qzAt`$8kS3Ugxdz1+h;DfGzKr9_#;N(s$59BX5@{8f>K>mxvfWj{)dCJZTYyePu zmseOV*@AaakjA%=T50vz$>DRh7P9$85sJeXl?DTQL^S~5co_cTCq4xfGnp+CTzqwqDkq4)SMoCK7{+4rdnNTF7=GE`E7^?bPY5$GClP+H1d0oZ9)p$+ zCVwpJ3xo2S1|714rZKZU1y(LQg)U!Ffh1Xe2mg9Y@i)Jjc#*4rPyDq1#91V88%T#& z$Vl|a(gjk^4q`v;%(Mf13+VEcSy#yIh2BOTb@ju-k$9D^i{x8Sow@z*CP+c=TM=VodTO!tP1u9EoqLJA z$?$g41aB{FXS%i2f>HC1fAU>27X_f8jO$)0LPMAn$rO?^dRvN?kD#u(0MRE|FUMs# z5s$8e-%YQJd+qtA(cvRqhM0rfkl^DY4lvJ-g@MIHu#>;tVPIxHhpj*qejD!^9!RDr zxgEsZn$zKr*h9cQ8FFI|R!=`)34Rg;!lL8DYCyK*FB%FpxQiT8qv@n;%66p2(rN!C zHCBxYc|+9r;S3D7vv(x4^?ys~8O?+)hERx*EN zkdl$J{edV{bo9EV>U0sevv-8x6R!@pU<2Sk3I?mF4TkPvRizd#KOL%`s4;20($sX) zf3E7qm;RSjO^yzERaAZDG^qO9|ImexT=HMis}b+YgW49o`amxmQ#)I$*|2|u7iHDh zd#zC0k-bFKQPn%sO{2%1TtUk^Vivx$J!51EomhbF59wuB={a~=N560Y-c`x)z%LqQ z2*ZGKEiJ@(H1?%9kA_6<8RsZ6K6>Lwtd2v-E@njdy)7b@Vaw9h@sq>e3+IR0$OrLk zZ+s{UAJjs8Gyra1_g6(o-<4eV+ zqa>G9rQMZ)v&z!$vzFkG$ST1A0Y(@7UjT_OO(_cwm9~2zWqkaPj00Z#U9icdgOn&T zz9F8(M3jRDKHlnM1`R?m&NiAM_VrUa#^JjvQ4%WPQYdVFQ(2z# zoU#GdRHG;Eb+nM=tzx{*8iYh+N%Y38m}_F6H?JscqpJOrHgcpkT&#`wL)W$4A z)|KlliJ5-LS0V*Ihtck1F`LEg0q;H3a3Vnh{=;TOTZGQu=Q+-> zGrSJ#QI7llUniOuc~!8v<(HNxjE#}!0UhGfIXrTE1zMQ6|ti6I5PV|RR1VSD6)16j9FA^ZjmybDAoPFbNrGH|(7pC#-E z1QGqfMk>yq7dgcOy`m1s(t4SvrO+z5Gsc5YLgI;y;hAJDI9e4|DbAR$1vKqeQ4jpf ze2X@UzKQ|&pq@TTO4yHFWYJE9ODKu#=Z_L zmhN&luvn|d*l=|8Cv`KubsXO*+wSBii4s*D#Ltl9W@Z++6Er14Q&O<=a%50A!`o{Y z9D#-*+(GvuKl}#$5P#|E-*`xstKc_Mx0bVGmBS`0=#sd1!rs}^C$v=0uAO}L$-_AN zs2-YJKO|WdPksw-e*rg6mSp&R>sMfa){zhgq64E8+vva6mo^o2tu(AV4JiY~|2;_Y zCQH=i4NZ%}h4aWa4>pCcRME#bN`O^B_0=a!ZKnNSP=wiRs+5>c`&XW9hnenpp@G06 z1?JRGXJh^WaKs@27FYy{*{FLHtn=@z00F*uk%m*r1Wi(YaU&(bph-4C6cBxCf!#`) zBu!E%6)sJZC2W#{h^BKc>Chyj|7T|OQh=Wy=uH<(v)rndMHjtAQ*qZm^JeHT5$h$E z2bM6Fd*K}@#>W?^0RCleB7$usg#L8vlSWcPI$5pzTThbKcQUkAYtsJB02vjuTK(Zf zH~r=F&?G3ueeIB%c@W-Ah+2pc>R}@U>sih|Ea&5dbEHc7`74oK300g7@4m3HaxAuY zSYy%ERdC@V;3OJH^7k3Ef&t@4HE?SI&Pxh)cTnSZxFDDR((WsGVgY`T%RJRz86vMf z3FM4dGbJO^PdiScE4jt=EtMyV+i**V2rFxkRU+8nCVBi#hz11-0l_0Iu^uUfhL#93 zD`OK!xlhjtd3iK+6yxg8QS5^G+^PIPas2rZuf^CNH)c@skDGxx1I}YGCi70JJO_3= zx9u|a7dzTFzs#uh*+B5diI${T;>W?3%|=>5B@^9iX;`e`=S3dw8?t!{jY3sAnHM{< z^6n)8vtxCmq(S(|UdypPNGA>GaRvilE*+nT1zX>tKC;*H%TS@&YZ=Y?5oP?i({lD& zo;+dah`85+Hzk-YA^Jt{{JoY<*UihHlR0bXpR0sjLubwmslizCkD(fNL&d0JH&pV~7>woPe?^Tc&D3~sfZb5Z z7y1~MP5%|4-{TcPPz7T9aS)1%u4UDSHdL7X85M2!DKuQK4NHN89e@4t@UY!bQBgS? zDg^E9(wiG9C;z7ov5MMYcn@#qavgFQRka%`M%D0!%Fk7mtC|0(?($Xb*G$!``a@N_ zq4IO}x)3Xz|B7A@;O$1P3;)$G8&f-5tERI-lW)GE^884u$_R6?D^ zkL;HJ$cD=7-8pZeT$#1e%NR9!!}A!nbhxQxA{#11!-mRi-B6K14aj}qjzlHP@S^B> z_J0`3%`3**zF>sci>O=jeMz&EOm>xKuQ@_X|Ek&fnw7!bKleELx~L{Wwz3@*ynfxr zA}r^I@Z-r~w)7h?S?t~eZ5CAW@8Gg-{s62ev7SN>iwEji*3R#USNxeV?YO%_P-ZwD zMP%kQJf+=b#KS+cWy7p)*@QE8y&fP>W{vPi z;T7!B3`H6F9fTD8x3EHqd`+- z&2o<>`#3iHI63<`Gy6C<`?w(cxY#^m1B6f9N`;i{9u0HBg7EWWM2|Y^fjt@`%7Rc7 z!`=-5x<`Wt*`pz1GVPyAx?qonK(A=Z!L(lHvY#Pn*{EfYMoPuTRV6f1H#PbkEqgSg zVoDtWz901Fy5|Xy&>z7b4X{FcG}3~I^Wzg<*`ooakt%yMuYrH_J(?O((bE~@&N0s4 zqnQZC!5+;|voHgI*`wJxgL3z1c9tf*N3(M@p*K+Xlpp`SRtz7nK&^%8S0zcM^j3XZ_z28@} zZB6Onnk_QfPMUpR0yORKrrAO*)t0$}uvAfxd0>ylEW#$(vpJ7ynLV4`4pH%Id)g)5 z$E^bjw1J3}OB?tK9Jy!12jy1FPgv1=aK|TE(@sXw+eo$n=WuX!3l;qb9&;4Eh3JjvMBN)v zl5-TSQ=sT^Mv*r@5JeqTl!+?Z*OdQ;ivB6b35rp08k3NP#7*)8C#8+kECuqTxP@Sv zlEh6LN|GixWD~K|x+M$0Li~Q;@0l}qdD0{4@6$hi{gnIB)7*Q`%$YN1&YU^3+_~p6 z$D(92tuid1_$^{xRT5dR!Y=<`2b}fsKa44~=#camAT)Bv6JYqZNBN(x=+1hd-6uNj z7)G!x{aA~XTL|N^2ajV9{3|m56(mguhU%fOs*HUP6rmooZhxG$uxr$^6W+AdGr}(b zKfcXz^<`ueVFGY{BOBBECk1wZ`h~w@S<8uGlkLtp~U}|(g&@AG-~;#N6VL) zkMbS-7nJXGmTv-yp6KVeVqA_N1u?wsBRTRvbG!vx1gCwniv`B{ZXgdWa?*VHLgaQG zb9+Fz(mfal$EIO4k7l;Sk4sJdkR@UpX(Rb|6A9z6|BifQ{qFsm@j20q|A-kM{skhn zMfCLNfOBT|-Fwum4J-er}u#a+q1lxFaNN^7qQEGSa zU=tUheg~W*j}RXdV8IQ6DJLNT0bD3?fxegM+a6^=KXuE=@D$tw>PyhOMkRA69+_?Z z<^@P+%`a$5G}+*LWQK|n$9ctQIvN(*x>~~ocxQ+CDW%cVKfaVmF>0qky=V(N0_>du z_8992*tr3AOMv~B#m>5&r#GPhoQQ-5NQ096PQNIK{J(7Ea){KK0row_jtGS#w*;B} zw$o=cJwE+&0d_?w{gNR4-v%+7G`$yK|1`j!7^Fw7NkL}a{-a6A9oyeo7VOXQtN2PQ z(|rPelmVQvFEjGaJNQJuN)q9{lpu>wQ!(H|T?Q;;`Q%=nCuKim$uThH3?f+o_XSD( zBO3#wecgg4;F9GE&yRW$8qDPirRR9)Vz}L%da9dX=H*kGz?LY?>dOPd9AVaj@Yh z{n9oj#b!eWGE@1pw=seG_}^nT4tsDc%g3EBojmK^o2HOIn{4NSlb?h?x{{!sj~)Oe z)Zr&=h4YDauAM1Iu&H!ZAd!Z6HTdF$XEvIo3b35{3=&r?D8CumIch%+q3|{IJKCcu zzY4T#*>bp^6&&Yb8&NvfJ6XH=l0^1jj~d~l6!Bx){2+8+zrIH-Tz{4dLjo2RhpS+D zR*QL+O?~3lHdLMfca4|u?j<1!4HC0C~Q=x+)g#E93Y>uzxB!s)yUc0lOVdT*0-QkL z7Yb*p($hil1zWeH$4FKMK0Oflm(CTi7V$S41G2}W@ys7z;TLl>63O%wvX@E$FuC?clEKsJ|5A>m)|03XIKG_9kq`w`uKhOc%?pm!#=JKOM5%%?K=I% zAqZ`>`$>OhYcA_e#|?jfCMAmV?~vXu4Q-K5uzR0Dr7#(>n^AB0J`FxkGqt>)|7YF( zJqQPPQ_2_d|E#;IK)9P!J{kWfmrr{Eds)z#S~@R$**(A;dEqne0ULf*8>aoaEwnWY zf6c<|I(@Na{Lo){OaL}AERZrjTN(fMT*vsMqT4tH8Zke|BHp`lqvN?{hy28)lG9hjUilc^DW@NVbL z581InQE4ysY0o8V2fOzgTq{R6?p?X7vJZqGlEDZ0J^elk_+vY7{+Pks#OSnZl*Dh& zp(H>YaW>)JR?$C@%&zXeSaJYiHb2}8k1N7FZb$wu@!5W2b-6cc4);h7!qP*Ce5cP+ zY>lu)0*|nu{wDiUi0nZRE-IPk+G)FzTV(gLWqhq3w0Xmr{;!yG;f*x2I~lSrm! z(e%B*h8y0|Ml?K}U_Px=Gx`p4$77#9e$76X_3n)UWl;qVi+a- z`y6^r#t+H9!Z&W@iNqCxFH#c^ z$Yh1g_90X5Y}_4?q=JuBemS!7*vRUm%SO7#@KILSG52}`8Ju)z*K`sa2Sx4@SGQP7 z++&lz#&0Z-jgJi)6Hs<>BQ)CT(QvW#O#sR9slWc4MrL_*!Yn6X#KnzdY1y8jkYIZP z%Ag@}BQz}A6VPz6YuKJawkMFu|L4-4{@+vDCys>mo}WGOp!~|iB2TeKc-6&6^*J3v(KMaD6Os}95n<7t0 zzj7BoGdKDNuP5fG@CkhkKiEMGj_W=+_H-D7lPU%CMq>UVj6oiP0ow=1?hRvb5R72X zCFYheX0~FQiAjYqZ&b{DVy<);@U*U#A9S9q1;bel_9GwXyC~06Fq-&MCY~9_xPo0l z%!~iQBEzGZ@KY)ymBRUrv2WTTnmpoAJAu+)+epV{2V`(3{ICOfehSYhCcd3GLU&NU#R8veBOEz?0d(US zU+KeW2GQX$-!|XhnEr;V!wy z`vQAJj{f7aJN83&6p8g+)*Rni6j}^s-SPy+>eK`+vJQmfsObq^!mVdUTtfGq(VOoa zsoW0LvO0lxUFBmDWO~)egr6Hd*nKaB`2wqW;MgT#VWfM{&YSm` z^7LKZdtSJoQE{#&h(6N6`^yfkoV5aH%3*wCB4{dDpB7z zmNNMPFH!PfYZ8@g*&9lv@Q^7YRiQZS^%sg>c`#G*O6gK5Q^>~}Ov|8Y;cqaV9pW7W z`8SZxr;CYl+O!OSZel<(@f`G~mI3dIQf7O43G?m6jhEh*$PH!FrNv%bYx|=4UfYVL z^Sz5U@LyZ|>ZPW;SS%D5dp-FriEJk2F)kg}6q&zQM#0Q|M@xYYJh3c*#P(KQmA%rc*Jm zcPO3A^bb=|D8NuQlgyN}!ye@1#|o#7nN(U?O+i|?@bx42?IZWeBS1|{AzBVic2i5r zyCRbxs+8mXnQVHAx2dJzT~Vl%eF)eOmp_zllNKs|m_R44iiGf5WPn+94r&`V6w?sqUzuIpV@V%ipmxS(_j<|-m z*1x>f$kT6M-%#6!jm4E|a)c zMJ3ug0$>G<*G*d6BvsiosOSqrW!Qnmo+0br3T^Y=n)Ft09W(<#%F|W}RYh8DbEv6D z8~J>%y>-#0^FU9V-g4AB>=kOWm=;ZgqN8ipjA*TzV(tCjiltP7SPTY<*P22Z=4KmMvYIg_uX=Yb#5S})<0I2MR1A?n+ofkTJfl%m!YIPl8DL+QoFYm0ds zSclrejMpCXmP0lwTdS#Fb6ZP0r59$b57tR+-8{oOQWR^4=3Izs`IsGROkg1vk`z)F z^#BH$C(J+F9i!X&FDZjy)uT;8k;;SGZ)AssS5}6ga)~@lPf`4q<%w*Tb=a3k-pKj@ zu9C=xNU-LHV_HVBdjT?}B+T;srqcOdzEDP~DwtxS1y1Do+=u#w?ocx(D8H1aru+h{ zQ+^enwU^res5OEQhgw>i$tPeZnq!6X&^$C%8l`k`OS+_-r;E9abe<)|MqdGmu>m`|!ThPSU+*s9eH&A_FNxvNmgrlgLt ziAp{>XsP%L;flzznwa)HY4*fj+LEAGB{Zo4I3a1*61?U#s(Wh&3MU;ZzD?)8Oy@pG z*D`Oh?QAE)k-r}HsKOXLox(sKu0@J&P^io-rgc1g1&^7l%vDRxY6ItFg;}xGY%m*^ zlAK>&tAK@}bl$fAz>}iC<*b5LSokr?L6(h*Y|U6rr-zz|c5s1#kz9>g4WBIo9|{c# z#He!87TC^7buoMPrdZp>CAE~=uc3vYR?)1*naL!rG=~cnsabeQF{&L5EQ3;+P94n$ z8<%8Y-A6Vqf_vZ%&?|G+q-*fT-b&8KVasyPCV1XZD5JTvPXJKucym*>;`I%e(D8rI)_dXLaP(U`GK*v{-!3#bmqEZVSqAZe8RkDhTiI>3rh37?hT!UxAvFj z(fAeG*-pkjLz^2jSnNAw{QZEN2|@iaeX zP$<%-payJ{q4tEjK)_LzTZtNQs6<5kdTb&)=Oy7`i4=w6)!3YowI6&Yv_%^$}VMd2~7U8dpvgspznX z(uoo69Ysg=m#7lrQ9%9HLEx^O#5I^+YQLnk&x|3tla+BC{J(f0p?Y-YT>Teoj?tDg z`BJ67A43fwoO2@u9#`H}n!`Jf>-C-CWP}y7M%RGeN<$W>|{!&SfW?4Vm17VCp&~B z(!D0iUZKCA>I1{4cHfoHRoji};ZhmNa&G)g+uAR#u`uf5yH=4FZ)zOb8a`~Jw{>Op zUpNBA+I9w$iSd6(#5J1U@chR;s6h?)`Okag_8Hr@E0LLAbgn0_K;s&3x^6ifwM9-+5 zI#1AW>=S!#_cjipB1F}|jBDOGY`|{xI6GwYMHUCn|i(Yp&!gB|6Lyg!IOVyqD%Ii5>MK-ADF% zL0$R;q!6kQt1DbOA?1136)LEmVK1My3|dbe6%1BVa5(6YIfhkAPTN?; z(k@|JeS^~AuxiPrO@z#{m5N}=Y#WiR@65vcndiRipx(Q z^fa~xy--#SGTgC)i}Ax5`r_k9G6GGfGH4adr*<4;{XFsGq22H;-4I67p*5(B9SmXM z3tA3_iiIs1v>B4Ek&Xl5j%syHH{jpx+Y@8;DyQ+8g?NwOcvIdHAg0>JvV(A_RKlEgRYsT zQ|+L2=j-cS2#>i;bUI~q7@~Gm1Ym1LX+cDGCVGKJ4oijRA*Lg$d47b*;MA8_xD854 z2hk^|q1h9ZtIrN^HHI&6J2Xze{YUhOf2GWyiurZZw$D7P!yo&y*d~AQ|XY4vlko4`0FG<^9iZDYC zjN?;^9uTTquj_5U3jqW!jR%8^v2Qscdt#}-oqQpV77;SX#>aSc1GMwglD@!*Slo_wQvD75yRE~2D5VQ>Ta?}Fhhqi zMawkGgGq8(eu%@Kn77DiejR48(&!-JS;(}PRg|}Q8@L)FVn&##lnWeqCNZrv>|7nQtZuI?@Z(^7@^*Co@S7H6u)6l@?)9CkR+$Z%9NHQnp1HbneNXo?a}}INJ_E9> z2eEp)O#{TqneyH)x@P?tLYD@;W+cqyDmi-O=*m;`s2OgGfzyznD_#-i(>HLY&yI0D zI;VhR+yw4r9zjO$;wpd&OFk}sXwnEgY#qb|NeLBK>d#=3QHHW2FsFy`hFsU;C9<@y z!<^kOl{t_{eQCU)pRle}kVpnQ@IO#m#`oLnxDc6JilIc9LmGVB^d=>ophp9wA4$!xD}D=xo{>b8bsK@?#x z6N2>JRwqv_55^{yAviObxXg2Q7HZ*GOBOIKEHlA&jYTtThztmVNKhv(@s;f=3+6w) zE=xe%6Q}$%8!|0Z!em@Nh5GV5U(=gXoVDrRCKJ+mw3$m=o0=}1?{WENv3D^a&@eo2 zV?Oh)3UA_%!ul_Dt1Vh?tU60?MXvR)ZMiUTz|5;vEv@WS0X=LU0}9SHbKL@_I)jNAHhd@@N2m)z z-bf_BdU`J=!@9Yy8eDbJb8mtbNAHRL(>|G*O{F=Tu9E(=DYXopXj>Qm@vbS=g{TZ6 z+GRZ()~+{O2QyeofN@_UbDVi9di#>|+RW8Gy&F4MnJt+dekPyD7YxQk$&$g?Ia4<1 zzPJw6zrEk&^LdlSg0)r_{GQMN58Pux2}}JLe%UP1(zaxZg*=uK%UBdBW@KGj{3!Gv z$R1fPV16;PpqvxuC(bNoniv4H7ApdF2qB{JGqXKx14@YU1np8QTE-Qm17pE{8M#~W zn{EJUm9gLroUDbUsoLyj*>NS^0-nZ40mWz1r59I1T@JwXqLji42x~pt`{#QQku3FO z`$L5y5x-ENT~jW}!(S^zzize8m}JT>*PVABoe0zdmTs_uStwykMm7x>O!*AOgOif- zVqOcEL%n|QCUWI1p_oFEz@*nQWMxMZAyl^CYw=lW;rb-AX_#pjcsFpA->s{>TmrS1 zYxlM5R(^%q;XhY$EQod(HPE6GXC13mX8$c~ja+jA3H-ve=vonHpV1Cs7ds#A}vV z{*;V$07-ft>KyrmGvJzz?D#RuGDWk@*gjeEZECp`n%~mSe{JLbUkaX^xT>#PXJon0 zF6_xIx6Sa+UV0<@<+yX9DRu$uMr)33t4-8J0rm`q>K%Itms&6by z*bWF)6^A*ndseMohR{Yb%Q<%#N5^6~?@-3ev;@rASq29qA>XKs%gygk)^RsPxlr)3 z1ymu~cX?U@WzIgs=ws-MKN+=0bKufaNWaW-_Hndf*5`5k*6%$ka2|)P34ec+EsD1S z^MmufUNQIA!rIGVctL11jC%}+7nqH`OBbW9+`=EMI|7uaWb7@6aboaLMSgWb>X2hl z6iO7WMMm*kxj5r?@TP#-AysN#MSZ^%$(=(1vIw zo4Jcg7qQV9u&w2|(49sL$9P@#eXQ2eb~AT725~u#2_mY!@fMXf7oyU5{AWV^Je=4N<=U^g8=4zx8*&FEqhuL!$ zt1B}!i0Xxw<>v|uL$RW3ru&DjTAekZ2f;^~%#v`kDuE`C2MbkvawZRNEK zV$q$v*FzG{k)uy0G$g&wx7OXt>bPq0bll{27|E`glE*EqbWfLf^*66B!Isz{f5r~- zXRm%!OV(vmH*E1%A!f)~fF$7g*qwLquU@4t3i5!(BGKuEU1M_(~J5R>7yg{&kJFp+mrCO~*$*%6v&x>r>ajU}LuJCgeez!g^(a=-6-%+}|J0{JUa`~Rg#=PAabLV%CSD(WD zzV9Ee9z*yL!s&QX>ZuOX_Z~YCu9{q}Zbo?eRGdeQu;JutbuYrj zr&X&55dPg8s@1~?Pr&|oGtHPk#5pb=!sl_8PzS=taBA9Sggkt32f`_>NRRNh2oE59 zFHV>|jPUh16O}J{-H*^i_$HjP+JUeW;bw%>aIESMgzv`b$a@i9bUD%^tm0_+!wA>D z73uMU*rNzNghvo|Ae{C#q(^w)GNdPbInpEib`R1cyb535I*jl*LcUz~&sQNm!YQkf z9^p4}M#*M`zg~;<2v6=sdW20Iksjf@5gtbPfoqWdBx7#44(SmtzX9nHX5)AV8R1h1 zZ%4RdbG7-XTHS~6{+-q8QwYc2gY!)g&KN;@yb$-{T}Y4ci|<2vgct5bdW3ua0_hR{>|vxw zxcO0}NBAd?Aw9x>M>riX({&#}dW3I%9O)7Mu^{ zY5RIJX7T~$`my8HDd6A7PyP=-{ZW8BUN~Nrtq4B- zAs;^j={^kn>Tvog%O*Mc^8tVI=f|thLDzlyQ=XaT@T-8oTZNpMRpm&j-FZvs!(BME-aB@?Qn~k50## zw@B`%FEX{Qzbx>_fv-SUef*T{$&UVA!1v;aoPh}cH~9QN2>eHI4AA!@{QslR|I@%< zGp|~eL4&K`bddj1;M2gL5urcRr#}O7--Oc+|0B%*?&DX{ z1_S@z2>v!7p9Ovf4lw-V2!6!J-v#_Fz}t;1PXF^h{z2fs4LsYRFTYKKw)zkJcYuE) zQogVFF86|Ko5fQb^GmS2GVuCty=wg1ph-HKOgv+ORLo_5&m<2`Bnj+1pW&V{AYc9 z7WiKS|APqr+dlp-;Q28>9ySs1ceY=?2Z8?z`arhx0ROm;e;W7!^o{k>9|bK$k|J0!Tz+b@ihQ;_~S(LWEM2Z2A1LqE<7r|;f5Wy)+JAv$^pam>}#>N^nlG$!}@ zJRSr73gB;z;J5hrM%ccO0>3v>h7b8=plzH3|KOSkevRNg&|C-n`b{`dA}r&T;PG-1 zv+!6(obB)}?vC*AZl8xez`p>z9L49i!!I>30^7+KfqxHtjL*X7@XI*mPfl|9e+2$i z_#TZB9#8bk@Jrw)RjSpgVL48I+0Xy=FxWo<{?u?8rcC$sWD)Ss;1G?ak$Sq!w-XzH zf9WRJsWAO1bNqTP1OEzqqR|L{xBL9<0X_pCX-*{n)BOCu2z&~@(^ei5;L9g}s$YKK zzp@AKik=q6Z$8c8e+m552k<^9y2l|3$#>{(QCC8R7p@ zpZ^WOfBp;ADt&#Q{^XUue9OQ;4}5O~f2EJ#1N`YUnT471^nKK4kpdQmu!Df4Xxr1{$Qngm{x zz-tnCO#-h;;57-nCV~H?1Ulwv0uAYA@LErQ1ebe$hd$4+U`+hmo0IjH<` zN`n{ArsBo(s=}8hGY{n~f11q9R>GVY<3*R0*O@jj$7b0Z2Om4dK0CgrTXLMXl*{GM zGp~5LcrRCN`4C+|II~O2d#Tc^>a*kPm_F}SLF0TsiSNo`ssGpge@N@4mSU@CAsrg7 z({QtfLmKYTaJPngHQcY^0Symncv!<@8k%#AT#Xuf8n$ZKq2W3WH)}Yg;SLRVYq(d# z{Td$7@Q{XwH9V%F97+Zb8s}O*JPlhl?9gzXhMP4U(r|}{yEWXa;eHJdXn07&!x|pb zP!2?6{%_IzHEh+eL&J3%Zq{%}!yOv#)^M+e`!zhE;UNtVYj{jUnOJ82=W6~MwrXgh zzyJT~Lz529@}ndZ;!Puny27b z7YDU#s`qfiG0J98{=r8T8;c z3BBCPqSbq+{Tl^-hH?EKri}YqU9b zYJx`;zC+m$*_|Hjv!1;|cTuR2gsPG*Mcln-xXcO^y zCg3dZWsHF9?eZVzJ}UgZ3YR$<;9iH{M074e{7G2FO7&VgN=AZ-;&)5@Gfi#(wnmb_ z&fL2^N+%)lXZwDFqrVmK)4>19r`Y&1F9YPA0(Y^3kYA5s{M>E}lR0bLat(>_5AC#Y znXAX`R}{YSb_l7@`0H{OE^A1*&DNoFYaRFp>cAfW+_R)3{~rhZROqdzdMnNq zVq67>&EqMhPR}*fGeW=7{7CEN3kn~rlkdxQ;4@)gCX(l^a9k&XpHnCPB7rxWUw^_f zB6IJ!U0)}D3UHRUwWhpV>ck&W`sZC_>C4(FZttrT|8sTVPbrA1b6PW;_<;Ga@DOEh0ulLejq3je{UE&cPXTA0V{ z(0Qf~{O3w%=cuJ4YmT_BD*RI)ws2XC!|fRuCsIG}yTig|jT5&m3ZM6+h0B@_ZdWRN z$=57g)@pG34u!A!wuQ?Y3T{gRx3KX4J2ifR9Xc*qb3*vZI`O}(@xP;TdqU;&q{7d> zS;!b!W5(@2Dg3@awD8+Bey_As3k(1MVV!(`sdVP5eYjf_om@w6-z4xx^L_32WvvBi z7S)O0132wWtv$b5>CFE>HbYrc!fmz=om=a`KTrq$KpptQfO|H}$p5G6#D5m>QzxHg zYR5%Ck@$_~(T`d(38nU89Xiv{!BG#teUhyxS%X5Fvj9Jp7DD|!m<73-HU3A{0g=5G z!2G6;-MC!iZ@%BAmvvg)_6pp>!v6;}{yo}2wK%`w(2mUFg^XYF|>asS8+rL!! zM?YZUvi6SKzpq2*2;hzAAD>dc)TYATHkI?{W(zq_5oe1W8qFuwA(g#1z#XeYe=-Ib z6WQT21m0-2=PhB`BZJ!u04G1S{o}+8VZP$zy- z<9}QgO7{F9{;dk1cDW@kd!=yufjV?PQwRQ-(z&K$={SG&TMEBumxaq-58VDt;d6D@ z=zA^NysYp$l+GDet<6kK@J%FVPvDK_&#trtWG@wNTNJ+bWJ_R^(qCSO&b1nU;2s-M z_H5v`SSS7+b>R0ZouBQrbhP@-0}5|BXyK>Ap7MG`;b%QzpJk5#ZogTF{^2_C7nRQD zg_e$*dovjmp|lSVs+|AWqRsON3BSJG0%VUKZf8q;jT8#z*MYwcaN4n2eZETb#knDJ z$sQZry+h$IX@;Hfr+Ezm?#W&B-ydoGZ(VEQveyl_cPsqID=l31*x~j*fm>Mk|9*}C zS=EPx=KGk!zkZvIEqkeOdr0AnJ1kuG2;%m6z~@>}D0?3PU#9T0KW5X&o*vx3qYj;N9r$|y=Xjx`X1wqbrJqb#`f3hf z=##~ATdZK>@kC!HUQP@c0PrP_{{C3f;K-!(0AAfl7vtqzJc*b6(j~-972*TgLSG^q zPvIr)Qan-FX52X#saWg8F>t(lJ`+zAi-}PBBT#^WZw?7B6bt9!ZtUa@9l zynCf$S1wy`;wx6IUD~-SzIOTY-tG<7x?k=`<9Lst9q7p#oJ@; zv5N@ei$KXt3B|)(P!6D{2Far<3kr-vmj?~r0F93W_7~IX2g%25XOJv-5291j`iyD9O097xAQzoFGQ%qKzu42C47 z#V!}3kyPCJ{s{z_ubBR1zMPGaLK95h7>8z1A$&~RC6Fy%RV$X*B(2B|&S&5$ZSGw) ztr$7&+VJ!ZHj=nR!QoQ&6kNYaxXQ;%e|{;U{zw6yvN+Vh&zhwv;OvK5QEG9dAxc^7 zsXmgaYj|-v6v$;@#6Ym3a3)8Dew+%2V&i?3INyf!k&8@8Uh?w+BHO_N>KY8eSY;YMYIFStx;u?W|fGN zu`f@dG4Qw+Y6;H_wWsO&mJjT~CiAp1epMw7s~eYZs>J1+Dj@@eBO#TH8jD<(Bq8j5NJo0y85_DS_QI8e>-BsW`I5NNW- zn8?X~IP}!#Opa{&$F`}?z%(qIInri9wzcSM27;$b!E@^Bng#Dn8d@Jy+YyzH`gsf1H5 zf<}4?3?k|d#}cQbT@ctE$A#;*?0cr7m>}F&VjG7hfN`AAm>h&V5#bcCHKSbUtzFZE zsB?uaQETZeJj>(ktjkXtBB=yuc zH80i~k)wfXj1dnJ!qGK}av>9?gML5MGW-sLHE26oipF_NqGQ&Nny3*Karz*JtR$V6IxaLAsY=ul@~c1&J2R!k7PKl~PCTxH zP8iQ1Mb)628i3v=wl}Y&Nu-VMk`3o1i{aY?G#tmO@!2_n; z{PdSR%435FTdNj=0d&)!;594>|FZU1meoz$0ED@-99)Mx_xhq z%d1xY?*fc_%3ON4kIaR8Bvr7l&c$=#Ex>b+ntOKp)Lgh<)4Muy{43aS9+)z9>D|6L z7rK3OweoZ2|B$9%ss!CWJQuosdrZ$XE`OK)PXMD#9DU>AftM%${r!pdX}tb$+9jOc z({z4VqcDw1$b}Eor0-CLb)k!=$uMJNFBk5wNxx3hyDpU6E_}Eqy_@%T;o+Y$ zRdBg<4)Pdo!sU1Cpe~&0B5GJ84)aw_@9N*JOSte}Ht^8ph33@r~)Loaq+rRRVrgws2`{U*Eb@lkCfRR%_z3zK`=z7cl${P8(^e*JGTsXbI z?zY*c_iEC6$SZu!Ks3^F`MddIug|96S5skLI5F-|MKs22t%1Ar7uLWnRhxz=KG(1) zDb{KJL#N84e4mzmoh!GqA6S;~dABj`{{tnP(*u}44PP#Q2P1h`b_ZiPu*1f5`*<9# HHvRtvAyki) diff --git a/otp.c b/otp.c index 99bac68..9901c65 100644 --- a/otp.c +++ b/otp.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -60,6 +61,464 @@ static int is_interactive_mode = 0; // MAIN //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// NEW SIMPLIFIED USB DETECTION SYSTEM v2 +// Uses lsusb cross-referencing for reliable USB device identification +//////////////////////////////////////////////////////////////////////////////// + +/** + * Decode octal escape sequences in mount paths (e.g., \040 for space) + * Used by the new v2 USB detection system + */ +void decode_octal_escapes(const char* input, char* output, size_t output_size) { + if (!input || !output || output_size == 0) return; + + size_t src = 0, dst = 0; + size_t input_len = strlen(input); + + while (src < input_len && dst < output_size - 1) { + if (input[src] == '\\' && src + 3 < input_len && + input[src + 1] >= '0' && input[src + 1] <= '7' && + input[src + 2] >= '0' && input[src + 2] <= '7' && + input[src + 3] >= '0' && input[src + 3] <= '7') { + // Decode octal escape sequence + int octal_val = (input[src + 1] - '0') * 64 + + (input[src + 2] - '0') * 8 + + (input[src + 3] - '0'); + output[dst++] = (char)octal_val; + src += 4; + } else { + output[dst++] = input[src++]; + } + } + output[dst] = '\0'; +} + +/** + * Build USB device registry by parsing lsusb output + * Returns array of USB devices found on system + */ +int build_usb_device_registry(usb_device_registry_t **devices, int *device_count) { + if (!devices || !device_count) return 0; + + *devices = NULL; + *device_count = 0; + + // Execute lsusb command and capture output + FILE *lsusb_pipe = popen("lsusb", "r"); + if (!lsusb_pipe) { + fprintf(stderr, "Error: Failed to execute lsusb command\n"); + return 0; + } + + // Allocate initial array for USB devices + int capacity = 16; + usb_device_registry_t *registry = malloc(capacity * sizeof(usb_device_registry_t)); + if (!registry) { + pclose(lsusb_pipe); + return 0; + } + + char line[512]; + int count = 0; + + // Parse each line of lsusb output + // Format: "Bus 001 Device 003: ID 0781:5583 SanDisk Corp. Ultra Fit" + while (fgets(line, sizeof(line), lsusb_pipe)) { + // Expand array if needed + if (count >= capacity) { + capacity *= 2; + usb_device_registry_t *new_registry = realloc(registry, capacity * sizeof(usb_device_registry_t)); + if (!new_registry) { + free(registry); + pclose(lsusb_pipe); + return 0; + } + registry = new_registry; + } + + usb_device_registry_t *device = ®istry[count]; + memset(device, 0, sizeof(usb_device_registry_t)); + + // Parse: "Bus 001 Device 003: ID 0781:5583 SanDisk Corp. Ultra Fit" + char *pos = line; + + // Extract bus number + if (sscanf(pos, "Bus %3s", device->bus_number) != 1) continue; + pos = strstr(pos, "Device "); + if (!pos) continue; + + // Extract device number + pos += 7; // Skip "Device " + if (sscanf(pos, "%3s", device->device_number) != 1) continue; + + // Find ID field + pos = strstr(pos, "ID "); + if (!pos) continue; + pos += 3; // Skip "ID " + + // Extract vendor:product IDs + if (sscanf(pos, "%7[^:]:%7s", device->vendor_id, device->product_id) != 2) continue; + + // Find start of vendor/product names after ID + pos = strchr(pos, ' '); + if (!pos) continue; + pos++; // Skip space + + // Extract vendor and product names (rest of line) + char *newline = strchr(pos, '\n'); + if (newline) *newline = '\0'; + + // Split vendor and product names + // Look for common patterns like "Corp.", "Inc.", "Ltd." + char *vendor_end = strstr(pos, " Corp."); + if (!vendor_end) vendor_end = strstr(pos, " Inc."); + if (!vendor_end) vendor_end = strstr(pos, " Ltd."); + if (!vendor_end) vendor_end = strstr(pos, " Co."); + + if (vendor_end) { + // Include the company suffix + if (strstr(vendor_end, " Corp.")) vendor_end += 6; + else if (strstr(vendor_end, " Inc.")) vendor_end += 5; + else if (strstr(vendor_end, " Ltd.")) vendor_end += 5; + else if (strstr(vendor_end, " Co.")) vendor_end += 4; + + size_t vendor_len = vendor_end - pos; + if (vendor_len >= sizeof(device->vendor_name)) vendor_len = sizeof(device->vendor_name) - 1; + strncpy(device->vendor_name, pos, vendor_len); + device->vendor_name[vendor_len] = '\0'; + + // Product name is what remains after vendor + char *product_start = vendor_end; + while (*product_start == ' ') product_start++; // Skip spaces + strncpy(device->product_name, product_start, sizeof(device->product_name) - 1); + } else { + // Fallback: split at first word boundary after reasonable vendor length + char *space = strchr(pos + 8, ' '); // Look for space after ~8 chars + if (space) { + size_t vendor_len = space - pos; + if (vendor_len >= sizeof(device->vendor_name)) vendor_len = sizeof(device->vendor_name) - 1; + strncpy(device->vendor_name, pos, vendor_len); + device->vendor_name[vendor_len] = '\0'; + + strncpy(device->product_name, space + 1, sizeof(device->product_name) - 1); + } else { + // Single word - treat as vendor + strncpy(device->vendor_name, pos, sizeof(device->vendor_name) - 1); + strcpy(device->product_name, "Unknown"); + } + } + + count++; + } + + pclose(lsusb_pipe); + + *devices = registry; + *device_count = count; + return 1; +} + +/** + * Free USB device registry memory + */ +void free_usb_device_registry(usb_device_registry_t *devices) { + if (devices) { + free(devices); + } +} + +/** + * Find USB device in registry by matching device path to USB bus info + * Maps /dev/sdX to USB device via /sys/block/sdX/device tree + */ +usb_device_registry_t* find_usb_device_by_path(const char* device_path, + usb_device_registry_t* usb_devices, int usb_count) { + if (!device_path || !usb_devices || usb_count <= 0) return NULL; + + // Extract device name from path: /dev/sda1 -> sda + char device_name[16]; + const char *dev_start = strrchr(device_path, '/'); + if (!dev_start) dev_start = device_path; + else dev_start++; // Skip '/' + + // Copy device name, removing partition number + strncpy(device_name, dev_start, sizeof(device_name) - 1); + device_name[sizeof(device_name) - 1] = '\0'; + + // Remove partition number (digits at end) + char *digit_pos = device_name; + while (*digit_pos && !isdigit(*digit_pos)) digit_pos++; + if (*digit_pos) *digit_pos = '\0'; // Truncate at first digit + + // Read vendor and product info from sysfs + char vendor_path[256], product_path[256]; + char model_path[256], manufacturer_path[256]; + snprintf(vendor_path, sizeof(vendor_path), "/sys/block/%s/device/vendor", device_name); + snprintf(product_path, sizeof(product_path), "/sys/block/%s/device/model", device_name); + snprintf(manufacturer_path, sizeof(manufacturer_path), "/sys/block/%s/device/manufacturer", device_name); + snprintf(model_path, sizeof(model_path), "/sys/block/%s/device/product", device_name); + + // Try to read device info from multiple possible sysfs locations + char vendor_info[64] = {0}, product_info[64] = {0}; + FILE *fp; + + // Try vendor file + fp = fopen(vendor_path, "r"); + if (fp) { + fgets(vendor_info, sizeof(vendor_info), fp); + fclose(fp); + // Trim whitespace + char *end = vendor_info + strlen(vendor_info) - 1; + while (end > vendor_info && isspace(*end)) *end-- = '\0'; + } + + // Try product/model file + fp = fopen(product_path, "r"); + if (!fp) fp = fopen(model_path, "r"); + if (fp) { + fgets(product_info, sizeof(product_info), fp); + fclose(fp); + // Trim whitespace + char *end = product_info + strlen(product_info) - 1; + while (end > product_info && isspace(*end)) *end-- = '\0'; + } + + // If we couldn't get device info from sysfs, check if it's in common USB mount locations + if (strlen(vendor_info) == 0 && strlen(product_info) == 0) { + // For devices in /media/ or /mnt/, assume they could be USB and do partial matching + // This handles cases where sysfs info isn't available + return usb_count > 0 ? &usb_devices[0] : NULL; // Return first USB device as fallback + } + + // Try to match vendor/product info with USB device registry + for (int i = 0; i < usb_count; i++) { + usb_device_registry_t *usb_dev = &usb_devices[i]; + + // Try partial string matching for vendor names + if (strlen(vendor_info) > 0 && strlen(usb_dev->vendor_name) > 0) { + if (strstr(usb_dev->vendor_name, vendor_info) || strstr(vendor_info, usb_dev->vendor_name)) { + return usb_dev; + } + } + + // Try partial string matching for product names + if (strlen(product_info) > 0 && strlen(usb_dev->product_name) > 0) { + if (strstr(usb_dev->product_name, product_info) || strstr(product_info, usb_dev->product_name)) { + return usb_dev; + } + } + } + + return NULL; // No USB device found for this path +} + +/** + * Get user-friendly status string for USB safety status + */ +const char* get_usb_status_string(usb_safety_status_t status) { + switch (status) { + case USB_SAFE: return "SAFE"; + case USB_TOO_LARGE: return "TOO_LARGE"; + case USB_NOT_USB: return "NOT_USB"; + default: return "UNKNOWN"; + } +} + +/** + * Detect drive information using new simplified v2 system + * Cross-references with USB device registry for validation + */ +int detect_drive_info_v2(const char* mount_path, drive_info_v2_t* drive_info, + usb_device_registry_t* usb_devices, int usb_count) { + if (!mount_path || !drive_info) return 0; + + // Initialize drive info structure + memset(drive_info, 0, sizeof(drive_info_v2_t)); + strncpy(drive_info->mount_path, mount_path, sizeof(drive_info->mount_path) - 1); + + // Get device path using existing working Method 1 from get_usb_drive_info + FILE* mounts_file = fopen("/proc/mounts", "r"); + if (!mounts_file) return 0; + + char line[1024]; + int device_found = 0; + char device_path[256] = {0}; + + while (fgets(line, sizeof(line), mounts_file)) { + char source_field[256], mount_field[256], fs_type[64]; + if (sscanf(line, "%255s %255s %63s", source_field, mount_field, fs_type) == 3) { + // Decode octal escape sequences in mount path + char decoded_mount[512]; + decode_octal_escapes(mount_field, decoded_mount, sizeof(decoded_mount)); + + if (strcmp(decoded_mount, mount_path) == 0) { + strncpy(device_path, source_field, sizeof(device_path) - 1); + strncpy(drive_info->filesystem, fs_type, sizeof(drive_info->filesystem) - 1); + device_found = 1; + break; + } + } + } + fclose(mounts_file); + + if (!device_found) return 0; + + // CRITICAL FIX: Filter out internal encrypted drives (luks/mapper devices) + if (strstr(device_path, "/dev/mapper/") || strstr(device_path, "luks-")) { + return 0; // Skip internal encrypted drives - these are not USB devices + } + + // Store device path and extract device name + strncpy(drive_info->device_path, device_path, sizeof(drive_info->device_path) - 1); + + const char *dev_start = strrchr(device_path, '/'); + if (dev_start) dev_start++; else dev_start = device_path; + + char temp_name[16]; + strncpy(temp_name, dev_start, sizeof(temp_name) - 1); + temp_name[sizeof(temp_name) - 1] = '\0'; + + // Remove partition number to get base device name + char *digit_pos = temp_name; + while (*digit_pos && !isdigit(*digit_pos)) digit_pos++; + if (*digit_pos) *digit_pos = '\0'; + + strncpy(drive_info->device_name, temp_name, sizeof(drive_info->device_name) - 1); + + // ENHANCED FIX: Validate USB connectivity using sysfs path check + char sysfs_path[512]; + char link_target[512]; + snprintf(sysfs_path, sizeof(sysfs_path), "/sys/block/%s", drive_info->device_name); + + ssize_t link_len = readlink(sysfs_path, link_target, sizeof(link_target) - 1); + if (link_len > 0) { + link_target[link_len] = '\0'; + // Check if the device path contains 'usb' - genuine USB devices have USB paths + if (!strstr(link_target, "/usb")) { + return 0; // Skip non-USB devices based on sysfs path validation + } + } else { + return 0; // Skip devices without valid sysfs paths + } + + // Extract label from mount path + const char *label_start = strrchr(mount_path, '/'); + if (label_start) { + label_start++; // Skip '/' + strncpy(drive_info->label, label_start, sizeof(drive_info->label) - 1); + } + + // Get filesystem stats for sizes + struct statvfs vfs_stat; + if (statvfs(mount_path, &vfs_stat) == 0) { + drive_info->total_size = (uint64_t)vfs_stat.f_blocks * vfs_stat.f_frsize; + drive_info->free_size = (uint64_t)vfs_stat.f_bavail * vfs_stat.f_frsize; + } + + // Count .pad files on drive + DIR *dir = opendir(mount_path); + if (dir) { + struct dirent *entry; + while ((entry = readdir(dir))) { + if (strstr(entry->d_name, ".pad")) { + drive_info->pad_count++; + } + } + closedir(dir); + } + + // Cross-reference with USB device registry + drive_info->usb_device = find_usb_device_by_path(device_path, usb_devices, usb_count); + + // Determine safety status + if (drive_info->usb_device) { + // Found in USB registry - check size limits + if (drive_info->total_size > (6ULL * 1024 * 1024 * 1024 * 1024)) { // 6TB limit + drive_info->status = USB_TOO_LARGE; + } else { + drive_info->status = USB_SAFE; + } + } else { + drive_info->status = USB_NOT_USB; + } + + return 1; +} + +/** + * New simplified USB drive listing function + * Replaces the complex list_usb_drives with lsusb cross-referencing + */ +void list_usb_drives_v2(void) { + // Build USB device registry from lsusb + usb_device_registry_t *usb_devices = NULL; + int usb_count = 0; + + if (!build_usb_device_registry(&usb_devices, &usb_count)) { + printf("Error: Failed to build USB device registry\n"); + return; + } + + // Parse mounted drives from /proc/mounts + FILE* mounts_file = fopen("/proc/mounts", "r"); + if (!mounts_file) { + printf("Error: Cannot read /proc/mounts\n"); + free_usb_device_registry(usb_devices); + return; + } + + drive_info_v2_t drives[MAX_USB_DRIVES]; + int drive_count = 0; + + char line[1024]; + while (fgets(line, sizeof(line), mounts_file) && drive_count < MAX_USB_DRIVES) { + char mount_point[512]; + if (sscanf(line, "%*s %511s", mount_point) == 1) { + // Decode octal escapes + char decoded_mount[512]; + decode_octal_escapes(mount_point, decoded_mount, sizeof(decoded_mount)); + + // Only check drives in common user mount locations + if (strstr(decoded_mount, "/media/") || strstr(decoded_mount, "/mnt/")) { + drive_info_v2_t drive_info; + if (detect_drive_info_v2(decoded_mount, &drive_info, usb_devices, usb_count)) { + drives[drive_count] = drive_info; + drive_count++; + } + } + } + } + fclose(mounts_file); + + // Display results + if (drive_count == 0) { + printf("No drives found in /media/ or /mnt/ locations.\n"); + } else { + printf("%-20s %-10s %-12s %-12s %-8s %-4s %-30s\n", + "Label", "Type", "Total", "Free", "FS", "Pads", "Mount Path"); + printf("%-20s %-10s %-12s %-12s %-8s %-4s %-30s\n", + "--------------------", "----------", "------------", "------------", + "--------", "----", "------------------------------"); + + for (int i = 0; i < drive_count; i++) { + drive_info_v2_t *drive = &drives[i]; + + char total_str[32], free_str[32]; + format_size_string(drive->total_size, total_str, sizeof(total_str)); + format_size_string(drive->free_size, free_str, sizeof(free_str)); + + const char *type = drive->usb_device ? "USB" : "Local"; + + printf("%-20s %-10s %-12s %-12s %-8s %-4d %-30s\n", + drive->label, type, total_str, free_str, drive->filesystem, + drive->pad_count, drive->mount_path); + } + } + + free_usb_device_registry(usb_devices); +} + int main(int argc, char* argv[]) { // Load preferences first @@ -273,6 +732,48 @@ int command_line_mode(int argc, char* argv[]) { } return 0; } + else if (strcmp(argv[1], "usb-list") == 0) { + list_usb_drives_v2(); + return 0; + } + else if (strcmp(argv[1], "usb-list-v2") == 0) { + list_usb_drives_v2(); + return 0; + } + else if (strcmp(argv[1], "usb-init") == 0) { + if (argc < 3) { + printf("Usage: %s usb-init [drive_path]\n", argv[0]); + printf("Examples:\n"); + printf(" %s usb-init OTP_BACKUP (interactive drive selection)\n", argv[0]); + printf(" %s usb-init OTP_BACKUP /media/user/USB_DRIVE\n", argv[0]); + return 1; + } + return handle_usb_init_cli(argc, argv); + } + else if (strcmp(argv[1], "usb-copy") == 0) { + if (argc < 4) { + printf("Usage: %s usb-copy \n", argv[0]); + printf("Example: %s usb-copy 1a2b3c /media/user/OTP_BACKUP\n", argv[0]); + return 1; + } + return handle_usb_copy_cli(argv[2], argv[3]); + } + else if (strcmp(argv[1], "usb-import") == 0) { + if (argc < 4) { + printf("Usage: %s usb-import \n", argv[0]); + printf("Example: %s usb-import 1a2b3c4d... /media/user/OTP_BACKUP\n", argv[0]); + return 1; + } + return handle_usb_import_cli(argv[2], argv[3]); + } + else if (strcmp(argv[1], "usb-verify") == 0) { + if (argc < 3) { + printf("Usage: %s usb-verify \n", argv[0]); + printf("Example: %s usb-verify /media/user/OTP_BACKUP\n", argv[0]); + return 1; + } + return handle_usb_verify_cli(argv[2]); + } else { print_usage(argv[0]); return 1; @@ -2183,10 +2684,1327 @@ int detect_otp_thumb_drive(char* otp_drive_path, size_t path_size) { return 0; // No OTP drive found } +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// USB DRIVE MANAGEMENT FUNCTIONS +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// USB Device Safety Functions +int is_device_removable_usb(const char* device_path) { + char removable_path[512]; + FILE* file; + char value; + + // Extract device name from path (e.g., /dev/sdb -> sdb) + const char* device_name = strrchr(device_path, '/'); + if (!device_name) return 0; + device_name++; // Skip the '/' + + // Remove partition number if present (e.g., sdb1 -> sdb) + char base_device[64]; + strncpy(base_device, device_name, sizeof(base_device) - 1); + base_device[sizeof(base_device) - 1] = '\0'; + + // Remove digits at the end to get base device name + int len = strlen(base_device); + while (len > 0 && isdigit(base_device[len - 1])) { + base_device[len - 1] = '\0'; + len--; + } + + // Check if device is marked as removable + snprintf(removable_path, sizeof(removable_path), "/sys/block/%s/removable", base_device); + file = fopen(removable_path, "r"); + if (!file) return 0; + + if (fread(&value, 1, 1, file) != 1 || value != '1') { + fclose(file); + return 0; + } + fclose(file); + + return 1; +} + +int get_device_bus_type(const char* device_path, char* bus_type, size_t bus_type_size) { + char sys_path[512]; + char link_target[512]; + ssize_t link_len; + + // Extract device name from path + const char* device_name = strrchr(device_path, '/'); + if (!device_name) return 0; + device_name++; // Skip the '/' + + // Remove partition number if present + char base_device[64]; + strncpy(base_device, device_name, sizeof(base_device) - 1); + base_device[sizeof(base_device) - 1] = '\0'; + + int len = strlen(base_device); + while (len > 0 && isdigit(base_device[len - 1])) { + base_device[len - 1] = '\0'; + len--; + } + + // Read the device symlink to determine bus type + snprintf(sys_path, sizeof(sys_path), "/sys/block/%s/device", base_device); + link_len = readlink(sys_path, link_target, sizeof(link_target) - 1); + if (link_len == -1) return 0; + + link_target[link_len] = '\0'; + + // Check for USB in the path + if (strstr(link_target, "usb")) { + strncpy(bus_type, "usb", bus_type_size - 1); + bus_type[bus_type_size - 1] = '\0'; + return 1; + } + + // Check for other bus types + if (strstr(link_target, "ata") || strstr(link_target, "scsi")) { + strncpy(bus_type, "ata", bus_type_size - 1); + bus_type[bus_type_size - 1] = '\0'; + return 1; + } + + strncpy(bus_type, "unknown", bus_type_size - 1); + bus_type[bus_type_size - 1] = '\0'; + return 1; +} + +int is_system_critical_mount(const char* mount_path) { + // List of critical system mount points that should never be formatted + const char* critical_mounts[] = { + "/", "/boot", "/boot/efi", "/home", "/usr", "/var", "/tmp", "/opt", + "/etc", "/lib", "/lib64", "/bin", "/sbin", "/sys", "/proc", "/dev" + }; + + const int num_critical_mounts = sizeof(critical_mounts) / sizeof(critical_mounts[0]); + + for (int i = 0; i < num_critical_mounts; i++) { + if (strcmp(mount_path, critical_mounts[i]) == 0) { + return 1; // This is a critical system mount + } + } + + return 0; // Not a critical system mount +} + +int validate_usb_device_safety(const usb_drive_info_t* drive) { + // Check if it's a system critical mount + if (is_system_critical_mount(drive->mount_path)) { + return 0; // Not safe - system critical + } + + // Check if device is removable + if (!drive->is_removable) { + return 0; // Not safe - not removable + } + + // Check if it's actually a USB device + if (!drive->is_usb_device) { + return 0; // Not safe - not USB + } + + // Check if bus type is USB + if (strcmp(drive->bus_type, "usb") != 0) { + return 0; // Not safe - not USB bus + } + + // Check drive size limit (1.1 TB = 1.1 * 1024^4 bytes) + const uint64_t max_drive_size = 1100ULL * 1024ULL * 1024ULL * 1024ULL * 1024ULL / 1000ULL; // 1.1 TB + if (drive->total_size > max_drive_size) { + return 0; // Not safe - drive too large (safety limit) + } + + return 1; // Safe for USB operations +} + +// Generate safety status letters for detailed failure reporting +void get_usb_safety_status(const usb_drive_info_t* drive, char* status, size_t status_size) { + if (!drive || !status || status_size < 6) { + if (status && status_size > 0) { + strncpy(status, "ERROR", status_size - 1); + status[status_size - 1] = '\0'; + } + return; + } + + // Check if all safety tests pass + if (validate_usb_device_safety(drive)) { + strncpy(status, "SAFE", status_size - 1); + status[status_size - 1] = '\0'; + return; + } + + // Build failure status with letters + char failures[6] = {0}; // Max 5 failure letters + null terminator + int failure_count = 0; + + // C - Critical Mount Check + if (is_system_critical_mount(drive->mount_path)) { + failures[failure_count++] = 'C'; + } + + // R - Removable Device Check + if (!drive->is_removable) { + failures[failure_count++] = 'R'; + } + + // U - USB Device Check + if (!drive->is_usb_device) { + failures[failure_count++] = 'U'; + } + + // B - Bus Type Validation + if (strcmp(drive->bus_type, "usb") != 0) { + failures[failure_count++] = 'B'; + } + + // S - Size Limit Check + const uint64_t max_drive_size = 1100ULL * 1024ULL * 1024ULL * 1024ULL * 1024ULL / 1000ULL; // 1.1 TB + if (drive->total_size > max_drive_size) { + failures[failure_count++] = 'S'; + } + + // Copy failure letters to status + if (failure_count > 0) { + strncpy(status, failures, status_size - 1); + status[status_size - 1] = '\0'; + } else { + // Shouldn't happen, but fallback + strncpy(status, "UNK", status_size - 1); + status[status_size - 1] = '\0'; + } +} + +int extract_device_name_from_path(const char* device_path, char* device_name, size_t device_name_size) { + const char* name_start = strrchr(device_path, '/'); + if (!name_start) return 0; + + name_start++; // Skip the '/' + strncpy(device_name, name_start, device_name_size - 1); + device_name[device_name_size - 1] = '\0'; + + return 1; +} + + +// Discover all available USB drives on the system +int discover_usb_drives(usb_drive_info_t** drives, int* drive_count) { + if (!drives || !drive_count) { + return 1; // Error: invalid parameters + } + + // Initialize output + *drives = NULL; + *drive_count = 0; + + // Allocate array for USB drive information + usb_drive_info_t* drive_array = malloc(MAX_USB_DRIVES * sizeof(usb_drive_info_t)); + if (!drive_array) { + return 2; // Error: memory allocation failed + } + + int found_count = 0; + const char* mount_dirs[] = {"/media", "/run/media", "/mnt", NULL}; + + // Search through common mount directories + for (int mount_idx = 0; mount_dirs[mount_idx] != NULL && found_count < MAX_USB_DRIVES; mount_idx++) { + DIR* mount_dir = opendir(mount_dirs[mount_idx]); + if (!mount_dir) continue; + + struct dirent* mount_entry; + while ((mount_entry = readdir(mount_dir)) != NULL && found_count < MAX_USB_DRIVES) { + if (mount_entry->d_name[0] == '.') continue; + + char mount_path[1024]; + snprintf(mount_path, sizeof(mount_path), "%s/%s", mount_dirs[mount_idx], mount_entry->d_name); + + // Handle different mount directory structures + if (strcmp(mount_dirs[mount_idx], "/media") == 0) { + // /media/[username] - look inside for drives + DIR* user_dir = opendir(mount_path); + if (!user_dir) continue; + + struct dirent* user_entry; + while ((user_entry = readdir(user_dir)) != NULL && found_count < MAX_USB_DRIVES) { + if (user_entry->d_name[0] == '.') continue; + + char drive_path[2048]; + snprintf(drive_path, sizeof(drive_path), "%s/%s", mount_path, user_entry->d_name); + + // Check if this is a readable directory (mounted drive) + DIR* drive_dir = opendir(drive_path); + if (drive_dir) { + closedir(drive_dir); + + // Get drive information - include ALL drives for debugging + usb_drive_info_t* drive = &drive_array[found_count]; + if (get_usb_drive_info(drive_path, user_entry->d_name, drive) == 0) { + // Store safety validation result in the drive structure + drive->is_safe = validate_usb_device_safety(drive); + found_count++; + } + } + } + closedir(user_dir); + + } else if (strcmp(mount_dirs[mount_idx], "/run/media") == 0) { + // /run/media/[username] - look inside for drives + DIR* user_dir = opendir(mount_path); + if (!user_dir) continue; + + struct dirent* user_entry; + while ((user_entry = readdir(user_dir)) != NULL && found_count < MAX_USB_DRIVES) { + if (user_entry->d_name[0] == '.') continue; + + char drive_path[2048]; + snprintf(drive_path, sizeof(drive_path), "%s/%s", mount_path, user_entry->d_name); + + DIR* drive_dir = opendir(drive_path); + if (drive_dir) { + closedir(drive_dir); + + usb_drive_info_t* drive = &drive_array[found_count]; + if (get_usb_drive_info(drive_path, user_entry->d_name, drive) == 0) { + // Store safety validation result in the drive structure + drive->is_safe = validate_usb_device_safety(drive); + found_count++; + } + } + } + closedir(user_dir); + + } else { + // Direct mount point (like /mnt/DRIVE_NAME) + DIR* drive_dir = opendir(mount_path); + if (drive_dir) { + closedir(drive_dir); + + usb_drive_info_t* drive = &drive_array[found_count]; + if (get_usb_drive_info(mount_path, mount_entry->d_name, drive) == 0) { + // Store safety validation result in the drive structure + drive->is_safe = validate_usb_device_safety(drive); + found_count++; + } + } + } + } + closedir(mount_dir); + } + + if (found_count == 0) { + free(drive_array); + return 3; // Error: no drives found + } + + // Resize array to actual count + usb_drive_info_t* final_array = realloc(drive_array, found_count * sizeof(usb_drive_info_t)); + if (!final_array) { + // Keep original array if realloc fails + final_array = drive_array; + } + + *drives = final_array; + *drive_count = found_count; + + return 0; // Success +} + +// Get detailed information about a USB drive +int get_usb_drive_info(const char* mount_path, const char* drive_label, usb_drive_info_t* drive_info) { + if (!mount_path || !drive_label || !drive_info) { + return 1; + } + + // Initialize drive info structure + memset(drive_info, 0, sizeof(usb_drive_info_t)); + + // Set basic information + strncpy(drive_info->mount_path, mount_path, sizeof(drive_info->mount_path) - 1); + strncpy(drive_info->volume_label, drive_label, sizeof(drive_info->volume_label) - 1); + + // Check if it's an OTP drive (label starts with "OTP_") + drive_info->is_otp_drive = (strncmp(drive_label, "OTP_", 4) == 0); + + // Enhanced device path detection with multiple methods + char device_path[256] = ""; + int device_found = 0; + + // Method 1: Enhanced /proc/mounts parsing + FILE* mounts = fopen("/proc/mounts", "r"); + if (mounts) { + char line[1024]; + while (fgets(line, sizeof(line), mounts)) { + char device[256], mountpoint[512], fstype[64]; + // Use more robust parsing that handles escaped characters + if (sscanf(line, "%255s %511s %63s", device, mountpoint, fstype) == 3) { + // Handle escaped characters in mount paths (octal sequences like \040 for space) + char decoded_mountpoint[512]; + size_t src = 0, dst = 0; + size_t mountpoint_len = strlen(mountpoint); + while (src < mountpoint_len && dst < sizeof(decoded_mountpoint) - 1) { + if (mountpoint[src] == '\\' && src + 3 < mountpoint_len && + mountpoint[src + 1] >= '0' && mountpoint[src + 1] <= '7') { + // Decode octal escape sequence + int octal_val = (mountpoint[src + 1] - '0') * 64 + + (mountpoint[src + 2] - '0') * 8 + + (mountpoint[src + 3] - '0'); + decoded_mountpoint[dst++] = (char)octal_val; + src += 4; + } else { + decoded_mountpoint[dst++] = mountpoint[src++]; + } + } + decoded_mountpoint[dst] = '\0'; + + // Debug: Print comparison details for DUAL DRIVE debugging + if (strstr(mount_path, "DUAL DRIVE") || strstr(decoded_mountpoint, "DUAL DRIVE")) { + printf("DEBUG Method1: mount_path='%s', decoded='%s', device='%s', match=%d\n", + mount_path, decoded_mountpoint, device, + strcmp(decoded_mountpoint, mount_path) == 0); + } + + if (strcmp(decoded_mountpoint, mount_path) == 0) { + strncpy(device_path, device, sizeof(device_path) - 1); + device_path[sizeof(device_path) - 1] = '\0'; + strncpy(drive_info->filesystem, fstype, sizeof(drive_info->filesystem) - 1); + drive_info->filesystem[sizeof(drive_info->filesystem) - 1] = '\0'; + device_found = 1; + break; + } + } + } + fclose(mounts); + } + + // Method 2: Try /proc/self/mountinfo for more detailed information + if (!device_found) { + FILE* mountinfo = fopen("/proc/self/mountinfo", "r"); + if (mountinfo) { + char line[1024]; + while (fgets(line, sizeof(line), mountinfo)) { + // Parse mountinfo format: mountid parentid major:minor root mount_point options - fstype source + char *token = strtok(line, " "); + int field_count = 0; + char *mount_field = NULL, *source_field = NULL; + + while (token && field_count < 10) { + field_count++; + if (field_count == 5) { // mount point field + mount_field = token; + } else if (field_count == 9) { // source field (after fstype) + source_field = token; + break; + } + token = strtok(NULL, " "); + } + + if (mount_field && source_field) { + // Decode escaped characters in mount point + char decoded_mount[512]; + size_t src = 0, dst = 0; + size_t mount_field_len = strlen(mount_field); + while (src < mount_field_len && dst < sizeof(decoded_mount) - 1) { + if (mount_field[src] == '\\' && src + 3 < mount_field_len) { + int octal_val = (mount_field[src + 1] - '0') * 64 + + (mount_field[src + 2] - '0') * 8 + + (mount_field[src + 3] - '0'); + decoded_mount[dst++] = (char)octal_val; + src += 4; + } else { + decoded_mount[dst++] = mount_field[src++]; + } + } + decoded_mount[dst] = '\0'; + + // Debug: Print comparison details for DUAL DRIVE debugging + if (strstr(mount_path, "DUAL DRIVE") || strstr(decoded_mount, "DUAL DRIVE")) { + printf("DEBUG Method2: mount_path='%s', decoded='%s', source='%s', match=%d\n", + mount_path, decoded_mount, source_field, + strcmp(decoded_mount, mount_path) == 0); + } + + if (strcmp(decoded_mount, mount_path) == 0) { + strncpy(device_path, source_field, sizeof(device_path) - 1); + device_path[sizeof(device_path) - 1] = '\0'; + device_found = 1; + break; + } + } + } + fclose(mountinfo); + } + } + + // Method 3: Enhanced smart inference from mount path patterns if device detection failed + int inferred_usb = 0; + if (!device_found) { + // Enhanced USB mount path pattern detection + if (strstr(mount_path, "/media/") || strstr(mount_path, "/run/media/")) { + // All /media/ and /run/media/ paths are highly likely to be USB drives + inferred_usb = 1; + strncpy(drive_info->device_name, "inferred_usb", sizeof(drive_info->device_name) - 1); + strncpy(drive_info->bus_type, "usb", sizeof(drive_info->bus_type) - 1); + } else if (strstr(mount_path, "/mnt/") && + (strstr(mount_path, "USB") || strstr(mount_path, "usb") || + strstr(mount_path, "DRIVE") || strstr(mount_path, "DISK"))) { + // /mnt/ paths with USB-like naming + inferred_usb = 1; + strncpy(drive_info->device_name, "inferred_usb", sizeof(drive_info->device_name) - 1); + strncpy(drive_info->bus_type, "usb", sizeof(drive_info->bus_type) - 1); + } else { + strncpy(drive_info->device_name, "unknown", sizeof(drive_info->device_name) - 1); + strncpy(drive_info->bus_type, "unknown", sizeof(drive_info->bus_type) - 1); + } + + // Set filesystem to unknown if not determined above + if (strlen(drive_info->filesystem) == 0) { + strncpy(drive_info->filesystem, "unknown", sizeof(drive_info->filesystem) - 1); + } + } + + // Debug: Print final device detection results for DUAL DRIVE + if (strstr(mount_path, "DUAL DRIVE")) { + printf("DEBUG Final: mount_path='%s', device_found=%d, device_path='%s', inferred_usb=%d\n", + mount_path, device_found, device_path, inferred_usb); + } + + // Populate safety fields using the USB safety functions + if (device_found && strlen(device_path) > 0) { + // Extract device name from device path + extract_device_name_from_path(device_path, drive_info->device_name, sizeof(drive_info->device_name)); + + // Check if device is removable + drive_info->is_removable = is_device_removable_usb(device_path); + + // Get bus type + get_device_bus_type(device_path, drive_info->bus_type, sizeof(drive_info->bus_type)); + + // Check if it's a USB device (based on bus type) + drive_info->is_usb_device = (strcmp(drive_info->bus_type, "usb") == 0); + + // Check if it's a system critical mount + drive_info->is_system_mount = is_system_critical_mount(mount_path); + } else if (inferred_usb) { + // Use smart inference for likely USB drives when device detection fails + drive_info->is_removable = 1; // Assume removable for USB mount paths + drive_info->is_usb_device = 1; // Inferred as USB device + drive_info->is_system_mount = is_system_critical_mount(mount_path); + } else { + // Conservative fallback - mark as unknown but not unsafe unless proven otherwise + drive_info->is_removable = 0; // Cannot determine, assume non-removable + drive_info->is_usb_device = 0; // Cannot determine, assume non-USB + drive_info->is_system_mount = is_system_critical_mount(mount_path); + } + + // Get filesystem information using statvfs + struct statvfs vfs; + if (statvfs(mount_path, &vfs) == 0) { + drive_info->total_size = (uint64_t)vfs.f_blocks * vfs.f_frsize; + drive_info->available_size = (uint64_t)vfs.f_bavail * vfs.f_frsize; + } else { + // Fallback: try to get basic directory info + struct stat st; + if (stat(mount_path, &st) == 0) { + drive_info->total_size = 0; // Unknown + drive_info->available_size = 0; // Unknown + } else { + return 2; // Cannot access drive + } + } + + // Count OTP pad files if it's an OTP drive + drive_info->pad_count = 0; + if (drive_info->is_otp_drive) { + DIR* dir = opendir(mount_path); + if (dir) { + struct dirent* entry; + while ((entry = readdir(dir)) != NULL) { + if (strstr(entry->d_name, ".pad") && strlen(entry->d_name) == 68) { + drive_info->pad_count++; + } + } + closedir(dir); + } + } + + // Set mount status (assume mounted since we can access it) + drive_info->is_mounted = 1; + + return 0; // Success +} + +// Initialize a USB drive for OTP use +usb_operation_result_t initialize_usb_drive(const char* mount_path, const char* drive_name, + int format_drive, const char* filesystem_type) { + usb_operation_result_t result = {0}; + + if (!mount_path || !drive_name) { + result.success = 0; + strncpy(result.error_message, "Invalid parameters", sizeof(result.error_message) - 1); + return result; + } + + // Validate drive name format + if (strncmp(drive_name, "OTP_", 4) != 0) { + result.success = 0; + strncpy(result.error_message, "Drive name must start with 'OTP_'", sizeof(result.error_message) - 1); + return result; + } + + if (strlen(drive_name) > 32 || strlen(drive_name) < 5) { + result.success = 0; + strncpy(result.error_message, "Drive name must be 5-32 characters", sizeof(result.error_message) - 1); + return result; + } + + // Check if drive is writable + if (access(mount_path, W_OK) != 0) { + result.success = 0; + strncpy(result.error_message, "Drive is not writable", sizeof(result.error_message) - 1); + return result; + } + + // Format drive if requested + if (format_drive) { + printf("Formatting USB drive (this may take several minutes)...\n"); + + // Get device path from mount path + char device_path[256] = ""; + if (get_device_from_mount(mount_path, device_path, sizeof(device_path)) != 0) { + result.success = 0; + strncpy(result.error_message, "Cannot determine device path", sizeof(result.error_message) - 1); + return result; + } + + // Unmount before formatting + char umount_cmd[512]; + snprintf(umount_cmd, sizeof(umount_cmd), "umount '%s' 2>/dev/null", mount_path); + system(umount_cmd); // Ignore errors + + // Format with specified filesystem + char format_cmd[512]; + if (strcmp(filesystem_type, "fat32") == 0) { + snprintf(format_cmd, sizeof(format_cmd), + "mkfs.fat -F32 -n '%s' '%s' 2>/dev/null", drive_name, device_path); + } else if (strcmp(filesystem_type, "ext4") == 0) { + snprintf(format_cmd, sizeof(format_cmd), + "mkfs.ext4 -L '%s' '%s' -F 2>/dev/null", drive_name, device_path); + } else { + result.success = 0; + strncpy(result.error_message, "Unsupported filesystem type", sizeof(result.error_message) - 1); + return result; + } + + if (system(format_cmd) != 0) { + result.success = 0; + strncpy(result.error_message, "Format operation failed", sizeof(result.error_message) - 1); + return result; + } + + // Wait for system to recognize the new filesystem + sleep(2); + + // Remount (let system handle this automatically) + printf("Format completed. Please remount the drive if necessary.\n"); + } + + // Set volume label if not formatting + if (!format_drive) { + if (set_volume_label(mount_path, drive_name) != 0) { + result.success = 0; + strncpy(result.error_message, "Failed to set volume label", sizeof(result.error_message) - 1); + return result; + } + } + + // Create OTP directory structure if needed + char otp_readme_path[1024]; + snprintf(otp_readme_path, sizeof(otp_readme_path), "%s/OTP_README.txt", mount_path); + + FILE* readme = fopen(otp_readme_path, "w"); + if (readme) { + fprintf(readme, "OTP Drive: %s\n", drive_name); + time_t init_time = time(NULL); + fprintf(readme, "Initialized: %s", ctime(&init_time)); + fprintf(readme, "\nThis USB drive has been initialized for use with the OTP program.\n"); + fprintf(readme, "OTP pad files stored on this drive use the same .pad/.state format\n"); + fprintf(readme, "as local pads and can be used for encryption/decryption operations.\n"); + fprintf(readme, "\nDO NOT modify .pad or .state files manually!\n"); + fprintf(readme, "Use the OTP program's pad management features instead.\n"); + fclose(readme); + } + + result.success = 1; + // Success details are in the error_message field for simplicity + strncpy(result.error_message, "USB drive initialized successfully", sizeof(result.error_message) - 1); + + return result; +} + +// Set volume label for a USB drive +int set_volume_label(const char* mount_path, const char* new_label) { + if (!mount_path || !new_label) { + return 1; + } + + // Get device path + char device_path[256]; + if (get_device_from_mount(mount_path, device_path, sizeof(device_path)) != 0) { + return 2; + } + + // Try different labeling methods based on filesystem + char label_cmd[512]; + + // First try FAT32 labeling + snprintf(label_cmd, sizeof(label_cmd), "fatlabel '%s' '%s' 2>/dev/null", device_path, new_label); + if (system(label_cmd) == 0) { + return 0; + } + + // Try ext filesystem labeling + snprintf(label_cmd, sizeof(label_cmd), "e2label '%s' '%s' 2>/dev/null", device_path, new_label); + if (system(label_cmd) == 0) { + return 0; + } + + // Try generic tune2fs for ext filesystems + snprintf(label_cmd, sizeof(label_cmd), "tune2fs -L '%s' '%s' 2>/dev/null", new_label, device_path); + if (system(label_cmd) == 0) { + return 0; + } + + return 3; // All methods failed +} + +// Get device path from mount path +int get_device_from_mount(const char* mount_path, char* device_path, size_t device_path_size) { + if (!mount_path || !device_path) { + return 1; + } + + FILE* mounts = fopen("/proc/mounts", "r"); + if (!mounts) { + return 2; + } + + char line[1024]; + while (fgets(line, sizeof(line), mounts)) { + char device[256], mountpoint[512]; + if (sscanf(line, "%s %s", device, mountpoint) == 2) { + if (strcmp(mountpoint, mount_path) == 0) { + strncpy(device_path, device, device_path_size - 1); + device_path[device_path_size - 1] = '\0'; + fclose(mounts); + return 0; + } + } + } + + fclose(mounts); + return 3; // Mount point not found +} + +// Select USB drive interactively +usb_drive_info_t* select_usb_drive_interactive(const char* title, const char* prompt, int require_otp) { + usb_drive_info_t* drives; + int drive_count; + + printf("\n%s\n", title); + printf("Scanning for USB drives...\n"); + + int result = discover_usb_drives(&drives, &drive_count); + if (result != 0) { + switch (result) { + case 3: // USB_ERROR_NO_DRIVES_FOUND + printf("No USB drives found.\n"); + printf("Please ensure your USB drive is properly mounted.\n"); + break; + case 2: // USB_ERROR_MEMORY_ALLOCATION + printf("Error: Memory allocation failed.\n"); + break; + default: + printf("Error: Failed to discover USB drives (code: %d).\n", result); + break; + } + return NULL; + } + + // Count safe drives that meet requirements + int safe_filtered_count = 0; + int total_filtered_count = 0; + for (int i = 0; i < drive_count; i++) { + if (!require_otp || drives[i].is_otp_drive) { + total_filtered_count++; + if (drives[i].is_safe) { + safe_filtered_count++; + } + } + } + + if (total_filtered_count == 0) { + if (require_otp) { + printf("No OTP USB drives found.\n"); + printf("Initialize a USB drive first using the 'Initialize USB drive' option.\n"); + } else { + printf("No USB drives found.\n"); + } + free(drives); + return NULL; + } + + // Display available drives with safety indicators + printf("\nFound USB drives:\n"); + printf("%-3s %-20s %-10s %-12s %-12s %-8s %-4s %-6s\n", + "#", "Label", "Type", "Total", "Free", "FS", "Pads", "Status"); + printf("%-3s %-20s %-10s %-12s %-12s %-8s %-4s %-6s\n", + "---", "--------------------", "----------", "----------", "----------", "--------", "----", "------"); + + int display_index = 1; + int safe_display_index = 1; + for (int i = 0; i < drive_count; i++) { + if (require_otp && !drives[i].is_otp_drive) { + continue; // Skip non-OTP drives when filtering + } + + // Format sizes for display + char total_str[16], free_str[16]; + format_size_string(drives[i].total_size, total_str, sizeof(total_str)); + format_size_string(drives[i].available_size, free_str, sizeof(free_str)); + + // Display with appropriate formatting + if (drives[i].is_safe) { + // Safe drive - normal display with selectable number + printf("%-3d %-20s %-10s %-12s %-12s %-8s %-4d %-6s\n", + safe_display_index, + drives[i].volume_label, + drives[i].is_otp_drive ? "OTP" : "Standard", + total_str, + free_str, + drives[i].filesystem, + drives[i].pad_count, + "SAFE"); + safe_display_index++; + } else { + // Unsafe drive - strikethrough display, no selection number + printf("%-3s \033[9m%-20s %-10s %-12s %-12s %-8s %-4d\033[0m %-6s\n", + "---", + drives[i].volume_label, + drives[i].is_otp_drive ? "OTP" : "Standard", + total_str, + free_str, + drives[i].filesystem, + drives[i].pad_count, + "UNSAFE"); + } + display_index++; + } + + if (safe_filtered_count == 0) { + printf("\n⚠ WARNING: No safe USB drives found!\n"); + printf("All detected drives failed safety validation.\n"); + printf("Common issues: System drives, non-removable media, non-USB devices\n"); + free(drives); + return NULL; + } + + printf("\n%s (1-%d): ", prompt, safe_filtered_count); + + char input[10]; + if (!fgets(input, sizeof(input), stdin)) { + free(drives); + return NULL; + } + + int selection = atoi(input); + if (selection < 1 || selection > safe_filtered_count) { + printf("Invalid selection.\n"); + free(drives); + return NULL; + } + + // Find the selected safe drive (accounting for filtering and safety) + int current_safe_index = 1; + for (int i = 0; i < drive_count; i++) { + if (require_otp && !drives[i].is_otp_drive) { + continue; + } + + if (drives[i].is_safe) { + if (current_safe_index == selection) { + // Create a copy of the selected drive info + usb_drive_info_t* selected_drive = malloc(sizeof(usb_drive_info_t)); + if (selected_drive) { + *selected_drive = drives[i]; + } + free(drives); + return selected_drive; + } + current_safe_index++; + } + } + + free(drives); + return NULL; +} + +// Format file size for display +void format_size_string(uint64_t bytes, char* result, size_t result_size) { + if (bytes == 0) { + strncpy(result, "Unknown", result_size - 1); + result[result_size - 1] = '\0'; + return; + } + + if (bytes < 1024) { + snprintf(result, result_size, "%luB", bytes); + } else if (bytes < 1024 * 1024) { + snprintf(result, result_size, "%.1fKB", (double)bytes / 1024.0); + } else if (bytes < 1024 * 1024 * 1024) { + snprintf(result, result_size, "%.1fMB", (double)bytes / (1024.0 * 1024.0)); + } else if (bytes < 1024ULL * 1024 * 1024 * 1024) { + snprintf(result, result_size, "%.2fGB", (double)bytes / (1024.0 * 1024.0 * 1024.0)); + } else { + snprintf(result, result_size, "%.2fTB", (double)bytes / (1024.0 * 1024.0 * 1024.0 * 1024.0)); + } +} + + // Custom base64 encode function + +// Copy pad from local storage to USB drive +int copy_pad_to_usb(const char* pad_checksum, const char* usb_mount_path) { + if (!pad_checksum || !usb_mount_path) { + printf("Error: Invalid parameters for pad copy operation\n"); + return 1; + } + + // Build source paths + char local_pad_path[1024], local_state_path[1024]; + get_pad_path(pad_checksum, local_pad_path, local_state_path); + + // Check if local pad exists + if (access(local_pad_path, R_OK) != 0) { + printf("Error: Local pad file not found: %s\n", local_pad_path); + return 2; + } + + // Build destination paths + char usb_pad_path[1024], usb_state_path[1024]; + snprintf(usb_pad_path, sizeof(usb_pad_path), "%s/%s.pad", usb_mount_path, pad_checksum); + snprintf(usb_state_path, sizeof(usb_state_path), "%s/%s.state", usb_mount_path, pad_checksum); + + // Check if destination pad already exists + if (access(usb_pad_path, F_OK) == 0) { + printf("Warning: Pad already exists on USB drive: %s\n", pad_checksum); + printf("Overwrite? (y/N): "); + fflush(stdout); + + char response[10]; + if (fgets(response, sizeof(response), stdin) == NULL || + (response[0] != 'y' && response[0] != 'Y')) { + printf("Copy operation cancelled.\n"); + return 3; + } + } + + // Copy pad file + printf("Copying pad file...\n"); + if (copy_file(local_pad_path, usb_pad_path) != 0) { + printf("Error: Failed to copy pad file to USB drive\n"); + return 4; + } + + // Copy state file if it exists + if (access(local_state_path, R_OK) == 0) { + printf("Copying state file...\n"); + if (copy_file(local_state_path, usb_state_path) != 0) { + printf("Warning: Failed to copy state file (pad file copied successfully)\n"); + // This is not fatal - pad can still be used + } + } + + // Verify the copy + printf("Verifying integrity...\n"); + if (verify_pad_integrity_cross_drive(local_pad_path, usb_pad_path) != 0) { + printf("Error: Integrity verification failed - removing corrupted copy\n"); + unlink(usb_pad_path); + unlink(usb_state_path); + return 5; + } + + printf("✓ Pad successfully copied to USB drive: %.16s...\n", pad_checksum); + return 0; +} + +// Copy pad from USB drive to local storage +int copy_pad_from_usb(const char* usb_mount_path, const char* pad_checksum) { + if (!usb_mount_path || !pad_checksum) { + printf("Error: Invalid parameters for pad import operation\n"); + return 1; + } + + // Build source paths on USB + char usb_pad_path[1024], usb_state_path[1024]; + snprintf(usb_pad_path, sizeof(usb_pad_path), "%s/%s.pad", usb_mount_path, pad_checksum); + snprintf(usb_state_path, sizeof(usb_state_path), "%s/%s.state", usb_mount_path, pad_checksum); + + // Check if USB pad exists + if (access(usb_pad_path, R_OK) != 0) { + printf("Error: Pad file not found on USB drive: %s\n", pad_checksum); + return 2; + } + + // Build destination paths + char local_pad_path[1024], local_state_path[1024]; + get_pad_path(pad_checksum, local_pad_path, local_state_path); + + // Check if local pad already exists + if (access(local_pad_path, F_OK) == 0) { + printf("Warning: Pad already exists locally: %s\n", pad_checksum); + printf("Overwrite? (y/N): "); + fflush(stdout); + + char response[10]; + if (fgets(response, sizeof(response), stdin) == NULL || + (response[0] != 'y' && response[0] != 'Y')) { + printf("Import operation cancelled.\n"); + return 3; + } + } + + // Ensure pads directory exists + if (ensure_pads_directory() != 0) { + printf("Error: Cannot create local pads directory\n"); + return 4; + } + + // Copy pad file from USB + printf("Importing pad file...\n"); + if (copy_file(usb_pad_path, local_pad_path) != 0) { + printf("Error: Failed to import pad file from USB drive\n"); + return 5; + } + + // Set pad file to read-only + if (chmod(local_pad_path, S_IRUSR) != 0) { + printf("Warning: Cannot set pad file to read-only\n"); + } + + // Copy state file if it exists + if (access(usb_state_path, R_OK) == 0) { + printf("Importing state file...\n"); + if (copy_file(usb_state_path, local_state_path) != 0) { + printf("Warning: Failed to import state file (pad file imported successfully)\n"); + // Create a fresh state file with default offset + FILE* new_state = fopen(local_state_path, "wb"); + if (new_state) { + uint64_t default_offset = 32; // Reserved bytes + fwrite(&default_offset, sizeof(uint64_t), 1, new_state); + fclose(new_state); + printf("Created new state file with default offset\n"); + } + } + } else { + // Create state file if it doesn't exist + printf("Creating state file...\n"); + FILE* new_state = fopen(local_state_path, "wb"); + if (new_state) { + uint64_t default_offset = 32; // Reserved bytes + fwrite(&default_offset, sizeof(uint64_t), 1, new_state); + fclose(new_state); + } + } + + // Verify the import + printf("Verifying integrity...\n"); + if (verify_pad_integrity_cross_drive(usb_pad_path, local_pad_path) != 0) { + printf("Error: Integrity verification failed - removing corrupted import\n"); + unlink(local_pad_path); + unlink(local_state_path); + return 6; + } + + printf("✓ Pad successfully imported from USB drive: %.16s...\n", pad_checksum); + return 0; +} + +// Generic file copy function +int copy_file(const char* source_path, const char* dest_path) { + if (!source_path || !dest_path) { + return 1; + } + + FILE* source = fopen(source_path, "rb"); + if (!source) { + return 2; + } + + FILE* dest = fopen(dest_path, "wb"); + if (!dest) { + fclose(source); + return 3; + } + + // Copy in chunks for large files + unsigned char buffer[64 * 1024]; // 64KB buffer + size_t bytes_read; + int error = 0; + + while ((bytes_read = fread(buffer, 1, sizeof(buffer), source)) > 0) { + if (fwrite(buffer, 1, bytes_read, dest) != bytes_read) { + error = 4; // Write error + break; + } + } + + fclose(source); + fclose(dest); + + if (error) { + unlink(dest_path); // Remove incomplete file + return error; + } + + return 0; +} + +// List pads available on a USB drive +int list_pads_on_drive(const char* mount_path, char pad_list[][65], int max_pads) { + if (!mount_path || !pad_list || max_pads <= 0) { + return -1; + } + + DIR* dir = opendir(mount_path); + if (!dir) { + return -2; + } + + int pad_count = 0; + struct dirent* entry; + + while ((entry = readdir(dir)) != NULL && pad_count < max_pads) { + // Check if this is a pad file + if (strstr(entry->d_name, ".pad") && strlen(entry->d_name) == 68) { + // Extract checksum (remove .pad extension) + strncpy(pad_list[pad_count], entry->d_name, 64); + pad_list[pad_count][64] = '\0'; + pad_count++; + } + } + + closedir(dir); + return pad_count; +} + +// Verify integrity between two pad files (cross-drive verification) +int verify_pad_integrity_cross_drive(const char* source_pad, const char* dest_pad) { + if (!source_pad || !dest_pad) { + return 1; + } + + // Calculate checksums of both files + char source_checksum[65], dest_checksum[65]; + + if (calculate_checksum(source_pad, source_checksum) != 0) { + printf("Error: Cannot calculate source pad checksum\n"); + return 2; + } + + if (calculate_checksum(dest_pad, dest_checksum) != 0) { + printf("Error: Cannot calculate destination pad checksum\n"); + return 3; + } + + // Compare checksums + if (strcmp(source_checksum, dest_checksum) != 0) { + printf("Error: Pad integrity verification failed\n"); + printf("Source: %s\n", source_checksum); + printf("Destination: %s\n", dest_checksum); + return 4; + } + + // Also verify file sizes match + struct stat source_stat, dest_stat; + if (stat(source_pad, &source_stat) != 0 || stat(dest_pad, &dest_stat) != 0) { + printf("Error: Cannot verify file sizes\n"); + return 5; + } + + if (source_stat.st_size != dest_stat.st_size) { + printf("Error: File sizes don't match\n"); + printf("Source: %lu bytes, Destination: %lu bytes\n", + source_stat.st_size, dest_stat.st_size); + return 6; + } + + return 0; // Verification successful +} + +// Selective drive duplication with pad selection +int duplicate_drive_selective(const char* source_mount, const char* dest_mount, + char selected_pads[][65], int pad_count) { + if (!source_mount || !dest_mount || !selected_pads || pad_count <= 0) { + printf("Error: Invalid parameters for drive duplication\n"); + return 1; + } + + printf("Starting selective drive duplication...\n"); + printf("Source: %s\n", source_mount); + printf("Destination: %s\n", dest_mount); + printf("Pads to copy: %d\n\n", pad_count); + + int success_count = 0; + int error_count = 0; + + // Copy each selected pad + for (int i = 0; i < pad_count; i++) { + printf("[%d/%d] Copying pad: %.16s...\n", i + 1, pad_count, selected_pads[i]); + + // Build source paths + char source_pad[1024], source_state[1024]; + snprintf(source_pad, sizeof(source_pad), "%s/%s.pad", source_mount, selected_pads[i]); + snprintf(source_state, sizeof(source_state), "%s/%s.state", source_mount, selected_pads[i]); + + // Build destination paths + char dest_pad[1024], dest_state[1024]; + snprintf(dest_pad, sizeof(dest_pad), "%s/%s.pad", dest_mount, selected_pads[i]); + snprintf(dest_state, sizeof(dest_state), "%s/%s.state", dest_mount, selected_pads[i]); + + // Check source pad exists + if (access(source_pad, R_OK) != 0) { + printf(" Error: Source pad not found - skipping\n"); + error_count++; + continue; + } + + // Copy pad file + if (copy_file(source_pad, dest_pad) != 0) { + printf(" Error: Failed to copy pad file - skipping\n"); + error_count++; + continue; + } + + // Copy state file if it exists + if (access(source_state, R_OK) == 0) { + if (copy_file(source_state, dest_state) != 0) { + printf(" Warning: Failed to copy state file\n"); + // Create default state file + FILE* new_state = fopen(dest_state, "wb"); + if (new_state) { + uint64_t default_offset = 32; + fwrite(&default_offset, sizeof(uint64_t), 1, new_state); + fclose(new_state); + } + } + } else { + // Create default state file + FILE* new_state = fopen(dest_state, "wb"); + if (new_state) { + uint64_t default_offset = 32; + fwrite(&default_offset, sizeof(uint64_t), 1, new_state); + fclose(new_state); + } + } + + // Verify integrity + if (verify_pad_integrity_cross_drive(source_pad, dest_pad) != 0) { + printf(" Error: Integrity verification failed - removing copy\n"); + unlink(dest_pad); + unlink(dest_state); + error_count++; + continue; + } + + printf(" ✓ Success\n"); + success_count++; + } + + printf("\nDuplication completed:\n"); + printf("Successfully copied: %d pads\n", success_count); + printf("Errors encountered: %d pads\n", error_count); + + return (error_count > 0) ? error_count : 0; +} + +// Verify complete drive duplication +int verify_drive_duplication(const char* source_mount, const char* dest_mount) { + if (!source_mount || !dest_mount) { + return 1; + } + + printf("Verifying drive duplication...\n"); + + // Get list of pads on source drive + char source_pads[100][65]; + int source_count = list_pads_on_drive(source_mount, source_pads, 100); + if (source_count < 0) { + printf("Error: Cannot list pads on source drive\n"); + return 2; + } + + // Get list of pads on destination drive + char dest_pads[100][65]; + int dest_count = list_pads_on_drive(dest_mount, dest_pads, 100); + if (dest_count < 0) { + printf("Error: Cannot list pads on destination drive\n"); + return 3; + } + + printf("Source drive: %d pads\n", source_count); + printf("Destination drive: %d pads\n", dest_count); + + int verified_count = 0; + int error_count = 0; + + // Verify each pad on source exists on destination and matches + for (int i = 0; i < source_count; i++) { + printf("Verifying pad: %.16s...\n", source_pads[i]); + + // Check if this pad exists on destination + int found = 0; + for (int j = 0; j < dest_count; j++) { + if (strcmp(source_pads[i], dest_pads[j]) == 0) { + found = 1; + break; + } + } + + if (!found) { + printf(" Error: Pad missing on destination drive\n"); + error_count++; + continue; + } + + // Verify integrity + char source_path[1024], dest_path[1024]; + snprintf(source_path, sizeof(source_path), "%s/%s.pad", source_mount, source_pads[i]); + snprintf(dest_path, sizeof(dest_path), "%s/%s.pad", dest_mount, source_pads[i]); + + if (verify_pad_integrity_cross_drive(source_path, dest_path) != 0) { + printf(" Error: Integrity verification failed\n"); + error_count++; + continue; + } + + printf(" ✓ Verified\n"); + verified_count++; + } + + printf("\nVerification completed:\n"); + printf("Successfully verified: %d pads\n", verified_count); + printf("Errors found: %d pads\n", error_count); + + return (error_count > 0) ? error_count : 0; +} + char* custom_base64_encode(const unsigned char* input, int length) { int output_length = 4 * ((length + 2) / 3); char* encoded = malloc(output_length + 1); @@ -2258,6 +4076,372 @@ unsigned char* custom_base64_decode(const char* input, int* output_length) { //////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// TRUERNG DEVICE DETECTION AND COMMUNICATION +// Ported from true_rng/main.c for entropy collection +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +// Read USB device info from sysfs (ported from TrueRNG reference) +int read_usb_device_info(const char* port_name, char* vid, char* pid) { + char path[512]; + FILE *fp; + + // Try to read idVendor first (works for both ttyUSB and ttyACM devices) + snprintf(path, sizeof(path), "/sys/class/tty/%s/device/../idVendor", port_name); + fp = fopen(path, "r"); + if (fp) { + if (fgets(vid, 8, fp) != NULL) { + // Remove newline if present + int len = strlen(vid); + if (len > 0 && vid[len-1] == '\n') { + vid[len-1] = '\0'; + } + } else { + fclose(fp); + return 0; + } + fclose(fp); + } else { + return 0; + } + + // Try to read idProduct + snprintf(path, sizeof(path), "/sys/class/tty/%s/device/../idProduct", port_name); + fp = fopen(path, "r"); + if (fp) { + if (fgets(pid, 8, fp) != NULL) { + // Remove newline if present + int len = strlen(pid); + if (len > 0 && pid[len-1] == '\n') { + pid[len-1] = '\0'; + } + } else { + fclose(fp); + return 0; + } + fclose(fp); + return 1; + } else { + return 0; + } +} + +// Find TrueRNG device port (ported and adapted from TrueRNG reference) +// Returns: 0=not found, 1=TrueRNGproV2, 2=TrueRNGpro, 3=TrueRNG +int find_truerng_port(char* port_path, size_t port_path_size, truerng_device_type_t* device_type) { + DIR *dir; + struct dirent *entry; + char vid[8], pid[8]; + int device_found = 0; + + dir = opendir("/dev"); + if (dir == NULL) { + return 0; + } + + while ((entry = readdir(dir)) != NULL) { + // Look for ttyUSB* or ttyACM* devices + if (strncmp(entry->d_name, "ttyUSB", 6) == 0 || + strncmp(entry->d_name, "ttyACM", 6) == 0) { + + if (read_usb_device_info(entry->d_name, vid, pid)) { + // Convert to uppercase for comparison + for (int i = 0; vid[i]; i++) vid[i] = toupper(vid[i]); + for (int i = 0; pid[i]; i++) pid[i] = toupper(pid[i]); + + // Check for TrueRNGproV2 + if (strcmp(vid, TRUERNGPROV2_VID) == 0 && strcmp(pid, TRUERNGPROV2_PID) == 0) { + snprintf(port_path, port_path_size, "/dev/%s", entry->d_name); + *device_type = TRUERNG_PRO_V2; + device_found = 1; + break; + } + + // Check for TrueRNGpro + if (strcmp(vid, TRUERNGPRO_VID) == 0 && strcmp(pid, TRUERNGPRO_PID) == 0) { + snprintf(port_path, port_path_size, "/dev/%s", entry->d_name); + *device_type = TRUERNG_PRO; + device_found = 2; + break; + } + + // Check for TrueRNG + if (strcmp(vid, TRUERNG_VID) == 0 && strcmp(pid, TRUERNG_PID) == 0) { + snprintf(port_path, port_path_size, "/dev/%s", entry->d_name); + *device_type = TRUERNG_ORIGINAL; + device_found = 3; + break; + } + } + } + } + + closedir(dir); + return device_found; +} + +// Setup serial port for TrueRNG communication (ported from TrueRNG reference) +int setup_truerng_serial_port(const char* port_path) { + int fd; + struct termios tty; + + fd = open(port_path, O_RDWR | O_NOCTTY); + if (fd < 0) { + return -1; + } + + // Get current port settings + if (tcgetattr(fd, &tty) != 0) { + close(fd); + return -1; + } + + // Set baud rate (TrueRNG devices use 9600) + cfsetospeed(&tty, B9600); + cfsetispeed(&tty, B9600); + + // 8N1 mode + tty.c_cflag &= ~PARENB; // No parity + tty.c_cflag &= ~CSTOPB; // 1 stop bit + tty.c_cflag &= ~CSIZE; // Clear size bits + tty.c_cflag |= CS8; // 8 data bits + tty.c_cflag &= ~CRTSCTS; // No hardware flow control + tty.c_cflag |= CREAD | CLOCAL; // Enable reading and ignore modem controls + + // Raw input mode + tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + tty.c_iflag &= ~(IXON | IXOFF | IXANY); + tty.c_oflag &= ~OPOST; + + // Set for blocking reads - wait for data indefinitely + tty.c_cc[VMIN] = 1; // Block until at least 1 character is received + tty.c_cc[VTIME] = 0; // No timeout + + // Apply settings + if (tcsetattr(fd, TCSANOW, &tty) != 0) { + close(fd); + return -1; + } + + // Flush input buffer + tcflush(fd, TCIFLUSH); + + // Set DTR + int status; + ioctl(fd, TIOCMGET, &status); + status |= TIOCM_DTR; + ioctl(fd, TIOCMSET, &status); + + return fd; +} + +// Get friendly name for TrueRNG device type +const char* get_truerng_device_name(truerng_device_type_t device_type) { + switch (device_type) { + case TRUERNG_PRO_V2: return "TrueRNGproV2"; + case TRUERNG_PRO: return "TrueRNGpro"; + case TRUERNG_ORIGINAL: return "TrueRNG"; + default: return "Unknown"; + } +} + +// Collect entropy from TrueRNG device with equivalent quality to keyboard entropy +int collect_truerng_entropy(unsigned char* entropy_buffer, size_t target_bytes, + size_t* collected_bytes, int display_progress) { + char port_path[512]; + truerng_device_type_t device_type; + int serial_fd = -1; + + // Find TrueRNG device + if (!find_truerng_port(port_path, sizeof(port_path), &device_type)) { + if (display_progress) { + printf("No TrueRNG device found.\n"); + printf("\nSupported devices:\n"); + printf(" - TrueRNG (PID: %s, VID: %s)\n", TRUERNG_VID, TRUERNG_PID); + printf(" - TrueRNGpro (PID: %s, VID: %s)\n", TRUERNGPRO_VID, TRUERNGPRO_PID); + printf(" - TrueRNGproV2 (PID: %s, VID: %s)\n", TRUERNGPROV2_VID, TRUERNGPROV2_PID); + printf("\nPlease connect a TrueRNG device and try again.\n"); + } + return 1; // Device not found + } + + if (display_progress) { + printf("Found %s at %s\n", get_truerng_device_name(device_type), port_path); + printf("Collecting %zu bytes of entropy...\n", target_bytes); + } + + // Setup serial port + serial_fd = setup_truerng_serial_port(port_path); + if (serial_fd < 0) { + if (display_progress) { + printf("Error: Cannot open TrueRNG device at %s\n", port_path); + printf("Check device permissions or run as root.\n"); + } + return 2; // Serial port setup failed + } + + // Collect entropy data + size_t bytes_read = 0; + unsigned char buffer[1024]; // Read in 1KB chunks + time_t start_time = time(NULL); + + while (bytes_read < target_bytes) { + size_t chunk_size = sizeof(buffer); + if (target_bytes - bytes_read < chunk_size) { + chunk_size = target_bytes - bytes_read; + } + + size_t bytes_in_chunk = 0; + while (bytes_in_chunk < chunk_size) { + ssize_t result = read(serial_fd, buffer + bytes_in_chunk, chunk_size - bytes_in_chunk); + if (result < 0) { + if (display_progress) { + printf("Error: Failed to read from TrueRNG device\n"); + } + close(serial_fd); + return 3; // Read failed + } else if (result == 0) { + if (display_progress) { + printf("Error: No data received from TrueRNG device\n"); + } + close(serial_fd); + return 4; // No data + } + bytes_in_chunk += result; + } + + // Copy to entropy buffer + memcpy(entropy_buffer + bytes_read, buffer, chunk_size); + bytes_read += chunk_size; + + // Show progress for large collections + if (display_progress && bytes_read % (4 * 1024) == 0) { // Every 4KB + double percentage = (double)bytes_read / target_bytes * 100.0; + printf("Progress: %.1f%% (%zu/%zu bytes)\r", percentage, bytes_read, target_bytes); + fflush(stdout); + } + } + + close(serial_fd); + + if (display_progress) { + double collection_time = difftime(time(NULL), start_time); + printf("\n✓ TrueRNG entropy collection complete!\n"); + printf(" Collected: %zu bytes in %.1f seconds\n", bytes_read, collection_time); + printf(" Device: %s\n", get_truerng_device_name(device_type)); + if (collection_time > 0) { + printf(" Rate: %.1f KB/s\n", (double)bytes_read / collection_time / 1024.0); + } + } + + *collected_bytes = bytes_read; + return 0; // Success +} + +// Collect dice entropy with manual input validation +int collect_dice_entropy(unsigned char* entropy_buffer, size_t target_bytes, + size_t* collected_bytes, int display_progress) { + if (display_progress) { + printf("=== Dice Entropy Collection ===\n"); + printf("Enter dice rolls as sequences of digits 1-6.\n"); + printf("Target: %zu bytes (%zu dice rolls needed)\n", target_bytes, target_bytes * 4); + printf("Press Enter after each sequence, or 'done' when finished.\n\n"); + } + + size_t entropy_bits = 0; + size_t target_bits = target_bytes * 8; + unsigned char current_byte = 0; + int bits_in_byte = 0; + size_t bytes_written = 0; + + char input[256]; + + while (entropy_bits < target_bits && bytes_written < target_bytes) { + if (display_progress) { + double percentage = (double)entropy_bits / target_bits * 100.0; + printf("Progress: %.1f%% (%zu/%zu bits) - Enter dice rolls: ", + percentage, entropy_bits, target_bits); + fflush(stdout); + } + + if (!fgets(input, sizeof(input), stdin)) { + if (display_progress) { + printf("Error: Failed to read input\n"); + } + return 1; + } + + // Remove newline + input[strcspn(input, "\n")] = 0; + + // Check for done command + if (strcmp(input, "done") == 0 && entropy_bits >= target_bits / 2) { + break; // Allow early exit if we have at least half the target + } + + // Process dice rolls + for (size_t i = 0; input[i] && entropy_bits < target_bits && bytes_written < target_bytes; i++) { + char c = input[i]; + if (c >= '1' && c <= '6') { + // Convert dice roll (1-6) to 3 bits of entropy + unsigned char roll_value = c - '1'; // 0-5 + + // Pack 3 bits into current byte + for (int bit = 2; bit >= 0 && entropy_bits < target_bits; bit--) { + current_byte = (current_byte << 1) | ((roll_value >> bit) & 1); + bits_in_byte++; + entropy_bits++; + + if (bits_in_byte == 8) { + entropy_buffer[bytes_written++] = current_byte; + current_byte = 0; + bits_in_byte = 0; + } + } + } + } + } + + // Handle partial byte + if (bits_in_byte > 0 && bytes_written < target_bytes) { + // Pad remaining bits with zeros + current_byte <<= (8 - bits_in_byte); + entropy_buffer[bytes_written++] = current_byte; + } + + if (display_progress) { + printf("\n✓ Dice entropy collection complete!\n"); + printf(" Collected: %zu bytes from dice rolls\n", bytes_written); + printf(" Entropy bits: %zu\n", entropy_bits); + } + + *collected_bytes = bytes_written; + return 0; // Success +} + +// Collect entropy by source type with unified interface +int collect_entropy_by_source(entropy_source_t source, unsigned char* entropy_buffer, + size_t target_bytes, size_t* collected_bytes, int display_progress) { + switch (source) { + case ENTROPY_SOURCE_KEYBOARD: + return collect_entropy_with_feedback(entropy_buffer, target_bytes, collected_bytes, 1); + + case ENTROPY_SOURCE_TRUERNG: + return collect_truerng_entropy(entropy_buffer, target_bytes, collected_bytes, display_progress); + + case ENTROPY_SOURCE_DICE: + return collect_dice_entropy(entropy_buffer, target_bytes, collected_bytes, display_progress); + + default: + if (display_progress) { + printf("Error: Unknown entropy source\n"); + } + return 1; + } +} + double get_precise_time(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); @@ -4068,6 +6252,7 @@ int handle_pads_menu(void) { printf(" \033[4mG\033[0menerate new pad\n"); printf(" \033[4mA\033[0mdd entropy to pad\n"); printf(" \033[4mS\033[0met default pad\n"); + printf(" \033[4mU\033[0mSB drive management\n"); printf(" E\033[4mx\033[0mit\n"); printf("\nSelect action: "); @@ -4158,10 +6343,17 @@ int handle_pads_menu(void) { free(selected_pad); return handle_pads_menu(); + } else if (toupper(input[0]) == 'U') { + // USB drive management + int result = handle_usb_submenu(); + if (result == 0) { + return handle_pads_menu(); // Return to pads menu + } + return result; } else if (toupper(input[0]) == 'X') { return 0; // Exit to main menu } else { - printf("Invalid action. Please select G, A, S, or X.\n"); + printf("Invalid action. Please select G, A, S, U, or X.\n"); return handle_pads_menu(); } } @@ -4298,26 +6490,59 @@ void get_directory_display(const char* file_path, char* result, size_t result_si int handle_add_entropy_to_pad(const char* pad_chksum) { printf("\n=== Add Entropy to Pad: %.16s... ===\n", pad_chksum); - printf("This will enhance the randomness of your pad using keyboard entropy.\n"); - printf("The entropy will be processed with Chacha20 and distributed throughout the entire pad.\n\n"); - printf("Entropy collection options:\n"); + // Present entropy source selection menu with consistent formatting + printf("Select entropy source:\n"); + printf(" \033[4mK\033[0meyboard entropy - Random typing for entropy collection\n"); + printf(" \033[4mD\033[0mice rolls - Manual dice roll input for high-quality entropy\n"); + printf(" \033[4mT\033[0mrueRNG devices - Hardware random number generators\n"); + printf(" E\033[4mx\033[0mit\n"); + printf("\nSelect option: "); + + char source_input[10]; + if (!fgets(source_input, sizeof(source_input), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + + char choice = toupper(source_input[0]); + entropy_source_t entropy_source; + + switch (choice) { + case 'K': + entropy_source = ENTROPY_SOURCE_KEYBOARD; + break; + case 'D': + entropy_source = ENTROPY_SOURCE_DICE; + break; + case 'T': + entropy_source = ENTROPY_SOURCE_TRUERNG; + break; + case 'X': + return 0; // Exit + default: + printf("Invalid choice. Please select K, D, T, or X.\n"); + return 1; + } + + // Get entropy amount + printf("\nEntropy collection options:\n"); printf(" 1. Recommended (2048 bytes) - Optimal security\n"); printf(" 2. Minimum (1024 bytes) - Good security\n"); printf(" 3. Maximum (4096 bytes) - Maximum security\n"); printf(" 4. Custom amount\n"); printf("Enter choice (1-4): "); - char choice_input[10]; - if (!fgets(choice_input, sizeof(choice_input), stdin)) { + char amount_input[10]; + if (!fgets(amount_input, sizeof(amount_input), stdin)) { printf("Error: Failed to read input\n"); return 1; } size_t target_bytes = 2048; // Default - int choice = atoi(choice_input); + int amount_choice = atoi(amount_input); - switch (choice) { + switch (amount_choice) { case 1: target_bytes = 2048; break; @@ -4347,7 +6572,7 @@ int handle_add_entropy_to_pad(const char* pad_chksum) { break; } - printf("\nCollecting %zu bytes of entropy...\n", target_bytes); + printf("\nCollecting %zu bytes of entropy from selected source...\n", target_bytes); // Allocate entropy buffer unsigned char* entropy_buffer = malloc(MAX_ENTROPY_BUFFER); @@ -4356,9 +6581,9 @@ int handle_add_entropy_to_pad(const char* pad_chksum) { return 1; } - // Collect entropy with visual feedback + // Collect entropy using unified interface size_t collected_bytes = 0; - int result = collect_entropy_with_feedback(entropy_buffer, target_bytes, &collected_bytes, 1); + int result = collect_entropy_by_source(entropy_source, entropy_buffer, target_bytes, &collected_bytes, 1); if (result != 0) { printf("Error: Entropy collection failed\n"); @@ -4393,6 +6618,598 @@ int handle_add_entropy_to_pad(const char* pad_chksum) { return 0; } +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// USB MENU HANDLERS +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +int handle_usb_submenu(void) { + printf("\n=== USB Drive Management ===\n"); + + // Auto-display USB drives above the menu (like Pad Management does) + list_usb_drives_v2(); + + printf("\nActions:\n"); + printf(" \033[4mI\033[0mnitialize USB drive\n"); + printf(" \033[4mC\033[0mopy pad to USB\n"); + printf(" \033[4mM\033[0mport pad from USB\n"); + printf(" \033[4mD\033[0muplicate USB drive\n"); + printf(" \033[4mV\033[0merify drive integrity\n"); + printf(" E\033[4mx\033[0mit\n"); + printf("\nSelect action: "); + + char input[10]; + if (!fgets(input, sizeof(input), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + + char choice = toupper(input[0]); + + switch (choice) { + case 'I': + return handle_initialize_usb(); + case 'C': + return handle_copy_pad_to_usb(); + case 'M': + return handle_import_pad_from_usb(); + case 'D': + return handle_duplicate_usb_drive(); + case 'V': + return handle_verify_usb_drive(); + case 'X': + case 'Q': + return 0; // Exit to pads menu + default: + printf("Invalid choice. Please try again.\n"); + return handle_usb_submenu(); + } +} + +int handle_initialize_usb(void) { + printf("\n=== Initialize USB Drive for OTP ===\n"); + printf("This will prepare a USB drive for storing OTP pads.\n\n"); + + // Select USB drive + usb_drive_info_t* selected_drive = select_usb_drive_interactive( + "Available USB Drives", "Select drive to initialize", 0); + + if (!selected_drive) { + printf("USB initialization cancelled.\n"); + return handle_usb_submenu(); + } + + printf("\nSelected drive: %s (%s)\n", selected_drive->volume_label, selected_drive->mount_path); + + // Get drive name + char drive_name[64]; + printf("Enter OTP drive name (must start with 'OTP_'): "); + if (!fgets(drive_name, sizeof(drive_name), stdin)) { + printf("Error: Failed to read input\n"); + free(selected_drive); + return 1; + } + drive_name[strcspn(drive_name, "\n")] = 0; + + // Validate drive name + if (strncmp(drive_name, "OTP_", 4) != 0) { + printf("Error: Drive name must start with 'OTP_'\n"); + free(selected_drive); + return handle_usb_submenu(); + } + + // Ask about formatting + printf("\nFormat options:\n"); + printf(" 1. Initialize without formatting (recommended if drive is already formatted)\n"); + printf(" 2. Format with FAT32 filesystem\n"); + printf(" 3. Format with ext4 filesystem\n"); + printf("Enter choice (1-3): "); + + char format_input[10]; + if (!fgets(format_input, sizeof(format_input), stdin)) { + printf("Error: Failed to read input\n"); + free(selected_drive); + return 1; + } + + int format_choice = atoi(format_input); + int format_drive = (format_choice > 1); + const char* filesystem = ""; + + if (format_choice == 2) { + filesystem = "fat32"; + } else if (format_choice == 3) { + filesystem = "ext4"; + } + + // Confirm operation + printf("\nConfirmation:\n"); + printf("Drive: %s\n", selected_drive->mount_path); + printf("Name: %s\n", drive_name); + if (format_drive) { + printf("Format: YES (%s) - THIS WILL ERASE ALL DATA\n", filesystem); + } else { + printf("Format: NO (label only)\n"); + } + printf("\nProceed? (y/N): "); + + char confirm[10]; + if (!fgets(confirm, sizeof(confirm), stdin) || + (confirm[0] != 'y' && confirm[0] != 'Y')) { + printf("Operation cancelled.\n"); + free(selected_drive); + return handle_usb_submenu(); + } + + // Initialize the drive + usb_operation_result_t result = initialize_usb_drive( + selected_drive->mount_path, drive_name, format_drive, filesystem); + + if (result.success) { + printf("\n✓ USB drive initialized successfully!\n"); + printf("Drive name: %s\n", drive_name); + printf("You can now copy pads to this drive.\n"); + } else { + printf("\n✗ USB drive initialization failed: %s\n", result.error_message); + } + + free(selected_drive); + printf("\nPress Enter to continue..."); + getchar(); + return handle_usb_submenu(); +} + +int handle_copy_pad_to_usb(void) { + printf("\n=== Copy Pad to USB Drive ===\n"); + + // Select pad to copy + char* selected_pad = select_pad_interactive("=== Select Pad to Copy ===", + "Select pad to copy to USB (by prefix)", + PAD_FILTER_ALL, 1); + if (!selected_pad) { + printf("Pad selection cancelled.\n"); + return handle_usb_submenu(); + } + + // Select USB drive (OTP drives only) + usb_drive_info_t* selected_drive = select_usb_drive_interactive( + "Available OTP USB Drives", "Select destination USB drive", 1); + + if (!selected_drive) { + printf("USB drive selection cancelled.\n"); + free(selected_pad); + return handle_usb_submenu(); + } + + printf("\nCopying pad %.16s... to %s\n", selected_pad, selected_drive->volume_label); + + // Copy the pad + int result = copy_pad_to_usb(selected_pad, selected_drive->mount_path); + + if (result == 0) { + printf("✓ Pad successfully copied to USB drive!\n"); + } else { + printf("✗ Failed to copy pad to USB drive (error code: %d)\n", result); + } + + free(selected_pad); + free(selected_drive); + printf("\nPress Enter to continue..."); + getchar(); + return handle_usb_submenu(); +} + +int handle_import_pad_from_usb(void) { + printf("\n=== Import Pad from USB Drive ===\n"); + + // Select USB drive (OTP drives only) + usb_drive_info_t* selected_drive = select_usb_drive_interactive( + "Available OTP USB Drives", "Select source USB drive", 1); + + if (!selected_drive) { + printf("USB drive selection cancelled.\n"); + return handle_usb_submenu(); + } + + // List pads on the drive + char pad_list[100][65]; + int pad_count = list_pads_on_drive(selected_drive->mount_path, pad_list, 100); + + if (pad_count <= 0) { + printf("No pads found on USB drive %s\n", selected_drive->volume_label); + free(selected_drive); + printf("Press Enter to continue..."); + getchar(); + return handle_usb_submenu(); + } + + printf("\nPads available on %s:\n", selected_drive->volume_label); + printf("%-3s %-16s\n", "#", "Checksum"); + printf("%-3s %-16s\n", "---", "----------------"); + + for (int i = 0; i < pad_count; i++) { + printf("%-3d %.16s...\n", i + 1, pad_list[i]); + } + + printf("\nSelect pad number to import (1-%d) or 0 to cancel: ", pad_count); + + char input[10]; + if (!fgets(input, sizeof(input), stdin)) { + printf("Error: Failed to read input\n"); + free(selected_drive); + return 1; + } + + int selection = atoi(input); + if (selection < 1 || selection > pad_count) { + printf("Import cancelled.\n"); + free(selected_drive); + return handle_usb_submenu(); + } + + char* selected_pad = pad_list[selection - 1]; + printf("\nImporting pad %.16s... from %s\n", selected_pad, selected_drive->volume_label); + + // Import the pad + int result = copy_pad_from_usb(selected_drive->mount_path, selected_pad); + + if (result == 0) { + printf("✓ Pad successfully imported from USB drive!\n"); + } else { + printf("✗ Failed to import pad from USB drive (error code: %d)\n", result); + } + + free(selected_drive); + printf("\nPress Enter to continue..."); + getchar(); + return handle_usb_submenu(); +} + +int handle_duplicate_usb_drive(void) { + printf("\n=== Duplicate USB Drive ===\n"); + printf("Copy selected pads from one OTP USB drive to another.\n\n"); + + // Select source drive + usb_drive_info_t* source_drive = select_usb_drive_interactive( + "Source OTP USB Drives", "Select source drive to copy from", 1); + + if (!source_drive) { + printf("Source drive selection cancelled.\n"); + return handle_usb_submenu(); + } + + // Select destination drive + usb_drive_info_t* dest_drive = select_usb_drive_interactive( + "Destination OTP USB Drives", "Select destination drive to copy to", 1); + + if (!dest_drive) { + printf("Destination drive selection cancelled.\n"); + free(source_drive); + return handle_usb_submenu(); + } + + // Prevent copying to same drive + if (strcmp(source_drive->mount_path, dest_drive->mount_path) == 0) { + printf("Error: Source and destination drives cannot be the same!\n"); + free(source_drive); + free(dest_drive); + printf("Press Enter to continue..."); + getchar(); + return handle_usb_submenu(); + } + + // List pads on source drive + char pad_list[100][65]; + int pad_count = list_pads_on_drive(source_drive->mount_path, pad_list, 100); + + if (pad_count <= 0) { + printf("No pads found on source drive %s\n", source_drive->volume_label); + free(source_drive); + free(dest_drive); + printf("Press Enter to continue..."); + getchar(); + return handle_usb_submenu(); + } + + printf("\nSource: %s (%d pads)\n", source_drive->volume_label, pad_count); + printf("Destination: %s\n\n", dest_drive->volume_label); + + // For simplicity, copy all pads (could be enhanced to allow selection) + printf("This will copy all %d pads from source to destination.\n", pad_count); + printf("Proceed? (y/N): "); + + char confirm[10]; + if (!fgets(confirm, sizeof(confirm), stdin) || + (confirm[0] != 'y' && confirm[0] != 'Y')) { + printf("Duplication cancelled.\n"); + free(source_drive); + free(dest_drive); + return handle_usb_submenu(); + } + + // Perform selective duplication with all pads + int result = duplicate_drive_selective(source_drive->mount_path, + dest_drive->mount_path, pad_list, pad_count); + + if (result == 0) { + printf("\n✓ USB drive duplication completed successfully!\n"); + } else { + printf("\n⚠ USB drive duplication completed with %d errors.\n", result); + } + + free(source_drive); + free(dest_drive); + printf("\nPress Enter to continue..."); + getchar(); + return handle_usb_submenu(); +} + +int handle_verify_usb_drive(void) { + printf("\n=== Verify USB Drive Integrity ===\n"); + + // Select USB drive to verify + usb_drive_info_t* selected_drive = select_usb_drive_interactive( + "Available OTP USB Drives", "Select drive to verify", 1); + + if (!selected_drive) { + printf("Drive selection cancelled.\n"); + return handle_usb_submenu(); + } + + printf("\nVerifying integrity of drive: %s\n", selected_drive->volume_label); + printf("Mount path: %s\n\n", selected_drive->mount_path); + + // List pads on the drive and verify each one + char pad_list[100][65]; + int pad_count = list_pads_on_drive(selected_drive->mount_path, pad_list, 100); + + if (pad_count <= 0) { + printf("No pads found on USB drive.\n"); + free(selected_drive); + printf("Press Enter to continue..."); + getchar(); + return handle_usb_submenu(); + } + + printf("Found %d pads to verify:\n\n", pad_count); + + int verified_count = 0; + int error_count = 0; + + for (int i = 0; i < pad_count; i++) { + printf("[%d/%d] Verifying pad: %.16s...\n", i + 1, pad_count, pad_list[i]); + + // Build pad path + char pad_path[1024]; + snprintf(pad_path, sizeof(pad_path), "%s/%s.pad", + selected_drive->mount_path, pad_list[i]); + + // Calculate and verify checksum + char calculated_checksum[65]; + if (calculate_checksum(pad_path, calculated_checksum) == 0) { + if (strcmp(calculated_checksum, pad_list[i]) == 0) { + printf(" ✓ Integrity verified\n"); + verified_count++; + } else { + printf(" ✗ Checksum mismatch!\n"); + printf(" Expected: %.16s...\n", pad_list[i]); + printf(" Calculated: %.16s...\n", calculated_checksum); + error_count++; + } + } else { + printf(" ✗ Cannot calculate checksum\n"); + error_count++; + } + } + + printf("\nVerification completed:\n"); + printf("Successfully verified: %d pads\n", verified_count); + printf("Errors found: %d pads\n", error_count); + + if (error_count == 0) { + printf("✓ All pads on USB drive are intact!\n"); + } else { + printf("⚠ Some pads have integrity issues. Consider re-copying them.\n"); + } + + free(selected_drive); + printf("\nPress Enter to continue..."); + getchar(); + return handle_usb_submenu(); +} + +int handle_list_usb_drives(void) { + printf("\n=== List USB Drives ===\n"); + list_usb_drives_v2(); + printf("\nPress Enter to continue..."); + getchar(); + return handle_usb_submenu(); +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// USB COMMAND LINE INTERFACE HANDLERS +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +int handle_list_usb_drives_cli(void) { + list_usb_drives_v2(); + return 0; +} +int handle_usb_init_cli(int argc, char* argv[]) { + const char* drive_name = argv[2]; + const char* drive_path = (argc > 3) ? argv[3] : NULL; + + // Validate drive name + if (strncmp(drive_name, "OTP_", 4) != 0) { + printf("Error: Drive name must start with 'OTP_'\n"); + return 1; + } + + if (strlen(drive_name) < 5 || strlen(drive_name) > 32) { + printf("Error: Drive name must be 5-32 characters\n"); + return 1; + } + + const char* mount_path; + usb_drive_info_t* selected_drive = NULL; + + if (drive_path) { + // Direct path specified + mount_path = drive_path; + + // Verify it's a valid mount point + if (access(drive_path, W_OK) != 0) { + printf("Error: Cannot access or write to specified path: %s\n", drive_path); + return 1; + } + } else { + // Interactive drive selection + printf("No drive path specified. Available USB drives:\n"); + selected_drive = select_usb_drive_interactive( + "Available USB Drives", "Select drive to initialize", 0); + + if (!selected_drive) { + printf("No drive selected.\n"); + return 1; + } + mount_path = selected_drive->mount_path; + } + + printf("Initializing USB drive...\n"); + printf("Drive path: %s\n", mount_path); + printf("Drive name: %s\n", drive_name); + + // Initialize without formatting by default in CLI mode + usb_operation_result_t result = initialize_usb_drive(mount_path, drive_name, 0, ""); + + if (result.success) { + printf("✓ USB drive initialized successfully!\n"); + } else { + printf("✗ USB drive initialization failed: %s\n", result.error_message); + if (selected_drive) free(selected_drive); + return 1; + } + + if (selected_drive) free(selected_drive); + return 0; +} + +int handle_usb_copy_cli(const char* pad_prefix, const char* usb_path) { + // Find pad by prefix + char* pad_checksum = find_pad_by_prefix(pad_prefix); + if (!pad_checksum) { + printf("Error: No pad found matching prefix '%s'\n", pad_prefix); + return 1; + } + + // Verify USB path is accessible + if (access(usb_path, W_OK) != 0) { + printf("Error: Cannot access USB drive path: %s\n", usb_path); + free(pad_checksum); + return 1; + } + + printf("Copying pad %.16s... to %s\n", pad_checksum, usb_path); + + int result = copy_pad_to_usb(pad_checksum, usb_path); + + if (result == 0) { + printf("✓ Pad successfully copied to USB drive!\n"); + } else { + printf("✗ Failed to copy pad to USB drive (error code: %d)\n", result); + free(pad_checksum); + return 1; + } + + free(pad_checksum); + return 0; +} + +int handle_usb_import_cli(const char* pad_checksum, const char* usb_path) { + // Verify USB path is accessible + if (access(usb_path, R_OK) != 0) { + printf("Error: Cannot access USB drive path: %s\n", usb_path); + return 1; + } + + // Verify pad exists on USB + char pad_path[1024]; + snprintf(pad_path, sizeof(pad_path), "%s/%s.pad", usb_path, pad_checksum); + + if (access(pad_path, R_OK) != 0) { + printf("Error: Pad not found on USB drive: %s\n", pad_checksum); + return 1; + } + + printf("Importing pad %.16s... from %s\n", pad_checksum, usb_path); + + int result = copy_pad_from_usb(usb_path, pad_checksum); + + if (result == 0) { + printf("✓ Pad successfully imported from USB drive!\n"); + } else { + printf("✗ Failed to import pad from USB drive (error code: %d)\n", result); + return 1; + } + + return 0; +} + +int handle_usb_verify_cli(const char* usb_path) { + // Verify USB path is accessible + if (access(usb_path, R_OK) != 0) { + printf("Error: Cannot access USB drive path: %s\n", usb_path); + return 1; + } + + printf("Verifying USB drive integrity: %s\n", usb_path); + + // List pads on the drive + char pad_list[100][65]; + int pad_count = list_pads_on_drive(usb_path, pad_list, 100); + + if (pad_count <= 0) { + printf("No pads found on USB drive.\n"); + return 1; + } + + printf("Found %d pads to verify:\n", pad_count); + + int verified_count = 0; + int error_count = 0; + + for (int i = 0; i < pad_count; i++) { + printf("[%d/%d] Verifying pad: %.16s...", i + 1, pad_count, pad_list[i]); + + // Build pad path + char pad_path[1024]; + snprintf(pad_path, sizeof(pad_path), "%s/%s.pad", usb_path, pad_list[i]); + + // Calculate and verify checksum + char calculated_checksum[65]; + if (calculate_checksum(pad_path, calculated_checksum) == 0) { + if (strcmp(calculated_checksum, pad_list[i]) == 0) { + printf(" ✓\n"); + verified_count++; + } else { + printf(" ✗ (checksum mismatch)\n"); + error_count++; + } + } else { + printf(" ✗ (cannot calculate checksum)\n"); + error_count++; + } + } + + printf("\nVerification results:\n"); + printf("Successfully verified: %d pads\n", verified_count); + printf("Errors found: %d pads\n", error_count); + + return (error_count > 0) ? 1 : 0; +} + void print_usage(const char* program_name) { printf("OTP Cipher - One Time Pad Implementation v0.3.7\n"); printf("Built for testing entropy system\n"); @@ -4418,6 +7235,19 @@ void print_usage(const char* program_name) { printf(" %s -d encrypted.otp.asc - Decrypt ASCII file\n", program_name); printf(" %s -g 1GB - Generate 1GB pad\n", program_name); printf(" %s -l - List pads\n", program_name); + printf("\nUSB Operations:\n"); + printf(" usb-list - List all USB drives\n"); + printf(" usb-init [path] - Initialize USB drive for OTP\n"); + printf(" usb-copy - Copy pad to USB drive\n"); + printf(" usb-import - Import pad from USB drive\n"); + printf(" usb-verify - Verify USB drive integrity\n"); + printf("\nUSB Examples:\n"); + printf(" %s usb-list - Show all USB drives\n", program_name); + printf(" %s usb-init OTP_BACKUP - Initialize drive (interactive)\n", program_name); + printf(" %s usb-init OTP_BACKUP /media/user/USB - Initialize specific drive\n", program_name); + printf(" %s usb-copy 1a2b3c /media/user/OTP_BACKUP - Copy pad to USB\n", program_name); + printf(" %s usb-import 1a2b3c4d /media/user/OTP_BACKUP - Import pad from USB\n", program_name); + printf(" %s usb-verify /media/user/OTP_BACKUP - Verify USB drive\n", program_name); printf("\nSize examples: 1GB, 5TB, 512MB, 2048 (bytes)\n"); printf("Pad selection: Full chksum or prefix\n"); } diff --git a/otp.h b/otp.h index ffd1ecc..855093d 100644 --- a/otp.h +++ b/otp.h @@ -14,7 +14,13 @@ #include #include #include +#include #include +#include +#include +#include +#include +#include // Constants #define MAX_INPUT_SIZE 4096 @@ -25,10 +31,74 @@ #define FILES_DIR "files" #define MAX_ENTROPY_BUFFER 32768 // 32KB entropy buffer +// USB Drive constants +#define MAX_USB_DRIVES 16 // Maximum USB drives to enumerate +#define USB_LABEL_PREFIX "OTP_" // Prefix for OTP USB drive labels +#define MAX_USB_LABEL_LENGTH 32 // Maximum length for USB volume labels + //////////////////////////////////////////////////////////////////////////////// // TYPE DEFINITIONS //////////////////////////////////////////////////////////////////////////////// +// USB Drive Information Structure +typedef struct { + char device_path[512]; // e.g., "/dev/sdb1" + char device_name[64]; // e.g., "sdb1" + char mount_path[512]; // e.g., "/media/user/OTP_ALICE" + char volume_label[64]; // e.g., "OTP_ALICE" + char filesystem[16]; // e.g., "ext4", "vfat" + char bus_type[16]; // "usb", "sata", "nvme", etc. + uint64_t total_size; // Total drive capacity in bytes + uint64_t available_size; // Available space in bytes + int is_mounted; // 1 if currently mounted, 0 otherwise + int is_otp_drive; // 1 if recognized as OTP drive + int is_removable; // 1 if removable media, 0 if fixed + int is_usb_device; // 1 if connected via USB bus + int is_system_mount; // 1 if critical system mount + int is_safe; // 1 if passed safety validation, 0 if unsafe + int pad_count; // Number of pads on drive +} usb_drive_info_t; + +// USB Operation Result Structure +typedef struct { + int success; // 1 for success, 0 for failure + char error_message[256]; // Error description if failed + int drives_found; // Number of drives discovered + int pads_transferred; // Number of pads copied/verified + uint64_t bytes_processed; // Total data processed +} usb_operation_result_t; + +// New simplified USB device structures for lsusb cross-referencing +typedef struct { + char vendor_id[8]; // "1234" from lsusb + char product_id[8]; // "5678" from lsusb + char vendor_name[64]; // "SanDisk Corp." from lsusb + char product_name[128]; // "Ultra Fit" from lsusb + char bus_number[4]; // "001" from lsusb + char device_number[4]; // "003" from lsusb +} usb_device_registry_t; + +// Simplified USB status for new detection system +typedef enum { + USB_SAFE, // ✓ Safe USB drive detected by lsusb + USB_TOO_LARGE, // ⚠ Drive over 6TB safety limit + USB_NOT_USB // ✗ Not found in USB device registry +} usb_safety_status_t; + +// New simplified drive info structure +typedef struct { + char mount_path[512]; // "/media/user/DUAL DRIVE" + char device_path[64]; // "/dev/sda1" + char device_name[16]; // "sda" + char label[64]; // "DUAL DRIVE" + char filesystem[16]; // "vfat" + uint64_t total_size; // Total bytes + uint64_t free_size; // Free bytes + int pad_count; // Number of .pad files + usb_safety_status_t status; // USB safety status + usb_device_registry_t *usb_device; // Pointer to USB device info (NULL if not USB) +} drive_info_v2_t; + // Decrypt operation modes for universal decrypt function typedef enum { DECRYPT_MODE_INTERACTIVE, // Interactive text decryption with prompts @@ -92,6 +162,67 @@ int set_default_pad_path(const char* pad_path); // OTP thumb drive detection function int detect_otp_thumb_drive(char* otp_drive_path, size_t path_size); +//////////////////////////////////////////////////////////////////////////////// +// USB DRIVE MANAGEMENT FUNCTIONS +//////////////////////////////////////////////////////////////////////////////// + +// USB Drive Discovery Functions +int discover_usb_drives(usb_drive_info_t** drives, int* drive_count); +int get_usb_drive_info(const char* mount_path, const char* drive_label, usb_drive_info_t* drive_info); +int get_device_from_mount(const char* mount_path, char* device_path, size_t device_path_size); +void format_size_string(uint64_t bytes, char* result, size_t result_size); + +// USB Device Safety Functions +int is_device_removable_usb(const char* device_path); +int get_device_bus_type(const char* device_path, char* bus_type, size_t bus_type_size); +int is_system_critical_mount(const char* mount_path); +int validate_usb_device_safety(const usb_drive_info_t* drive); +int extract_device_name_from_path(const char* device_path, char* device_name, size_t device_name_size); + +// Drive Initialization Functions +usb_operation_result_t initialize_usb_drive(const char* mount_path, const char* drive_name, + int format_drive, const char* filesystem_type); +int set_volume_label(const char* mount_path, const char* new_label); + +// Pad Transfer Functions +int copy_pad_to_usb(const char* pad_checksum, const char* usb_mount_path); +int copy_pad_from_usb(const char* usb_mount_path, const char* pad_checksum); +int copy_file(const char* source_path, const char* dest_path); +int list_pads_on_drive(const char* mount_path, char pad_list[][65], int max_pads); +int verify_pad_integrity_cross_drive(const char* local_pad, const char* usb_pad); + +// Drive Duplication Functions +int duplicate_drive_selective(const char* source_mount, const char* dest_mount, + char selected_pads[][65], int pad_count); +int verify_drive_duplication(const char* source_mount, const char* dest_mount); + +// USB Menu Integration Functions +int handle_usb_submenu(void); +int handle_initialize_usb(void); +int handle_copy_pad_to_usb(void); +int handle_import_pad_from_usb(void); +int handle_duplicate_usb_drive(void); +int handle_verify_usb_drive(void); +int handle_list_usb_drives(void); +usb_drive_info_t* select_usb_drive_interactive(const char* title, const char* prompt, int require_otp); + +// USB CLI handler functions +int handle_list_usb_drives_cli(void); +int handle_usb_init_cli(int argc, char* argv[]); +int handle_usb_copy_cli(const char* pad_prefix, const char* usb_path); +int handle_usb_import_cli(const char* pad_checksum, const char* usb_path); +int handle_usb_verify_cli(const char* usb_path); + +// New simplified USB detection system functions +int build_usb_device_registry(usb_device_registry_t **devices, int *device_count); +void free_usb_device_registry(usb_device_registry_t *devices); +usb_device_registry_t* find_usb_device_by_path(const char* device_path, + usb_device_registry_t* usb_devices, int usb_count); +int detect_drive_info_v2(const char* mount_path, drive_info_v2_t* drive_info, + usb_device_registry_t* usb_devices, int usb_count); +void list_usb_drives_v2(void); +const char* get_usb_status_string(usb_safety_status_t status); + //////////////////////////////////////////////////////////////////////////////// // EXTERNAL TOOL INTEGRATION FUNCTIONS //////////////////////////////////////////////////////////////////////////////// @@ -119,6 +250,13 @@ int decrypt_ascii_file(const char* input_file, const char* output_file); // ENHANCED ENTROPY SYSTEM FUNCTIONS //////////////////////////////////////////////////////////////////////////////// +// Entropy source types +typedef enum { + ENTROPY_SOURCE_KEYBOARD = 1, + ENTROPY_SOURCE_DICE = 2, + ENTROPY_SOURCE_TRUERNG = 3 +} entropy_source_t; + // Terminal control for entropy collection int setup_raw_terminal(struct termios* original_termios); void restore_terminal(struct termios* original_termios); @@ -130,6 +268,34 @@ void display_entropy_progress(const entropy_collection_state_t* state); void draw_progress_bar(double percentage, int width); void draw_quality_bar(double quality, int width, const char* label); +// TrueRNG Device Constants (updated to match otp.c implementation) +#define TRUERNG_VID "04D8" +#define TRUERNG_PID "F5FE" +#define TRUERNGPRO_VID "16D0" +#define TRUERNGPRO_PID "0AA0" +#define TRUERNGPROV2_VID "04D8" +#define TRUERNGPROV2_PID "EBB5" + +// TrueRNG Device Type enumeration +typedef enum { + TRUERNG_ORIGINAL = 1, + TRUERNG_PRO = 2, + TRUERNG_PRO_V2 = 3 +} truerng_device_type_t; + +// TrueRNG entropy collection functions (updated to match implementation) +int find_truerng_port(char* port_path, size_t port_path_size, truerng_device_type_t* device_type); +int setup_truerng_serial_port(const char* port_path); +int collect_truerng_entropy(unsigned char* entropy_buffer, size_t target_bytes, size_t* collected_bytes, int display_progress); +const char* get_truerng_device_name(truerng_device_type_t device_type); +int read_usb_device_info(const char* port_name, char* vid, char* pid); + +// Dice entropy collection functions (updated to match implementation) +int collect_dice_entropy(unsigned char* entropy_buffer, size_t target_bytes, size_t* collected_bytes, int display_progress); + +// Unified entropy collection interface (updated to match implementation) +int collect_entropy_by_source(entropy_source_t source, unsigned char* entropy_buffer, size_t target_bytes, size_t* collected_bytes, int display_progress); + // Entropy quality calculation double calculate_timing_quality(const entropy_collection_state_t* state); double calculate_variety_quality(const entropy_collection_state_t* state); diff --git a/true_rng b/true_rng new file mode 160000 index 0000000..52ed7af --- /dev/null +++ b/true_rng @@ -0,0 +1 @@ +Subproject commit 52ed7af980856f88ccf8efe89dba82b013291122