pax_global_header00006660000000000000000000000064132275155050014517gustar00rootroot0000000000000052 comment=1e3fde8c875770a5e906d026f96798c956563137 ippusbxd-1.32/000077500000000000000000000000001322751550500133025ustar00rootroot00000000000000ippusbxd-1.32/.gitignore000066400000000000000000000000061322751550500152660ustar00rootroot00000000000000exe/* ippusbxd-1.32/.travis.yml000066400000000000000000000014241322751550500154140ustar00rootroot00000000000000language: 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.32/doc/000077500000000000000000000000001322751550500140475ustar00rootroot00000000000000ippusbxd-1.32/doc/behaviour000066400000000000000000000001401322751550500157510ustar00rootroot00000000000000- Must use CRLF for transfer: chunked messages - Rotates USB interfaces between TCP connections ippusbxd-1.32/doc/ippusbxd.8000066400000000000000000000073511322751550500160040ustar00rootroot00000000000000.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.32/doc/ippusbxd_logo.svg000066400000000000000000000066451322751550500174610ustar00rootroot00000000000000 image/svg+xml Extreme! IPP over USB ippusbxd ippusbxd-1.32/doc/ippusbxd_presentation.odp000066400000000000000000001406121322751550500212100ustar00rootroot00000000000000PKLE3&//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.32/makefile000066400000000000000000000021731322751550500150050ustar00rootroot00000000000000SHELL := /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.32/readme.md000066400000000000000000000375651322751550500151010ustar00rootroot00000000000000# IPPUSBXD [![Coverity analysis status](https://scan.coverity.com/projects/2634/badge.svg)](https://scan.coverity.com/projects/2634) Version 1.31 ## 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 cmake ``` Under Fedora: ``` sudo yum install libusbx-devel.* cmake ``` Install also the *-devel packages of 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.32/src/000077500000000000000000000000001322751550500140715ustar00rootroot00000000000000ippusbxd-1.32/src/CMakeLists.txt000066400000000000000000000024201322751550500166270ustar00rootroot00000000000000cmake_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}) add_executable(ippusbxd ippusbxd.c http.c tcp.c usb.c logging.c options.c dnssd.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}) ippusbxd-1.32/src/FindAVAHICLIENT.cmake000066400000000000000000000021331322751550500174220ustar00rootroot00000000000000# 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.32/src/FindAVAHICOMMON.cmake000066400000000000000000000021411322751550500174330ustar00rootroot00000000000000# 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.32/src/FindLIBUSB.cmake000066400000000000000000000023111322751550500166510ustar00rootroot00000000000000# 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.32/src/apparmor/000077500000000000000000000000001322751550500157125ustar00rootroot00000000000000ippusbxd-1.32/src/apparmor/usr.sbin.ippusbxd000066400000000000000000000012031322751550500212310ustar00rootroot00000000000000# vim:syntax=apparmor # Last Modified: Fri Sep 12 15:52:02 2014 # Author: Daniel Dressler #include /usr/sbin/ippusbxd { #include #include capability wake_alarm, /usr/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.32/src/dnssd.c000066400000000000000000000331661322751550500153610ustar00rootroot00000000000000/* 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 "dnssd.h" #include "logging.h" #include "options.h" /* * 'dnssd_callback()' - Handle DNS-SD registration events. */ static void dnssd_callback(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; 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_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); 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; 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."); } /* * 'dnssd_register()' - Register a printer object via DNS-SD. */ 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 has_duplex = 'U', has_color = 'U', has_copies = 'U'; char formats[1024]; /* I - Supported formats */ char *ptr; int error; /* * Parse the device ID for MFG, MDL, and CMD */ dev_id = strdup(g_options.device_id); 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'; if (urf) { if ((ptr = strcasestr(urf, "DM")) != NULL && *(ptr + 2) >= '1' && *(ptr + 2) <= '4') has_duplex = 'T'; else has_duplex = 'F'; if ((ptr = strcasestr(urf, "CP")) != NULL) { ptr += 2; if ((*ptr >= '2' && *ptr <= '9') || (*ptr == '1' && *(ptr + 1) >= '0' && *(ptr + 1) <= '9')) has_copies = 'T'; else has_copies = 'F'; } else has_copies = 'F'; if ((ptr = strcasestr(urf, "RGB")) != NULL) has_color = 'T'; else has_color = 'F'; } /* * 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); /* * Create the TXT record... */ ipp_txt = NULL; ipp_txt = avahi_string_list_add_printf(ipp_txt, "rp=ipp/print"); ipp_txt = avahi_string_list_add_printf(ipp_txt, "ty=%s %s", make, model); if (strcasecmp(g_options.interface, "lo") == 0) ipp_txt = avahi_string_list_add_printf(ipp_txt, "adminurl=%s", temp); ipp_txt = avahi_string_list_add_printf(ipp_txt, "product=(%s)", model); ipp_txt = avahi_string_list_add_printf(ipp_txt, "pdl=%s", formats); ipp_txt = avahi_string_list_add_printf(ipp_txt, "Color=%c", has_color); ipp_txt = avahi_string_list_add_printf(ipp_txt, "Duplex=%c", has_duplex); ipp_txt = avahi_string_list_add_printf(ipp_txt, "Copies=%c", has_copies); 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); if (urf) ipp_txt = avahi_string_list_add_printf(ipp_txt, "URF=%s", urf); else if (appleraster) ipp_txt = avahi_string_list_add_printf(ipp_txt, "URF=CP1,IS1,MT1,RS300,SRGB24,W8"); 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 (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, 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... */ avahi_entry_group_commit(g_options.dnssd_data->ipp_ref); avahi_string_list_free(ipp_txt); return 0; } /* * 'dnssd_unregister()' - Unregister a printer object from DNS-SD. */ 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; } } ippusbxd-1.32/src/dnssd.h000066400000000000000000000017371322751550500153650ustar00rootroot00000000000000/* 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; } dnssd_t; int dnssd_init(); void dnssd_shutdown(); int dnssd_register(); void dnssd_unregister(); ippusbxd-1.32/src/http.c000066400000000000000000000374501322751550500152250ustar00rootroot00000000000000/* 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_message_t *http_message_new() { struct http_message_t *msg = calloc(1, sizeof(*msg)); if (msg == NULL) { ERR("failed to alloc space for http message"); return NULL; } msg->spare_capacity = 0; msg->spare_filled = 0; msg->spare_buffer = NULL; return msg; } void message_free(struct http_message_t *msg) { free(msg->spare_buffer); free(msg); } static void packet_check_completion(struct http_packet_t *pkt) { struct http_message_t *msg = pkt->parent_message; /* Msg full */ if (msg->claimed_size && msg->received_size >= msg->claimed_size) { msg->is_completed = 1; NOTE("http: Message completed: Received size >= claimed size"); /* Sanity check */ if (msg->spare_filled > 0) ERR_AND_EXIT("Msg spare not empty upon completion"); } /* Pkt full */ if (pkt->expected_size && pkt->filled_size >= pkt->expected_size) { pkt->is_completed = 1; NOTE("http: Packet completed: Packet full"); } /* Pkt over capacity */ if (pkt->filled_size > pkt->buffer_capacity) { /* Santiy check */ ERR_AND_EXIT("Overflowed packet buffer"); } } static int doesMatch(const char *matcher, size_t matcher_len, const uint8_t *key, size_t key_len) { for (size_t i = 0; i < matcher_len; i++) if (i >= key_len || matcher[i] != key[i]) return 0; return 1; } static int inspect_header_field(struct http_packet_t *pkt, size_t header_size, char *key, size_t key_size) { /* Find key */ uint8_t *pos = memmem(pkt->buffer, header_size, key, key_size); if (pos == NULL) return -1; /* Find first digit */ size_t number_pos = (size_t) (pos - pkt->buffer) + key_size; while (number_pos < pkt->filled_size && !isdigit(pkt->buffer[number_pos])) ++number_pos; /* Find next non-digit */ size_t number_end = number_pos; while (number_end < pkt->filled_size && isdigit(pkt->buffer[number_end])) ++number_end; /* Failed to find next non-digit header field might be broken */ if (number_end >= pkt->filled_size) return -1; /* Temporary stringification of buffer for atoi() */ uint8_t original_char = pkt->buffer[number_end]; pkt->buffer[number_end] = '\0'; int val = atoi((const char *)(pkt->buffer + number_pos)); /* Restore buffer */ pkt->buffer[number_end] = original_char; return val; } static void packet_store_excess(struct http_packet_t *pkt) { struct http_message_t *msg = pkt->parent_message; if (msg->spare_buffer != NULL) ERR_AND_EXIT("Do not store excess to non-empty packet"); if (pkt->expected_size >= pkt->filled_size) ERR_AND_EXIT("Do not call packet_store_excess() unless needed"); size_t spare_size = pkt->filled_size - pkt->expected_size; size_t non_spare = pkt->expected_size; NOTE("HTTP: Storing %d bytes of excess", spare_size); /* Align to BUFFER_STEP */ size_t needed_size = 0; needed_size += spare_size / BUFFER_STEP; needed_size += (spare_size % BUFFER_STEP) > 0 ? BUFFER_STEP : 0; if (msg->spare_buffer == NULL) { uint8_t *buffer = calloc(1, needed_size); if (buffer == NULL) ERR_AND_EXIT("Failed to alloc msg spare buffer"); msg->spare_buffer = buffer; } memcpy(msg->spare_buffer, pkt->buffer + non_spare, spare_size); pkt->filled_size = non_spare; msg->spare_capacity = needed_size; msg->spare_filled = spare_size; } static void packet_take_spare(struct http_packet_t *pkt) { struct http_message_t *msg = pkt->parent_message; if (msg->spare_filled == 0) return; if (msg->spare_buffer == NULL) return; if (pkt->filled_size > 0) ERR_AND_EXIT("pkt should be empty when loading msg spare"); /* Take message's buffer */ size_t msg_size = msg->spare_capacity; size_t msg_filled = msg->spare_filled; uint8_t *msg_buffer = msg->spare_buffer; pkt->buffer_capacity = msg_size; pkt->filled_size = msg_filled; pkt->buffer = msg_buffer; msg->spare_capacity = 0; msg->spare_filled = 0; msg->spare_buffer = NULL; } static ssize_t packet_find_chunked_size(struct http_packet_t *pkt) { /* NOTE: chunks can have trailers which are tacked on http header fields. NOTE: chunks may also have extensions. No one uses or supports them. */ /* Find end of size string */ if (pkt->filled_size >= SSIZE_MAX) ERR_AND_EXIT("Buffer beyond sane size"); ssize_t max = (ssize_t) pkt->filled_size; ssize_t size_end = -1; ssize_t miniheader_end = -1; ssize_t delimiter_start = -1; for (ssize_t i = 0; i < max; i++) { uint8_t *buf = pkt->buffer; if (size_end < 0) { /* No extension */ if (i + 1 < max && (buf[i] == '\r' && /* CR */ buf[i + 1] == '\n')) { /* LF */ size_end = i + 1; miniheader_end = size_end; delimiter_start = i; break; } /* No extension */ if (buf[i] == '\n') { /* LF */ size_end = i; miniheader_end = size_end; delimiter_start = i; break; } /* Has extensions */ if (buf[i] == ';') { size_end = i; continue; } } if (miniheader_end < 0) { if (i + 1 < max && (buf[i] == '\r' && /* CR */ buf[i + 1] == '\n')) { /* LF */ miniheader_end = i + 1; delimiter_start = i; break; } if (buf[i] == '\n') { /* LF */ miniheader_end = i; delimiter_start = i; break; } } } if (miniheader_end < 0) { /* NOTE: knowing just the size field is not enough since the extensions are not included in the size */ NOTE("failed to find chunk mini-header so far"); return -1; } /* Temporary stringification for strtol() */ uint8_t original_char = *(pkt->buffer + size_end); *(pkt->buffer + size_end) = '\0'; size_t size = strtoul((char *)pkt->buffer, NULL, 16); NOTE("Chunk size raw: %s", pkt->buffer); *(pkt->buffer + size_end) = original_char; if (size > SSIZE_MAX) ERR_AND_EXIT("chunk size is insane"); if (size > 0) { /* Regular chunk */ ssize_t chunk_size = (ssize_t) size; /* Chunk body */ chunk_size += miniheader_end + 1; /* Mini-header */ chunk_size += 2; /* Trailing CRLF */ NOTE("HTTP: Chunk size: %lu", chunk_size); return (ssize_t) chunk_size; } /* Terminator chunk May have trailers in body */ ssize_t full_size = -1; for (ssize_t i = delimiter_start; i < max; i++) { uint8_t *buf = pkt->buffer; if (i + 3 < max && (buf[i] == '\r' && /* CR */ buf[i + 1] == '\n' && /* LF */ buf[i + 2] == '\r' && /* CR */ buf[i + 3] == '\n')) { /* LF */ full_size = i + 4; break; } if (i + 1 < max && buf[i] == '\n' && /* LF */ buf[i + 1] == '\n') { /* LF */ full_size = i + 2; break; } } if (full_size < 0) { NOTE("Chunk miniheader present but body incomplete"); return -1; } NOTE("Found end chunked packet"); pkt->parent_message->is_completed = 1; pkt->is_completed = 1; return full_size; } static ssize_t packet_get_header_size(struct http_packet_t *pkt) { if (pkt->header_size != 0) goto found; /* * RFC2616 recomends we match newline on \n despite full * complience requires the message to use only \r\n * http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.3 */ /* Find header */ for (size_t i = 0; i < pkt->filled_size && i < SSIZE_MAX; i++) { /* two \r\n pairs */ if ((i + 3) < pkt->filled_size && '\r' == pkt->buffer[i] && '\n' == pkt->buffer[i + 1] && '\r' == pkt->buffer[i + 2] && '\n' == pkt->buffer[i + 3]) { pkt->header_size = i + 4; goto found; } /* two \n pairs */ if ((i + 1) < pkt->filled_size && '\n' == pkt->buffer[i] && '\n' == pkt->buffer[i + 1]) { pkt->header_size = i + 2; goto found; } } return -1; found: return (ssize_t) pkt->header_size; } enum http_request_t packet_find_type(struct http_packet_t *pkt) { enum http_request_t type = HTTP_UNSET; size_t size = 0; /* * Valid methods for determining http request * size are defined by W3 in RFC2616 section 4.4 * link: http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4 */ /* * This function attempts to find what method this * packet would use. This is only possible in specific case: * 1. if the request uses method 1 we can check the http * request type. We must be called on a packet which * has the full header. * 2. if the request uses method 2 we need the full header * but a simple network-byte-order-aware string search * works. This function does not work if called with * a chunked transport's sub-packet. * 3. if the request uses method 3 we again perform the * string search. * * All cases require the packat to contain the full header. */ ssize_t header_size_raw = packet_get_header_size(pkt); if (header_size_raw < 0) { /* We don't have the header yet */ goto do_ret; } size_t header_size = (size_t) header_size_raw; /* Try Transfer-Encoding Chunked */ char xfer_encode_str[] = "Transfer-Encoding: chunked"; size_t xfer_encode_str_size = sizeof(xfer_encode_str) - 1; uint8_t *xfer_encode_pos = memmem(pkt->buffer, header_size, xfer_encode_str, xfer_encode_str_size); if (xfer_encode_pos != NULL) { size = 0; type = HTTP_CHUNKED; goto do_ret; } /* Try Content-Length */ char content_length_str[] = "Content-Length: "; ssize_t contlen_size = inspect_header_field(pkt, header_size, content_length_str, sizeof(content_length_str) - 1); if (contlen_size >= 0) { size = (size_t) contlen_size + header_size; type = HTTP_CONTENT_LENGTH; goto do_ret; } /* GET requests (start with GET) or answers from the server (start with HTTP) */ if (doesMatch("GET", 3, pkt->buffer, pkt->filled_size) || doesMatch("HTTP", 4, pkt->buffer, pkt->filled_size)) { size = header_size; type = HTTP_HEADER_ONLY; goto do_ret; } /* No size was detectable yet header was found */ type = HTTP_UNKNOWN; size = 0; do_ret: pkt->parent_message->claimed_size = size; pkt->parent_message->type = type; return type; } size_t packet_pending_bytes(struct http_packet_t *pkt) { struct http_message_t *msg = pkt->parent_message; /* Check Cache */ if (pkt->expected_size > 0) goto pending_known; if (HTTP_UNSET == msg->type) { msg->type = packet_find_type(pkt); if (HTTP_CHUNKED == msg->type) { /* Note: this was the packet with the header of our chunked message. */ /* Save any non-header data we got */ ssize_t header_size = packet_get_header_size(pkt); /* Sanity check */ if (header_size < 0 || (size_t)header_size > pkt->filled_size) ERR_AND_EXIT("HTTP: Could not find header twice"); NOTE("HTTP: Chunked header size is %ld bytes", header_size); pkt->expected_size = (size_t) header_size; msg->claimed_size = 0; goto pending_known; } } if (HTTP_CHUNKED == msg->type) { if (pkt->filled_size == 0) { /* Grab chunk's mini-header */ goto pending_known; } if (pkt->expected_size == 0) { /* Check chunk's mini-header */ ssize_t size = packet_find_chunked_size(pkt); if (size <= 0) { ERR("============================================="); ERR("Malformed chunk-transport http header receivd"); ERR("Missing chunk's mini-headers in first data"); ERR("Have %d bytes", pkt->filled_size); printf("%.*s\n", (int)pkt->filled_size, pkt->buffer); ERR("Malformed chunk-transport http header receivd"); ERR("============================================="); goto pending_known; } pkt->expected_size = (size_t) size; msg->claimed_size = 0; } goto pending_known; } if (HTTP_HEADER_ONLY == msg->type) { /* Note: we can only know it is header only when the buffer already contains the header. So this next call cannot fail. */ pkt->expected_size = (size_t) packet_get_header_size(pkt); msg->claimed_size = pkt->expected_size; goto pending_known; } if (HTTP_CONTENT_LENGTH == msg->type) { /* Note: find_header() has filled msg's claimed_size */ msg->claimed_size = msg->claimed_size; pkt->expected_size = msg->claimed_size; goto pending_known; } pending_known: /* Save excess data */ if (pkt->expected_size && pkt->filled_size > pkt->expected_size) packet_store_excess(pkt); size_t expected = pkt->expected_size; if (expected == 0) expected = msg->claimed_size; if (expected == 0) expected = pkt->buffer_capacity; /* Sanity check */ if (expected < pkt->filled_size) ERR_AND_EXIT("Expected cannot be larger than filled"); size_t pending = expected - pkt->filled_size; /* Expand buffer as needed */ while (pending + pkt->filled_size > pkt->buffer_capacity) { ssize_t size_added = packet_expand(pkt); if (size_added < 0) { WARN("packet at max allowed size"); return 0; } if (size_added == 0) { ERR("Failed to expand packet"); return 0; } } packet_check_completion(pkt); return pending; } void packet_mark_received(struct http_packet_t *pkt, size_t received) { struct http_message_t *msg = pkt->parent_message; msg->received_size += received; pkt->filled_size += received; if (received) { NOTE("HTTP: got %lu bytes so: pkt has %lu bytes, " "msg has %lu bytes", received, pkt->filled_size, msg->received_size); } packet_check_completion(pkt); if (pkt->filled_size > pkt->buffer_capacity) ERR_AND_EXIT("Overflowed packet's buffer"); if (pkt->expected_size && pkt->filled_size > pkt->expected_size) { /* Store excess data */ packet_store_excess(pkt); } } struct http_packet_t *packet_new(struct http_message_t *parent_msg) { struct http_packet_t *pkt = NULL; uint8_t *buf = NULL; size_t const capacity = BUFFER_STEP; assert(parent_msg != NULL); pkt = calloc(1, sizeof(*pkt)); if (pkt == NULL) { ERR("failed to alloc packet"); return NULL; } pkt->parent_message = parent_msg; pkt->expected_size = 0; /* Claim any spare data from prior packets */ packet_take_spare(pkt); if (pkt->buffer == NULL) { 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); } #define MAX_PACKET_SIZE (1 << 26) /* 64MiB */ ssize_t packet_expand(struct http_packet_t *pkt) { size_t cur_size = pkt->buffer_capacity; size_t new_size = cur_size * 2; if (new_size > MAX_PACKET_SIZE) { WARN("HTTP: cannot expand packet beyond limit"); return -1; } NOTE("HTTP: doubling packet buffer to %lu", new_size); uint8_t *new_buf = realloc(pkt->buffer, new_size); if (new_buf == NULL) { /* If realloc fails the original buffer is still valid */ WARN("Failed to expand packet"); return 0; } pkt->buffer = new_buf; pkt->buffer_capacity = new_size; size_t diff = new_size - cur_size; if (diff > SSIZE_MAX) ERR_AND_EXIT("Buffer expanded beyond sane limit"); return (ssize_t) diff; } ippusbxd-1.32/src/http.h000066400000000000000000000032341322751550500152230ustar00rootroot00000000000000/* 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 enum http_request_t { HTTP_UNSET, HTTP_UNKNOWN, HTTP_CHUNKED, HTTP_CONTENT_LENGTH, HTTP_HEADER_ONLY }; struct http_message_t { enum http_request_t type; size_t spare_filled; size_t spare_capacity; uint8_t *spare_buffer; size_t unreceived_size; uint8_t is_completed; /* Detected from child packets */ size_t claimed_size; size_t received_size; }; struct http_packet_t { /* Cache */ size_t header_size; size_t filled_size; size_t expected_size; size_t buffer_capacity; uint8_t *buffer; struct http_message_t *parent_message; uint8_t is_completed; }; struct http_message_t *http_message_new(void); void message_free(struct http_message_t *); enum http_request_t packet_find_type(struct http_packet_t *pkt); size_t packet_pending_bytes(struct http_packet_t *); void packet_mark_received(struct http_packet_t *, size_t); struct http_packet_t *packet_new(struct http_message_t *); void packet_free(struct http_packet_t *); ssize_t packet_expand(struct http_packet_t *); ippusbxd-1.32/src/ippusbxd.c000066400000000000000000000514231322751550500161000ustar00rootroot00000000000000/* 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 "options.h" #include "logging.h" #include "http.h" #include "tcp.h" #include "usb.h" #include "dnssd.h" struct service_thread_param { struct tcp_conn_t *tcp; struct usb_sock_t *usb_sock; pthread_t thread_handle; int thread_num; }; static pthread_mutex_t thread_register_mutex; static struct service_thread_param **service_threads = NULL; static int 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(int num_service_threads, struct service_thread_param **service_threads) { int i; char *p; char buf[10240]; snprintf(buf, sizeof(buf), "Threads currently running: "); p = buf + strlen(buf); if (num_service_threads == 0) { snprintf(p, sizeof(buf) - strlen(buf), "None"); } else { for (i = 0; i < num_service_threads; i ++) { snprintf(p, sizeof(buf) - strlen(buf), "#%d, ", service_threads[i]->thread_num); p = buf + strlen(buf); } p -= 2; *p = '\0'; } buf[sizeof(buf) - 1] = '\0'; NOTE("%s", buf); } static int register_service_thread(int *num_service_threads, struct service_thread_param ***service_threads, struct service_thread_param *new_thread) { NOTE("Registering thread #%d", new_thread->thread_num); (*num_service_threads) ++; *service_threads = realloc(*service_threads, *num_service_threads * sizeof(void*)); if (*service_threads == NULL) { ERR("Registering thread #%d: 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(int *num_service_threads, struct service_thread_param ***service_threads, int thread_num) { int i; NOTE("Unregistering thread #%d", thread_num); 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 #%d: Cannot unregister, not found", thread_num); return -1; } (*num_service_threads) --; for (; i < *num_service_threads; i ++) (*service_threads)[i] = (*service_threads)[i + 1]; *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 #%d: Failed to alloc space for thread registration list", thread_num); return -1; } return 0; } static void cleanup_handler(void *arg_void) { int thread_num = *((int *)(arg_void)); NOTE("Thread #%d: 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 *service_connection(void *arg_void) { struct service_thread_param *arg = (struct service_thread_param *)arg_void; int thread_num = arg->thread_num; NOTE("Thread #%d: Starting", 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); /* classify priority */ struct usb_conn_t *usb = NULL; int usb_failed = 0; while (!arg->tcp->is_closed && usb_failed == 0 && !g_options.terminate) { struct http_message_t *server_msg = NULL; struct http_message_t *client_msg = NULL; /* Client's request */ client_msg = http_message_new(); if (client_msg == NULL) { ERR("Thread #%d: Failed to create client message", thread_num); break; } NOTE("Thread #%d: M %p: Client msg starting", thread_num, client_msg); while (!client_msg->is_completed && !g_options.terminate) { struct http_packet_t *pkt; pkt = tcp_packet_get(arg->tcp, client_msg); if (pkt == NULL) { if (arg->tcp->is_closed) { NOTE("Thread #%d: M %p: Client closed connection", thread_num, client_msg); goto cleanup_subconn; } ERR("Thread #%d: M %p: Got null packet from tcp", thread_num, client_msg); goto cleanup_subconn; } if (usb == NULL && arg->usb_sock != NULL) { usb = usb_conn_acquire(arg->usb_sock); if (usb == NULL) { ERR("Thread #%d: M %p: Failed to acquire usb interface", thread_num, client_msg); packet_free(pkt); usb_failed = 1; goto cleanup_subconn; } usb_failed = 0; NOTE("Thread #%d: M %p: Interface #%d: acquired usb conn", thread_num, client_msg, usb->interface_index); } if (g_options.terminate) goto cleanup_subconn; NOTE("Thread #%d: M %p P %p: Pkt from tcp (buffer size: %d)\n===\n%s===", thread_num, client_msg, pkt, pkt->filled_size, hexdump(pkt->buffer, (int)pkt->filled_size)); /* In no-printer mode we simply ignore passing the client message on to the printer */ if (arg->usb_sock != NULL) { if (usb_conn_packet_send(usb, pkt) != 0) { ERR("Thread #%d: M %p P %p: Interface #%d: Unable to send client package via USB", thread_num, client_msg, pkt, usb->interface_index); packet_free(pkt); goto cleanup_subconn; } NOTE("Thread #%d: M %p P %p: Interface #%d: Client pkt done", thread_num, client_msg, pkt, usb->interface_index); } packet_free(pkt); } if (usb != NULL) NOTE("Thread #%d: M %p: Interface #%d: Client msg completed", thread_num, client_msg, usb->interface_index); else NOTE("Thread #%d: M %p: Client msg completed", thread_num, client_msg); message_free(client_msg); client_msg = NULL; if (g_options.terminate) goto cleanup_subconn; /* Server's response */ server_msg = http_message_new(); if (server_msg == NULL) { ERR("Thread #%d: Failed to create server message", thread_num); goto cleanup_subconn; } if (usb != NULL) NOTE("Thread #%d: M %p: Interface #%d: Server msg starting", thread_num, server_msg, usb->interface_index); else NOTE("Thread #%d: M %p: Server msg starting", thread_num, server_msg); while (!server_msg->is_completed && !g_options.terminate) { struct http_packet_t *pkt; if (arg->usb_sock != NULL) { pkt = usb_conn_packet_get(usb, server_msg); if (pkt == NULL) { usb_failed = 1; goto cleanup_subconn; } } else { /* In no-printer mode we "invent" the answer of the printer, a simple HTML message as a pseudo web interface */ pkt = packet_new(server_msg); snprintf((char*)(pkt->buffer), pkt->buffer_capacity - 1, "HTTP/1.1 200 OK\r\nContent-Type: text/html; name=ippusbxd.html; charset=UTF-8\r\n\r\n

