1 /* list.c - twoftpd routines to handle list/nlst commands
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 <stdlib.h>
20 #include <string.h>
21 #include <sys/types.h>
22 #include <unistd.h>
23 #include <bglibs/systime.h>
24 #include <bglibs/sysdeps.h>
25 #include "twoftpd.h"
26 #include "backend.h"
27 #include <bglibs/socket.h>
28 #include <bglibs/path.h>
29 #include <bglibs/str.h>
30
31 int list_options;
32
33 static int list_long;
34 static int list_flags;
35 static int mode_nlst;
36
mode2str(int mode)37 static const char* mode2str(int mode)
38 {
39 static char buf[11];
40 buf[0] = S_ISREG(mode) ? '-' :
41 S_ISDIR(mode) ? 'd' :
42 S_ISCHR(mode) ? 'c' :
43 S_ISBLK(mode) ? 'b' :
44 S_ISFIFO(mode) ? 'p' :
45 S_ISSOCK(mode) ? 's' :
46 '?';
47 buf[1] = (mode & 0400) ? 'r' : '-';
48 buf[2] = (mode & 0200) ? 'w' : '-';
49 buf[3] = (mode & 04000) ?
50 (mode & 0100) ? 's' : 'S' :
51 (mode & 0100) ? 'x' : '-';
52 buf[4] = (mode & 0040) ? 'r' : '-';
53 buf[5] = (mode & 0020) ? 'w' : '-';
54 buf[6] = (mode & 02000) ?
55 (mode & 0010) ? 's' : 'S' :
56 (mode & 0010) ? 'x' : '-';
57 buf[7] = (mode & 0004) ? 'r' : '-';
58 buf[8] = (mode & 0002) ? 'w' : '-';
59 buf[9] = (mode & 01000) ?
60 (mode & 0001) ? 't' : 'T' :
61 (mode & 0001) ? 'x' : '-';
62 buf[10] = 0;
63 return buf;
64 }
65
str_catmode(str * s,int mode)66 static int str_catmode(str* s, int mode)
67 {
68 return str_catb(s, mode2str(mode), 10);
69 }
70
str_catowner(str * s,uid_t owner)71 static int str_catowner(str* s, uid_t owner)
72 {
73 const char* name;
74 unsigned len;
75 if (owner == uid)
76 name = user, len = user_len;
77 else
78 name = "somebody", len = 8;
79 if (!str_catb(s, name, len)) return 0;
80 while (len++ < MAX_NAME_LEN)
81 if (!str_catc(s, SPACE)) return 0;
82 return 1;
83 }
84
str_catgroup(str * s,gid_t g)85 static int str_catgroup(str* s, gid_t g)
86 {
87 const char* name;
88 unsigned len;
89 if (g == gid)
90 name = group, len = group_len;
91 else
92 name = "somegrp", len = 7;
93 if (!str_catb(s, name, len)) return 0;
94 while (len++ < MAX_NAME_LEN)
95 if (!str_catc(s, SPACE)) return 0;
96 return 1;
97 }
98
str_cattime(str * s,time_t then)99 static int str_cattime(str* s, time_t then)
100 {
101 struct tm* tm;
102 time_t year;
103 char buf[16];
104 unsigned len;
105
106 year = now - 365/2*24*60*60;
107
108 tm = localtime(&then);
109 if (then > year)
110 len = strftime(buf, sizeof buf - 1, "%b %e %H:%M", tm);
111 else
112 len = strftime(buf, sizeof buf - 1, "%b %e %Y", tm);
113 return str_catb(s, buf, len);
114 }
115
str_catstat(str * s,const struct stat * st)116 static int str_catstat(str* s, const struct stat* st)
117 {
118 if (st)
119 return str_catmode(s, st->st_mode) &&
120 str_catb(s, " 1 ", 6) &&
121 str_catowner(s, st->st_uid) &&
122 str_catc(s, SPACE) &&
123 str_catgroup(s, st->st_gid) &&
124 str_catc(s, SPACE) &&
125 str_catuwll(s, st->st_size, 8, ' ') &&
126 str_catc(s, SPACE) &&
127 str_cattime(s, st->st_mtime) &&
128 str_catc(s, SPACE);
129 else
130 return str_cats(s,
131 "??????????"
132 " ? "
133 "???????? "
134 "???????? "
135 " ? "
136 "??? ?? ????? ");
137 }
138
str_catflags(str * s,const struct stat * st)139 static int str_catflags(str* s, const struct stat* st)
140 {
141 if (st) {
142 int ch;
143 int mode = st->st_mode;
144 ch = 0;
145 if (S_ISDIR(mode)) ch = '/';
146 else if (S_ISFIFO(mode)) ch = '|';
147 else if (S_ISSOCK(mode)) ch = '=';
148 else if (list_flags == 'F' && mode & 0111) ch = '*';
149 if (ch)
150 return str_catc(s, ch);
151 }
152 return 1;
153 }
154
str_catfn(str * s,const char * fn,int striplen)155 static int str_catfn(str* s, const char* fn, int striplen)
156 {
157 if (striplen < 0) {
158 if (!str_catc(s, '/')) return 0;
159 striplen = 0;
160 }
161 return str_cats(s, fn + striplen);
162 }
163
164 static char send_buf[8192];
165 static unsigned long send_used;
166
send_line(int out,const str * s)167 static int send_line(int out, const str* s)
168 {
169 int result;
170
171 if (send_used + s->len > sizeof send_buf) {
172 if ((result = netwrite(out, send_buf, send_used, timeout * 1000)) != 0)
173 return result;
174 send_used = 0;
175 }
176 if (s->len > sizeof send_buf)
177 return netwrite(out, s->s, s->len, timeout * 1000);
178 memcpy(send_buf + send_used, s->s, s->len);
179 send_used += s->len;
180 return 0;
181 }
182
183 static str entries;
184
list_entries(long count,int striplen)185 static int list_entries(long count, int striplen)
186 {
187 static str line;
188 int out;
189 int result;
190 unsigned long bytes_out;
191 struct stat statbuf;
192 struct stat* statptr;
193 const char* filename = entries.s;
194 int need_stat = list_long || list_flags;
195
196 if (mode_nlst && count < 0)
197 return respond(550, 1, "No such file or directory");
198
199 if ((out = make_out_connection()) == -1)
200 return 1;
201
202 send_used = 0;
203 for (bytes_out = 0; count > 0; --count, filename += strlen(filename)+1) {
204 statptr = 0;
205 if (need_stat) {
206 if (stat(filename, &statbuf) == -1) {
207 if (errno == ENOENT) continue;
208 }
209 else
210 statptr = &statbuf;
211 }
212 line.len = 0;
213 if ((list_long && !str_catstat(&line, statptr))
214 || !str_catfn(&line, filename, striplen)
215 || (list_flags && !str_catflags(&line, statptr))
216 || !str_cats(&line, CRLF)) {
217 close(out);
218 return respond_xferresult(-1, bytes_out, 1);
219 }
220 if ((result = send_line(out, &line)) != 0)
221 return respond_xferresult(result, bytes_out, 1);
222 bytes_out += line.len;
223 }
224 if (send_used > 0) {
225 if ((result = netwrite(out, send_buf, send_used, timeout * 1000)) != 0) {
226 socket_uncork(out);
227 close(out);
228 return respond_xferresult(result, bytes_out, 1);
229 }
230 }
231 return respond_xferresult(close(out), bytes_out, 1);
232 }
233
list_dir()234 static int list_dir()
235 {
236 long count;
237 DIR* dir;
238 direntry* entry;
239
240 /* Turning "/" into "/." simplifies several other steps below. */
241 if (fullpath.len == 1)
242 if (!str_copys(&fullpath, "/."))
243 return respond_internal_error();
244
245 if ((dir = opendir(fullpath.s+1)) == 0)
246 return respond_syserr(550, "Could not open directory");
247 entries.len = 0;
248 count = 0;
249 while ((entry = readdir(dir)) != 0) {
250 /* Always skip the "." and ".." entries. Skip all other ".*"
251 * entries if nodotfiles was set. */
252 if (entry->d_name[0] != '.'
253 || (!(entry->d_name[1] == 0
254 || (entry->d_name[1] == '.' && entry->d_name[2] == 0))
255 && (list_options & PATH_MATCH_DOTFILES) != 0)) {
256 /* Add the entry to the list, skipping the "/" separator if none
257 * is needed */
258 if (!str_catb(&entries, fullpath.s+1, fullpath.len-1)
259 || !str_catc(&entries, '/')
260 || !str_catb(&entries, entry->d_name, strlen(entry->d_name)+1)) {
261 closedir(dir);
262 return respond_internal_error();
263 }
264 ++count;
265 }
266 }
267 closedir(dir);
268 if (!str_sort(&entries, 0, count, 0))
269 return respond_internal_error();
270 return list_entries(count, fullpath.len);
271 }
272
handle_listing(int longfmt)273 static int handle_listing(int longfmt)
274 {
275 struct stat statbuf;
276 long count;
277 int striplen;
278
279 mode_nlst = !longfmt;
280 list_long = longfmt;
281 list_flags = 0;
282
283 if (req_param) {
284 while (*req_param == '-') {
285 ++req_param;
286 if (*req_param == SPACE) break;
287 while (*req_param && *req_param != SPACE) {
288 char tmp[2];
289 switch (*req_param) {
290 case 'a': break; /* Listing all files is */
291 case 'A': break; /* not controlled by client */
292 case 'L': break; /* We already dereference symlinks */
293 case '1': break; /* We already list in a single column */
294 case 'F':
295 case 'p':
296 list_flags = *req_param; break;
297 case 'l': list_long = 1; break;
298 default:
299 tmp[0] = *req_param;
300 tmp[1] = 0;
301 return respond_start(550, 1) &&
302 respond_str("Unknown list option: ") &&
303 respond_str(tmp) && respond_end();
304 }
305 ++req_param;
306 }
307 if (*req_param == SPACE) ++req_param;
308 }
309 }
310
311 if (!req_param || !*req_param)
312 req_param = ".";
313
314 if (path_contains(req_param, ".."))
315 return respond(553, 1, "Paths containing '..' not allowed.");
316
317 /* Prefix the requested path with CWD, and strip it after */
318 if (!qualify_validate(req_param)) return 1;
319 if (fullpath.len == 1) return list_dir();
320 if ((count = path_match(fullpath.s+1, &entries, list_options)) == -1)
321 return respond_internal_error();
322 striplen = (req_param[0] == '/') ? -1 : (cwd.len == 1) ? 0 : cwd.len;
323
324 if (count <= 1) {
325 /* If no entries were matched, try the literal path. */
326 if (count == 0)
327 if (!str_copyb(&entries, fullpath.s+1, fullpath.len))
328 return respond_internal_error();
329
330 if (stat(entries.s, &statbuf) == -1) {
331 if (errno == ENOENT)
332 return respond(550, 1, "File or directory does not exist.");
333 else
334 return respond(550, 1, "Could not access file.");
335 }
336 else if (S_ISDIR(statbuf.st_mode)) {
337 if (!str_copys(&fullpath, "/") ||
338 !str_catb(&fullpath, entries.s, entries.len-1))
339 return respond_internal_error();
340 return list_dir();
341 }
342 }
343 return list_entries(count, striplen);
344 }
345
handle_list(void)346 int handle_list(void)
347 {
348 return handle_listing(1);
349 }
350
handle_nlst(void)351 int handle_nlst(void)
352 {
353 return handle_listing(0);
354 }
355