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