ippusbxd

Debug/development mode without connection to IPP-over-USB printer

\r\n"); pkt->filled_size = 183; /* End the TCP connection, so that a web browser does not wait for more data */ server_msg->is_completed = 1; arg->tcp->is_closed = 1; } if (g_options.terminate) goto cleanup_subconn; NOTE("Thread #%d: M %p P %p: Pkt from usb (buffer size: %d)\n===\n%s===", thread_num, server_msg, pkt, pkt->filled_size, hexdump(pkt->buffer, (int)pkt->filled_size)); if (tcp_packet_send(arg->tcp, pkt) != 0) { ERR("Thread #%d: M %p P %p: Unable to send client package via TCP", thread_num, client_msg, pkt); packet_free(pkt); goto cleanup_subconn; } if (usb != NULL) NOTE("Thread #%d: M %p P %p: Interface #%d: Server pkt done", thread_num, server_msg, pkt, usb->interface_index); else NOTE("Thread #%d: M %p P %p: Server pkt done", thread_num, server_msg, pkt); packet_free(pkt); } if (usb != NULL) NOTE("Thread #%d: M %p: Interface #%d: Server msg completed", thread_num, server_msg, usb->interface_index); else NOTE("Thread #%d: M %p: Server msg completed", thread_num, server_msg); cleanup_subconn: if (usb != NULL && (arg->tcp->is_closed || usb_failed == 1)) { NOTE("Thread #%d: M %p: Interface #%d: releasing usb conn", thread_num, server_msg, usb->interface_index); usb_conn_release(usb); usb = NULL; } if (client_msg != NULL) message_free(client_msg); if (server_msg != NULL) message_free(server_msg); } NOTE("Thread #%d: Closing, %s", thread_num, g_options.terminate ? "shutdown requested" : "communication thread terminated"); tcp_conn_close(arg->tcp); free(arg); /* Execute clean-up handler */ pthread_cleanup_pop(1); pthread_exit(NULL); } static void start_daemon() { /* Capture USB device if not in no-printer mode */ struct usb_sock_t *usb_sock; /* Termination flag */ g_options.terminate = 0; if (g_options.noprinter_mode == 0) { usb_sock = usb_open(); if (usb_sock == NULL) goto cleanup_usb; } else { usb_sock = NULL; g_options.device_id = "MFG:Acme;MDL:LaserStar 2000;CMD:AppleRaster,PWGRaster;CLS:PRINTER;DES:Acme LaserStar 2000;SN:001;"; } /* Capture a socket */ 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 https://en.wikipedia.org/wiki/Ephemeral_port */ desired_port = 49152; NOTE("Access to desired port failed, trying alternative port %d", desired_port); } 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 */ int i = 0; pthread_mutex_init(&thread_register_mutex, NULL); while (!g_options.terminate) { i ++; struct service_thread_param *args = calloc(1, sizeof(*args)); if (args == NULL) { ERR("Preparing thread #%d: Failed to alloc space for thread args", i); goto cleanup_thread; } args->thread_num = i; args->usb_sock = usb_sock; /* For each request/response round we use the socket (IPv4 or IPv6) which receives data first */ args->tcp = tcp_conn_select(g_options.tcp_socket, g_options.tcp6_socket); if (g_options.terminate) goto cleanup_thread; if (args->tcp == NULL) { ERR("Preparing thread #%d: Failed to open tcp connection", i); goto cleanup_thread; } pthread_mutex_lock(&thread_register_mutex); register_service_thread(&num_service_threads, &service_threads, args); list_service_threads(num_service_threads, service_threads); pthread_mutex_unlock(&thread_register_mutex); int status = pthread_create(&args->thread_handle, NULL, &service_connection, args); if (status) { ERR("Creating thread #%d: Failed to spawn thread, error %d", i, status); pthread_mutex_lock(&thread_register_mutex); unregister_service_thread(&num_service_threads, &service_threads, i); list_service_threads(num_service_threads, service_threads); pthread_mutex_unlock(&thread_register_mutex); goto cleanup_thread; } 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 #%d 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 */ 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' }, {"no-printer", no_argument, 0, 'N' }, {"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:NB", 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 'N': g_options.noprinter_mode = 1; 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" " --no-printer\n" " -N No-printer mode, debug/developer mode which makes ippusbxd\n" " run without IPP-over-USB printer\n" , argv[0], argv[0], argv[0]); return 0; } start_daemon(); return 0; } ippusbxd-1.32/src/logging.c000066400000000000000000000045731322751550500156740ustar00rootroot00000000000000/* 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.32/src/logging.h000066400000000000000000000044711322751550500156760ustar00rootroot00000000000000/* 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.32/src/options.c000066400000000000000000000012301322751550500157240ustar00rootroot00000000000000/* 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.32/src/options.h000066400000000000000000000025401322751550500157360ustar00rootroot00000000000000/* 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 noprinter_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.32/src/tcp.c000066400000000000000000000234401322751550500150260ustar00rootroot00000000000000/* 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 "options.h" #include "logging.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, struct http_message_t *msg) { /* Alloc packet ==---------------------------------------------------== */ struct http_packet_t *pkt = packet_new(msg); if (pkt == NULL) { ERR("failed to create packet for incoming tcp message"); goto error; } size_t want_size = packet_pending_bytes(pkt); if (want_size == 0) { NOTE("TCP: Got %lu from spare buffer", pkt->filled_size); return pkt; } struct timeval tv; tv.tv_sec = 3; tv.tv_usec = 0; setsockopt(tcp->sd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(struct timeval)); while (want_size != 0 && !msg->is_completed && !g_options.terminate) { NOTE("TCP: Getting %d bytes", want_size); uint8_t *subbuffer = pkt->buffer + pkt->filled_size; ssize_t gotten_size = recv(tcp->sd, subbuffer, want_size, 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; } NOTE("TCP: Got %d bytes", gotten_size); if (gotten_size == 0) { tcp->is_closed = 1; if (pkt->filled_size == 0) { /* Client closed TCP conn */ goto error; } else { break; } } packet_mark_received(pkt, (unsigned) gotten_size); want_size = packet_pending_bytes(pkt); NOTE("TCP: Want more %d bytes; Message %scompleted", want_size, msg->is_completed ? "" : "not "); } NOTE("TCP: Received %lu bytes", pkt->filled_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_ulong = (unsigned) sent; total += sent_ulong; if (sent_ulong >= remaining) remaining = 0; else remaining -= sent_ulong; } 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; } 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); free(conn); } ippusbxd-1.32/src/tcp.h000066400000000000000000000030521322751550500150300ustar00rootroot00000000000000/* 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 "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; }; 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 *, struct http_message_t *); int tcp_packet_send(struct tcp_conn_t *, struct http_packet_t *); ippusbxd-1.32/src/usb.c000066400000000000000000000620231322751550500150310ustar00rootroot00000000000000/* 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) 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); } 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; } 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."); libusb_close(usb->printer); 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"); } static void usb_conn_mark_staled(struct usb_conn_t *conn) { if (conn->is_staled) return; struct usb_sock_t *usb = conn->parent; sem_wait(&usb->num_staled_lock); { usb->num_staled++; } sem_post(&usb->num_staled_lock); conn->is_staled = 1; } static void usb_conn_mark_moving(struct usb_conn_t *conn) { if (!conn->is_staled) return; struct usb_sock_t *usb = conn->parent; sem_wait(&usb->num_staled_lock); { usb->num_staled--; } sem_post(&usb->num_staled_lock); conn->is_staled = 0; } static int usb_all_conns_staled(struct usb_sock_t *usb) { int staled; sem_wait(&usb->num_staled_lock); { sem_wait(&usb->pool_manage_lock); { staled = usb->num_staled == usb->num_taken; } sem_post(&usb->pool_manage_lock); } sem_post(&usb->num_staled_lock); return staled; } 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; } usleep(100000); } 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; } /* 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); } /* 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", conn->interface_index); switch (status) { case LIBUSB_ERROR_NOT_FOUND: ERR("USB Interface did not exist"); goto acquire_error; case LIBUSB_ERROR_NO_DEVICE: ERR("Printer was removed"); goto acquire_error; default: break; } } while (status != 0 && !g_options.terminate); if (g_options.terminate) goto acquire_error; /* Select the IPP-USB alt setting of the interface */ libusb_set_interface_alt_setting(usb->printer, uf->libusb_interface_index, uf->interface_alt); /* 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); { int status = 0; do { /* Spinlock-like libusb does not offer a blocking call so we're left with a spinlock */ status = libusb_release_interface(usb->printer, conn->interface->libusb_interface_index); if (status) NOTE("Failed to release interface %d, retrying", conn->interface_index); } while (status != 0 && !g_options.terminate); /* 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 http_packet_t *usb_conn_packet_get(struct usb_conn_t *conn, struct http_message_t *msg) { if (msg->is_completed) return NULL; struct http_packet_t *pkt = packet_new(msg); if (pkt == NULL) { ERR("failed to create packet for incoming usb message"); goto cleanup; } /* File packet */ const int timeout = 1000; /* 1 sec */ size_t read_size_ulong = packet_pending_bytes(pkt); if (read_size_ulong == 0) return pkt; uint64_t times_staled = 0; while (read_size_ulong > 0 && !msg->is_completed && !g_options.terminate) { if (read_size_ulong >= INT_MAX) goto cleanup; int read_size = (int)read_size_ulong; /* Pad read_size to multiple of usb's max packet size */ read_size += (512 - (read_size % 512)) % 512; /* Expand buffer if needed */ if (pkt->buffer_capacity < pkt->filled_size + read_size_ulong) if (packet_expand(pkt) < 0) { ERR("Failed to ensure room for usb pkt"); goto cleanup; } int gotten_size = 0; int status = libusb_bulk_transfer(conn->parent->printer, conn->interface->endpoint_in, pkt->buffer + pkt->filled_size, read_size, &gotten_size, timeout); if (status == LIBUSB_ERROR_NO_DEVICE) { ERR("Printer has been disconnected"); goto cleanup; } if (status != 0 && status != LIBUSB_ERROR_TIMEOUT) { ERR("bulk xfer failed with error code %d", status); ERR("tried reading %d bytes", read_size); goto cleanup; } else if (status == LIBUSB_ERROR_TIMEOUT) { ERR("bulk xfer timed out, retrying ..."); ERR("tried reading %d bytes, actually read %d bytes", read_size, gotten_size); } if (gotten_size < 0) { ERR("Negative read size unexpected"); goto cleanup; } if (gotten_size > 0) { times_staled = 0; usb_conn_mark_moving(conn); } else { /* Performance Test --------------- How long we sleep here has a dramatic affect on how long it takes to load a page. Earlier versions waited a tenth of a second which resulted in minute long page loads. On my HP printer the most obvious bottleneck is the "Unified.js" file which weighs 517.87KB. My profiling looked at how shortening this sleep could improve this file's load times. The cycle count is from perf and covers an entire page load. Below are my results: 1 in 100 == 2447ms, 261M cycles 1 in 1,000 == 483ms, 500M cycles 5 in 10,000 == 433ms, 800M cycles 1 in 10,000 == 320ms, 3000M cycles */ #define TIMEOUT_RATIO (10000 / 5) static uint64_t stale_timeout = CONN_STALE_THRESHHOLD * TIMEOUT_RATIO; static uint64_t crash_timeout = PRINTER_CRASH_TIMEOUT_ANSWER * TIMEOUT_RATIO; static uint64_t skip_timeout = 1000000000 / TIMEOUT_RATIO; struct timespec sleep_dur; sleep_dur.tv_sec = 0; sleep_dur.tv_nsec = skip_timeout; nanosleep(&sleep_dur, NULL); if (status == LIBUSB_ERROR_TIMEOUT) times_staled += TIMEOUT_RATIO * timeout / 1000; else times_staled++; if (times_staled % TIMEOUT_RATIO == 0 || status == LIBUSB_ERROR_TIMEOUT) { NOTE("No bytes received for %d sec.", times_staled / TIMEOUT_RATIO); if (pkt->filled_size > 0) NOTE("Packet so far \n===\n%s===\n", hexdump(pkt->buffer, pkt->filled_size)); } if (times_staled > stale_timeout) { usb_conn_mark_staled(conn); if (pkt->filled_size > 0 || usb_all_conns_staled(conn->parent) || times_staled > crash_timeout) { ERR("USB timed out, giving up waiting for more data"); break; } } } if (gotten_size) { NOTE("USB: Getting %d bytes of %d", read_size, pkt->expected_size); NOTE("USB: Got %d bytes", gotten_size); } packet_mark_received(pkt, (size_t)gotten_size); read_size_ulong = packet_pending_bytes(pkt); } NOTE("USB: Received %d bytes of %d with type %d", pkt->filled_size, pkt->expected_size, msg->type); if (pkt->filled_size == 0) goto cleanup; return pkt; cleanup: if (pkt != NULL) packet_free(pkt); return NULL; } ippusbxd-1.32/src/usb.h000066400000000000000000000035361322751550500150420ustar00rootroot00000000000000/* 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 http_packet_t *usb_conn_packet_get(struct usb_conn_t *, struct http_message_t *); ippusbxd-1.32/systemd-udev/000077500000000000000000000000001322751550500157335ustar00rootroot00000000000000ippusbxd-1.32/systemd-udev/55-ippusbxd.rules000066400000000000000000000004441322751550500210760ustar00rootroot00000000000000# 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.32/systemd-udev/ippusbxd@.service000066400000000000000000000004321322751550500212520ustar00rootroot00000000000000[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.32/systemd-udev/ippusbxd@.service.dummy0000066400000000000000000000004551322751550500224710ustar00rootroot00000000000000[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