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 <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)
     31 const int CUPS_PRINTER_SCANNER = 0x2000000;  // Scanner-only device
     32 #endif
     33 
     34 #if !defined(OS_MACOSX)
     35 GCRY_THREAD_OPTION_PTHREAD_IMPL;
     36 
     37 namespace {
     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 
     56 class 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 
     98 base::LazyInstance<GcryptInitializer> g_gcrypt_initializer =
     99     LAZY_INSTANCE_INITIALIZER;
    100 
    101 }  // namespace
    102 #endif  // !defined(OS_MACOSX)
    103 
    104 namespace printing {
    105 
    106 static const char kCUPSPrinterInfoOpt[] = "printer-info";
    107 static const char kCUPSPrinterStateOpt[] = "printer-state";
    108 static const char kCUPSPrinterTypeOpt[] = "printer-type";
    109 static const char kCUPSPrinterMakeModelOpt[] = "printer-make-and-model";
    110 
    111 class 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 
    145 PrintBackendCUPS::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 
    153 bool 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 
    216 std::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 
    226 bool 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 
    237 bool 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 
    269 std::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 
    289 bool 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 
    303 scoped_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 
    327 int 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 
    337 base::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