1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "printing/backend/print_backend.h"
6
7 #include "build/build_config.h"
8
9 #include <dlfcn.h>
10 #include <errno.h>
11 #include <pthread.h>
12
13 #if !defined(OS_MACOSX)
14 #include <gcrypt.h>
15 #endif
16
17 #include "base/debug/leak_annotations.h"
18 #include "base/file_util.h"
19 #include "base/lazy_instance.h"
20 #include "base/logging.h"
21 #include "base/strings/string_number_conversions.h"
22 #include "base/synchronization/lock.h"
23 #include "base/values.h"
24 #include "printing/backend/cups_helper.h"
25 #include "printing/backend/print_backend_consts.h"
26 #include "url/gurl.h"
27
28 #if !defined(OS_MACOSX)
29 GCRY_THREAD_OPTION_PTHREAD_IMPL;
30
31 namespace {
32
33 // Init GCrypt library (needed for CUPS) using pthreads.
34 // There exists a bug in CUPS library, where it crashed with: "ath.c:184:
35 // _gcry_ath_mutex_lock: Assertion `*lock == ((ath_mutex_t) 0)' failed."
36 // It happened when multiple threads tried printing simultaneously.
37 // Google search for 'gnutls thread safety' provided a solution that
38 // initialized gcrypt and gnutls.
39
40 // TODO(phajdan.jr): Remove this after https://bugs.g10code.com/gnupg/issue1197
41 // gets fixed on all Linux distros we support (i.e. when they ship libgcrypt
42 // with the fix).
43
44 // Initially, we linked with -lgnutls and simply called gnutls_global_init(),
45 // but this did not work well since we build one binary on Ubuntu Hardy and
46 // expect it to run on many Linux distros. (See http://crbug.com/46954)
47 // So instead we use dlopen() and dlsym() to dynamically load and call
48 // gnutls_global_init().
49
50 class GcryptInitializer {
51 public:
GcryptInitializer()52 GcryptInitializer() {
53 Init();
54 }
55
56 private:
Init()57 void Init() {
58 const char* kGnuTlsFiles[] = {
59 "libgnutls.so.28",
60 "libgnutls.so.26",
61 "libgnutls.so",
62 };
63 gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
64 for (size_t i = 0; i < arraysize(kGnuTlsFiles); ++i) {
65 void* gnutls_lib = dlopen(kGnuTlsFiles[i], RTLD_NOW);
66 if (!gnutls_lib) {
67 VLOG(1) << "Cannot load " << kGnuTlsFiles[i];
68 continue;
69 }
70 const char* kGnuTlsInitFuncName = "gnutls_global_init";
71 int (*pgnutls_global_init)(void) = reinterpret_cast<int(*)()>(
72 dlsym(gnutls_lib, kGnuTlsInitFuncName));
73 if (!pgnutls_global_init) {
74 VLOG(1) << "Could not find " << kGnuTlsInitFuncName
75 << " in " << kGnuTlsFiles[i];
76 continue;
77 }
78 {
79 // GnuTLS has a genuine small memory leak that is easier to annotate
80 // than suppress. See http://crbug.com/176888#c7
81 // TODO(earthdok): remove this once the leak is fixed.
82 ANNOTATE_SCOPED_MEMORY_LEAK;
83 if ((*pgnutls_global_init)() != 0)
84 LOG(ERROR) << "gnutls_global_init() failed";
85 }
86 return;
87 }
88 LOG(ERROR) << "Cannot find libgnutls";
89 }
90 };
91
92 base::LazyInstance<GcryptInitializer> g_gcrypt_initializer =
93 LAZY_INSTANCE_INITIALIZER;
94
95 } // namespace
96 #endif // !defined(OS_MACOSX)
97
98 namespace printing {
99
100 static const char kCUPSPrinterInfoOpt[] = "printer-info";
101 static const char kCUPSPrinterStateOpt[] = "printer-state";
102 static const char kCUPSPrinterTypeOpt[] = "printer-type";
103 static const char kCUPSPrinterMakeModelOpt[] = "printer-make-and-model";
104
105 class PrintBackendCUPS : public PrintBackend {
106 public:
107 PrintBackendCUPS(const GURL& print_server_url,
108 http_encryption_t encryption, bool blocking);
109
110 // PrintBackend implementation.
111 virtual bool EnumeratePrinters(PrinterList* printer_list) OVERRIDE;
112 virtual std::string GetDefaultPrinterName() OVERRIDE;
113 virtual bool GetPrinterSemanticCapsAndDefaults(
114 const std::string& printer_name,
115 PrinterSemanticCapsAndDefaults* printer_info) OVERRIDE;
116 virtual bool GetPrinterCapsAndDefaults(
117 const std::string& printer_name,
118 PrinterCapsAndDefaults* printer_info) OVERRIDE;
119 virtual std::string GetPrinterDriverInfo(
120 const std::string& printer_name) OVERRIDE;
121 virtual bool IsValidPrinter(const std::string& printer_name) OVERRIDE;
122
123 protected:
~PrintBackendCUPS()124 virtual ~PrintBackendCUPS() {}
125
126 private:
127 // Following functions are wrappers around corresponding CUPS functions.
128 // <functions>2() are called when print server is specified, and plain
129 // version in another case. There is an issue specifing CUPS_HTTP_DEFAULT
130 // in the <functions>2(), it does not work in CUPS prior to 1.4.
131 int GetDests(cups_dest_t** dests);
132 base::FilePath GetPPD(const char* name);
133
134 GURL print_server_url_;
135 http_encryption_t cups_encryption_;
136 bool blocking_;
137 };
138
PrintBackendCUPS(const GURL & print_server_url,http_encryption_t encryption,bool blocking)139 PrintBackendCUPS::PrintBackendCUPS(const GURL& print_server_url,
140 http_encryption_t encryption,
141 bool blocking)
142 : print_server_url_(print_server_url),
143 cups_encryption_(encryption),
144 blocking_(blocking) {
145 }
146
EnumeratePrinters(PrinterList * printer_list)147 bool PrintBackendCUPS::EnumeratePrinters(PrinterList* printer_list) {
148 DCHECK(printer_list);
149 printer_list->clear();
150
151 cups_dest_t* destinations = NULL;
152 int num_dests = GetDests(&destinations);
153 if ((num_dests == 0) && (cupsLastError() > IPP_OK_EVENTS_COMPLETE)) {
154 VLOG(1) << "CUPS: Error getting printers from CUPS server"
155 << ", server: " << print_server_url_
156 << ", error: " << static_cast<int>(cupsLastError());
157 return false;
158 }
159
160 for (int printer_index = 0; printer_index < num_dests; ++printer_index) {
161 const cups_dest_t& printer = destinations[printer_index];
162
163 // CUPS can have 'printers' that are actually scanners. (not MFC)
164 // At least on Mac. Check for scanners and skip them.
165 const char* type_str = cupsGetOption(kCUPSPrinterTypeOpt,
166 printer.num_options, printer.options);
167 if (type_str != NULL) {
168 int type;
169 if (base::StringToInt(type_str, &type) && (type & CUPS_PRINTER_SCANNER))
170 continue;
171 }
172
173 PrinterBasicInfo printer_info;
174 printer_info.printer_name = printer.name;
175 printer_info.is_default = printer.is_default;
176
177 const char* info = cupsGetOption(kCUPSPrinterInfoOpt,
178 printer.num_options, printer.options);
179 if (info != NULL)
180 printer_info.printer_description = info;
181
182 const char* state = cupsGetOption(kCUPSPrinterStateOpt,
183 printer.num_options, printer.options);
184 if (state != NULL)
185 base::StringToInt(state, &printer_info.printer_status);
186
187 const char* drv_info = cupsGetOption(kCUPSPrinterMakeModelOpt,
188 printer.num_options,
189 printer.options);
190 if (drv_info)
191 printer_info.options[kDriverInfoTagName] = *drv_info;
192
193 // Store printer options.
194 for (int opt_index = 0; opt_index < printer.num_options; ++opt_index) {
195 printer_info.options[printer.options[opt_index].name] =
196 printer.options[opt_index].value;
197 }
198
199 printer_list->push_back(printer_info);
200 }
201
202 cupsFreeDests(num_dests, destinations);
203
204 VLOG(1) << "CUPS: Enumerated printers"
205 << ", server: " << print_server_url_
206 << ", # of printers: " << printer_list->size();
207 return true;
208 }
209
GetDefaultPrinterName()210 std::string PrintBackendCUPS::GetDefaultPrinterName() {
211 // Not using cupsGetDefault() because it lies about the default printer.
212 cups_dest_t* dests;
213 int num_dests = GetDests(&dests);
214 cups_dest_t* dest = cupsGetDest(NULL, NULL, num_dests, dests);
215 std::string name = dest ? std::string(dest->name) : std::string();
216 cupsFreeDests(num_dests, dests);
217 return name;
218 }
219
GetPrinterSemanticCapsAndDefaults(const std::string & printer_name,PrinterSemanticCapsAndDefaults * printer_info)220 bool PrintBackendCUPS::GetPrinterSemanticCapsAndDefaults(
221 const std::string& printer_name,
222 PrinterSemanticCapsAndDefaults* printer_info) {
223 PrinterCapsAndDefaults info;
224 if (!GetPrinterCapsAndDefaults(printer_name, &info) )
225 return false;
226
227 return ParsePpdCapabilities(
228 printer_name, info.printer_capabilities, printer_info);
229 }
230
GetPrinterCapsAndDefaults(const std::string & printer_name,PrinterCapsAndDefaults * printer_info)231 bool PrintBackendCUPS::GetPrinterCapsAndDefaults(
232 const std::string& printer_name,
233 PrinterCapsAndDefaults* printer_info) {
234 DCHECK(printer_info);
235
236 VLOG(1) << "CUPS: Getting caps and defaults"
237 << ", printer name: " << printer_name;
238
239 base::FilePath ppd_path(GetPPD(printer_name.c_str()));
240 // In some cases CUPS failed to get ppd file.
241 if (ppd_path.empty()) {
242 LOG(ERROR) << "CUPS: Failed to get PPD"
243 << ", printer name: " << printer_name;
244 return false;
245 }
246
247 std::string content;
248 bool res = base::ReadFileToString(ppd_path, &content);
249
250 base::DeleteFile(ppd_path, false);
251
252 if (res) {
253 printer_info->printer_capabilities.swap(content);
254 printer_info->caps_mime_type = "application/pagemaker";
255 // In CUPS, printer defaults is a part of PPD file. Nothing to upload here.
256 printer_info->printer_defaults.clear();
257 printer_info->defaults_mime_type.clear();
258 }
259
260 return res;
261 }
262
GetPrinterDriverInfo(const std::string & printer_name)263 std::string PrintBackendCUPS::GetPrinterDriverInfo(
264 const std::string& printer_name) {
265 cups_dest_t* destinations = NULL;
266 int num_dests = GetDests(&destinations);
267 std::string result;
268 for (int printer_index = 0; printer_index < num_dests; ++printer_index) {
269 const cups_dest_t& printer = destinations[printer_index];
270 if (printer_name == printer.name) {
271 const char* info = cupsGetOption(kCUPSPrinterMakeModelOpt,
272 printer.num_options,
273 printer.options);
274 if (info)
275 result = *info;
276 }
277 }
278
279 cupsFreeDests(num_dests, destinations);
280 return result;
281 }
282
IsValidPrinter(const std::string & printer_name)283 bool PrintBackendCUPS::IsValidPrinter(const std::string& printer_name) {
284 // This is not very efficient way to get specific printer info. CUPS 1.4
285 // supports cupsGetNamedDest() function. However, CUPS 1.4 is not available
286 // everywhere (for example, it supported from Mac OS 10.6 only).
287 PrinterList printer_list;
288 EnumeratePrinters(&printer_list);
289
290 PrinterList::iterator it;
291 for (it = printer_list.begin(); it != printer_list.end(); ++it)
292 if (it->printer_name == printer_name)
293 return true;
294 return false;
295 }
296
CreateInstance(const base::DictionaryValue * print_backend_settings)297 scoped_refptr<PrintBackend> PrintBackend::CreateInstance(
298 const base::DictionaryValue* print_backend_settings) {
299 #if !defined(OS_MACOSX)
300 // Initialize gcrypt library.
301 g_gcrypt_initializer.Get();
302 #endif
303
304 std::string print_server_url_str, cups_blocking;
305 int encryption = HTTP_ENCRYPT_NEVER;
306 if (print_backend_settings) {
307 print_backend_settings->GetString(kCUPSPrintServerURL,
308 &print_server_url_str);
309
310 print_backend_settings->GetString(kCUPSBlocking,
311 &cups_blocking);
312
313 print_backend_settings->GetInteger(kCUPSEncryption, &encryption);
314 }
315 GURL print_server_url(print_server_url_str.c_str());
316 return new PrintBackendCUPS(print_server_url,
317 static_cast<http_encryption_t>(encryption),
318 cups_blocking == kValueTrue);
319 }
320
GetDests(cups_dest_t ** dests)321 int PrintBackendCUPS::GetDests(cups_dest_t** dests) {
322 if (print_server_url_.is_empty()) { // Use default (local) print server.
323 return cupsGetDests(dests);
324 } else {
325 HttpConnectionCUPS http(print_server_url_, cups_encryption_);
326 http.SetBlocking(blocking_);
327 return cupsGetDests2(http.http(), dests);
328 }
329 }
330
GetPPD(const char * name)331 base::FilePath PrintBackendCUPS::GetPPD(const char* name) {
332 // cupsGetPPD returns a filename stored in a static buffer in CUPS.
333 // Protect this code with lock.
334 CR_DEFINE_STATIC_LOCAL(base::Lock, ppd_lock, ());
335 base::AutoLock ppd_autolock(ppd_lock);
336 base::FilePath ppd_path;
337 const char* ppd_file_path = NULL;
338 if (print_server_url_.is_empty()) { // Use default (local) print server.
339 ppd_file_path = cupsGetPPD(name);
340 if (ppd_file_path)
341 ppd_path = base::FilePath(ppd_file_path);
342 } else {
343 // cupsGetPPD2 gets stuck sometimes in an infinite time due to network
344 // configuration/issues. To prevent that, use non-blocking http connection
345 // here.
346 // Note: After looking at CUPS sources, it looks like non-blocking
347 // connection will timeout after 10 seconds of no data period. And it will
348 // return the same way as if data was completely and sucessfully downloaded.
349 HttpConnectionCUPS http(print_server_url_, cups_encryption_);
350 http.SetBlocking(blocking_);
351 ppd_file_path = cupsGetPPD2(http.http(), name);
352 // Check if the get full PPD, since non-blocking call may simply return
353 // normally after timeout expired.
354 if (ppd_file_path) {
355 // There is no reliable way right now to detect full and complete PPD
356 // get downloaded. If we reach http timeout, it may simply return
357 // downloaded part as a full response. It might be good enough to check
358 // http->data_remaining or http->_data_remaining, unfortunately http_t
359 // is an internal structure and fields are not exposed in CUPS headers.
360 // httpGetLength or httpGetLength2 returning the full content size.
361 // Comparing file size against that content length might be unreliable
362 // since some http reponses are encoded and content_length > file size.
363 // Let's just check for the obvious CUPS and http errors here.
364 ppd_path = base::FilePath(ppd_file_path);
365 ipp_status_t error_code = cupsLastError();
366 int http_error = httpError(http.http());
367 if (error_code > IPP_OK_EVENTS_COMPLETE || http_error != 0) {
368 LOG(ERROR) << "Error downloading PPD file"
369 << ", name: " << name
370 << ", CUPS error: " << static_cast<int>(error_code)
371 << ", HTTP error: " << http_error;
372 base::DeleteFile(ppd_path, false);
373 ppd_path.clear();
374 }
375 }
376 }
377 return ppd_path;
378 }
379
380 } // namespace printing
381