Skip to content

Commit 9e5de55

Browse files
author
Craig Ringer
committed
SCO Openserver 5.0.5 replacement lpd
1 parent 1a01926 commit 9e5de55

File tree

2 files changed

+340
-0
lines changed

2 files changed

+340
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
#!/usr/bin/env python
2+
#
3+
# Install this script as `/usr/bin/lp', renaming the old lp
4+
# command. Make sure printserver.py is running on your CUPS
5+
# server and ready to accept data.
6+
#
7+
# You need Python (1.3.x) on your SCO box to run this.
8+
#
9+
#
10+
# This spooler uses the following very simple network protocol:
11+
# First, a handshake string is sent. This is a fixed-size message
12+
# of the form:
13+
#
14+
# SPOOL queuename job-bytes
15+
#
16+
# "SPOOL" is a literal five byte string.
17+
#
18+
# queuename is a string exactly thirty bytes long that encodes the
19+
# name of the print queue, with a null byte terminating the name.
20+
# The remainder of the string is padding and is ignored.
21+
#
22+
# job-bytes is a string exactly 10 bytes long. It is a number in ASCII
23+
# representation, padded by spaces with no sign, that indicates the size
24+
# of the file that will be sent by the client.
25+
#
26+
# Total message size is 45 bytes. There is no terminator, and no
27+
# separator between fields.
28+
#
29+
#
30+
#
31+
# The server replies to this message with another fixed size message,
32+
# simply the five-byte string:
33+
#
34+
# READY
35+
#
36+
# without any terminator.
37+
#
38+
#
39+
#
40+
# The client proceeds to send the print job data. When it's sent the last
41+
# byte it waits for a reply from the server.
42+
#
43+
#
44+
# The server reads client data until the last byte is received. It spools
45+
# it, then informs the client of the result with a simple fixed-length
46+
# 7 byte message, either:
47+
#
48+
# SUCCESS
49+
#
50+
# or
51+
#
52+
# FAILURE
53+
#
54+
# (unterminated strings). No additional error info is provided; that'll be
55+
# logged server side.
56+
#
57+
# After sending status, the server closes its socket. The client closes
58+
# its socket when it receives the ack message.
59+
#
60+
61+
#
62+
import os
63+
import sys
64+
import socket
65+
import threading
66+
import tempfile
67+
import syslog
68+
69+
printer_map = {
70+
'hp' : 'iprint',
71+
'P1' : 'iprint',
72+
'P2' : 'accounts'
73+
}
74+
75+
listen_host = "10.0.0.10"
76+
spoolport = 6668
77+
78+
class ClientThread(threading.Thread):
79+
80+
def __init__(self, sock_info):
81+
threading.Thread.__init__(self)
82+
(self.sock, self.client_address) = sock_info
83+
84+
def _log(self, prio, msg):
85+
syslog.syslog(syslog.LOG_DAEMON|prio, "exprintserver (%s:%s): " % (self.client_address) + msg)
86+
87+
def run(self):
88+
try:
89+
self._log(syslog.LOG_DEBUG, "connect")
90+
# A 30-second timeout is set on socket I/O operations
91+
# so that protocol issues or unexpected remote end death
92+
# don't cause our threads to hang around forever.
93+
self.sock.settimeout(30)
94+
self.expect_handshake()
95+
self.send_handshake_reply()
96+
self.read_job_data()
97+
if self._should_ignore_job():
98+
status_ok = True
99+
else:
100+
status_ok = self.spool_job()
101+
self.send_job_ack(status_ok)
102+
self.shutdown_connection()
103+
self._log(syslog.LOG_DEBUG, "disconnect")
104+
except Exception, e:
105+
self._log(syslog.LOG_ERR, "exception during client communication: " + str(e))
106+
107+
def _recv_fixed_msg(self,length_bytes):
108+
bytes_remaining = length_bytes
109+
msg = ""
110+
while bytes_remaining > 0:
111+
frag = self.sock.recv(bytes_remaining)
112+
if (frag == ""):
113+
raise Exception("Unexpected EOF in fixed msg recv")
114+
bytes_remaining = bytes_remaining - len(frag)
115+
msg = msg + frag
116+
return msg
117+
118+
def _send_fixed_msg(self,msg):
119+
totalsent = 0
120+
msglen = len(msg)
121+
while totalsent < msglen:
122+
sent = self.sock.send(msg[totalsent:])
123+
if sent == 0:
124+
raise Exception("Unexpected EOF in fixed msg send")
125+
totalsent = totalsent + sent
126+
127+
def expect_handshake(self):
128+
msg = self._recv_fixed_msg(45)
129+
if not msg.startswith("SPOOL"):
130+
raise Exception("Invalid handshake")
131+
self.queuename = msg[5:35].strip()
132+
jobsize_str = msg[35:45].strip()
133+
self.jobsize = int(jobsize_str)
134+
if self.jobsize == 0:
135+
raise Exception("Zero sized job proposed")
136+
137+
def send_handshake_reply(self):
138+
self._send_fixed_msg("READY")
139+
140+
def read_job_data(self):
141+
# spool the fucker into RAM for now
142+
jobdata = self._recv_fixed_msg(self.jobsize)
143+
self.f = tempfile.NamedTemporaryFile()
144+
self.f.write(jobdata)
145+
self.jobfilename = self.f.name
146+
self.f.flush()
147+
148+
def _should_ignore_job(self):
149+
# Return true if this job should be discarded unprinted.
150+
# this lets us filter jobs from the system based on their contents.
151+
self.f.seek(0)
152+
if self.f.read().find("Cannot get TTY") != -1:
153+
# Ignore "Cannot get TTY" error messages instead of printing them.
154+
self._log(syslog.LOG_INFO, "ignoring job '%s' to '%s' - Cannot get TTY error" % (self.jobfilename, self.queuename))
155+
return True
156+
return False
157+
158+
def spool_job(self):
159+
"""Note: this function may be skipped by the server, so it must not be involved
160+
in any I/O with the client lest the client get confused if it's skipped."""
161+
print "before", self.queuename
162+
self.queuename = printer_map.get(self.queuename, self.queuename)
163+
print "after", self.queuename
164+
self._log(syslog.LOG_INFO, "spooling job '%s' to '%s'" % (self.jobfilename, self.queuename))
165+
lpcmd = "lp '-d%s' '%s' >/dev/null 2>/dev/null" % (self.queuename, self.jobfilename)
166+
print lpcmd
167+
ret = os.system(lpcmd)
168+
return ret >> 8 == 0
169+
170+
def send_job_ack(self,ok):
171+
if ok:
172+
msg = "SUCCESS"
173+
else:
174+
msg = "FAILURE"
175+
self._send_fixed_msg(msg)
176+
self.sock.shutdown(1)
177+
178+
def shutdown_connection(self):
179+
self.sock.shutdown(0)
180+
self.sock.close()
181+
182+
def main():
183+
try:
184+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
185+
s.bind((listen_host, spoolport))
186+
s.listen(5)
187+
syslog.syslog(syslog.LOG_DAEMON|syslog.LOG_INFO, "exprintserver: startup complete")
188+
except Exception, e:
189+
syslog.syslog(syslog.LOG_DAEMON|syslog.LOG_ERR, "exprintserver: startup failed: " + str(e))
190+
sys.exit(1)
191+
try:
192+
while 1:
193+
try:
194+
ct = ClientThread(s.accept())
195+
ct.start()
196+
except socket.timeout:
197+
syslog.syslog(syslog.LOG_DAEMON|syslog.LOG_ERR, "exprintserver: unexpected timeout on listening socket")
198+
except KeyboardInterrupt:
199+
return
200+
except Exception, e:
201+
syslog.syslog(syslog.LOG_DAEMON|syslog.LOG_ERR, "exprintserver: unexpected exception during client thread setup: " + str(e))
202+
203+
def close_stdio():
204+
#sys.stdout.close()
205+
#sys.stdin.close()
206+
#sys.stderr.close()
207+
#os.close(0)
208+
#os.close(1)
209+
#os.close(2)
210+
pass
211+
212+
if __name__ == '__main__':
213+
close_stdio()
214+
main()
215+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
#!/usr/local/bin/python
2+
#
3+
# See http://blog.ringerc.id.au/2010/07/how-to-make-sco-openserver-505-printing.html
4+
5+
spoolhost = "10.0.0.10"
6+
spoolport = 6668
7+
defaultqueue = "iprint"
8+
9+
import sys
10+
import os
11+
import socket
12+
import string
13+
14+
def syslog(facility, message):
15+
cmd = "/usr/bin/logger '%s' \"exprint: %s\"" % (facility, message)
16+
os.system(cmd)
17+
18+
def fatal(errmsg, exc):
19+
syslog("local1.error", "%s (%s)" % (errmsg, exc))
20+
sys.exit(1)
21+
22+
class Client:
23+
24+
def __init__(self):
25+
pass
26+
27+
def spool_socket(self, queue, spoolfile):
28+
self.spoolfile = spoolfile
29+
self.jobsize = os.stat(spoolfile)[6]
30+
self.queue = queue
31+
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
32+
self.sock.connect((spoolhost, spoolport))
33+
self.send_handshake()
34+
self.expect_handshake_reply()
35+
self.send_job_data()
36+
ok = self.expect_job_ack()
37+
self.shutdown_connection()
38+
return ok
39+
40+
def _recv_fixed_msg(self,length_bytes):
41+
bytes_remaining = length_bytes
42+
msg = ""
43+
while bytes_remaining > 0:
44+
frag = self.sock.recv(bytes_remaining)
45+
if (frag == ""):
46+
raise Exception("Unexpected EOF in fixed msg recv")
47+
bytes_remaining = bytes_remaining - len(frag)
48+
msg = msg + frag
49+
return msg
50+
51+
def _send_fixed_msg(self,msg):
52+
totalsent = 0
53+
msglen = len(msg)
54+
while totalsent < msglen:
55+
sent = self.sock.send(msg[totalsent:])
56+
if sent == 0:
57+
raise Exception("Unexpected EOF in fixed msg send")
58+
totalsent = totalsent + sent
59+
60+
def send_handshake(self):
61+
self._send_fixed_msg("SPOOL%30s%10s" % (self.queue, self.jobsize))
62+
63+
def expect_handshake_reply(self):
64+
if self._recv_fixed_msg(5) != "READY":
65+
raise Exception("Unexpected handshake reply")
66+
67+
def send_job_data(self):
68+
# Hack: just slurp the job into RAM
69+
data = open(self.spoolfile, "rb").read(self.jobsize)
70+
if len(data) != self.jobsize:
71+
raise Exception("Job size mismatch: expected %s read %s" % (self.jobsize, len(data)))
72+
self._send_fixed_msg(data)
73+
self.sock.shutdown(1)
74+
75+
def expect_job_ack(self):
76+
ack = self._recv_fixed_msg(7)
77+
return ack == "SUCCESS"
78+
79+
def shutdown_connection(self):
80+
self.sock.shutdown(0)
81+
self.sock.close()
82+
83+
def spool_lpd(queue, spoolfile):
84+
print_cmd = "/usr/bin/lp '-d%s' '%s' >/dev/null 2>/dev/null" % (queue, spoolfile)
85+
ret = os.system(print_cmd)
86+
if (ret >> 8) == 0 or (ret >> 8) == 1:
87+
syslog("local1.info", "spooled %s to %s" % (spoolfile, queue))
88+
else:
89+
syslog("local1.error", "spooler error sending %s to %s (ret: %s)" % (spoolfile, queue, ret >> 8))
90+
91+
def spool(queue, spoolfile):
92+
Client().spool_socket(queue, spoolfile)
93+
94+
def main():
95+
spoolfile = None
96+
queue = defaultqueue
97+
98+
# Test for args
99+
if len(sys.argv) < 2:
100+
print "Usage: %s [-dPRINTERNAME] /path/to/printfile" % sys.argv[0]
101+
sys.exit(1)
102+
103+
# Test for printer override
104+
if sys.argv[1][:2] == "-d":
105+
queue = sys.argv[1][2:]
106+
spoolfile = sys.argv[2]
107+
else:
108+
spoolfile = sys.argv[1]
109+
110+
# check print file
111+
try:
112+
finfo = os.stat(spoolfile)
113+
if finfo[5] == 0:
114+
fatal("Zero size spool file %s" % spoolfile, e)
115+
except OSError, e:
116+
fatal("Spool file %s missing or inaccessible" % spoolfile, e)
117+
118+
# spool print file
119+
spool(queue, spoolfile)
120+
121+
# and move the tempfile into the PRINTED directory
122+
os.rename(spoolfile, os.path.join("/usr/tmp/PRINTED", string.split(spoolfile, "/")[-1]))
123+
124+
if __name__ == '__main__':
125+
main()

0 commit comments

Comments
 (0)