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