1 /* socket.c - twoftpd routines for handling sockets
2  * Copyright (C) 2008  Bruce Guenter <bruce@untroubled.org>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18 #include <errno.h>
19 #include <string.h>
20 #include <unistd.h>
21 #include <bglibs/fmt.h>
22 #include <bglibs/socket.h>
23 #include "twoftpd.h"
24 #include "backend.h"
25 #include <bglibs/sysdeps.h>
26 #include <bglibs/unix.h>
27 
28 /* State variables */
29 static int socket_fd = -1;
30 static ipv4addr socket_ip;
31 static unsigned short socket_port;
32 static ipv4addr remote_ip;
33 static unsigned short remote_port;
34 static ipv4addr client_ip;
35 static ipv4addr server_ip;
36 static enum { NONE, PASV, PORT } connect_mode = NONE;
37 
respond_timedoutconn(void)38 static int respond_timedoutconn(void)
39 {
40   return respond(425, 1, "Timed out waiting for connection.");
41 }
42 
respond_connfailed(void)43 static int respond_connfailed(void)
44 {
45   return respond_syserr(425, "Connection failed");
46 }
47 
respond_connaborted(void)48 static int respond_connaborted(void)
49 {
50   return respond(425, 1, "Connection aborted by incoming command.");
51 }
52 
accept_connection(void)53 static int accept_connection(void)
54 {
55   int fd;
56   iopoll_fd pf[2];
57 
58   pf[0].fd = 0;
59   pf[0].events = IOPOLL_READ;
60   pf[0].revents = 0;
61   pf[1].fd = socket_fd;
62   pf[1].events = IOPOLL_READ;
63   switch (iopoll_restart(pf, 2, connect_timeout*1000)) {
64   case 2: break;
65   case 1: break;
66   case 0: respond_timedoutconn(); return -1;
67   default: respond_connfailed(); return -1;
68   }
69   if (pf[0].revents) {
70     respond_connaborted();
71     return -1;
72   }
73   if ((fd = socket_accept4(socket_fd, &remote_ip, &remote_port)) == -1) {
74     respond_connfailed();
75     return -1;
76   }
77   close(socket_fd);
78   socket_fd = -1;
79   connect_mode = NONE;
80   if (!nonblock_on(fd)) {
81     respond_syserr(425, "Could not set flags on socket");
82     close(fd);
83     return -1;
84   }
85   return fd;
86 }
87 
make_connect_socket(void)88 static int make_connect_socket(void)
89 {
90   int fd;
91   char buf;
92 
93   fd = -1;
94   if (bind_port_fd != -1) {
95     if (write(bind_port_fd, &buf, 1) == 1 &&
96 	read(bind_port_fd, &buf, 1) == 1 &&
97 	buf == 0)
98       fd = socket_recvfd(bind_port_fd);
99   }
100   if (fd == -1) {
101     if ((fd = socket_tcp()) == -1) {
102       respond_syserr(425, "Could not allocate a socket");
103       return -1;
104     }
105     if (!socket_reuse(fd) ||
106 	!socket_bind4(fd, &server_ip, 0)) {
107       respond_syserr(425, "Could not set flags on socket");
108       close(fd);
109       return -1;
110     }
111   }
112   return fd;
113 }
114 
start_connection(void)115 static int start_connection(void)
116 {
117   int fd;
118   iopoll_fd pf[2];
119 
120   if ((fd = make_connect_socket()) == -1) return -1;
121   if (!nonblock_on(fd)) {
122     respond_syserr(425, "Could not set flags on socket");
123     close(fd);
124     return -1;
125   }
126 
127   if (socket_connect4(fd, &remote_ip, remote_port)) return fd;
128 
129   if (errno != EINPROGRESS && errno != EWOULDBLOCK) {
130     respond_connfailed();
131     return -1;
132   }
133 
134   pf[0].fd = 0;
135   pf[0].events = IOPOLL_READ;
136   pf[0].revents = 0;
137   pf[1].fd = fd;
138   pf[1].events = IOPOLL_WRITE;
139   switch (iopoll_restart(pf, 2, connect_timeout*1000)) {
140   case 0:
141     respond_timedoutconn();
142     break;
143   case 2:
144   case 1:
145     if (pf[0].revents) {
146       respond_connaborted();
147       break;
148     }
149     if (socket_connected(fd)) return fd;
150     /* No break, fall through */
151   default:
152     respond_connfailed();
153   }
154   close(fd);
155   return -1;
156 }
157 
make_connection_fd(void)158 static int make_connection_fd(void)
159 {
160   int fd;
161 
162   if (connect_mode == NONE) {
163     fd = -1;
164     respond(425, 1, "No PORT or PASV commands have been issued.");
165   }
166   else {
167     fd = (connect_mode == PASV) ? accept_connection() : start_connection();
168     if (fd != -1) respond(150, 1, "Opened data connection.");
169   }
170   respond_start_xfer();
171   return fd;
172 }
173 
make_in_connection(void)174 int make_in_connection(void)
175 {
176   return make_connection_fd();
177 }
178 
make_out_connection(void)179 int make_out_connection(void)
180 {
181   int fd;
182   if ((fd = make_connection_fd()) != -1) {
183     socket_cork(fd);
184     if (!socket_linger(fd, 1, timeout)) {
185       respond_syserr(425, "Could not set flags on socket");
186       close(fd);
187       fd = -1;
188     }
189   }
190   return fd;
191 }
192 
close_out_connection(int out)193 int close_out_connection(int out)
194 {
195   socket_uncork(out);
196   return close(out) == 0;
197 }
198 
strtoc(const char * s,const char ** end)199 static unsigned char strtoc(const char* s, const char** end)
200 {
201   long tmp;
202   tmp = strtol(s, (char**)end, 10);
203   if (tmp < 0 || tmp > 0xff) *end = s;
204   return tmp;
205 }
206 
scan_ip(const char * s,char sep,ipv4addr * addr,const char ** endptr)207 static int scan_ip(const char* s, char sep,
208 		   ipv4addr* addr, const char** endptr)
209 {
210   const char* end;
211   addr->addr[0] = strtoc(s, &end); if (*end != sep) return 0;
212   addr->addr[1] = strtoc(end+1, &end); if (*end != sep) return 0;
213   addr->addr[2] = strtoc(end+1, &end); if (*end != sep) return 0;
214   addr->addr[3] = strtoc(end+1, &end);
215   *endptr = end;
216   return 1;
217 }
218 
parse_localip(const char * s)219 int parse_localip(const char* s)
220 {
221   const char* end;
222   return scan_ip(s, '.', &server_ip, &end) && *end == 0;
223 }
224 
parse_remoteip(const char * s)225 int parse_remoteip(const char* s)
226 {
227   const char* end;
228   return scan_ip(s, '.', &client_ip, &end) && *end == 0;
229 }
230 
parse_addr(const char * s)231 static int parse_addr(const char* s)
232 {
233   const char* end;
234   if (!scan_ip(s, ',', &remote_ip, &end) || *end != ',') return 0;
235   remote_port = strtoc(end+1, &end) << 8; if (*end != ',') return 0;
236   remote_port |= strtoc(end+1, &end); if (*end != 0) return 0;
237   connect_mode = PORT;
238   return 1;
239 }
240 
make_accept_socket(void)241 static int make_accept_socket(void)
242 {
243   if (socket_fd != -1) close(socket_fd);
244   if ((socket_fd = socket_tcp()) != -1) {
245     if (socket_bind4(socket_fd, &server_ip, 0) &&
246 	socket_listen(socket_fd, 1) &&
247 	socket_getaddr4(socket_fd, &socket_ip, &socket_port)) {
248       connect_mode = PASV;
249       return 1;
250     }
251     else {
252       close(socket_fd);
253       socket_fd = -1;
254     }
255   }
256   connect_mode = NONE;
257   return 0;
258 }
259 
handle_pasv(void)260 int handle_pasv(void)
261 {
262   char buffer[6*4+25];
263   if (!make_accept_socket())
264     return respond_syserr(425, "Could not create socket");
265   buffer[fmt_multi(buffer, "{Entering Passive Mode (}u{,}u{,}u{,}u{,}u{,}u{).",
266 		   socket_ip.addr[0], socket_ip.addr[1],
267 		   socket_ip.addr[2], socket_ip.addr[3],
268 		   (socket_port>>8)&0xff, socket_port&0xff)] = 0;
269   return respond(227, 1, buffer);
270 }
271 
handle_port(void)272 int handle_port(void)
273 {
274   if (!parse_addr(req_param))
275     return respond(501, 1, "Can't parse your PORT address.");
276   if (memcmp(&remote_ip, &client_ip, sizeof client_ip))
277     return respond(501, 1, "PORT IP does not match client address.");
278   return respond_ok();
279 }
280