Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# -*- coding: utf-8 -*- 

2# This file is part of Xpra. 

3# Copyright (C) 2011 Serviware (Arthur Huillet, <ahuillet@serviware.com>) 

4# Copyright (C) 2010-2020 Antoine Martin <antoine@xpra.org> 

5# Copyright (C) 2008 Nathaniel Smith <njs@pobox.com> 

6# Xpra is released under the terms of the GNU GPL v2, or, at your option, any 

7# later version. See the file COPYING for details. 

8#pylint: disable-msg=E1101 

9 

10import os.path 

11import hashlib 

12 

13from xpra.simple_stats import to_std_unit, std_unit 

14from xpra.os_util import bytestostr, osexpand, load_binary_file, WIN32, POSIX 

15from xpra.util import engs, repr_ellipsized, XPRA_FILETRANSFER_NOTIFICATION_ID 

16from xpra.net.file_transfer import FileTransferAttributes 

17from xpra.server.mixins.stub_server_mixin import StubServerMixin 

18from xpra.log import Logger 

19 

20printlog = Logger("printing") 

21filelog = Logger("file") 

22 

23SAVE_PRINT_JOBS = os.environ.get("XPRA_SAVE_PRINT_JOBS", None) 

24 

25 

26""" 

27Mixin for servers that can handle file transfers and forwarded printers. 

28Printer forwarding is only supported on Posix servers with the cups backend script. 

29""" 

30class FilePrintServer(StubServerMixin): 

31 

32 def __init__(self): 

33 self.lpadmin = "" 

34 self.lpinfo = "" 

35 self.add_printer_options = [] 

36 self.file_transfer = FileTransferAttributes() 

37 

38 def init(self, opts): 

39 self.file_transfer.init_opts(opts, can_ask=False) 

40 self.lpadmin = opts.lpadmin 

41 self.lpinfo = opts.lpinfo 

42 self.add_printer_options = opts.add_printer_options 

43 #server-side printer handling is only for posix via pycups for now: 

44 self.postscript_printer = opts.postscript_printer 

45 self.pdf_printer = opts.pdf_printer 

46 

47 def threaded_setup(self): 

48 self.init_printing() 

49 

50 def init_sockets(self, sockets): 

51 #verify we have a local socket for printing: 

52 unixsockets = [info for socktype, _, info, _ in sockets if socktype=="unix-domain"] 

53 printlog("local unix domain sockets we can use for printing: %s", unixsockets) 

54 if not unixsockets and self.file_transfer.printing: 

55 if not WIN32: 

56 printlog.warn("Warning: no local sockets defined,") 

57 printlog.warn(" disabling printer forwarding") 

58 printlog("printer forwarding disabled") 

59 self.file_transfer.printing = False 

60 

61 

62 def get_server_features(self, _source): 

63 f = self.file_transfer.get_file_transfer_features() 

64 f["printer.attributes"] = ("printer-info", "device-uri") 

65 ftf = self.file_transfer.get_file_transfer_features() 

66 if self.file_transfer.file_transfer: 

67 ftf["request-file"] = True 

68 f.update(ftf) 

69 return f 

70 

71 def get_info(self, _proto): 

72 d = {} 

73 if POSIX: 

74 d.update({ 

75 "lpadmin" : self.lpadmin, 

76 "lpinfo" : self.lpinfo, 

77 "add-printer-options" : self.add_printer_options, 

78 }) 

79 if self.file_transfer.printing: 

80 from xpra.platform.printing import get_info 

81 d.update(get_info()) 

82 info = {"printing" : d} 

83 if self.file_transfer.file_transfer: 

84 fti = self.file_transfer.get_info() 

85 if self.file_transfer.file_transfer: 

86 fti["request-file"] = True 

87 info["file"] = fti 

88 return info 

89 

90 

91 def init_printing(self): 

92 printing = self.file_transfer.printing 

93 if not printing or WIN32: 

94 return 

95 try: 

96 from xpra.platform import pycups_printing 

97 pycups_printing.set_lpadmin_command(self.lpadmin) 

98 pycups_printing.set_lpinfo_command(self.lpinfo) 

99 pycups_printing.set_add_printer_options(self.add_printer_options) 

100 if self.postscript_printer: 

101 pycups_printing.add_printer_def("application/postscript", self.postscript_printer) 

102 if self.pdf_printer: 

103 pycups_printing.add_printer_def("application/pdf", self.pdf_printer) 

104 printer_definitions = pycups_printing.validate_setup() 

105 printing = bool(printer_definitions) 

106 if printing: 

107 printlog.info("printer forwarding enabled using %s", " and ".join( 

108 x.replace("application/", "") for x in printer_definitions)) 

109 else: 

110 printlog.warn("Warning: no printer definitions found,") 

111 printlog.warn(" cannot enable printer forwarding") 

