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