1 /* ftpget.c - Get a remote file from FTP.
2  *
3  * Copyright 2013 Ranjan Kumar <ranjankumar.bth@gmail.com>
4  * Copyright 2013 Kyungwan Han <asura321@gmail.com>
5  *
6  * No Standard.
7  *
8 USE_FTPGET(NEWTOY(ftpget, "<2cvu:p:P#<0=21>65535", TOYFLAG_BIN))
9 USE_FTPGET(OLDTOY(ftpput, ftpget, TOYFLAG_BIN))
10 
11 config FTPGET
12   bool "ftpget/ftpput"
13   default n
14   help
15     usage: ftpget [-cv] [-u USER -p PASSWORD -P PORT] HOST_NAME [LOCAL_FILENAME] REMOTE_FILENAME
16     usage: ftpput [-v] [-u USER -p PASSWORD -P PORT] HOST_NAME [REMOTE_FILENAME] LOCAL_FILENAME
17 
18     ftpget - Get a remote file from FTP.
19     ftpput - Upload a local file on remote machine through FTP.
20 
21     -c Continue previous transfer.
22     -v Verbose.
23     -u User name.
24     -p Password.
25     -P Port Number (default 21).
26 */
27 #define FOR_ftpget
28 #include "toys.h"
29 
GLOBALS(long port;char * password;char * username;FILE * sockfp;int c;int isget;char buf[sizeof (struct sockaddr_storage)];)30 GLOBALS(
31   long port; //  char *port;
32   char *password;
33   char *username;
34 
35   FILE *sockfp;
36   int c;
37   int isget;
38   char buf[sizeof(struct sockaddr_storage)];
39 )
40 
41 #define DATACONNECTION_OPENED   125
42 #define FTPFILE_STATUSOKAY      150
43 #define FTP_COMMAND_OKAY        200
44 #define FTPFILE_STATUS          213
45 #define FTPSERVER_READY         220
46 #define CLOSE_DATACONECTION     226
47 #define PASSIVE_MODE            227
48 #define USERLOGGED_SUCCESS      230
49 #define PASSWORD_REQUEST        331
50 #define REQUESTED_PENDINGACTION 350
51 
52 
53 static void setport(unsigned port_num)
54 {
55   int af = ((struct sockaddr *)TT.buf)->sa_family;
56 
57   if (af == AF_INET) ((struct sockaddr_in*)TT.buf)->sin_port = port_num;
58   else if (af == AF_INET6) ((struct sockaddr_in6*)TT.buf)->sin6_port = port_num;
59 }
60 
connect_to_stream()61 static int connect_to_stream()
62 {
63   int sockfd, af = ((struct sockaddr *)TT.buf)->sa_family;
64 
65   sockfd = xsocket(af, SOCK_STREAM, 0);
66   if (connect(sockfd, (struct sockaddr*)TT.buf,((af == AF_INET)?
67           sizeof(struct sockaddr_in):sizeof(struct sockaddr_in6))) < 0) {
68     close(sockfd);
69     perror_exit("can't connect to remote host");
70   }
71   return sockfd;
72 }
73 
74 //close ftp connection and print the message.
close_stream(char * msg_str)75 static void close_stream(char *msg_str)
76 {
77   char *str = toybuf; //toybuf holds response data.
78 
79   //Remove garbage chars (from ' ' space to '\x7f') DEL remote server response.
80   while ((*str >= 0x20) && (*str < 0x7f)) str++;
81   *str = '\0';
82   if (TT.sockfp) fclose(TT.sockfp);
83   error_exit("%s server response: %s", (msg_str) ? msg_str:"", toybuf);
84 }
85 
86 //send command to ftp and get return status.
get_ftp_response(char * command,char * param)87 static int get_ftp_response(char *command, char *param)
88 {
89   unsigned cmd_status = 0;
90   char *fmt = "%s %s\r\n";
91 
92   if (command) {
93     if (!param) fmt += 3;
94     fprintf(TT.sockfp, fmt, command, param);
95     fflush(TT.sockfp);
96     if (toys.optflags & FLAG_v)
97       fprintf(stderr, "FTP Request: %s %s\r\n", command, param);
98   }
99 
100   do {
101     if (!fgets(toybuf, sizeof(toybuf)-1, TT.sockfp)) close_stream(NULL);
102   } while (!isdigit(toybuf[0]) || toybuf[3] != ' ');
103 
104   toybuf[3] = '\0';
105   cmd_status = atolx_range(toybuf, 0, INT_MAX);
106   toybuf[3] = ' ';
107   return cmd_status;
108 }
109 
send_requests(void)110 static void send_requests(void)
111 {
112   int cmd_status = 0;
113 
114   //FTP connection request.
115   if (get_ftp_response(NULL, NULL) != FTPSERVER_READY) close_stream(NULL);
116 
117   //230 User authenticated, password please; 331 Password request.
118   cmd_status = get_ftp_response("USER", TT.username);
119   if (cmd_status == PASSWORD_REQUEST) { //user logged in. Need Password.
120     if (get_ftp_response("PASS", TT.password) != USERLOGGED_SUCCESS)
121       close_stream("PASS");
122   } else if (cmd_status == USERLOGGED_SUCCESS); //do nothing
123   else close_stream("USER");
124   //200 Type Binary. Command okay.
125   if (get_ftp_response("TYPE I", NULL) != FTP_COMMAND_OKAY)
126     close_stream("TYPE I");
127 }
128 
get_sockaddr(char * host)129 static void get_sockaddr(char *host)
130 {
131   struct addrinfo hints, *result;
132   char port[6];
133   int status;
134 
135   errno = 0;
136   snprintf(port, 6, "%ld", TT.port);
137 
138   memset(&hints, 0 , sizeof(struct addrinfo));
139   hints.ai_family = AF_UNSPEC;
140   hints.ai_socktype = SOCK_STREAM;
141 
142   status = getaddrinfo(host, port, &hints, &result);
143   if (status) error_exit("bad address '%s' : %s", host, gai_strerror(status));
144 
145   memcpy(TT.buf, result->ai_addr, result->ai_addrlen);
146   freeaddrinfo(result);
147 }
148 
149 // send commands to ftp fo PASV mode.
verify_pasv_mode(char * r_filename)150 static void verify_pasv_mode(char *r_filename)
151 {
152   char *pch;
153   unsigned portnum;
154 
155   //vsftpd reply like:- "227 Entering Passive Mode (125,19,39,117,43,39)".
156   if (get_ftp_response("PASV", NULL) != PASSIVE_MODE) goto close_stream;
157 
158   //Response is "NNN <some text> (N1,N2,N3,N4,P1,P2) garbage.
159   //Server's IP is N1.N2.N3.N4
160   //Server's port for data connection is P1*256+P2.
161   if (!(pch = strrchr(toybuf, ')'))) goto close_stream;
162   *pch = '\0';
163   if (!(pch = strrchr(toybuf, ','))) goto close_stream;
164   *pch = '\0';
165 
166   portnum = atolx_range(pch + 1, 0, 255);
167 
168   if (!(pch = strrchr(toybuf, ','))) goto close_stream;
169   *pch = '\0';
170   portnum = portnum + (atolx_range(pch + 1, 0, 255) * 256);
171   setport(htons(portnum));
172 
173   if (TT.isget && get_ftp_response("SIZE", r_filename) != FTPFILE_STATUS)
174     TT.c = 0;
175   return;
176 
177 close_stream:
178   close_stream("PASV");
179 }
180 
181 /*
182  * verify the local file presence.
183  * if present, get the size of the file.
184  */
is_localfile_present(char * l_filename)185 static void is_localfile_present(char *l_filename)
186 {
187   struct stat sb;
188 
189   if (stat(l_filename, &sb) < 0) perror_exit("stat");
190   //if local file present, then request for pending file action.
191   if (sb.st_size > 0) {
192     sprintf(toybuf, "REST %lu", (unsigned long) sb.st_size);
193     if (get_ftp_response(toybuf, NULL) != REQUESTED_PENDINGACTION) TT.c = 0;
194   } else TT.c = 0;
195 }
196 
transfer_file(int local_fd,int remote_fd)197 static void transfer_file(int local_fd, int remote_fd)
198 {
199   int len, rfd = (TT.isget)?remote_fd:local_fd,
200       wfd = (TT.isget)?local_fd:remote_fd;
201 
202   if (rfd < 0 || wfd < 0) error_exit("Error in file creation:");
203   while ((len = xread(rfd, toybuf, sizeof(toybuf)))) xwrite(wfd, toybuf, len);
204 }
205 
get_file(char * l_filename,char * r_filename)206 static void get_file(char *l_filename, char *r_filename)
207 {
208   int local_fd = -1, remote_fd;
209 
210   verify_pasv_mode(r_filename);
211   remote_fd = connect_to_stream(); //Connect to data socket.
212 
213   //if local file name will be '-' then local fd will be stdout.
214   if ((l_filename[0] == '-') && !l_filename[1]) {
215     local_fd = 1; //file descriptor will become stdout.
216     TT.c = 0;
217   }
218 
219   //if continue, check for local file existance.
220   if (TT.c) is_localfile_present(l_filename);
221 
222   //verify the remote file presence.
223   if (get_ftp_response("RETR", r_filename) > FTPFILE_STATUSOKAY)
224     close_stream("RETR");
225 
226   //if local fd is not stdout, create a file descriptor.
227   if (local_fd == -1) {
228     int flags = O_WRONLY;
229 
230     flags |= (TT.c)? O_APPEND : (O_CREAT | O_TRUNC);
231     local_fd = xcreate((char *)l_filename, flags, 0666);
232   }
233   transfer_file(local_fd, remote_fd);
234   xclose(remote_fd);
235   xclose(local_fd);
236   if (get_ftp_response(NULL, NULL) != CLOSE_DATACONECTION) close_stream(NULL);
237   get_ftp_response("QUIT", NULL);
238   toys.exitval = EXIT_SUCCESS;
239 }
240 
put_file(char * r_filename,char * l_filename)241 static void put_file(char *r_filename, char *l_filename)
242 {
243   int local_fd = 0, remote_fd;
244   unsigned cmd_status = 0;
245 
246   verify_pasv_mode(r_filename);
247   remote_fd = connect_to_stream(); //Connect to data socket.
248 
249   //open the local file for transfer.
250   if ((l_filename[0] != '-') || l_filename[1])
251     local_fd = xcreate((char *)l_filename, O_RDONLY, 0666);
252 
253   //verify for the remote file status, Ok or Open: transfer File.
254   cmd_status = get_ftp_response("STOR", r_filename);
255   if ( (cmd_status == DATACONNECTION_OPENED) ||
256       (cmd_status == FTPFILE_STATUSOKAY)) {
257     transfer_file(local_fd, remote_fd);
258     if (get_ftp_response(NULL, NULL) != CLOSE_DATACONECTION) close_stream(NULL);
259     get_ftp_response("QUIT", NULL);
260     toys.exitval = EXIT_SUCCESS;
261   } else {
262     toys.exitval = EXIT_FAILURE;
263     close_stream("STOR");
264   }
265   xclose(remote_fd);
266   xclose(local_fd);
267 }
268 
ftpget_main(void)269 void ftpget_main(void)
270 {
271   char **argv = toys.optargs; //host name + file name.
272 
273   TT.isget = toys.which->name[3] == 'g';
274   TT.c = 1;
275   //if user name is not specified.
276   if (!(toys.optflags & FLAG_u) && (toys.optflags & FLAG_p))
277     error_exit("Missing username:");
278   //if user name and password is not specified in command line.
279   if (!(toys.optflags & FLAG_u) && !(toys.optflags & FLAG_p))
280     TT.username = TT.password ="anonymous";
281 
282   //if continue is not in the command line argument.
283   if (TT.isget && !(toys.optflags & FLAG_c)) TT.c = 0;
284 
285   if (toys.optflags & FLAG_v) fprintf(stderr, "Connecting to %s\n", argv[0]);
286   get_sockaddr(argv[0]);
287 
288   TT.sockfp = xfdopen(connect_to_stream(), "r+");
289   send_requests();
290 
291   if (TT.isget) get_file(argv[1], argv[2] ? argv[2] : argv[1]);
292   else put_file(argv[1], argv[2] ? argv[2] : argv[1]);
293 }
294