112 except ImportError as e: 

113 printlog("printing module is not installed: %s", e) 

114 printing = False 

115 except Exception: 

116 printlog.error("Error: failed to set lpadmin and lpinfo commands", exc_info=True) 

117 printing = False 

118 #verify that we can talk to the socket: 

119 auth_class = self.auth_classes.get("unix-domain") 

120 if printing and auth_class: 

121 try: 

122 #this should be the name of the auth module: 

123 auth_name = auth_class[0] 

124 except: 

125 auth_name = str(auth_class) 

126 if auth_name not in ("none", "file"): 

127 printlog.warn("Warning: printer forwarding cannot be used,") 

128 printlog.warn(" it conflicts with socket authentication module '%r'", auth_name) 

129 printing = False 

130 #update file transfer attributes since printing nay have been disabled here 

131 self.file_transfer.printing = printing 

132 printlog("init_printing() printing=%s", printing) 

133 

134 def _process_print(self, _proto, packet): 

135 #ie: from the xpraforwarder we call this command: 

136 #command = ["xpra", "print", "socket:/path/tosocket", 

137 # filename, mimetype, source, title, printer, no_copies, print_options] 

138 assert self.file_transfer.printing 

139 #printlog("_process_print(%s, %s)", proto, packet) 

140 if len(packet)<3: 

141 printlog.error("Error: invalid print packet, only %i arguments", len(packet)) 

142 printlog.error(" %s", [repr_ellipsized(x) for x in packet]) 

143 return 

144 def s(b): 

145 try: 

146 return b.decode("utf-8") 

147 except Exception: 

148 return bytestostr(b) 

149 filename = s(packet[1]) 

150 file_data = packet[2] 

151 mimetype, source_uuid, title, printer, no_copies, print_options = "", "*", "unnamed document", "", 1, "" 

152 if len(packet)>=4: 

153 mimetype = bytestostr(packet[3]) 

154 if len(packet)>=5: 

155 source_uuid = bytestostr(packet[4]) 

156 if len(packet)>=6: 

157 title = s(packet[5]) 

158 if len(packet)>=7: 

159 printer = bytestostr(packet[6]) 

160 if len(packet)>=8: 

161 no_copies = int(packet[7]) 

162 if len(packet)>=9: 

163 print_options = packet[8] 

164 #parse and validate: 

165 if len(mimetype)>=128: 

166 printlog.error("Error: invalid mimetype in print packet:") 

167 printlog.error(" %s", repr_ellipsized(mimetype)) 

168 return 

169 if not isinstance(print_options, dict): 

170 s = bytestostr(print_options) 

171 print_options = {} 

172 for x in s.split(" "): 

173 parts = x.split("=", 1) 

174 if len(parts)==2: 

175 print_options[parts[0]] = parts[1] 

176 printlog("process_print: %s", (filename, mimetype, "%s bytes" % len(file_data), 

177 source_uuid, title, printer, no_copies, print_options)) 

178 printlog("process_print: got %s bytes for file %s", len(file_data), filename) 

179 #parse the print options: 

180 u = hashlib.sha1() 

181 u.update(file_data) 

182 printlog("sha1 digest: %s", u.hexdigest()) 

183 options = { 

184 "printer" : printer, 

185 "title" : title, 

186 "copies" : no_copies, 

187 "options" : print_options, 

188 "sha1" : u.hexdigest(), 

189 } 

190 printlog("parsed printer options: %s", options) 

191 if SAVE_PRINT_JOBS: 

192 self._save_print_job(filename, file_data) 

193 

194 sent = 0 

195 sources = tuple(self._server_sources.values()) 

196 printlog("will try to send to %i clients: %s", len(sources), sources) 

197 for ss in sources: 

198 if source_uuid not in ("*", ss.uuid): 

199 printlog("not sending to %s (uuid=%s, wanted uuid=%s)", ss, ss.uuid, source_uuid) 

200 continue 

201 if not ss.printing: 

202 if source_uuid!='*': 

203 printlog.warn("Warning: printing is not enabled for:") 

204 printlog.warn(" %s", ss) 

205 else: 

206 printlog("printing is not enabled for %s", ss) 

207 continue 

208 if not ss.printers: 

209 printlog.warn("Warning: client %s does not have any printers", ss.uuid) 

210 continue 

211 if printer not in ss.printers: 

212 printlog.warn("Warning: client %s does not have a '%s' printer", ss.uuid, printer) 

213 continue 

214 printlog("'%s' sent to %s for printing on '%s'", bytestostr(title or filename), ss, printer) 

215 if ss.send_file(filename, mimetype, file_data, len(file_data), True, True, options): 

216 sent += 1 

217 #warn if not sent: 

218 if sent==0: 

219 l = printlog.warn 

220 else: 

221 l = printlog.info 

222 unit_str, v = to_std_unit(len(file_data), unit=1024) 

