1 /*
2 * part of owftpd By Paul H Alfille
3 * The whole is GPLv2 licenced though the ftp code was more liberally licenced when first used.
4 */
5
6 #include "owftpd.h"
7 #include <limits.h>
8 #include <stdarg.h>
9 #include <fnmatch.h>
10
11 /* AIX requires this to be the first thing in the file. */
12 #ifndef __GNUC__
13 # if HAVE_ALLOCA_H
14 # include <alloca.h>
15 # else
16 # ifdef _AIX
17 #pragma alloca
18 # else
19 # ifndef alloca /* predefined by HP cc +Olibcalls */
20 char *alloca();
21 # endif
22 # endif
23 # endif
24 #endif
25
26 static void fdprintf(FILE_DESCRIPTOR_OR_ERROR file_descriptor, const char *fmt, ...);
27 static void List_show(struct file_parse_s *fps, const struct parsedname *pn);
28 static void WildLexParse(struct file_parse_s *fps, ASCII * match);
29 static char *skip_ls_options(char *filespec);
30
31 /* if no localtime_r() is available, provide one */
32 #ifndef HAVE_LOCALTIME_R
33 #include <pthread.h>
34
localtime_r(const time_t * timep,struct tm * timeptr)35 struct tm *localtime_r(const time_t * timep, struct tm *timeptr)
36 {
37 static pthread_mutex_t time_lock = PTHREAD_MUTEX_INITIALIZER;
38
39 _MUTEX_LOCK(time_lock);
40 *timeptr = *(localtime(timep));
41 _MUTEX_UNLOCK(time_lock);
42 return timeptr;
43 }
44 #endif /* HAVE_LOCALTIME_R */
45
List_show(struct file_parse_s * fps,const struct parsedname * pn)46 static void List_show(struct file_parse_s *fps, const struct parsedname *pn)
47 {
48 struct stat stbuf;
49 time_t now;
50 struct tm tm_now;
51 double age;
52 char date_buf[13];
53 ASCII *perms[] = {
54 "---------",
55 "--x--x--x",
56 "-w--w--w-",
57 "-wx-wx-wx",
58 "r--r--r--",
59 "r-xr-xr-x",
60 "rw-rw-rw-",
61 "rwxrwxrwx",
62 };
63 LEVEL_DEBUG("List_show %s", pn->path);
64 switch (fps->fle) {
65 case file_list_list:
66 FS_fstat_postparse(&stbuf, pn);
67 fdprintf(fps->out, "%s%s", stbuf.st_mode & S_IFDIR ? "d" : "-", perms[stbuf.st_mode & 0x07]);
68 /* fps->output link & ownership information */
69 fdprintf(fps->out, " %3d %-8d %-8d %8lu ", stbuf.st_nlink, stbuf.st_uid, stbuf.st_gid, (unsigned long) stbuf.st_size);
70 /* fps->output date */
71 time(&now);
72 localtime_r(&stbuf.st_mtime, &tm_now);
73 age = difftime(now, stbuf.st_mtime);
74 if ((age > 60 * 60 * 24 * 30 * 6)
75 || (age < -(60 * 60 * 24 * 30 * 6))) {
76 strftime(date_buf, sizeof(date_buf), "%b %e %Y", &tm_now);
77 } else {
78 strftime(date_buf, sizeof(date_buf), "%b %e %H:%M", &tm_now);
79 }
80 fdprintf(fps->out, "%s ", date_buf);
81 /* Fall Through */
82 case file_list_nlst:
83 /* fps->output filename */
84 fdprintf(fps->out, "%s\r\n", &pn->path[fps->start]);
85 }
86 }
87
FileLexParse(struct file_parse_s * fps)88 void FileLexParse(struct file_parse_s *fps)
89 {
90 struct parsedname pn;
91 while (1) {
92 switch (fps->pse) {
93 case parse_status_init:
94 LEVEL_DEBUG("FTP parse_status_init Path<%s> File <%s>", fps->buffer, fps->rest);
95 /* fps->buffer is absolute */
96 /* trailing / only at root */
97 fps->ret = 0;
98 fps->start = strlen(fps->buffer);
99 if (fps->start > 1) {
100 ++fps->start;
101 }
102 fps->rest = skip_ls_options(fps->rest);
103 if (fps->rest == NULL || fps->rest[0] == '\0') {
104 fps->pse = parse_status_tame;
105 } else {
106 if (fps->rest[0] == '/') { // root specification
107 fps->buffer[1] = '\0';
108 fps->start = 0;
109 ++fps->rest;
110 }
111 fps->pse = parse_status_init2;
112 }
113 break;
114 case parse_status_init2:
115 LEVEL_DEBUG("FTP parse_status_init2 Path<%s> File <%s>", fps->buffer, fps->rest);
116 /* fps->buffer is absolute */
117 /* trailing / only at root */
118 if ((fps->rest[0] == '.' && fps->rest[1] == '.')
119 || strpbrk(fps->rest, "*[?")) {
120 fps->pse = parse_status_back;
121 } else {
122 fps->pse = parse_status_tame;
123 }
124 break;
125 case parse_status_back:
126 LEVEL_DEBUG("FTP parse_status_back Path<%s> File <%s>", fps->buffer, fps->rest);
127 /* fps->buffer is absolute */
128 /* trailing / only at root */
129 if (fps->rest[0] == '.' && fps->rest[1] == '.') {
130 // Move back
131 ASCII *back = strrchr(fps->buffer, '/');
132 back[1] = '\0';
133 fps->start = strlen(fps->buffer);
134 // look for next file part
135 if (fps->rest[2] == '\0') {
136 fps->pse = parse_status_last;
137 fps->rest = NULL;
138 } else if (fps->rest[2] == '/') {
139 fps->pse = parse_status_next;
140 fps->rest = &fps->rest[3];
141 } else {
142 fps->ret = -ENOENT;
143 return;
144 }
145 } else {
146 fps->pse = parse_status_next; // off the double dot trail
147 }
148 break;
149 case parse_status_next:
150 LEVEL_DEBUG("FTP parse_status_next Path<%s> File <%s>", fps->buffer, fps->rest);
151 /* fps->buffer is absolute */
152 /* trailing / only at root */
153 if (fps->rest == NULL || fps->rest[0] == '\0') {
154 fps->pse = parse_status_last;
155 } else {
156 ASCII *oldrest = strsep(&fps->rest, "/");
157 if (strpbrk(oldrest, "*[?")) {
158 WildLexParse(fps, oldrest);
159 return;
160 } else {
161 if (strlen(fps->buffer) + strlen(oldrest) + 4 > PATH_MAX) {
162 fps->ret = -ENAMETOOLONG;
163 return;
164 }
165 if (fps->buffer[1]) {
166 strcat(fps->buffer, "/");
167 }
168 strcat(fps->buffer, oldrest);
169 fps->pse = parse_status_next;
170 }
171 }
172 break;
173 case parse_status_tame:
174 LEVEL_DEBUG("FTP parse_status_tame Path<%s> File <%s>", fps->buffer, fps->rest);
175 /* fps->buffer is absolute */
176 /* trailing / only at root */
177 if ( fps-> rest != NULL ) {
178 if ( strlen(fps->buffer) + strlen(fps->rest) + 4 > PATH_MAX ) {
179 fps->ret = -ENAMETOOLONG;
180 return;
181 }
182 if ( fps->buffer[1] != 0 ) {
183 strcat(fps->buffer, "/");
184 }
185 strcat(fps->buffer, fps->rest);
186 }
187 if (FS_ParsedName(fps->buffer, &pn) == 0) {
188 if (IsDir(&pn)) {
189 fps->start = strlen(fps->buffer) + 1;
190 if (fps->start == 2) {
191 fps->start = 1;
192 } else if (fps->buffer[fps->start - 2] == '/') {
193 --fps->start;
194 fps->buffer[fps->start - 1] = '\0';
195 }
196 fps->rest = NULL;
197 WildLexParse(fps, "*");
198 } else {
199 List_show(fps, &pn);
200 }
201 FS_ParsedName_destroy(&pn);
202 } else {
203 fps->ret = -ENOENT;
204 }
205 return;
206 case parse_status_last:
207 LEVEL_DEBUG("FTP parse_status_last Path<%s> File <%s>", fps->buffer, fps->rest);
208 /* fps->buffer is absolute */
209 /* trailing / only at root */
210 if (fps->rest && (strlen(fps->buffer) + strlen(fps->rest) + 4 > PATH_MAX)) {
211 fps->ret = -ENAMETOOLONG;
212 return;
213 }
214 if (FS_ParsedNamePlus(fps->buffer, fps->rest, &pn) == 0) {
215 List_show(fps, &pn);
216 FS_ParsedName_destroy(&pn);
217 }
218 return;
219 }
220 }
221 }
222
223 struct wildlexparse {
224 ASCII *end;
225 ASCII *match;
226 struct file_parse_s *fps;
227 };
228 /* Called for each directory element, and operates recursively */
229 /* uses C library fnmatch for file wildcard comparisons */
WildLexParseCallback(void * v,const struct parsedname * const pn_entry)230 static void WildLexParseCallback(void *v, const struct parsedname *const pn_entry)
231 {
232 struct wildlexparse *wlp = v;
233 struct file_parse_s fps; // duplicate for recursive call
234
235 /* get real name from parsedname struct */
236 strcpy(&wlp->end[1], FS_DirName(pn_entry));
237
238 LEVEL_DEBUG("WildLexParseCallback: Try %s vs %s", &wlp->end[1], wlp->match);
239 if (fnmatch(wlp->match, &wlp->end[1], FNM_PATHNAME)) {
240 return;
241 }
242
243 /* Matched! So set up for recursive call on nect elements in path name */
244 memcpy(&fps, wlp->fps, sizeof(fps));
245 fps.pse = parse_status_next;
246 FileLexParse(&fps);
247 }
248
249 /* Called for each directory, calls WildLEexParseCallback on each element
250 to see if matches wildcards (even used for normal listings with * wildcard)
251 */
WildLexParse(struct file_parse_s * fps,ASCII * match)252 static void WildLexParse(struct file_parse_s *fps, ASCII * match)
253 {
254 struct parsedname pn;
255 /* Embedded callback function */
256
257 LEVEL_DEBUG("FTP Wildcard pattern matching: Path=%s, Pattern=%s, File=%s", SAFESTRING(fps->buffer), SAFESTRING(match), SAFESTRING(fps->rest));
258
259 /* Check potential length */
260 if (strlen(fps->buffer) + OW_FULLNAME_MAX + 2 > PATH_MAX) {
261 fps->ret = -ENAMETOOLONG;
262 return;
263 }
264
265 /* Can we understand the current path? */
266 if (FS_ParsedName(fps->buffer, &pn) != 0 ) {
267 fps->ret = -ENOENT;
268 return;
269 }
270
271 if (pn.selected_filetype) {
272 fps->ret = -ENOTDIR;
273 } else {
274 struct wildlexparse wlp = { NULL, match, fps, };
275 int root = (fps->buffer[1] == '\0');
276
277 wlp.end = &fps->buffer[strlen(fps->buffer)];
278
279 if (root) {
280 --wlp.end;
281 }
282 wlp.end[0] = '/';
283 FS_dir(WildLexParseCallback, &wlp, &pn);
284 if (root) {
285 ++wlp.end;
286 }
287 wlp.end[0] = '\0'; // restore fps->buffer
288 }
289 FS_ParsedName_destroy(&pn);
290
291 }
292
293 /* write with care for max length and incomplete outout */
fdprintf(FILE_DESCRIPTOR_OR_ERROR file_descriptor,const char * fmt,...)294 static void fdprintf(FILE_DESCRIPTOR_OR_ERROR file_descriptor, const char *fmt, ...)
295 {
296 char buf[PATH_MAX + 1];
297 ssize_t buflen;
298 va_list ap;
299 int amt_written;
300 int write_ret;
301
302 daemon_assert(FILE_DESCRIPTOR_VALID(file_descriptor));
303 daemon_assert(fmt != NULL);
304
305 va_start(ap, fmt);
306 buflen = vsnprintf(buf, sizeof(buf), fmt, ap);
307 va_end(ap);
308 if (buflen <= 0) {
309 return;
310 }
311 if ((size_t) buflen >= sizeof(buf)) {
312 buflen = sizeof(buf) - 1;
313 }
314
315 amt_written = 0;
316 while (amt_written < buflen) {
317 write_ret = write(file_descriptor, buf + amt_written, buflen - amt_written);
318 if (write_ret <= 0) {
319 return;
320 }
321 amt_written += write_ret;
322 }
323 }
324
325 /*
326 hack workaround clients like Midnight Commander that send:
327 LIST -al /dirname
328 */
skip_ls_options(char * filespec)329 static char *skip_ls_options(char *filespec)
330 {
331 daemon_assert(filespec != NULL);
332
333 for (;;) {
334 /* end when we've passed all options */
335 if (*filespec != '-') {
336 break;
337 }
338 filespec++;
339
340 /* if we find "--", eat it and any following whitespace then return */
341 if ((filespec[0] == '-') && (filespec[1] == ' ')) {
342 filespec += 2;
343 while (isspace(*filespec)) {
344 filespec++;
345 }
346 break;
347 }
348
349 /* otherwise, skip this option */
350 while ((*filespec != '\0') && !isspace(*filespec)) {
351 filespec++;
352 }
353
354 /* and skip any whitespace */
355 while (isspace(*filespec)) {
356 filespec++;
357 }
358 }
359
360 daemon_assert(filespec != NULL);
361
362 return filespec;
363 }
364