1 /*
2 * ProFTPD: mod_wrap2_file -- a mod_wrap2 sub-module for supplying IP-based
3 * access control data via file-based tables
4 * Copyright (c) 2002-2016 TJ Saunders
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
19 *
20 * As a special exemption, TJ Saunders gives permission to link this program
21 * with OpenSSL, and distribute the resulting executable, without including
22 * the source code for OpenSSL in the source distribution.
23 */
24
25 #include "mod_wrap2.h"
26
27 #define MOD_WRAP2_FILE_VERSION "mod_wrap2_file/1.3"
28
29 module wrap2_file_module;
30
31 static const char *filetab_service_name = NULL;
32
33 static array_header *filetab_clients_list = NULL;
34 static array_header *filetab_daemons_list = NULL;
35 static array_header *filetab_options_list = NULL;
36
37 #ifndef MOD_WRAP2_FILE_BUFFER_SIZE
38 # define MOD_WRAP2_FILE_BUFFER_SIZE PR_TUNABLE_BUFFER_SIZE
39 #endif
40
filetab_parse_table(wrap2_table_t * filetab)41 static void filetab_parse_table(wrap2_table_t *filetab) {
42 unsigned int lineno = 0;
43 char buf[MOD_WRAP2_FILE_BUFFER_SIZE] = {'\0'};
44
45 while (pr_fsio_getline(buf, sizeof(buf), (pr_fh_t *) filetab->tab_handle,
46 &lineno) != NULL) {
47 char *ptr, *res = NULL, *service = NULL;
48 size_t buflen = strlen(buf);
49
50 if (buf[buflen-1] != '\n') {
51 wrap2_log("file '%s': missing newline or line too long (%u) at line %u",
52 filetab->tab_name, (unsigned int) buflen, lineno);
53 continue;
54 }
55
56 if (buf[0] == '#' || buf[strspn(buf, " \t\r\n")] == 0) {
57 continue;
58 }
59
60 buf[buflen-1] = '\0';
61
62 /* The list of daemons is from the start of the line to a ':' delimiter.
63 * This list is assumed to be space-delimited; failure to match this
64 * syntax will result in lack of desired results when doing the access
65 * checks.
66 */
67 ptr = strchr(buf, ':');
68 if (ptr == NULL) {
69 wrap2_log("file '%s': badly formatted list of daemon/service names at "
70 "line %u", filetab->tab_name, lineno);
71 continue;
72 }
73
74 service = pstrndup(filetab->tab_pool, buf, (ptr - buf));
75
76 if (filetab_service_name &&
77 (strcasecmp(filetab_service_name, service) == 0 ||
78 strncasecmp("ALL", service, 4) == 0)) {
79 if (filetab_daemons_list == NULL) {
80 filetab_daemons_list = make_array(filetab->tab_pool, 0, sizeof(char *));
81 }
82
83 *((char **) push_array(filetab_daemons_list)) = service;
84
85 res = wrap2_strsplit(buf, ':');
86 if (res == NULL) {
87 wrap2_log("file '%s': missing \":\" separator at %u",
88 filetab->tab_name, lineno);
89 continue;
90 }
91
92 if (filetab_clients_list == NULL) {
93 filetab_clients_list = make_array(filetab->tab_pool, 0, sizeof(char *));
94 }
95
96 /* Check for another ':' delimiter. If present, anything following that
97 * delimiter is an option/shell command (as per the hosts_access(5) man
98 * page syntax description).
99 *
100 * If there are commas or whitespace in the line, parse them as separate
101 * client names. Otherwise, a comma- or space-delimited list of names
102 * will be treated as a single name, and violate the principle of least
103 * surprise for the site admin.
104 *
105 * NOTE: Disable support for options in the file syntax if IPv6 addresses
106 * are present, since the parsing code below is not sufficient for
107 * handling both IPv6 addresses AND options, e.g.:
108 *
109 * proftpd: [::1] [::2]: <options>
110 */
111
112 ptr = strchr(res, ':');
113 if (ptr != NULL) {
114 char *clients;
115 size_t clients_len;
116
117 clients_len = (ptr - res);
118 clients = pstrndup(filetab->tab_pool, res, clients_len);
119
120 if (strcspn(clients, "[]") == clients_len) {
121 ptr = wrap2_strsplit(res, ':');
122
123 if (filetab_options_list == NULL) {
124 filetab_options_list = make_array(filetab->tab_pool, 0,
125 sizeof(char *));
126 }
127
128 /* Skip redundant whitespaces */
129 while (*ptr == ' ' ||
130 *ptr == '\t') {
131 pr_signals_handle();
132 ptr++;
133 }
134
135 *((char **) push_array(filetab_options_list)) =
136 pstrdup(filetab->tab_pool, ptr);
137
138 } else {
139 /* Ignoring options and IPv6 addresses (Bug#4090) for now. */
140 }
141
142 } else {
143 /* No options present. */
144 ptr = res;
145 }
146
147 ptr = strpbrk(res, ", \t");
148 if (ptr != NULL) {
149 char *dup_opts, *word;
150
151 dup_opts = pstrdup(filetab->tab_pool, res);
152 while ((word = pr_str_get_token(&dup_opts, ", \t")) != NULL) {
153 size_t wordlen;
154
155 pr_signals_handle();
156
157 wordlen = strlen(word);
158 if (wordlen == 0) {
159 continue;
160 }
161
162 /* Remove any trailing comma */
163 if (word[wordlen-1] == ',') {
164 word[wordlen-1] = '\0';
165 wordlen--;
166 }
167
168 *((char **) push_array(filetab_clients_list)) = word;
169
170 /* Skip redundant whitespaces */
171 while (*dup_opts == ' ' ||
172 *dup_opts == '\t') {
173 pr_signals_handle();
174 dup_opts++;
175 }
176 }
177
178 } else {
179 *((char **) push_array(filetab_clients_list)) =
180 pstrdup(filetab->tab_pool, res);
181 }
182
183 } else {
184 wrap2_log("file '%s': skipping irrevelant daemon/service ('%s') line %u",
185 filetab->tab_name, service, lineno);
186 }
187 }
188
189 return;
190 }
191
filetab_close_cb(wrap2_table_t * filetab)192 static int filetab_close_cb(wrap2_table_t *filetab) {
193 int res = pr_fsio_close((pr_fh_t *) filetab->tab_handle);
194 filetab->tab_handle = NULL;
195
196 filetab_clients_list = NULL;
197 filetab_daemons_list = NULL;
198 filetab_options_list = NULL;
199
200 filetab_service_name = NULL;
201
202 return res;
203 }
204
filetab_fetch_clients_cb(wrap2_table_t * filetab,const char * name)205 static array_header *filetab_fetch_clients_cb(wrap2_table_t *filetab,
206 const char *name) {
207
208 /* If this table/file has not yet been parsed, parse it. */
209 if (*((unsigned char *) filetab->tab_data) == FALSE) {
210 filetab_parse_table(filetab);
211 *((unsigned char *) filetab->tab_data) = TRUE;
212 }
213
214 return filetab_clients_list;
215 }
216
filetab_fetch_daemons_cb(wrap2_table_t * filetab,const char * name)217 static array_header *filetab_fetch_daemons_cb(wrap2_table_t *filetab,
218 const char *name) {
219
220 filetab_service_name = name;
221
222 /* If this table/file has not yet been parsed, parse it. */
223 if (*((unsigned char *) filetab->tab_data) == FALSE) {
224 filetab_parse_table(filetab);
225 *((unsigned char *) filetab->tab_data) = TRUE;
226 }
227
228 return filetab_daemons_list;
229 }
230
filetab_fetch_options_cb(wrap2_table_t * filetab,const char * name)231 static array_header *filetab_fetch_options_cb(wrap2_table_t *filetab,
232 const char *name) {
233
234 /* If this table/file has not yet been parsed, parse it. */
235 if (*((unsigned char *) filetab->tab_data) == FALSE) {
236 filetab_parse_table(filetab);
237 *((unsigned char *) filetab->tab_data) = TRUE;
238 }
239
240 return filetab_options_list;
241 }
242
filetab_open_cb(pool * parent_pool,const char * srcinfo)243 static wrap2_table_t *filetab_open_cb(pool *parent_pool, const char *srcinfo) {
244 struct stat st;
245 wrap2_table_t *tab = NULL;
246 pool *tab_pool = make_sub_pool(parent_pool);
247
248 /* Do not allow relative paths. */
249 if (*srcinfo != '/' &&
250 *srcinfo != '~') {
251 wrap2_log("error: table relative paths are forbidden: '%s'", srcinfo);
252 destroy_pool(tab_pool);
253 errno = EINVAL;
254 return NULL;
255 }
256
257 /* If the path starts with a tilde, expand it out. */
258 if (srcinfo[0] == '~' &&
259 srcinfo[1] == '/') {
260 char *path = NULL;
261
262 PRIVS_USER
263 path = dir_realpath(tab_pool, srcinfo);
264 PRIVS_RELINQUISH
265
266 if (path) {
267 srcinfo = path;
268 wrap2_log("resolved tilde: path now '%s'", srcinfo);
269 }
270 }
271
272 /* If the path contains a %U variable, interpolate it. */
273 if (strstr(srcinfo, "%U") != NULL) {
274 const char *orig_user;
275
276 orig_user = pr_table_get(session.notes, "mod_auth.orig-user", NULL);
277 if (orig_user != NULL) {
278 const char *interp_path;
279
280 interp_path = sreplace(tab_pool, srcinfo, "%U", orig_user, NULL);
281 if (interp_path != NULL) {
282 srcinfo = interp_path;
283 wrap2_log("resolved %%U: path now '%s'", srcinfo);
284 }
285 }
286 }
287
288 tab = (wrap2_table_t *) pcalloc(tab_pool, sizeof(wrap2_table_t));
289 tab->tab_pool = tab_pool;
290
291 /* Open the table handle */
292 while ((tab->tab_handle = (void *) pr_fsio_open(srcinfo, O_RDONLY)) == NULL) {
293 int xerrno = errno;
294
295 if (xerrno == EINTR) {
296 pr_signals_handle();
297 continue;
298 }
299
300 destroy_pool(tab->tab_pool);
301 errno = xerrno;
302 return NULL;
303 }
304
305 /* Stat the opened file to determine the optimal buffer size for IO. */
306 memset(&st, 0, sizeof(st));
307 if (pr_fsio_fstat((pr_fh_t *) tab->tab_handle, &st) < 0) {
308 int xerrno = errno;
309
310 destroy_pool(tab->tab_pool);
311 pr_fsio_close((pr_fh_t *) tab->tab_handle);
312 tab->tab_handle = NULL;
313
314 errno = xerrno;
315 return NULL;
316 }
317
318 if (S_ISDIR(st.st_mode)) {
319 int xerrno = EISDIR;
320
321 destroy_pool(tab->tab_pool);
322 pr_fsio_close((pr_fh_t *) tab->tab_handle);
323 tab->tab_handle = NULL;
324
325 errno = xerrno;
326 return NULL;
327 }
328
329 ((pr_fh_t *) tab->tab_handle)->fh_iosz = st.st_blksize;
330
331 tab->tab_name = pstrdup(tab->tab_pool, srcinfo);
332
333 /* Set the necessary callbacks. */
334 tab->tab_close = filetab_close_cb;
335 tab->tab_fetch_clients = filetab_fetch_clients_cb;
336 tab->tab_fetch_daemons = filetab_fetch_daemons_cb;
337 tab->tab_fetch_options = filetab_fetch_options_cb;
338
339 /* Use the tab_data member as a Boolean flag. */
340 tab->tab_data = pcalloc(tab->tab_pool, sizeof(unsigned char));
341 *((unsigned char *) tab->tab_data) = FALSE;
342
343 return tab;
344 }
345
346 /* Event handlers
347 */
348
349 #if defined(PR_SHARED_MODULE)
filetab_mod_unload_ev(const void * event_data,void * user_data)350 static void filetab_mod_unload_ev(const void *event_data, void *user_data) {
351 if (strcmp("mod_wrap2_file.c", (const char *) event_data) == 0) {
352 pr_event_unregister(&wrap2_file_module, NULL, NULL);
353 wrap2_unregister("file");
354 }
355 }
356 #endif /* PR_SHARED_MODULE */
357
358 /* Initialization routines
359 */
360
filetab_init(void)361 static int filetab_init(void) {
362
363 /* Initialize the wrap source objects for type "file". */
364 wrap2_register("file", filetab_open_cb);
365
366 #if defined(PR_SHARED_MODULE)
367 pr_event_register(&wrap2_file_module, "core.module-unload",
368 filetab_mod_unload_ev, NULL);
369 #endif /* PR_SHARED_MODULE */
370
371 return 0;
372 }
373
374 module wrap2_file_module = {
375 NULL, NULL,
376
377 /* Module API version 2.0 */
378 0x20,
379
380 /* Module name */
381 "wrap2_file",
382
383 /* Module configuration handler table */
384 NULL,
385
386 /* Module command handler table */
387 NULL,
388
389 /* Module authentication handler table */
390 NULL,
391
392 /* Module initialization function */
393 filetab_init,
394
395 /* Session initialization function */
396 NULL,
397
398 /* Module version */
399 MOD_WRAP2_FILE_VERSION
400 };
401