home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 October / usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso / unix / volume16 / plp / part11 < prev    next >
Text File  |  1988-09-14  |  51KB  |  1,874 lines

  1. Subject:  v16i024:  Public lineprinter spooler package, Part11/16
  2. Newsgroups: comp.sources.unix
  3. Sender: sources
  4. Approved: rsalz@uunet.UU.NET
  5.  
  6. Submitted-by: papowell@julius.cs.umn.edu
  7. Posting-number: Volume 16, Issue 24
  8. Archive-name: plp/part11
  9.  
  10. #! /bin/sh
  11. # This is a shell archive.  Remove anything before this line, then unpack
  12. # it by saving it into a file and typing "sh file".  To overwrite existing
  13. # files, type "sh file -c".  You can also feed this as standard input via
  14. # unshar, or by typing "sh <file", e.g..  If this archive is complete, you
  15. # will see the following message at the end:
  16. #        "End of archive 11 (of 16)."
  17. # Contents:  doc/PLP/11.t src/localprinter.c src/lpr_parms.c
  18. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  19. if test -f 'doc/PLP/11.t' -a "${1}" != "-c" ; then 
  20.   echo shar: Will not clobber existing file \"'doc/PLP/11.t'\"
  21. else
  22. echo shar: Extracting \"'doc/PLP/11.t'\" \(15498 characters\)
  23. sed "s/^X//" >'doc/PLP/11.t' <<'END_OF_FILE'
  24. X.ig
  25. X$Header: 11.t,v 1.1 88/05/21 18:39:52 papowell Locked $
  26. X$log$
  27. X..
  28. X.bp
  29. X.NH 1
  30. Installation and Testing
  31. X.PP
  32. The following is a summary of the installation
  33. and test procedures for the PLP software.
  34. X.NH 2
  35. Source Files
  36. X.PP
  37. As distributed,
  38. all source and related files for the software have been
  39. collected under a common directory
  40. X$(PLP),
  41. The $(PLP) directory has the following structure.
  42. X.DS
  43. X.DT
  44. X.ta 12m +4n +4n
  45. X.L
  46. X.SM
  47. README    -- brief summary
  48. Makefile    -- calls {bin, filters, utilities}/Makefile
  49. bin/    -- directory for binaries and executables
  50. bin/Makefile    -- Makefile for executables
  51. doc/    -- documentation source, this document
  52. filters/    -- output filters
  53. lint/    -- directory for lint output
  54. man/    -- man pages
  55. src/    -- source for lpd, lpr, lpc, etc.
  56. test/    -- test programs and files
  57. utilities/    -- source for pr, cpr and other handy print formatters.
  58. printcap/    -- a method to organize printcap entries
  59. X.DE
  60. X.NH 2
  61. Printcap And Printer Permissions Files
  62. X.PP
  63. The PLP software uses a different organization of printcap and permissions
  64. files than the Berkeley LPD.
  65. All of the PLP related control and informations files are gathered in
  66. X.L /usr/spool/lpd .
  67. XFor example:
  68. X.DS
  69. X.nf
  70. X.SM
  71. X.L
  72. X.ta 15n +4n +4n +4n 8i
  73. X.vs -2
  74. X.R
  75. X.LG
  76. X/usr/spool/lpd/printcap.<host>    # printcap for <host>
  77. X/usr/spool/lpd/printer_perms.<host>    # printer permissions for <host>
  78. X/usr/spool/lpd/lpd.lock.<host>    # lpd daemon lock for <host>
  79. X/usr/spool/lpd/log.<host>    # lpd daemon log for <host>
  80. X.vs +2
  81. X.LG
  82. X.R
  83. X.DE
  84. In a networked base file system,
  85. this allows a single spool directory to be shared among all the
  86. systems using PLP.
  87. In addition,
  88. only system which are performing unspooling operations will need to
  89. have an LPD daemon running.
  90. XFinally,
  91. care has been taken to ensure that all accesses to the shared files are
  92. done by
  93. user id
  94. X.L daemon .
  95. X.PP
  96. In order to update an existing system,
  97. all that needs to be done is to create a spool directory and
  98. provide the above files.
  99. The file
  100. X.L $(PLP)/test/printer_perms.all
  101. is a prototype printer permissions file that does not provide
  102. many restrictions.
  103. X.KF
  104. X.in +1i
  105. X.nf
  106. X.DT
  107. X.L
  108. X.SM
  109. X.vs -2
  110. X.ta 4n +4n +4n +4n +4n +4n +4n +4n +4n +4n +4n 8i
  111. X#!/bin/csh
  112. X# Creating the /usr/spool/lpd directory
  113. set h=<<<<host>>>>;    # specify your host name
  114. mkdir /usr/spool/lpd
  115. ln -s /etc/printcap /usr/spool/lpd/printcap.${h}
  116. cp $(PLP)/test/printer_perms.all /usr/spool/lpd/printer_perms.${h}
  117. X.vs +2
  118. X.sp .5v
  119. X.R
  120. X.LG
  121. X.in -1i
  122. X.ce
  123. XFigure 11.1  Creating Directories
  124. X.KE
  125. X.NH 2
  126. Compilation and Installation
  127. X.PP
  128. The
  129. X.L bin
  130. directory is used to hold binaries.
  131. During the initial installation procedure,
  132. you should copy the
  133. X.L Makefile
  134. from the
  135. X.L src directory,
  136. and set any system  dependent flags in the Makefile.
  137. The flags
  138. X.L IS_<system>
  139. are used to specify the system type,
  140. and cause conditional compilation of
  141. entries in the
  142. X.L src/lp.h
  143. file.
  144. After checking the flags and their implications,
  145. X.L "make all"
  146. can be used
  147. to compile and link the programs.
  148. X.PP
  149. As distributed,
  150. the
  151. X.L XPERIMENT
  152. compilation flag is set in the Makefile,
  153. and causes a test verion of the PLP softwar to be generated.
  154. It is strongly suggested you try the test or experimental version first,
  155. i.e.- compile with the XPERIMENT option enabled,
  156. and then try a fully functional version.
  157. X.NH 2
  158. Test Version
  159. X.PP
  160. A test version of the spooling software can be generated
  161. by compiling with the XPERIMENT flag set.
  162. This causes
  163. X.L /tmp/printcap.<host>,
  164. X.L /tmp/printer_perms.<host>,
  165. and a non-priviledged INET port
  166. to be used,
  167. rather than
  168. X.L /usr/spool/lpd/printcap.<host>,
  169. and the priviledged TCP/IP printer server port.
  170. X.PP
  171. The PLP distribution
  172. X.B test
  173. directory contains prototype or debugging versions of printcap,
  174. printer_perms,
  175. and other files;
  176. X.B test/Makefile
  177. will generate and install the necessary directories,
  178. etc.,
  179. and
  180. will install the necessary filters.
  181. The test devices have their spool directories in /tmp,
  182. and should be trivial to install.
  183. Run the tests listed below
  184. to ensure that the various programs are working.
  185. Note that all the functionality of the PLP software can be checked out
  186. as an ordinary user,
  187. and you do NOT need to run as ROOT.
  188. X.PP
  189. In summary,
  190. to make the test verions,
  191. do the following.
  192. X.IP 1).
  193. Create a bin directory,
  194. copy the Makefile from src,
  195. edit the Makefile and set the various system dependencies.
  196. X.IP 2).
  197. Run
  198. X.L "make all" .
  199. At this point you will have all of your binaries in the bin
  200. directory,
  201. where you should leave them.
  202. Set up your shell $path or $PATH to use
  203. X$(PLP)/bin directory ahead of /usr/ucb,
  204. otherwise you will use the existing LPD programs.
  205. X.IP 3).
  206. Go to the
  207. X.L test
  208. directory,
  209. read the
  210. X.L test/README
  211. file,
  212. edit the
  213. X.L test/Makefile
  214. according to directions,
  215. and do
  216. X.L "make bin" .
  217. in the
  218. X.L test
  219. directory.
  220. This generates some executables and places them in the /tmp
  221. directory,
  222. as well as installing a test version of the printer permission file.
  223. X.IP 4).
  224. Now you should take a nap,
  225. play rogue,
  226. or whatever you do to relax.
  227. The next part is not fun.
  228. X.NH 3
  229. Test 1\- LPC, LPR, LPQ
  230. X.PP
  231. To run the first tests,
  232. change to the
  233. X.L test
  234. directory and do
  235. X.L "make test1" .
  236. This installs the printcap file of Figure 11.2 in /tmp/printcap:
  237. X.KF
  238. X.in +1i
  239. X.nf
  240. X.DT
  241. X.L
  242. X.SM
  243. X.vs -2
  244. X.ta 4n +4n +4n +4n +4n +4n +4n +4n +4n +4n +4n 8i
  245. X#
  246. X# TEST VERSION OF PRINTCAP FILE:
  247. X# Test 1: simple lpr functions
  248. X#
  249. test:\e
  250. X    :fx=flpdnt:ex:\e
  251. X    :pw#75:pl#66:rw:\e
  252. X    :br#9600:fs#040:fc#011:\e
  253. X    :ty=new 19200 even -tabs tandem:\e
  254. X    :lp=output:\e
  255. X    :if=/tmp/filter -delay30:\e
  256. X    :of=/tmp/lpf -D5:\e
  257. X    :sd=/tmp/test:
  258. X.vs +2
  259. X.sp .5v
  260. X.R
  261. X.LG
  262. X.in -1i
  263. X.ce
  264. XFigure 11.2  Test 1 Printcap File
  265. X.KE
  266. X.PP
  267. You should look at the code for the
  268. X.L filter
  269. and the
  270. X.L lpf
  271. programs which were created by the
  272. X.L "make bin" .
  273. The
  274. X.L filter
  275. program is a handy dandy debugging filter.
  276. The
  277. X.L lpf
  278. filter simply copies stdin to stdout,
  279. and suspends itself when it detects the stop string.
  280. The
  281. X.L lpf
  282. filter source is the skeleton for any new filters that are needed.
  283. X.PP
  284. You can test the functionality of the
  285. lpq,
  286. and lpr
  287. programs by using the illustrated commands.
  288. Sample output has been provided.
  289. X.nf
  290. X.vs -2
  291. X.DT
  292. X.SM
  293. X.L
  294. X.ta 12n +4n +4n +4n +4n +4n +4n +4n
  295. X%>lpq -D5    #should display queue (empty)
  296. lpq: pid=4233, LOG_DEBUG, First_printer: simple at Fri May 20 17:02:08 1988
  297. lpq: simple- pid=4233, LOG_DEBUG, Get_Printer: First_printer 'simple' at Fri May 20 17:02:08 1988
  298. lpq: simple- pid=4233, LOG_DEBUG, Get_printer: using Printer simple at Fri May 20 17:02:08 1988
  299. lpq: simple- pid=4233, LOG_DEBUG, Readlockfile: lockfile 'lock' at Fri May 20 17:02:08 1988
  300. lpq: simple- pid=4233, LOG_DEBUG, Readlockfile: lock, perms 0100644 at Fri May 20 17:02:08 1988
  301. lpq: simple- pid=4233, LOG_DEBUG, Readlockfile: 'lock' pid 0 len 0 at Fri May 20 17:02:08 1988
  302. lpq: simple- pid=4233, LOG_DEBUG, Checklockfile: lock server 0 at Fri May 20 17:02:08 1988
  303. lpq: simple- pid=4233, LOG_DEBUG, printstatus: ST status at Fri May 20 17:02:08 1988
  304. Printer 'simple' (attila.cs.umn.edu):
  305. X  work done at Wed May 18 09:38:59 1988
  306. X
  307. X%>lpr xx    #place job in queue
  308. lpr: Warning- File 'xx' not printed: cannot access it
  309. lpr: Fatal error- nothing to print
  310. X
  311. X%>echo hi | lpr    #place job in queue
  312. lpr: simple- Startserver: host 'attila.cs.umn.edu' server for 'simple'
  313. X    not started - Connection refused at Fri May 20 17:08:51 1988
  314. X
  315. X%>lpq    #should display queue one entry
  316. Printer 'simple' (attila.cs.umn.edu):
  317. Warning: no server present
  318. X  Rank Owner       Pr Job Host        Files                Size
  319. X   1st papowell    Z  13  attila      (stdin)              3
  320. X
  321. X%>lpc    #play with lpc
  322. X>status
  323. Queue          Jobs    Queueing     Printing
  324. simple            1    enabled      enabled (no server)
  325. X
  326. X>disable simple
  327. simple: queueing disabled
  328. X>status
  329. Queue          Jobs    Queueing     Printing
  330. simple            1    disabled     enabled (no server)
  331. X>quit
  332. X.R
  333. X.LP
  334. X.NH 3
  335. Test 2\- LPD Functionality
  336. X.PP
  337. Use the commands:
  338. X.ti +.5i
  339. X.L "echo >/tmp/error"
  340. X.ti +.5i
  341. X.L "lpd  -D5 -L /tmp/error; tail -f /tmp/error"
  342. X.br
  343. to create the error log file and to start up ldp.
  344. It is best to do this at one terminal,
  345. and then do the remaining tests at another terminal.
  346. Lucky windowing system users will,
  347. of course,
  348. simply open another window.
  349. You will be amused to watch the lpd spring into action,
  350. trying to create servers,
  351. etc.
  352. The
  353. X.L simple
  354. server will log into the
  355. X.L /tmp/simple/log
  356. file.
  357. X.PP
  358. You can remove /tmp/test/log,
  359. and see the interaction of the lpd daemon and the printer server
  360. little clearer.
  361. To kill the lpd daemon,
  362. do the following:
  363. X.DS
  364. X.DT
  365. X.L
  366. X.SM
  367. lpc lpd        ##- prints the lpd daemon id
  368. kill <pid>    # kill off the daemon
  369. rm /tmp/test/log    # remove the log file
  370. echo >/tmp/error
  371. lpd -D5 -L /tmp/error
  372. tail -f /tmp/error
  373. X.R
  374. X.DE
  375. X.PP
  376. If you are curious,
  377. try starting multiple
  378. X.L "lpd"
  379. programs;
  380. when
  381. X.L lpd
  382. is started,
  383. it checks for a running daemon,
  384. print a message,
  385. and then exits.
  386. X.PP
  387. You can examine the conditions of the servers and other
  388. activites by using
  389. X.I lpq .
  390. Note that the lpq status report includes a chatty informational piece about
  391. the job progress.
  392. X.PP
  393. The OF filter has been carefully created to print out
  394. various pieces of information.
  395. You can see what this is if you look at /tmp/test/output,
  396. where the output is going.
  397. You will notice that the IF filter is a shell script,
  398. and will do a sleep for a while.
  399. You can now test out the various lpq,
  400. lpr,
  401. and lpc functions.
  402. X.NH 3
  403. Test 3 \- Remote Servers
  404. X.PP
  405. These tests check out the functionality of inter host spooling functions.
  406. They have been written so that they will use the local host intitially,
  407. but can be extended to the remote host.
  408. X.PP
  409. In the
  410. X.L test
  411. direcotory, do
  412. X.L "make test2"
  413. to install the second form of the printcap file.
  414. You will not have to kill the lpd to do this.
  415. Now try
  416. X.ti +.5i
  417. X.L "lpq -Premote"
  418. X.br
  419. and examine the output.
  420. The lpq program will print out the local queue,
  421. and then send a message to the remote host (which is the local host)
  422. using the INET protocol.
  423. This will check out the functionality of the network communication.
  424. You can play with lpr,
  425. lpc,
  426. etc. and check this out.
  427. Try the
  428. X.I remote
  429. functions of the
  430. X.I lpc
  431. program.
  432. X.PP
  433. If you have two hosts available,
  434. install the PLP software on both of them.
  435. XEdit the printcap files so that one of them has the local test,
  436. and the other the remote test entry.
  437. Don't forget to install and modify the printer_perms file so that the
  438. remote host can access the test spool queue.
  439. Try the communications out.
  440. X.NH 3
  441. Test 4 \- Multiple Servers
  442. X.PP
  443. This will demonstrate how to have multiple servers for a single spool
  444. queue.
  445. Install the printcap file by doing
  446. make test3.
  447. Use lpq to check the status of the spool queue.
  448. Now use lpr to send a slog (10 or more) jobs to the multi queue.
  449. You can use
  450. X.ti +.5i
  451. X.L "lpq +10 -a"
  452. X.br
  453. to monitor what happens.
  454. X.NH 3
  455. Test 5 \- Serial Line Control
  456. X.PP
  457. If you have a printer,
  458. attach it to a suitable serial line,
  459. and modify the test printcap entry so that it is set up
  460. for the printer.
  461. I have found that the following procedure works pretty good.
  462. X.PP
  463. Set the
  464. X.B sh
  465. X(suppress headers or banners)
  466. in the printcap entry,
  467. and remove the
  468. X.B of
  469. entry.
  470. Set up a new entry for if,
  471. X.B "if=/tmp/serial -d30"
  472. and edit the filter program to print out the stty settings.
  473. You can now run lpr to try and send things out to the printer.
  474. You will either have lots of problems or no problems in communicating
  475. with the printer.
  476. X.PP
  477. Watch out for parity;
  478. you may have to fool around with the printer parity settings.
  479. I strongly suggest a NO parity setting.
  480. Tandem flow control is another tricky area.
  481. You may have to open the printer RW using
  482. X.B rw
  483. to get tandem flow control.
  484. This has been a problem with several installations.
  485. X.NH 2
  486. Installing a Working Version
  487. X.PP
  488. Before you install a the PLP software,
  489. you should make a copy of the existing print spooler software.
  490. There is a set of programs in the
  491. X.L backup
  492. directory of the PLP distribution that have proven to be very useful.
  493. X.IP 1).
  494. XEdit the
  495. X.L bin/Makefile ,
  496. and disable the XPERIMENT flag.
  497. Do a
  498. X.L "make clean; make all"
  499. to regenerate the working version.
  500. X.IP 2).
  501. Create a printer permissions file.
  502. You can base this on the version in the
  503. X.L test
  504. directory.
  505. The version
  506. in Figure 12.3 is useful for initial installations,
  507. and can be found in
  508. X.L test/printer_perms.all .
  509. X.KF
  510. X.nf
  511. X.SM
  512. X.L
  513. X.vs -2
  514. X# Printer permissions data base
  515. X# host user printerqueue maxpriority maxpages currentpages
  516. X# * is a wildcard
  517. X#host   user      printer     perms  pr maxpages pages
  518. X*       root      *           *      *  0        0  #root on any system
  519. X*       *         *           R      G  0        0 #anybody can do remote
  520. X.vs +2
  521. X.LG
  522. X.R
  523. X.sp .5v
  524. X.ce
  525. XFigure 11.3  Sample Printer Permissions File
  526. X.KE
  527. X.IP 3).
  528. Do a
  529. X.L "make install"
  530. and check that permissions have been set correctly.
  531. X.IP 4).
  532. Using the same tests as outlined above,
  533. make sure that lpr,
  534. lpc,
  535. and lpq
  536. are functional.
  537. X.IP 5).
  538. Start lpd
  539. and check that files are unspooled and transferred correctly.
  540. When sending files to remote sites,
  541. both must be running PLP versions.
  542. X.NH 2
  543. Organization of Printcap Information
  544. X.PP
  545. If you are working in an environment with a large number of
  546. different sites and different printers,
  547. you may find the programs in the
  548. X.L printcap
  549. directory handy to manage printcap files.
  550. The organization and management is based on the following structure.
  551. X.PP
  552. XEach printer has a physical location name and a set of aliases.
  553. XFor our sample printer,
  554. X.L printer_loc ,
  555. There is a
  556. X.L printer_loc.local
  557. file which contains a printcap entry suitable for the host which has the device
  558. attached,
  559. a
  560. X.L printer_loc.remote
  561. file which contains a printcap entry suitable for a host
  562. which can communicate directly to the remote host,
  563. and a 
  564. X.L printer_loc.forward
  565. file which contains a printcap entry suitable for a host
  566. which forwards jobs to either a local or remote host.
  567. XFor example,
  568. here are typical entries for the 3 files.
  569. These files are stored in the directory
  570. X.L printcap/devices .
  571. XFigure 12.4 is a sample of several of them.
  572. X.KF
  573. X.TS
  574. center box;
  575. lw(2.5) |  lw(2.5i) .
  576. X.L
  577. X.SM
  578. dg1_lind23.local    dg1_lind23.remote
  579. X_
  580. T{
  581. X.nf
  582. dg1_lind23.local|dg1:\e
  583. X    :sd=/usr/spool/dg1_lind23:\e
  584. X    :lp=/dev/tty09:sy=9600 -even -odd:
  585. T}    T{
  586. X.nf
  587. dg1_lind23.local|dg1:\e
  588. X    :sd=/usr/spool/dg1_lind23:\e
  589. X    :rm=attila:rp=dg1_lind23:
  590. T}
  591. X.TE
  592. X.TS
  593. center box;
  594. lw(2.5i) .
  595. X.L
  596. X.SM
  597. dg1_lind23.forward
  598. X_
  599. T{
  600. X.nf
  601. dg1_lind23.local|dg1:\e
  602. X    :sd=/usr/spool/dg1_lind23:\e
  603. X    :rm=attila:rp=dg1_lind23:
  604. T}
  605. X.TE
  606. X.ce
  607. XFigure 11.4  Sample Printcap Files
  608. X.KE
  609. X.PP
  610. XEach host will have a file containing the names of the entries in
  611. the printcap file.
  612. The
  613. X.L genpc
  614. program will generate a printcap file from the named printer file
  615. by concatenating the printcap information and placing it in a
  616. X.L host.printcap
  617. file.
  618. This file can then be copied to the desired destination.
  619. X.NH 2
  620. Checking Out Exisitng Printcaps and Filters
  621. X.PP
  622. If you have an exisiting printcap file,
  623. you can check it out by using the XPERIMENTAL form
  624. of PLP before you commit to the actual form.
  625. X.IP 1). 3
  626. XFirst,
  627. copy the original
  628. X.L /etc/printcap
  629. file to
  630. X.L /tmp/printcap.<host> .
  631. X.IP 2). 3
  632. Modify the 
  633. X.L /tmp/printcap.<host>
  634. file so that the
  635. X.L /usr/spool
  636. directories in it are modified to be
  637. X.L /tmp
  638. directories.
  639. X.IP 3). 3
  640. Compile the test version of the PLP software;
  641. there is an entry in the makefile,
  642. X.L "make test"
  643. which will do this in an efficient manner.
  644. X.IP 4). 3
  645. Set your 
  646. X.L $path
  647. to use  the test version,
  648. and run
  649. X.L "checkpc -f"
  650. to create the spool direcotories needed by the
  651. X.L /tmp/printcap.<host>
  652. version.
  653. X.IP 4). 3
  654. You can now run a parallel version of the PLP software,
  655. and check out the behaviour of devices and filters.
  656. END_OF_FILE
  657. if test 15498 -ne `wc -c <'doc/PLP/11.t'`; then
  658.     echo shar: \"'doc/PLP/11.t'\" unpacked with wrong size!
  659. fi
  660. # end of 'doc/PLP/11.t'
  661. fi
  662. if test -f 'src/localprinter.c' -a "${1}" != "-c" ; then 
  663.   echo shar: Will not clobber existing file \"'src/localprinter.c'\"
  664. else
  665. echo shar: Extracting \"'src/localprinter.c'\" \(16055 characters\)
  666. sed "s/^X//" >'src/localprinter.c' <<'END_OF_FILE'
  667. X/***************************************************************************
  668. X * U. Minnesota LPD Software * Copyright 1987, 1988, Patrick Powell
  669. X ***************************************************************************
  670. X * MODULE: localprinter.c
  671. X * local Printer queue job handler
  672. X ***************************************************************************
  673. X * Revision History: Created Wed Jan 13 15:34:42 CST 1988
  674. X * $Log:    localprinter.c,v $
  675. X * Revision 3.1  88/06/18  09:34:24  papowell
  676. X * Version 3.0- Distributed Sat Jun 18 1988
  677. X * 
  678. X * Revision 2.4  88/05/21  10:27:48  papowell
  679. X * Minor editing
  680. X * 
  681. X * Revision 2.3  88/05/19  10:33:53  papowell
  682. X * Fixed open() calls to have a 0 parameter, ie: open(f, perms, 0), where needed
  683. X * 
  684. X * Revision 2.2  88/05/14  10:17:51  papowell
  685. X * Use long format for job file names;
  686. X * Added 'fd', no forward flag;
  687. X * Control file has to have hostname and origination agree.
  688. X * 
  689. X * Revision 2.1  88/05/09  10:08:26  papowell
  690. X * PLP: Released Version
  691. X * 
  692. X * Revision 1.5  88/05/09  10:03:18  papowell
  693. X * Revised effects of -h option
  694. X * 
  695. X * Revision 1.4  88/05/05  20:08:11  papowell
  696. X * Added a NOHEADER option that allows user to suppress banner
  697. X * 
  698. X * Revision 1.3  88/03/25  14:59:44  papowell
  699. X * Debugged Version:
  700. X * 1. Added the PLP control file first transfer
  701. X * 2. Checks for MX during file transfers
  702. X * 3. Found and fixed a mysterious bug involving the SYSLOG facilities;
  703. X *     apparently they open files and then assume that they will stay
  704. X *     open.
  705. X * 4. Made sure that stdin, stdout, stderr was available at all times.
  706. X * 
  707. X * Revision 1.2  88/03/05  15:01:02  papowell
  708. X * Minor Corrections,  Lint Problems
  709. X * 
  710. X * Revision 1.1  88/03/01  11:08:31  papowell
  711. X * Initial revision
  712. X * 
  713. X ***************************************************************************/
  714. X#ifndef lint
  715. static char id_str1[] =
  716. X    "$Header: localprinter.c,v 3.1 88/06/18 09:34:24 papowell Exp $ PLP Copyright 1988 Patrick Powell";
  717. X#endif lint
  718. X
  719. X#include "lp.h"
  720. X
  721. char *Setup_filter();            /* setup filter */
  722. static int prjobs;                /* numbers of jobs done */
  723. X
  724. X/**********************************************************************
  725. X * Printinit()
  726. X * 1. set up the state to indicated 0 completed jobs
  727. X * 2. get the printer ready
  728. X **********************************************************************/
  729. Printinit()
  730. X{
  731. X    int n;
  732. X
  733. X    if(Debug>3)log(XLOG_DEBUG,"Printinit: called with prjobs %d", prjobs );
  734. X    n = Print_ready();
  735. X    return( n );
  736. X}
  737. X
  738. X/**********************************************************************
  739. X * Printerror()
  740. X * called when there is an error in the job handling
  741. X * 1. set up the state to indicated 0 completed jobs
  742. X * 2. close the printer
  743. X **********************************************************************/
  744. void
  745. Printerror()
  746. X{
  747. X    if(Debug>3)log(XLOG_DEBUG,"Printerror: called with prjobs %d", prjobs );
  748. X
  749. X    prjobs = 0;
  750. X    Print_close();
  751. X}
  752. X/**********************************************************************
  753. X * Printfinal()
  754. X * called when there is an error in the job handling
  755. X * 1. if there are completed jobs, print the trailer
  756. X * 2. close the printer
  757. X **********************************************************************/
  758. void
  759. Printfinal()
  760. X{
  761. X    if(Debug>3)log(XLOG_DEBUG,"Printfinal: called with prjobs %d", prjobs );
  762. X    /*
  763. X     * print out trailer on close with a job out
  764. X     */
  765. X    if( prjobs ){
  766. X        /*
  767. X         * FQ set means FF needed on open
  768. X         */
  769. X        if( FQ && FF && *FF ){
  770. X            if(Debug>3)log( XLOG_DEBUG, "Printfinal: FF on final");
  771. X            (void)Print_string( FF );
  772. X        }
  773. X        if( TR && *TR ){
  774. X            /*
  775. X             * TR is sent out
  776. X             */
  777. X            if(Debug>2)log( XLOG_DEBUG, "Printfinal: TR '%s'", TR);
  778. X            (void)Print_string( TR );
  779. X        }
  780. X    }
  781. X    prjobs = 0;
  782. X    Print_close();
  783. X}
  784. X
  785. X/**********************************************************************
  786. X * Printjob
  787. X *   1.  First scan extracts information which controls printing
  788. X *   2.  Set any default values not passed
  789. X *   3.  Second scan does the printing.
  790. X * Returns:  JBUSY, JFAIL, JSUCC, JABORT
  791. X * Side effects:  sets information vector  CFparm[].
  792. X **********************************************************************/
  793. Printjob(cfp, q)
  794. X    FILE *cfp;            /* control file */
  795. X    struct queue *q;    /* job entry */
  796. X{
  797. X    int i;                /* ACME Integer, Inc. */
  798. X    char parm[BUFSIZ];    /* holds a line read in */
  799. X    char *arg;            /* control line argument */
  800. X    char opt;            /* control line option */
  801. X    int  jstatus;        /* job status */
  802. X    int  perms = 'R';    /* file perms */
  803. X    long jobsize;        /* job size */
  804. X
  805. X    if( fseek( cfp, 0L, 0 ) < 0 ){
  806. X        logerr_die(XLOG_INFO, "Printjob: start- fseek failed" );
  807. X    }
  808. X    /*
  809. X     * set job status
  810. X     */
  811. X    jstatus = JABORT;    /* default */
  812. X    /*
  813. X     * initialize the CFparm array, which holds value read from file
  814. X     */
  815. X    for( i = 0; i < 26; ++i ){
  816. X        CFparm[i][0] = 0;
  817. X    } 
  818. X
  819. X    /*
  820. X     * read the control file and extract user information
  821. X     */
  822. X    jobsize = 0;
  823. X    while(fgets( parm, sizeof(parm), cfp )){
  824. X        if( (arg = index(parm, '\n')) == 0 ){
  825. X            log(XLOG_INFO,"Printjob: bad control file (%s), no endline",
  826. X                q->q_name);
  827. X            goto error;
  828. X        }
  829. X        *arg = 0;
  830. X        opt = parm[0];
  831. X        arg = parm+1;
  832. X        if( !isascii(opt) || ! isalnum( opt )){
  833. X            log( XLOG_INFO, "Printjob: bad control file (%s), line('%d'%s)",
  834. X                q->q_name,opt, arg);
  835. X            goto error;
  836. X        }
  837. X        /*
  838. X         * copy it into the appropriate place if needed
  839. X         */
  840. X        if( isupper( opt ) ){
  841. X            switch( opt ){
  842. X            case 'U':
  843. X            case 'N': break;
  844. X            default:
  845. X                if( CFparm[opt - 'A'][0] ){
  846. X                    log(XLOG_INFO,
  847. X                        "Printjob: duplicate %c parm '%s', previous:'%s'",
  848. X                        opt, arg, CFparm[opt-'A']);
  849. X                    goto error;
  850. X                }
  851. X                if( strlen( arg ) >= MAXPARMLEN ){
  852. X                    log(XLOG_INFO,
  853. X                        "Printjob: control file %s line too long:'%s'",
  854. X                        q->q_name,arg);
  855. X                    goto error;
  856. X                }
  857. X                (void)strcpy( CFparm[opt - 'A'], arg );
  858. X                break;
  859. X            }
  860. X        } else if( islower( opt )){
  861. X            if( Job_match( q->q_name, arg) == 0 ){
  862. X                logerr(XLOG_INFO,"Printjob: bad control file '%s' entry '%s'",
  863. X                    q->q_name,parm);
  864. X                goto error;
  865. X            }
  866. X            if( stat( arg, &LO_statb ) < 0 ){
  867. X                logerr( XLOG_INFO,"Printjob: cannot stat file %s",arg);
  868. X                goto error;
  869. X            }
  870. X            if( (i = Add_name( arg )) < 0 ){
  871. X                logerr( XLOG_INFO,"Printjob: too many files %s",q->q_name);
  872. X                goto error;
  873. X            }
  874. X            jobsize = jobsize + (LO_statb.st_size + 1023)/1024;
  875. X            if( MX && jobsize > MX ){
  876. X                logerr( XLOG_INFO,"Printjob: job too big %s",q->q_name);
  877. X                goto error;
  878. X            }
  879. X            switch( opt ){
  880. X                case 'f':    /* uses the IF filter */
  881. X                case 'l':
  882. X                    break;
  883. X                default:
  884. X                    if( Filter_name[opt-'a'] == 0){
  885. X                        log( XLOG_INFO,"Printjob: no %c filter",opt);
  886. X                        goto error;
  887. X                    }
  888. X                    break;
  889. X            }
  890. X        }
  891. X    }
  892. X    /*
  893. X     * Set up any parameters that were not provided to default values
  894. X     */
  895. X    if( PWIDTH[0] = 0){
  896. X        (void)sprintf( PWIDTH, "%d", PW );
  897. X    }
  898. X    /*
  899. X     * check permissions
  900. X     */
  901. X    if( strcmp( &q->q_from, FROMHOST ) ){
  902. X        log(XLOG_INFO,
  903. X            "Printjob: control file origin '%s' and H entry '%s' do not match",
  904. X            &q->q_from, FROMHOST );
  905. X            goto error;
  906. X    }
  907. X    if((Permfile && *Permfile &&
  908. X            !Checkperm(Permfile,FROMHOST,LOGNAME,First_name,&perms,(int *)0,0))
  909. X       ||(XU && *XU &&
  910. X            !Checkperm(XU,FROMHOST,LOGNAME,First_name,&perms,(int *)0,0 ) )){
  911. X        log(XLOG_INFO,
  912. X            "Sorry %s@%s, you don't have permission to use '%s'",
  913. X            LOGNAME, FROMHOST, First_name );
  914. X            goto error;
  915. X    } else if((Permfile && *Permfile &&
  916. X            !Checkperm(Permfile,FROMHOST,LOGNAME,First_name,&perms,(int *)0,1))
  917. X       ||(XU && *XU &&
  918. X            !Checkperm(XU,FROMHOST,LOGNAME,First_name,&perms,(int *)0,1 ) )){
  919. X        log( XLOG_INFO, "Sorry %s@%s, no more pages allowed on '%s'",
  920. X            LOGNAME, FROMHOST, First_name );
  921. X            goto error;
  922. X    }
  923. X    /*
  924. X     * See if there are any files to print
  925. X     */
  926. X    if( Parmcount == 0 ){
  927. X        /* no files */
  928. X        if(Debug>4)log(XLOG_DEBUG,"Printjob: no files");
  929. X        jstatus = JSUCC;
  930. X        goto error;
  931. X    }
  932. X    /*
  933. X     * Pass 2: Do the printing now
  934. X     */
  935. X    if( fseek(cfp, 0L, 0) < 0 ){
  936. X        /* this is impossible */
  937. X        logerr( XLOG_NOTICE, "Printjob: fseek %s failed",q->q_name);
  938. X        goto error;
  939. X    }
  940. X    if( QH ){
  941. X        /*
  942. X         * queuejob returns one of the Job Status settings
  943. X         */
  944. X        jstatus = queuejob(cfp, q);
  945. X        goto error;
  946. X    }
  947. X    /*
  948. X     * Put out FF on open and between jobs
  949. X     */
  950. X    if( prjobs == 0 ){
  951. X        /*
  952. X         * FO set means FF needed on open
  953. X         */
  954. X        if( LD && *LD ){
  955. X            if(Debug>3)log( XLOG_DEBUG, "Printjob: putting out LD on open");
  956. X            jstatus = Print_string( LD );
  957. X            if( jstatus != JSUCC ){
  958. X                log( XLOG_INFO, "Printjob: LD on open printing failed" );
  959. X                goto error;
  960. X            }
  961. X        }
  962. X        if( FO && FF && *FF ){
  963. X            if(Debug>3)log( XLOG_DEBUG, "Printjob: putting out FF on open");
  964. X            jstatus = Print_string( FF );
  965. X            if( jstatus != JSUCC ){
  966. X                log( XLOG_INFO, "Printjob: FF on open printing failed" );
  967. X                goto error;
  968. X            }
  969. X        }
  970. X        prjobs = 1;
  971. X    } else if( SF == 0 && FF && *FF ){
  972. X        if(Debug>3)log(XLOG_DEBUG,"Printjob: putting out FF between jobs");
  973. X        jstatus = Print_string( FF );
  974. X        if( jstatus != JSUCC ){
  975. X            log( XLOG_INFO, "Printjob: FF printing failed" );
  976. X            goto error;
  977. X        }
  978. X    }
  979. X    /*
  980. X     * print banner
  981. X     * if it has not been disabled by the SH flag
  982. X     * or the lpr -h flag was used and the AB (always banner) is clear
  983. X     */
  984. X    if( !(SH || (NOHEADER[0] && !AB)) ){
  985. X        jstatus = Print_banner();
  986. X        if( jstatus != JSUCC ){
  987. X            log( XLOG_INFO, "Printjob: banner printing failed" );
  988. X            goto error;
  989. X        }
  990. X        prjobs = 1;
  991. X    }
  992. X    /*
  993. X     * Banner Printing Program
  994. X     */
  995. X    if( BP && *BP ){
  996. X        jstatus = Banner_print(BP);
  997. X        if( jstatus != JSUCC ){
  998. X            log( XLOG_INFO, "Printjob: BP program %s failed", BP);
  999. X            goto error;
  1000. X        }
  1001. X        prjobs = 1;
  1002. X    }
  1003. X    /*
  1004. X     * print individual jobs
  1005. X     */
  1006. X    while(fgets( parm, sizeof(parm), cfp )){
  1007. X        if( (arg = index(parm, '\n')) == 0 ){
  1008. X            log( XLOG_INFO,"Printjob: bad control file format (%s), no endline",
  1009. X                q->q_name);
  1010. X            goto error;
  1011. X        }
  1012. X        *arg = 0;
  1013. X        opt = parm[0];
  1014. X        arg = parm+1;
  1015. X        if( opt == 0 || ! isalnum( opt )){
  1016. X            log( XLOG_INFO, "bad control file (%s), line(%s)",
  1017. X                q->q_name,parm);
  1018. X            goto error;
  1019. X        }
  1020. X        if( islower( opt ) ){
  1021. X            /*
  1022. X             * print the file
  1023. X             */
  1024. X            prjobs = 1;
  1025. X            jstatus = printfile( opt, arg );
  1026. X            if( jstatus != JSUCC ){
  1027. X                goto error;
  1028. X            }
  1029. X        }
  1030. X    }
  1031. X    /*
  1032. X     * End Printing Program
  1033. X     */
  1034. X    if( EP && *EP ){
  1035. X        jstatus = Banner_print(EP);
  1036. X        if( jstatus != JSUCC ){
  1037. X            log( XLOG_INFO, "Printjob: EP program %s failed", EP);
  1038. X            goto error;
  1039. X        }
  1040. X        prjobs = 1;
  1041. X    }
  1042. X
  1043. X    /*
  1044. X     * error and status reporting
  1045. X     */
  1046. error:
  1047. X    if( MAILNAME[0] ){
  1048. X        sendmail(q, jstatus);
  1049. X    }
  1050. X    if(Debug>3)log(XLOG_DEBUG,"Printjob: '%s' status %d",q->q_name,jstatus);
  1051. X    return (jstatus);
  1052. X}
  1053. X
  1054. X/***********************************************************************
  1055. X * printfile( int format; char *datafile )
  1056. X *   1. determine the format and corresponding filter command
  1057. X *   2. if 'p' format, set up pr process to format output
  1058. X *   3. output to Printer
  1059. X * Returns: JFAIL if retry, JSUCC if success, JABORT if ugly
  1060. X ***********************************************************************/
  1061. static int
  1062. printfile(format, file)
  1063. X    int format;
  1064. X    char *file;
  1065. X{
  1066. X    int dfd;            /* data file fd */
  1067. X    int status;            /* return status */
  1068. X    char *filter;        /* program Name */
  1069. X    int nofilter = 0;    /* no filter, just copy */
  1070. X    char buf[BUFSIZ];    /* hold the Printer command */
  1071. X    int p[2];            /* pipe file descriptors */
  1072. X    int prchild;        /* Printer process */
  1073. X
  1074. X    if(Debug>3)log( XLOG_DEBUG, "printfile: format %c, file %s", format, file);
  1075. X    /*
  1076. X     * Open input file
  1077. X     */
  1078. X    if ((dfd = open_daemon(file, O_RDONLY, 0)) < 0) {
  1079. X        logerr( XLOG_NOTICE,"printfile: open failed '%s'", file);
  1080. X        return(JABORT);
  1081. X    }
  1082. X    /*
  1083. X     * find filter chain to be generated
  1084. X     */
  1085. X    switch (format) {
  1086. X    /*
  1087. X     * use the filter Name tagged by 'Xf', where X is format
  1088. X     */
  1089. X    default:
  1090. X        filter = Setup_filter( format, Filter_name[format-'a']);
  1091. X        break;
  1092. X    /*
  1093. X     * 'f' and 'l' formats use the IF filter, and OF if not present
  1094. X     */
  1095. X    case 'f':    /* default */
  1096. X    case 'l':    /* ignore control */
  1097. X    case 'p':    /* print file using 'pr' */
  1098. X        if( IF == 0 || *IF == 0){
  1099. X            /*
  1100. X             * you have to use the OF filter; we don't set up a filter
  1101. X             */
  1102. X            nofilter = 1;
  1103. X        } else {
  1104. X            filter = Setup_filter( 'f', IF);
  1105. X        }
  1106. X        if( format != 'p' ){
  1107. X            break;
  1108. X        }
  1109. X        /*
  1110. X         * create a process to invoke 'pr'
  1111. X         * first, set up the pr command
  1112. X         */
  1113. X        (void)sprintf(buf, "%s -w%d -l%d %s %s",
  1114. X            PR, PWIDTH, PL, PRTITLE[0]?"-h":"", PRTITLE);
  1115. X        if(Debug>3)log( XLOG_DEBUG, "printfile: pr command '%s'", buf);
  1116. X        /*
  1117. X         * create PR process
  1118. X         */
  1119. X        if( pipe(p) < 0 ){
  1120. X            logerr_die( XLOG_NOTICE, "printfile: cannot make pipe" );
  1121. X        }
  1122. X        /*
  1123. X         * make a pipe, fork PR process
  1124. X         * Run this process as DAEMON
  1125. X         */
  1126. X        if ((prchild = fork()) == 0) {    /* child */
  1127. X            if( dup2(dfd, 0) < 0        /* file is stdin */
  1128. X             || dup2(p[1], 1) < 0 ){    /* pipe stdout */
  1129. X                logerr_die( XLOG_NOTICE, "printfile: dup2 failed, PR process" );
  1130. X            }
  1131. X            /* set the uid to be safe */
  1132. X            if( geteuid() == 0 && setreuid( Daemon_uid, Daemon_uid ) < 0 ){
  1133. X                logerr_die( XLOG_INFO, "printfile: setreuid failed" );
  1134. X            }
  1135. X            mexecv(buf);
  1136. X            logerr_die( XLOG_NOTICE,"printfile: cannot execv %s", buf);
  1137. X        } else if (prchild < 0) {
  1138. X            logerr_die( XLOG_NOTICE,"printfile: fork for pr failed");
  1139. X        }
  1140. X        (void) close(p[1]);        /* close output side */
  1141. X        (void) close(dfd);
  1142. X        dfd = p[0];    /* use pipe for input */
  1143. X        break;
  1144. X    }
  1145. X    /*
  1146. X     * start filter
  1147. X     */
  1148. X    if( nofilter ){
  1149. X        status = Print_copy( dfd );
  1150. X    } else if (filter == 0 || *filter==0) {
  1151. X        log( XLOG_INFO,"printfile: no %c filter, file %s", format,file);
  1152. X        status = JABORT;
  1153. X    } else {
  1154. X        /*
  1155. X         * start filter up
  1156. X         */
  1157. X        if(Debug>0)log( XLOG_DEBUG,
  1158. X            "printfile: format %c, file %s, filter %s", format,file,filter);
  1159. X        status = Print_filter( dfd, filter );
  1160. X    }
  1161. X    (void)close( dfd );
  1162. X    return( status );
  1163. X}
  1164. X
  1165. X/********************************************************************
  1166. X * Banner_print( char *prog)
  1167. X * Print the banner using the user specified program
  1168. X * 1. setup the filter
  1169. X * 2. start program
  1170. X * 3. wait for completion.
  1171. X ********************************************************************/
  1172. Banner_print( prog )
  1173. X    char *prog;
  1174. X{
  1175. X    char *filter;
  1176. X    int status;
  1177. X
  1178. X    if(Debug>3)log( XLOG_DEBUG, "Banner_print %s", prog);
  1179. X    filter = Setup_filter( 'f', prog);
  1180. X    if (filter == 0 || *filter==0) {
  1181. X        log( XLOG_INFO,"Banner_printer: bad program %s", prog );
  1182. X        status = JABORT;
  1183. X    } else {
  1184. X        /*
  1185. X         * start filter up
  1186. X         */
  1187. X        if(Debug>3)log( XLOG_DEBUG, "Banner_print: filter %s", filter);
  1188. X        status = Print_filter( 0, filter );
  1189. X    }
  1190. X    return( status );
  1191. X}
  1192. X/********************************************************************
  1193. X * queuejob()
  1194. X * Use the user specified Queue Handler.
  1195. X * exec'd as `qh -PPrinter -B[nsl] control_file'.
  1196. X *  exit codes are used to indicate the different success:
  1197. X *   0 - successful; 1 retry; anything else- abandon
  1198. X * Returns: JSUCC, etc.
  1199. X * NOTE: the queue handler is invoked with the control file as
  1200. X *  stdin and the Printer as stdout.
  1201. X ********************************************************************/
  1202. X
  1203. queuejob(cfp, q)
  1204. X    FILE *cfp;                /* control file */
  1205. X    struct queue *q;        /* queue entry */
  1206. X{
  1207. X    union wait status;
  1208. X    int id, pid, ret;
  1209. X    char buf[BUFSIZ];
  1210. X
  1211. X    /*
  1212. X     * set up arguments
  1213. X     */
  1214. X    (void)sprintf(buf, "%s %s", QH, Printer, q->q_name);
  1215. X    if(Debug>0)log( XLOG_DEBUG,"queuejob: %s", buf);
  1216. X    /*
  1217. X     * Open the Printer
  1218. X     */
  1219. X    Print_open();
  1220. X    if( (pid = fork()) == 0 ){    /* child */
  1221. X        if( dup2(fileno(cfp), 0) < 0 ){    /* stdin is cf */
  1222. X            logerr_die( XLOG_NOTICE, "queuejob: dup2 failed" );
  1223. X        }
  1224. X        if( dup2(Print_fd, 1) < 0 ){    /* stdin is cf */
  1225. X            logerr_die( XLOG_NOTICE, "queuejob: dup2 failed" );
  1226. X        }
  1227. X        if( geteuid() == 0 && setreuid( Daemon_uid, Daemon_uid ) < 0 ){
  1228. X            logerr_die( XLOG_INFO, "queuejob: setreuid failed" );
  1229. X        }
  1230. X        mexecv(buf);
  1231. X        logerr_die( XLOG_INFO, "queuejob: exec failed" );
  1232. X    } else if( pid < 0 ){
  1233. X        logerr_die( XLOG_INFO, "queuejob: fork failed" );
  1234. X    }
  1235. X    /*
  1236. X     * wait for process
  1237. X     */
  1238. X    if(Debug>0)log( XLOG_DEBUG, "waiting for queuejob %d", pid );
  1239. X    while ((id = wait(&status) ) > 0 && pid!=id){
  1240. X        if(Debug>3)log(XLOG_DEBUG,"queuejob: caught %d, %s", id,
  1241. X            Decode_status(&status) );
  1242. X    }
  1243. X    if(Debug>0)log( XLOG_DEBUG, "queuejob: pid %d status %s", id,
  1244. X        Decode_status(&status ) );
  1245. X    if( id < 0 || status.w_stopval != 0 || (unsigned)status.w_retcode > 1 ){
  1246. X        logerr( XLOG_INFO, "queuejob process %d failed, status (%s)",
  1247. X            id, Decode_status(&status));
  1248. X        ret = JABORT;
  1249. X    } else if( status.w_retcode == 1 ){
  1250. X        ret = JFAIL;
  1251. X    } else {
  1252. X        ret = JSUCC;
  1253. X    }
  1254. X    return (ret);
  1255. X}
  1256. END_OF_FILE
  1257. if test 16055 -ne `wc -c <'src/localprinter.c'`; then
  1258.     echo shar: \"'src/localprinter.c'\" unpacked with wrong size!
  1259. fi
  1260. # end of 'src/localprinter.c'
  1261. fi
  1262. if test -f 'src/lpr_parms.c' -a "${1}" != "-c" ; then 
  1263.   echo shar: Will not clobber existing file \"'src/lpr_parms.c'\"
  1264. else
  1265. echo shar: Extracting \"'src/lpr_parms.c'\" \(15634 characters\)
  1266. sed "s/^X//" >'src/lpr_parms.c' <<'END_OF_FILE'
  1267. X/***************************************************************************
  1268. X * U. Minnesota LPD Software * Copyright 1987, 1988, Patrick Powell
  1269. X ***************************************************************************
  1270. X * MODULE: lpr_parms.c
  1271. X * get the parameters for lpr program
  1272. X ***************************************************************************
  1273. X * Revision History: Created Mon Jan 25 17:29:45 CST 1988
  1274. X * $Log:    lpr_parms.c,v $
  1275. X * Revision 3.1  88/06/18  09:35:00  papowell
  1276. X * Version 3.0- Distributed Sat Jun 18 1988
  1277. X * 
  1278. X * Revision 2.4  88/05/25  15:42:37  papowell
  1279. X * No header flag modification
  1280. X * 
  1281. X * Revision 2.3  88/05/19  09:00:18  papowell
  1282. X * Fixed lint unused variables
  1283. X * 
  1284. X * Revision 2.2  88/05/14  10:18:40  papowell
  1285. X * Use long format for job file names;
  1286. X * Added 'fd', no forward flag;
  1287. X * Control file has to have hostname and origination agree.
  1288. X * 
  1289. X * Revision 2.1  88/05/09  10:09:23  papowell
  1290. X * PLP: Released Version
  1291. X * 
  1292. X * Revision 1.8  88/05/09  10:03:47  papowell
  1293. X * Revised effects of -h option
  1294. X * 
  1295. X * Revision 1.7  88/05/05  20:08:45  papowell
  1296. X * Added a NOHEADER option that allows user to suppress banner
  1297. X * 
  1298. X * Revision 1.6  88/04/07  12:31:53  papowell
  1299. X * Modified to use Getopt
  1300. X * 
  1301. X * Revision 1.5  88/04/06  12:13:46  papowell
  1302. X * Minor updates, changes in error message formats.
  1303. X * Elimination of the AF_UNIX connections, use AF_INET only.
  1304. X * Better error messages.
  1305. X * 
  1306. X * Revision 1.4  88/03/25  15:00:39  papowell
  1307. X * Debugged Version:
  1308. X * 1. Added the PLP control file first transfer
  1309. X * 2. Checks for MX during file transfers
  1310. X * 3. Found and fixed a mysterious bug involving the SYSLOG facilities;
  1311. X *     apparently they open files and then assume that they will stay
  1312. X *     open.
  1313. X * 4. Made sure that stdin, stdout, stderr was available at all times.
  1314. X * 
  1315. X * Revision 1.3  88/03/11  19:28:41  papowell
  1316. X * Minor Changes, Updates
  1317. X * 
  1318. X * Revision 1.2  88/03/05  15:01:51  papowell
  1319. X * Minor Corrections,  Lint Problems
  1320. X * 
  1321. X * Revision 1.1  88/03/01  11:08:50  papowell
  1322. X * Initial revision
  1323. X * 
  1324. X ***************************************************************************/
  1325. X#ifndef lint
  1326. static char id_str1[] =
  1327. X    "$Header: lpr_parms.c,v 3.1 88/06/18 09:35:00 papowell Exp $ PLP Copyright 1988 Patrick Powell";
  1328. X#endif lint
  1329. X
  1330. X#include "lpr.h"
  1331. X
  1332. X/***************************************************************************
  1333. X * Setup_parms( int argc; char **argv )
  1334. X * 1. get the parameters;
  1335. X * 2. set up the get the printcap entry
  1336. X * 3. check to see if any parameters violate the printcap restrictions
  1337. X * 4. get the sequence number
  1338. X ***************************************************************************/
  1339. X
  1340. Setup_parms( argc, argv )
  1341. X    int argc;
  1342. X    char **argv;
  1343. X{
  1344. X    /*
  1345. X     * get the parameters
  1346. X     */
  1347. X    Get_parms( argc, argv );
  1348. X    /*
  1349. X     * print the parameters passed
  1350. X     */
  1351. X    if(Debug>4)Show_parms();
  1352. X    /*
  1353. X     * get the printcap entry
  1354. X     */
  1355. X    Get_Printer(1);
  1356. X    /*
  1357. X     * check authorizations
  1358. X     */
  1359. X    Can_use();
  1360. X    /*
  1361. X     * check consistency of parameters
  1362. X     */
  1363. X    Check_parms();
  1364. X    /*
  1365. X     * get the sequence number
  1366. X     */
  1367. X    Get_sequence();
  1368. X    if(Debug>4)Show_parms();
  1369. X}
  1370. X/***************************************************************************
  1371. X * Get_parms( int argc; char **argv )
  1372. X * 1. Scan the argument list and get the flags
  1373. X ***************************************************************************/
  1374. X
  1375. static char *optstr = "#:C:D:F:J:P:R:T:U:XZ:bcdfghi:lm?nprstvw:";
  1376. Get_parms( argc, argv )
  1377. X    int argc;
  1378. X    char **argv;
  1379. X{
  1380. X    int option;
  1381. X    int i;
  1382. X
  1383. X    while( (option = Getopt(argc,argv,optstr)) != EOF ){
  1384. X        switch( option ){
  1385. X        case '#':
  1386. X            Check_int_dup( option, &Copies, Optarg );
  1387. X            break;
  1388. X        case 'C':
  1389. X            Check_str_dup( option, CLASSNAME, Optarg );
  1390. X            break;
  1391. X        case 'D':
  1392. X            Check_int_dup( option, &Debug, Optarg );
  1393. X            break;
  1394. X        case 'F':
  1395. X            if( strlen( Optarg ) != 1 ){
  1396. X                Diemsg( "bad -F format string '%s'\n",Optarg);
  1397. X            }
  1398. X            if( Format ){
  1399. X                Diemsg( "duplicate format specification -F%s\n", Optarg);
  1400. X            } else {
  1401. X                Format = *Optarg;
  1402. X            }
  1403. X            break;
  1404. X        case 'J':
  1405. X            Check_str_dup( option, JOBNAME, Optarg );
  1406. X            break;
  1407. X        case 'P':
  1408. X            if( Printer ){
  1409. X                Check_str_dup( option, Printer, Optarg );
  1410. X            }
  1411. X            if( *Optarg == 0 ){
  1412. X                Diemsg( "missing printer name in -P option\n" );
  1413. X            }
  1414. X            Printer = Optarg;
  1415. X            break;
  1416. X        case 'R':
  1417. X            Check_str_dup( option, ACCNTNAME, Optarg );
  1418. X            break;
  1419. X        case 'T':
  1420. X            Check_str_dup( option, PRTITLE, Optarg );
  1421. X            break;
  1422. X        case 'U':
  1423. X            Root_only( option );
  1424. X            Check_str_dup( option, BNRNAME, Optarg );
  1425. X            (void)strcpy( LOGNAME, Optarg );
  1426. X            break;
  1427. X        case 'X':
  1428. X            Check_dup( option, &Exper );
  1429. X#            ifdef DEBUG
  1430. X                Setup_test();
  1431. X                Tailor_names();
  1432. X#            else
  1433. X                Diemsg( "-X not allowed" );
  1434. X#            endif DEBUG
  1435. X            break;
  1436. X        case 'Z':
  1437. X            Check_str_dup( option, CLASSNAME, Optarg );
  1438. X            break;
  1439. X        case 'b':
  1440. X            Check_dup( option, &Binary );
  1441. X            break;
  1442. X        case 'h':
  1443. X            Check_dup( option, &Noheader );
  1444. X            /*
  1445. X             * set the NOHEADER flag for lpd
  1446. X             */
  1447. X            NOHEADER[0] = 'X';
  1448. X            break;
  1449. X        case 'i':
  1450. X            Check_str_dup( option, INDENT, Optarg );
  1451. X            break;
  1452. X        case 'm':
  1453. X            /*
  1454. X             * -m[Mailname]
  1455. X             */
  1456. X            if( Optarg == 0 ){
  1457. X                Check_str_dup( option, MAILNAME, Person );
  1458. X                (void)sprintf(MAILNAME,"%s@%s",Person,Host);
  1459. X            } else {
  1460. X                Check_str_dup( option, MAILNAME, Optarg );
  1461. X            }
  1462. X            break;
  1463. X        case 'r':
  1464. X            Check_dup( option, &Remove );
  1465. X            break;
  1466. X        case 's':
  1467. X            Check_dup( option, &Use_links );
  1468. X            break;
  1469. X        case 'c':
  1470. X        case 'd':
  1471. X        case 'f':
  1472. X        case 'g':
  1473. X        case 'l':
  1474. X        case 'n':
  1475. X        case 'p':
  1476. X        case 't':
  1477. X        case 'v':
  1478. X            if( Format ){
  1479. X                Diemsg( "duplicate format specification -%c\n", option);
  1480. X                exit( 1 );
  1481. X            } else {
  1482. X                Format = option;
  1483. X            }
  1484. X            break;
  1485. X        case '?':
  1486. X            break;
  1487. X        case 'w':
  1488. X            Check_str_dup( option, PWIDTH, Optarg );
  1489. X            i = atoi( PWIDTH );
  1490. X            if( i <= 0 ){
  1491. X                Diemsg( "-w <width>, width value '%s' bad", PWIDTH );
  1492. X                exit( 1 );
  1493. X            }
  1494. X            (void)sprintf( PWIDTH, "%d", i );
  1495. X            break;
  1496. X        default:
  1497. X            fatal(XLOG_INFO, "Get_parms: badparm %c", option );
  1498. X        }
  1499. X    }
  1500. X
  1501. X    /*
  1502. X     * set up the Parms[] array
  1503. X     */
  1504. X    for( ; Optind < argc; ++Optind ){
  1505. X        if( Parmcount < MAXPARMS ){
  1506. X            Parms[Parmcount].str = argv[Optind];
  1507. X            ++Parmcount;
  1508. X        } else {
  1509. X            Diemsg( "too many files to print; break job up" );
  1510. X        }
  1511. X    }
  1512. X    /*
  1513. X     * set default format
  1514. X     */
  1515. X    if( Format == 0 ){
  1516. X        Format = 'f';
  1517. X    }
  1518. X    if( FROMHOST[0] == 0 ){
  1519. X        (void)strcpy(FROMHOST, Host);
  1520. X    }
  1521. X}
  1522. X
  1523. X/***************************************************************************
  1524. X * Check_int_dup( int option, int *value, char *arg )
  1525. X * 1.  check to see if value has been set
  1526. X * 2.  if not, then get integer value from arg
  1527. X ***************************************************************************/
  1528. Check_int_dup( option, value, arg )
  1529. X    int option;
  1530. X    int *value;
  1531. X    char *arg;
  1532. X{
  1533. X    if( *value ){
  1534. X        Diemsg( "duplicate option %c", option );
  1535. X    }
  1536. X    if( sscanf( arg, "%d", value ) != 1 || *value <= 0){
  1537. X        Diemsg( "option %c parameter (%s) is not positive integer",
  1538. X            option, arg);
  1539. X    }
  1540. X}
  1541. X/***************************************************************************
  1542. X * Check_str_dup( int option, char *value, char *arg )
  1543. X * 1.  check to see if value has been set
  1544. X * 2.  if not, then set it
  1545. X ***************************************************************************/
  1546. Check_str_dup( option, value, arg )
  1547. X    int option;
  1548. X    char *value;
  1549. X    char *arg;
  1550. X{
  1551. X    if( *value ){
  1552. X        Diemsg( "duplicate option %c", option );
  1553. X    }
  1554. X    if( strlen( arg ) > MAXPARMLEN ){
  1555. X        Diemsg( "option %c arguement too long (%s)", option, arg );
  1556. X    }
  1557. X    (void)strcpy(value, arg );
  1558. X}
  1559. X/***************************************************************************
  1560. X * Check_dup( int option, int *value )
  1561. X * 1.  check to see if value has been set
  1562. X * 2.  if not, then set it
  1563. X ***************************************************************************/
  1564. Check_dup( option, value )
  1565. X    int option;
  1566. X    int *value;
  1567. X{
  1568. X    if( *value ){
  1569. X        Diemsg( "duplicate option %c", option );
  1570. X    }
  1571. X    *value = 1;
  1572. X}
  1573. X/***************************************************************************
  1574. X * Root_only( int option );
  1575. X * 1.  check to see if root
  1576. X ***************************************************************************/
  1577. Root_only( option )
  1578. X    int option;
  1579. X{
  1580. X    if( Is_root == 0 ){
  1581. X        Diemsg( "option %c can be used only by root", option );
  1582. X    }
  1583. X}
  1584. X/***************************************************************************
  1585. X * Can_use()
  1586. X * Check for permissions and priority
  1587. X * 1. RG - restrict access by group: must be in group
  1588. X * 2. printcap: check for restrictions
  1589. X * 3. XU: check for restrictions
  1590. X ***************************************************************************/
  1591. Can_use()
  1592. X{
  1593. X    int prior;        /* find the max priority */
  1594. X    int op;    /* 'R' for lpr */
  1595. X    char buf[BUFSIZ];
  1596. X    char *pf;        /* XU perm file */
  1597. X
  1598. X    /*
  1599. X     * get the full path name
  1600. X     */
  1601. X    pf = XU;
  1602. X    if( pf && *pf && pf[0] != '/' ){
  1603. X        (void)sprintf( buf, "%s/%s", SD, pf );
  1604. X        pf = buf;
  1605. X    }
  1606. X    /*
  1607. X     * check for priority
  1608. X     */
  1609. X    Priority = CLASSNAME[0];
  1610. X    if( Priority == 0 ){
  1611. X        Priority = 'Z';
  1612. X    }
  1613. X    if( !isascii(Priority) || !isupper(Priority) ){
  1614. X        Priority = 'Z';
  1615. X        Warnmsg( "Priority set to %c", Priority );
  1616. X    }
  1617. X    /*
  1618. X     * check to see if we have access restricted by group perms
  1619. X     */
  1620. X    if( !Is_root && RG && *RG && !Checkgrp( Person,RG ) ){
  1621. X        Diemsg( "Sorry %s@%s, you don't have permission to use the %s",
  1622. X            Person, Host, Printer );
  1623. X    }
  1624. X    /*
  1625. X     * check to see if we have restricted access
  1626. X     */
  1627. X    prior = Priority;
  1628. X    op = 'R';    /* able to use lpr */
  1629. X    if((Permfile && *Permfile
  1630. X        && !Checkperm(Permfile,Host,Person,First_name,&op,(int *)0,0))
  1631. X        ||(pf && *pf
  1632. X            && !Checkperm(pf,Host,Person,First_name,&op,(int *)0,0 ) )){
  1633. X        Diemsg( "Sorry %s@%s, you don't have permission to use '%s'",
  1634. X            Person, Host, Printer );
  1635. X    }
  1636. X    if((Permfile && *Permfile
  1637. X            && !Checkperm(Permfile,Host,Person,First_name,&op,&prior,1 ))
  1638. X        ||(pf && *pf
  1639. X            && !Checkperm(pf,Host,Person,First_name,&op,&prior,1 ) )){
  1640. X        Diemsg( "Sorry %s@%s, no more pages allowed on '%s'",
  1641. X            Person, Host, Printer );
  1642. X    }
  1643. X    /*
  1644. X     * now check to see if you have not exceeded your page limits
  1645. X     */
  1646. X    if( prior > Priority ){
  1647. X        Warnmsg( "maximum Priority allowed is %c", prior );
  1648. X        Priority = prior;
  1649. X    }
  1650. X    if(Debug>2)log(XLOG_DEBUG,"Can_use: %s can use %s, priority %c",
  1651. X        Person, Printer, Priority );
  1652. X}
  1653. X
  1654. X/***************************************************************************
  1655. X * Checkgrp( name, list )
  1656. X * -- check to see if person is a member of one of the groups
  1657. X * Returns: 1 if  person is a member, 0 if not
  1658. X ***************************************************************************/
  1659. X
  1660. Checkgrp( name, list )
  1661. X    char *name, *list;
  1662. X{
  1663. X    char buf[BUFSIZ];
  1664. X    char *cp, *bp;
  1665. X    struct group *gr;
  1666. X    char **grplist;
  1667. X
  1668. X    if(Debug>4)log(XLOG_DEBUG,"Checkgrp: checking for %s in %s", name, list );
  1669. X    cp = list;
  1670. X    while( cp && *cp ){
  1671. X        for( bp = buf; *cp && (*bp = *cp) && (*bp != ','); ++bp, ++cp );
  1672. X        *bp = 0;
  1673. X        if( *cp ){
  1674. X            ++cp;
  1675. X        }
  1676. X        if(Debug>5)log(XLOG_DEBUG,"Checkgrp: checking group %s", buf );
  1677. X        if( gr = getgrnam(buf) ){
  1678. X            for( grplist = gr->gr_mem; *grplist; ++grplist ){
  1679. X                if( strcmp( name, *grplist ) == 0 ){
  1680. X                    if(Debug>4)log(XLOG_DEBUG,"Checkgrp: %s in %s",
  1681. X                        name, gr->gr_name );
  1682. X                    return(1);
  1683. X                }
  1684. X            }
  1685. X        }
  1686. X    }
  1687. X    if(Debug>4)log(XLOG_DEBUG,"Checkgrp: %s not in any group" );
  1688. X    return( 0 );
  1689. X}
  1690. X/***************************************************************************
  1691. X * Check_parms()
  1692. X *    Check for parameter consistency
  1693. X *  1. Check to see if the format is allowed
  1694. X *  2. Check on the multiple copies
  1695. X ***************************************************************************/
  1696. Check_parms()
  1697. X{
  1698. X    char *msg;
  1699. X    /* check format and options */
  1700. X    if( FX && *FX && index( FX, Format ) == 0 ){
  1701. X        msg=NULL;
  1702. X        switch(Format){
  1703. X        case 'f': msg = "Normal or plain files";    break;
  1704. X        case 't': msg = "Troff files, use -Fn for Ditroff";    break;
  1705. X        case 'n': msg = "Ditroff files, use -Ft for (old) troff files";break;
  1706. X        case 'v': msg = "Varian raster images";        break;
  1707. X        case 'd': msg = "TeX intermediate files (DVI)";    break;
  1708. X        case 'g': msg = "Plot intermediate files";    break;
  1709. X        case 'c': msg = "Cifplot intermediate files";    break;
  1710. X        default:  msg = "format 'X' files"; msg[8]=Format;
  1711. X        }
  1712. X        Diemsg("Printer %s does not know how to interpret %s\n", Printer, msg);
  1713. X    }
  1714. X    if( SC && Copies > 1){
  1715. X        Warnmsg("multiple copies are not allowed");
  1716. X        Copies = 0;
  1717. X    }
  1718. X    if(MC > 0 && Copies > MC){
  1719. X        Warnmsg("only %d copies are allowed", MC);
  1720. X        Copies = MC;
  1721. X    }
  1722. X    if( Remove && !Is_root
  1723. X        && (LN == 0 || *LN == 0 || !Checkgrp( Person, LN ))){
  1724. X        Warnmsg( "-r (remove) not allowed" );
  1725. X        Remove = 0;
  1726. X    }
  1727. X#ifdef NOSYMLINK
  1728. X    if( Use_links ){
  1729. X        Warnmsg("-s (symbolic links) disabled");
  1730. X        Use_links = 0;
  1731. X    }
  1732. X#else
  1733. X    if( Use_links ){
  1734. X        /*
  1735. X         * check to see if we are a member of the right group
  1736. X         */
  1737. X        if( !Is_root && (LN == 0 || *LN == 0 || !Checkgrp( Person, LN ) )){
  1738. X            Warnmsg("-s (symbolic links) not allowed by user %s", Person);
  1739. X            Use_links = 0;
  1740. X        } else if( RM && NW ){
  1741. X            Warnmsg("-s (symbolic links) not available for this queue" );
  1742. X            Use_links = 0;
  1743. X        }
  1744. X    }
  1745. X#endif NOSYMLINK
  1746. X    if( Remove && !Use_links ){
  1747. X        Warnmsg( "-r (remove) not allowed without -s" );
  1748. X        Remove = 0;
  1749. X    }
  1750. X}
  1751. X
  1752. X/***************************************************************************
  1753. X * Get_sequence()
  1754. X * Get the job sequence number
  1755. X * 1. Check to see if queuing is enabled
  1756. X * 2. Get the job number from the .job file
  1757. X ***************************************************************************/
  1758. X
  1759. Get_sequence()
  1760. X{
  1761. X    char buf[MAXPATHLEN];        /* holds the pathname */
  1762. X    FILE *fp;                /* for sequence number */
  1763. X    int i;                    /* waiting time */
  1764. X
  1765. X    /*
  1766. X     * check to see if printing is enabled
  1767. X     */
  1768. X    if( LO == 0 || *LO == 0 || SD == 0 || *SD == 0 ){
  1769. X        fatal( XLOG_CRIT, "Get_sequence: bad printcap entry" );
  1770. X    }
  1771. X    if( LO[0] == '/' ){
  1772. X        (void)strcpy( buf, LO );
  1773. X    } else {
  1774. X        (void)sprintf( buf, "%s/%s", SD, LO );
  1775. X    }
  1776. X    /*
  1777. X     * get the sequence file name
  1778. X     */
  1779. X    (void)sprintf( buf, "%s/.seq.%s", SD, Host );
  1780. X    if(Debug>3)log(XLOG_DEBUG,"sequence file name '%s'", buf );
  1781. X    /*
  1782. X     * lock the sequence file and get a new number
  1783. X     */
  1784. X    for( i = 0;
  1785. X        (fp = Getlockfile( buf, &Job_number,(char *)0,0,&LO_statb )) == NULL
  1786. X            && i < 3;
  1787. X        ++i ){
  1788. X        sleep( (unsigned)(i+ (getpid()&1) ) );
  1789. X    }
  1790. X    if( fp == NULL ){
  1791. X        Diemsg("cannot lock sequence file %s, try later (%s)", buf,
  1792. X            Errormsg( errno ) );
  1793. X    }
  1794. X    if( !Is_root && (LO_statb.st_mode & DISABLE_QUEUE) ){
  1795. X        Diemsg( "printer %s- queueing disabled", Printer );
  1796. X    }
  1797. X    /*
  1798. X     * set the sequence number to the new value mod 1000;
  1799. X     */
  1800. X    Job_number = (Job_number+1) % 1000;
  1801. X    Setlockfile( buf, fp, Job_number, Time_str() );
  1802. X    (void)fclose(fp);
  1803. X    if(Debug>4)log(XLOG_DEBUG, "Get_sequence: number %d", Job_number);
  1804. X}
  1805. X
  1806. X/***************************************************************************
  1807. X * Show_parms()
  1808. X * Display the values of the parameters that were read.
  1809. X * 1. Print the CFparm array
  1810. X * 2. Print the other parameters
  1811. X ***************************************************************************/
  1812. X
  1813. Show_parms()
  1814. X{
  1815. X    int i;
  1816. X    char *s;
  1817. X
  1818. X    /*
  1819. X     * CFparm first:
  1820. X     */
  1821. X    (void)fprintf(stderr,"CFparm:\n");
  1822. X    for( i = 'A'; i <= 'Z'; ++i ){
  1823. X        s = CFparm[i-'A'];
  1824. X        if( s[0] ){
  1825. X            (void)fprintf(stderr," %c  '%s'\n", i, s );
  1826. X        }
  1827. X    }
  1828. X    (void)fprintf(stderr, " Format: %c\n",  Format );
  1829. X    (void)fprintf(stderr, " Copies: %d\n",  Copies );
  1830. X    (void)fprintf(stderr, " Binary: %d\n",  Binary );
  1831. X    (void)fprintf(stderr, " Use_links: %d\n",  Use_links );
  1832. X    (void)fprintf(stderr, " Exper: %d\n",  Exper );
  1833. X    (void)fprintf(stderr, " Priority: %d\n",  Priority );
  1834. X    (void)fprintf(stderr, " Job_number: %d\n",  Job_number );
  1835. X    if( Read_stdin ){
  1836. X        (void)fprintf(stderr, " Read_stdin: %s\n",  Read_stdin );
  1837. X    }
  1838. X    if( Filter_out ){
  1839. X        (void)fprintf(stderr, " Filter_out: %s\n",  Filter_out );
  1840. X    }
  1841. X    (void)fprintf(stderr, " Temp_count: %d\n",  Temp_count );
  1842. X    (void)fprintf(stderr, " Temp_max: %d\n",  Temp_max );
  1843. X    for( i = 0; i < Temp_count; ++i ){
  1844. X        s  = Temp_file[i];
  1845. X        (void)fprintf(stderr," %d  '%s'\n", i, s );
  1846. X    }
  1847. X}
  1848. END_OF_FILE
  1849. if test 15634 -ne `wc -c <'src/lpr_parms.c'`; then
  1850.     echo shar: \"'src/lpr_parms.c'\" unpacked with wrong size!
  1851. fi
  1852. # end of 'src/lpr_parms.c'
  1853. fi
  1854. echo shar: End of archive 11 \(of 16\).
  1855. cp /dev/null ark11isdone
  1856. MISSING=""
  1857. for I in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ; do
  1858.     if test ! -f ark${I}isdone ; then
  1859.     MISSING="${MISSING} ${I}"
  1860.     fi
  1861. done
  1862. if test "${MISSING}" = "" ; then
  1863.     echo You have unpacked all 16 archives.
  1864.     rm -f ark[1-9]isdone ark[1-9][0-9]isdone
  1865. else
  1866.     echo You still need to unpack the following archives:
  1867.     echo "        " ${MISSING}
  1868. fi
  1869. ##  End of shell archive.
  1870. exit 0
  1871.  
  1872.