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 34 namespace cloud_print { 35 36 namespace { 37 38 const wchar_t kIePath[] = L"Internet Explorer\\iexplore.exe"; 39 40 const char kChromeInstallUrl[] = 41 "http://google.com/cloudprint/learn/chrome.html"; 42 43 const wchar_t kCloudPrintRegKey[] = L"Software\\Google\\CloudPrint"; 44 45 const wchar_t kXpsMimeType[] = L"application/vnd.ms-xpsdocument"; 46 47 const wchar_t kAppDataDir[] = L"Google\\Cloud Printer"; 48 49 struct MonitorData { 50 scoped_ptr<base::AtExitManager> at_exit_manager; 51 }; 52 53 struct 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 base::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 75 typedef struct { 76 ACCESS_MASK granted_access; 77 } XcvUiData; 78 79 80 MONITORUI g_monitor_ui = { 81 sizeof(MONITORUI), 82 MonitorUiAddPortUi, 83 MonitorUiConfigureOrDeletePortUI, 84 MonitorUiConfigureOrDeletePortUI 85 }; 86 87 MONITOR2 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 109 base::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. 122 void 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. 136 bool GetJobTitle(HANDLE printer_handle, 137 DWORD job_id, 138 base::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. 166 void HandlePortUi(HWND hwnd, const base::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. 173 bool 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. 198 bool LaunchPrintDialog(const base::FilePath& xps_path, 199 const base::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 (at) 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 237 void 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. 260 bool 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 287 base::FilePath ReadPathFromRegistry(HKEY root, const wchar_t* path_name) { 288 base::win::RegKey gcp_key(HKEY_CURRENT_USER, kCloudPrintRegKey, KEY_READ); 289 base::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 297 base::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 304 base::FilePath GetChromeExePath() { 305 base::FilePath path = ReadPathFromAnyRegistry(kChromeExePathRegValue); 306 if (!path.empty()) 307 return path; 308 return chrome_launcher_support::GetAnyChromePath(); 309 } 310 311 base::FilePath GetChromeProfilePath() { 312 base::FilePath path = ReadPathFromAnyRegistry(kChromeProfilePathRegValue); 313 if (!path.empty() && base::DirectoryExists(path)) 314 return path; 315 return base::FilePath(); 316 } 317 318 BOOL 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 386 BOOL WINAPI Monitor2OpenPort(HANDLE, wchar_t*, HANDLE* handle) { 387 if (handle == NULL) { 388 LOG(ERROR) << "handle should not be NULL."; 389 SetLastError(ERROR_INVALID_PARAMETER); 390 return FALSE; 391 } 392 *handle = new PortData(); 393 return TRUE; 394 } 395 396 BOOL WINAPI Monitor2StartDocPort(HANDLE port_handle, 397 wchar_t* printer_name, 398 DWORD job_id, 399 DWORD, 400 BYTE*) { 401 SetGoogleUpdateUsage(kGoogleUpdateProductId); 402 if (port_handle == NULL) { 403 LOG(ERROR) << "port_handle should not be NULL."; 404 SetLastError(ERROR_INVALID_PARAMETER); 405 return FALSE; 406 } 407 if (printer_name == NULL) { 408 LOG(ERROR) << "printer_name should not be NULL."; 409 SetLastError(ERROR_INVALID_PARAMETER); 410 return FALSE; 411 } 412 if (!ValidateCurrentUser()) { 413 // TODO(abodenha (at) chromium.org) Abort the print job. 414 return FALSE; 415 } 416 PortData* port_data = reinterpret_cast<PortData*>(port_handle); 417 port_data->job_id = job_id; 418 if (!OpenPrinter(printer_name, &(port_data->printer_handle), NULL)) { 419 LOG(WARNING) << "Unable to open printer " << printer_name << "."; 420 // We can continue without a handle to the printer. 421 // It just means we can't get the job title or tell the spooler that 422 // the print job is complete. 423 // This is the normal flow during a unit test. 424 port_data->printer_handle = NULL; 425 } 426 base::FilePath& file_path = port_data->file_path; 427 base::FilePath app_data_dir = GetAppDataDir(); 428 if (app_data_dir.empty()) 429 return FALSE; 430 DeleteLeakedFiles(app_data_dir); 431 if (!base::CreateDirectory(app_data_dir) || 432 !base::CreateTemporaryFileInDir(app_data_dir, &file_path)) { 433 LOG(ERROR) << "Can't create temporary file in " << app_data_dir.value(); 434 return FALSE; 435 } 436 port_data->file = base::OpenFile(file_path, "wb+"); 437 if (port_data->file == NULL) { 438 LOG(ERROR) << "Error opening file " << file_path.value() << "."; 439 return FALSE; 440 } 441 return TRUE; 442 } 443 444 BOOL WINAPI Monitor2WritePort(HANDLE port_handle, 445 BYTE* buffer, 446 DWORD buffer_size, 447 DWORD* bytes_written) { 448 PortData* port_data = reinterpret_cast<PortData*>(port_handle); 449 if (!ValidateCurrentUser()) { 450 // TODO(abodenha (at) chromium.org) Abort the print job. 451 return FALSE; 452 } 453 *bytes_written = 454 static_cast<DWORD>(fwrite(buffer, 1, buffer_size, port_data->file)); 455 if (*bytes_written > 0) { 456 return TRUE; 457 } else { 458 return FALSE; 459 } 460 } 461 462 BOOL WINAPI Monitor2ReadPort(HANDLE, BYTE*, DWORD, DWORD* read_bytes) { 463 LOG(ERROR) << "Read is not supported."; 464 *read_bytes = 0; 465 SetLastError(ERROR_NOT_SUPPORTED); 466 return FALSE; 467 } 468 469 BOOL WINAPI Monitor2EndDocPort(HANDLE port_handle) { 470 if (!ValidateCurrentUser()) { 471 // TODO(abodenha (at) chromium.org) Abort the print job. 472 return FALSE; 473 } 474 PortData* port_data = reinterpret_cast<PortData*>(port_handle); 475 if (port_data == NULL) { 476 SetLastError(ERROR_INVALID_PARAMETER); 477 return FALSE; 478 } 479 480 if (port_data->file != NULL) { 481 base::CloseFile(port_data->file); 482 port_data->file = NULL; 483 bool delete_file = true; 484 int64 file_size = 0; 485 base::GetFileSize(port_data->file_path, &file_size); 486 if (file_size > 0) { 487 base::string16 job_title; 488 if (port_data->printer_handle != NULL) { 489 GetJobTitle(port_data->printer_handle, 490 port_data->job_id, 491 &job_title); 492 } 493 if (!LaunchPrintDialog(port_data->file_path, job_title)) { 494 LaunchChromeDownloadPage(); 495 } else { 496 delete_file = false; 497 } 498 } 499 if (delete_file) 500 base::DeleteFile(port_data->file_path, false); 501 } 502 if (port_data->printer_handle != NULL) { 503 // Tell the spooler that the job is complete. 504 SetJob(port_data->printer_handle, 505 port_data->job_id, 506 0, 507 NULL, 508 JOB_CONTROL_SENT_TO_PRINTER); 509 } 510 port_data->Close(); 511 // Return success even if we can't display the dialog. 512 // TODO(abodenha (at) chromium.org) Come up with a better way of handling 513 // this situation. 514 return TRUE; 515 } 516 517 BOOL WINAPI Monitor2ClosePort(HANDLE port_handle) { 518 if (port_handle == NULL) { 519 LOG(ERROR) << "port_handle should not be NULL."; 520 SetLastError(ERROR_INVALID_PARAMETER); 521 return FALSE; 522 } 523 PortData* port_data = reinterpret_cast<PortData*>(port_handle); 524 delete port_data; 525 return TRUE; 526 } 527 528 VOID WINAPI Monitor2Shutdown(HANDLE monitor_handle) { 529 if (monitor_handle != NULL) { 530 MonitorData* monitor_data = 531 reinterpret_cast<MonitorData*>(monitor_handle); 532 delete monitor_handle; 533 } 534 } 535 536 BOOL WINAPI Monitor2XcvOpenPort(HANDLE, 537 const wchar_t*, 538 ACCESS_MASK granted_access, 539 HANDLE* handle) { 540 if (handle == NULL) { 541 LOG(ERROR) << "handle should not be NULL."; 542 SetLastError(ERROR_INVALID_PARAMETER); 543 return FALSE; 544 } 545 XcvUiData* xcv_data = new XcvUiData(); 546 xcv_data->granted_access = granted_access; 547 *handle = xcv_data; 548 return TRUE; 549 } 550 551 DWORD WINAPI Monitor2XcvDataPort(HANDLE xcv_handle, 552 const wchar_t* data_name, 553 BYTE*, 554 DWORD, 555 BYTE* output_data, 556 DWORD output_data_bytes, 557 DWORD* output_data_bytes_needed) { 558 XcvUiData* xcv_data = reinterpret_cast<XcvUiData*>(xcv_handle); 559 DWORD ret_val = ERROR_SUCCESS; 560 if ((xcv_data->granted_access & SERVER_ACCESS_ADMINISTER) == 0) { 561 return ERROR_ACCESS_DENIED; 562 } 563 if (output_data == NULL || output_data_bytes == 0) { 564 return ERROR_INVALID_PARAMETER; 565 } 566 // We don't handle AddPort or DeletePort since we don't support 567 // dynamic creation of ports. 568 if (lstrcmp(L"MonitorUI", data_name) == 0) { 569 DWORD dll_path_len = 0; 570 base::FilePath dll_path(GetPortMonitorDllName()); 571 dll_path_len = static_cast<DWORD>(dll_path.value().length()); 572 if (output_data_bytes_needed != NULL) { 573 *output_data_bytes_needed = dll_path_len; 574 } 575 if (output_data_bytes < dll_path_len) { 576 return ERROR_INSUFFICIENT_BUFFER; 577 } else { 578 ret_val = StringCbCopy(reinterpret_cast<wchar_t*>(output_data), 579 output_data_bytes, 580 dll_path.value().c_str()); 581 } 582 } else { 583 return ERROR_INVALID_PARAMETER; 584 } 585 return ret_val; 586 } 587 588 BOOL WINAPI Monitor2XcvClosePort(HANDLE handle) { 589 XcvUiData* xcv_data = reinterpret_cast<XcvUiData*>(handle); 590 delete xcv_data; 591 return TRUE; 592 } 593 594 BOOL WINAPI MonitorUiAddPortUi(const wchar_t*, 595 HWND hwnd, 596 const wchar_t* monitor_name, 597 wchar_t**) { 598 HandlePortUi(hwnd, monitor_name); 599 return TRUE; 600 } 601 602 BOOL WINAPI MonitorUiConfigureOrDeletePortUI(const wchar_t*, 603 HWND hwnd, 604 const wchar_t* port_name) { 605 HandlePortUi(hwnd, port_name); 606 return TRUE; 607 } 608 609 } // namespace cloud_print 610 611 MONITOR2* WINAPI InitializePrintMonitor2(MONITORINIT*, 612 HANDLE* handle) { 613 if (handle == NULL) { 614 SetLastError(ERROR_INVALID_PARAMETER); 615 return NULL; 616 } 617 cloud_print::MonitorData* monitor_data = new cloud_print::MonitorData; 618 *handle = monitor_data; 619 if (!cloud_print::kIsUnittest) { 620 // Unit tests set up their own AtExitManager 621 monitor_data->at_exit_manager.reset(new base::AtExitManager()); 622 // Single spooler.exe handles verbose users. 623 PathService::DisableCache(); 624 } 625 return &cloud_print::g_monitor_2; 626 } 627 628 MONITORUI* WINAPI InitializePrintMonitorUI(void) { 629 return &cloud_print::g_monitor_ui; 630 } 631 632