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 WildLexCD(struct cd_parse_s *cps, ASCII * match);
27 
FileLexCD(struct cd_parse_s * cps)28 void FileLexCD(struct cd_parse_s *cps)
29 {
30 	struct parsedname pn;
31 	while (1) {
32 		switch (cps->pse) {
33 		case parse_status_init:
34 			LEVEL_DEBUG("FTP parse_status_init Path<%s> File <%s>", cps->buffer, cps->rest);
35 			/* cps->buffer is absolute */
36 			/* trailing / only at root */
37 			cps->ret = 0;
38 			cps->solutions = 0;
39 			cps->dir = NULL;
40 			if (cps->rest == NULL || cps->rest[0] == '\0') {
41 				cps->pse = parse_status_tame;
42 			} else {
43 				if (cps->rest[0] == '/') {	// root (absolute) specification
44 					cps->buffer[1] = '\0';
45 					++cps->rest;
46 				}
47 				cps->pse = parse_status_init2;
48 			}
49 			break;
50 		case parse_status_init2:
51 			LEVEL_DEBUG("FTP parse_status_init2 Path<%s> File <%s>", cps->buffer, cps->rest);
52 			/* cps->buffer is absolute */
53 			/* trailing / only at root */
54 			if ((cps->rest[0] == '.' && cps->rest[1] == '.')
55 				|| strpbrk(cps->rest, "*[?")) {
56 				cps->pse = parse_status_back;
57 			} else {
58 				cps->pse = parse_status_tame;
59 			}
60 			break;
61 		case parse_status_back:
62 			LEVEL_DEBUG("FTP parse_status_back Path<%s> File <%s>", cps->buffer, cps->rest);
63 			/* cps->buffer is absolute */
64 			/* trailing / only at root */
65 			if (cps->rest[0] == '.' && cps->rest[1] == '.') {
66 				// Move back
67 				ASCII *back = strrchr(cps->buffer, '/');
68 				back[1] = '\0';
69 				// look for next file part
70 				if (cps->rest[2] == '\0') {
71 					cps->pse = parse_status_last;
72 					cps->rest = NULL;
73 				} else if (cps->rest[2] == '/') {
74 					cps->pse = parse_status_next;
75 					cps->rest = &cps->rest[3];
76 				} else {
77 					cps->ret = -ENOENT;
78 					return;
79 				}
80 			} else {
81 				cps->pse = parse_status_next;	// off the double dot trail
82 			}
83 			break;
84 		case parse_status_next:
85 			LEVEL_DEBUG("FTP parse_status_next Path<%s> File <%s>", cps->buffer, cps->rest);
86 			/* cps->buffer is absolute */
87 			/* trailing / only at root */
88 			if (cps->rest == NULL || cps->rest[0] == '\0') {
89 				cps->pse = parse_status_last;
90 			} else {
91 				ASCII *oldrest = strsep(&cps->rest, "/");
92 				if (strpbrk(oldrest, "*[?")) {
93 					WildLexCD(cps, oldrest);
94 					return;
95 				} else {
96 					if (strlen(cps->buffer) + strlen(oldrest) + 4 > PATH_MAX) {
97 						cps->ret = -ENAMETOOLONG;
98 						return;
99 					}
100 					if (cps->buffer[1]) {
101 						strcat(cps->buffer, "/");
102 					}
103 					strcat(cps->buffer, oldrest);
104 					cps->pse = parse_status_next;
105 				}
106 			}
107 			break;
108 		case parse_status_tame:
109 			LEVEL_DEBUG("FTP parse_status_tame Path<%s> File <%s>", cps->buffer, cps->rest);
110 			/* cps->buffer is absolute */
111 			/* trailing / only at root */
112 			if (cps->rest && (strlen(cps->buffer) + strlen(cps->rest) + 4 > PATH_MAX)) {
113 				cps->ret = -ENAMETOOLONG;
114 				return;
115 			}
116 			if (cps->buffer[1]) {
117 				strcat(cps->buffer, "/");
118 			}
119 			strcat(cps->buffer, cps->rest);
120 			if (FS_ParsedName(cps->buffer, &pn) == 0) {
121 				if (IsDir(&pn)) {
122 					++cps->solutions;
123 					if (cps->solutions == 1) {
124 						cps->dir = strdup(pn.path);
125 					}
126 				} else {
127 					cps->ret = -ENOTDIR;
128 				}
129 				FS_ParsedName_destroy(&pn);
130 			} else {
131 				cps->ret = -ENOENT;
132 			}
133 			return;
134 		case parse_status_last:
135 			LEVEL_DEBUG("FTP parse_status_last Path<%s> File <%s>", cps->buffer, cps->rest);
136 			/* cps->buffer is absolute */
137 			/* trailing / only at root */
138 			if (cps->rest && (strlen(cps->buffer) + strlen(cps->rest) + 4 > PATH_MAX)) {
139 				cps->ret = -ENAMETOOLONG;
140 				return;
141 			}
142 			if (FS_ParsedNamePlus(cps->buffer, cps->rest, &pn) == 0) {
143 				if (IsDir(&pn)) {
144 					++cps->solutions;
145 					if (cps->solutions == 1) {
146 						cps->dir = strdup(pn.path);
147 					}
148 				} else {
149 					cps->ret = -ENOTDIR;
150 				}
151 				FS_ParsedName_destroy(&pn);
152 			}
153 			return;
154 		}
155 	}
156 }
157 
158 struct wildlexcd {
159 	ASCII *end;
160 	ASCII *match;
161 	struct cd_parse_s *cps;
162 };
163 
WildLexCDCallback(void * v,const struct parsedname * const pn_entry)164 static void WildLexCDCallback(void *v, const struct parsedname *const pn_entry)
165 {
166 	struct wildlexcd *wlcd = v;
167 	struct cd_parse_s cps;
168 	strcpy(&wlcd->end[1], FS_DirName(pn_entry));
169 	//printf("Try %s vs %s\n",end,match) ;
170 	//if ( fnmatch( match, end, FNM_PATHNAME|FNM_CASEFOLD ) ) return ;
171 	if (fnmatch(wlcd->match, &wlcd->end[1], FNM_PATHNAME)) {
172 		return;
173 	}
174 	//printf("Match! %s\n",end) ;
175 	memcpy(&cps, wlcd->cps, sizeof(cps));
176 	cps.pse = parse_status_next;
177 	FileLexCD(&cps);
178 }
179 
WildLexCD(struct cd_parse_s * cps,ASCII * match)180 static void WildLexCD(struct cd_parse_s *cps, ASCII * match)
181 {
182 	struct parsedname pn;
183 
184 	LEVEL_DEBUG("FTP Wildcard pattern matching: Path=%s, Pattern=%s, rest=%s", SAFESTRING(cps->buffer), SAFESTRING(match), SAFESTRING(cps->rest));
185 	/* Check potential length */
186 	if (strlen(cps->buffer) + OW_FULLNAME_MAX + 2 > PATH_MAX) {
187 		cps->ret = -ENAMETOOLONG;
188 		return;
189 	}
190 
191 	if ( FS_ParsedName(cps->buffer, &pn) != 0 ) {
192 		cps->ret = -ENOENT;
193 		return;
194 	}
195 
196 	if (!IsDir(&pn)) {
197 		cps->ret = -ENOTDIR;
198 	} else {
199 		struct wildlexcd wlcd = { NULL, match, cps, };
200 		int root = (cps->buffer[1] == '\0');
201 
202 		wlcd.end = &cps->buffer[strlen(cps->buffer)];
203 		if (root) {
204 			--wlcd.end;
205 		}
206 		wlcd.end[0] = '/';
207 		FS_dir(WildLexCDCallback, &wlcd, &pn);
208 		if (root) {
209 			++wlcd.end;
210 		}
211 		wlcd.end[0] = '\0';		// restore cps->buffer
212 	}
213 	FS_ParsedName_destroy(&pn);
214 }
215