1 /*
2  * Part of Very Secure FTPd
3  * Licence: GPL v2
4  * Author: Dmitriy Balashov
5  * http.c
6  *
7  * Routines to handle HTTP.
8  */
9 
10 #include "session.h"
11 #include "access.h"
12 #include "tunables.h"
13 #include "charconv.h"
14 #include "filestr.h"
15 #include "str.h"
16 #include "strlist.h"
17 #include "ftpcmdio.h"
18 #include "ls.h"
19 #include "sysutil.h"
20 #include "sysstr.h"
21 #include "logging.h"
22 #include "defs.h"
23 #include "utility.h"
24 #include "postlogin.h"
25 #include "http_msg.h"
26 #include "http_str.h"
27 
28 enum EResourceType
29 {
30   ResTypeNotFound = -1,
31   ResTypeFile,
32   ResType_Dir,
33   ResTypeDir
34 };
35 
36 static void http_error(struct vsf_session* p_sess, const enum EVSFHttpMsgType p_type);
37 static void http_html_content(struct vsf_session* p_sess, const char* response, const struct mystr* body);
38 static void http_header_print(struct vsf_session* p_sess, const char* response);
39 static enum EResourceType http_check_resource(struct vsf_session* p_sess, filesize_t* p_size);
40 static enum EResourceType http_request_resource_type(struct vsf_session* p_sess, filesize_t* p_size);
41 static void http_handle_send(struct vsf_session* p_sess, filesize_t p_size);
42 static void http_handle_browse(struct vsf_session* p_sess);
43 
44 static char* err_403_forbidden = "403 Forbidden";
45 static char* err_404_not_found = "404 Not Found";
46 static char* err_200_ok        = "200 OK";
47 
48 void
handle_http(struct vsf_session * p_sess)49 handle_http(struct vsf_session* p_sess)
50 {
51   /* Warning: Doesn't respect cmds_allowed etc. because there is currently only
52    * one command (GET)!
53    * HTTP likely doesn't respect other important FTP options. I don't think
54    * logging works.
55    */
56   if (!tunable_download_enable)
57   {
58     bug("HTTP needs download - fix your config");
59   }
60   /* Eat the HTTP headers, which we don't care about. */
61 
62   do
63   {
64     vsf_cmdio_get_cmd_and_arg(p_sess, &p_sess->ftp_cmd_str,
65                               &p_sess->ftp_arg_str, 1, 1);
66   }
67   while (!str_isempty(&p_sess->ftp_cmd_str) ||
68          !str_isempty(&p_sess->ftp_arg_str));
69 
70   if (!p_sess->is_anonymous)
71   {
72     http_error(p_sess, kVSFHttpMsgType403Serv);
73   }
74 
75   /* Split the path from the HTTP/1.x */
76   str_split_char(&p_sess->http_get_arg, &p_sess->ftp_arg_str, ' ');
77   str_unescape(&p_sess->http_get_arg);
78 
79   static filesize_t p_size;
80   enum EResourceType p_res_type = http_check_resource(p_sess, &p_size);
81 
82   switch (p_res_type)
83   {
84     case ResTypeDir:
85       if (!tunable_http_browse || str_chdir(&p_sess->http_get_arg) != 0)
86       {
87         http_error(p_sess, kVSFHttpMsgType403);
88       }
89       break;
90     case ResTypeNotFound:
91       http_error(p_sess, kVSFHttpMsgType404);
92       break;
93     default:
94       break;
95   }
96 
97   if (p_res_type == ResTypeDir)
98   {
99     http_handle_browse(p_sess);
100   }
101   else
102   {
103     http_handle_send(p_sess, p_size);
104   }
105 
106   if (vsf_log_entry_pending(p_sess))
107   {
108     vsf_log_do_log(p_sess, 0);
109   }
110   vsf_sysutil_exit(0);
111 }
112 
113 static void
http_error(struct vsf_session * p_sess,const enum EVSFHttpMsgType p_type)114 http_error(struct vsf_session* p_sess, const enum EVSFHttpMsgType p_type)
115 {
116   static char* response;
117   static struct mystr p_tpl = INIT_MYSTR;
118   static struct mystr p_res = INIT_MYSTR;
119 
120   switch (p_type)
121   {
122     case kVSFHttpMsgType403Serv:
123       response = err_403_forbidden;
124       break;
125     case kVSFHttpMsgType403:
126       response = err_403_forbidden;
127       break;
128     case kVSFHttpMsgType404:
129       response = err_404_not_found;
130       break;
131     default:
132       bug("Bad HTTP error code");
133       break;
134   }
135 
136   str_alloc_text(&p_tpl, vsf_http_messages_get(p_type));
137   str_process_template(p_sess, &p_res, &p_tpl, 0, 0, vsf_sysutil_get_time_sec(), 0);
138   http_html_content(p_sess, response, &p_res);
139 }
140 
141 static void
http_html_content(struct vsf_session * p_sess,const char * response,const struct mystr * body)142 http_html_content(struct vsf_session* p_sess,
143                   const char* response,
144                   const struct mystr* body)
145 {
146   http_header_print(p_sess, response);
147   vsf_cmdio_write_raw_quiet(p_sess, "Content-Type: text/html\r\n");
148 
149   vsf_cmdio_write_raw_quiet(p_sess, "Content-Length: ");
150   vsf_cmdio_write_raw_quiet(p_sess, vsf_sysutil_filesize_t_to_str(str_getlen(body)));
151   vsf_cmdio_write_raw_quiet(p_sess, "\r\n\r\n");
152   vsf_cmdio_write_raw_quiet(p_sess, str_getbuf(body));
153 
154   vsf_sysutil_exit(0);
155 }
156 
157 
158 static void
http_header_print(struct vsf_session * p_sess,const char * response)159 http_header_print(struct vsf_session* p_sess, const char* response)
160 {
161   static const char* hdr = "\r\n"
162                            "Server: vsftpd\r\n"
163                            "Connection: close\r\n"
164                            "X-Frame-Options: SAMEORIGIN\r\n"
165                            "X-Content-Type-Options: nosniff\r\n";
166 
167   vsf_cmdio_write_raw_quiet(p_sess, "HTTP/1.0 ");
168   vsf_cmdio_write_raw_quiet(p_sess, response);
169   vsf_cmdio_write_raw_quiet(p_sess, hdr);
170 }
171 
172 static enum EResourceType
http_check_resource(struct vsf_session * p_sess,filesize_t * p_size)173 http_check_resource(struct vsf_session* p_sess, filesize_t* p_size)
174 {
175   enum EResourceType ret = http_request_resource_type(p_sess, p_size);
176 
177   if (ret == ResType_Dir)
178   {
179     str_chdir(&p_sess->http_get_arg);
180     str_getcwd(&p_sess->http_get_arg);
181     http_header_print(p_sess, "302 Found");
182     vsf_cmdio_write_raw_quiet(p_sess, "Location: ");
183     vsf_cmdio_write_raw_quiet(p_sess, str_getbuf(&p_sess->http_get_arg));
184     vsf_cmdio_write_raw_quiet(p_sess, "/\r\n\r\n");
185 
186     vsf_sysutil_exit(0);
187   }
188 
189   return ret;
190 }
191 
192 static enum EResourceType
http_request_resource_type(struct vsf_session * p_sess,filesize_t * p_size)193 http_request_resource_type(struct vsf_session* p_sess, filesize_t* p_size)
194 {
195   static struct vsf_sysutil_statbuf* p_statbuf;
196   static struct mystr path = INIT_MYSTR;
197   int retval = 0;
198   enum EResourceType ret = ResTypeFile;
199 
200   if (str_equal_text(&p_sess->http_get_arg, "/"))
201   {
202     ret = ResTypeDir;
203   }
204   else
205   {
206     if (str_get_char_at(&p_sess->http_get_arg, str_getlen(&p_sess->http_get_arg) - 1) == '/')
207     {
208       ret++; // _Dir
209       str_left(&p_sess->http_get_arg, &path, str_getlen(&p_sess->http_get_arg) - 1);
210     }
211     else
212     {
213       str_copy(&path, &p_sess->http_get_arg);
214     }
215 
216     retval = str_stat(&path, &p_statbuf);
217     if (retval == 0)
218     {
219       if (vsf_sysutil_statbuf_is_dir(p_statbuf))
220       {
221         ret++;
222         struct str_locate_result locate = str_locate_text(&path, "..");
223         if (locate.found && ret == ResTypeDir)
224         {
225           ret--;
226         }
227       }
228       else
229       {
230         if (ret != ResTypeFile)
231         {
232           ret = ResTypeNotFound;
233         }
234         else
235         {
236           *p_size = vsf_sysutil_statbuf_get_size(p_statbuf);
237         }
238       }
239     }
240     else
241     {
242       ret = ResTypeNotFound;
243     }
244   }
245 
246   if ((ret == ResTypeDir || ret == ResTypeFile) &&
247       !vsf_access_check_file(&p_sess->http_get_arg))
248   {
249     http_error(p_sess, kVSFHttpMsgType403);
250   }
251   if (ret == ResTypeDir && tunable_http_default_index)
252   {
253     struct mystr old = INIT_MYSTR;
254     str_alloc_text(&old, str_getbuf(&p_sess->http_get_arg));
255     str_append_text(&p_sess->http_get_arg, tunable_http_default_index);
256     ret = http_request_resource_type(p_sess, p_size);
257     if (ret != ResTypeFile)
258     {
259       ret = ResTypeDir;
260       str_alloc_text(&p_sess->http_get_arg, str_getbuf(&old));
261     }
262     str_empty(&old);
263   }
264   if (ret == ResTypeFile)
265   {
266     int opened_file;
267     opened_file = str_open(&p_sess->http_get_arg, kVSFSysStrOpenReadOnly);
268     if (vsf_sysutil_retval_is_error(opened_file))
269     {
270       http_error(p_sess, kVSFHttpMsgType403);
271     }
272     vsf_sysutil_close(opened_file);
273   }
274 
275   str_empty(&path);
276 
277   return ret;
278 }
279 
280 static void
http_handle_send(struct vsf_session * p_sess,filesize_t p_size)281 http_handle_send(struct vsf_session* p_sess, filesize_t p_size)
282 {
283   http_header_print(p_sess, err_200_ok);
284   str_copy(&p_sess->ftp_arg_str, &p_sess->http_get_arg);
285 
286   {
287     struct str_locate_result locate;
288     locate = str_locate_text_reverse(&p_sess->ftp_arg_str, ".");
289     if (locate.found == 1)
290     {
291       str_right(&p_sess->ftp_arg_str, &p_sess->ftp_cmd_str, str_getlen(&p_sess->ftp_arg_str) - locate.index - 1);
292     }
293     else
294     {
295       str_empty(&p_sess->ftp_cmd_str);
296     }
297   }
298 
299   str_upper(&p_sess->ftp_cmd_str);
300   if (str_equal_text(&p_sess->ftp_cmd_str, "HTML") ||
301       str_equal_text(&p_sess->ftp_cmd_str, "HTM"))
302   {
303     vsf_cmdio_write_raw_quiet(p_sess, "Content-Type: text/html\r\n");
304   }
305   else
306   {
307     vsf_cmdio_write_raw_quiet(p_sess, "Content-Type: dunno\r\n");
308   }
309   vsf_cmdio_write_raw_quiet(p_sess, "Content-Length: ");
310   vsf_cmdio_write_raw_quiet(p_sess, vsf_sysutil_filesize_t_to_str(p_size));
311   vsf_cmdio_write_raw_quiet(p_sess, "\r\n\r\n");
312   p_sess->is_ascii = 0;
313   p_sess->restart_pos = 0;
314   process_http_retr(p_sess);
315 }
316 
http_handle_browse(struct vsf_session * p_sess)317 static void http_handle_browse(struct vsf_session* p_sess)
318 {
319   struct vsf_sysutil_dir* p_dir = 0;
320   static struct vsf_sysutil_statbuf* s_p_dirstat;
321   int dir_allow_read = 1;
322   struct mystr_list dir_list = INIT_STRLIST;
323 
324   p_dir = str_opendir(&p_sess->http_get_arg);
325   if (p_dir && tunable_anon_world_readable_only)
326   {
327     vsf_sysutil_dir_stat(p_dir, &s_p_dirstat);
328     if (!vsf_sysutil_statbuf_is_readable_other(s_p_dirstat))
329     {
330       dir_allow_read = 0;
331     }
332   }
333   if (p_dir != 0 && dir_allow_read)
334   {
335     static struct mystr s_option_str = INIT_MYSTR;
336     static struct mystr s_filter_str = INIT_MYSTR;
337 
338     vsf_ls_populate_dir_list(p_sess, &dir_list, 0, p_dir, &p_sess->http_get_arg,
339                              &s_option_str, &s_filter_str, 1, 1);
340   }
341   if (p_dir)
342   {
343     vsf_sysutil_closedir(p_dir);
344   }
345   if (!dir_allow_read)
346   {
347     http_error(p_sess, kVSFHttpMsgType403);
348   }
349   else
350   {
351     static struct mystr p_tpl = INIT_MYSTR;
352     static struct mystr p_res = INIT_MYSTR;
353 
354     str_alloc_text(&p_tpl, vsf_http_messages_get(kVSFHttpMsgType200Browse));
355     str_process_template(p_sess, &p_res, &p_tpl, 0, 0, vsf_sysutil_get_time_sec(), &dir_list);
356     str_list_free(&dir_list);
357     http_html_content(p_sess, err_200_ok, &p_res);
358   }
359 }
360