pax_global_header00006660000000000000000000000064136232140540014512gustar00rootroot0000000000000052 comment=2b060a933540c077fe328e9d83009a8c57ad06de ippusbxd-1.34/000077500000000000000000000000001362321405400132775ustar00rootroot00000000000000ippusbxd-1.34/.gitignore000066400000000000000000000000061362321405400152630ustar00rootroot00000000000000exe/* ippusbxd-1.34/.travis.yml000066400000000000000000000014241362321405400154110ustar00rootroot00000000000000language: c script: make compiler: - clang - gcc before_install: - sudo apt-get update -qq - sudo apt-get install -y libusb-1.0-0-dev env: global: - secure: CtAND8k/8Lfs6o4dmrD9Jp4yLjLQRrv5TZA1MkhSO/LRfViKlbzy7ENv1/ku0xgtnsegFRDFY75/dLHxZUiax5TGNVrHqpkkGSE9h2a5YYHxbOtINb8Hdzk+S2/EN9YPmrOEX1ZjMg/wH08c3MntdW6gDUuddQhjCuaNnH+8fUc= - secure: foN6qnWGy6p47PEutfZtUYUcg3DVHtuY3hfODDLDjc3TF7w7W9p/Mf/w4u++rkLuoKu/hESoiyj7/bSePx98EJgXsDprS/JkT8KKCaSsyViaIizrnF4YTQac/FC+A+7BUdg6xil0FI1xMipHQ/Z+nSwMT2ueeRThApNgufz66qw= addons: coverity_scan: project: name: daniel-dressler/ippusbxd description: IPP over USB daemon driver for linux notification_email: danieru.dressler@gmail.com build_command_prepend: build_command: make branch_pattern: coverity_check ippusbxd-1.34/doc/000077500000000000000000000000001362321405400140445ustar00rootroot00000000000000ippusbxd-1.34/doc/behaviour000066400000000000000000000001401362321405400157460ustar00rootroot00000000000000- Must use CRLF for transfer: chunked messages - Rotates USB interfaces between TCP connections ippusbxd-1.34/doc/ippusbxd.8000066400000000000000000000073511362321405400160010ustar00rootroot00000000000000.TH IPPUSBXD 8 .SH NAME ippusbxd \- Communication driver for IPP-over-USB class printers .SH SYNOPSIS .B ippusbxd [\fB\-v\fR|\fB--vid \fR \fIVENDOR_ID\fR] [\fB\-m\fR|\fB--pid \fR \fIPRODUCT_ID\fR] [\fB\-s\fR|\fB--serial \fR \fISERIAL_NUMBER\fR] [\fB\--bus \fR \fIBUS\fR] [\fB\--device \fR \fIDEVICE\fR] [\fB\--bus-device \fR \fIBUS\fR\fB:\fR\fIDEVICE\fR] [\fB\-p\fR|\fB--only-port \fR \fIPORT_NUMBER\fR] [\fB\-P\fR|\fB--from-port \fR \fIPORT_NUMBER\fR] [\fB\-i\fR|\fB--interface \fR \fIINTERFACE\fR] [\fB\-l\fR|\fB--logging\fR] [\fB\-q\fR|\fB--verbose\fR] [\fB\-d\fR|\fB--debug\fR] [\fB\-n\fR|\fB--no-fork\fR] [\fB\-B\fR|\fB--no-broadcast\fR] [\fB\-N\fR|\fB--no-printer\fR] .SH DESCRIPTION .B ippusbxd connects to a IPP-over-USB printer and exposes it to a network interface (like localhost or dummy0) on a given port, so that the printer can be accessed like an IPP network printer. The printer is also registered at Avahi to be advertised via DNS-SD on the interface, so \fBCUPS\fP and \fBcups-browsed(8)\fP will auto-discover the printer for easy setup of a print queue. This requires avahi-daemon to be running and the network interface to be supported by the Avahi version in use. Upon successful startup the TCP port it is listening on and the process ID of the daemon are printed to stdout. \fBippusbxd\fR will shut itself down when the connected printer disconnects. When not specifying information about the desired printer, \fBippusbxd\fR scans the USB and connects to the first available IPP-over-USB printer. .SH OPTIONS .TP .B \fB-h\fP, \fB--help\fP Show help message. .TP .B \fB-v\fP \fIVENDOR_ID\fR, \fB--vid\fP \fIVENDOR_ID\fR USB vendor ID of desired printer. .TP .B \fB-m\fP \fIPRODUCT_ID\fR, \fB--pid\fP \fIPRODUCT_ID\fR USB product ID of desired printer. .TP .B \fB-s\fP \fISERIAL_NUMBER\fR, \fB--serial\fP \fISERIAL_NUMBER\fR Serial number of desired printer. .TP .B \fB--bus\fP \fIBUS\fR \fB--device\fP \fIDEVICE\fR, \fB--bus-device\fP \fIBUS\fR\fB:\fP\fIDEVICE\fR USB bus and device numbers where the device is currently connected (see output of \fBlsusb(8)\fP). Note that these numbers change when the device is disconnected and reconnected. This method of calling \fBippusbxd\fP is only for calling via UDEV. \fIBUS\fR and \fIDEVICE\fR have to be given in decimal numbers. .TP .B \fB-p\fP \fIPORT_NUMBER\fR, \fB--only-port\fP \fIPORT_NUMBER\fR Port number \fBippusbxd\fP will expose the printer over. If this port is already taken, \fBippusbxd\fP will error out. .TP .B \fB-P\fP \fIPORT_NUMBER\fR, \fB--from-port\fP \fIPORT_NUMBER\fR Port number \fBippusbxd\fP will expose the printer over. If this port is already taken, \fBippusbxd\fP will increase the port number by 1 and try again until it finds a free port. .TP .B \fB-i\fP \fIINTERFACE\fR, \fB--interface\fP \fIINTERFACE\fR Network interface to use. Default is the loopback interface (lo, localhost). .TP .B \fB-l\fP, \fB--logging\fP Send all logging to syslog. .TP .B \fB-q\fP, \fB--verbose\fP Enable verbose logging. .TP .B \fB-d\fP, \fB--debug\fP Enables debug mode. Implies \-q and \-n. Verbose logging will be sent to stdout .TP .B \fB-n\fP, \fB--no-fork\fP Enables no fork mode. Disables deamonization. .TP .B \fB-B\fP, \fB--no-broadcast\fP No-broadcast mode, do not DNS-SD-broadcast .TP .B \fB-N\fP, \fB--no-printer\fP No-printer mode, debug/developer mode which makes \fBippusbxd\fP run without IPP-over-USB printer .SH BUGS \fBippusbxd\fR does not detect whether a USB printer is already connected by another instance of \fBippusbxd\fR, so the system/the user has to take care to not start \fBippusbxd\fR more than once for one and the same printer. Especially one should never start \fBippusbxd\fR repeatedly without specifying a printer to assure that all connected IPP-over-USB printers get their \fBippusbxd\fR instance. ippusbxd-1.34/doc/ippusbxd_logo.svg000066400000000000000000000066451362321405400174560ustar00rootroot00000000000000 image/svg+xml Extreme! IPP over USB ippusbxd ippusbxd-1.34/doc/ippusbxd_presentation.odp000066400000000000000000001406121362321405400212050ustar00rootroot00000000000000PKLE3&//mimetypeapplication/vnd.oasis.opendocument.presentationPKLE,,Thumbnails/thumbnail.pngPNG  IHDRd*1+IDATxwֆ{v6.JP$䬄 J" (AD (H$ \ HR$U^ ,6|a+fvW@Wfz_OOuթPU]v-"V ``p+a``k!a``k!a``k!a``k!a``k!a``kFv;9PiԊj(Z1VPJ˕INӨ튠$Α3-8 ukbpk`Sݻ7]tiiBBBl^}Ոm#D";v=: 3(PYfjm#'Nwy'ڙ!ce͚c AF/Yd֭wuW\\\i$ 9rdڴizҢ-[`"hhLL ?9Jv- 2f̸`F.\Xn,Fb?Ceʔi· .n7N ѕ 䧟~3gNdddZ>PN`͚5ׯQ` XI_&O|93q{M&J*'p 87n\jUJ#?#ݜy0ֵ ;cϞ=x֭[!A;(}ꌏOrک}-D_p`֬Yϖ-d_A@ϟ?׮]˗'UWˣ7 : y}yM(.eȐ{̘1IExmˊ=***}CbºvJn lѶĨwԉ(+&&F܈h9#@%*WlyRNp3gE}=MkdĺA'|KP-ܷoI(n۷oWzF{n''%4mڴCsFaonLd|4tL8ŋ>=< +W. ::ʕ+h**K@Ot=*&<<c߶m[xUBnݺXC<Ak9sFDxrZ"N.x =]v ~zn:(^?oy̿\8hРM6֭[WTZXj֭[*T/bX^8Vz?lpsqc2Hdɒ#G=Zj1" @ /|2_m$s͐! NWA l9I:!Á7xCF?|G.]%2l?po 7%-4y5Vg`ޡU*.cML!$wVMI DǧO.T.],o8)p-+8X "J_!ObQߨ:J=>ԝQe;:sҿ_\k] NUH{",m3**ԩSɚlժUɒ%Wvz}ݖ'7ڛ+0{D IV3tIYZ*`Y/EREnmO54G|ͧ~?):Aț3gζmZd˖ͧf]Ĝ[Gay虴f+ P ( [G ]}s|&HnQ1*_ u|l>%gz) >BҴgW\!zjժխ[^N/XPgb u=xgO@L/Μ93g*TP yhsz6?6m={6^E$ ۵kt뮻W^A B%hݻ9Y|,XPkQA{ԩt9|O<-+V|GQF.Rѣio׮s!@ܹRldV# J, fOҲkZzu_Y Pcj*jG-~;a2QB8Ei♼ybQJ ( zjU8HPLy*f!R~$!"$Sa6QTƟ@4"Mϟ$&DΝ;@fFm_/U¼hLD|Mp'LVLB%(1^q#L 0A!cI>6r8/n6(OHi蓟EsjK~X TbBSlj =ReZ)c}*@AݦMl3E  q$h~99I,뭈97Tyb#FOcmFN Iw=툯CecgЕ~ 9oB6i+gΜb* [?Q2x@yȢ@;E.Yѣ2_CqpUHCdo@[\NJ@wQA=h>'9&G͚5 "Do-7 gO?v˗fbK{E8R TbHFsȁwBWp/+x衇,1۱/]tʕ˕+'J6"a.& ? 1bŊu֍H>?H@ iJJ>`[C5[l]Fta_~IRW8 vQo]ՃΩ%]v݄.Wd^8(Ish3є G*2Z-$t`F|%6u`1?v{Kʯ,W'PR]H@!̼KoGJLL'b?7ږۻϘE<E*i/x Ibˣ5k:uJȄP:}jӭ R֭Wm3&tR8 g?9z >73X(px٤IlNg*E!,X@sy,>y$[~$(S,"R\(n˻ ]BԈ(M\YUJmT2HO+۷߿c ̞=Yf+>HvءduGwy aN4N:y [V^]a,o5~5`(i:) HONz;6ʔ)S G(MBL50b͛'$+Z^KFA<- %9C1g=^Wd1L\mRJDm۶%>p m0s$dGB2u=+WٳP^nT_$\B``$V4 àA`' Ep-ΝS|FnDY}PNSBŋ `<ȑ#~a޽]7c۶m#OF 3!'a[1FA"WQrȑ}PM%$Z2!ѣG6mU08CE! Kx.\K,ю껨#mai */FP޼yK0"9>}kB4>gH}ѣGSaݺueW}C4&Bd@Pʒf_)ō]ХZjT/:sϑM0OGX U8˗-[O?4"u ۇ䦝8qg$#(,{g t>,('ϓ'O˖-.P;6c/%g Ly}Qb8@ ,STRIXfM:,"@HL8VZ9AVp3( /l}N%eHȆʅ\< (HbӰaí["!aa, /ʹxܹCo߾ٳg93:5~x8PX1:E*U?c ']n uD4ԒEۻG;|,Ɇef%w_\U Oucd8}B0Y;v,JK%?D5X8 u_sM ЪU+D FsXh(ڣ zĂrz ԇ4hj%>~ۻu)Zt]6wq#xŋ8O ˻x饗p'۽{7fUFT(˫ %bI3&OM'|\>fBOѢ#he?Zr= Nl*U"(Q#V@{rPYOJۼys1Y΀Ip G$@(Fԃ6Өhre/_> T%Kl֬I6Ya*Xbt_q=C`?("(F"Dj.UNf֬Y$9bh_Dy4~T(,o*/^ B@H= 5ɛϟOI'Aˑ/MX^=z m$L H k'~駲gH_QS]lӦUo0)H+b [,YD Du_ETڵktt44 oy "" HdR2Dk ?@%12f,~,**J7 & FIs1ehi^tK$)8߮];<'@Z8g%fٖBuH&0x+KI5N  Pp۶mN:ezpCqIv…z+[lbyZm9ͫRJ\hûw)gEw7Y3B 6لhܸqlyӧ˔)ӬY;v̜9SVqUzVV+{liJ/sp>eqa5\paΝU]vaA~Gng֭f:p@bhzIB (x^~~)fy'Y< =޽{˯'ݿ%dih.KnZ.ˌ Ecbb^˳JOWϟ?=k.ث*9~@٭=~g'|{}B/+VȔ)ܹs=zؽ{7(dr瓵kڵz!UIJ};`y0 \9 (M ן~)B/{\ҦM:N)St>` 4(]4koNw ҴȊ#CRHH&`HV!h pxߙ$nr͈=:{lyW.δi5jD\\9,_Μ98@x-ZU?Yl/^ܩS~{ɒ%`РA\8jԨUVPaݳg% WSEs͛#虼}4rA0YN1wN֦)@E=s \B>(e˖'NU û0] 9ېi0(e886n8~ e Ч]3-LU`PeD/%iРU; vp r %cyTeʔ!t%K-ZT6Qm׮]9vz_f9xYFf xPtL;dS,N4I"XgX6ƒ]-m@&2&'еkF`(!Kd2쁧<tpX͜9sryMA-ԧHI4yҺv UvxHOt_>B(4ad'YEH%/L빁.8RXVeKV}_w)\Kk_ @P@GJkQdvTb=TTt}JEN~J))omBP#>NH {Jջn^m0+x Om߫Rfէ)]챺:I̟ Ssp酓* ^-005  l C[005  l C[005  l C[005  l C[005  l C[005  l C[005  l C[005  l C[005  l C[005  l C[005  l C np1t{;v@w{J8_CBBNI~O _BC_(} JBp*{yJ&&&'|}+W!nRm]GU}6'a Q/\xyNSnڙ#ӹLW#ĉNHHq:hnB|\hXݟ1jPWB h|\\XxE&ДD>= <<4dTW9nw“8C Ǟne3%PgR1ˁJYzտ` xH>v#Og K.~Lʐ!Z@)/_q-ِ0T+᜿{W뷬Ot$v]BE'q|KgHfu.Ɔ;ĸCG^O!+66bCK ng$;lu}W̭s>/a̙0["Ӈޛ1o;/oվ{ݾ%&1ŋ.wS'l^1 2.6}҈GhHH*mqߗp s?p%>JJp:[v?2fTܼOXЦ/֗o>[ŒOV?S:/w5t삵KzZ.kS> T/:_~{<̺v3qGƟP/?.kړ~KɮÉ[-eջ20׫iأˍg߹{u &lXU7K*2HIB ];ʔ(0sdv̥?.\_j.^,o̅h)2}ؙQXwCUg($1+CziǞ1grb]}zIU ]V~5kw= 8{c69Cc}oFN$%!&)F֭YPh;^nK3߹b'Gyg.s\y 哰󵮰{re #MT,rR}?}귮ݺgΒ7W,6.>;,8rprvp,_s|uo뺯bNϕ*+qi3V~7f;.fg񉉱b#]둢/S \_z\,ZF`Dd~ ϐi -}_VO4]x9k~ӼSY"lvGOQάwۃwLeG2xa#+5pÇwyTJp7yQHσ6C_xFحtYrumbHH3m6oӪȅ_,~{Nn}*,V1~gh;+Uj݇ՓS{>ZYx{۞! URA Ę?gG!Ipg =)py3bǺd&$$&]2I4D3\lD"1$iMIeVOdϵNg(%'HU!޹99o?EyX(ˋ0EKu6JC*$1tK깜%dIo4Z!U᫵=x<4V(ߝQF/\?S$N̎ o 5H67KͲRzM gtqd#yUO)[ˌX{R\7?ޅ1iic<QR>[ y`E0 &I06qᚘ9%(%kf0Aq9M @k߶K~?#B<.+]?_A}OBݏp,]ON-A,Bg%'s5\+h~,fg 1 A]U_PK$ _.PKLE settings.xmlZ[o~ -Рu%Y+mHKrPHM-m7 -ڗ[?:$+SZE`Is83T@NX V{Plc;F=~7`;!߆I5jXJ(hwQIno4YLbJRTj;%(N%oN:d+7C]U+ ;X"8w'M%* sC(ʨ9ǣѵ"hQ.mByaQQo{nH<5IlO#̫PcmfR&OxDOȭ}X^'!c)4$zVx5v?rꆾ9؈|@u5~um}=(~XR9x i.CM-b1ܔ*@maq&r2|L0FC?+cqzWj"fʚO2";Gb/" bujV&)c/CPaz˶ :5 A@ezδVz4u4wpУYo(pc-E{nzLH-pro6%ffTp` 1wMU A9tc/ՏhN],gS.i WX(b.yktӈoᩚ9CxMX>M TPbU=+j*)@g t w22p,5W_; 65 >vY[yޡm_~Xc^,>vo7q3]&#rmn·6M]E,bUYDž T(.hIi$k(P*X~O*hݎF0hԀweWJU ԓX˞vѮN)qydmo@zJm:kxsOOz3Uc H@\T|dr[. `6˭g8*5+Ԭt[.JD<'XH[̐:^J Ů٭V*r-%yg^\JnRL<Ѐ(E]'P TauB]2Nj"ʧbr{vRs,a!⍲\;`4{ ژtY\R`VsƤxB;-WΔbjS&J Jis:q\ }+@v2cuLٲ]F8CIqfǖ+in7R VGY9WE*tSTӇWe+ u8 *'}o}jL[< SRLg¤^ B$u9>.|B]‚IrW*w$TCN.z\C挗TlglRnu8yҡbbU֠~.ސ.7T?P4cSՋQc3g'E(*/]EOb ߖk$EFKL]9S$Uf?'=^npZJnqt {a ._cE;i exUW͐QkdB7,)v'6~~_~ ~]^M Nm: BS#?p@hMeJ[zEGZF.0>.T>NȅF3U4\xJ{sȴ fgd~M{;vfϘdv Et)ZB Tq*U⹊ͮB.f wag|CEIn$תK&y~e2ɺ:-Omݾ=[9y#;[˜ ˤ_9 7;cɑ3 x;g\Vj{+)tu~)f6f ]wƭ</ mP"(zҶ2b95A~9gf#)fV,}w]yM`smG΢6IX%(aU_SDmpkaԌo+h 7 >.X @%,9*jh?LeA7>ָ,8T7AxJc}C5Ru/o^"BU}5Pm/im=!TS;"Bi j_:U'֮U=c.{QoNոk^]n͏ѡ{Cۋ3㾃(Q֞Pۋ;{pr_ 2ճqxyaiTѓ8yHgٜ<2D9lDrੴU]P3/J~LeL>BbextftkQt<(̶OSLB:d˰07jdtQlz.7y?>26U긐eka>Q&ꩌdѓW*TpfЗM;C 0UyO$RZ_^=txQcēN$nc}~ ucԞ=3ڝǏ%W QSb(ǷvItFzce.k-:>sbfb8l=+ d 1'y8e%Ij*r% FYDgSzҒǙ1yӐj2)*,JůI ptP\ʁE8'!ٌ*N?%_@<ۇr^JXgy# j7bcddnRhC4P=DFC478=?=LJv#3ن$BG`[2 X9H.l;"ps.B[W_gWS% T04[zmepeҤP쬛n~ κ8> 9r<}Ղ~X.]k,ѺYՌ+,{zyqS<&OOMiV/֮|d_X^{pE{Ne*"d]{"*Hfem%z,wxߔū7 'CYk̓kHT^u<8uW1vaE+n+\ j/d<>GsBIT|d pHYs&s&slu tEXtSoftwarewww.inkscape.org< IDATxw]Uo&AH* {лtBAIA齉KzGiR ɼ=d2dfns}gIϚ-g֠.DDDDD OHR" """"ҁt %"""""HHR" """"ҁt %"""""HHR" """"ҁt %"""""HHR" """"ҁt %"""""HHR" """"ҁt %"""""HHR" """"ҁt %"""""HH;雙 ̓;i[<T BDDD֠ %RlAHJ*̦!)<Tӯs!a53 w"""RA]]]claQt~C";)VH ;)V*tO܁H9"Pf6pb8cM;)7w67r!"""֠ 0)s"`ywHӊ@5e-r!"""ӊ@ff4 0(w,"DDDDNEIT,AbiE #3pI8D& Br""""Њ@&f60"w"}8!w"""R%̞;~ofBDDDAٜ3c>&w """V8% ;IO+%3s!Rw܁H:Z( & i%D\KB¹țH'BDDDQ"P_3B kٺ4tXf6?0,w,"M74G+8 %BDDD I#B97s""""ӊ@L,M ;ib,;lcfKBDDDD f6qdj'""Ҕ`AhE3Y DDD1:,\3[ x; ,DDDDb BDDD')gJ@DDDvZH&NH&G@Z{sB$lAH5(3xB%N0;VdfV)h)hM;;̒; w"""7n<L;zXGDDDDN+;%"Y-w"""2aZhܖ;0PN܁՘q$aC 8Y;Vdfg#AH`fg;#Rh@DDDdvu,M ;\;@;uLh'"""D>".`kYB~vK8W1 s!"""(- ;.ts!t:l ;&Jjw2ucAȄ{#i%r!"""ٺZH(w#w7w>w ~FDDDv.M;^ pH|;DDDDƧ Db`w_]IH5@?l&9`ܱ$t;MMxΘ;| dSDDڴ"пi$` G >@%$@DDtFf4u8;4p^J^6rUvM6VGbvws!s{?厃Ѓ``!w"w0"""m &F#3r!IL]AA!"""us"=AH$um!lkQuus!٤p-)V7JF{Brπ} 'JDDDړV"3[PBafw+' 8_K'$c0 `A9JD&K4ܿDcTm|/ kJo :J>[.P"$wmf{m{f")m%%#rPDcurǑr |~=p o:JDDD[GPq$p\ $=5u9CN] X(wBqgӁJDDD/"`f3#B*``F@:. t۝&w uʅjADDDZCGm 2ŀrǑ؅` DDDDtT" DBBDDDDZO$fZ8;Hlگq h"""""uD f ݿOlVq$v%w""""Һ>&DBc=r!""""3[0yQUkw r YS8'x} Ǚ ,&H0WҞOHmg)[L;prbY5[žBeo䎥FSf wgfS 6%ro:o'ﭺ%/ #3.x}9[vgj8v''@C؆JD"@XʫH!4 X>w, 1pYU+Y)rR!l܁H:qjv\Z+Ա%d?9-DL4~pku\r뮮CڙPjQ;|"`f+8A[U&gouVzD(a@$SesQ O÷Mo3V?)L Ұf6pr!$4WS&pmLd^n z78#_Wcf;#+ As\bf4'&tf:% A7k) 7~+i|JNTuӁ5^lFH*-;swrbuvִWMk4 "άI 3z6H`\[[R BsTI\#3Z6=ͬ64@,vT8{LD+^gN k}aI5 BX"nCRL.AҸfV!X \I(]=z ΥZ (BI]wgAc+O^G^!vql6L%F$4{h'{pR̍AnٷL1|/u8.={% 7{l2Mь}DBow!2 t]Mݽ'n>],wE䊀GXj'׹67UeSؚt{Gfw=}wp#}"U/) 6ph-g&!fvH{(wg δf+UN]53SSz$TRx0 2=V%Ƕ]Q}-mtoo&,&f#4&[Xrk+ -x]iΉ XhCCfԾ: Fh:aԄҞ \-Kx eO3ݟ{#a-BKl4UAKf6 i$ B' ]&lXjҶ\"`fGbsS&0B2 PsB I TjkP<(mM{$ qs~V!@(:"<@0wfU+ u4|R5QzЙ4oA8r#`2cJ 8ݷs ?w?Jp)g~df5uv2S+P^z>l¤LѺGs5&_Vg6X"JPo'$5w}o?#,mvga`w_iv0w7KX5(hEs՟KMݟp+3~}SnJ:V&wVL]j-' b \`fVc2$.:s(V#z|55oѲ-ے*􄥥v%&IO˺}2qfk;`#¹E5W!n,5V*/ՄiK(Ka물&`gBS;G?'lko?W?df"aLloWΔÄ3gǦT&Db{Xݟ/"~a(8v?R}P~M2nǛض |k:Pjy}okE koG]!S5qǨoa ;#H\l@1ILȍFsm *UWD̾MΈ*'eU3+Sƒf\Acc,֮EjXbliHWunDt37Hh۞bͬE7'fIX8{Q~_=0a{^v`/$T븭U ;b3ʲ}N'6)᛻?lHQMl ?7[T%$S\AX+eG2pCAP´x$x(f65ae$ʨzW)`qrn}:le {"`f=.Yج٫Y 'fL~^]E x&8l!؆Ћ?=4K9SǟO!]M Lm([?~aPlkZD&:F .1˙[aFcKdcN,x́TjY5!n0@[Ҽԕ^3Y{H=nI?D*L"l}1IKOR^? epZ7XҰ͎qQlr"U껥ZJ^- $w6RnKJ_ݶw X=Wp8i3[+w0-bd(|̈?^*@:a::x5s I$"fZѴcw"} \Kg̀R}x<; Q˙YQ St*@\?g ;wO<^b(m9~ J{p͑; K}ISfX㦞)xR+%}H@35w"P"Jwۢ?4ߖW{L',u"qlNB9"M\jfPc'pZof&'i ! kjŹG[w_"od Sl&(u"Sq|ph '')Ȩ@_3(f> ;p%tf6QbE`c6"df$Ͻ״㶤f`.`LAedڨcLմh&$\!/c[`5oy>x͘ ,ifMw5wn4aIj0fQ&-X5ef?*`f~_&">cf䌡JCw7!l]eW2 F[oRYX 񘏻{Cc-Ө! OG҅]J' \"AȜ&:o2=J0 D`pxI-}woaҔear 04+?v?*]j(rAc}k{#ܗ \3"Tj)ez4̆Rlӷ*rǑfZ kZI9 gd#3a~MlS4QI_fn5o D1gL)`̾Y?g(}W;X(Q Ff]/bekfwӽ!qj[L_"Sؗ6sʥnwON5*`=qkHL~ł}VV%yן ֗ pC2Iwf pbNkb Mlo+qX8VDGb;B&ܜ9yٛf ֮ }3v.Ox8x 7uw*e\6bVnw27=]"`hɀKͬmKq[8zC6,BߐVD tz;9j:Ukp9m,K58S]Tv1zZ6aٖ(~o3}K,1(nu2p.; {u8zZ̊&[33ۅPU:p1g:耥2۲/[U-wBfVDŞ>`dBQ :,πSeef3)Ѓ Ql3˲%$Ek5*~5i_wϛrv,=[r\}$ŝ hfk4ʼn?*D{S̡\(K5m\Ⱦz-~L([W~6<{/6/2׸{g6'l9+ƺXw3sI3['U`,cA±18M go+]u3pN19ٌt?.l4<6pm]1?S| JjD nUB>pWlMdf&̤9pajA}mfQ/"T(ؽ|sP^ozl3;pSVm6Uyf\I}驄ckߊ:gfCl8p >or ]SجwZS X2 ]䜙MefRgrHk@tF ;"d–1Sh #XEF Wpa3[6`f68.{X#ŘߙpGP ̪)3#Zv峅 [} ,*p]ifeܟ 3]K8V~1'&~ppmM$r^CC`pSmv/h(>X3{xИL,4-e̖q [{fSO[L3fax $̨OA Zt%כLP-.[~q3ngfϻ(AiD7)"Hٶ3ߺùLNieD&v,_7e%į"%TDZ|%\g lAn!l^1#pD9I#(ϟwЬgWIx?EM<~'BTg?X:^-1)'RK\*`T໹0 .Y h9kf䴋 7ԥ%'sj,fN^LL%/]\w{z@Ld M-{l6T ^"t i&.5kY6%VOu^j#mZjW5q$RRvSRbmŽC` Tu-[#ʺ`l繁f$\H8 }iff1.a;y좒O oj=} %W*u[]MMXS[_Z/[P\o. h+-D'RLGΜFQ[]%yX<;$dnTMbMc{P|,~=*/jpŠugNtK{49{]AqXHn =!qIH[Bv1&a _%n.hD .ELK?m,_eq[I>N*h^EnSjW^3٫aj<`nfkvao;t/R \g ? h+-H3=w0A4S׋-~G(؊\>r1?t{YUI*GГ}Zz|ezt@!g%lo]pKC.{TphW(yf- /jӚB衐QJ~XOC\|@}杒8a;Z'ڽjV+^/a` wy~_̜ 3IռW7nLCNvg.ZuO-c!;MlAtx.v}+ݯ@b9a~IXn') H%זYKhVe+˙؁Pn+eag-Zm]/܁ e[KN5f7H{V-`ۄ) OٰtbBIњ 3BV&t:axw merǑjbKM`U{`%wo7"wPGq.tJ^%ޠ|ԫP9eU:c/uOg>%T82p{XodfSf'Jv~u„d8&l%>1v9u.H37I3kצE9r Kw厥+Ҍ wXꕫ{X :2}`n`Zw{OpJnUU&٨VkEܽg%w~mj5[_w4 }P&l g }mg8&% V!oNWFQܹ] ,fc!e {s \=dwX pƦ ʊ&ed/*il3`˄<">؜U19w߄pv /=)mI뽏LP$~#aafDr{ŀ(л/E㶚c~7eu`w_[+,CB˄Fʩ%8tMo?%tW}ookR(fLe{@#+(s MXyLiQb_sL \lfCj Uq)w߿w؄|խh/ %w!l1(#!y%L]\5ffV5vm`ׁT1գ]fKBOpjUIz2%G_ Vr2Wu;ϜUIz2 7 ǯ%9M27y.+ůz|=+*Eu]X90 }BW !̺.yljBBar_P[E.gyPA/TeExkSꉯϓ׷=n lc;<4+-ȸ,aϦc|yɍ+{kD̶^iUD/f6 mj(–U1[c n'%(V|hf6{0z<''5) v;oK%&ݯǯeIŕݯoݟX}i &[*w, 56Ai7VIDDD$v<, $d:׹׶|UM+-;immD4[DDDDObH{f^ DDDDu}"GΆE:1[G$z~Hl^0#w 7wbf3BDDDDZO$pBWv1pt DDDDtT"Gb[ҹQ@t0u ;i-HlE3$w""""::.NDb#lAHkDG{#9s!""""#w)wgfsBDDDDchOA$4)0"w""""R}Hl3[)w""""RmD"SL""""ҧYt/sǑR6D :x,wefSBDDDDIc=rǑL!jR"Hl͛;%^%O)XJpkIY Ǯ5|/6Ho}}c3kV{qRtwϘ[#dwĘYfcsp5-?ky~~ ,Ϸ _ [cJwK62f_hܖSV?i 2 逓73\83;E afӛ5_? ``#!3fI`}\aff!+$ٱIT~x''"_9mFMR_֨sۆ0 XX0 a1H7[m?/^V lyZm=`3ݯ#/ *0Eaf/)H`;`܁dPAHF7=Hث =j3[?q ^83.alfjfK ) b$H{ofCu#Eof׻o=bfnueUu%N]^sID*o#/wN-"ٙWV' wW^;f# h㯧1uݼ%w#Hr;Y)+.SB%n[|}oRy(wqEz B瀛}T#?GL(7w6mt>̮"'if =.+%u3g#<ׁ׀jEcMLou5eMk4:پA9hɢ #TV_lrBeI۸9L,Q^fզ6ÁsRMK{'ǚЗxxM`IB LS"}r*~{ Ys}sX CSҒN=k> @|V]é D<=~Nue_½{*/O%!c#ud6}<| 79jᯈ#ؤ'(S,Z==s{GO^sshӕmރ_w;\^-r:a_r']j]$m7z_awaOxrkH&RԵ-$w| *-./BcغQ`fJ/,7Nɾހ{g*ǘ/pCHvWrv uY%"=\/Žt?0ےvN5ߑ]Ư%9X4ݳ./&f+%%efH^~9*nL#i=h,jW\qZ Si:^f;T=~OLK: 82|+~U_o4?gK8\ ~_朗\qh@2tf?g#5x)G^+C֬&\]3}̠̞QJN:&E( ىs<2^> `'cF\Jyś@OKJ|A }N1=EE(_6Ւm$uh1̌ٙE# s]kŤW[%"|q+`#3d5I٭.7_7w?3ś(1 L0(9_uM}ÀwյW3{fR酏ΓϵefYDEzqNfl`f_c,~K-yMPO7W;E_ ̶Jk졉4j%;ҽ+N'pH>`f=ʸp-="_S3~ۀlv36ISn4s!]$%n " M=+5!/#r+WqO-bbvUBNcf?|[H|}/d66b ׾O+3/)?.½.Oz7&5Ȯ2fÍ$-of|`'98&-cfWEǥOK:o2r7m#Nڭ㟀8ż}K)63^KŸ/QrBS$lXOYYN_ | Qz?D6>1Sj*yx])kM{r~HϱY=!_{Vt/St-RcxIh#kdhpcVe#q$TtIIԥ,;Z8>gFI.7!_f%mRY(z(!)i/#OV}s-P[E^iU*6V:WҬUg țKnf/=G=W@x2gZ/#GX *[Im\0oW5XOEу:eӘsr:aʑӛ=N=ᒱw_Kx%{A;PW}(Nëa}+a-ͧ]4 :UxwX='A}ļpDcX/:NiYkfgHڄ׹~Fg/ L3jY~rVtS C-jafKmw7SAhKT~ZWHF1e8V-,?D)҄'铅'WK^r*zypo%f֭hD S2ɍl\`f%G̿W [9^bmv ɝ KZ )ͬ_2nVuHF7oTŖuXa3QexEİz3\zxueNIDx {(WHj6nu_,U^{ ~<6 $=KP֫,_BDaۤjSň52lPr!M.+xI̠pCwny'BKhWV޷}/Jԍ8I=P6O?k={Ex-Q=^2w;_I>qǟ7H~~#a OW z"]dX bfJ?^r^%$!v)osvbk$MJ򅺴 <ֱ#4P5i'^?ſx#ՅJ<  Ӧ?K"lYj~S!0 ?j/KP% zm:~]NH^w2 ~R4>r ҍv]ZR=G >8M7Iڢ1Α<^IooTun"`fcpIbTHIݔ=dx-n\X>ؐto(e9E/fouMkZC'x8$I׌MURs ]%f6_@ְ!&w/GG<OUޯZsJEZ%F&;xt!k3S Q=bjWOf*IFax3+ \.Y]K Ii|1KkMBjVI;`o%mH>Te_52NfOR_]7-u#yvVȮ 5U{;X^>$BغWV]R%6y\$yu1A 1KF6. ʸ/U/7z:Y?ޮv0~Y!tבyb,.Ln .j)aШ[Sx^j m-7R>רa(FcC*y%|*j&/)x݅8PR=̭j"b)Ixm hTnX)hn4CJ M C4ĕ"J~X6cfmrJdjNjOusAU4_zDt|fD$ntl@s/d5b~rƗ%CJQ߾P\C^|P޾3w)n;<6|^4ma>qiHŲ%M8aeml{ibR.dm'a fp|JHڟrTa%L9?JZs"M CHR'yLG pN r[J~O ~E I"< \´,85Rv)3Z逜") HZT=-zI7 1+('n W'Lk_$m?[wL]nTQHz%pU5anz2S /&8ɛWahq^$5K$hnѫlBgS CS(O%{ǥjO>O{zZAJTwv_ʺfy#pJ|Rf \UxoڭE\,^6O?XLI+B=$'*6Y"dJc.DXxRI9oER_ fAϣF|Jr7𼀗^Vf*z5%TpjվNxU%}[k??'2_;V^ Jfg,`a k̥gLH{P^tNZ6%K(7:ّ̺'B0pݚ"JZ_QJ79 W;w/i ZsJXe [=:Az-sl}O3>7( ZMX7jMfvo< x1-IS0`zig D3~ʋ87e5:I3=t%>|w& fPҮxw큻$}V>I9;O餃~.CW?^y._EB,3}ʲFbf~6LOZߓ"~M;mf;~cf?ng5ooI5Qc?vnvmfH|78aI~7|,gyi>&bEG'Lѡocafvc&>8tsiaw &sHz酡̬iKVo3%m5~xY,Ug'zh Ia࣒n=JuqIzrP3CY%Oҵ+:͛ f]IU~2΅$O]?oj=:#$zdmwiwH7w7IzOp~_$<_C̬@1K%݁/^h`v|~z+i%]'2O{=K<`fvODkU"p* -po@jv0FzN1zNMaf Oոnޏ }.r0P&4v},Oˌg G0f%pwpx1O] #`tI&rs&3oF 1Nf_NHQM?>Z7xbmf,e0G# č#ih}3;U#2_ه(94>]{md~(̞t<a1,~WKZxvHCS%Sg$ O<^w=q_1=&?OK:IDATKxg NN~@lIRlF3{ʮ/A.>HKnq~j>EJc4\ƨ]uuuWuۻA ZUABͯJH 49ZB/a7+Vy- t-Adw"_PP0kiim°TՇ.$}M1F.Vd[-0 @nTKރ6)֮y UŰ뫊am Z+n^m*XЦǯ-&#VI6)@$_Nܪ$k%ehNc O^:nm1mϩ =ͩj@/p(dV^d,[n2HmL:lcWs#)T+QjZ5Dl}BL_"'K <0pHpir C#*}R'% ia֥2=Cx 6 f^MËtE &鰚?\K D@ &jC[WwKxayn@P3bGRl` %ڿk:2^XP٦FlN9 ;@K}F-P9!<s-@@т[Lu9#C6Ne3 b'x 1y5㒬-DגS{3TVS7.se.tnJ*t߲5$֐QBλd?2!HəIX7gZGg{V)vV.UBOC!W!p > L;Vא9%3@qs<qSH7 BxH"?$[.KBNɉ = e9/ V0TTy2x" /Q@,seO^D 1%*1rׯ󝒭 uv.lI՗6#ЈFU[j>KT`?-3goQuB1 ynSPI|xLISX+ڢd:'OpZC&/PCdG'%|NbFjlB!h0#TLQ΅ Ba E9MP(ccHO|(e?4r:aڦftH$s2byr]X%)UdU2q}MpCm'`kae(6n#a9ET8-`1eU6AXSxpy!1=8-"4H!+DhBE3n ӓ\6[D2ij{l2ǫ2Μ̼B؃H2/%]8(TcZJshnŔF%p7OJ ԈhNG 6]䉢?CSȬ.Q-m£ ,,k $^a XG$~ܚu<>`goY58@1yY2ߠUat*ڴ5<\ڄIRrrJH6L-s&g*4O B][B\hń #]qIo SaIWdߣnNRc0Yiԙ䬗[h%}RQ0lЍFOoY@epZ}MӁo)rȬ-tȐÓ Ct A 1_nt+rQ^Halalc s"i# S`$8;l0:K:i9K9~~vC͚GwNps] v8'`t5{.%_97@-VV@ectR*6uOp8[o̊ql2beyf{kO kI.C隻$JCk>,k.4&۵i^$>kjԴeOج¤.hrĠwA& g4)MrlIP>fт48ͥĊaMl1h/ץxV_oA+x7+J 3ð;5lA;x5I8^V>ƭ5z6xMZkl2[krxia~nVIiim,mTmwaHwaHwaș@uttSwaȓ9?KK8ͅ!T6^Nu'UIUwRu'&b$]t13Et1.FHy#HZ>c -Ƕ b%_HCZ>J qH>$*uTf}\<-z vǵxw+I|uqj#MUQ\,nx NDSq &@`&ک؍~|j8ߴ[Co<߄N7s3cЇL ֖T2Tǻ -UĘ*S~C'|גxYU*!}1]\笏)CS,1NJ&  ͷ9EX vL *Ew -j7AYX;\7Sv1_W|񕚡 ;6הЮ/sϨvzhnn`,v QoۉS s2bۥ>ۤ.=gcyh\d00B EsžcCqi:?8-&^xZ)|7zcNzv\#]^uwEG6kYUͫ]]Gѥ B]GѥtEK?Tonmv>?v6fl7Bf[ȵ.yKFl1'n~'Mgzi5hRR  Q~V8O]̄4"9™fIP| %\ 4m-V˩ 1Tt3ɯF =Kd*0xq;Te01NDz>9<ޏ {,~\;Qwȝ>7@0G#;5-!4) )J{|I0kJ3Y&Tmzo0Ou<5]:&]M`ÚtsdSi>R,ZwXue}ëk5BbZ˚o:x}>G ;@/#]qQъ75"i# 1I?O "v*c\6uΌ|\IBc=fd/_I^nيU7{0F -H gk Ҏ M@(pȦ2j|q2N8pE몢6@ɯR)׳+bF3s4ɍvrc(Cm1l}ñ.꘢ MQŌY㜋,'d_;#[ FMM̯(x@\l#kL^ _PKO'ĸPKLEConfigurations2/popupmenu/PKLEConfigurations2/toolbar/PKLEConfigurations2/menubar/PKLEConfigurations2/floater/PKLE'Configurations2/accelerator/current.xmlPKPKLEConfigurations2/images/Bitmaps/PKLEConfigurations2/toolpanel/PKLEConfigurations2/progressbar/PKLEConfigurations2/statusbar/PKLEMETA-INF/manifest.xmlTMo WDI{颦u"Tԏijl`>Zf%/X\-_ii%Ug'K0\šEA P\8- U뫑Svk>_ ### Terms and Conditions for use, reproduction, and distribution #### 1. Definitions “License” shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. “Licensor” shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. “Legal Entity” shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, “control” means **(i)** the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the outstanding shares, or **(iii)** beneficial ownership of such entity. “You” (or “Your”) shall mean an individual or Legal Entity exercising permissions granted by this License. “Source” form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. “Object” form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. “Work” shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). “Derivative Works” shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. “Contribution” shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, “submitted” means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as “Not a Contribution.” “Contributor” shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. #### 2. Grant of Copyright License Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. #### 3. Grant of Patent License Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. #### 4. Redistribution You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: * **(a)** You must give any other recipients of the Work or Derivative Works a copy of this License; and * **(b)** You must cause any modified files to carry prominent notices stating that You changed the files; and * **(c)** You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and * **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. #### 5. Submission of Contributions Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. #### 6. Trademarks This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. #### 7. Disclaimer of Warranty Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. #### 8. Limitation of Liability In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. #### 9. Accepting Warranty or Additional Liability While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. _END OF TERMS AND CONDITIONS_ ### APPENDIX: How to apply the Apache License to your work To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets `[]` replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same “printed page” as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ippusbxd-1.34/makefile000066400000000000000000000021731362321405400150020ustar00rootroot00000000000000SHELL := /bin/bash ifndef VERBOSE CMD_VERB := @ NOPRINTD := --no-print-directory endif ################################################################################ BuildTargets := all clean configure redep distclean .PHONY: $(BuildTargets) ################################################################################ all: ifeq ($(wildcard exe/Makefile),) $(CMD_VERB) $(MAKE) $(NOPRINTD) configure $(CMD_VERB) $(MAKE) $(NOPRINTD) -C exe else $(CMD_VERB) $(MAKE) $(NOPRINTD) -C exe endif ################################################################################ configure: $(CMD_VERB) rm -rf ./exe ; mkdir -p exe $(CMD_VERB) cd exe/ ; cmake ../src ################################################################################ redep: $(CMD_VERB) cd exe/ ; cmake ../src ; cd .. ################################################################################ clean: $(CMD_VERB) $(MAKE) $(NOPRINTD) -C exe clean ################################################################################ distclean: $(CMD_VERB) if [ -f exe/Makefile ]; \ then $(MAKE) $(NOPRINTD) -C exe clean ;\ fi $(CMD_VERB) rm -rf exe ippusbxd-1.34/readme.md000066400000000000000000000376361362321405400150750ustar00rootroot00000000000000# IPPUSBXD [![Coverity analysis status](https://scan.coverity.com/projects/2634/badge.svg)](https://scan.coverity.com/projects/2634) Version 1.34 ## About IPPUSBXD is a userland driver for IPP-over-USB class USB devices. It has been designed for Linux but uses a cross platform usb library allowing eventual porting to Windows and other non-POSIX platforms. The IPP-over-USB standard was ratified by the USB forum in 2012. As of 2014 Mac OS X implemented this standard and with the addition of ippusbxd Linux shall as well. IPPUSBXD depends on POSIX threads, POSIX networking, and libusb as developed by the community at libusb.info IPPUSBXD has the following advantages; 1. At runtime links only with libc, pthreads, libusb, and libavahi*. On a typical system these libraries will already be in RAM. This gives ippusbxd a minimal ram footprint. 2. Requires no read access to any files. 3. Ships with a strict AppArmor profile. 3. Runs warning & leak free in valgrind 4. Compiles warning free in clang 5. Analyzed warning free in Coverity 6. Can be installed anywhere 7. Near zero CPU usage while idle 8. Low CPU usage while working ## Building To build ippusbxd you must have the development headers of libusb 1.0, libavahi-common, and libavahi-client installed along with cmake. Under Ubuntu and Debian: ``` sudo apt-get install libusb-1.0-0-dev libavahi-common-dev libavahi-client-dev libcups2-dev libxml2-dev cmake ``` Under Fedora: ``` sudo yum install libusbx-devel.* cmake ``` Install also the *-devel packages of libxml2, cups, libavahi-common and libavahi-client Once the dependencies are installed simply run: ``` make ``` That will run a makefile which will in turn run cmake. This makefile also supports several GNU-style make commands such as clean, and redep. ## Installation on a system with systemd, UDEV, and cups-filters Most systems nowadays use systemd for starting up all system services (instead of System V "init", as PID 1), UDEV to automatically set up hardware added to the system while it is running, and cups-filters to provide non-Mac-OS filters, backends, and cups-browsed. Therefore we explain only a method using systemd and UDEV here. In these systems it is recommended to start ippusbxd via systemd when an appropriate printer is connected and discovered by UDEV. cups-filters from version 1.13.2 on and CUPS from version 2.2.2 has everything needed for driverless printer setup. "driverless" means that no printer driver, with the driver being any software or data specific to (a) certain printer model(s) is needed. driverless printing makes use of IPP to allow the client to query the printer's capabilities and IPP-over-USB was developed to allow these queries also if the printer is not on the network but connected via USB. Therefore we can assume that all IPP-over-USB printers support driverless printing. A remark to driverless printing: There are several very similar standards: AirPrint, a proprietary standard from Apple and IPP Everywhere, an open standard of the Printer Working Group (PWG, http://www.pwg.org/), and also Mopria and Wi-Fi Direct. They all use the same methods of DNS-SD broadcasting of network printers, IPP-over-USB via the USB interface class 7, subclass 1, protocol 4, and IPP 2.0 with all its attributes for querying of capabilities, sending jobs with options as IPP attributes, and monitoring the status of the printer. The only difference is that IPP Everywhere uses PWG Raster as its raster data format, AirPrint uses Apple Raster, and Mopria and Wi-Fi Direct use PWG Raster or PCLm. All standards also support PDF as page description language. Even the PWG and Apple Raster formats are very similar. Therefore CUPS and CUPS filters simply support all methods. Note that these instructions and the sample files are tested on Ubuntu. On other distributions there are perhaps some changes needed, for example of the directories where to place the file and of paths in the files. There are two methods to install ippusbxd, one exposing the printer on localhost. and one exposing the printer on the dummy0 interface. Exposing the printer on localhost is the way how the IPP-over-USB standard is intended and therefore this is how it is intended to proceed on production systems and especially on Linux distributions. Disadvantage of this method is that Avahi needs to be modified so that it advertises services on localhost and these only on the local machine. Exposing the printer on the dummy0 interface does not require any changes on Avahi, but it is more awkward to set up the system and to access the printer and its web administration interface. ### 1. Expose the printer on localhost First, install ippusbxd: ``` sudo cp exe/ippusbxd /usr/sbin ``` Make sure that this file is owned by root and world-readable and -executable. Now install the files to manage the automatic start of ippusbxd: ``` sudo cp systemd-udev/55-ippusbxd.rules /lib/udev/rules.d/ sudo cp systemd-udev/ippusbxd@.service /lib/systemd/system/ ``` Make sure that they are owned by root and world-readable. Why do we not start ippusbxd directly out of the UDEV rules file? If we would do so, UDEV would kill ippusbxd after a timeout of 5 minutes. Out of UDEV rules you can only start programs which do not need to keep running permanently, like daemons. Therefore we use systemd here. Apply the following patch to the source code of Avahi (tested with version 0.6.32 and 0.7): ``` --- avahi-core/iface-linux.c~ +++ avahi-core/iface-linux.c @@ -104,8 +104,8 @@ hw->flags_ok = (ifinfomsg->ifi_flags & IFF_UP) && (!m->server->config.use_iff_running || (ifinfomsg->ifi_flags & IFF_RUNNING)) && - !(ifinfomsg->ifi_flags & IFF_LOOPBACK) && - (ifinfomsg->ifi_flags & IFF_MULTICAST) && + ((ifinfomsg->ifi_flags & IFF_LOOPBACK) || + (ifinfomsg->ifi_flags & IFF_MULTICAST)) && (m->server->config.allow_point_to_point || !(ifinfomsg->ifi_flags & IFF_POINTOPOINT)); /* Handle interface attributes */ --- avahi-core/iface-pfroute.c~ +++ avahi-core/iface-pfroute.c @@ -80,8 +80,8 @@ hw->flags_ok = (ifm->ifm_flags & IFF_UP) && (!m->server->config.use_iff_running || (ifm->ifm_flags & IFF_RUNNING)) && - !(ifm->ifm_flags & IFF_LOOPBACK) && - (ifm->ifm_flags & IFF_MULTICAST) && + ((ifm->ifm_flags & IFF_LOOPBACK) || + (ifm->ifm_flags & IFF_MULTICAST)) && (m->server->config.allow_point_to_point || !(ifm->ifm_flags & IFF_POINTOPOINT)); avahi_free(hw->name); @@ -427,8 +427,8 @@ hw->flags_ok = (flags & IFF_UP) && (!m->server->config.use_iff_running || (flags & IFF_RUNNING)) && - !(flags & IFF_LOOPBACK) && - (flags & IFF_MULTICAST) && + ((flags & IFF_LOOPBACK) || + (flags & IFF_MULTICAST)) && (m->server->config.allow_point_to_point || !(flags & IFF_POINTOPOINT)); hw->name = avahi_strdup(lifreq->lifr_name); hw->mtu = mtu; --- avahi-core/resolve-service.c~ +++ avahi-core/resolve-service.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -129,7 +130,7 @@ r->service_name, r->service_type, r->domain_name, - r->srv_record->data.srv.name, + (r->interface == if_nametoindex("lo")) ? "localhost" : r->srv_record->data.srv.name, r->address_record ? &a : NULL, r->srv_record->data.srv.port, r->txt_record ? r->txt_record->data.txt.string_list : NULL, ``` Build and install Avahi. This makes Avahi not only advertising services on the usual network interfaces but also on the "lo" (loopback) interface (localhost). The services on the loopback interface (on localhost) are only advertised on the local machine, so no additional info gets exposed to the network, especially no local-only service gets shared by this. This works well as long as your machine is connected to some kind of network (does not necessarily need to be a connection to the Internet, a virtual network interface to virtual machines running locally is enough). It is possible that the advertising of the printer stops if the loopback interface is the only network interface running due to lack of a multicast-capable interface. The patch above is already submitted upstream and also the problem with network-less machines is reported. See https://github.com/lathiat/avahi/issues/125 With this done, we have a completely standard-conforming support for IPP-over-USB. For the time being we have to take this into account in automated printer setup processes and in printer setup tools. cups-browsed for example uses the numeric IP if it is a local (127.X.Y.Z) one. Now we can restart systemd and UDEV to activate all this: ``` sudo systemctl daemon-reload sudo systemctl restart udev ``` If we connect and turn on an IPP-over-USB printer, ippusbxd gets started and makes the printer available under the IPP URI ``` ipp://localhost:60000/ipp/print ``` and its web administration interface under ``` http://localhost:60000/ ``` (if you have problems with the Chrome browser, use Firefox). It is also DNS-SD-broadcasted via our modified Avahi on the lo interface. To set up a print queue you could simply run ``` lpadmin -p printer -E -v ipp://localhost:60000/ipp/print -meverywhere ``` The "-meverywhere" makes CUPS auto-generate the PPD file for the printer, based on an IPP query of the printer's capabilities, independent whether the printer is an IPP Everywhere printer or an AirPrint printer. This method does not support PCLm-only printers, but the methods described below do. To create a print queue with the web interface of CUPS (`http://localhost:631/`), look for your printer under the discovered network printers (CUPS does not see that it is USB in reality) and select the entry which contains "driverless". On the page to select the models/PPDs/drivers, also select the entry containing "driverless". Then complete the setup as ususal. The best solution is to let cups-browsed auto-create a print queue when the printer gets connected and remove it when the printer gets turned off or disconnected (do not worry about option settings, cups-browsed saves them). To do so, edit /etc/cups/cups-browsed.conf making sure that there is a line ``` CreateIPPPrinterQueues driverless ``` or ``` CreateIPPPrinterQueues all ``` and no other line beginning with ``` CreateIPPPrinterQueues ``` After editing the file restart cups-browsed with ``` sudo systemctl stop cups-browsed sudo systemctl start cups-browsed ``` Now you have a print queue whenever the printer is available and no print queue cluttering your print dialogs when the printer is not available. ### 2. Expose the printer on the dummy0 interface First, install ippusbxd: ``` sudo cp exe/ippusbxd /usr/sbin ``` Make sure that this file is owned by root and world-readable and -executable. Now install the files to manage the automatic start of ippusbxd: ``` sudo cp systemd-udev/55-ippusbxd.rules /lib/udev/rules.d/ sudo cp systemd-udev/ippusbxd@.service.dummy0 /lib/systemd/system/ippusbxd@.service ``` Make sure that they are owned by root and world-readable. Now create a "dummy0" network interface: ``` sudo modprobe dummy sudo ifconfig dummy0 10.0.0.1 netmask 255.255.255.0 multicast sudo ifconfig dummy0 up multicast ``` You could put these commands into /etc/rc.local to run them automatically at boot. Why not simply use "localhost" with the always available loopback ("lo") interface? We want that our IPP-over-USB printer appears to our system like a network printer, so that CUPS and cups-browsed auto-detect it with the usual methods so that we can easily set up a print queue, even fully automatically, and that we can use CUPS' IPP backend to send print jobs to our printer. If we use "localhost", we can access the printer with the IPP CUPS backend and also access its web administration interface with a web browser, but the printer cannot get auto-discovered by cups-browsed or by CUPS backends like dnssd or driverless, making it awkward to create a print queue for the printer. This is because the loopback interface (which provides "localhost") is not multicast-capable and therefore cannot get DNS-SD-broadcasted by Avahi. Now one could think why not simply use the standard network interface "eth0" or "wlan0"? The problem here is that the printer gets broadcasted and accessible in the whole local network, so we share our USB printer and do not want it. In addition, if our computer is not connected to a network, these interfaces are not available. "dummy0" is always local-only but does multicast and therefore gets DNS-SD-broadcasted by Avahi, and that only on the local machine. So we have the full emulation of a driverless network printer only on our local machine, as we want a USB printer only be available on our local machine. Now we can restart systemd and UDEV to activate all this: ``` sudo systemctl daemon-reload sudo systemctl restart udev ``` If we connect and turn on an IPP-over-USB printer, ippusbxd gets started and makes the printer available under the IPP URI ``` ipp://10.0.0.1:60000/ipp/print ``` and its web administration interface under ``` http://10.0.0.1:60000/ ``` (if you have problems with the Chrome browser, use Firefox). It is also DNS-SD-broadcasted via Avahi on the dummy0 interface. To set up a print queue you could simply run ``` lpadmin -p printer -E -v ipp://10.0.0.1:60000/ipp/print -meverywhere ``` The "-meverywhere" makes CUPS auto-generate the PPD file for the printer, based on an IPP query of the printer's capabilities, independent whether the printer is an IPP Everywhere printer or an AirPrint printer. This method does not support PCLm-only printers, but the methods described below do. To create a print queue with the web interface of CUPS (`http://localhost:631/`), look for your printer under the discovered network printers (CUPS does not see that it is USB in reality) and select the entry which contains "driverless". On the page to select the models/PPDs/drivers, also select the entry containing "driverless". Then complete the setup as ususal. The best solution is to let cups-browsed auto-create a print queue when the printer gets connected and remove it when the printer gets turned off or disconnected (do not worry about option settings, cups-browsed saves them). To do so, edit /etc/cups/cups-browsed.conf making sure that there is a line ``` CreateIPPPrinterQueues driverless ``` or ``` CreateIPPPrinterQueues all ``` and no other line beginning with ``` CreateIPPPrinterQueues ``` After editing the file restart cups-browsed with ``` sudo systemctl stop cups-browsed sudo systemctl start cups-browsed ``` Now you have a print queue whenever the printer is available and no print queue cluttering your print dialogs when the printer is not available. ## Presentation on IPPUSBXD On August 2014 at the Fall Printer Working Group meeting I gave a presentation on ippusbxd and the ipp over usb protocol. Slides from this presentation can be found in the docs folder. ## IPPUSBXD, the name The original name for this project was ippusbd. Part way through development it came to my attention that ippusbd was the name of the ipp over usb implemented used by Mac OSX. This prompted a rename and Ira of the OpenPrinting group and PWG suggested IPPUSBXD. Either all-caps IPPUSBXD or all-lower-case ippusbxd are valid names. ## License Copyright 2014 Daniel Dressler, 2015-2016 Till Kamppeter Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ippusbxd-1.34/src/000077500000000000000000000000001362321405400140665ustar00rootroot00000000000000ippusbxd-1.34/src/CMakeLists.txt000066400000000000000000000031471362321405400166330ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.6) project(ippusbxd) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -o2 -g -std=c99 -Wall -Wextra -pedantic -pedantic-errors") # Compiler specific configuration if (${CMAKE_C_COMPILER_ID} STREQUAL "GNU") # Nothing elseif( ${CMAKE_C_COMPILER_ID} STREQUAL "Clang") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Weverything -Wno-documentation -Wno-padded") # Compiler cannot detect that our logging functions really do use string # literals. So be careful to never-ever call printf with a user provided # format string since you will not get a warning. Coverity is smart enough # on this matter. set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-format-nonliteral") endif() set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}) # Pthreads find_package(Threads REQUIRED) # Avahi find_package(AVAHICOMMON REQUIRED) find_package(AVAHICLIENT REQUIRED) # Libusb find_package(LIBUSB REQUIRED) include_directories(${LIBUSB_INCLUDE_DIR}) # Libxml-2.0 find_package(LibXml2 REQUIRED) set(LIBXML2_LIBRARIES ${LIBXML2_LIBRARY}) include_directories(${LIBXML2_INCLUDE_DIR}) unset(LIBXML2_LIBRARY) find_package(LibXml2 REQUIRED) # Libcups find_package(Cups REQUIRED) add_executable(ippusbxd ippusbxd.c http.c tcp.c usb.c logging.c options.c dnssd.c capabilities.c ) target_link_libraries(ippusbxd ${CMAKE_THREAD_LIBS_INIT}) target_link_libraries(ippusbxd ${LIBUSB_LIBRARIES}) target_link_libraries(ippusbxd ${AVAHICOMMON_LIBRARIES}) target_link_libraries(ippusbxd ${AVAHICLIENT_LIBRARIES}) target_link_libraries(ippusbxd ${LIBXML2_LIBRARIES}) target_link_libraries(ippusbxd ${CUPS_LIBRARIES}) ippusbxd-1.34/src/FindAVAHICLIENT.cmake000066400000000000000000000021331362321405400174170ustar00rootroot00000000000000# Credit to chdromiumos project # - Try to find the freetype library # Once done this defines # # AVAHICLIENT_FOUND - system has libusb # AVAHICLIENT_INCLUDE_DIR - the libusb include directory # AVAHICLIENT_LIBRARIES - Link these to use libusb # Copyright (c) 2006, 2008 Laurent Montel, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. if (AVAHICLIENT_INCLUDE_DIR AND AVAHICLIENT_LIBRARIES) # in cache already set(AVAHICLIENT_FOUND TRUE) else (AVAHICLIENT_INCLUDE_DIR AND AVAHICLIENT_LIBRARIES) FIND_PATH(AVAHICLIENT_INCLUDE_DIR client.h PATHS ${PC_AVAHICLIENT_INCLUDEDIR} ${PC_AVAHICLIENT_INCLUDE_DIRS}) FIND_LIBRARY(AVAHICLIENT_LIBRARIES NAMES avahi-client PATHS ${PC_AVAHICLIENT_LIBDIR} ${PC_AVAHICLIENT_LIBRARY_DIRS}) #include(FindPackageHandleStandardArgs) #FIND_PACKAGE_HANDLE_STANDARD_ARGS(AVAHICLIENT DEFAULT_MSG AVAHICLIENT_LIBRARIES AVAHICLIENT_INCLUDE_DIR) MARK_AS_ADVANCED(AVAHICLIENT_INCLUDE_DIR AVAHICLIENT_LIBRARIES) endif (AVAHICLIENT_INCLUDE_DIR AND AVAHICLIENT_LIBRARIES) ippusbxd-1.34/src/FindAVAHICOMMON.cmake000066400000000000000000000021411362321405400174300ustar00rootroot00000000000000# Credit to chdromiumos project # - Try to find the freetype library # Once done this defines # # AVAHICOMMON_FOUND - system has libusb # AVAHICOMMON_INCLUDE_DIR - the libusb include directory # AVAHICOMMON_LIBRARIES - Link these to use libusb # Copyright (c) 2006, 2008 Laurent Montel, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. if (AVAHICOMMON_INCLUDE_DIR AND AVAHICOMMON_LIBRARIES) # in cache already set(AVAHICOMMON_FOUND TRUE) else (AVAHICOMMON_INCLUDE_DIR AND AVAHICOMMON_LIBRARIES) FIND_PATH(AVAHICOMMON_INCLUDE_DIR thread-watch.h PATHS ${PC_AVAHICOMMON_INCLUDEDIR} ${PC_AVAHICOMMON_INCLUDE_DIRS}) FIND_LIBRARY(AVAHICOMMON_LIBRARIES NAMES avahi-common PATHS ${PC_AVAHICOMMON_LIBDIR} ${PC_AVAHICOMMON_LIBRARY_DIRS}) #include(FindPackageHandleStandardArgs) #FIND_PACKAGE_HANDLE_STANDARD_ARGS(AVAHICOMMON DEFAULT_MSG AVAHICOMMON_LIBRARIES AVAHICOMMON_INCLUDE_DIR) MARK_AS_ADVANCED(AVAHICOMMON_INCLUDE_DIR AVAHICOMMON_LIBRARIES) endif (AVAHICOMMON_INCLUDE_DIR AND AVAHICOMMON_LIBRARIES) ippusbxd-1.34/src/FindCups.cmake000066400000000000000000000037051362321405400166100ustar00rootroot00000000000000# - Try to find the Cups printing system # Once done this will define # # CUPS_FOUND - system has Cups # CUPS_INCLUDE_DIR - the Cups include directory # CUPS_LIBRARIES - Libraries needed to use Cups # Set CUPS_REQUIRE_IPP_DELETE_ATTRIBUTE to TRUE if you need a version which # features this function (i.e. at least 1.1.19) #============================================================================= # Copyright 2006-2009 Kitware, Inc. # Copyright 2006 Alexander Neundorf # # Distributed under the OSI-approved BSD License (the "License"); # see accompanying file Copyright.txt for details. # # This software is distributed WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the License for more information. #============================================================================= # (To distributed this file outside of CMake, substitute the full # License text for the above reference.) INCLUDE(CheckLibraryExists) FIND_PATH(CUPS_INCLUDE_DIR cups/cups.h ) FIND_LIBRARY(CUPS_LIBRARIES NAMES cups ) IF (CUPS_INCLUDE_DIR AND CUPS_LIBRARIES) SET(CUPS_FOUND TRUE) # ippDeleteAttribute is new in cups-1.1.19 (and used by kdeprint) CHECK_LIBRARY_EXISTS(cups ippDeleteAttribute "" CUPS_HAS_IPP_DELETE_ATTRIBUTE) IF (CUPS_REQUIRE_IPP_DELETE_ATTRIBUTE AND NOT CUPS_HAS_IPP_DELETE_ATTRIBUTE) SET(CUPS_FOUND FALSE) ENDIF (CUPS_REQUIRE_IPP_DELETE_ATTRIBUTE AND NOT CUPS_HAS_IPP_DELETE_ATTRIBUTE) ELSE (CUPS_INCLUDE_DIR AND CUPS_LIBRARIES) SET(CUPS_FOUND FALSE) ENDIF (CUPS_INCLUDE_DIR AND CUPS_LIBRARIES) IF (CUPS_FOUND) IF (NOT Cups_FIND_QUIETLY) MESSAGE(STATUS "Found Cups: ${CUPS_LIBRARIES}") ENDIF (NOT Cups_FIND_QUIETLY) ELSE (CUPS_FOUND) SET(CUPS_LIBRARIES ) IF (Cups_FIND_REQUIRED) MESSAGE(FATAL_ERROR "Could NOT find Cups") ENDIF (Cups_FIND_REQUIRED) ENDIF (CUPS_FOUND) MARK_AS_ADVANCED(CUPS_INCLUDE_DIR CUPS_LIBRARIES) ippusbxd-1.34/src/FindLIBUSB.cmake000066400000000000000000000023111362321405400166460ustar00rootroot00000000000000# Credit to chdromiumos project # - Try to find the freetype library # Once done this defines # # LIBUSB_FOUND - system has libusb # LIBUSB_INCLUDE_DIR - the libusb include directory # LIBUSB_LIBRARIES - Link these to use libusb # Copyright (c) 2006, 2008 Laurent Montel, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. if (LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES) # in cache already set(LIBUSB_FOUND TRUE) else (LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES) IF (NOT WIN32) # use pkg-config to get the directories and then use these values # in the FIND_PATH() and FIND_LIBRARY() calls find_package(PkgConfig) pkg_check_modules(PC_LIBUSB libusb-1.0) ENDIF(NOT WIN32) FIND_PATH(LIBUSB_INCLUDE_DIR libusb.h PATHS ${PC_LIBUSB_INCLUDEDIR} ${PC_LIBUSB_INCLUDE_DIRS}) FIND_LIBRARY(LIBUSB_LIBRARIES NAMES usb-1.0 PATHS ${PC_LIBUSB_LIBDIR} ${PC_LIBUSB_LIBRARY_DIRS}) #include(FindPackageHandleStandardArgs) #FIND_PACKAGE_HANDLE_STANDARD_ARGS(LIBUSB DEFAULT_MSG LIBUSB_LIBRARIES LIBUSB_INCLUDE_DIR) MARK_AS_ADVANCED(LIBUSB_INCLUDE_DIR LIBUSB_LIBRARIES) endif (LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES) ippusbxd-1.34/src/FindLibXml2.cmake000066400000000000000000000021001362321405400171330ustar00rootroot00000000000000# - Try to find LibXml2 # Once done this will define # LIBXML2_FOUND - System has LibXml2 # LIBXML2_INCLUDE_DIRS - The LibXml2 include directories # LIBXML2_LIBRARIES - The libraries needed to use LibXml2 # LIBXML2_DEFINITIONS - Compiler switches required for using LibXml2 find_package(PkgConfig) pkg_check_modules(PC_LIBXML QUIET libxml-2.0) set(LIBXML2_DEFINITIONS ${PC_LIBXML_CFLAGS_OTHER}) find_path(LIBXML2_INCLUDE_DIR libxml/xpath.h HINTS ${PC_LIBXML_INCLUDEDIR} ${PC_LIBXML_INCLUDE_DIRS} PATH_SUFFIXES libxml2 ) find_library(LIBXML2_LIBRARY NAMES xml2 libxml2 HINTS ${PC_LIBXML_LIBDIR} ${PC_LIBXML_LIBRARY_DIRS} ) set(LIBXML2_LIBRARIES ${LIBXML2_LIBRARY} ) set(LIBXML2_INCLUDE_DIRS ${LIBXML2_INCLUDE_DIR} ) include(FindPackageHandleStandardArgs) # handle the QUIETLY and REQUIRED arguments and set LIBXML2_FOUND to TRUE # if all listed variables are TRUE find_package_handle_standard_args(LibXml2 DEFAULT_MSG LIBXML2_LIBRARY LIBXML2_INCLUDE_DIR) mark_as_advanced(LIBXML2_INCLUDE_DIR LIBXML2_LIBRARY ) ippusbxd-1.34/src/apparmor/000077500000000000000000000000001362321405400157075ustar00rootroot00000000000000ippusbxd-1.34/src/apparmor/usr.sbin.ippusbxd000066400000000000000000000012401362321405400212270ustar00rootroot00000000000000# vim:syntax=apparmor # Last Modified: Fri Sep 12 15:52:02 2014 # Author: Daniel Dressler #include profile ippusbxd /usr/{bin,sbin}/ippusbxd { #include #include capability wake_alarm, /usr/{bin,sbin}/ippusbxd mr, # Scanning for USB devices /dev/bus/usb/ r, /dev/bus/usb/*/* rw, /etc/udev/udev.conf r, /sys/bus/ r, /sys/bus/usb/devices/ r, /sys/class/ r, /sys/devices/** r, /run/udev/data/** r, # Network access network inet raw, network inet6 raw, network inet stream, network inet6 stream, network netlink raw, network netlink stream, } ippusbxd-1.34/src/capabilities.c000066400000000000000000000377511362321405400167000ustar00rootroot00000000000000#ifndef _DEFAULT_SOURCE # define _DEFAULT_SOURCE #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "capabilities.h" #include "logging.h" struct cap { char *memory; size_t size; }; #define SIZE_DATA 32784 typedef void (*fct_parcours_t)(xmlNodePtr, ippScanner *ippscanner); void parcours_prefixe(xmlNodePtr noeud, fct_parcours_t f, ippScanner *ippscanner); void afficher_noeud(xmlNodePtr noeud, ippScanner *ippscanner); void parcours_prefixe(xmlNodePtr noeud, fct_parcours_t f, ippScanner *ippscanner) { xmlNodePtr n; for (n = noeud; n != NULL; n = n->next) { f(n, ippscanner); if ((n->type == XML_ELEMENT_NODE) && (n->children != NULL)) parcours_prefixe(n->children, f, ippscanner); } } int is_array(const char *name, ippScanner *ippscanner) { if (!strcmp(name, "Platen") || !strcmp(name, "Adf")) { char is[256] = { 0 }; if (!strcmp(name, "Platen")) snprintf(is, sizeof(is), "platen"); else if (!strcmp(name, "Adf")) snprintf(is, sizeof(is), "adf"); if (is[0]) { if (ippscanner->is) { if (!strstr(ippscanner->is, is)) { int len = (strlen(ippscanner->is) + strlen(is) + 2); ippscanner->is = (char *) realloc(ippscanner->is, len); strcat(ippscanner->is, ","); strcat(ippscanner->is, is); } } else ippscanner->is = strdup(is); } return 1; } if (!strcmp(name, "AdfDuplexInputCaps")) { ippscanner->duplex = strdup("T"); return 1; } if (!strcmp(name, "ScannerCapabilities") || !strcmp(name, "SharpenSupport") || !strcmp(name, "SupportedIntents") || !strcmp(name, "CcdChannels") || !strcmp(name, "ColorSpaces") || !strcmp(name, "ColorModes") || !strcmp(name, "DiscreteResolutions") || !strcmp(name, "SupportedResolutions") || !strcmp(name, "DocumentFormats") || !strcmp(name, "ContentTypes") || !strcmp(name, "DiscreteResolution") || !strcmp(name, "CompressionFactorSupport") || !strcmp(name, "SupportedMediaTypes") || !strcmp(name, "SettingProfiles") || !strcmp(name, "SettingProfile") || !strcmp(name, "PlatenInputCaps")) return 1; return 0; } void set_value_escl_scanner(const char *noeud, const char *contenu, ippScanner *ippscanner) { if (!strcmp(noeud, "Version")) { ippscanner->vers = strdup(contenu); } else if (!strcmp(noeud, "MakeAndModel")) { ippscanner->ty = strdup(contenu); } else if (!strcmp(noeud, "UUID")) { ippscanner->uuid = strdup(contenu); } else if (!strcmp(noeud, "AdminURI")) { ippscanner->adminurl = strdup(contenu); } else if (!strcmp(noeud, "IconURI")) { ippscanner->representation = strdup(contenu); } else if (!strcmp(noeud, "DocumentFormat")){ if (ippscanner->pdl) { if (!strstr(ippscanner->pdl, contenu)) { int len = (strlen(ippscanner->pdl) + strlen(contenu) + 2); ippscanner->pdl = (char *) realloc(ippscanner->pdl, len); strcat(ippscanner->pdl, ","); strcat(ippscanner->pdl, contenu); } } else { ippscanner->pdl = strdup(contenu); } } else if (!strcmp(noeud, "ColorMode")){ char modecolor[256] = { 0 }; if (!strcmp(contenu, "Grayscale8")) snprintf(modecolor, sizeof(modecolor), "grayscale"); else if (!strcmp(contenu, "RGB24")) snprintf(modecolor, sizeof(modecolor), "color"); else if (!strcmp(contenu, "BlackAndWhite1")) snprintf(modecolor, sizeof(modecolor), "binary"); if (modecolor[0]) { if (ippscanner->cs) { if (!strstr(ippscanner->cs, modecolor)) { int len = (strlen(ippscanner->cs) + strlen(modecolor) + 2); ippscanner->cs = (char *) realloc(ippscanner->cs, len); strcat(ippscanner->cs, ","); strcat(ippscanner->cs, modecolor); } } else { ippscanner->cs = strdup(modecolor); } } } } void afficher_noeud(xmlNodePtr noeud, ippScanner *ippscanner) { if (is_array((const char*)noeud->name, ippscanner)) return; if (noeud->type == XML_ELEMENT_NODE) { xmlChar *chemin = xmlGetNodePath(noeud); if (noeud->children != NULL && noeud->children->type == XML_TEXT_NODE) { xmlChar *contenu = xmlNodeGetContent(noeud); if (noeud->name != NULL) set_value_escl_scanner((const char*)noeud->name , (const char*)contenu, ippscanner); xmlFree(contenu); } xmlFree(chemin); } } static char *get_format(int x_dim_max, int y_dim_max) { if (x_dim_max == 0 || y_dim_max == 0) { return NULL; } // Now classify by printer size // US name US inches US mm ISO mm // "legal-A4" A, Legal 8.5 x 14 215.9 x 355.6 A4: 210 x 297 // "tabloid-A3" B, Tabloid 11 x 17 279.4 x 431.8 A3: 297 x 420 // "isoC-A2" C 17 × 22 431.8 × 558,8 A2: 420 x 594 // // Please note, Apple in the "Bonjour Printing Specification" // incorrectly states paper sizes as 9x14, 13x19 and 18x24 inches int legal_a4_x = 21590, legal_a4_y = 35560, tabloid_a3_x = 29700, tabloid_a3_y = 43180, isoC_a2_x = 43180, isoC_a2_y = 55880; if (x_dim_max > isoC_a2_x && y_dim_max > isoC_a2_y) return strdup(">isoC-A2"); if (x_dim_max >= isoC_a2_x && y_dim_max >= isoC_a2_y) return strdup("isoC-A2"); if (x_dim_max >= tabloid_a3_x && y_dim_max >= tabloid_a3_y) return strdup("tabloid-A3"); if (x_dim_max >= legal_a4_x && y_dim_max >= legal_a4_y) return strdup("legal-A4"); return strdup("representation = strdup(buffer); else if(!strcasecmp(attr_name, "printer-uuid")) printer->uuid = strdup(buffer + 9); else if(!strcasecmp(attr_name, "printer-more-info")) printer->adminurl = strdup(buffer); else if(!strcasecmp(attr_name, "mopria-certified")) printer->mopria_certified = strdup(buffer); else if(!strcasecmp(attr_name, "printer-kind")) printer->kind = strdup(buffer); else if(!strcasecmp(attr_name, "color-supported")) { if(!strcasecmp(buffer, "true")) printer->color = strdup("T"); else printer->color = strdup("F"); } else if(!strcasecmp(attr_name, "sides-supported")) { if(strcasestr(buffer, "two-")) printer->side = strdup("T"); else if(strcasestr(buffer, "one-")) printer->side = strdup("F"); else printer->side = strdup("U"); } else if(!strcasecmp(attr_name, "printer-location")) printer->note = strdup(buffer); else if(!strcasecmp(attr_name, "printer-make-and-model")) printer->ty = strdup(buffer); else if(!strcasecmp(attr_name, "document-format-supported")) printer->pdl = strdup(buffer); else if(!strcasecmp(attr_name, "urf-supported")) printer->urf = strdup(buffer); else if(!strcasecmp(attr_name, "media-size-supported")) printer->papermax = get_format_paper(buffer); /* next attribute */ attr = ippNextAttribute(response); } if (!strcasecmp(httpGetField(http, HTTP_FIELD_CONNECTION), "close")) { httpClearFields(http); if (httpReconnect2(http, 30000, NULL)) { goto close_http; } } httpClearFields(http); httpSetField(http, HTTP_FIELD_AUTHORIZATION, httpGetAuthString(http)); httpSetField(http, HTTP_FIELD_ACCEPT_LANGUAGE, "en"); if (!httpHead(http, "/ipp/faxout")) { printer->fax = strdup("T"); } close_http: httpClose(http); return 0; } ippPrinter * free_printer(ippPrinter *printer) { if (!printer) return NULL; free(printer->representation); free(printer->uuid); free(printer->adminurl); free(printer->mopria_certified); free(printer->kind); free(printer->papermax); free(printer->urf); free(printer->color); free(printer->note); free(printer->pdl); free(printer->ty); free(printer->side); free(printer->fax); free(printer); return NULL; } static char * http_request(const char *hostname, const char *ressource, int port, int *size_data) { http_t *http = NULL; /* HTTP connection */ http_status_t status = HTTP_STATUS_OK; /* Status of GET command */ char buffer[SIZE_DATA] = { 0 }; /* Input buffer */ long bytes; /* Number of bytes read */ off_t total; /* Total bytes */ const char *encoding; /* Negotiated Content-Encoding */ char *memory = (char*)calloc(1, sizeof (char)); char *tmp = NULL; //////////////////////////////////////////////////////////////////////////////////////////////////////: http = httpConnect2(hostname, port, NULL, AF_UNSPEC, HTTP_ENCRYPTION_IF_REQUESTED, 1, 30000, NULL); if (http == NULL) { perror(hostname); return 0; } NOTE("Checking file \"%s\"...\n", ressource); do { if (!strcasecmp(httpGetField(http, HTTP_FIELD_CONNECTION), "close")) { httpClearFields(http); if (httpReconnect2(http, 30000, NULL)) { status = HTTP_STATUS_ERROR; break; } } httpClearFields(http); httpSetField(http, HTTP_FIELD_AUTHORIZATION, httpGetAuthString(http)); httpSetField(http, HTTP_FIELD_ACCEPT_LANGUAGE, "en"); if (httpHead(http, ressource)) { if (httpReconnect2(http, 30000, NULL)) { status = HTTP_STATUS_ERROR; break; } } while ((status = httpUpdate(http)) == HTTP_STATUS_CONTINUE); if (status == HTTP_STATUS_UNAUTHORIZED) { /* * Flush any error message... */ httpFlush(http); /* * See if we can do authentication... */ if (cupsDoAuthentication(http, "GET", ressource)) { status = HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED; break; } if (httpReconnect2(http, 30000, NULL)) { status = HTTP_STATUS_ERROR; break; } return 0; } } while (status == HTTP_STATUS_UNAUTHORIZED || status == HTTP_STATUS_UPGRADE_REQUIRED); if (status != HTTP_STATUS_OK) NOTE("HEAD failed with status %d...\n", status); encoding = httpGetContentEncoding(http); NOTE("Requesting file \"%s\" (Accept-Encoding: %s)...\n", ressource, encoding ? encoding : "identity"); do { if (!strcasecmp(httpGetField(http, HTTP_FIELD_CONNECTION), "close")) { httpClearFields(http); if (httpReconnect2(http, 30000, NULL)) { status = HTTP_STATUS_ERROR; break; } } httpClearFields(http); httpSetField(http, HTTP_FIELD_AUTHORIZATION, httpGetAuthString(http)); httpSetField(http, HTTP_FIELD_ACCEPT_LANGUAGE, "en"); httpSetField(http, HTTP_FIELD_ACCEPT_ENCODING, encoding); if (httpGet(http, ressource)) { if (httpReconnect2(http, 30000, NULL)) { status = HTTP_STATUS_ERROR; break; } } while ((status = httpUpdate(http)) == HTTP_STATUS_CONTINUE); } while (status == HTTP_STATUS_UNAUTHORIZED || status == HTTP_STATUS_UPGRADE_REQUIRED); if (status != HTTP_STATUS_OK) { NOTE("GET failed with status %d...\n", status); return NULL; } total = 0; while ((bytes = httpRead2(http, buffer, (SIZE_DATA - 1))) > 0) { char *str = realloc(memory, total + bytes + 1); memory = str; memcpy(&(memory[total]), buffer, bytes); total += bytes; memory[total] = 0; memset(buffer, 0, SIZE_DATA); } tmp = (char *)strstr(memory, "'); if (tmp2) { int len = total - strlen(tmp2); tmp[len + 1] = 0; tmp2 = strdup(tmp); free(memory); memory = tmp2; total = strlen(memory); } } *size_data = total; httpClose(http); return memory; } int is_scanner_present(ippScanner *scanner, int port) { xmlDocPtr doc; xmlNodePtr racine; int size = 0; NOTE("is_scanner_present"); if (!scanner) return 0; NOTE("go is_scanner_present"); char *memory = http_request("127.0.0.1", "/eSCL/ScannerCapabilities", port, &size); NOTE("Capabilites[\n%s\n]\n", memory); // Ouverture du fichier XML doc = xmlReadMemory(memory, size, "ScannerCapabilities.xml", NULL, 0); //xmlParseFile("ScannerCapabilities.xml"); if (doc == NULL) { NOTE("Document XML invalide\n"); return 0; } // Récupération de la racine racine = xmlDocGetRootElement(doc); if (racine == NULL) { NOTE("Document XML vierge\n"); xmlFreeDoc(doc); return 0; } // Parcours parcours_prefixe(racine, afficher_noeud, scanner); if (!scanner->duplex) scanner->duplex = strdup("F"); NOTE("txt = [\n\"representation=%s\"\n\"note=\"\n\"UUID=%s\"\n\"adminurl=%s\"\n\"duplex=%s\"\n\"is=%s\"\n\"cs=%s\"\n\"pdl=%s\"\n\"ty=%s\"\n\"rs=eSCL\"\n\"vers=%s\"\n\"txtvers=1\"\n]", scanner->representation, scanner->uuid, scanner->adminurl, scanner->duplex, scanner->is, scanner->cs, scanner->pdl, scanner->ty, scanner->vers); xmlFreeDoc(doc); return 1; } ippScanner * free_scanner (ippScanner *scanner) { if (!scanner) return NULL; free(scanner->representation); free(scanner->uuid); free(scanner->adminurl); free(scanner->duplex); free(scanner->is); free(scanner->cs); free(scanner->pdl); free(scanner->ty); free(scanner->vers); free(scanner); return NULL; } ippusbxd-1.34/src/capabilities.h000066400000000000000000000012331362321405400166670ustar00rootroot00000000000000#ifndef __CAPABILITIES_H__ #define __CAPABILITIES_H__ typedef struct { char *representation; char *uuid; char *adminurl; char *duplex; char *is; char *cs; char *pdl; char *ty; char *vers; } ippScanner; typedef struct { char *representation; char *uuid; char *adminurl; char *mopria_certified; char *kind; char *papermax; char *urf; char *color; char *pdl; char *note; char *ty; char *side; char *fax; } ippPrinter; int is_scanner_present(ippScanner *scanner, int port); ippScanner *free_scanner(ippScanner *scanner); int ipp_request(ippPrinter *printer, int port); ippPrinter *free_printer(ippPrinter *printer); #endif ippusbxd-1.34/src/dnssd.c000066400000000000000000000467001362321405400153540ustar00rootroot00000000000000/* Copyright (C) 2014 Daniel Dressler and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define _GNU_SOURCE #include #include #include #include #include #include "dnssd.h" #include "logging.h" #include "options.h" #include "capabilities.h" /* * 'dnssd_callback()' - Handle DNS-SD registration events generic. */ static void dnssd_callback(AvahiEntryGroup *g, /* I - Service */ AvahiEntryGroupState state) /* I - Registration state */ { switch (state) { case AVAHI_ENTRY_GROUP_ESTABLISHED : /* The entry group has been established successfully */ NOTE("Service entry for the printer successfully established."); break; case AVAHI_ENTRY_GROUP_COLLISION : ERR("DNS-SD service name for this printer already exists"); break; case AVAHI_ENTRY_GROUP_FAILURE : ERR("Entry group failure: %s\n", avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g)))); g_options.terminate = 1; break; case AVAHI_ENTRY_GROUP_UNCOMMITED: case AVAHI_ENTRY_GROUP_REGISTERING: default: break; } } /* * 'dnssd_callback()' - Handle DNS-SD registration events ipp. */ static void dnssd_callback_ipp(AvahiEntryGroup *g, /* I - Service */ AvahiEntryGroupState state, /* I - Registration state */ void *context) /* I - Printer */ { (void)context; if (g == NULL || (g_options.dnssd_data->ipp_ref != NULL && g_options.dnssd_data->ipp_ref != g)) return; dnssd_callback(g, state); } /* * 'dnssd_callback()' - Handle DNS-SD registration events uscan. */ static void dnssd_callback_uscan(AvahiEntryGroup *g, /* I - Service */ AvahiEntryGroupState state, /* I - Registration state */ void *context) /* I - Printer */ { (void)context; if (g == NULL || (g_options.dnssd_data->uscan_ref != NULL && g_options.dnssd_data->uscan_ref != g)) return; dnssd_callback(g, state); } /* * 'dnssd_client_cb()' - Client callback for Avahi. * * Called whenever the client or server state changes... */ static void dnssd_client_cb(AvahiClient *c, /* I - Client */ AvahiClientState state, /* I - Current state */ void *userdata) /* I - User data (unused) */ { (void)userdata; int error; /* Error code, if any */ if (!c) return; switch (state) { default : NOTE("Ignore Avahi state %d.", state); break; case AVAHI_CLIENT_CONNECTING: NOTE("Waiting for Avahi server."); break; case AVAHI_CLIENT_S_RUNNING: NOTE("Avahi server connection got available, registering printer."); dnssd_register(c); break; case AVAHI_CLIENT_S_REGISTERING: case AVAHI_CLIENT_S_COLLISION: NOTE("Dropping printer registration because of possible host name change."); if (g_options.dnssd_data->ipp_ref) avahi_entry_group_reset(g_options.dnssd_data->ipp_ref); if (g_options.dnssd_data->uscan_ref) avahi_entry_group_reset(g_options.dnssd_data->uscan_ref); break; case AVAHI_CLIENT_FAILURE: if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) { NOTE("Avahi server disappeared, unregistering printer"); dnssd_unregister(); /* Renewing client */ if (g_options.dnssd_data->DNSSDClient) avahi_client_free(g_options.dnssd_data->DNSSDClient); if ((g_options.dnssd_data->DNSSDClient = avahi_client_new(avahi_threaded_poll_get (g_options.dnssd_data->DNSSDMaster), AVAHI_CLIENT_NO_FAIL, dnssd_client_cb, NULL, &error)) == NULL) { ERR("Error: Unable to initialize DNS-SD client."); g_options.terminate = 1; } } else { ERR("Avahi server connection failure: %s", avahi_strerror(avahi_client_errno(c))); g_options.terminate = 1; } break; } } int dnssd_init() { int error; /* Error code, if any */ g_options.dnssd_data = calloc(1, sizeof(dnssd_t)); if (g_options.dnssd_data == NULL) { ERR("Unable to allocate memory for DNS-SD broadcast data."); goto fail; } g_options.dnssd_data->DNSSDMaster = NULL; g_options.dnssd_data->DNSSDClient = NULL; g_options.dnssd_data->ipp_ref = NULL; g_options.dnssd_data->uscan_ref = NULL; if ((g_options.dnssd_data->DNSSDMaster = avahi_threaded_poll_new()) == NULL) { ERR("Error: Unable to initialize DNS-SD."); goto fail; } if ((g_options.dnssd_data->DNSSDClient = avahi_client_new( avahi_threaded_poll_get(g_options.dnssd_data->DNSSDMaster), AVAHI_CLIENT_NO_FAIL, dnssd_client_cb, NULL, &error)) == NULL) { ERR("Error: Unable to initialize DNS-SD client."); goto fail; } avahi_threaded_poll_start(g_options.dnssd_data->DNSSDMaster); NOTE("DNS-SD initialized."); return 0; fail: dnssd_shutdown(); return -1; } void dnssd_shutdown() { if (g_options.dnssd_data->DNSSDMaster) { avahi_threaded_poll_stop(g_options.dnssd_data->DNSSDMaster); dnssd_unregister(); } if (g_options.dnssd_data->DNSSDClient) { avahi_client_free(g_options.dnssd_data->DNSSDClient); g_options.dnssd_data->DNSSDClient = NULL; } if (g_options.dnssd_data->DNSSDMaster) { avahi_threaded_poll_free(g_options.dnssd_data->DNSSDMaster); g_options.dnssd_data->DNSSDMaster = NULL; } free(g_options.dnssd_data); NOTE("DNS-SD shut down."); } void * dnssd_escl_register(void *data) { AvahiStringList *uscan_txt; /* DNS-SD USCAN TXT record */ AvahiStringList *ipp_txt; /* DNS-SD USCAN TXT record */ ippScanner *scanner = NULL; int error; char temp[256]; /* Subtype service string */ ipp_txt = (AvahiStringList *)data; ippPrinter *printer = (ippPrinter *)calloc(1, sizeof(ippPrinter)); ipp_request(printer, g_options.real_port); if (printer->adminurl) ipp_txt = avahi_string_list_add_printf(ipp_txt, "adminurl=%s", printer->adminurl); else ipp_txt = avahi_string_list_add_printf(ipp_txt, "adminurl=%s", temp); if (printer->uuid) ipp_txt = avahi_string_list_add_printf(ipp_txt, "UUID=%s", printer->uuid); if (printer->mopria_certified) ipp_txt = avahi_string_list_add_printf(ipp_txt, "mopria-certified=%s", printer->mopria_certified); if (printer->kind) ipp_txt = avahi_string_list_add_printf(ipp_txt, "kind=%s", printer->kind); if (printer->color) ipp_txt = avahi_string_list_add_printf(ipp_txt, "Color=%s", printer->color); if (printer->note) ipp_txt = avahi_string_list_add_printf(ipp_txt, "note=%s", printer->note); else ipp_txt = avahi_string_list_add_printf(ipp_txt, "note="); if (printer->ty) { ipp_txt = avahi_string_list_add_printf(ipp_txt, "ty=%s", printer->ty); ipp_txt = avahi_string_list_add_printf(ipp_txt, "product=(%s)", printer->ty); } if (printer->pdl) ipp_txt = avahi_string_list_add_printf(ipp_txt, "pdl=%s", printer->pdl); if (printer->urf) ipp_txt = avahi_string_list_add_printf(ipp_txt, "URF=%s", printer->urf); if (printer->papermax) ipp_txt = avahi_string_list_add_printf(ipp_txt, "PaperMax=%s", printer->papermax); if (printer->side) ipp_txt = avahi_string_list_add_printf(ipp_txt, "Duplex=%s", printer->side); if (printer->fax) { ipp_txt = avahi_string_list_add_printf(ipp_txt, "Fax=%s", printer->fax); ipp_txt = avahi_string_list_add_printf(ipp_txt, "rfo=ipp/faxout"); } else ipp_txt = avahi_string_list_add_printf(ipp_txt, "Fax=F"); NOTE("Printer TXT[\n\tadminurl=%s\n\tUUID=%s\t\n]\n", printer->adminurl, printer->uuid); /* * Then register the _ipp._tcp (IPP)... */ error = avahi_entry_group_update_service_txt_strlst( g_options.dnssd_data->ipp_ref, (g_options.interface ? (int)if_nametoindex(g_options.interface) : AVAHI_IF_UNSPEC), AVAHI_PROTO_UNSPEC, 0, g_options.dnssd_data->dnssd_name, "_ipp._tcp", NULL, ipp_txt); if (error) { ERR("Error registering %s as IPP printer (_ipp._tcp): %d", g_options.dnssd_data->dnssd_name, error); } avahi_entry_group_commit(g_options.dnssd_data->ipp_ref); avahi_string_list_free(ipp_txt); snprintf(temp, sizeof(temp), "http://127.0.0.1:%d/", g_options.real_port); scanner = (ippScanner*) calloc(1, sizeof(ippScanner)); if (is_scanner_present(scanner, g_options.real_port) == 0 || scanner == NULL) goto noscanner; /* * Create the TXT record for scanner ... */ uscan_txt = NULL; if (scanner->representation) uscan_txt = avahi_string_list_add_printf(uscan_txt, "representation=%s", scanner->representation); else if (printer->representation) uscan_txt = avahi_string_list_add_printf(uscan_txt, "representation=%s", printer->representation); uscan_txt = avahi_string_list_add_printf(uscan_txt, "note="); if (scanner->uuid) uscan_txt = avahi_string_list_add_printf(uscan_txt, "UUID=%s", scanner->uuid); else if (printer->uuid) uscan_txt = avahi_string_list_add_printf(uscan_txt, "UUID=%s", printer->uuid); if (scanner->adminurl) uscan_txt = avahi_string_list_add_printf(uscan_txt, "adminurl=%s", scanner->adminurl); else if (printer->adminurl) uscan_txt = avahi_string_list_add_printf(uscan_txt, "adminurl=%s", printer->adminurl); else uscan_txt = avahi_string_list_add_printf(uscan_txt, "adminurl=%s", temp); uscan_txt = avahi_string_list_add_printf(uscan_txt, "duplex=%s", scanner->duplex); uscan_txt = avahi_string_list_add_printf(uscan_txt, "cs=%s", scanner->cs); uscan_txt = avahi_string_list_add_printf(uscan_txt, "pdl=%s", scanner->pdl); uscan_txt = avahi_string_list_add_printf(uscan_txt, "ty=%s", scanner->ty); uscan_txt = avahi_string_list_add_printf(uscan_txt, "rs=eSCL"); uscan_txt = avahi_string_list_add_printf(uscan_txt, "vers=%s", scanner->vers); uscan_txt = avahi_string_list_add_printf(uscan_txt, "txtvers=1"); /* * Register _uscan._tcp (LPD) with port 0 to reserve the service name... */ NOTE("Registering scanner %s on interface %s for DNS-SD broadcasting ...", scanner->ty, g_options.interface); if (g_options.dnssd_data->uscan_ref == NULL) g_options.dnssd_data->uscan_ref = avahi_entry_group_new(g_options.dnssd_data->DNSSDClient, dnssd_callback_uscan, NULL); if (g_options.dnssd_data->uscan_ref == NULL) { ERR("Could not establish Avahi entry group"); avahi_string_list_free(uscan_txt); scanner = free_scanner(scanner); goto noscanner; } error = avahi_entry_group_add_service_strlst(g_options.dnssd_data->uscan_ref, (g_options.interface ? (int)if_nametoindex(g_options.interface) : AVAHI_IF_UNSPEC), AVAHI_PROTO_UNSPEC, 0, g_options.dnssd_data->dnssd_name, "_uscan._tcp", NULL, NULL, g_options.real_port, uscan_txt); if (error) { ERR("Error registering %s as Unix scanner (_uscan._tcp): %d", scanner->ty, error); scanner = free_scanner(scanner); goto noscanner; }else NOTE("Registered %s as Unix scanner (_uscan._tcp).", scanner->ty); /* * Commit it scanner ... */ avahi_entry_group_commit(g_options.dnssd_data->uscan_ref); avahi_string_list_free(uscan_txt); scanner = free_scanner(scanner); noscanner: printer = free_printer(printer); return 0; } int dnssd_register(AvahiClient *c) { AvahiStringList *ipp_txt; /* DNS-SD IPP TXT record */ char temp[256]; /* Subtype service string */ char dnssd_name[1024]; char *dev_id = NULL; const char *make; /* I - Manufacturer */ const char *model; /* I - Model name */ const char *serial = NULL; const char *cmd; const char *urf = NULL; int pwgraster = 0, appleraster = 0, pclm = 0, pdf = 0, jpeg = 0; char formats[1024]; /* I - Supported formats */ char *ptr; int error; pthread_t thread_escl; /* * Parse the device ID for MFG, MDL, and CMD */ dev_id = strdup(g_options.device_id); NOTE("%s", "======================================="); NOTE("%s", dev_id); NOTE("%s", "======================================="); if ((ptr = strcasestr(dev_id, "MFG:")) == NULL) if ((ptr = strcasestr(dev_id, "MANUFACTURER:")) == NULL) { ERR("No manufacturer info in device ID"); free(dev_id); return -1; } make = strchr(ptr, ':') + 1; if ((ptr = strcasestr(dev_id, "MDL:")) == NULL) if ((ptr = strcasestr(dev_id, "MODEL:")) == NULL) { ERR("No model info in device ID"); free(dev_id); return -1; } model = strchr(ptr, ':') + 1; if ((ptr = strcasestr(dev_id, "SN:")) == NULL) if ((ptr = strcasestr(dev_id, "SERN:")) == NULL) { if ((ptr = strcasestr(dev_id, "SERIALNUMBER:")) == NULL) { NOTE("No serial number info in device ID"); } } if (ptr) serial = strchr(ptr, ':') + 1; if ((ptr = strcasestr(dev_id, "URF:")) == NULL) NOTE("No URF info in device ID"); if (ptr) urf = strchr(ptr, ':') + 1; if ((ptr = strcasestr(dev_id, "CMD:")) == NULL) if ((ptr = strcasestr(dev_id, "COMMAND SET:")) == NULL) { ERR("No page description language info in device ID"); free(dev_id); return -1; } cmd = strchr(ptr, ':') + 1; ptr = strchr(make, ';'); if (ptr) *ptr = '\0'; ptr = strchr(model, ';'); if (ptr) *ptr = '\0'; if (serial) { ptr = strchr(serial, ';'); if (ptr) *ptr = '\0'; } ptr = strchr(cmd, ';'); if (ptr) *ptr = '\0'; if (urf) { ptr = strchr(urf, ';'); if (ptr) *ptr = '\0'; } if ((ptr = strcasestr(cmd, "pwg")) != NULL && (ptr = strcasestr(ptr, "raster")) != NULL) pwgraster = 1; if (((ptr = strcasestr(cmd, "apple")) != NULL && (ptr = strcasestr(ptr, "raster")) != NULL) || ((ptr = strcasestr(cmd, "urf")) != NULL) || urf != NULL) appleraster = 1; if ((ptr = strcasestr(cmd, "pclm")) != NULL) pclm = 1; if ((ptr = strcasestr(cmd, "pdf")) != NULL) pdf = 1; if ((ptr = strcasestr(cmd, "jpeg")) != NULL || (ptr = strcasestr(cmd, "jpg")) != NULL) jpeg = 1; snprintf(formats, sizeof(formats),"%s%s%s%s%s", (pdf ? "application/pdf," : ""), (pwgraster ? "image/pwg-raster," : ""), (appleraster ? "image/urf," : ""), (pclm ? "application/PCLm," : ""), (jpeg ? "image/jpeg," : "")); formats[strlen(formats) - 1] = '\0'; /* * Additional printer properties */ snprintf(temp, sizeof(temp), "http://localhost:%d/", g_options.real_port); if (serial) snprintf(dnssd_name, sizeof(dnssd_name), "%s [%s]", model, serial); else snprintf(dnssd_name, sizeof(dnssd_name), "%s", model); g_options.dnssd_data->dnssd_name = strdup(dnssd_name); /* * Create the TXT record for printer ... */ // UUID=cfe92100-67c4-11d4-a45f-f8d0273ebac3" "note=" "adminurl=http://EPSON3EBAC3.local.:80/PRESENTATION/BONJOUR" ipp_txt = NULL; ipp_txt = avahi_string_list_add_printf(ipp_txt, "rp=ipp/print"); ipp_txt = avahi_string_list_add_printf(ipp_txt, "usb_MFG=%s", make); ipp_txt = avahi_string_list_add_printf(ipp_txt, "usb_MDL=%s", model); ipp_txt = avahi_string_list_add_printf(ipp_txt, "priority=60"); ipp_txt = avahi_string_list_add_printf(ipp_txt, "txtvers=1"); ipp_txt = avahi_string_list_add_printf(ipp_txt, "qtotal=1"); free(dev_id); /* * Register _printer._tcp (LPD) with port 0 to reserve the service name... */ NOTE("Registering printer %s on interface %s for DNS-SD broadcasting ...", dnssd_name, g_options.interface); if (c) g_options.dnssd_data->DNSSDClient = c; if (g_options.dnssd_data->ipp_ref == NULL) g_options.dnssd_data->ipp_ref = avahi_entry_group_new((c ? c : g_options.dnssd_data->DNSSDClient), dnssd_callback_ipp, NULL); if (g_options.dnssd_data->ipp_ref == NULL) { ERR("Could not establish Avahi entry group"); avahi_string_list_free(ipp_txt); return -1; } error = avahi_entry_group_add_service_strlst( g_options.dnssd_data->ipp_ref, (g_options.interface ? (int)if_nametoindex(g_options.interface) : AVAHI_IF_UNSPEC), AVAHI_PROTO_UNSPEC, 0, dnssd_name, "_printer._tcp", NULL, NULL, 0, NULL); if (error) ERR("Error registering %s as Unix printer (_printer._tcp): %d", dnssd_name, error); else NOTE("Registered %s as Unix printer (_printer._tcp).", dnssd_name); /* * Then register the _ipp._tcp (IPP)... */ error = avahi_entry_group_add_service_strlst( g_options.dnssd_data->ipp_ref, (g_options.interface ? (int)if_nametoindex(g_options.interface) : AVAHI_IF_UNSPEC), AVAHI_PROTO_UNSPEC, 0, dnssd_name, "_ipp._tcp", NULL, NULL, g_options.real_port, ipp_txt); if (error) { ERR("Error registering %s as IPP printer (_ipp._tcp): %d", dnssd_name, error); } else { NOTE("Registered %s as IPP printer (_ipp._tcp).", dnssd_name); error = avahi_entry_group_add_service_subtype( g_options.dnssd_data->ipp_ref, (g_options.interface ? (int)if_nametoindex(g_options.interface) : AVAHI_IF_UNSPEC), AVAHI_PROTO_UNSPEC, 0, dnssd_name, "_ipp._tcp", NULL, (appleraster && !pwgraster ? "_universal._sub._ipp._tcp" : "_print._sub._ipp._tcp")); if (error) ERR("Error registering subtype for IPP printer %s (_print._sub._ipp._tcp " "or _universal._sub._ipp._tcp): %d", dnssd_name, error); else NOTE( "Registered subtype for IPP printer %s (_print._sub._ipp._tcp or " "_universal._sub._ipp._tcp).", dnssd_name); } /* * Finally _http.tcp (HTTP) for the web interface... */ error = avahi_entry_group_add_service_strlst( g_options.dnssd_data->ipp_ref, (g_options.interface ? (int)if_nametoindex(g_options.interface) : AVAHI_IF_UNSPEC), AVAHI_PROTO_UNSPEC, 0, dnssd_name, "_http._tcp", NULL, NULL, g_options.real_port, NULL); if (error) { ERR("Error registering web interface of %s (_http._tcp): %d", dnssd_name, error); } else { NOTE("Registered web interface of %s (_http._tcp).", dnssd_name); error = avahi_entry_group_add_service_subtype( g_options.dnssd_data->ipp_ref, (g_options.interface ? (int)if_nametoindex(g_options.interface) : AVAHI_IF_UNSPEC), AVAHI_PROTO_UNSPEC, 0, dnssd_name, "_http._tcp", NULL, "_printer._sub._http._tcp"); if (error) ERR("Error registering subtype for web interface of %s " "(_printer._sub._http._tcp): %d", dnssd_name, error); else NOTE( "Registered subtype for web interface of %s " "(_printer._sub._http._tcp).", dnssd_name); } /* * Commit it printer ... */ // avahi_entry_group_commit(g_options.dnssd_data->ipp_ref); pthread_create (&thread_escl, NULL, dnssd_escl_register, ipp_txt); return 0; } void dnssd_unregister() { if (g_options.dnssd_data->ipp_ref) { avahi_entry_group_free(g_options.dnssd_data->ipp_ref); g_options.dnssd_data->ipp_ref = NULL; } if (g_options.dnssd_data->uscan_ref) { avahi_entry_group_free(g_options.dnssd_data->uscan_ref); g_options.dnssd_data->uscan_ref = NULL; } } ippusbxd-1.34/src/dnssd.h000066400000000000000000000024161362321405400153550ustar00rootroot00000000000000/* Copyright (C) 2014 Daniel Dressler and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include #include #include #include typedef struct dnssd_s { AvahiThreadedPoll *DNSSDMaster; AvahiClient *DNSSDClient; AvahiEntryGroup *ipp_ref; AvahiEntryGroup *uscan_ref; char *dnssd_name; } dnssd_t; /* Initializes DNS-SD broadcasting. Returns 0 on success and a non-zero value if there is a failure. */ int dnssd_init(); /* Shutdown DNS-SD broadcasting. */ void dnssd_shutdown(); /* Register a printer object via DNS-SD. */ int dnssd_register(AvahiClient *c); /* Unregister a printer object from DNS-SD. */ void dnssd_unregister(); ippusbxd-1.34/src/http.c000066400000000000000000000026411362321405400152140ustar00rootroot00000000000000/* Copyright (C) 2014 Daniel Dressler and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include "http.h" #include "logging.h" #define BUFFER_STEP (1 << 12) struct http_packet_t *packet_new() { size_t const capacity = BUFFER_STEP; struct http_packet_t *pkt = calloc(1, sizeof(*pkt)); if (pkt == NULL) { ERR("failed to alloc packet"); return NULL; } uint8_t *buf = calloc(capacity, sizeof(*buf)); if (buf == NULL) { ERR("failed to alloc space for packet's buffer or space for packet"); free(pkt); return NULL; } /* Assemble packet */ pkt->buffer = buf; pkt->buffer_capacity = capacity; pkt->filled_size = 0; return pkt; } void packet_free(struct http_packet_t *pkt) { free(pkt->buffer); free(pkt); } ippusbxd-1.34/src/http.h000066400000000000000000000015171362321405400152220ustar00rootroot00000000000000/* Copyright (C) 2014 Daniel Dressler and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include #include struct http_packet_t { size_t filled_size; size_t buffer_capacity; uint8_t *buffer; }; struct http_packet_t *packet_new(); void packet_free(struct http_packet_t *pkt); ippusbxd-1.34/src/ippusbxd.c000066400000000000000000000672721362321405400161060ustar00rootroot00000000000000/* Copyright (C) 2014 Daniel Dressler and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define _GNU_SOURCE #include "ippusbxd.h" #include #include #include #include #include #include #include #include #include #include #include "dnssd.h" #include "http.h" #include "logging.h" #include "options.h" #include "tcp.h" #include "usb.h" /* Global variables */ static pthread_mutex_t thread_register_mutex; static struct service_thread_param **service_threads = NULL; static uint32_t num_service_threads = 0; static void sigterm_handler(int sig) { /* Flag that we should stop and return... */ g_options.terminate = 1; NOTE("Caught signal %d, shutting down ...", sig); } static void list_service_threads( uint32_t num_service_threads, struct service_thread_param **service_threads) { uint32_t i; char *p; char buf[10240]; size_t written = 0; /* Set all bytes in |buf| to the null terminator so that we don't have to worry about it later. */ memset(buf, '\0', sizeof(buf)); written += snprintf(buf, sizeof(buf), "Threads currently running: "); p = buf + written; if (num_service_threads == 0) { written += snprintf(p, sizeof(buf) - written, "None"); } else { for (i = 0; i < num_service_threads; i++) { written += snprintf(p, sizeof(buf) - written, "#%u", service_threads[i]->thread_num); p = buf + written; if (i + 1 < num_service_threads) { written += snprintf(p, sizeof(buf) - written, ", "); p = buf + written; } } } NOTE("%s", buf); } static int register_service_thread( uint32_t *num_service_threads, struct service_thread_param ***service_threads, struct service_thread_param *new_thread) { NOTE("Registering thread #%u", new_thread->thread_num); (*num_service_threads)++; *service_threads = realloc(*service_threads, *num_service_threads * sizeof(void *)); if (*service_threads == NULL) { ERR("Registering thread #%u: Failed to alloc space for thread registration " "list", new_thread->thread_num); return -1; } (*service_threads)[*num_service_threads - 1] = new_thread; return 0; } static int unregister_service_thread( uint32_t *num_service_threads, struct service_thread_param ***service_threads, uint32_t thread_num) { uint32_t i; NOTE("Unregistering thread #%u", thread_num); /* Search |service_threads| for an element with a matching thread number. */ for (i = 0; i < *num_service_threads; i++) { if ((*service_threads)[i]->thread_num == thread_num) break; } if (i >= *num_service_threads) { ERR("Unregistering thread #%u: Cannot unregister, not found", thread_num); return -1; } (*num_service_threads)--; struct service_thread_param *removed_thread = (*service_threads)[i]; /* Shift the contents after |removed_thread| down. */ for (; i < *num_service_threads; i++) { (*service_threads)[i] = (*service_threads)[i + 1]; } free(removed_thread); *service_threads = realloc(*service_threads, *num_service_threads * sizeof(void *)); if (*num_service_threads == 0) { *service_threads = NULL; } else if (*service_threads == NULL) { ERR("Unregistering thread #%u: Failed to alloc space for thread " "registration list", thread_num); return -1; } return 0; } static void cleanup_handler(void *arg_void) { uint32_t thread_num = *((int *)(arg_void)); NOTE("Thread #%u: Called clean-up handler", thread_num); pthread_mutex_lock(&thread_register_mutex); unregister_service_thread(&num_service_threads, &service_threads, thread_num); list_service_threads(num_service_threads, service_threads); pthread_mutex_unlock(&thread_register_mutex); } static void read_transfer_callback(struct libusb_transfer *transfer) { struct libusb_callback_data *user_data = (struct libusb_callback_data *)transfer->user_data; uint32_t thread_num = user_data->thread_num; pthread_mutex_t *read_inflight_mutex = user_data->read_inflight_mutex; pthread_cond_t *read_inflight_cond = user_data->read_inflight_cond; switch (transfer->status) { case LIBUSB_TRANSFER_COMPLETED: user_data->pkt->filled_size = transfer->actual_length; if (transfer->actual_length) { NOTE("Thread #%u: Pkt from %s (buffer size: %zu)\n===\n%s===", thread_num, "usb", user_data->pkt->filled_size, hexdump(user_data->pkt->buffer, (int)user_data->pkt->filled_size)); tcp_packet_send(user_data->tcp, user_data->pkt); /* Mark the tcp socket as active. */ set_is_active(user_data->tcp, 1); } else { /* Set that we received an empty response from the printer. */ *user_data->empty_response = 1; } break; case LIBUSB_TRANSFER_ERROR: ERR("Thread #%u: There was an error completing the transfer", thread_num); g_options.terminate = 1; break; case LIBUSB_TRANSFER_TIMED_OUT: NOTE( "Thread #%u: The transfer timed out before it could be completed: " "Received %u bytes", thread_num, transfer->actual_length); break; case LIBUSB_TRANSFER_CANCELLED: NOTE("Thread #%u: The transfer was cancelled", thread_num); break; case LIBUSB_TRANSFER_STALL: ERR("Thread #%u: The transfer has stalled", thread_num); g_options.terminate = 1; break; case LIBUSB_TRANSFER_NO_DEVICE: ERR("Thread #%u: The printer was disconnected during the transfer", thread_num); g_options.terminate = 1; break; case LIBUSB_TRANSFER_OVERFLOW: ERR("Thread #%u: The printer sent more data than was requested", thread_num); g_options.terminate = 1; break; default: ERR("Thread #%u: Something unexpected happened", thread_num); g_options.terminate = 1; } /* Free the packet used for the transfer. */ packet_free(user_data->pkt); /* Mark the transfer as completed. */ pthread_mutex_lock(read_inflight_mutex); *user_data->read_inflight = 0; pthread_cond_broadcast(read_inflight_cond); pthread_mutex_unlock(read_inflight_mutex); /* Cleanup the data used for the transfer */ free(user_data); libusb_free_transfer(transfer); } void *service_connection(void *params_void) { struct service_thread_param *params = (struct service_thread_param *)params_void; uint32_t thread_num = params->thread_num; /* Detach this thread so that the main thread does not need to join this thread after termination for clean-up. */ pthread_detach(pthread_self()); /* Register clean-up handler. */ pthread_cleanup_push(cleanup_handler, &thread_num); /* Allow immediate cancelling of this thread. */ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); /* Attempt to establish a connection with the printer. */ if (setup_usb_connection(params->usb_sock, params)) goto cleanup; /* Condition variable used to broadcast updates to the printer thread. */ pthread_cond_t cond; if (pthread_cond_init(&cond, NULL)) goto cleanup; params->cond = &cond; /* Copy the contents of |params| into |printer_params|. The only differences between the two are the |thread_num| and |thread_handle|. */ struct service_thread_param *printer_params = calloc(1, sizeof(*printer_params)); memcpy(printer_params, params, sizeof(*printer_params)); printer_params->thread_num += 1; /* Attempt to start the printer's end of the communication. */ if (setup_communication_thread(&service_printer_connection, printer_params)) goto cleanup; pthread_t printer_params_thread_handle = printer_params->thread_handle; /* This function will run until the socket has been closed. When this function returns it means that the communication has been completed. */ service_socket_connection(params); /* Notify the printer's end that the socket has closed so that it does not have to wait for any pending asynchronous transfers to complete. */ pthread_cond_broadcast(params->cond); /* Wait for the printer thread to exit. */ NOTE("Thread #%u: Waiting for thread #%u to complete", thread_num, thread_num + 1); if (pthread_join(printer_params_thread_handle, NULL)) ERR("Thread #%u: Something went wrong trying to join the printer thread", thread_num); cleanup: if (params->usb_conn != NULL) { NOTE("Thread #%u: interface #%u: releasing usb conn", thread_num, params->usb_conn->interface_index); usb_conn_release(params->usb_conn); params->usb_conn = NULL; } NOTE("Thread #%u: closing, %s", thread_num, g_options.terminate ? "shutdown requested" : "communication thread terminated"); tcp_conn_close(params->tcp); /* Execute clean-up handler. */ pthread_cleanup_pop(1); pthread_exit(NULL); } void service_socket_connection(struct service_thread_param *params) { uint32_t thread_num = params->thread_num; while (is_socket_open(params) && !g_options.terminate) { int result = poll_tcp_socket(params->tcp); if (result < 0 || !is_socket_open(params)) { NOTE("Thread #%u: Client closed connection", thread_num); return; } else if (result == 0) { continue; } struct http_packet_t *pkt = tcp_packet_get(params->tcp); if (pkt == NULL) { NOTE("Thread #%u: There was an error reading from the socket", thread_num); return; } if (!is_socket_open(params)) { NOTE("Thread #%u: Client closed connection", thread_num); return; } NOTE("Thread #%u: Pkt from tcp (buffer size: %zu)\n===\n%s===", thread_num, pkt->filled_size, hexdump(pkt->buffer, (int)pkt->filled_size)); /* Send pkt to printer. */ usb_conn_packet_send(params->usb_conn, pkt); packet_free(pkt); } } void *service_printer_connection(void *params_void) { struct service_thread_param *params = (struct service_thread_param *)params_void; uint32_t thread_num = params->thread_num; /* Register clean-up handler. */ pthread_cleanup_push(cleanup_handler, &thread_num); /* Amount of time to wait in milliseconds before sending another read request if we received a 0-byte response from the printer. */ int backoff = initial_backoff; int read_inflight = 0; int empty_response = 0; pthread_mutex_t read_inflight_mutex; if (pthread_mutex_init(&read_inflight_mutex, NULL)) goto cleanup; struct libusb_transfer *read_transfer = NULL; while (is_socket_open(params) && !g_options.terminate) { /* If there is already a read from the printer underway, block until it has completed. */ pthread_mutex_lock(&read_inflight_mutex); while (is_socket_open(params) && read_inflight) pthread_cond_wait(params->cond, &read_inflight_mutex); pthread_mutex_unlock(&read_inflight_mutex); /* After waking up due to a completed transfer, verify that the socket is still open and that the termination flag has not been set before attempting to start another transfer. */ if (!is_socket_open(params) || g_options.terminate) break; /* If we received an empty response from the printer then wait for |backoff| milliseconds and update the backoff period. */ if (empty_response) { /* usleep accepts microseconds. */ usleep(backoff * 1000); backoff = update_backoff(backoff); /* Reset the empty response indicator before sending the next read request. A mutex should not be needed here since the transfer callback won't be fired until after calling libusb_submit_transfer(). */ empty_response = 0; } else { /* If we received a non-empty response from the printer then reset the backoff to its initial value. */ backoff = initial_backoff; } NOTE("Thread #%u: No read in flight, starting a new one", thread_num); struct http_packet_t *pkt = packet_new(); if (pkt == NULL) { ERR("Thread #%u: Failed to allocate packet", thread_num); break; } struct libusb_callback_data *user_data = setup_libusb_callback_data( pkt, &read_inflight, &empty_response, params, &read_inflight_mutex); if (user_data == NULL) { ERR("Thread #%u: Failed to allocate memory for libusb_callback_data", thread_num); break; } read_transfer = setup_async_read( params->usb_conn, pkt, read_transfer_callback, (void *)user_data, 5000); if (read_transfer == NULL) { ERR("Thread #%u: Failed to allocate memory for libusb transfer", thread_num); break; } /* Mark that there is a new read in flight. A mutex should not be needed here since the transfer callback won't be fired until after calling libusb_submit_transfer() */ read_inflight = 1; if (libusb_submit_transfer(read_transfer)) { ERR("Thread #%u: Failed to submit asynchronous USB transfer", thread_num); set_read_inflight(0, &read_inflight_mutex, &read_inflight); break; } } /* If the socket used for communication has closed and there is still a transfer from the printer in flight then we attempt to cancel it. */ if (get_read_inflight(&read_inflight, &read_inflight_mutex)) { NOTE( "Thread #%u: There was a read in flight when the connection was " "closed, cancelling transfer", thread_num); int cancel_status = libusb_cancel_transfer(read_transfer); if (!cancel_status) { /* Wait until the cancellation has completed. */ NOTE("Thread #%u: Waiting until the transfer has been cancelled", thread_num); pthread_mutex_lock(&read_inflight_mutex); while (read_inflight) pthread_cond_wait(params->cond, &read_inflight_mutex); pthread_mutex_unlock(&read_inflight_mutex); } else if (cancel_status == LIBUSB_ERROR_NOT_FOUND) { NOTE("Thread #%u: The transfer has already completed", thread_num); } else { NOTE("Thread #%u: Failed to cancel transfer"); g_options.terminate = 1; } } pthread_mutex_destroy(&read_inflight_mutex); cleanup: /* Execute clean-up handler. */ pthread_cleanup_pop(1); pthread_exit(NULL); } static uint16_t open_tcp_socket(void) { uint16_t desired_port = g_options.desired_port; g_options.tcp_socket = NULL; g_options.tcp6_socket = NULL; for (;;) { g_options.tcp_socket = tcp_open(desired_port, g_options.interface); g_options.tcp6_socket = tcp6_open(desired_port, g_options.interface); if (g_options.tcp_socket || g_options.tcp6_socket || g_options.only_desired_port) break; /* Search for a free port. */ desired_port ++; /* We failed with 0 as port number or we reached the max port number. */ if (desired_port == 1 || desired_port == 0) /* IANA recommendation of 49152 to 65535 for ephemeral ports. */ desired_port = 49152; NOTE("Access to desired port failed, trying alternative port %d", desired_port); } return desired_port; } int allocate_socket_connection(struct service_thread_param *param) { param->tcp = calloc(1, sizeof(*param->tcp)); if (param->tcp == NULL) { ERR("Preparing thread #%u: Failed to allocate space for cups connection", param->thread_num); return -1; } return 0; } int setup_socket_connection(struct service_thread_param *param) { param->tcp = tcp_conn_select(g_options.tcp_socket, g_options.tcp6_socket); if (g_options.terminate || param->tcp == NULL) return -1; return 0; } int setup_usb_connection(struct usb_sock_t *usb_sock, struct service_thread_param *param) { param->usb_conn = usb_conn_acquire(usb_sock); if (param->usb_conn == NULL) { ERR("Thread #%u: Failed to acquire usb interface", param->thread_num); return -1; } return 0; } int setup_communication_thread(void *(*routine)(void *), struct service_thread_param *param) { pthread_mutex_lock(&thread_register_mutex); register_service_thread(&num_service_threads, &service_threads, param); list_service_threads(num_service_threads, service_threads); pthread_mutex_unlock(&thread_register_mutex); int status = pthread_create(¶m->thread_handle, NULL, routine, param); if (status) { ERR("Creating thread #%u: Failed to spawn thread, error %d", param->thread_num, status); pthread_mutex_lock(&thread_register_mutex); unregister_service_thread(&num_service_threads, &service_threads, param->thread_num); list_service_threads(num_service_threads, service_threads); pthread_mutex_unlock(&thread_register_mutex); return -1; } return 0; } struct libusb_callback_data *setup_libusb_callback_data( struct http_packet_t *pkt, int *read_inflight, int *empty_response, struct service_thread_param *thread_param, pthread_mutex_t *read_inflight_mutex) { struct libusb_callback_data *data = calloc(1, sizeof(*data)); if (data == NULL) return NULL; data->pkt = pkt; data->read_inflight = read_inflight; data->empty_response = empty_response; data->thread_num = thread_param->thread_num; data->read_inflight_mutex = read_inflight_mutex; data->read_inflight_cond = thread_param->cond; data->tcp = thread_param->tcp; return data; } int get_read_inflight(const int *read_inflight, pthread_mutex_t *mtx) { pthread_mutex_lock(mtx); int val = *read_inflight; pthread_mutex_unlock(mtx); return val; } void set_read_inflight(int val, pthread_mutex_t *mtx, int *read_inflight) { pthread_mutex_lock(mtx); *read_inflight = val; pthread_mutex_unlock(mtx); } int is_socket_open(const struct service_thread_param *param) { return !param->tcp->is_closed; } int update_backoff(int backoff) { int updated = backoff * 2; /* Cap the maximum backoff time at 1 second. */ if (updated > maximum_backoff) { updated = maximum_backoff; } return updated; } static void start_daemon() { /* Capture USB device. */ struct usb_sock_t *usb_sock; /* Termination flag */ g_options.terminate = 0; usb_sock = usb_open(); if (usb_sock == NULL) goto cleanup_usb; /* Capture a socket */ uint16_t desired_port = open_tcp_socket(); if (g_options.tcp_socket == NULL && g_options.tcp6_socket == NULL) goto cleanup_tcp; if (g_options.tcp_socket) g_options.real_port = tcp_port_number_get(g_options.tcp_socket); else g_options.real_port = tcp_port_number_get(g_options.tcp6_socket); if (desired_port != 0 && g_options.only_desired_port == 1 && desired_port != g_options.real_port) { ERR("Received port number did not match requested port number." " The requested port number may be too high."); goto cleanup_tcp; } printf("%u|", g_options.real_port); fflush(stdout); NOTE("Port: %d, IPv4 %savailable, IPv6 %savailable", g_options.real_port, g_options.tcp_socket ? "" : "not ", g_options.tcp6_socket ? "" : "not "); /* Lose connection to caller */ uint16_t pid; if (!g_options.nofork_mode && (pid = fork()) > 0) { printf("%u|", pid); exit(0); } /* Redirect SIGINT and SIGTERM so that we do a proper shutdown, unregistering the printer from DNS-SD */ #ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */ sigset(SIGTERM, sigterm_handler); sigset(SIGINT, sigterm_handler); NOTE("Using signal handler SIGSET"); #elif defined(HAVE_SIGACTION) struct sigaction action; /* Actions for POSIX signals */ memset(&action, 0, sizeof(action)); sigemptyset(&action.sa_mask); sigaddset(&action.sa_mask, SIGTERM); action.sa_handler = sigterm_handler; sigaction(SIGTERM, &action, NULL); sigemptyset(&action.sa_mask); sigaddset(&action.sa_mask, SIGINT); action.sa_handler = sigterm_handler; sigaction(SIGINT, &action, NULL); NOTE("Using signal handler SIGACTION"); #else signal(SIGTERM, sigterm_handler); signal(SIGINT, sigterm_handler); NOTE("Using signal handler SIGNAL"); #endif /* HAVE_SIGSET */ /* Register for unplug event */ if (usb_can_callback(usb_sock)) usb_register_callback(usb_sock); /* DNS-SD-broadcast the printer on the local machine so that cups-browsed and ippfind will discover it */ if (g_options.nobroadcast == 0) { if (dnssd_init() == -1) goto cleanup_tcp; } /* Main loop */ uint32_t i = 1; pthread_mutex_init(&thread_register_mutex, NULL); while (!g_options.terminate) { struct service_thread_param *args = calloc(1, sizeof(*args)); if (args == NULL) { ERR("Preparing thread #%u: Failed to alloc space for thread args", i); goto cleanup_thread; } args->thread_num = i; args->usb_sock = usb_sock; /* Allocate space for a tcp socket to be used for communication. */ if (allocate_socket_connection(args)) goto cleanup_thread; /* Attempt to establish a connection to the relevant socket. */ if (setup_socket_connection(args)) goto cleanup_thread; /* Attempt to start up a new thread to handle the socket's end of communication. */ if (setup_communication_thread(&service_connection, args)) goto cleanup_thread; i += 2; continue; cleanup_thread: if (args != NULL) { if (args->tcp != NULL) tcp_conn_close(args->tcp); free(args); } break; } cleanup_tcp: /* Stop DNS-SD advertising of the printer */ if (g_options.dnssd_data != NULL) dnssd_shutdown(); /* Cancel communication threads which did not terminate by themselves when stopping ippusbxd, so that no USB communication with the printer can happen after the final reset */ while (num_service_threads) { NOTE("Thread #%u did not terminate, canceling it now ...", service_threads[0]->thread_num); i = num_service_threads; pthread_cancel(service_threads[0]->thread_handle); while (i == num_service_threads) usleep(1000000); } /* Wait for USB unplug event observer thread to terminate */ NOTE("Shutting down usb observer thread"); pthread_join(g_options.usb_event_thread_handle, NULL); /* TCP clean-up */ if (g_options.tcp_socket!= NULL) tcp_close(g_options.tcp_socket); if (g_options.tcp6_socket!= NULL) tcp_close(g_options.tcp6_socket); cleanup_usb: /* USB clean-up and final reset of the printer */ if (usb_sock != NULL) usb_close(usb_sock); return; } static uint16_t strto16hex(const char *str) { unsigned long val = strtoul(str, NULL, 16); if (val > UINT16_MAX) exit(1); return (uint16_t)val; } static uint16_t strto16dec(const char *str) { unsigned long val = strtoul(str, NULL, 10); if (val > UINT16_MAX) exit(1); return (uint16_t)val; } int main(int argc, char *argv[]) { int c; int option_index = 0; static struct option long_options[] = { {"vid", required_argument, 0, 'v' }, {"pid", required_argument, 0, 'm' }, {"serial", required_argument, 0, 's' }, {"bus", required_argument, 0, 'b' }, {"device", required_argument, 0, 'D' }, {"bus-device", required_argument, 0, 'X' }, {"from-port", required_argument, 0, 'P' }, {"only-port", required_argument, 0, 'p' }, {"interface", required_argument, 0, 'i' }, {"logging", no_argument, 0, 'l' }, {"debug", no_argument, 0, 'd' }, {"verbose", no_argument, 0, 'q' }, {"no-fork", no_argument, 0, 'n' }, {"no-broadcast", no_argument, 0, 'B' }, {"help", no_argument, 0, 'h' }, {NULL, 0, 0, 0 } }; g_options.log_destination = LOGGING_STDERR; g_options.only_desired_port = 0; g_options.desired_port = 60000; g_options.interface = "lo"; g_options.serial_num = NULL; g_options.vendor_id = 0; g_options.product_id = 0; g_options.bus = 0; g_options.device = 0; while ((c = getopt_long(argc, argv, "qnhdp:P:i:s:lv:m:B", long_options, &option_index)) != -1) { switch (c) { case '?': case 'h': g_options.help_mode = 1; break; case 'p': case 'P': { long long port = 0; /* Request specific port */ port = atoi(optarg); if (port < 0) { ERR("Port number must be non-negative"); return 1; } if (port > UINT16_MAX) { ERR("Port number must be %u or less, " "but not negative", UINT16_MAX); return 2; } g_options.desired_port = (uint16_t)port; if (c == 'p') g_options.only_desired_port = 1; else g_options.only_desired_port = 0; break; } case 'i': /* Request a specific network interface */ g_options.interface = strdup(optarg); break; case 'l': g_options.log_destination = LOGGING_SYSLOG; break; case 'd': g_options.nofork_mode = 1; g_options.verbose_mode = 1; break; case 'q': g_options.verbose_mode = 1; break; case 'n': g_options.nofork_mode = 1; break; case 'v': g_options.vendor_id = strto16hex(optarg); break; case 'm': g_options.product_id = strto16hex(optarg); break; case 'b': g_options.bus = strto16dec(optarg); break; case 'D': g_options.device = strto16dec(optarg); break; case 'X': { char *p = strchr(optarg, ':'); if (p == NULL) { ERR("Bus and device must be given in the format :"); return 3; } p ++; g_options.bus = strto16dec(optarg); g_options.device = strto16dec(p); break; } case 's': g_options.serial_num = (unsigned char *)optarg; break; case 'B': g_options.nobroadcast = 1; break; } } if (g_options.help_mode) { printf("Usage: %s -v -m -s -P \n" " %s --bus --device -P \n" " %s -h\n" "Options:\n" " --help\n" " -h Show this help message\n" " --vid \n" " -v Vendor ID of desired printer (as hexadecimal number)\n" " --pid \n" " -m Product ID of desired printer (as hexadecimal number)\n" " --serial \n" " -s Serial number of desired printer\n" " --bus \n" " --device \n" " --bus-device :\n" " USB bus and device numbers where the device is currently\n" " connected (see output of lsusb). Note that these numbers change\n" " when the device is disconnected and reconnected. This method of\n" " calling ippusbxd is only for calling via UDEV. and\n" " have to be given in decimal numbers.\n" " --only-port \n" " -p Port number to bind against, error out if port already taken\n" " --from-port \n" " -P Port number to bind against, use another port if port already\n" " taken\n" " --interface \n" " -i Network interface to use. Default is the loopback interface\n" " (lo, localhost).\n" " --logging\n" " -l Redirect logging to syslog\n" " --verbose\n" " -q Enable verbose tracing\n" " --debug\n" " -d Debug mode for verbose output and no fork\n" " --no-fork\n" " -n No-fork mode\n" " --no-broadcast\n" " -B No-broadcast mode, do not DNS-SD-broadcast\n" , argv[0], argv[0], argv[0]); return 0; } start_daemon(); NOTE("ippusbxd completed successfully"); return 0; } ippusbxd-1.34/src/ippusbxd.h000066400000000000000000000112601362321405400160750ustar00rootroot00000000000000#pragma once #include #include #include #include "tcp.h" #include "usb.h" struct service_thread_param { /* Connection to the device issuing requests to the printer. */ struct tcp_conn_t *tcp; /* Socket which holds the context for the bound USB printer. */ struct usb_sock_t *usb_sock; /* Represents a connection to a specific USB interface. */ struct usb_conn_t *usb_conn; pthread_t thread_handle; uint32_t thread_num; pthread_cond_t *cond; }; struct libusb_callback_data { /* Represents whether there is currently an active attempt to read from the printer. */ int *read_inflight; /* * Indicates that the previous read response was empty. This is used to * perform exponential backoff in service_printer_connection() to avoid * overloading the printer with read requests when there is nothing to read. */ int *empty_response; uint32_t thread_num; struct tcp_conn_t *tcp; /* The contents of the response from the printer. */ struct http_packet_t *pkt; pthread_mutex_t *read_inflight_mutex; pthread_cond_t *read_inflight_cond; }; /* Constants */ /* Times to wait in milliseconds before sending another read request to the printer. */ const int initial_backoff = 100; const int maximum_backoff = 1000; /* Function prototypes */ /* Handles connection requests and is run in a separate thread. It detaches itself from the main thread and sets up a USB connection with the printer. This function spawns a partner thread which is responsible for reading from the printer, and then this function calls into service_socket_connection() which is responsible for reading from the socket which made the connection request. Once the socket has closed its end of communiction, this function notifies its partner thread that the connection has been closed and then joins on the partner thread before shutting down. */ void *service_connection(void *params_void); /* Reads from the connected socket in |params| and writes any received messages to the printer. */ void service_socket_connection(struct service_thread_param *params); /* Reads from messages from the printer and writes any responses to the connected socket in |params_void|. */ void *service_printer_connection(void *params_void); /* Attempts to allocate space for a tcp socket. If the allocation is successful then a value of 0 is returned, otherwise a non-zero value is returned. */ int allocate_socket_connection(struct service_thread_param *param); /* Attempts to setup a connection for to a tcp socket. Returns a 0 value on success and a non-zero value if something went wrong attempting to establish the connection. */ int setup_socket_connection(struct service_thread_param *param); /* Attempts to create a new usb_conn_t and assign it to |param| by acquiring an available usb interface. Returns 0 if the creation of the connection struct was successful, and non-zero if there was an error attempting to acquire the interface. */ int setup_usb_connection(struct usb_sock_t *usb_sock, struct service_thread_param *param); /* Attempts to register a new communication thread to execute the function |routine| with the given |params|. Returns 0 if successful, and a non-zero value otherwise. */ int setup_communication_thread(void *(*routine)(void *), struct service_thread_param *param); /* Creates a new libusb_callback_data struct with the given paramaters. */ struct libusb_callback_data *setup_libusb_callback_data( struct http_packet_t *pkt, int *read_inflight, int *empty_response, struct service_thread_param *thread_param, pthread_mutex_t *read_inflight_mutex); /* Returns the value of |read_inflight|. The given |read_inflight_mutex| is used to lock changes to |read_inflight| as another thread performing the asynchronous transfer with the printer may change the value upon completion. */ int get_read_inflight(const int *read_inflight, pthread_mutex_t *read_inflight_mutex); /* Sets the value of |read_inflight| to |val|. Uses |mtx| as another thread which is processing the asynchronous transfer may change the value once the transfer is complete. */ void set_read_inflight(int val, pthread_mutex_t *mtx, int *read_inflight); /* Returns a non-zero value if the communication socket in |param| is currently open for communication. */ int is_socket_open(const struct service_thread_param *param); /* Accepts the given |backoff| value used to determine the time to wait between unsuccessful asynchronous printer transfers and returns the updated value. The updated value will not exceed |maximum_backoff|. */ int update_backoff(int backoff); ippusbxd-1.34/src/logging.c000066400000000000000000000045731362321405400156710ustar00rootroot00000000000000/* Copyright (C) 2014 Daniel Dressler and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include "logging.h" #include "options.h" void BASE_LOG(enum log_level level, const char *fmt, ...) { char buf[65536]; if (!g_options.verbose_mode && level != LOGGING_ERROR) return; va_list arg; va_start(arg, fmt); if (g_options.log_destination == LOGGING_STDERR) vfprintf(stderr, fmt, arg); else if (g_options.log_destination == LOGGING_SYSLOG) { vsnprintf(buf, sizeof(buf), fmt, arg); syslog(LOG_ERR, "%s", buf); } va_end(arg); } char* hexdump (void *addr, int len) { int i; char linebuf[17]; char *pc = (char*)addr; char *outbuf, *outbufp; outbuf = calloc((len / 16 + 1) * 80, sizeof(char)); if (outbuf == NULL) return "*** Failed to allocate memory for hex dump! ***"; outbufp = outbuf; /* Process every byte in the data. */ for (i = 0; i < len; i++) { if ((i % 16) == 0) { /* Multiple of 16 means new line (with line offset). */ if (i != 0) { /* Just don't print ASCII for the zeroth line. */ sprintf (outbufp, " %s\n", linebuf); outbufp += strlen(linebuf) + 3; } /* Output the offset. */ sprintf (outbufp, " %08x ", i); outbufp += 11; } /* Now the hex code for the specific character. */ sprintf (outbufp, " %02x", pc[i]); outbufp += 3; /* And store a printable ASCII character for later. */ if ((pc[i] < 0x20) || (pc[i] > 0x7e)) linebuf[i % 16] = '.'; else linebuf[i % 16] = pc[i]; linebuf[(i % 16) + 1] = '\0'; } /* Pad out last line if not exactly 16 characters. */ while ((i % 16) != 0) { sprintf (outbufp, " "); outbufp += 3; i++; } /* And print the final ASCII bit. */ sprintf (outbufp, " %s\n", linebuf); return outbuf; } ippusbxd-1.34/src/logging.h000066400000000000000000000044711362321405400156730ustar00rootroot00000000000000/* Copyright (C) 2014 Daniel Dressler and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include /* For pthread_self() */ #include "options.h" #include "dnssd.h" #define TID() (pthread_self()) enum log_level { LOGGING_ERROR, LOGGING_WARNING, LOGGING_NOTICE, LOGGING_CONFORMANCE, }; #define PP_CAT(x, y) PP_CAT_2(x, y) #define PP_CAT_2(x, y) x##y # define LOG_ARITY(...) LOG_ARITY_2(__VA_ARGS__, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 0) # define LOG_ARITY_2(_15, _14, _13, _12, _11, _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, _0, ...) _0 #define LOG_OVERLOAD(Name, ...) PP_CAT(Name, LOG_ARITY(__VA_ARGS__))(__VA_ARGS__) #define ERR(...) LOG_OVERLOAD(ERR_, __VA_ARGS__) #define ERR_1(msg) BASE_LOG(LOGGING_ERROR, "<%d>Error: " msg "\n", TID()) #define ERR_2(msg, ...) BASE_LOG(LOGGING_ERROR, "<%d>Error: " msg "\n", TID(), __VA_ARGS__) #define WARN(...) LOG_OVERLOAD(WARN_, __VA_ARGS__) #define WARN_1(msg) BASE_LOG(LOGGING_WARNING, "<%d>Warning: " msg "\n", TID()) #define WARN_2(msg, ...) BASE_LOG(LOGGING_WARNING, "<%d>Warning: " msg "\n", TID(), __VA_ARGS__) #define NOTE(...) LOG_OVERLOAD(NOTE_, __VA_ARGS__) #define NOTE_1(msg) BASE_LOG(LOGGING_NOTICE, "<%d>Note: " msg "\n", TID()) #define NOTE_2(msg, ...) BASE_LOG(LOGGING_NOTICE, "<%d>Note: " msg "\n", TID(), __VA_ARGS__) #define CONF(...) LOG_OVERLOAD(CONF_, __VA_ARGS__) #define CONF_1(msg) BASE_LOG(LOGGING_CONFORMANCE, "<%d>Standard Conformance Failure: " msg "\n", TID()) #define CONF_2(msg, ...) BASE_LOG(LOGGING_CONFORMANCE, "<%d>Standard Conformance Failure: " msg "\n", TID(), __VA_ARGS__) #define ERR_AND_EXIT(...) do { ERR(__VA_ARGS__); if (g_options.dnssd_data != NULL) dnssd_shutdown(g_options.dnssd_data); exit(-1);} while (0) void BASE_LOG(enum log_level, const char *, ...); char* hexdump (void *addr, int len); ippusbxd-1.34/src/options.c000066400000000000000000000012301362321405400157210ustar00rootroot00000000000000/* Copyright (C) 2014 Daniel Dressler and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "options.h" struct options g_options; ippusbxd-1.34/src/options.h000066400000000000000000000025121362321405400157320ustar00rootroot00000000000000/* Copyright (C) 2014 Daniel Dressler and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include #include #include "dnssd.h" enum log_target { LOGGING_STDERR, LOGGING_SYSLOG }; struct options { /* Runtime configuration */ uint16_t desired_port; int only_desired_port; uint16_t real_port; char *interface; enum log_target log_destination; /* Behavior */ int help_mode; int verbose_mode; int nofork_mode; int nobroadcast; /* Printer identity */ unsigned char *serial_num; int vendor_id; int product_id; int bus; int device; char *device_id; /* Global variables */ int terminate; dnssd_t *dnssd_data; pthread_t usb_event_thread_handle; struct tcp_sock_t *tcp_socket; struct tcp_sock_t *tcp6_socket; }; extern struct options g_options; ippusbxd-1.34/src/tcp.c000066400000000000000000000250221362321405400150210ustar00rootroot00000000000000/* Copyright (C) 2014 Daniel Dressler and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "http.h" #include "logging.h" #include "options.h" #include "tcp.h" struct tcp_sock_t *tcp_open(uint16_t port, char* interface) { struct tcp_sock_t *this = calloc(1, sizeof *this); if (this == NULL) { ERR("IPv4: callocing this failed"); goto error; } /* Open [S]ocket [D]escriptor */ this->sd = -1; this->sd = socket(AF_INET, SOCK_STREAM, 0); if (this->sd < 0) { ERR("IPv4 socket open failed"); goto error; } /* Set SO_REUSEADDR option to allow for a clean host/port unbinding even with pending requests on shutdown of ippusbxd. Otherwise the port will stay unavailable for a certain kernel-defined timeout. See also http://stackoverflow.com/questions/10619952/how-to-completely-destroy-a-socket-connection-in-c */ int true = 1; if (setsockopt(this->sd, SOL_SOCKET, SO_REUSEADDR, &true, sizeof(int)) == -1) { ERR("IPv4 setting socket options failed"); goto error; } /* Find the IP address for the selected interface */ struct ifaddrs *ifaddr, *ifa; getifaddrs(&ifaddr); for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { if (ifa->ifa_addr == NULL) continue; if ((strcmp(ifa->ifa_name, interface) == 0) && (ifa->ifa_addr->sa_family == AF_INET)) break; } if (ifa == NULL) { ERR("Interface %s does not exist or IPv4 IP not found.", interface); goto error; } /* Configure socket params */ struct sockaddr_in addr, *if_addr; if_addr = (struct sockaddr_in *) ifa->ifa_addr; memset(&addr, 0, sizeof addr); addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = if_addr->sin_addr.s_addr; /* addr.sin_addr.s_addr = htonl(0xC0A8000F); */ NOTE("IPv4: Binding to %s:%d", inet_ntoa(if_addr->sin_addr), port); /* Bind to the interface/IP/port */ if (bind(this->sd, (struct sockaddr *)&addr, sizeof addr) < 0) { if (g_options.only_desired_port == 1) ERR("IPv4 bind on port failed. " "Requested port may be taken or require root permissions."); goto error; } /* Let kernel over-accept max number of connections */ if (listen(this->sd, HTTP_MAX_PENDING_CONNS) < 0) { ERR("IPv4 listen failed on socket"); goto error; } return this; error: if (this != NULL) { if (this->sd != -1) { close(this->sd); } free(this); } return NULL; } struct tcp_sock_t *tcp6_open(uint16_t port, char* interface) { struct tcp_sock_t *this = calloc(1, sizeof *this); if (this == NULL) { ERR("IPv6: callocing this failed"); goto error; } /* Open [S]ocket [D]escriptor */ this->sd = -1; this->sd = socket(AF_INET6, SOCK_STREAM, 0); if (this->sd < 0) { ERR("Ipv6 socket open failed"); goto error; } /* Set SO_REUSEADDR option to allow for a clean host/port unbinding even with pending requests on shutdown of ippusbxd. Otherwise the port will stay unavailable for a certain kernel-defined timeout. See also http://stackoverflow.com/questions/10619952/how-to-completely-destroy-a-socket-connection-in-c */ int true = 1; if (setsockopt(this->sd, SOL_SOCKET, SO_REUSEADDR, &true, sizeof(int)) == -1) { ERR("IPv6 setting socket options failed"); goto error; } /* Find the IP address for the selected interface */ struct ifaddrs *ifaddr, *ifa; getifaddrs(&ifaddr); for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { if (ifa->ifa_addr == NULL) continue; if ((strcmp(ifa->ifa_name, interface) == 0) && (ifa->ifa_addr->sa_family == AF_INET6)) break; } if (ifa == NULL) { ERR("Interface %s does not exist or IPv6 IP not found.", interface); goto error; } /* Configure socket params */ struct sockaddr_in6 addr, *if_addr; char buf[64]; if_addr = (struct sockaddr_in6 *) ifa->ifa_addr; memset(&addr, 0, sizeof addr); addr.sin6_family = AF_INET6; addr.sin6_port = htons(port); addr.sin6_addr = if_addr->sin6_addr; addr.sin6_scope_id=if_nametoindex(interface); if (inet_ntop(addr.sin6_family, (void *)&(addr.sin6_addr), buf, sizeof(buf)) == NULL) { ERR("Could not determine IPv6 IP address for interface %s.", interface); goto error; } NOTE("IPv6: Binding to [%s]:%d", buf, port); /* Bind to the interface/IP/port */ if (bind(this->sd, (struct sockaddr *)&addr, sizeof addr) < 0) { if (g_options.only_desired_port == 1) ERR("IPv6 bind on port failed. " "Requested port may be taken or require root permissions."); goto error; } /* Let kernel over-accept max number of connections */ if (listen(this->sd, HTTP_MAX_PENDING_CONNS) < 0) { ERR("IPv6 listen failed on socket"); goto error; } return this; error: if (this != NULL) { if (this->sd != -1) { close(this->sd); } free(this); } return NULL; } void tcp_close(struct tcp_sock_t *this) { close(this->sd); free(this); } uint16_t tcp_port_number_get(struct tcp_sock_t *sock) { sock->info_size = sizeof sock->info; int query_status = getsockname(sock->sd, (struct sockaddr *) &(sock->info), &(sock->info_size)); if (query_status == -1) { ERR("query on socket port number failed"); goto error; } return ntohs(sock->info.sin6_port); error: return 0; } struct http_packet_t *tcp_packet_get(struct tcp_conn_t *tcp) { /* Allocate packet for incoming message. */ struct http_packet_t *pkt = packet_new(); if (pkt == NULL) { ERR("failed to create packet for incoming tcp message"); goto error; } struct timeval tv; tv.tv_sec = 3; tv.tv_usec = 0; if (setsockopt(tcp->sd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))) { ERR("TCP: Setting options for tcp connection socket failed"); goto error; } ssize_t gotten_size = recv(tcp->sd, pkt->buffer, pkt->buffer_capacity, 0); if (gotten_size < 0) { int errno_saved = errno; ERR("recv failed with err %d:%s", errno_saved, strerror(errno_saved)); tcp->is_closed = 1; goto error; } if (gotten_size == 0) { tcp->is_closed = 1; } pkt->filled_size = gotten_size; return pkt; error: if (pkt != NULL) packet_free(pkt); return NULL; } int tcp_packet_send(struct tcp_conn_t *conn, struct http_packet_t *pkt) { size_t remaining = pkt->filled_size; size_t total = 0; while (remaining > 0 && !g_options.terminate) { ssize_t sent = send(conn->sd, pkt->buffer + total, remaining, MSG_NOSIGNAL); if (sent < 0) { if (errno == EPIPE) { conn->is_closed = 1; return 0; } ERR("Failed to sent data over TCP"); return -1; } size_t sent_unsigned = (size_t)sent; total += sent_unsigned; if (sent_unsigned >= remaining) remaining = 0; else remaining -= sent_unsigned; } NOTE("TCP: sent %lu bytes", total); return 0; } struct tcp_conn_t *tcp_conn_select(struct tcp_sock_t *sock, struct tcp_sock_t *sock6) { struct tcp_conn_t *conn = calloc(1, sizeof *conn); if (conn == NULL) { ERR("Calloc for connection struct failed"); goto error; } fd_set rfds; int retval = 0; int nfds = 0; FD_ZERO(&rfds); if (sock) { FD_SET(sock->sd, &rfds); nfds = sock->sd; } if (sock6) { FD_SET(sock6->sd, &rfds); if (sock6->sd > nfds) nfds = sock6->sd; } if (nfds == 0) { ERR("No valid TCP socket supplied."); goto error; } nfds += 1; retval = select(nfds, &rfds, NULL, NULL, NULL); if (g_options.terminate) goto error; if (retval < 1) { ERR("Failed to open tcp connection"); goto error; } if (sock && FD_ISSET(sock->sd, &rfds)) { conn->sd = accept(sock->sd, NULL, NULL); NOTE ("Using IPv4"); } else if (sock6 && FD_ISSET(sock6->sd, &rfds)) { conn->sd = accept(sock6->sd, NULL, NULL); NOTE ("Using IPv6"); } else { ERR("select failed"); goto error; } if (conn->sd < 0) { ERR("accept failed"); goto error; } /* Attempt to initialize the connection's mutex. */ if (pthread_mutex_init(&conn->mutex, NULL)) goto error; return conn; error: if (conn != NULL) free(conn); return NULL; } void tcp_conn_close(struct tcp_conn_t *conn) { /* Unbind host/port cleanly even with pending requests. Otherwise the port will stay unavailable for a certain kernel-defined timeout. See also http://stackoverflow.com/questions/10619952/how-to-completely-destroy-a-socket-connection-in-c */ shutdown(conn->sd, SHUT_RDWR); close(conn->sd); pthread_mutex_destroy(&conn->mutex); free(conn); } /* Poll the tcp socket to determine if it is ready to transmit data. */ int poll_tcp_socket(struct tcp_conn_t *tcp) { struct pollfd poll_fd; poll_fd.fd = tcp->sd; poll_fd.events = POLLIN; const int nfds = 1; const int timeout = 5000; /* 5 seconds. */ int result = poll(&poll_fd, nfds, timeout); if (result < 0) { ERR("poll failed with error %d:%s", errno, strerror(errno)); tcp->is_closed = 1; } else if (result == 0) { /* In the case where the poll timed out, check to see whether or not data * has recently been sent from the printer along the socket. If so, then we * keep the connection alive an reset the is_active flag. Otherwise, close * the connection. */ if (get_is_active(tcp)) { set_is_active(tcp, 0); } else { tcp->is_closed = 1; } } else { if (poll_fd.revents != POLLIN) { ERR("poll returned an unexpected event"); tcp->is_closed = 1; return -1; } } return result; } int get_is_active(struct tcp_conn_t *tcp) { pthread_mutex_lock(&tcp->mutex); int val = tcp->is_active; pthread_mutex_unlock(&tcp->mutex); return val; } void set_is_active(struct tcp_conn_t *tcp, int val) { pthread_mutex_lock(&tcp->mutex); tcp->is_active = val; pthread_mutex_unlock(&tcp->mutex); } ippusbxd-1.34/src/tcp.h000066400000000000000000000032731362321405400150320ustar00rootroot00000000000000/* Copyright (C) 2014 Daniel Dressler and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include #include #include #include #include #include #include "http.h" #define HTTP_MAX_PENDING_CONNS 0 #define BUFFER_STEP (1 << 13) #define BUFFER_STEP_RATIO (2) #define BUFFER_INIT_RATIO (1) #define BUFFER_MAX (1 << 20) struct tcp_sock_t { int sd; struct sockaddr_in6 info; socklen_t info_size; }; struct tcp_conn_t { int sd; int is_closed; int is_active; pthread_mutex_t mutex; }; struct tcp_sock_t *tcp_open(uint16_t, char* interface); struct tcp_sock_t *tcp6_open(uint16_t, char* interface); void tcp_close(struct tcp_sock_t *); uint16_t tcp_port_number_get(struct tcp_sock_t *); struct tcp_conn_t *tcp_conn_select(struct tcp_sock_t *sock, struct tcp_sock_t *sock6); void tcp_conn_close(struct tcp_conn_t *); struct http_packet_t *tcp_packet_get(struct tcp_conn_t *); int tcp_packet_send(struct tcp_conn_t *, struct http_packet_t *); int poll_tcp_socket(struct tcp_conn_t *tcp); int get_is_active(struct tcp_conn_t *tcp); void set_is_active(struct tcp_conn_t *tcp, int val); ippusbxd-1.34/src/usb.c000066400000000000000000000521461362321405400150330ustar00rootroot00000000000000/* Copyright (C) 2014 Daniel Dressler and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define _XOPEN_SOURCE 600 #include #include #include #include #include #include #include #include "options.h" #include "dnssd.h" #include "logging.h" #include "http.h" #include "tcp.h" #include "usb.h" #define IGNORE(x) (void)(x) #define le16_to_cpu(x) libusb_cpu_to_le16(libusb_cpu_to_le16(x)) static int bus, dev_addr; static int is_ippusb_interface(const struct libusb_interface_descriptor *interf) { return interf->bInterfaceClass == 0x07 && interf->bInterfaceSubClass == 0x01 && interf->bInterfaceProtocol == 0x04; } static int count_ippoverusb_interfaces(struct libusb_config_descriptor *config) { int ippusb_interface_count = 0; NOTE("Counting IPP-over-USB interfaces ..."); for (uint8_t interface_num = 0; interface_num < config->bNumInterfaces; interface_num++) { const struct libusb_interface *interface = NULL; interface = &config->interface[interface_num]; for (int alt_num = 0; alt_num < interface->num_altsetting; alt_num++) { const struct libusb_interface_descriptor *alt = NULL; alt = &interface->altsetting[alt_num]; NOTE("Interface %d, Alt %d: Class %d, Subclass %d, Protocol %d", interface_num, alt_num, alt->bInterfaceClass, alt->bInterfaceSubClass, alt->bInterfaceProtocol); /* Check for IPP over USB interfaces */ if (!is_ippusb_interface(alt)) continue; NOTE(" -> is IPP-over-USB"); ippusb_interface_count++; break; } } NOTE(" -> %d Interfaces", ippusb_interface_count); return ippusb_interface_count; } static int is_our_device(libusb_device *dev, struct libusb_device_descriptor desc) { static const int SERIAL_MAX = 1024; unsigned char serial[1024]; NOTE("Found device: VID %04x, PID %04x on Bus %03d, Device %03d", desc.idVendor, desc.idProduct, libusb_get_bus_number(dev), libusb_get_device_address(dev)); if ((g_options.vendor_id && desc.idVendor != g_options.vendor_id) || (g_options.product_id && desc.idProduct != g_options.product_id) || (g_options.bus && libusb_get_bus_number(dev) != g_options.bus) || (g_options.device && libusb_get_device_address(dev) != g_options.device)) return 0; if (g_options.serial_num == NULL) return 1; libusb_device_handle *handle = NULL; int status = libusb_open(dev, &handle); if (status != 0) { /* Device turned off or disconnected, we cannot retrieve its serial number any more, so we identify it via bus and device addresses */ return (bus == libusb_get_bus_number(dev) && dev_addr == libusb_get_device_address(dev)); } else { /* Device is turned on and connected, read out its serial number and use the serial number for identification */ status = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, serial, SERIAL_MAX); libusb_close(handle); if (status <= 0) { WARN("Failed to get serial from device"); return 0; } return strcmp((char *)serial, (char *)g_options.serial_num) == 0; } } int get_device_id(struct libusb_device_handle *handle, int conf, int iface, int altset, char *buffer, size_t bufsize) { size_t length; if (libusb_control_transfer(handle, LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_ENDPOINT_IN | LIBUSB_RECIPIENT_INTERFACE, 0, conf, (iface << 8) | altset, (unsigned char *)buffer, bufsize, 5000) < 0) { *buffer = '\0'; return (-1); } /* Extract the length of the device ID string from the first two bytes. The 1284 spec says the length is stored MSB first... */ length = (int)((((unsigned)buffer[0] & 255) << 8) | ((unsigned)buffer[1] & 255)); /* Check to see if the length is larger than our buffer or less than 14 bytes (the minimum valid device ID is "MFG:x;MDL:y;" with 2 bytes for the length). If the length is out-of-range, assume that the vendor incorrectly implemented the 1284 spec and re-read the length as LSB first, ... */ if (length > bufsize || length < 14) length = (int)((((unsigned)buffer[1] & 255) << 8) | ((unsigned)buffer[0] & 255)); if (length > bufsize) length = bufsize; if (length < 14) { /* Invalid device ID, clear it! */ *buffer = '\0'; return (-1); } length -= 2; memmove(buffer, buffer + 2, (size_t)length); buffer[length] = '\0'; return (0); } static void try_detach_kernel_driver(struct usb_sock_t *usb, struct usb_interface *uf) { /* Make kernel release interface */ if (libusb_kernel_driver_active(usb->printer, uf->libusb_interface_index) == 1) { /* Only linux supports this other platforms will fail thus we ignore the error code it either works or it does not */ libusb_detach_kernel_driver(usb->printer, uf->libusb_interface_index); } } static int try_claim_usb_interface(struct usb_sock_t *usb, struct usb_interface *uf) { /* Claim the whole interface */ int status = 0; do { /* Spinlock-like Libusb does not offer a blocking call so we're left with a spinlock. */ status = libusb_claim_interface(usb->printer, uf->libusb_interface_index); if (status) NOTE("Failed to claim interface %d, retrying", uf->libusb_interface_index); switch (status) { case LIBUSB_ERROR_NOT_FOUND: ERR("USB Interface did not exist"); return -1; case LIBUSB_ERROR_NO_DEVICE: ERR("Printer was removed"); return -1; default: break; } } while (status != 0 && !g_options.terminate); return 0; } struct usb_sock_t *usb_open() { int status_lock; struct usb_sock_t *usb = calloc(1, sizeof *usb); int status = 1; usb->device_id = NULL; status = libusb_init(&usb->context); if (status < 0) { ERR("libusb init failed with error: %s", libusb_error_name(status)); goto error_usbinit; } libusb_device **device_list = NULL; ssize_t device_count = libusb_get_device_list(usb->context, &device_list); if (device_count < 0) { ERR("failed to get list of usb devices"); goto error; } /* Discover device and count interfaces ==---------------------------== */ int selected_config = -1; unsigned int selected_ipp_interface_count = 0; int auto_pick = !((g_options.vendor_id && g_options.product_id) || g_options.serial_num || (g_options.bus && g_options.device)); libusb_device *printer_device = NULL; if (g_options.vendor_id || g_options.product_id) NOTE("Searching for device: VID %04x, PID %04x", g_options.vendor_id, g_options.product_id); if (g_options.serial_num) NOTE("Searching for device with serial number %s", g_options.serial_num); if (g_options.bus || g_options.device) NOTE("Searching for device: Bus %03d, Device %03d", g_options.bus, g_options.device); if (auto_pick) NOTE("Searching for first IPP-over-USB-capable device available"); struct libusb_device_descriptor desc; for (ssize_t i = 0; i < device_count; i++) { libusb_device *candidate = device_list[i]; libusb_get_device_descriptor(candidate, &desc); if (!is_our_device(candidate, desc)) continue; bus = libusb_get_bus_number(candidate); dev_addr = libusb_get_device_address(candidate); NOTE("Device connected on bus %03d device %03d", bus, dev_addr); for (uint8_t config_num = 0; config_num < desc.bNumConfigurations; config_num++) { struct libusb_config_descriptor *config = NULL; status = libusb_get_config_descriptor(candidate, config_num, &config); if (status < 0) { ERR("USB: didn't get config desc %s", libusb_error_name(status)); goto error; } int interface_count = count_ippoverusb_interfaces(config); libusb_free_config_descriptor(config); if (interface_count >= 2) { selected_config = config_num; selected_ipp_interface_count = (unsigned) interface_count; printer_device = candidate; goto found_device; } /* CONFTEST: Two or more interfaces are required */ if (interface_count == 1) { CONF("usb device has only one ipp interface " "in violation of standard"); goto error; } if (!auto_pick) { ERR("No ipp-usb interfaces found"); goto error; } } } found_device: /* Save VID/PID for exit-on-unplug */ if (g_options.vendor_id == 0) g_options.vendor_id = desc.idVendor; if (g_options.product_id == 0) g_options.product_id = desc.idProduct; if (printer_device == NULL) { if (!auto_pick) { ERR("No printer found by that vid, pid, serial or bus, device"); } else { ERR("No IPP over USB printer found"); } goto error; } /* Open the printer ==-----------------------------------------------== */ status = libusb_open(printer_device, &usb->printer); if (status != 0) { ERR("failed to open device"); goto error; } /* Open every IPP-USB interface ==-----------------------------------== */ usb->num_interfaces = selected_ipp_interface_count; usb->interfaces = calloc(usb->num_interfaces, sizeof(*usb->interfaces)); if (usb->interfaces == NULL) { ERR("Failed to alloc interfaces"); goto error; } struct libusb_config_descriptor *config = NULL; status = libusb_get_config_descriptor(printer_device, (uint8_t)selected_config, &config); if (status != 0 || config == NULL) { ERR("Failed to acquire config descriptor"); goto error; } unsigned int interfs = selected_ipp_interface_count; for (uint8_t interf_num = 0; interf_num < config->bNumInterfaces; interf_num++) { const struct libusb_interface *interf = NULL; interf = &config->interface[interf_num]; for (int alt_num = 0; alt_num < interf->num_altsetting; alt_num++) { const struct libusb_interface_descriptor *alt = NULL; alt = &interf->altsetting[alt_num]; /* Get the IEE-1284 device ID */ if (usb->device_id == NULL) { usb->device_id = calloc(2048, sizeof(char)); if (usb->device_id == NULL) { ERR("Failed to allocate memory for the device ID"); goto error; } if (get_device_id(usb->printer, selected_config, interf_num, alt_num, usb->device_id, 2048) != 0 || strlen(usb->device_id) == 0) { NOTE("Could not retrieve device ID for config #%d, interface #%d, alt setting #%d, will try with other combo ...", selected_config, interf_num, alt_num); free(usb->device_id); usb->device_id = NULL; g_options.device_id = NULL; } else { NOTE("USB device ID: %s", usb->device_id); g_options.device_id = usb->device_id; } } /* Skip non-IPP-USB interfaces */ if (!is_ippusb_interface(alt)) continue; interfs--; struct usb_interface *uf = usb->interfaces + interfs; uf->interface_number = interf_num; uf->libusb_interface_index = alt->bInterfaceNumber; uf->interface_alt = alt_num; /* Store interface's two endpoints */ for (int end_i = 0; end_i < alt->bNumEndpoints; end_i++) { const struct libusb_endpoint_descriptor *end; end = &alt->endpoint[end_i]; usb->max_packet_size = end->wMaxPacketSize; /* High bit set means endpoint is an INPUT or IN endpoint. */ uint8_t address = end->bEndpointAddress; if (address & 0x80) uf->endpoint_in = address; else uf->endpoint_out = address; } status_lock = sem_init(&uf->lock, 0, 1); if (status_lock != 0) { ERR("Failed to create interface lock #%d", interf_num); goto error; } /* Try to make the kernel release the usb interface. */ try_detach_kernel_driver(usb, uf); /* Try to claim the usb interface. */ if (try_claim_usb_interface(usb, uf)) { ERR("Failed to claim usb interface #%d", uf->interface_number); goto error; } /* Select the IPP-USB alt setting of the interface. */ if (libusb_set_interface_alt_setting( usb->printer, uf->libusb_interface_index, uf->interface_alt)) { ERR("Failed to set alt setting for interface #%d", uf->interface_number); goto error; } break; } } libusb_free_config_descriptor(config); libusb_free_device_list(device_list, 1); /* Pour interfaces into pool ==--------------------------------------== */ usb->num_avail = usb->num_interfaces; usb->interface_pool = calloc(usb->num_avail, sizeof(*usb->interface_pool)); if (usb->interface_pool == NULL) { ERR("Failed to alloc interface pool"); goto error; } for (uint32_t i = 0; i < usb->num_avail; i++) { usb->interface_pool[i] = i; } NOTE("USB interfaces pool: %d interfaces", usb->num_avail); /* Stale lock */ status_lock = sem_init(&usb->num_staled_lock, 0, 1); if (status_lock != 0) { ERR("Failed to create num_staled lock"); goto error; } /* Pool management lock */ status_lock = sem_init(&usb->pool_manage_lock, 0, 1); if (status_lock != 0) { ERR("Failed to create pool management lock"); goto error; } return usb; error: if (device_list != NULL) libusb_free_device_list(device_list, 1); error_usbinit: if (usb != NULL) { if (usb->context != NULL) libusb_exit(usb->context); if (usb->interfaces != NULL) free(usb->interfaces); if (usb->interface_pool != NULL) free(usb->interface_pool); free(usb); } return NULL; } void usb_close(struct usb_sock_t *usb) { /* Release interfaces */ for (uint32_t i = 0; i < usb->num_interfaces; i++) { int number = usb->interfaces[i].interface_number; libusb_release_interface(usb->printer, number); sem_destroy(&usb->interfaces[i].lock); } NOTE("Resetting printer ..."); libusb_reset_device(usb->printer); NOTE("Reset completed."); NOTE("Closing device handle..."); libusb_close(usb->printer); NOTE("Closed device handle."); if (usb != NULL) { if (usb->context != NULL) libusb_exit(usb->context); sem_destroy(&usb->num_staled_lock); if (usb->interfaces != NULL) free(usb->interfaces); if (usb->interface_pool != NULL) free(usb->interface_pool); free(usb); usb = NULL; } return; } int usb_can_callback(struct usb_sock_t *usb) { IGNORE(usb); if (!g_options.vendor_id || !g_options.product_id) { NOTE("Exit-on-unplug requires vid & pid"); return 0; } int works = !!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG); if (!works) WARN("Libusb cannot tell us when to disconnect"); return works; } static int LIBUSB_CALL usb_exit_on_unplug(libusb_context *context, libusb_device *device, libusb_hotplug_event event, void *call_data) { IGNORE(context); IGNORE(event); IGNORE(call_data); NOTE("Received unplug callback"); struct libusb_device_descriptor desc; libusb_get_device_descriptor(device, &desc); if (is_our_device(device, desc)) { /* We prefer an immediate shutdown with only DNS-SD and TCP clean-up here as by a regular sgutdown request via termination flag g_options.terminate there can still happen USB communication attempts with long timeouts, making ippusbxd get stuck for a significant time. This way we immediately stop the DNS-SD advertising and release the host/port binding. */ /* Unregister DNS-SD for printer on Avahi */ if (g_options.dnssd_data != NULL) dnssd_shutdown(); /* TCP clean-up */ if (g_options.tcp_socket!= NULL) tcp_close(g_options.tcp_socket); if (g_options.tcp6_socket!= NULL) tcp_close(g_options.tcp6_socket); exit(0); } return 0; } static void *usb_pump_events(void *user_data) { IGNORE(user_data); NOTE("USB unplug event observer thread starting"); while (!g_options.terminate) { /* NOTE: This is a blocking call so no need for sleep() */ struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 500000; libusb_handle_events_timeout_completed(NULL, &tv, NULL); } NOTE("USB unplug event observer thread terminating"); return NULL; } void usb_register_callback(struct usb_sock_t *usb) { IGNORE(usb); int status = libusb_hotplug_register_callback(NULL, LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, /* Note: libusb's enum has no default value a bug has been filled with libusb. Please switch the below line to 0 once the issue has been fixed in deployed versions of libusb https://github.com/libusb/libusb/issues/35 */ /* 0, */ LIBUSB_HOTPLUG_ENUMERATE, g_options.vendor_id, g_options.product_id, LIBUSB_HOTPLUG_MATCH_ANY, &usb_exit_on_unplug, NULL, NULL); if (status == LIBUSB_SUCCESS) { pthread_create(&(g_options.usb_event_thread_handle), NULL, &usb_pump_events, NULL); NOTE("Registered unplug callback"); } else ERR("Failed to register unplug callback"); } struct usb_conn_t *usb_conn_acquire(struct usb_sock_t *usb) { int i; if (usb->num_avail <= 0) { NOTE("All USB interfaces busy, waiting ..."); for (i = 0; i < 30 && usb->num_avail <= 0; i ++) { if (g_options.terminate) return NULL; usleep(100000); } if (usb->num_avail <= 0) { ERR("Timed out waiting for a free USB interface"); return NULL; } } struct usb_conn_t *conn = calloc(1, sizeof(*conn)); if (conn == NULL) { ERR("Failed to alloc space for usb connection"); return NULL; } sem_wait(&usb->pool_manage_lock); { conn->parent = usb; uint32_t slot = usb->num_taken; conn->interface_index = usb->interface_pool[slot]; conn->interface = usb->interfaces + conn->interface_index; struct usb_interface *uf = conn->interface; /* Sanity check: Is the interface still free */ if (sem_trywait(&uf->lock)) { ERR("Interface #%d (%d) already in use!", conn->interface_index, uf->libusb_interface_index); goto acquire_error; } /* Take successfully acquired interface from the pool */ usb->num_taken++; usb->num_avail--; } sem_post(&usb->pool_manage_lock); return conn; acquire_error: sem_post(&usb->pool_manage_lock); free(conn); return NULL; } void usb_conn_release(struct usb_conn_t *conn) { struct usb_sock_t *usb = conn->parent; sem_wait(&usb->pool_manage_lock); { /* Return usb interface to pool */ usb->num_taken--; usb->num_avail++; uint32_t slot = usb->num_taken; usb->interface_pool[slot] = conn->interface_index; /* Release our interface lock */ sem_post(&conn->interface->lock); free(conn); } sem_post(&usb->pool_manage_lock); } int usb_conn_packet_send(struct usb_conn_t *conn, struct http_packet_t *pkt) { int size_sent = 0; const int timeout = 1000; /* 1 sec */ int num_timeouts = 0; size_t sent = 0; size_t pending = pkt->filled_size; while (pending > 0 && !g_options.terminate) { int to_send = (int)pending; NOTE("P %p: USB: want to send %d bytes", pkt, to_send); int status = libusb_bulk_transfer(conn->parent->printer, conn->interface->endpoint_out, pkt->buffer + sent, to_send, &size_sent, timeout); if (status == LIBUSB_ERROR_NO_DEVICE) { ERR("P %p: Printer has been disconnected", pkt); return -1; } if (status == LIBUSB_ERROR_TIMEOUT) { NOTE("P %p: USB: send timed out, retrying", pkt); if (num_timeouts++ > PRINTER_CRASH_TIMEOUT_RECEIVE) { ERR("P %p: Usb send fully timed out", pkt); return -1; } /* Sleep for tenth of a second */ struct timespec sleep_dur; sleep_dur.tv_sec = 0; sleep_dur.tv_nsec = 100000000; nanosleep(&sleep_dur, NULL); if (size_sent == 0) continue; } else if (status < 0) { ERR("P %p: USB: send failed with status %s", pkt, libusb_error_name(status)); return -1; } if (size_sent < 0) { ERR("P %p: Unexpected negative size_sent", pkt); return -1; } pending -= (size_t) size_sent; sent += (size_t) size_sent; NOTE("P %p: USB: sent %d bytes", pkt, size_sent); } NOTE("P %p: USB: sent %d bytes in total", pkt, sent); return 0; } struct libusb_transfer *setup_async_read(struct usb_conn_t *conn, struct http_packet_t *pkt, libusb_transfer_cb_fn callback, void *user_data, uint32_t timeout) { struct libusb_transfer *transfer = libusb_alloc_transfer(0); if (transfer == NULL) return NULL; libusb_fill_bulk_transfer(transfer, conn->parent->printer, conn->interface->endpoint_in, pkt->buffer, pkt->buffer_capacity, callback, user_data, timeout); return transfer; } ippusbxd-1.34/src/usb.h000066400000000000000000000040421362321405400150300ustar00rootroot00000000000000/* Copyright (C) 2014 Daniel Dressler and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include #include /* In seconds */ #define PRINTER_CRASH_TIMEOUT_RECEIVE (60 * 60 * 6) #define PRINTER_CRASH_TIMEOUT_ANSWER 5 #define CONN_STALE_THRESHHOLD 5 struct usb_interface { uint8_t interface_number; uint8_t libusb_interface_index; int interface_alt; uint8_t endpoint_in; uint8_t endpoint_out; sem_t lock; }; struct usb_sock_t { libusb_context *context; libusb_device_handle *printer; char *device_id; int max_packet_size; uint32_t num_interfaces; struct usb_interface *interfaces; uint32_t num_staled; sem_t num_staled_lock; sem_t pool_manage_lock; uint32_t num_avail; uint32_t num_taken; uint32_t *interface_pool; }; struct usb_conn_t { struct usb_sock_t *parent; struct usb_interface *interface; uint32_t interface_index; int is_staled; }; struct usb_sock_t *usb_open(void); void usb_close(struct usb_sock_t *); int usb_can_callback(struct usb_sock_t *); void usb_register_callback(struct usb_sock_t *); struct usb_conn_t *usb_conn_acquire(struct usb_sock_t *); void usb_conn_release(struct usb_conn_t *); int usb_conn_packet_send(struct usb_conn_t *, struct http_packet_t *); struct libusb_transfer *setup_async_read(struct usb_conn_t *conn, struct http_packet_t *pkt, libusb_transfer_cb_fn callback, void *user_data, uint32_t timeout); ippusbxd-1.34/systemd-udev/000077500000000000000000000000001362321405400157305ustar00rootroot00000000000000ippusbxd-1.34/systemd-udev/55-ippusbxd.rules000066400000000000000000000004441362321405400210730ustar00rootroot00000000000000# ippusbxd udev rules file ACTION=="add", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device" ENV{ID_USB_INTERFACES}=="*:070104:*", OWNER="root", GROUP="lp", MODE="0664", TAG+="systemd", PROGRAM="/bin/systemd-escape --template=ippusbxd@.service $env{BUSNUM}:$env{DEVNUM}", ENV{SYSTEMD_WANTS}+="%c" ippusbxd-1.34/systemd-udev/ippusbxd@.service000066400000000000000000000004321362321405400212470ustar00rootroot00000000000000[Unit] Description=Daemon to make IPP-over-USB printers available as network printers (%i) [Service] Type=forking GuessMainPID=true ExecStart=/usr/sbin/ippusbxd --bus-device %I --from-port 60000 --logging # ExecStop= Not needed, ippusbxd stops by itself on shutdown of the printer ippusbxd-1.34/systemd-udev/ippusbxd@.service.dummy0000066400000000000000000000004551362321405400224660ustar00rootroot00000000000000[Unit] Description=Daemon to make IPP-over-USB printers available as network printers (%i) [Service] Type=forking GuessMainPID=true ExecStart=/usr/sbin/ippusbxd --bus-device %I --from-port 60000 --interface dummy0 --logging # ExecStop= Not needed, ippusbxd stops by itself on shutdown of the printer