foo
[oftp] / src / ftp.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <stdarg.h>
5 #include <errno.h>
6 #include <ctype.h>
7 #include <sys/socket.h>
8 #include <arpa/inet.h>
9 #include <netinet/in.h>
10 #include <netdb.h>
11 #include "darray.h"
12 #include "ftp.h"
13 #include "util.h"
14
15 #ifdef __unix__
16 #include <unistd.h>
17 #include <fcntl.h>
18 #define closesocket             close
19 #define fcntlsocket             fcntl
20 #endif
21
22 static int sendcmd(struct ftp *ftp, const char *fmt, ...);
23 static int handle_control(struct ftp *ftp);
24 static int handle_data(struct ftp *ftp, int s);
25 static void proc_control(struct ftp *ftp, const char *buf);
26
27 static void cproc_pwd(struct ftp *ftp, int code, const char *buf, void *cls);
28 static void cproc_cwd(struct ftp *ftp, int code, const char *buf, void *cls);
29
30
31 struct ftp *ftp_alloc(void)
32 {
33         struct ftp *ftp;
34
35         if(!(ftp = calloc(1, sizeof *ftp))) {
36                 return 0;
37         }
38         ftp->ctl = ftp->data = -1;
39         return ftp;
40 }
41
42 void ftp_free(struct ftp *ftp)
43 {
44         if(!ftp) return;
45
46         if(ftp->ctl >= 0) {
47                 ftp_close(ftp);
48         }
49 }
50
51 int ftp_connect(struct ftp *ftp, const char *hostname, int port)
52 {
53         struct hostent *host;
54         struct sockaddr_in addr;
55
56         if((ftp->ctl = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
57                 errmsg("failed to create control socket\n");
58                 return -1;
59         }
60
61         if(!(host = gethostbyname(hostname))) {
62                 errmsg("failed to resolve host: %s\n", hostname);
63                 closesocket(ftp->ctl);
64                 ftp->ctl = -1;
65                 return -1;
66         }
67
68         memset(&addr, 0, sizeof addr);
69         addr.sin_family = AF_INET;
70         addr.sin_addr = *((struct in_addr*)host->h_addr);
71         addr.sin_port = htons(port);
72
73         if(connect(ftp->ctl, (struct sockaddr*)&addr, sizeof addr) == -1) {
74                 errmsg("failed to connect to: %s (port: %d)\n", hostname, port);
75                 closesocket(ftp->ctl);
76                 ftp->ctl = -1;
77                 return -1;
78         }
79
80         fcntlsocket(ftp->ctl, F_SETFL, fcntlsocket(ftp->ctl, F_GETFL) | O_NONBLOCK);
81         ftp->num_crecv = 0;
82         return 0;
83 }
84
85 void ftp_close(struct ftp *ftp)
86 {
87         if(ftp->ctl >= 0) {
88                 closesocket(ftp->ctl);
89                 ftp->ctl = -1;
90                 ftp->num_crecv = 0;
91         }
92         if(ftp->data >= 0) {
93                 closesocket(ftp->data);
94                 ftp->data = -1;
95                 ftp->num_drecv = 0;
96         }
97 }
98
99 int ftp_sockets(struct ftp *ftp, int *sockv, int maxsize)
100 {
101         if(ftp->ctl >= 0 && maxsize) {
102                 *sockv++ = ftp->ctl;
103                 maxsize--;
104         }
105         return 1;
106 }
107
108 int ftp_pending(struct ftp *ftp)
109 {
110         int maxfd = -1;
111         fd_set rdset;
112         struct timeval tv = {0, 0};
113
114         FD_ZERO(&rdset);
115         if(ftp->ctl >= 0) {
116                 FD_SET(ftp->ctl, &rdset);
117                 maxfd = ftp->ctl;
118         }
119         if(ftp->data >= 0) {
120                 FD_SET(ftp->data, &rdset);
121                 if(ftp->data > maxfd) maxfd = ftp->data;
122         }
123         if(maxfd == -1) return 0;
124
125         return select(maxfd + 1, &rdset, 0, 0, &tv) > 0 ? 1 : 0;
126 }
127
128 int ftp_handle(struct ftp *ftp, int s)
129 {
130         if(s == ftp->ctl) {
131                 return handle_control(ftp);
132         }
133         if(s == ftp->data) {
134                 return handle_data(ftp, s);
135         }
136         return -1;
137 }
138
139 static int sendcmd(struct ftp *ftp, const char *fmt, ...)
140 {
141         char buf[256];
142         va_list ap;
143
144         if(ftp->ctl < 0) {
145                 return -1;
146         }
147
148         va_start(ap, fmt);
149         vsprintf(buf, fmt, ap);
150         va_end(ap);
151         strcat(buf, "\r\n");
152
153         return send(ftp->ctl, buf, strlen(buf), 0);
154 }
155
156 static int handle_control(struct ftp *ftp)
157 {
158         int i, sz, rd;
159         char *buf, *start, *end;
160
161         while((sz = sizeof ftp->crecv - ftp->num_crecv) > 0) {
162                 start = ftp->crecv + ftp->num_crecv;
163                 if((rd = recv(ftp->ctl, start, sz, 0)) == -1) {
164                         if(errno == EINTR) continue;
165                         /* assume EWOULDBLOCK, try again next time */
166                         return 0;
167                 }
168                 if(rd == 0) {
169                         ftp_close(ftp);
170                         return -1;
171                 }
172
173                 end = start + rd;
174                 buf = ftp->crecv;
175                 for(i=0; i<rd; i++) {
176                         if(start[i] == '\n') {
177                                 start[i] = 0;
178                                 proc_control(ftp, buf);
179                                 buf = start + i + 1;
180                         }
181                 }
182                 if(buf != ftp->crecv && buf < end) {
183                         ftp->num_crecv = end - buf;
184                         memmove(ftp->crecv, buf, ftp->num_crecv);
185                 }
186         }
187         return 0;
188 }
189
190 static int handle_data(struct ftp *ftp, int s)
191 {
192         return -1;
193 }
194
195 static int respcode(const char *resp)
196 {
197         if(!isdigit(resp[0]) || !isdigit(resp[1]) || !isdigit(resp[2])) {
198                 return 0;
199         }
200
201         if(isspace(resp[3])) {
202                 return atoi(resp);
203         }
204         if(resp[3] == '-') {
205                 return -atoi(resp);
206         }
207         return 0;
208 }
209
210 static void proc_control(struct ftp *ftp, const char *buf)
211 {
212         int code;
213
214         while(*buf && isspace(*buf)) buf++;
215
216         if((code = respcode(buf)) == 0) {
217                 warnmsg("ignoring invalid response: %s\n", buf);
218                 return;
219         }
220         if(code < 0) {
221                 return; /* ignore continuations for now */
222         }
223
224         if(ftp->cproc) {
225                 ftp->cproc(ftp, code, buf, ftp->cproc_cls);
226                 ftp->cproc = 0;
227                 return;
228         }
229
230         switch(code) {
231         case 220:
232                 sendcmd(ftp, "user %s", ftp->user ? ftp->user : "anonymous");
233                 break;
234         case 331:
235                 sendcmd(ftp, "pass %s", ftp->pass ? ftp->pass : "foobar");
236                 break;
237         case 230:
238                 ftp->status = 1;
239                 infomsg("login successful\n");
240                 ftp_pwd(ftp);
241                 ftp->modified = 1;
242                 break;
243         case 530:
244                 ftp->status = 0;
245                 errmsg("login failed\n");
246                 break;
247         }
248 }
249
250 int ftp_update(struct ftp *ftp)
251 {
252         return -1;
253 }
254
255 int ftp_pwd(struct ftp *ftp)
256 {
257         sendcmd(ftp, "pwd");
258         ftp->cproc = cproc_pwd;
259         return 0;
260 }
261
262 int ftp_chdir(struct ftp *ftp, const char *dirname)
263 {
264         // TODO queue
265         sendcmd(ftp, "cwd %s", dirname);
266         ftp->cproc = cproc_cwd;
267         return 0;
268 }
269
270 static int get_quoted_text(const char *str, char *buf)
271 {
272         int len;
273         const char *src, *end;
274
275         if(!(src = strchr(str, '"'))) {
276                 return -1;
277         }
278         src++;
279         end = src;
280         while(*end && *end != '"') end++;
281         if(!*end) return -1;
282
283         len = end - src;
284         memcpy(buf, src, len);
285         buf[len] = 0;
286         return 0;
287 }
288
289 static void cproc_pwd(struct ftp *ftp, int code, const char *buf, void *cls)
290 {
291         char *dirname;
292
293         if(code != 257) {
294                 warnmsg("pwd failed\n");
295                 return;
296         }
297
298         dirname = alloca(strlen(buf) + 1);
299         if(get_quoted_text(buf, dirname) == -1) {
300                 warnmsg("pwd: invalid response: %s\n", buf);
301                 return;
302         }
303
304         free(ftp->curdir_rem);
305         ftp->curdir_rem = strdup_nf(dirname);
306         ftp->modified = 1;
307 }
308
309 static void cproc_cwd(struct ftp *ftp, int code, const char *buf, void *cls)
310 {
311 }