Home | History | Annotate | Download | only in backend
      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:
     52   GcryptInitializer() {
     53     Init();
     54   }
     55 
     56  private:
     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:
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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