1 /*
2 * ProFTPD: mod_wrap -- use Wietse Venema's TCP wrappers library for
3 * access control
4 * Copyright (c) 2000-2020 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 * -- DO NOT MODIFY THE TWO LINES BELOW --
25 * $Libraries: -lwrap -lnsl$
26 */
27
28 #define MOD_WRAP_VERSION "mod_wrap/1.2.4"
29
30 #include "conf.h"
31 #include "privs.h"
32 #include "tcpd.h"
33
34 /* these need to be defined for the libwrap functions -- default settings
35 * are those from tcpd.h
36 */
37 int allow_severity = PR_LOG_INFO;
38 int deny_severity = PR_LOG_WARNING;
39
40 module wrap_module;
41
42 /* Necessary prototypes */
43 static int wrap_eval_expression(char **, array_header *);
44 static const char *wrap_get_user_table(cmd_rec *, const char *, char *);
45 static int wrap_is_usable_file(const char *);
46 static void wrap_log_request_allowed(int, struct request_info *);
47 static void wrap_log_request_denied(int, struct request_info *);
48 static config_rec *wrap_resolve_user(pool *, const char **);
49 static int wrap_sess_init(void);
50
51 static char *wrap_service_name = "proftpd";
52
53 /* Support routines
54 */
55
56 /* boolean "expression" matching, returns TRUE if the entire expression matches
57 */
wrap_eval_expression(char ** config_expr,array_header * session_expr)58 static int wrap_eval_expression(char **config_expr,
59 array_header *session_expr) {
60
61 unsigned char found = FALSE;
62 unsigned int i = 0;
63 char *elem = NULL, **list = NULL;
64
65 /* sanity check */
66 if (!config_expr || !*config_expr || !session_expr)
67 return FALSE;
68
69 list = (char **) session_expr->elts;
70
71 for (; *config_expr; config_expr++) {
72 elem = *config_expr;
73 found = FALSE;
74
75 if (*elem == '!') {
76 found = !found;
77 elem++;
78 }
79
80 for (i = 0; i < session_expr->nelts; i++) {
81 if (list[i] &&
82 strcmp(list[i], elem) == 0) {
83 found = !found;
84 break;
85 }
86 }
87
88 if (!found) {
89 config_expr = NULL;
90 break;
91 }
92 }
93
94 if (config_expr)
95 return TRUE;
96
97 return FALSE;
98 }
99
wrap_get_user_table(cmd_rec * cmd,const char * user,char * path)100 static const char *wrap_get_user_table(cmd_rec *cmd, const char *user,
101 char *path) {
102 int xerrno = 0;
103
104 char *real_path = NULL;
105 struct passwd *pw = NULL;
106
107 pw = pr_auth_getpwnam(cmd->pool, user);
108
109 /* Handle the case where the given user does not exist. */
110 if (pw == NULL) {
111 return NULL;
112 }
113
114 /* For the dir_realpath() function to work, some session members need to
115 * be set.
116 */
117 session.user = pstrdup(cmd->pool, pw->pw_name);
118 session.login_uid = pw->pw_uid;
119
120 PRIVS_USER
121 real_path = dir_realpath(cmd->pool, path);
122 xerrno = errno;
123 PRIVS_RELINQUISH
124
125 if (real_path) {
126 path = real_path;
127 }
128
129 errno = xerrno;
130 return path;
131 }
132
wrap_is_usable_file(const char * filename)133 static int wrap_is_usable_file(const char *filename) {
134 struct stat st;
135 pr_fh_t *fh = NULL;
136
137 /* check the easy case first */
138 if (filename == NULL) {
139 return FALSE;
140 }
141
142 /* Make sure that the current process can _read_ the file. */
143 fh = pr_fsio_open(filename, O_RDONLY);
144 if (fh == NULL) {
145 int xerrno = errno;
146
147 pr_log_pri(PR_LOG_NOTICE, MOD_WRAP_VERSION ": failed to read \"%s\": %s",
148 filename, strerror(xerrno));
149
150 errno = xerrno;
151 return FALSE;
152 }
153
154 if (pr_fsio_fstat(fh, &st) < 0) {
155 int xerrno = errno;
156
157 pr_log_pri(PR_LOG_NOTICE, MOD_WRAP_VERSION ": failed to stat \"%s\": %s",
158 filename, strerror(xerrno));
159
160 pr_fsio_close(fh);
161 errno = xerrno;
162 return FALSE;
163 }
164
165 if (S_ISDIR(st.st_mode)) {
166 int xerrno = EISDIR;
167
168 pr_log_pri(PR_LOG_NOTICE, MOD_WRAP_VERSION ": unable to use \"%s\": %s",
169 filename, strerror(xerrno));
170
171 pr_fsio_close(fh);
172 errno = xerrno;
173 return FALSE;
174 }
175
176 pr_fsio_close(fh);
177 return TRUE;
178 }
179
wrap_log_request_allowed(int severity,struct request_info * request)180 static void wrap_log_request_allowed(int severity,
181 struct request_info *request) {
182 int priority;
183
184 /* Mask off the facility bits. */
185 priority = (severity & PR_LOG_PRIMASK);
186
187 pr_log_pri(priority, MOD_WRAP_VERSION ": allowed connection from %s",
188 eval_client(request));
189
190 /* done */
191 return;
192 }
193
wrap_log_request_denied(int severity,struct request_info * request)194 static void wrap_log_request_denied(int severity,
195 struct request_info *request) {
196 int priority;
197
198 /* Mask off the facility bits. */
199 priority = (severity & PR_LOG_PRIMASK);
200
201 pr_log_pri(priority, MOD_WRAP_VERSION ": refused connection from %s",
202 eval_client(request));
203
204 /* done */
205 return;
206 }
207
wrap_resolve_user(pool * p,const char ** user)208 static config_rec *wrap_resolve_user(pool *p, const char **user) {
209 config_rec *conf = NULL, *top_conf;
210 char *ourname = NULL, *anonname = NULL;
211 unsigned char is_alias = FALSE, force_anon = FALSE;
212
213 /* Precedence rules:
214 * 1. Search for UserAlias directive.
215 * 2. Search for Anonymous directive.
216 * 3. Normal user login
217 */
218
219 ourname = (char*) get_param_ptr(main_server->conf, "UserName", FALSE);
220
221 conf = find_config(main_server->conf, CONF_PARAM, "UserAlias", TRUE);
222
223 if (conf) do {
224 if (strcmp(conf->argv[0], "*") == 0 ||
225 strcmp(conf->argv[0], *user) == 0) {
226 is_alias = TRUE;
227 break;
228 }
229
230 } while ((conf = find_config_next(conf, conf->next, CONF_PARAM,
231 "UserAlias", TRUE)) != NULL);
232
233 /* if AuthAliasOnly is set, ignore this one and continue */
234 top_conf = conf;
235
236 while (conf && conf->parent &&
237 find_config(conf->parent->set, CONF_PARAM, "AuthAliasOnly", FALSE)) {
238
239 is_alias = FALSE;
240 find_config_set_top(top_conf);
241 conf = find_config_next(conf, conf->next, CONF_PARAM, "UserAlias", TRUE);
242
243 if (conf &&
244 (strcmp(conf->argv[0], "*") == 0 ||
245 strcmp(conf->argv[0], *user) == 0))
246 is_alias = TRUE;
247 }
248
249 if (conf != NULL) {
250 *user = conf->argv[1];
251
252 /* If the alias is applied inside an <Anonymous> context, we have found
253 * our anon block
254 */
255 if (conf->parent &&
256 conf->parent->config_type == CONF_ANON) {
257 conf = conf->parent;
258
259 } else {
260 conf = NULL;
261 }
262 }
263
264 /* Next, search for an anonymous entry */
265 if (conf == NULL) {
266 conf = find_config(main_server->conf, CONF_ANON, NULL, FALSE);
267
268 } else {
269 find_config_set_top(conf);
270 }
271
272 if (conf != NULL) do {
273 anonname = (char*) get_param_ptr(conf->subset, "UserName", FALSE);
274
275 if (!anonname)
276 anonname = ourname;
277
278 if (anonname &&
279 strcmp(anonname, *user) == 0) {
280 break;
281 }
282
283 } while ((conf = find_config_next(conf, conf->next, CONF_ANON, NULL,
284 FALSE)) != NULL);
285
286 if (!is_alias && !force_anon) {
287
288 if (find_config((conf ? conf->subset :
289 main_server->conf), CONF_PARAM, "AuthAliasOnly", FALSE)) {
290
291 if (conf != NULL &&
292 conf->config_type == CONF_ANON) {
293 conf = NULL;
294
295 } else {
296 *user = NULL;
297 }
298
299 if (*user != NULL &&
300 find_config(main_server->conf, CONF_PARAM, "AuthAliasOnly", FALSE)) {
301 *user = NULL;
302 }
303 }
304 }
305
306 return conf;
307 }
308
309 /* Configuration handlers
310 */
311
set_tcpaccessfiles(cmd_rec * cmd)312 MODRET set_tcpaccessfiles(cmd_rec *cmd) {
313 config_rec *c = NULL;
314
315 /* assume use of the standard TCP wrappers installation locations */
316 char *allow_filename = "/etc/hosts.allow";
317 char *deny_filename = "/etc/hosts.deny";
318
319 CHECK_ARGS(cmd, 2);
320 CHECK_CONF(cmd, CONF_ROOT|CONF_ANON|CONF_VIRTUAL|CONF_GLOBAL);
321
322 /* use the user-given files, checking to make sure that they exist and
323 * are readable.
324 */
325 allow_filename = cmd->argv[1];
326 deny_filename = cmd->argv[2];
327
328 /* if the filenames begin with a '~', AND this is not immediately followed
329 * by a '/' (ie '~/'), expand it out for checking and storing for later
330 * lookups. If the filenames DO begin with '~/', do the expansion later,
331 * after authenication. In other words, do checking of static filenames
332 * now, and checking of dynamic (user-authentication-based) filenames
333 * later.
334 */
335 if (allow_filename[0] == '/') {
336
337 /* it's an absolute path, so the filename will be checked as is */
338 if (!wrap_is_usable_file(allow_filename))
339 return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool,
340 cmd->argv[0], ": '", allow_filename, "' must be a usable file", NULL));
341
342 } else if (allow_filename[0] == '~' && allow_filename[1] != '/') {
343 char *allow_real_file = NULL;
344
345 allow_real_file = dir_realpath(cmd->pool, allow_filename);
346
347 if (allow_real_file == NULL || !wrap_is_usable_file(allow_real_file))
348 return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool,
349 cmd->argv[0], ": '", allow_filename, "' must be a usable file", NULL));
350
351 allow_filename = allow_real_file;
352
353 } else if (allow_filename[0] != '~' && allow_filename[0] != '/') {
354
355 /* no relative paths allowed */
356 return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool,
357 cmd->argv[0], ": '", allow_filename, "' must start with \"/\" or \"~\"",
358 NULL));
359
360 } else {
361
362 /* it's a determine-at-login-time filename -- check it later */
363 ;
364 }
365
366 if (deny_filename[0] == '/') {
367
368 /* it's an absolute path, so the filename will be checked as is */
369 if (!wrap_is_usable_file(deny_filename))
370 return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool,
371 cmd->argv[0], ": '", deny_filename, "' must be a usable file", NULL));
372
373 } else if (deny_filename[0] == '~' && deny_filename[1] != '/') {
374 char *deny_real_file = NULL;
375
376 deny_real_file = dir_realpath(cmd->pool, deny_filename);
377
378 if (deny_real_file == NULL || !wrap_is_usable_file(deny_real_file))
379 return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool,
380 cmd->argv[0], ": '", deny_filename, "' must be a usable file", NULL));
381
382 deny_filename = deny_real_file;
383
384 } else if (deny_filename[0] != '~' && deny_filename[0] != '/') {
385
386 /* no relative paths allowed */
387 return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool,
388 cmd->argv[0], ": '", deny_filename, "' must start with \"/\" or \"~\"",
389 NULL));
390
391 } else {
392
393 /* it's a determine-at-login-time filename -- check it later */
394 ;
395 }
396
397 c = add_config_param_str(cmd->argv[0], 2, (void *) allow_filename,
398 (void *) deny_filename);
399 c->flags |= CF_MERGEDOWN;
400
401 /* done */
402 return PR_HANDLED(cmd);
403 }
404
set_tcpgroupaccessfiles(cmd_rec * cmd)405 MODRET set_tcpgroupaccessfiles(cmd_rec *cmd) {
406 unsigned int group_argc = 1;
407 char *expr, **group_argv = NULL;
408 array_header *group_acl = NULL;
409 config_rec *c = NULL;
410
411 /* assume use of the standard TCP wrappers installation locations */
412 char *allow_filename = NULL, *deny_filename = NULL;
413
414 CHECK_ARGS(cmd, 3);
415 CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
416
417 /* use the user-given files, checking to make sure that they exist and
418 * are readable.
419 */
420 allow_filename = cmd->argv[2];
421 deny_filename = cmd->argv[3];
422
423 /* if the filenames begin with a '~', AND this is not immediately followed
424 * by a '/' (ie '~/'), expand it out for checking and storing for later
425 * lookups. If the filenames DO begin with '~/', do the expansion later,
426 * after authenication. In other words, do checking of static filenames
427 * now, and checking of dynamic (user-authentication-based) filenames
428 * later.
429 */
430 if (allow_filename[0] == '/') {
431
432 /* it's an absolute path, so the filename will be checked as is */
433 if (!wrap_is_usable_file(allow_filename))
434 return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool,
435 cmd->argv[0], ": '", allow_filename, "' must be a usable file", NULL));
436
437 } else if (allow_filename[0] == '~' && allow_filename[1] != '/') {
438 char *allow_real_file = NULL;
439
440 allow_real_file = dir_realpath(cmd->pool, allow_filename);
441
442 if (allow_real_file == NULL || !wrap_is_usable_file(allow_real_file))
443 return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool,
444 cmd->argv[0], ": '", allow_filename, "' must be a usable file", NULL));
445
446 allow_filename = allow_real_file;
447
448 } else if (allow_filename[0] != '~' && allow_filename[0] != '/') {
449
450 /* no relative paths allowed */
451 return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool,
452 cmd->argv[0], ": '", allow_filename, "' must start with \"/\" or \"~\"",
453 NULL));
454
455 } else {
456
457 /* it's a determine-at-login-time filename -- check it later */
458 ;
459 }
460
461 if (deny_filename[0] == '/') {
462
463 /* it's an absolute path, so the filename will be checked as is */
464 if (!wrap_is_usable_file(deny_filename))
465 return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool,
466 cmd->argv[0], ": '", deny_filename, "' must be a usable file", NULL));
467
468 } else if (deny_filename[0] == '~' && deny_filename[1] != '/') {
469 char *deny_real_file = NULL;
470
471 deny_real_file = dir_realpath(cmd->pool, deny_filename);
472
473 if (deny_real_file == NULL || !wrap_is_usable_file(deny_real_file))
474 return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool,
475 cmd->argv[0], ": '", deny_filename, "' must be a usable file", NULL));
476
477 deny_filename = deny_real_file;
478
479 } else if (deny_filename[0] != '~' && deny_filename[0] != '/') {
480
481 /* no relative paths allowed */
482 return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool,
483 cmd->argv[0], ": '", deny_filename, "' must start with \"/\" or \"~\"",
484 NULL));
485
486 } else {
487
488 /* it's a determine-at-login-time filename -- check it later */
489 ;
490 }
491
492 c = add_config_param(cmd->argv[0], 0);
493
494 expr = (char *) cmd->argv[0];
495 group_acl = pr_expr_create(cmd->tmp_pool, &group_argc, &expr);
496
497 /* build the desired config_rec manually */
498 c->argc = group_argc + 2;
499 c->argv = pcalloc(c->pool, (group_argc + 3) * sizeof(char *));
500 group_argv = (char **) c->argv;
501
502 /* the access files are the first two arguments */
503 *group_argv++ = pstrdup(c->pool, allow_filename);
504 *group_argv++ = pstrdup(c->pool, deny_filename);
505
506 /* and the group names follow */
507 if (group_argc && group_acl)
508 while (group_argc--) {
509 *group_argv++ = pstrdup(c->pool, *((char **) group_acl->elts));
510 group_acl->elts = ((char **) group_acl->elts) + 1;
511 }
512
513 /* don't forget to NULL-terminate */
514 *group_argv = NULL;
515
516 c->flags |= CF_MERGEDOWN;
517
518 /* done */
519 return PR_HANDLED(cmd);
520 }
521
set_tcpuseraccessfiles(cmd_rec * cmd)522 MODRET set_tcpuseraccessfiles(cmd_rec *cmd) {
523 unsigned int user_argc = 1;
524 char *expr, **user_argv = NULL;
525 array_header *user_acl = NULL;
526 config_rec *c = NULL;
527
528 /* assume use of the standard TCP wrappers installation locations */
529 char *allow_filename = NULL, *deny_filename = NULL;
530
531 CHECK_ARGS(cmd, 3);
532 CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
533
534 /* use the user-given files, checking to make sure that they exist and
535 * are readable.
536 */
537 allow_filename = cmd->argv[2];
538 deny_filename = cmd->argv[3];
539
540 /* if the filenames begin with a '~', AND this is not immediately followed
541 * by a '/' (ie '~/'), expand it out for checking and storing for later
542 * lookups. If the filenames DO begin with '~/', do the expansion later,
543 * after authenication. In other words, do checking of static filenames
544 * now, and checking of dynamic (user-authentication-based) filenames
545 * later.
546 */
547 if (allow_filename[0] == '/') {
548
549 /* it's an absolute path, so the filename will be checked as is */
550 if (!wrap_is_usable_file(allow_filename))
551 return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool,
552 cmd->argv[0], ": '", allow_filename, "' must be a usable file", NULL));
553
554 } else if (allow_filename[0] == '~' && allow_filename[1] != '/') {
555 char *allow_real_file = NULL;
556
557 allow_real_file = dir_realpath(cmd->pool, allow_filename);
558
559 if (allow_real_file == NULL || !wrap_is_usable_file(allow_real_file))
560 return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool,
561 cmd->argv[0], ": '", allow_filename, "' must be a usable file", NULL));
562
563 allow_filename = allow_real_file;
564
565 } else if (allow_filename[0] != '~' && allow_filename[0] != '/') {
566
567 /* no relative paths allowed */
568 return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool,
569 cmd->argv[0], ": '", allow_filename, "' must start with \"/\" or \"~\"",
570 NULL));
571
572 } else {
573
574 /* it's a determine-at-login-time filename -- check it later */
575 ;
576 }
577
578 if (deny_filename[0] == '/') {
579
580 /* it's an absolute path, so the filename will be checked as is */
581 if (!wrap_is_usable_file(deny_filename))
582 return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool,
583 cmd->argv[0], ": '", deny_filename, "' must be a usable file", NULL));
584
585 } else if (deny_filename[0] == '~' && deny_filename[1] != '/') {
586 char *deny_real_file = NULL;
587
588 deny_real_file = dir_realpath(cmd->pool, deny_filename);
589
590 if (deny_real_file == NULL || !wrap_is_usable_file(deny_real_file))
591 return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool,
592 cmd->argv[0], ": '", deny_filename, "' must be a usable file", NULL));
593
594 deny_filename = deny_real_file;
595
596 } else if (deny_filename[0] != '~' && deny_filename[0] != '/') {
597
598 /* no relative paths allowed */
599 return PR_ERROR_MSG(cmd, NULL, pstrcat(cmd->tmp_pool,
600 cmd->argv[0], ": '", deny_filename, "' must start with \"/\" or \"~\"",
601 NULL));
602
603 } else {
604
605 /* it's a determine-at-login-time filename -- check it later */
606 ;
607 }
608
609 c = add_config_param_str(cmd->argv[0], 0);
610
611 expr = (char *) cmd->argv[0];
612 user_acl = pr_expr_create(cmd->tmp_pool, &user_argc, &expr);
613
614 /* build the desired config_rec manually */
615 c->argc = user_argc + 2;
616 c->argv = pcalloc(c->pool, (user_argc + 3) * sizeof(char *));
617 user_argv = (char **) c->argv;
618
619 /* the access files are the first two arguments */
620 *user_argv++ = pstrdup(c->pool, allow_filename);
621 *user_argv++ = pstrdup(c->pool, deny_filename);
622
623 /* and the user names follow */
624 if (user_argc && user_acl)
625 while (user_argc--) {
626 *user_argv++ = pstrdup(c->pool, *((char **) user_acl->elts));
627 user_acl->elts = ((char **) user_acl->elts) + 1;
628 }
629
630 /* don't forget to NULL-terminate */
631 *user_argv = NULL;
632
633 c->flags |= CF_MERGEDOWN;
634
635 /* done */
636 return PR_HANDLED(cmd);
637 }
638
639 /* This function was copied, almost verbatim, from the set_sysloglevel()
640 * function in modules/mod_core.c. I hereby cite the source for this code
641 * as MacGuyver <macguyver@tos.net>. =)
642 */
set_tcpaccesssysloglevels(cmd_rec * cmd)643 MODRET set_tcpaccesssysloglevels(cmd_rec *cmd) {
644 config_rec *c = NULL;
645 int allow_level = PR_LOG_DEBUG, deny_level = PR_LOG_DEBUG;
646
647 CHECK_ARGS(cmd, 2);
648 CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_ANON|CONF_GLOBAL);
649
650 if (strcasecmp(cmd->argv[1], "emerg") == 0) {
651 allow_level = PR_LOG_EMERG;
652
653 } else if (strcasecmp(cmd->argv[1], "alert") == 0) {
654 allow_level = PR_LOG_ALERT;
655
656 } else if (strcasecmp(cmd->argv[1], "crit") == 0) {
657 allow_level = PR_LOG_CRIT;
658
659 } else if (strcasecmp(cmd->argv[1], "error") == 0) {
660 allow_level = PR_LOG_ERR;
661
662 } else if (strcasecmp(cmd->argv[1], "warn") == 0) {
663 allow_level = PR_LOG_WARNING;
664
665 } else if (strcasecmp(cmd->argv[1], "notice") == 0) {
666 allow_level = PR_LOG_NOTICE;
667
668 } else if (strcasecmp(cmd->argv[1], "info") == 0) {
669 allow_level = PR_LOG_INFO;
670
671 } else if (strcasecmp(cmd->argv[1], "debug") == 0) {
672 allow_level = PR_LOG_DEBUG;
673
674 } else {
675 CONF_ERROR(cmd, "TCPAccessSyslogLevels requires \"allow\" level keyword: "
676 "one of emerg/alert/crit/error/warn/notice/info/debug");
677 }
678
679 if (strcasecmp(cmd->argv[2], "emerg") == 0) {
680 deny_level = PR_LOG_EMERG;
681
682 } else if (strcasecmp(cmd->argv[2], "alert") == 0) {
683 deny_level = PR_LOG_ALERT;
684
685 } else if (strcasecmp(cmd->argv[2], "crit") == 0) {
686 deny_level = PR_LOG_CRIT;
687
688 } else if (strcasecmp(cmd->argv[2], "error") == 0) {
689 deny_level = PR_LOG_ERR;
690
691 } else if (strcasecmp(cmd->argv[2], "warn") == 0) {
692 deny_level = PR_LOG_WARNING;
693
694 } else if (strcasecmp(cmd->argv[2], "notice") == 0) {
695 deny_level = PR_LOG_NOTICE;
696
697 } else if (strcasecmp(cmd->argv[2], "info") == 0) {
698 deny_level = PR_LOG_INFO;
699
700 } else if (strcasecmp(cmd->argv[2], "debug") == 0 ) {
701 deny_level = PR_LOG_DEBUG;
702
703 } else {
704 CONF_ERROR(cmd, "TCPAccessSyslogLevels requires \"deny\" level keyword: "
705 "one of emerg/alert/crit/error/warn/notice/info/debug");
706 }
707
708 c = add_config_param(cmd->argv[0], 2, NULL, NULL);
709 c->argv[0] = palloc(c->pool, sizeof(int));
710 *((int *) c->argv[0]) = allow_level;
711 c->argv[1] = palloc(c->pool, sizeof(int));
712 *((int *) c->argv[1]) = deny_level;
713
714 c->flags |= CF_MERGEDOWN;
715
716 return PR_HANDLED(cmd);
717 }
718
719 /* usage: TCPServiceName <name> */
set_tcpservicename(cmd_rec * cmd)720 MODRET set_tcpservicename(cmd_rec *cmd) {
721 CHECK_ARGS(cmd, 1);
722 CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
723
724 add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
725 return PR_HANDLED(cmd);
726 }
727
728 /* Command handlers
729 */
730
wrap_handle_request(cmd_rec * cmd)731 MODRET wrap_handle_request(cmd_rec *cmd) {
732
733 /* these variables are names expected to be set by the TCP wrapper code
734 */
735 struct request_info request;
736
737 const char *user = NULL;
738 config_rec *conf = NULL, *access_conf = NULL, *syslog_conf = NULL;
739 hosts_allow_table = NULL;
740 hosts_deny_table = NULL;
741
742 /* hide passwords */
743 session.hide_password = TRUE;
744
745 /* Sneaky...found in mod_auth.c's cmd_pass() function. Need to find the
746 * login UID in order to resolve the possibly-login-dependent filename.
747 */
748 user = pr_table_get(session.notes, "mod_auth.orig-user", NULL);
749
750 /* It's possible that a PASS command came before USER. This is a PRE_CMD
751 * handler, so it won't be protected from this case; we'll need to do
752 * it manually.
753 */
754 if (user == NULL) {
755 return PR_DECLINED(cmd);
756 }
757
758 /* Use mod_auth's _auth_resolve_user() [imported for use here] to get the
759 * right configuration set, since the user may be logging in anonymously,
760 * and the session struct hasn't yet been set for that yet (thus short-
761 * circuiting the easiest way to get the right context...the macros.
762 */
763 conf = wrap_resolve_user(cmd->pool, &user);
764
765 /* Search first for user-specific access files. Multiple TCPUserAccessFiles
766 * directives are allowed.
767 */
768 if ((access_conf = find_config(conf ? conf->subset : CURRENT_CONF, CONF_PARAM,
769 "TCPUserAccessFiles", FALSE)) != NULL) {
770 int matched = FALSE;
771 array_header *user_array = NULL;
772
773 while (access_conf) {
774
775 user_array = make_array(cmd->tmp_pool, 0, sizeof(char *));
776 *((char **) push_array(user_array)) = pstrdup(cmd->tmp_pool, user);
777
778 /* Check the user expression -- don't forget the offset, to skip
779 * the access file name strings in argv
780 */
781 if (wrap_eval_expression(((char **) access_conf->argv) + 2,
782 user_array)) {
783 pr_log_debug(DEBUG4, MOD_WRAP_VERSION
784 ": matched TCPUserAccessFiles expression");
785 matched = TRUE;
786 break;
787 }
788
789 access_conf = find_config_next(access_conf, access_conf->next,
790 CONF_PARAM, "TCPUserAccessFiles", FALSE);
791 }
792
793 if (!matched)
794 access_conf = NULL;
795 }
796
797 /* Next, search for group-specific access files. Multiple
798 * TCPGroupAccessFiles directives are allowed.
799 */
800 if (!access_conf && (access_conf = find_config(conf ? conf->subset :
801 CURRENT_CONF, CONF_PARAM, "TCPGroupAccessFiles", FALSE)) != NULL) {
802 unsigned char matched = FALSE;
803
804 /* NOTE: this gid_array is only necessary until Bug#1461 is fixed */
805 array_header *gid_array = make_array(cmd->pool, 0, sizeof(gid_t));
806
807 array_header *group_array = make_array(cmd->pool, 0, sizeof(char *));
808
809 while (access_conf) {
810 if (pr_auth_getgroups(cmd->pool, user, &gid_array, &group_array) < 1) {
811 pr_log_debug(DEBUG3, MOD_WRAP_VERSION
812 ": no supplemental groups found for user '%s'", user);
813
814 } else {
815
816 /* Check the group expression -- don't forget the offset, to skip
817 * the access file names strings in argv
818 */
819 if (wrap_eval_expression(((char **) access_conf->argv) + 2,
820 group_array)) {
821 pr_log_debug(DEBUG4, MOD_WRAP_VERSION
822 ": matched TCPGroupAccessFiles expression");
823 matched = TRUE;
824 break;
825 }
826 }
827
828 access_conf = find_config_next(access_conf, access_conf->next,
829 CONF_PARAM, "TCPGroupAccessFiles", FALSE);
830 }
831
832 if (!matched)
833 access_conf = NULL;
834 }
835
836 /* Finally for globally-applicable access files. Only one such directive
837 * is allowed.
838 */
839 if (!access_conf) {
840 access_conf = find_config(conf ? conf->subset : CURRENT_CONF,
841 CONF_PARAM, "TCPAccessFiles", FALSE);
842 }
843
844 if (access_conf) {
845 hosts_allow_table = (char *) access_conf->argv[0];
846 hosts_deny_table = (char *) access_conf->argv[1];
847 }
848
849 /* Now, check the retrieved filename, and see if it requires a login-time
850 * file.
851 */
852 if (hosts_allow_table != NULL && hosts_allow_table[0] == '~' &&
853 hosts_allow_table[1] == '/') {
854 const char *allow_real_table = NULL;
855
856 allow_real_table = wrap_get_user_table(cmd, user, hosts_allow_table);
857
858 if (!wrap_is_usable_file(allow_real_table)) {
859 pr_log_pri(PR_LOG_WARNING, MOD_WRAP_VERSION
860 ": configured TCPAllowFile %s is unusable", hosts_allow_table);
861 hosts_allow_table = NULL;
862
863 } else
864 hosts_allow_table = (char *) allow_real_table;
865 }
866
867 if (hosts_deny_table != NULL && hosts_deny_table[0] == '~' &&
868 hosts_deny_table[1] == '/') {
869 char *deny_real_table = NULL;
870
871 deny_real_table = dir_realpath(cmd->pool, hosts_deny_table);
872
873 if (!wrap_is_usable_file(deny_real_table)) {
874 pr_log_pri(PR_LOG_WARNING, MOD_WRAP_VERSION
875 ": configured TCPDenyFile %s is unusable", hosts_deny_table);
876 hosts_deny_table = NULL;
877
878 } else
879 hosts_deny_table = deny_real_table;
880 }
881
882 /* Make sure that _both_ allow and deny TCPAccessFiles are present.
883 * If not, log the missing file, and by default allow request to succeed.
884 */
885 if (hosts_allow_table != NULL && hosts_deny_table != NULL) {
886
887 /* Most common case...nothing more necessary */
888
889 } else if (hosts_allow_table == NULL && hosts_deny_table != NULL) {
890
891 /* Log the missing file */
892 pr_log_pri(PR_LOG_INFO, MOD_WRAP_VERSION ": no usable allow access file -- "
893 "allowing connection");
894
895 return PR_DECLINED(cmd);
896
897 } else if (hosts_allow_table != NULL && hosts_deny_table == NULL) {
898
899 /* log the missing file */
900 pr_log_pri(PR_LOG_INFO, MOD_WRAP_VERSION ": no usable deny access file -- "
901 "allowing connection");
902
903 return PR_DECLINED(cmd);
904
905 } else {
906
907 /* Neither set -- assume the admin hasn't configured these directives
908 * at all.
909 */
910 return PR_DECLINED(cmd);
911 }
912
913 /* Log the names of the allow/deny files being used. */
914 pr_log_pri(PR_LOG_DEBUG, MOD_WRAP_VERSION ": using access files: %s, %s",
915 hosts_allow_table, hosts_deny_table);
916
917 /* retrieve the user-defined syslog priorities, if any. Fall back to the
918 * defaults as seen in tcpd.h if not defined.
919 */
920 syslog_conf = find_config(main_server->conf, CONF_PARAM,
921 "TCPAccessSyslogLevels", FALSE);
922
923 if (syslog_conf) {
924 allow_severity = *((int *) syslog_conf->argv[0]);
925 deny_severity = *((int *) syslog_conf->argv[1]);
926
927 } else {
928 allow_severity = PR_LOG_INFO;
929 deny_severity = PR_LOG_WARNING;
930 }
931
932 /* While it may look odd to OR together the syslog facility and level,
933 * that is the way that syslog(3) says to do it:
934 *
935 * "The priority argument is formed by ORing the facility and the level
936 * values..."
937 *
938 * Note that we do this OR here because the allow_severity/deny_severity
939 * values are ALSO used by the libwrap library; it is also why we need
940 * to mask off some bits later, when using proftpd's logging functions.
941 */
942 allow_severity = log_getfacility() | allow_severity;
943 deny_severity = log_getfacility() | deny_severity;
944
945 pr_log_debug(DEBUG4, MOD_WRAP_VERSION ": checking under service name '%s'",
946 wrap_service_name);
947 request_init(&request, RQ_DAEMON, wrap_service_name, RQ_FILE,
948 session.c->rfd, 0);
949
950 fromhost(&request);
951
952 if (STR_EQ(eval_hostname(request.client), paranoid) ||
953 !hosts_access(&request)) {
954 char *denymsg = NULL;
955
956 /* log the denied connection */
957 wrap_log_request_denied(deny_severity, &request);
958
959 /* Broadcast this event to any interested listeners. */
960 pr_event_generate("mod_wrap.connection-denied", NULL);
961
962 /* check for AccessDenyMsg */
963 denymsg = (char *) get_param_ptr(TOPLEVEL_CONF, "AccessDenyMsg", FALSE);
964 if (denymsg != NULL) {
965 denymsg = (char *) sreplace(cmd->tmp_pool, denymsg, "%u", user, NULL);
966 }
967
968 if (denymsg != NULL) {
969 return PR_ERROR_MSG(cmd, R_530, denymsg);
970 }
971
972 return PR_ERROR_MSG(cmd, R_530, _("Access denied"));
973 }
974
975 /* If request is allowable, return DECLINED (for engine to act as if this
976 * handler was never called, else ERROR (for engine to abort processing and
977 * deny request.
978 */
979 wrap_log_request_allowed(allow_severity, &request);
980
981 return PR_DECLINED(cmd);
982 }
983
984 /* Event listeners
985 */
986
wrap_sess_reinit_ev(const void * event_data,void * user_data)987 static void wrap_sess_reinit_ev(const void *event_data, void *user_data) {
988 int res;
989
990 /* A HOST command changed the main_server pointer; reinitialize ourselves. */
991
992 pr_event_unregister(&wrap_module, "core.session-reinit", wrap_sess_reinit_ev);
993
994 /* Reset defaults */
995 wrap_service_name = "proftpd";
996
997 res = wrap_sess_init();
998 if (res < 0) {
999 pr_session_disconnect(&wrap_module, PR_SESS_DISCONNECT_SESSION_INIT_FAILED,
1000 NULL);
1001 }
1002 }
1003
1004 /* Initialization routines
1005 */
1006
wrap_sess_init(void)1007 static int wrap_sess_init(void) {
1008 pr_event_register(&wrap_module, "core.session-reinit", wrap_sess_reinit_ev,
1009 NULL);
1010
1011 /* look up any configured TCPServiceName */
1012 wrap_service_name = get_param_ptr(main_server->conf, "TCPServiceName", FALSE);
1013 if (wrap_service_name == NULL) {
1014 wrap_service_name = "proftpd";
1015 }
1016
1017 return 0;
1018 }
1019
1020 /* Module API tables
1021 */
1022
1023 static conftable wrap_conftab[] = {
1024 { "TCPAccessFiles", set_tcpaccessfiles, NULL },
1025 { "TCPAccessSyslogLevels", set_tcpaccesssysloglevels, NULL },
1026 { "TCPGroupAccessFiles", set_tcpgroupaccessfiles, NULL },
1027 { "TCPServiceName", set_tcpservicename, NULL },
1028 { "TCPUserAccessFiles", set_tcpuseraccessfiles, NULL },
1029 { NULL }
1030 };
1031
1032 static cmdtable wrap_cmdtab[] = {
1033 { PRE_CMD, C_PASS, G_NONE, wrap_handle_request, FALSE, FALSE },
1034 { 0, NULL }
1035 };
1036
1037 module wrap_module = {
1038 NULL, NULL,
1039
1040 /* Module API version 2.0 */
1041 0x20,
1042
1043 /* Module name */
1044 "wrap",
1045
1046 /* Mmodule configuration handler table */
1047 wrap_conftab,
1048
1049 /* Module command handler table */
1050 wrap_cmdtab,
1051
1052 /* Module authentication handler table */
1053 NULL,
1054
1055 /* Module initialization */
1056 NULL,
1057
1058 /* Session initialization */
1059 wrap_sess_init,
1060
1061 /* Module version */
1062 MOD_WRAP_VERSION
1063 };
1064