223 l("'%s' (%i%sB) sent to %i client%s for printing", title or filename, v, unit_str, sent, engs(sent)) 

224 

225 def _save_print_job(self, filename, file_data): 

226 try: 

227 save_filename = os.path.join(SAVE_PRINT_JOBS, filename) 

228 with open(save_filename, "wb") as f: 

229 f.write(file_data) 

230 printlog.info("saved print job to: %s", save_filename) 

231 except Exception as e: 

232 printlog.error("Error: failed to save print job to %s", save_filename) 

233 printlog.error(" %s", e) 

234 

235 def _process_printers(self, proto, packet): 

236 if not self.file_transfer.printing or WIN32: 

237 printlog.error("Error: received printer definitions data") 

238 printlog.error(" but this server does not support printer forwarding") 

239 return 

240 ss = self.get_server_source(proto) 

241 if ss is None: 

242 return 

243 printers = packet[1] 

244 auth_class = self.auth_classes.get("unix-domain") 

245 ss.set_printers(printers, self.password_file, auth_class, self.encryption, self.encryption_keyfile) 

246 

247 

248 ###################################################################### 

249 # file transfers: 

250 def _process_send_file(self, proto, packet): 

251 ss = self.get_server_source(proto) 

252 if not ss: 

253 printlog.warn("Warning: invalid client source for send-file packet") 

254 return 

255 ss._process_send_file(packet) 

256 

257 def _process_ack_file_chunk(self, proto, packet): 

258 ss = self.get_server_source(proto) 

259 if not ss: 

260 printlog.warn("Warning: invalid client source for ack-file-chunk packet") 

261 return 

262 ss._process_ack_file_chunk(packet) 

263 

264 def _process_send_file_chunk(self, proto, packet): 

265 ss = self.get_server_source(proto) 

266 if not ss: 

267 printlog.warn("Warning: invalid client source for send-file-chunk packet") 

268 return 

269 ss._process_send_file_chunk(packet) 

270 

271 def _process_send_data_request(self, proto, packet): 

272 ss = self.get_server_source(proto) 

273 if not ss: 

274 printlog.warn("Warning: invalid client source for send-file-request packet") 

275 return 

276 ss._process_send_data_request(packet) 

277 

278 def _process_send_data_response(self, proto, packet): 

279 ss = self.get_server_source(proto) 

280 if not ss: 

281 printlog.warn("Warning: invalid client source for send-data-response packet") 

282 return 

283 ss._process_send_data_response(packet) 

284 

285 def _process_request_file(self, proto, packet): 

286 ss = self.get_server_source(proto) 

287 if not ss: 

288 printlog.warn("Warning: invalid client source for send-data-response packet") 

289 return 

290 try: 

291 argf = packet[1].decode("utf-8") 

292 except UnicodeDecodeError: 

293 argf = bytestostr(packet[1]) 

294 openit = packet[2] 

295 filename = os.path.abspath(osexpand(argf)) 

296 if not os.path.exists(filename): 

297 filelog.warn("Warning: the file requested does not exist:") 

298 filelog.warn(" %s", filename) 

299 ss.may_notify(XPRA_FILETRANSFER_NOTIFICATION_ID, 

300 "File not found", "The file requested does not exist:\n%s" % filename, 

301 icon_name="file") 

302 return 

303 try: 

304 stat = os.stat(filename) 

305 filelog("os.stat(%s)=%s", filename, stat) 

306 except os.error: 

307 filelog("os.stat(%s)", filename, exc_info=True) 

308 else: 

309 file_size = stat.st_size 

310 if file_size>self.file_transfer.file_size_limit or file_size>ss.file_size_limit: 

311 ss.may_notify(XPRA_FILETRANSFER_NOTIFICATION_ID, 

312 "File too large", 

313 "The file requested is too large to send:\n%s\nis %s" % (argf, std_unit(file_size)), 

314 icon_name="file") 

315 return 

316 data = load_binary_file(filename) 

317 ss.send_file(filename, "", data, len(data), openit=openit, options={"request-file" : (argf, openit)}) 

318 

319 

320 def init_packet_handlers(self): 

321 if self.file_transfer.printing: 

322 self.add_packet_handlers({ 

323 "printers": self._process_printers, 

324 "print": self._process_print, 

325 }, False) 

326 if self.file_transfer.printing or self.file_transfer.file_transfer: 

327 self.add_packet_handlers({ 

328 "send-file": self._process_send_file, 

329 "ack-file-chunk": self._process_ack_file_chunk, 

330 "send-file-chunk": self._process_send_file_chunk, 

331 "send-data-request": self._process_send_data_request, 

332 "send-data-response": self._process_send_data_response, 

333 }, False) 

334 if self.file_transfer.file_transfer: 

335 self.add_packet_handler("request-file", self._process_request_file, False)