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 "cloud_print/virtual_driver/win/port_monitor/port_monitor.h"
6
7#include <lmcons.h>
8#include <shellapi.h>
9#include <shlobj.h>
10#include <strsafe.h>
11#include <userenv.h>
12#include <windows.h>
13#include <winspool.h>
14
15#include "base/at_exit.h"
16#include "base/command_line.h"
17#include "base/file_util.h"
18#include "base/files/file_enumerator.h"
19#include "base/logging.h"
20#include "base/path_service.h"
21#include "base/process/process.h"
22#include "base/process/launch.h"
23#include "base/strings/string16.h"
24#include "base/win/registry.h"
25#include "base/win/scoped_handle.h"
26#include "base/win/windows_version.h"
27#include "chrome/common/chrome_switches.h"
28#include "chrome/installer/launcher_support/chrome_launcher_support.h"
29#include "cloud_print/common/win/cloud_print_utils.h"
30#include "cloud_print/virtual_driver/win/port_monitor/spooler_win.h"
31#include "cloud_print/virtual_driver/win/virtual_driver_consts.h"
32#include "cloud_print/virtual_driver/win/virtual_driver_helpers.h"
33
34namespace cloud_print {
35
36namespace {
37
38const wchar_t kIePath[] = L"Internet Explorer\\iexplore.exe";
39
40const char kChromeInstallUrl[] =
41    "http://google.com/cloudprint/learn/chrome.html";
42
43const wchar_t kCloudPrintRegKey[] = L"Software\\Google\\CloudPrint";
44
45const wchar_t kXpsMimeType[] = L"application/vnd.ms-xpsdocument";
46
47const wchar_t kAppDataDir[] = L"Google\\Cloud Printer";
48
49struct MonitorData {
50  scoped_ptr<base::AtExitManager> at_exit_manager;
51};
52
53struct PortData {
54  PortData() : job_id(0), printer_handle(NULL), file(0) {
55  }
56  ~PortData() {
57    Close();
58  }
59  void Close() {
60    if (printer_handle) {
61      ClosePrinter(printer_handle);
62      printer_handle = NULL;
63    }
64    if (file) {
65      file_util::CloseFile(file);
66      file = NULL;
67    }
68  }
69  DWORD job_id;
70  HANDLE printer_handle;
71  FILE* file;
72  base::FilePath file_path;
73};
74
75typedef struct {
76  ACCESS_MASK granted_access;
77} XcvUiData;
78
79
80MONITORUI g_monitor_ui = {
81  sizeof(MONITORUI),
82  MonitorUiAddPortUi,
83  MonitorUiConfigureOrDeletePortUI,
84  MonitorUiConfigureOrDeletePortUI
85};
86
87MONITOR2 g_monitor_2 = {
88  sizeof(MONITOR2),
89  Monitor2EnumPorts,
90  Monitor2OpenPort,
91  NULL,           // OpenPortEx is not supported.
92  Monitor2StartDocPort,
93  Monitor2WritePort,
94  Monitor2ReadPort,
95  Monitor2EndDocPort,
96  Monitor2ClosePort,
97  NULL,           // AddPort is not supported.
98  NULL,           // AddPortEx is not supported.
99  NULL,           // ConfigurePort is not supported.
100  NULL,           // DeletePort is not supported.
101  NULL,
102  NULL,           // SetPortTimeOuts is not supported.
103  Monitor2XcvOpenPort,
104  Monitor2XcvDataPort,
105  Monitor2XcvClosePort,
106  Monitor2Shutdown
107};
108
109base::FilePath GetAppDataDir() {
110  base::FilePath file_path;
111  base::win::Version version = base::win::GetVersion();
112  int path_id = (version >= base::win::VERSION_VISTA) ?
113                base::DIR_LOCAL_APP_DATA_LOW : base::DIR_LOCAL_APP_DATA;
114  if (!PathService::Get(path_id, &file_path)) {
115    LOG(ERROR) << "Can't get DIR_LOCAL_APP_DATA";
116    return base::FilePath();
117  }
118  return file_path.Append(kAppDataDir);
119}
120
121// Delete files which where not deleted by chrome.
122void DeleteLeakedFiles(const base::FilePath& dir) {
123  base::Time delete_before = base::Time::Now() - base::TimeDelta::FromDays(1);
124  base::FileEnumerator enumerator(dir, false, base::FileEnumerator::FILES);
125  for (base::FilePath file_path = enumerator.Next(); !file_path.empty();
126       file_path = enumerator.Next()) {
127    if (enumerator.GetInfo().GetLastModifiedTime() < delete_before)
128      base::DeleteFile(file_path, false);
129  }
130}
131
132// Attempts to retrieve the title of the specified print job.
133// On success returns TRUE and the first title_chars characters of the job title
134// are copied into title.
135// On failure returns FALSE and title is unmodified.
136bool GetJobTitle(HANDLE printer_handle,
137                 DWORD job_id,
138                 string16 *title) {
139  DCHECK(printer_handle != NULL);
140  DCHECK(title != NULL);
141  DWORD bytes_needed = 0;
142  GetJob(printer_handle, job_id, 1, NULL, 0, &bytes_needed);
143  if (bytes_needed == 0) {
144    LOG(ERROR) << "Unable to get bytes needed for job info.";
145    return false;
146  }
147  scoped_ptr<BYTE[]> buffer(new BYTE[bytes_needed]);
148  if (!GetJob(printer_handle,
149              job_id,
150              1,
151              buffer.get(),
152              bytes_needed,
153              &bytes_needed)) {
154    LOG(ERROR) << "Unable to get job info.";
155    return false;
156  }
157  JOB_INFO_1* job_info = reinterpret_cast<JOB_INFO_1*>(buffer.get());
158  *title = job_info->pDocument;
159  return true;
160}
161
162// Handler for the UI functions exported by the port monitor.
163// Verifies that a valid parent Window exists and then just displays an
164// error message to let the user know that there is no interactive
165// configuration.
166void HandlePortUi(HWND hwnd, const string16& caption) {
167  if (hwnd != NULL && IsWindow(hwnd)) {
168    DisplayWindowsMessage(hwnd, CO_E_NOT_SUPPORTED, cloud_print::kPortName);
169  }
170}
171
172// Gets the primary token for the user that submitted the print job.
173bool GetUserToken(HANDLE* primary_token) {
174  HANDLE token = NULL;
175  if (!OpenThreadToken(GetCurrentThread(),
176                      TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY,
177                      FALSE,
178                      &token)) {
179    LOG(ERROR) << "Unable to get thread token.";
180    return false;
181  }
182  base::win::ScopedHandle token_scoped(token);
183  if (!DuplicateTokenEx(token,
184                        TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY,
185                        NULL,
186                        SecurityImpersonation,
187                        TokenPrimary,
188                        primary_token)) {
189    LOG(ERROR) << "Unable to get primary thread token.";
190    return false;
191  }
192  return true;
193}
194
195// Launches the Cloud Print dialog in Chrome.
196// xps_path references a file to print.
197// job_title is the title to be used for the resulting print job.
198bool LaunchPrintDialog(const base::FilePath& xps_path,
199                       const string16& job_title) {
200  HANDLE token = NULL;
201  if (!GetUserToken(&token)) {
202    LOG(ERROR) << "Unable to get user token.";
203    return false;
204  }
205  base::win::ScopedHandle primary_token_scoped(token);
206
207  base::FilePath chrome_path = GetChromeExePath();
208  if (chrome_path.empty()) {
209    LOG(ERROR) << "Unable to get chrome exe path.";
210    return false;
211  }
212
213  CommandLine command_line(chrome_path);
214
215  base::FilePath chrome_profile = GetChromeProfilePath();
216  if (!chrome_profile.empty()) {
217    command_line.AppendSwitchPath(switches::kUserDataDir, chrome_profile);
218  }
219
220  command_line.AppendSwitchPath(switches::kCloudPrintFile,
221                                xps_path);
222  command_line.AppendSwitchNative(switches::kCloudPrintFileType,
223                                  kXpsMimeType);
224  command_line.AppendSwitchNative(switches::kCloudPrintJobTitle,
225                                  job_title);
226  command_line.AppendSwitch(switches::kCloudPrintDeleteFile);
227  base::LaunchOptions options;
228  options.as_user = primary_token_scoped;
229  base::LaunchProcess(command_line, options, NULL);
230  return true;
231}
232
233// Launches a page to allow the user to download chrome.
234// TODO(abodenha@chromium.org) Point to a custom page explaining what's wrong
235// rather than the generic chrome download page.  See
236// http://code.google.com/p/chromium/issues/detail?id=112019
237void LaunchChromeDownloadPage() {
238  if (kIsUnittest)
239    return;
240  HANDLE token = NULL;
241  if (!GetUserToken(&token)) {
242    LOG(ERROR) << "Unable to get user token.";
243    return;
244  }
245  base::win::ScopedHandle token_scoped(token);
246
247  base::FilePath ie_path;
248  PathService::Get(base::DIR_PROGRAM_FILESX86, &ie_path);
249  ie_path = ie_path.Append(kIePath);
250  CommandLine command_line(ie_path);
251  command_line.AppendArg(kChromeInstallUrl);
252
253  base::LaunchOptions options;
254  options.as_user = token_scoped;
255  base::LaunchProcess(command_line, options, NULL);
256}
257
258// Returns false if the print job is being run in a context
259// that shouldn't be launching Chrome.
260bool ValidateCurrentUser() {
261  HANDLE token = NULL;
262  if (!GetUserToken(&token)) {
263    // If we can't get the token we're probably not impersonating
264    // the user, so validation should fail.
265    return false;
266  }
267  base::win::ScopedHandle token_scoped(token);
268
269  if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
270    DWORD session_id = 0;
271    DWORD dummy;
272    if (!GetTokenInformation(token_scoped,
273                             TokenSessionId,
274                             reinterpret_cast<void *>(&session_id),
275                             sizeof(DWORD),
276                             &dummy)) {
277      return false;
278    }
279    if (session_id == 0) {
280      return false;
281    }
282  }
283  return true;
284}
285}  // namespace
286
287base::FilePath ReadPathFromRegistry(HKEY root, const wchar_t* path_name) {
288  base::win::RegKey gcp_key(HKEY_CURRENT_USER, kCloudPrintRegKey, KEY_READ);
289  string16 data;
290  if (SUCCEEDED(gcp_key.ReadValue(path_name, &data)) &&
291      base::PathExists(base::FilePath(data))) {
292    return base::FilePath(data);
293  }
294  return base::FilePath();
295}
296
297base::FilePath ReadPathFromAnyRegistry(const wchar_t* path_name) {
298  base::FilePath result = ReadPathFromRegistry(HKEY_CURRENT_USER, path_name);
299  if (!result.empty())
300    return result;
301  return ReadPathFromRegistry(HKEY_LOCAL_MACHINE, path_name);
302}
303
304base::FilePath GetChromeExePath() {
305  base::FilePath path = ReadPathFromAnyRegistry(kChromeExePathRegValue);
306  if (!path.empty())
307    return path;
308  return chrome_launcher_support::GetAnyChromePath();
309}
310
311base::FilePath GetChromeProfilePath() {
312  base::FilePath path = ReadPathFromAnyRegistry(kChromeProfilePathRegValue);
313  if (!path.empty() && base::DirectoryExists(path))
314    return path;
315  return base::FilePath();
316}
317
318BOOL WINAPI Monitor2EnumPorts(HANDLE,
319                              wchar_t*,
320                              DWORD level,
321                              BYTE*  ports,
322                              DWORD   ports_size,
323                              DWORD* needed_bytes,
324                              DWORD* returned) {
325  if (needed_bytes == NULL) {
326    LOG(ERROR) << "needed_bytes should not be NULL.";
327    SetLastError(ERROR_INVALID_PARAMETER);
328    return FALSE;
329  }
330  if (level == 1) {
331    *needed_bytes = sizeof(PORT_INFO_1);
332  } else if (level == 2) {
333    *needed_bytes = sizeof(PORT_INFO_2);
334  } else {
335    LOG(ERROR) << "Level "  << level << "is not supported.";
336    SetLastError(ERROR_INVALID_LEVEL);
337    return FALSE;
338  }
339  *needed_bytes += static_cast<DWORD>(cloud_print::kPortNameSize);
340  if (ports_size < *needed_bytes) {
341    LOG(WARNING) << *needed_bytes << " bytes are required.  Only "
342                 << ports_size << " were allocated.";
343    SetLastError(ERROR_INSUFFICIENT_BUFFER);
344    return FALSE;
345  }
346  if (ports == NULL) {
347    LOG(ERROR) << "ports should not be NULL.";
348    SetLastError(ERROR_INVALID_PARAMETER);
349    return FALSE;
350  }
351  if (returned == NULL) {
352    LOG(ERROR) << "returned should not be NULL.";
353    SetLastError(ERROR_INVALID_PARAMETER);
354    return FALSE;
355  }
356
357  // Windows expects any strings refernced by PORT_INFO_X structures to
358  // appear at the END of the buffer referenced by ports.  Placing
359  // strings immediately after the PORT_INFO_X structure will cause
360  // EnumPorts to fail until the spooler is restarted.
361  // This is NOT mentioned in the documentation.
362  wchar_t* string_target =
363      reinterpret_cast<wchar_t*>(ports + ports_size -
364      cloud_print::kPortNameSize);
365  if (level == 1) {
366    PORT_INFO_1* port_info = reinterpret_cast<PORT_INFO_1*>(ports);
367    port_info->pName = string_target;
368    StringCbCopy(port_info->pName,
369                 cloud_print::kPortNameSize,
370                 cloud_print::kPortName);
371  } else {
372    PORT_INFO_2* port_info = reinterpret_cast<PORT_INFO_2*>(ports);
373    port_info->pPortName = string_target;
374    StringCbCopy(port_info->pPortName,
375                 cloud_print::kPortNameSize,
376                 cloud_print::kPortName);
377    port_info->pMonitorName = NULL;
378    port_info->pDescription = NULL;
379    port_info->fPortType = PORT_TYPE_WRITE;
380    port_info->Reserved = 0;
381  }
382  *returned = 1;
383  return TRUE;
384}
385
386BOOL WINAPI Monitor2OpenPort(HANDLE, wchar_t*, HANDLE* handle) {
387  PortData* port_data = new PortData();
388  if (port_data == NULL) {
389    LOG(ERROR) << "Unable to allocate memory for internal structures.";
390    SetLastError(E_OUTOFMEMORY);
391    return FALSE;
392  }
393  if (handle == NULL) {
394    LOG(ERROR) << "handle should not be NULL.";
395    SetLastError(ERROR_INVALID_PARAMETER);
396    return FALSE;
397  }
398  *handle = (HANDLE)port_data;
399  return TRUE;
400}
401
402BOOL WINAPI Monitor2StartDocPort(HANDLE port_handle,
403                                 wchar_t* printer_name,
404                                 DWORD job_id,
405                                 DWORD,
406                                 BYTE*) {
407  SetGoogleUpdateUsage(kGoogleUpdateProductId);
408  if (port_handle == NULL) {
409    LOG(ERROR) << "port_handle should not be NULL.";
410    SetLastError(ERROR_INVALID_PARAMETER);
411    return FALSE;
412  }
413  if (printer_name == NULL) {
414    LOG(ERROR) << "printer_name should not be NULL.";
415    SetLastError(ERROR_INVALID_PARAMETER);
416    return FALSE;
417  }
418  if (!ValidateCurrentUser()) {
419    // TODO(abodenha@chromium.org) Abort the print job.
420    return FALSE;
421  }
422  PortData* port_data = reinterpret_cast<PortData*>(port_handle);
423  port_data->job_id = job_id;
424  if (!OpenPrinter(printer_name, &(port_data->printer_handle), NULL)) {
425    LOG(WARNING) << "Unable to open printer " << printer_name << ".";
426    // We can continue without a handle to the printer.
427    // It just means we can't get the job title or tell the spooler that
428    // the print job is complete.
429    // This is the normal flow during a unit test.
430    port_data->printer_handle = NULL;
431  }
432  base::FilePath& file_path = port_data->file_path;
433  base::FilePath app_data_dir = GetAppDataDir();
434  if (app_data_dir.empty())
435    return FALSE;
436  DeleteLeakedFiles(app_data_dir);
437  if (!file_util::CreateDirectory(app_data_dir) ||
438      !file_util::CreateTemporaryFileInDir(app_data_dir, &file_path)) {
439    LOG(ERROR) << "Can't create temporary file in " << app_data_dir.value();
440    return FALSE;
441  }
442  port_data->file = file_util::OpenFile(file_path, "wb+");
443  if (port_data->file == NULL) {
444    LOG(ERROR) << "Error opening file " << file_path.value() << ".";
445    return FALSE;
446  }
447  return TRUE;
448}
449
450BOOL WINAPI Monitor2WritePort(HANDLE port_handle,
451                              BYTE* buffer,
452                              DWORD buffer_size,
453                              DWORD* bytes_written) {
454  PortData* port_data = reinterpret_cast<PortData*>(port_handle);
455  if (!ValidateCurrentUser()) {
456    // TODO(abodenha@chromium.org) Abort the print job.
457    return FALSE;
458  }
459  *bytes_written =
460      static_cast<DWORD>(fwrite(buffer, 1, buffer_size, port_data->file));
461  if (*bytes_written > 0) {
462    return TRUE;
463  } else {
464    return FALSE;
465  }
466}
467
468BOOL WINAPI Monitor2ReadPort(HANDLE, BYTE*, DWORD, DWORD* read_bytes) {
469  LOG(ERROR) << "Read is not supported.";
470  *read_bytes = 0;
471  SetLastError(ERROR_NOT_SUPPORTED);
472  return FALSE;
473}
474
475BOOL WINAPI Monitor2EndDocPort(HANDLE port_handle) {
476  if (!ValidateCurrentUser()) {
477    // TODO(abodenha@chromium.org) Abort the print job.
478    return FALSE;
479  }
480  PortData* port_data = reinterpret_cast<PortData*>(port_handle);
481  if (port_data == NULL) {
482    SetLastError(ERROR_INVALID_PARAMETER);
483    return FALSE;
484  }
485
486  if (port_data->file != NULL) {
487    file_util::CloseFile(port_data->file);
488    port_data->file = NULL;
489    bool delete_file = true;
490    int64 file_size = 0;
491    file_util::GetFileSize(port_data->file_path, &file_size);
492    if (file_size > 0) {
493      string16 job_title;
494      if (port_data->printer_handle != NULL) {
495        GetJobTitle(port_data->printer_handle,
496                    port_data->job_id,
497                    &job_title);
498      }
499      if (!LaunchPrintDialog(port_data->file_path, job_title)) {
500        LaunchChromeDownloadPage();
501      } else {
502        delete_file = false;
503      }
504    }
505    if (delete_file)
506      base::DeleteFile(port_data->file_path, false);
507  }
508  if (port_data->printer_handle != NULL) {
509    // Tell the spooler that the job is complete.
510    SetJob(port_data->printer_handle,
511           port_data->job_id,
512           0,
513           NULL,
514           JOB_CONTROL_SENT_TO_PRINTER);
515  }
516  port_data->Close();
517  // Return success even if we can't display the dialog.
518  // TODO(abodenha@chromium.org) Come up with a better way of handling
519  // this situation.
520  return TRUE;
521}
522
523BOOL WINAPI Monitor2ClosePort(HANDLE port_handle) {
524  if (port_handle == NULL) {
525    LOG(ERROR) << "port_handle should not be NULL.";
526    SetLastError(ERROR_INVALID_PARAMETER);
527    return FALSE;
528  }
529  PortData* port_data = reinterpret_cast<PortData*>(port_handle);
530  delete port_data;
531  return TRUE;
532}
533
534VOID WINAPI Monitor2Shutdown(HANDLE monitor_handle) {
535  if (monitor_handle != NULL) {
536    MonitorData* monitor_data =
537      reinterpret_cast<MonitorData*>(monitor_handle);
538    delete monitor_handle;
539  }
540}
541
542BOOL WINAPI Monitor2XcvOpenPort(HANDLE,
543                                const wchar_t*,
544                                ACCESS_MASK granted_access,
545                                HANDLE* handle) {
546  if (handle == NULL) {
547    LOG(ERROR) << "handle should not be NULL.";
548    SetLastError(ERROR_INVALID_PARAMETER);
549    return FALSE;
550  }
551  XcvUiData* xcv_data = new XcvUiData();
552  if (xcv_data == NULL) {
553    LOG(ERROR) << "Unable to allocate memory for internal structures.";
554    SetLastError(E_OUTOFMEMORY);
555    return FALSE;
556  }
557  xcv_data->granted_access = granted_access;
558  *handle = (HANDLE)xcv_data;
559  return TRUE;
560}
561
562DWORD WINAPI Monitor2XcvDataPort(HANDLE xcv_handle,
563                                 const wchar_t* data_name,
564                                 BYTE*,
565                                 DWORD,
566                                 BYTE* output_data,
567                                 DWORD output_data_bytes,
568                                 DWORD* output_data_bytes_needed) {
569  XcvUiData* xcv_data = reinterpret_cast<XcvUiData*>(xcv_handle);
570  DWORD ret_val = ERROR_SUCCESS;
571  if ((xcv_data->granted_access & SERVER_ACCESS_ADMINISTER) == 0) {
572    return ERROR_ACCESS_DENIED;
573  }
574  if (output_data == NULL || output_data_bytes == 0) {
575    return ERROR_INVALID_PARAMETER;
576  }
577  // We don't handle AddPort or DeletePort since we don't support
578  // dynamic creation of ports.
579  if (lstrcmp(L"MonitorUI", data_name) == 0) {
580    DWORD dll_path_len = 0;
581    base::FilePath dll_path(GetPortMonitorDllName());
582    dll_path_len = static_cast<DWORD>(dll_path.value().length());
583    if (output_data_bytes_needed != NULL) {
584      *output_data_bytes_needed = dll_path_len;
585    }
586    if (output_data_bytes < dll_path_len) {
587        return ERROR_INSUFFICIENT_BUFFER;
588    } else {
589        ret_val = StringCbCopy(reinterpret_cast<wchar_t*>(output_data),
590                               output_data_bytes,
591                               dll_path.value().c_str());
592    }
593  } else {
594    return ERROR_INVALID_PARAMETER;
595  }
596  return ret_val;
597}
598
599BOOL WINAPI Monitor2XcvClosePort(HANDLE handle) {
600  XcvUiData* xcv_data = reinterpret_cast<XcvUiData*>(handle);
601  delete xcv_data;
602  return TRUE;
603}
604
605BOOL WINAPI MonitorUiAddPortUi(const wchar_t*,
606                               HWND hwnd,
607                               const wchar_t* monitor_name,
608                               wchar_t**) {
609  HandlePortUi(hwnd, monitor_name);
610  return TRUE;
611}
612
613BOOL WINAPI MonitorUiConfigureOrDeletePortUI(const wchar_t*,
614                                             HWND hwnd,
615                                             const wchar_t* port_name) {
616  HandlePortUi(hwnd, port_name);
617  return TRUE;
618}
619
620}   // namespace cloud_print
621
622MONITOR2* WINAPI InitializePrintMonitor2(MONITORINIT*,
623                                         HANDLE* handle) {
624  cloud_print::MonitorData* monitor_data = new cloud_print::MonitorData;
625  if (monitor_data == NULL) {
626    return NULL;
627  }
628  if (handle != NULL) {
629    *handle = (HANDLE)monitor_data;
630    if (!cloud_print::kIsUnittest) {
631      // Unit tests set up their own AtExitManager
632      monitor_data->at_exit_manager.reset(new base::AtExitManager());
633      // Single spooler.exe handles verbose users.
634      PathService::DisableCache();
635    }
636  } else {
637    SetLastError(ERROR_INVALID_PARAMETER);
638    return NULL;
639  }
640  return &cloud_print::g_monitor_2;
641}
642
643MONITORUI* WINAPI InitializePrintMonitorUI(void) {
644  return &cloud_print::g_monitor_ui;
645}
646
647