blob: 5e725e328e065b82faa3bb606bce0014635472a7 [file] [log] [blame]
Luum Habtemariamc2ab5bc82019-09-06 21:10:051// Copyright 2019 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 "chrome/services/cups_proxy/proxy_manager.h"
6
7#include <cups/ipp.h>
8
9#include <memory>
10#include <string>
11#include <utility>
12#include <vector>
13
14#include "base/bind.h"
Pranav Batraffbed602020-09-23 02:38:3515#include "base/containers/ring_buffer.h"
Luum Habtemariamc2ab5bc82019-09-06 21:10:0516#include "base/feature_list.h"
17#include "base/logging.h"
18#include "base/task/post_task.h"
Luum Habtemariamc2ab5bc82019-09-06 21:10:0519#include "chrome/services/cups_proxy/cups_proxy_service_delegate.h"
20#include "chrome/services/cups_proxy/ipp_validator.h"
21#include "chrome/services/cups_proxy/printer_installer.h"
22#include "chrome/services/cups_proxy/public/cpp/cups_util.h"
23#include "chrome/services/cups_proxy/public/cpp/ipp_messages.h"
24#include "chrome/services/cups_proxy/public/cpp/type_conversions.h"
25#include "chrome/services/cups_proxy/socket_manager.h"
Robbie McElrath8b728442021-01-14 00:20:0126#include "chrome/services/ipp_parser/public/cpp/browser/ipp_parser_launcher.h"
Luum Habtemariamc2ab5bc82019-09-06 21:10:0527#include "chrome/services/ipp_parser/public/cpp/ipp_converter.h"
Luum Habtemariamc2ab5bc82019-09-06 21:10:0528#include "mojo/public/cpp/bindings/receiver.h"
29#include "mojo/public/cpp/bindings/remote.h"
30#include "net/http/http_response_headers.h"
31#include "net/http/http_util.h"
Daniel Hosseinian9c725992020-02-20 23:54:3732#include "printing/backend/cups_ipp_helper.h"
Luum Habtemariamc2ab5bc82019-09-06 21:10:0533
34namespace cups_proxy {
35namespace {
36
37struct InFlightRequest {
38 IppRequest request;
39 ProxyManager::ProxyRequestCallback cb;
40};
41
42class ProxyManagerImpl : public ProxyManager {
43 public:
44 ProxyManagerImpl(mojo::PendingReceiver<mojom::CupsProxier> request,
45 std::unique_ptr<CupsProxyServiceDelegate> delegate,
46 std::unique_ptr<IppValidator> ipp_validator,
47 std::unique_ptr<PrinterInstaller> printer_installer,
Pranav Batraffbed602020-09-23 02:38:3548 std::unique_ptr<SocketManager> socket_manager)
49 : delegate_(std::move(delegate)),
50 ipp_validator_(std::move(ipp_validator)),
51 printer_installer_(std::move(printer_installer)),
52 socket_manager_(std::move(socket_manager)),
53 receiver_(this, std::move(request)) {
54 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
Pranav Batrae992b112020-11-20 02:21:4455 receiver_.set_disconnect_handler(
56 base::BindOnce([] { LOG(ERROR) << "CupsProxy mojo connection lost"; }));
Pranav Batraffbed602020-09-23 02:38:3557 }
58
Peter Boström53c6c5952021-09-17 09:41:2659 ProxyManagerImpl(const ProxyManagerImpl&) = delete;
60 ProxyManagerImpl& operator=(const ProxyManagerImpl&) = delete;
61
Pranav Batraffbed602020-09-23 02:38:3562 ~ProxyManagerImpl() override = default;
Luum Habtemariamc2ab5bc82019-09-06 21:10:0563
64 void ProxyRequest(const std::string& method,
65 const std::string& url,
66 const std::string& version,
67 const std::vector<ipp_converter::HttpHeader>& headers,
68 const std::vector<uint8_t>& body,
69 ProxyRequestCallback cb) override;
70
71 private:
72 // These methods interface with |ipp_parser_| to parse the syntax of the
73 // inflight IPP request.
74 void ParseIpp();
75 void OnParseIpp(ipp_parser::mojom::IppRequestPtr parsed_request);
76
77 // For CUPS-Get-Printers requests, we spoof the response.
78 void SpoofGetPrinters();
79
80 // The callback for interfacing with |printer_installer_|; used to
81 // pre-install any printers referenced by the inflight request into CUPS.
82 void OnInstallPrinter(InstallPrinterResult res);
83
84 // These methods interface with |socket_manager_| to actually proxy the
85 // inflight request to CUPS and propagate back its IPP response.
86 void ProxyToCups();
87 void OnProxyToCups(std::unique_ptr<std::vector<uint8_t>> response);
88
89 // Proxy the IPP response back to the caller.
90 void ProxyResponseToCaller(const std::vector<uint8_t>& response);
91
92 // Fail in-flight request.
Luum Habtemariamb9a4f7f62019-10-03 19:18:5893 void Fail(const std::string& error_message, int http_status_code);
Luum Habtemariamc2ab5bc82019-09-06 21:10:0594
95 // Delegate providing necessary Profile dependencies.
96 std::unique_ptr<CupsProxyServiceDelegate> delegate_;
97
98 // Current in-flight request.
99 std::unique_ptr<InFlightRequest> in_flight_;
100
Pranav Batraffbed602020-09-23 02:38:35101 // Timestamp ring buffer.
102 base::RingBuffer<base::TimeTicks, kRateLimit> timestamp_;
103
Luum Habtemariamc2ab5bc82019-09-06 21:10:05104 // CupsIppParser Service handle.
105 mojo::Remote<ipp_parser::mojom::IppParser> ipp_parser_;
106
107 // Runs in current sequence.
108 std::unique_ptr<IppValidator> ipp_validator_;
109 std::unique_ptr<PrinterInstaller> printer_installer_;
110 std::unique_ptr<SocketManager> socket_manager_;
111
112 SEQUENCE_CHECKER(sequence_checker_);
113
114 mojo::Receiver<mojom::CupsProxier> receiver_;
115 base::WeakPtrFactory<ProxyManagerImpl> weak_factory_{this};
Luum Habtemariamc2ab5bc82019-09-06 21:10:05116};
117
Anton Bikineev46bbb972021-05-15 17:53:53118absl::optional<std::vector<uint8_t>> RebuildIppRequest(
Luum Habtemariamc2ab5bc82019-09-06 21:10:05119 const std::string& method,
120 const std::string& url,
121 const std::string& version,
122 const std::vector<ipp_converter::HttpHeader>& headers,
123 const std::vector<uint8_t>& body) {
124 auto request_line_buffer =
125 ipp_converter::BuildRequestLine(method, url, version);
126 if (!request_line_buffer.has_value()) {
Anton Bikineev46bbb972021-05-15 17:53:53127 return absl::nullopt;
Luum Habtemariamc2ab5bc82019-09-06 21:10:05128 }
129
130 auto headers_buffer = ipp_converter::BuildHeaders(headers);
131 if (!headers_buffer.has_value()) {
Anton Bikineev46bbb972021-05-15 17:53:53132 return absl::nullopt;
Luum Habtemariamc2ab5bc82019-09-06 21:10:05133 }
134
135 std::vector<uint8_t> ret;
136 ret.insert(ret.end(), request_line_buffer->begin(),
137 request_line_buffer->end());
138 ret.insert(ret.end(), headers_buffer->begin(), headers_buffer->end());
139 ret.insert(ret.end(), body.begin(), body.end());
140 return ret;
141}
142
Luum Habtemariamc2ab5bc82019-09-06 21:10:05143void ProxyManagerImpl::ProxyRequest(
144 const std::string& method,
145 const std::string& url,
146 const std::string& version,
147 const std::vector<ipp_converter::HttpHeader>& headers,
148 const std::vector<uint8_t>& body,
149 ProxyRequestCallback cb) {
150 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
151
Pranav Batraffbed602020-09-23 02:38:35152 // Limit the rate of requests to kRateLimit per second.
153 // The way the algorithm works is if the number of times this method was
154 // called between a second ago and now, including the current call, exceeds
155 // kRateLimit, the request is blocked and the HTTP 429 Too Many Requests
156 // response status code is returned.
157 base::TimeTicks time = base::TimeTicks::Now();
Peter Kastinge5a38ed2021-10-02 03:06:35158 bool block_request = timestamp_.CurrentIndex() >= timestamp_.BufferSize() &&
159 time - timestamp_.ReadBuffer(0) < base::Seconds(1);
Pranav Batraffbed602020-09-23 02:38:35160 timestamp_.SaveToBuffer(time);
161 if (block_request) {
Pranav Batraa280e3a2020-12-11 19:20:12162 LOG(WARNING) << "CupsPrintService: Rate limit (" << kRateLimit
163 << ") exceeded";
Pranav Batraffbed602020-09-23 02:38:35164 std::move(cb).Run({}, {}, 429); // HTTP_STATUS_TOO_MANY_REQUESTS
165 return;
166 }
167
Timothy Loh2058fef8e2020-04-02 23:03:27168 if (!delegate_->IsPrinterAccessAllowed()) {
169 DVLOG(1) << "Printer access not allowed";
170 std::move(cb).Run(/*headers=*/{}, /*ipp_message=*/{},
171 HTTP_STATUS_FORBIDDEN);
172 return;
173 }
174
Luum Habtemariamc2ab5bc82019-09-06 21:10:05175 // If we already have an in-flight request, we fail this incoming one
176 // directly.
177 if (in_flight_) {
178 DVLOG(1) << "CupsPrintService Error: Already have an in-flight request";
Luum Habtemariamb9a4f7f62019-10-03 19:18:58179 std::move(cb).Run({}, {}, HTTP_STATUS_SERVICE_UNAVAILABLE);
Luum Habtemariamc2ab5bc82019-09-06 21:10:05180 return;
181 }
182
183 in_flight_ = std::make_unique<InFlightRequest>();
184 in_flight_->cb = std::move(cb);
185
186 // TODO(crbug.com/945409): Rename in both proxy.mojom's: url -> endpoint.
187 auto request_buffer = RebuildIppRequest(method, url, version, headers, body);
188 if (!request_buffer) {
Luum Habtemariamb9a4f7f62019-10-03 19:18:58189 return Fail("Failed to rebuild incoming IPP request",
190 HTTP_STATUS_SERVER_ERROR);
Luum Habtemariamc2ab5bc82019-09-06 21:10:05191 }
192
193 // Save request.
194 in_flight_->request.buffer = *request_buffer;
195 ParseIpp();
196 return;
197}
198
199void ProxyManagerImpl::ParseIpp() {
200 // Launch CupsIppParser service, if needed.
201 if (!ipp_parser_) {
202 ipp_parser_.Bind(ipp_parser::LaunchIppParser());
203 ipp_parser_.reset_on_disconnect();
204 }
205
206 // Run out-of-process IPP parsing.
207 ipp_parser_->ParseIpp(in_flight_->request.buffer,
208 base::BindOnce(&ProxyManagerImpl::OnParseIpp,
209 weak_factory_.GetWeakPtr()));
210}
211
212void ProxyManagerImpl::OnParseIpp(
213 ipp_parser::mojom::IppRequestPtr parsed_request) {
214 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
215 DCHECK(in_flight_);
216
217 if (!parsed_request) {
Luum Habtemariamb9a4f7f62019-10-03 19:18:58218 return Fail("Failed to parse IPP request", HTTP_STATUS_BAD_REQUEST);
Luum Habtemariamc2ab5bc82019-09-06 21:10:05219 }
220
221 // Validate parsed request.
222 auto valid_request =
223 ipp_validator_->ValidateIppRequest(std::move(parsed_request));
224 if (!valid_request) {
Luum Habtemariamb9a4f7f62019-10-03 19:18:58225 return Fail("Failed to validate IPP request", HTTP_STATUS_BAD_REQUEST);
Luum Habtemariamc2ab5bc82019-09-06 21:10:05226 }
227
228 // Save newly validated request.
229 in_flight_->request = std::move(*valid_request);
230
231 auto opcode = ippGetOperation(in_flight_->request.ipp.get());
232 if (opcode == IPP_OP_CUPS_NONE) {
Luum Habtemariamb9a4f7f62019-10-03 19:18:58233 return Fail("Failed to parse IPP operation ID", HTTP_STATUS_BAD_REQUEST);
Luum Habtemariamc2ab5bc82019-09-06 21:10:05234 }
235
236 // Since Chrome is the source-of-truth for printers on ChromeOS, for
237 // CUPS-Get-Printers requests, we spoof the response rather than proxying to
238 // CUPS.
239 if (opcode == IPP_OP_CUPS_GET_PRINTERS) {
240 SpoofGetPrinters();
241 return;
242 }
243
244 // If this request references a printer, pre-install it into CUPS.
245 auto printer_uuid = GetPrinterId(in_flight_->request.ipp.get());
246 if (printer_uuid.has_value()) {
247 printer_installer_->InstallPrinter(
248 *printer_uuid, base::BindOnce(&ProxyManagerImpl::OnInstallPrinter,
249 weak_factory_.GetWeakPtr()));
250 return;
251 }
252
253 // Nothing left to do, skip straight to proxying.
254 ProxyToCups();
255 return;
256}
257
258void ProxyManagerImpl::SpoofGetPrinters() {
Timothy Loh2058fef8e2020-04-02 23:03:27259 std::vector<chromeos::Printer> printers = FilterPrintersForPluginVm(
Luum Habtemariam1c157b32019-09-20 01:12:02260 delegate_->GetPrinters(chromeos::PrinterClass::kSaved),
Pranav Batraabf9ca82020-09-18 04:32:33261 delegate_->GetPrinters(chromeos::PrinterClass::kEnterprise),
262 delegate_->GetRecentlyUsedPrinters());
Anton Bikineev46bbb972021-05-15 17:53:53263 absl::optional<IppResponse> response =
Timothy Loh2058fef8e2020-04-02 23:03:27264 BuildGetDestsResponse(in_flight_->request, printers);
Luum Habtemariamc2ab5bc82019-09-06 21:10:05265 if (!response.has_value()) {
Luum Habtemariamb9a4f7f62019-10-03 19:18:58266 return Fail("Failed to spoof CUPS-Get-Printers response",
267 HTTP_STATUS_SERVER_ERROR);
Luum Habtemariamc2ab5bc82019-09-06 21:10:05268 }
269
270 ProxyResponseToCaller(response->buffer);
271 return;
272}
273
274void ProxyManagerImpl::OnInstallPrinter(InstallPrinterResult res) {
275 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
276 DCHECK(in_flight_);
277
278 if (res != InstallPrinterResult::kSuccess) {
Luum Habtemariamb9a4f7f62019-10-03 19:18:58279 return Fail("Failed to pre-install printer", HTTP_STATUS_SERVER_ERROR);
Luum Habtemariamc2ab5bc82019-09-06 21:10:05280 }
281
282 ProxyToCups();
283 return;
284}
285
286void ProxyManagerImpl::ProxyToCups() {
287 // Queue request with socket_manager_
288 socket_manager_->ProxyToCups(std::move(in_flight_->request.buffer),
289 base::BindOnce(&ProxyManagerImpl::OnProxyToCups,
290 weak_factory_.GetWeakPtr()));
291}
292
293void ProxyManagerImpl::OnProxyToCups(
294 std::unique_ptr<std::vector<uint8_t>> response) {
295 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
296 DCHECK(in_flight_);
297
298 if (!response) {
Luum Habtemariamb9a4f7f62019-10-03 19:18:58299 return Fail("Failed to proxy request", HTTP_STATUS_SERVER_ERROR);
Luum Habtemariamc2ab5bc82019-09-06 21:10:05300 }
301
302 ProxyResponseToCaller(*response);
303 return;
304}
305
306void ProxyManagerImpl::ProxyResponseToCaller(
307 const std::vector<uint8_t>& response) {
308 // Convert to string for parsing HTTP headers.
309 std::string response_str = ipp_converter::ConvertToString(response);
310 auto end_of_headers = net::HttpUtil::LocateEndOfHeaders(response_str.data(),
311 response_str.size());
312 if (end_of_headers < 0) {
Luum Habtemariamb9a4f7f62019-10-03 19:18:58313 return Fail("IPP response missing end of headers",
314 HTTP_STATUS_SERVER_ERROR);
Luum Habtemariamc2ab5bc82019-09-06 21:10:05315 }
316
317 base::StringPiece headers_slice(response_str.data(), end_of_headers);
318 scoped_refptr<net::HttpResponseHeaders> response_headers =
319 net::HttpResponseHeaders::TryToCreate(headers_slice);
320 if (!response_headers) {
Luum Habtemariamb9a4f7f62019-10-03 19:18:58321 return Fail("Failed to parse HTTP response headers",
322 HTTP_STATUS_SERVER_ERROR);
Luum Habtemariamc2ab5bc82019-09-06 21:10:05323 }
324
325 std::vector<ipp_converter::HttpHeader> parsed_headers;
326 size_t iter = 0;
327 std::string name, value;
328 while (response_headers->EnumerateHeaderLines(&iter, &name, &value)) {
329 parsed_headers.push_back({name, value});
330 }
331
332 // Slice off the ipp_message as is.
333 std::vector<uint8_t> ipp_message(response.begin() + end_of_headers,
334 response.end());
335
336 // Send parsed response back to caller.
337 std::move(in_flight_->cb)
Luum Habtemariamb9a4f7f62019-10-03 19:18:58338 .Run(std::move(parsed_headers), std::move(ipp_message), HTTP_STATUS_OK);
Luum Habtemariamc2ab5bc82019-09-06 21:10:05339 in_flight_.reset();
340}
341
342// TODO(crbug.com/945409): Fail with comprehensive HTTP Error response.
343// Fails current request by running its callback with an empty response and
344// clearing in_flight_.
Luum Habtemariamb9a4f7f62019-10-03 19:18:58345void ProxyManagerImpl::Fail(const std::string& error_message,
346 int http_status_code) {
Luum Habtemariamc2ab5bc82019-09-06 21:10:05347 DCHECK(in_flight_);
348
349 DVLOG(1) << "CupsPrintService Error: " << error_message;
350
Luum Habtemariamb9a4f7f62019-10-03 19:18:58351 std::move(in_flight_->cb).Run({}, {}, http_status_code);
Luum Habtemariamc2ab5bc82019-09-06 21:10:05352 in_flight_.reset();
353}
354
355} // namespace
356
357// static
358std::unique_ptr<ProxyManager> ProxyManager::Create(
359 mojo::PendingReceiver<mojom::CupsProxier> request,
360 std::unique_ptr<CupsProxyServiceDelegate> delegate) {
Pranav Batraffbed602020-09-23 02:38:35361 auto* delegate_ptr = delegate.get();
Luum Habtemariamc2ab5bc82019-09-06 21:10:05362 return std::make_unique<ProxyManagerImpl>(
Pranav Batraffbed602020-09-23 02:38:35363 std::move(request), std::move(delegate),
364 std::make_unique<IppValidator>(delegate_ptr),
365 std::make_unique<PrinterInstaller>(delegate_ptr),
366 SocketManager::Create(delegate_ptr));
Luum Habtemariamc2ab5bc82019-09-06 21:10:05367}
368
369std::unique_ptr<ProxyManager> ProxyManager::CreateForTesting(
370 mojo::PendingReceiver<mojom::CupsProxier> request,
371 std::unique_ptr<CupsProxyServiceDelegate> delegate,
372 std::unique_ptr<IppValidator> ipp_validator,
373 std::unique_ptr<PrinterInstaller> printer_installer,
374 std::unique_ptr<SocketManager> socket_manager) {
375 return std::make_unique<ProxyManagerImpl>(
376 std::move(request), std::move(delegate), std::move(ipp_validator),
377 std::move(printer_installer), std::move(socket_manager));
378}
379
380} // namespace cups_proxy