1 /*
2  * Part of Very Secure FTPd
3  * Licence: GPL v2
4  * Author: Chris Evans
5  * ftpcmdio.c
6  *
7  * Routines applicable to reading and writing the FTP command stream.
8  */
9 
10 #include "ftpcmdio.h"
11 #include "ftpcodes.h"
12 #include "str.h"
13 #include "netstr.h"
14 #include "sysutil.h"
15 #include "tunables.h"
16 #include "defs.h"
17 #include "secbuf.h"
18 #include "utility.h"
19 #include "logging.h"
20 #include "session.h"
21 #include "readwrite.h"
22 #include "charconv.h"
23 
24 /* Internal functions */
25 static int control_getline(struct mystr* p_str, struct vsf_session* p_sess);
26 static void ftp_write_text_common(struct vsf_session* p_sess, int status,
27                                   const char* p_text, char sep);
28 static void ftp_write_str_common(struct vsf_session* p_sess, int status,
29                                  char sep, const struct mystr* p_str);
30 static void handle_alarm_timeout(void* p_private);
31 
32 void
vsf_cmdio_sock_setup(void)33 vsf_cmdio_sock_setup(void)
34 {
35   vsf_sysutil_activate_keepalive(VSFTP_COMMAND_FD);
36   vsf_sysutil_set_nodelay(VSFTP_COMMAND_FD);
37   vsf_sysutil_activate_oobinline(VSFTP_COMMAND_FD);
38 }
39 
40 static void
handle_alarm_timeout(void * p_private)41 handle_alarm_timeout(void* p_private)
42 {
43   struct vsf_session* p_sess = (struct vsf_session*) p_private;
44   p_sess->idle_timeout = 1;
45   vsf_sysutil_activate_noblock(VSFTP_COMMAND_FD);
46   vsf_sysutil_shutdown_read_failok(VSFTP_COMMAND_FD);
47 }
48 
49 void
vsf_cmdio_write(struct vsf_session * p_sess,int status,const char * p_text)50 vsf_cmdio_write(struct vsf_session* p_sess, int status, const char* p_text)
51 {
52   ftp_write_text_common(p_sess, status, p_text, ' ');
53 }
54 
55 void
vsf_cmdio_write_hyphen(struct vsf_session * p_sess,int status,const char * p_text)56 vsf_cmdio_write_hyphen(struct vsf_session* p_sess, int status,
57                        const char* p_text)
58 {
59   ftp_write_text_common(p_sess, status, p_text, '-');
60 }
61 
62 void
vsf_cmdio_write_raw(struct vsf_session * p_sess,const char * p_text)63 vsf_cmdio_write_raw(struct vsf_session* p_sess, const char* p_text)
64 {
65   static struct mystr s_the_str;
66   int retval;
67   str_alloc_text(&s_the_str, p_text);
68   if (tunable_log_ftp_protocol)
69   {
70     vsf_log_line(p_sess, kVSFLogEntryFTPOutput, &s_the_str);
71   }
72   retval = ftp_write_str(p_sess, &s_the_str, kVSFRWControl);
73   if (retval != 0)
74   {
75     die("ftp_write_str");
76   }
77 }
78 
79 void
vsf_cmdio_write_raw_quiet(struct vsf_session * p_sess,const char * p_text)80 vsf_cmdio_write_raw_quiet(struct vsf_session* p_sess, const char* p_text)
81 {
82   static struct mystr s_the_str;
83   int retval;
84   str_alloc_text(&s_the_str, p_text);
85   retval = ftp_write_str(p_sess, &s_the_str, kVSFRWControl);
86   if (retval != 0)
87   {
88     die("ftp_write_str");
89   }
90 }
91 
92 void
vsf_cmdio_write_exit(struct vsf_session * p_sess,int status,const char * p_text,int exit_val)93 vsf_cmdio_write_exit(struct vsf_session* p_sess, int status, const char* p_text,
94                      int exit_val)
95 {
96   /* Unblock any readers on the dying control channel. This is needed for SSL
97    * connections, where the SSL control channel slave is in a separate
98    * process.
99    */
100   vsf_sysutil_activate_noblock(VSFTP_COMMAND_FD);
101   vsf_sysutil_shutdown_read_failok(VSFTP_COMMAND_FD);
102   vsf_cmdio_write(p_sess, status, p_text);
103   vsf_sysutil_shutdown_failok(VSFTP_COMMAND_FD);
104   vsf_sysutil_exit(exit_val);
105 }
106 
107 static void
ftp_write_text_common(struct vsf_session * p_sess,int status,const char * p_text,char sep)108 ftp_write_text_common(struct vsf_session* p_sess, int status,
109                       const char* p_text, char sep)
110 {
111   /* XXX - could optimize */
112   static struct mystr s_the_str;
113   str_alloc_text(&s_the_str, p_text);
114   ftp_write_str_common(p_sess, status, sep, &s_the_str);
115 }
116 
117 void
vsf_cmdio_write_str_hyphen(struct vsf_session * p_sess,int status,const struct mystr * p_str)118 vsf_cmdio_write_str_hyphen(struct vsf_session* p_sess, int status,
119                            const struct mystr* p_str)
120 {
121   ftp_write_str_common(p_sess, status, '-', p_str);
122 }
123 
124 void
vsf_cmdio_write_str(struct vsf_session * p_sess,int status,const struct mystr * p_str)125 vsf_cmdio_write_str(struct vsf_session* p_sess, int status,
126                     const struct mystr* p_str)
127 {
128   ftp_write_str_common(p_sess, status, ' ', p_str);
129 }
130 
131 static void
ftp_write_str_common(struct vsf_session * p_sess,int status,char sep,const struct mystr * p_str)132 ftp_write_str_common(struct vsf_session* p_sess, int status, char sep,
133                      const struct mystr* p_str)
134 {
135   static struct mystr s_write_buf_str;
136   static struct mystr s_text_mangle_str;
137   int retval;
138   if (tunable_log_ftp_protocol)
139   {
140     str_alloc_ulong(&s_write_buf_str, (unsigned long) status);
141     str_append_char(&s_write_buf_str, sep);
142     str_append_str(&s_write_buf_str, p_str);
143     vsf_log_line(p_sess, kVSFLogEntryFTPOutput, &s_write_buf_str);
144   }
145   str_copy(&s_text_mangle_str, p_str);
146   vsf_charconv_convert(p_sess, &s_text_mangle_str, VSFTP_CONVDIRECT_FORWARD);
147   /* Process the output response according to the specifications.. */
148   /* Escape telnet characters properly */
149   if (tunable_double_377)
150   {
151     str_replace_text(&s_text_mangle_str, "\377", "\377\377");
152   }
153   /* Change \n for \0 in response */
154   str_replace_char(&s_text_mangle_str, '\n', '\0');
155   /* Build string to squirt down network */
156   str_alloc_ulong(&s_write_buf_str, (unsigned long) status);
157   str_append_char(&s_write_buf_str, sep);
158   str_append_str(&s_write_buf_str, &s_text_mangle_str);
159   str_append_text(&s_write_buf_str, "\r\n");
160   retval = ftp_write_str(p_sess, &s_write_buf_str, kVSFRWControl);
161   if (retval != 0)
162   {
163     die("ftp_write");
164   }
165 }
166 
167 void
vsf_cmdio_set_alarm(struct vsf_session * p_sess)168 vsf_cmdio_set_alarm(struct vsf_session* p_sess)
169 {
170   if (tunable_idle_session_timeout > 0)
171   {
172     vsf_sysutil_install_sighandler(kVSFSysUtilSigALRM,
173                                    handle_alarm_timeout,
174                                    p_sess,
175                                    1);
176     vsf_sysutil_set_alarm(tunable_idle_session_timeout);
177   }
178 }
179 
180 void
vsf_cmdio_get_cmd_and_arg(struct vsf_session * p_sess,struct mystr * p_cmd_str,struct mystr * p_arg_str,int set_alarm,int is_quiet)181 vsf_cmdio_get_cmd_and_arg(struct vsf_session* p_sess, struct mystr* p_cmd_str,
182                           struct mystr* p_arg_str, int set_alarm, int is_quiet)
183 {
184   int ret;
185   /* Prepare an alarm to timeout the session.. */
186   if (set_alarm)
187   {
188     vsf_cmdio_set_alarm(p_sess);
189   }
190   /* Blocks */
191   ret = control_getline(p_cmd_str, p_sess);
192   if (p_sess->idle_timeout)
193   {
194     vsf_cmdio_write_exit(p_sess, FTP_IDLE_TIMEOUT, "Timeout.", 1);
195   }
196   if (ret == 0)
197   {
198     /* Remote end hung up without a polite QUIT. The shutdown is to make
199      * sure buggy clients don't ever see an OOPS message.
200      */
201     vsf_sysutil_shutdown_failok(VSFTP_COMMAND_FD);
202     vsf_sysutil_exit(1);
203   }
204   /* View a single space as a command of " ", which although a useless command,
205    * permits the caller to distinguish input of "" from " ".
206    */
207   if (str_getlen(p_cmd_str) == 1 && str_get_char_at(p_cmd_str, 0) == ' ')
208   {
209     str_empty(p_arg_str);
210   }
211   else
212   {
213     str_split_char(p_cmd_str, p_arg_str, ' ');
214   }
215   str_upper(p_cmd_str);
216   if (tunable_log_ftp_protocol && !is_quiet)
217   {
218     static struct mystr s_log_str;
219     if (str_equal_text(p_cmd_str, "PASS"))
220     {
221       str_alloc_text(&s_log_str, "PASS <password>");
222     }
223     else
224     {
225       str_copy(&s_log_str, p_cmd_str);
226       if (!str_isempty(p_arg_str))
227       {
228         str_append_char(&s_log_str, ' ');
229         str_append_str(&s_log_str, p_arg_str);
230       }
231     }
232     vsf_log_line(p_sess, kVSFLogEntryFTPInput, &s_log_str);
233   }
234 }
235 
236 static int
control_getline(struct mystr * p_str,struct vsf_session * p_sess)237 control_getline(struct mystr* p_str, struct vsf_session* p_sess)
238 {
239   int ret;
240   if (p_sess->p_control_line_buf == 0)
241   {
242     vsf_secbuf_alloc(&p_sess->p_control_line_buf, VSFTP_MAX_COMMAND_LINE);
243   }
244   ret = ftp_getline(p_sess, p_str, p_sess->p_control_line_buf);
245   if (ret == 0)
246   {
247     return ret;
248   }
249   else if (ret < 0)
250   {
251     vsf_cmdio_write_exit(p_sess, FTP_BADCMD, "Input line too long.", 1);
252   }
253   /* As mandated by the FTP specifications.. */
254   str_replace_char(p_str, '\0', '\n');
255   /* If the last character is a \r, strip it */
256   {
257     unsigned int len = str_getlen(p_str);
258     while (len > 0 && str_get_char_at(p_str, len - 1) == '\r')
259     {
260       str_trunc(p_str, len - 1);
261       --len;
262     }
263   }
264   vsf_charconv_convert(p_sess, p_str, VSFTP_CONVDIRECT_BACKWARD);
265   return 1;
266 }
267 
268