1 /* <!-- copyright */
2 /*
3 * aria2 - The high speed download utility
4 *
5 * Copyright (C) 2006 Tatsuhiro Tsujikawa
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 *
21 * In addition, as a special exception, the copyright holders give
22 * permission to link the code of portions of this program with the
23 * OpenSSL library under certain conditions as described in each
24 * individual source file, and distribute linked combinations
25 * including the two.
26 * You must obey the GNU General Public License in all respects
27 * for all of the code used other than OpenSSL. If you modify
28 * file(s) with this exception, you may extend this exception to your
29 * version of the file(s), but you are not obligated to do so. If you
30 * do not wish to do so, delete this exception statement from your
31 * version. If you delete this exception statement from all source
32 * files in the program, then also delete it here.
33 */
34 /* copyright --> */
35 #include "FtpConnection.h"
36
37 #include <array>
38 #include <cstring>
39 #include <cstdio>
40 #include <cassert>
41
42 #include "Request.h"
43 #include "Segment.h"
44 #include "Option.h"
45 #include "util.h"
46 #include "message.h"
47 #include "prefs.h"
48 #include "LogFactory.h"
49 #include "Logger.h"
50 #include "AuthConfigFactory.h"
51 #include "AuthConfig.h"
52 #include "DlRetryEx.h"
53 #include "DlAbortEx.h"
54 #include "SocketCore.h"
55 #include "A2STR.h"
56 #include "fmt.h"
57 #include "AuthConfig.h"
58 #include "a2functional.h"
59 #include "util.h"
60 #include "error_code.h"
61
62 namespace aria2 {
63
FtpConnection(cuid_t cuid,const std::shared_ptr<SocketCore> & socket,const std::shared_ptr<Request> & req,const std::shared_ptr<AuthConfig> & authConfig,const Option * op)64 FtpConnection::FtpConnection(cuid_t cuid,
65 const std::shared_ptr<SocketCore>& socket,
66 const std::shared_ptr<Request>& req,
67 const std::shared_ptr<AuthConfig>& authConfig,
68 const Option* op)
69 : cuid_(cuid),
70 socket_(socket),
71 req_(req),
72 authConfig_(authConfig),
73 option_(op),
74 socketBuffer_(socket),
75 baseWorkingDir_("/")
76 {
77 }
78
79 FtpConnection::~FtpConnection() = default;
80
sendUser()81 bool FtpConnection::sendUser()
82 {
83 if (socketBuffer_.sendBufferIsEmpty()) {
84 std::string request = "USER ";
85 request += authConfig_->getUser();
86 request += "\r\n";
87 A2_LOG_INFO(fmt(MSG_SENDING_REQUEST, cuid_, "USER ********"));
88 socketBuffer_.pushStr(std::move(request));
89 }
90 socketBuffer_.send();
91 return socketBuffer_.sendBufferIsEmpty();
92 }
93
sendPass()94 bool FtpConnection::sendPass()
95 {
96 if (socketBuffer_.sendBufferIsEmpty()) {
97 std::string request = "PASS ";
98 request += authConfig_->getPassword();
99 request += "\r\n";
100 A2_LOG_INFO(fmt(MSG_SENDING_REQUEST, cuid_, "PASS ********"));
101 socketBuffer_.pushStr(std::move(request));
102 }
103 socketBuffer_.send();
104 return socketBuffer_.sendBufferIsEmpty();
105 }
106
sendType()107 bool FtpConnection::sendType()
108 {
109 if (socketBuffer_.sendBufferIsEmpty()) {
110 std::string request = "TYPE ";
111 request += (option_->get(PREF_FTP_TYPE) == V_ASCII ? 'A' : 'I');
112 request += "\r\n";
113 A2_LOG_INFO(fmt(MSG_SENDING_REQUEST, cuid_, request.c_str()));
114 socketBuffer_.pushStr(std::move(request));
115 }
116 socketBuffer_.send();
117 return socketBuffer_.sendBufferIsEmpty();
118 }
119
sendPwd()120 bool FtpConnection::sendPwd()
121 {
122 if (socketBuffer_.sendBufferIsEmpty()) {
123 std::string request = "PWD\r\n";
124 A2_LOG_INFO(fmt(MSG_SENDING_REQUEST, cuid_, request.c_str()));
125 socketBuffer_.pushStr(std::move(request));
126 }
127 socketBuffer_.send();
128 return socketBuffer_.sendBufferIsEmpty();
129 }
130
sendCwd(const std::string & dir)131 bool FtpConnection::sendCwd(const std::string& dir)
132 {
133 if (socketBuffer_.sendBufferIsEmpty()) {
134 std::string request = "CWD ";
135 request += util::percentDecode(dir.begin(), dir.end());
136 request += "\r\n";
137 A2_LOG_INFO(fmt(MSG_SENDING_REQUEST, cuid_, request.c_str()));
138 socketBuffer_.pushStr(std::move(request));
139 }
140 socketBuffer_.send();
141 return socketBuffer_.sendBufferIsEmpty();
142 }
143
sendMdtm()144 bool FtpConnection::sendMdtm()
145 {
146 if (socketBuffer_.sendBufferIsEmpty()) {
147 std::string request = "MDTM ";
148 request +=
149 util::percentDecode(req_->getFile().begin(), req_->getFile().end());
150 request += "\r\n";
151 A2_LOG_INFO(fmt(MSG_SENDING_REQUEST, cuid_, request.c_str()));
152 socketBuffer_.pushStr(std::move(request));
153 }
154 socketBuffer_.send();
155 return socketBuffer_.sendBufferIsEmpty();
156 }
157
sendSize()158 bool FtpConnection::sendSize()
159 {
160 if (socketBuffer_.sendBufferIsEmpty()) {
161 std::string request = "SIZE ";
162 request +=
163 util::percentDecode(req_->getFile().begin(), req_->getFile().end());
164 request += "\r\n";
165 A2_LOG_INFO(fmt(MSG_SENDING_REQUEST, cuid_, request.c_str()));
166 socketBuffer_.pushStr(std::move(request));
167 }
168 socketBuffer_.send();
169 return socketBuffer_.sendBufferIsEmpty();
170 }
171
sendEpsv()172 bool FtpConnection::sendEpsv()
173 {
174 if (socketBuffer_.sendBufferIsEmpty()) {
175 std::string request("EPSV\r\n");
176 A2_LOG_INFO(fmt(MSG_SENDING_REQUEST, cuid_, request.c_str()));
177 socketBuffer_.pushStr(std::move(request));
178 }
179 socketBuffer_.send();
180 return socketBuffer_.sendBufferIsEmpty();
181 }
182
sendPasv()183 bool FtpConnection::sendPasv()
184 {
185 if (socketBuffer_.sendBufferIsEmpty()) {
186 std::string request("PASV\r\n");
187 A2_LOG_INFO(fmt(MSG_SENDING_REQUEST, cuid_, request.c_str()));
188 socketBuffer_.pushStr(std::move(request));
189 }
190 socketBuffer_.send();
191 return socketBuffer_.sendBufferIsEmpty();
192 }
193
createServerSocket()194 std::shared_ptr<SocketCore> FtpConnection::createServerSocket()
195 {
196 auto endpoint = socket_->getAddrInfo();
197 auto serverSocket = std::make_shared<SocketCore>();
198 serverSocket->bind(endpoint.addr.c_str(), 0, AF_UNSPEC);
199 serverSocket->beginListen();
200 return serverSocket;
201 }
202
sendEprt(const std::shared_ptr<SocketCore> & serverSocket)203 bool FtpConnection::sendEprt(const std::shared_ptr<SocketCore>& serverSocket)
204 {
205 if (socketBuffer_.sendBufferIsEmpty()) {
206 auto endpoint = serverSocket->getAddrInfo();
207 auto request =
208 fmt("EPRT |%d|%s|%u|\r\n", endpoint.family == AF_INET ? 1 : 2,
209 endpoint.addr.c_str(), endpoint.port);
210 A2_LOG_INFO(fmt(MSG_SENDING_REQUEST, cuid_, request.c_str()));
211 socketBuffer_.pushStr(std::move(request));
212 }
213 socketBuffer_.send();
214 return socketBuffer_.sendBufferIsEmpty();
215 }
216
sendPort(const std::shared_ptr<SocketCore> & serverSocket)217 bool FtpConnection::sendPort(const std::shared_ptr<SocketCore>& serverSocket)
218 {
219 if (socketBuffer_.sendBufferIsEmpty()) {
220 auto endpoint = socket_->getAddrInfo();
221 int ipaddr[4];
222 sscanf(endpoint.addr.c_str(), "%d.%d.%d.%d", &ipaddr[0], &ipaddr[1],
223 &ipaddr[2], &ipaddr[3]);
224 auto svEndpoint = serverSocket->getAddrInfo();
225 auto request =
226 fmt("PORT %d,%d,%d,%d,%d,%d\r\n", ipaddr[0], ipaddr[1], ipaddr[2],
227 ipaddr[3], svEndpoint.port / 256, svEndpoint.port % 256);
228 A2_LOG_INFO(fmt(MSG_SENDING_REQUEST, cuid_, request.c_str()));
229 socketBuffer_.pushStr(std::move(request));
230 }
231 socketBuffer_.send();
232 return socketBuffer_.sendBufferIsEmpty();
233 }
234
sendRest(const std::shared_ptr<Segment> & segment)235 bool FtpConnection::sendRest(const std::shared_ptr<Segment>& segment)
236 {
237 if (socketBuffer_.sendBufferIsEmpty()) {
238 std::string request =
239 fmt("REST %" PRId64 "\r\n", segment ? segment->getPositionToWrite()
240 : static_cast<int64_t>(0LL));
241 A2_LOG_INFO(fmt(MSG_SENDING_REQUEST, cuid_, request.c_str()));
242 socketBuffer_.pushStr(std::move(request));
243 }
244 socketBuffer_.send();
245 return socketBuffer_.sendBufferIsEmpty();
246 }
247
sendRetr()248 bool FtpConnection::sendRetr()
249 {
250 if (socketBuffer_.sendBufferIsEmpty()) {
251 std::string request = "RETR ";
252 request +=
253 util::percentDecode(req_->getFile().begin(), req_->getFile().end());
254 request += "\r\n";
255 A2_LOG_INFO(fmt(MSG_SENDING_REQUEST, cuid_, request.c_str()));
256 socketBuffer_.pushStr(std::move(request));
257 }
258 socketBuffer_.send();
259 return socketBuffer_.sendBufferIsEmpty();
260 }
261
getStatus(const std::string & response) const262 int FtpConnection::getStatus(const std::string& response) const
263 {
264 int status;
265 // When the response is not like "%d %*s",
266 // we return 0.
267 if (response.find_first_not_of("0123456789") != 3 ||
268 !(response.find(" ") == 3 || response.find("-") == 3)) {
269 return 0;
270 }
271 if (sscanf(response.c_str(), "%d %*s", &status) == 1) {
272 return status;
273 }
274 else {
275 return 0;
276 }
277 }
278
279 // Returns the length of the response if the whole response has been received.
280 // The length includes \r\n.
281 // If the whole response has not been received, then returns std::string::npos.
282 std::string::size_type
findEndOfResponse(int status,const std::string & buf) const283 FtpConnection::findEndOfResponse(int status, const std::string& buf) const
284 {
285 if (buf.size() <= 4) {
286 return std::string::npos;
287 }
288 // if 4th character of buf is '-', then multi line response is expected.
289 if (buf.at(3) == '-') {
290 // multi line response
291 std::string::size_type p;
292 p = buf.find(fmt("\r\n%d ", status));
293 if (p == std::string::npos) {
294 return std::string::npos;
295 }
296 p = buf.find("\r\n", p + 6);
297 if (p == std::string::npos) {
298 return std::string::npos;
299 }
300 else {
301 return p + 2;
302 }
303 }
304 else {
305 // single line response
306 std::string::size_type p = buf.find("\r\n");
307 if (p == std::string::npos) {
308 return std::string::npos;
309 }
310 else {
311 return p + 2;
312 }
313 }
314 }
315
bulkReceiveResponse(std::pair<int,std::string> & response)316 bool FtpConnection::bulkReceiveResponse(std::pair<int, std::string>& response)
317 {
318 std::array<char, 1_k> buf;
319 while (1) {
320 size_t size = buf.size();
321 socket_->readData(buf.data(), size);
322 if (size == 0) {
323 if (socket_->wantRead() || socket_->wantWrite()) {
324 break;
325 }
326 else {
327 throw DL_RETRY_EX(EX_GOT_EOF);
328 }
329 }
330 if (strbuf_.size() + size > MAX_RECV_BUFFER) {
331 throw DL_RETRY_EX(fmt("Max FTP recv buffer reached. length=%lu",
332 static_cast<unsigned long>(strbuf_.size() + size)));
333 }
334 strbuf_.append(std::begin(buf), std::begin(buf) + size);
335 }
336 int status;
337 if (strbuf_.size() >= 4) {
338 status = getStatus(strbuf_);
339 if (status == 0) {
340 throw DL_ABORT_EX2(EX_INVALID_RESPONSE, error_code::FTP_PROTOCOL_ERROR);
341 }
342 }
343 else {
344 return false;
345 }
346 std::string::size_type length;
347 if ((length = findEndOfResponse(status, strbuf_)) != std::string::npos) {
348 response.first = status;
349 response.second.assign(strbuf_.begin(), strbuf_.begin() + length);
350 A2_LOG_INFO(fmt(MSG_RECEIVE_RESPONSE, cuid_, response.second.c_str()));
351 strbuf_.erase(0, length);
352 return true;
353 }
354 else {
355 // didn't receive response fully.
356 return false;
357 }
358 }
359
receiveResponse()360 int FtpConnection::receiveResponse()
361 {
362 std::pair<int, std::string> response;
363 if (bulkReceiveResponse(response)) {
364 return response.first;
365 }
366 else {
367 return 0;
368 }
369 }
370
371 #ifdef __MINGW32__
372 # define LONGLONG_PRINTF "%I64d"
373 # define ULONGLONG_PRINTF "%I64u"
374 # define LONGLONG_SCANF "%I64d"
375 # define ULONGLONG_SCANF "%I64u"
376 #else
377 # define LONGLONG_PRINTF "%" PRId64 ""
378 # define ULONGLONG_PRINTF "%llu"
379 # define LONGLONG_SCANF "%Ld"
380 // Mac OSX uses "%llu" for 64bits integer.
381 # define ULONGLONG_SCANF "%Lu"
382 #endif // __MINGW32__
383
receiveSizeResponse(int64_t & size)384 int FtpConnection::receiveSizeResponse(int64_t& size)
385 {
386 std::pair<int, std::string> response;
387 if (bulkReceiveResponse(response)) {
388 if (response.first == 213) {
389 auto rp = util::divide(std::begin(response.second),
390 std::end(response.second), ' ');
391 if (!util::parseLLIntNoThrow(
392 size, std::string(rp.second.first, rp.second.second)) ||
393 size < 0) {
394 throw DL_ABORT_EX("Size must be positive integer");
395 }
396 }
397 return response.first;
398 }
399 else {
400 return 0;
401 }
402 }
403
404 namespace {
405 template <typename T>
getInteger(T * dest,const char * first,const char * last)406 bool getInteger(T* dest, const char* first, const char* last)
407 {
408 int res = 0;
409 // We assume *dest won't overflow.
410 for (; first != last; ++first) {
411 if (util::isDigit(*first)) {
412 res *= 10;
413 res += (*first) - '0';
414 }
415 else {
416 return false;
417 }
418 }
419 *dest = res;
420 return true;
421 }
422 } // namespace
423
receiveMdtmResponse(Time & time)424 int FtpConnection::receiveMdtmResponse(Time& time)
425 {
426 // MDTM command, specified in RFC3659.
427 std::pair<int, std::string> response;
428 if (bulkReceiveResponse(response)) {
429 if (response.first == 213) {
430 char buf[15]; // YYYYMMDDhhmmss+\0, milli second part is dropped.
431 sscanf(response.second.c_str(), "%*u %14s", buf);
432 if (strlen(buf) == 14) {
433 // We don't use Time::parse(buf,"%Y%m%d%H%M%S") here because Mac OS X
434 // and included strptime doesn't parse data for this format.
435 struct tm tm;
436 memset(&tm, 0, sizeof(tm));
437 if (getInteger(&tm.tm_sec, &buf[12], &buf[14]) &&
438 getInteger(&tm.tm_min, &buf[10], &buf[12]) &&
439 getInteger(&tm.tm_hour, &buf[8], &buf[10]) &&
440 getInteger(&tm.tm_mday, &buf[6], &buf[8]) &&
441 getInteger(&tm.tm_mon, &buf[4], &buf[6]) &&
442 getInteger(&tm.tm_year, &buf[0], &buf[4])) {
443 tm.tm_mon -= 1;
444 tm.tm_year -= 1900;
445 time = Time(timegm(&tm));
446 }
447 else {
448 time = Time::null();
449 }
450 }
451 else {
452 time = Time::null();
453 }
454 }
455 return response.first;
456 }
457 else {
458 return 0;
459 }
460 }
461
receiveEpsvResponse(uint16_t & port)462 int FtpConnection::receiveEpsvResponse(uint16_t& port)
463 {
464 std::pair<int, std::string> response;
465 if (bulkReceiveResponse(response)) {
466 if (response.first == 229) {
467 port = 0;
468 std::string::size_type leftParen = response.second.find("(");
469 std::string::size_type rightParen = response.second.find(")");
470 if (leftParen == std::string::npos || rightParen == std::string::npos ||
471 leftParen > rightParen) {
472 return response.first;
473 }
474 std::vector<Scip> rd;
475 util::splitIter(response.second.begin() + leftParen + 1,
476 response.second.begin() + rightParen,
477 std::back_inserter(rd), '|', true, true);
478 uint32_t portTemp = 0;
479 if (rd.size() == 5 &&
480 util::parseUIntNoThrow(portTemp,
481 std::string(rd[3].first, rd[3].second))) {
482 if (0 < portTemp && portTemp <= UINT16_MAX) {
483 port = portTemp;
484 }
485 }
486 }
487 return response.first;
488 }
489 else {
490 return 0;
491 }
492 }
493
receivePasvResponse(std::pair<std::string,uint16_t> & dest)494 int FtpConnection::receivePasvResponse(std::pair<std::string, uint16_t>& dest)
495 {
496 std::pair<int, std::string> response;
497 if (bulkReceiveResponse(response)) {
498 if (response.first == 227) {
499 // we assume the format of response is "227 Entering Passive
500 // Mode (h1,h2,h3,h4,p1,p2)."
501 int h1, h2, h3, h4, p1, p2;
502 std::string::size_type p = response.second.find("(");
503 if (p >= 4) {
504 sscanf(response.second.c_str() + p, "(%d,%d,%d,%d,%d,%d).", &h1, &h2,
505 &h3, &h4, &p1, &p2);
506 // ip address
507 dest.first = fmt("%d.%d.%d.%d", h1, h2, h3, h4);
508 // port number
509 dest.second = 256 * p1 + p2;
510 }
511 else {
512 throw DL_RETRY_EX(EX_INVALID_RESPONSE);
513 }
514 }
515 return response.first;
516 }
517 else {
518 return 0;
519 }
520 }
521
receivePwdResponse(std::string & pwd)522 int FtpConnection::receivePwdResponse(std::string& pwd)
523 {
524 std::pair<int, std::string> response;
525 if (bulkReceiveResponse(response)) {
526 if (response.first == 257) {
527 std::string::size_type first;
528 std::string::size_type last;
529
530 if ((first = response.second.find("\"")) != std::string::npos &&
531 (last = response.second.find("\"", ++first)) != std::string::npos) {
532 pwd.assign(response.second.begin() + first,
533 response.second.begin() + last);
534 }
535 else {
536 throw DL_ABORT_EX2(EX_INVALID_RESPONSE, error_code::FTP_PROTOCOL_ERROR);
537 }
538 }
539 return response.first;
540 }
541 else {
542 return 0;
543 }
544 }
545
setBaseWorkingDir(const std::string & baseWorkingDir)546 void FtpConnection::setBaseWorkingDir(const std::string& baseWorkingDir)
547 {
548 baseWorkingDir_ = baseWorkingDir;
549 }
550
getUser() const551 const std::string& FtpConnection::getUser() const
552 {
553 return authConfig_->getUser();
554 }
555
556 } // namespace aria2
557