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 command_line.AppendSwitchPath(switches::kCloudPrintFile, xps_path); 220 command_line.AppendSwitchNative(switches::kCloudPrintFileType, kXpsMimeType); 221 command_line.AppendSwitchNative(switches::kCloudPrintJobTitle, job_title); 222 base::LaunchOptions options; 223 options.as_user = primary_token_scoped; 224 base::LaunchProcess(command_line, options, NULL); 225 return true; 226 } 227 228 // Launches a page to allow the user to download chrome. 229 // TODO(abodenha (at) chromium.org) Point to a custom page explaining what's wrong 230 // rather than the generic chrome download page. See 231 // http://code.google.com/p/chromium/issues/detail?id=112019 232 void LaunchChromeDownloadPage() { 233 if (kIsUnittest) 234 return; 235 HANDLE token = NULL; 236 if (!GetUserToken(&token)) { 237 LOG(ERROR) << "Unable to get user token."; 238 return; 239 } 240 base::win::ScopedHandle token_scoped(token); 241 242 base::FilePath ie_path; 243 PathService::Get(base::DIR_PROGRAM_FILESX86, &ie_path); 244 ie_path = ie_path.Append(kIePath); 245 CommandLine command_line(ie_path); 246 command_line.AppendArg(kChromeInstallUrl); 247 248 base::LaunchOptions options; 249 options.as_user = token_scoped; 250 base::LaunchProcess(command_line, options, NULL); 251 } 252 253 // Returns false if the print job is being run in a context 254 // that shouldn't be launching Chrome. 255 bool ValidateCurrentUser() { 256 HANDLE token = NULL; 257 if (!GetUserToken(&token)) { 258 // If we can't get the token we're probably not impersonating 259 // the user, so validation should fail. 260 return false; 261 } 262 base::win::ScopedHandle token_scoped(token); 263 264 if (base::win::GetVersion() >= base::win::VERSION_VISTA) { 265 DWORD session_id = 0; 266 DWORD dummy; 267 if (!GetTokenInformation(token_scoped, 268 TokenSessionId, 269 reinterpret_cast<void *>(&session_id), 270 sizeof(DWORD), 271 &dummy)) { 272 return false; 273 } 274 if (session_id == 0) { 275 return false; 276 } 277 } 278 return true; 279 } 280 } // namespace 281 282 base::FilePath ReadPathFromRegistry(HKEY root, const wchar_t* path_name) { 283 base::win::RegKey gcp_key(HKEY_CURRENT_USER, kCloudPrintRegKey, KEY_READ); 284 base::string16 data; 285 if (SUCCEEDED(gcp_key.ReadValue(path_name, &data)) && 286 base::PathExists(base::FilePath(data))) { 287 return base::FilePath(data); 288 } 289 return base::FilePath(); 290 } 291 292 base::FilePath ReadPathFromAnyRegistry(const wchar_t* path_name) { 293 base::FilePath result = ReadPathFromRegistry(HKEY_CURRENT_USER, path_name); 294 if (!result.empty()) 295 return result; 296 return ReadPathFromRegistry(HKEY_LOCAL_MACHINE, path_name); 297 } 298 299 base::FilePath GetChromeExePath() { 300 base::FilePath path = ReadPathFromAnyRegistry(kChromeExePathRegValue); 301 if (!path.empty()) 302 return path; 303 return chrome_launcher_support::GetAnyChromePath(); 304 } 305 306 base::FilePath GetChromeProfilePath() { 307 base::FilePath path = ReadPathFromAnyRegistry(kChromeProfilePathRegValue); 308 if (!path.empty() && base::DirectoryExists(path)) 309 return path; 310 return base::FilePath(); 311 } 312 313 BOOL WINAPI Monitor2EnumPorts(HANDLE, 314 wchar_t*, 315 DWORD level, 316 BYTE* ports, 317 DWORD ports_size, 318 DWORD* needed_bytes, 319 DWORD* returned) { 320 if (needed_bytes == NULL) { 321 LOG(ERROR) << "needed_bytes should not be NULL."; 322 SetLastError(ERROR_INVALID_PARAMETER); 323 return FALSE; 324 } 325 if (level == 1) { 326 *needed_bytes = sizeof(PORT_INFO_1); 327 } else if (level == 2) { 328 *needed_bytes = sizeof(PORT_INFO_2); 329 } else { 330 LOG(ERROR) << "Level " << level << "is not supported."; 331 SetLastError(ERROR_INVALID_LEVEL); 332 return FALSE; 333 } 334 *needed_bytes += static_cast<DWORD>(cloud_print::kPortNameSize); 335 if (ports_size < *needed_bytes) { 336 LOG(WARNING) << *needed_bytes << " bytes are required. Only " 337 << ports_size << " were allocated."; 338 SetLastError(ERROR_INSUFFICIENT_BUFFER); 339 return FALSE; 340 } 341 if (ports == NULL) { 342 LOG(ERROR) << "ports should not be NULL."; 343 SetLastError(ERROR_INVALID_PARAMETER); 344 return FALSE; 345 } 346 if (returned == NULL) { 347 LOG(ERROR) << "returned should not be NULL."; 348 SetLastError(ERROR_INVALID_PARAMETER); 349 return FALSE; 350 } 351 352 // Windows expects any strings refernced by PORT_INFO_X structures to 353 // appear at the END of the buffer referenced by ports. Placing 354 // strings immediately after the PORT_INFO_X structure will cause 355 // EnumPorts to fail until the spooler is restarted. 356 // This is NOT mentioned in the documentation. 357 wchar_t* string_target = 358 reinterpret_cast<wchar_t*>(ports + ports_size - 359 cloud_print::kPortNameSize); 360 if (level == 1) { 361 PORT_INFO_1* port_info = reinterpret_cast<PORT_INFO_1*>(ports); 362 port_info->pName = string_target; 363 StringCbCopy(port_info->pName, 364 cloud_print::kPortNameSize, 365 cloud_print::kPortName); 366 } else { 367 PORT_INFO_2* port_info = reinterpret_cast<PORT_INFO_2*>(ports); 368 port_info->pPortName = string_target; 369 StringCbCopy(port_info->pPortName, 370 cloud_print::kPortNameSize, 371 cloud_print::kPortName); 372 port_info->pMonitorName = NULL; 373 port_info->pDescription = NULL; 374 port_info->fPortType = PORT_TYPE_WRITE; 375 port_info->Reserved = 0; 376 } 377 *returned = 1; 378 return TRUE; 379 } 380 381 BOOL WINAPI Monitor2OpenPort(HANDLE, wchar_t*, HANDLE* handle) { 382 if (handle == NULL) { 383 LOG(ERROR) << "handle should not be NULL."; 384 SetLastError(ERROR_INVALID_PARAMETER); 385 return FALSE; 386 } 387 *handle = new PortData(); 388 return TRUE; 389 } 390 391 BOOL WINAPI Monitor2StartDocPort(HANDLE port_handle, 392 wchar_t* printer_name, 393 DWORD job_id, 394 DWORD, 395 BYTE*) { 396 SetGoogleUpdateUsage(kGoogleUpdateProductId); 397 if (port_handle == NULL) { 398 LOG(ERROR) << "port_handle should not be NULL."; 399 SetLastError(ERROR_INVALID_PARAMETER); 400 return FALSE; 401 } 402 if (printer_name == NULL) { 403 LOG(ERROR) << "printer_name should not be NULL."; 404 SetLastError(ERROR_INVALID_PARAMETER); 405 return FALSE; 406 } 407 if (!ValidateCurrentUser()) { 408 // TODO(abodenha (at) chromium.org) Abort the print job. 409 return FALSE; 410 } 411 PortData* port_data = reinterpret_cast<PortData*>(port_handle); 412 port_data->job_id = job_id; 413 if (!OpenPrinter(printer_name, &(port_data->printer_handle), NULL)) { 414 LOG(WARNING) << "Unable to open printer " << printer_name << "."; 415 // We can continue without a handle to the printer. 416 // It just means we can't get the job title or tell the spooler that 417 // the print job is complete. 418 // This is the normal flow during a unit test. 419 port_data->printer_handle = NULL; 420 } 421 base::FilePath& file_path = port_data->file_path; 422 base::FilePath app_data_dir = GetAppDataDir(); 423 if (app_data_dir.empty()) 424 return FALSE; 425 DeleteLeakedFiles(app_data_dir); 426 if (!base::CreateDirectory(app_data_dir) || 427 !base::CreateTemporaryFileInDir(app_data_dir, &file_path)) { 428 LOG(ERROR) << "Can't create temporary file in " << app_data_dir.value(); 429 return FALSE; 430 } 431 port_data->file = base::OpenFile(file_path, "wb+"); 432 if (port_data->file == NULL) { 433 LOG(ERROR) << "Error opening file " << file_path.value() << "."; 434 return FALSE; 435 } 436 return TRUE; 437 } 438 439 BOOL WINAPI Monitor2WritePort(HANDLE port_handle, 440 BYTE* buffer, 441 DWORD buffer_size, 442 DWORD* bytes_written) { 443 PortData* port_data = reinterpret_cast<PortData*>(port_handle); 444 if (!ValidateCurrentUser()) { 445 // TODO(abodenha (at) chromium.org) Abort the print job. 446 return FALSE; 447 } 448 *bytes_written = 449 static_cast<DWORD>(fwrite(buffer, 1, buffer_size, port_data->file)); 450 if (*bytes_written > 0) { 451 return TRUE; 452 } else { 453 return FALSE; 454 } 455 } 456 457 BOOL WINAPI Monitor2ReadPort(HANDLE, BYTE*, DWORD, DWORD* read_bytes) { 458 LOG(ERROR) << "Read is not supported."; 459 *read_bytes = 0; 460 SetLastError(ERROR_NOT_SUPPORTED); 461 return FALSE; 462 } 463 464 BOOL WINAPI Monitor2EndDocPort(HANDLE port_handle) { 465 if (!ValidateCurrentUser()) { 466 // TODO(abodenha (at) chromium.org) Abort the print job. 467 return FALSE; 468 } 469 PortData* port_data = reinterpret_cast<PortData*>(port_handle); 470 if (port_data == NULL) { 471 SetLastError(ERROR_INVALID_PARAMETER); 472 return FALSE; 473 } 474 475 if (port_data->file != NULL) { 476 base::CloseFile(port_data->file); 477 port_data->file = NULL; 478 bool delete_file = true; 479 int64 file_size = 0; 480 base::GetFileSize(port_data->file_path, &file_size); 481 if (file_size > 0) { 482 base::string16 job_title; 483 if (port_data->printer_handle != NULL) { 484 GetJobTitle(port_data->printer_handle, 485 port_data->job_id, 486 &job_title); 487 } 488 if (!LaunchPrintDialog(port_data->file_path, job_title)) { 489 LaunchChromeDownloadPage(); 490 } else { 491 delete_file = false; 492 } 493 } 494 if (delete_file) 495 base::DeleteFile(port_data->file_path, false); 496 } 497 if (port_data->printer_handle != NULL) { 498 // Tell the spooler that the job is complete. 499 SetJob(port_data->printer_handle, 500 port_data->job_id, 501 0, 502 NULL, 503 JOB_CONTROL_SENT_TO_PRINTER); 504 } 505 port_data->Close(); 506 // Return success even if we can't display the dialog. 507 // TODO(abodenha (at) chromium.org) Come up with a better way of handling 508 // this situation. 509 return TRUE; 510 } 511 512 BOOL WINAPI Monitor2ClosePort(HANDLE port_handle) { 513 if (port_handle == NULL) { 514 LOG(ERROR) << "port_handle should not be NULL."; 515 SetLastError(ERROR_INVALID_PARAMETER); 516 return FALSE; 517 } 518 PortData* port_data = reinterpret_cast<PortData*>(port_handle); 519 delete port_data; 520 return TRUE; 521 } 522 523 VOID WINAPI Monitor2Shutdown(HANDLE monitor_handle) { 524 if (monitor_handle != NULL) { 525 MonitorData* monitor_data = 526 reinterpret_cast<MonitorData*>(monitor_handle); 527 delete monitor_handle; 528 } 529 } 530 531 BOOL WINAPI Monitor2XcvOpenPort(HANDLE, 532 const wchar_t*, 533 ACCESS_MASK granted_access, 534 HANDLE* handle) { 535 if (handle == NULL) { 536 LOG(ERROR) << "handle should not be NULL."; 537 SetLastError(ERROR_INVALID_PARAMETER); 538 return FALSE; 539 } 540 XcvUiData* xcv_data = new XcvUiData(); 541 xcv_data->granted_access = granted_access; 542 *handle = xcv_data; 543 return TRUE; 544 } 545 546 DWORD WINAPI Monitor2XcvDataPort(HANDLE xcv_handle, 547 const wchar_t* data_name, 548 BYTE*, 549 DWORD, 550 BYTE* output_data, 551 DWORD output_data_bytes, 552 DWORD* output_data_bytes_needed) { 553 XcvUiData* xcv_data = reinterpret_cast<XcvUiData*>(xcv_handle); 554 DWORD ret_val = ERROR_SUCCESS; 555 if ((xcv_data->granted_access & SERVER_ACCESS_ADMINISTER) == 0) { 556 return ERROR_ACCESS_DENIED; 557 } 558 if (output_data == NULL || output_data_bytes == 0) { 559 return ERROR_INVALID_PARAMETER; 560 } 561 // We don't handle AddPort or DeletePort since we don't support 562 // dynamic creation of ports. 563 if (lstrcmp(L"MonitorUI", data_name) == 0) { 564 DWORD dll_path_len = 0; 565 base::FilePath dll_path(GetPortMonitorDllName()); 566 dll_path_len = static_cast<DWORD>(dll_path.value().length()); 567 if (output_data_bytes_needed != NULL) { 568 *output_data_bytes_needed = dll_path_len; 569 } 570 if (output_data_bytes < dll_path_len) { 571 return ERROR_INSUFFICIENT_BUFFER; 572 } else { 573 ret_val = StringCbCopy(reinterpret_cast<wchar_t*>(output_data), 574 output_data_bytes, 575 dll_path.value().c_str()); 576 } 577 } else { 578 return ERROR_INVALID_PARAMETER; 579 } 580 return ret_val; 581 } 582 583 BOOL WINAPI Monitor2XcvClosePort(HANDLE handle) { 584 XcvUiData* xcv_data = reinterpret_cast<XcvUiData*>(handle); 585 delete xcv_data; 586 return TRUE; 587 } 588 589 BOOL WINAPI MonitorUiAddPortUi(const wchar_t*, 590 HWND hwnd, 591 const wchar_t* monitor_name, 592 wchar_t**) { 593 HandlePortUi(hwnd, monitor_name); 594 return TRUE; 595 } 596 597 BOOL WINAPI MonitorUiConfigureOrDeletePortUI(const wchar_t*, 598 HWND hwnd, 599 const wchar_t* port_name) { 600 HandlePortUi(hwnd, port_name); 601 return TRUE; 602 } 603 604 } // namespace cloud_print 605 606 MONITOR2* WINAPI InitializePrintMonitor2(MONITORINIT*, 607 HANDLE* handle) { 608 if (handle == NULL) { 609 SetLastError(ERROR_INVALID_PARAMETER); 610 return NULL; 611 } 612 cloud_print::MonitorData* monitor_data = new cloud_print::MonitorData; 613 *handle = monitor_data; 614 if (!cloud_print::kIsUnittest) { 615 // Unit tests set up their own AtExitManager 616 monitor_data->at_exit_manager.reset(new base::AtExitManager()); 617 // Single spooler.exe handles verbose users. 618 PathService::DisableCache(); 619 } 620 return &cloud_print::g_monitor_2; 621 } 622 623 MONITORUI* WINAPI InitializePrintMonitorUI(void) { 624 return &cloud_print::g_monitor_ui; 625 } 626 627