1 /*
2 * ProFTPD: mod_ifsession -- a module supporting conditional
3 * per-user/group/class configuration contexts.
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 and other respective copyright holders
21 * give permission to link this program with OpenSSL, and distribute the
22 * resulting executable, without including the source code for OpenSSL in the
23 * source distribution.
24 *
25 * This is mod_ifsession, contrib software for proftpd 1.3.x and above.
26 * For more information contact TJ Saunders <tj@castaglia.org>.
27 */
28
29 #include "conf.h"
30
31 #define MOD_IFSESSION_VERSION "mod_ifsession/1.3"
32
33 /* Make sure the version of proftpd is as necessary. */
34 #if PROFTPD_VERSION_NUMBER < 0x0001030402
35 # error "ProFTPD 1.3.4rc2 or later required"
36 #endif
37
38 #define IFSESS_CLASS_NUMBER 100
39 #define IFSESS_CLASS_TEXT "<IfClass>"
40 #define IFSESS_GROUP_NUMBER 101
41 #define IFSESS_GROUP_TEXT "<IfGroup>"
42 #define IFSESS_USER_NUMBER 102
43 #define IFSESS_USER_TEXT "<IfUser>"
44 #define IFSESS_AUTHN_NUMBER 103
45 #define IFSESS_AUTHN_TEXT "<IfAuthenticated>"
46
47 module ifsession_module;
48
49 static int ifsess_ctx = -1;
50 static int ifsess_merged = FALSE;
51
52 /* For storing the home directory of user, symlinks resolved. */
53 static const char *ifsess_home_dir = NULL;
54
55 /* For supporting DisplayLogin files in <IfUser>/<IfGroup> sections. */
56 static pr_fh_t *displaylogin_fh = NULL;
57
58 static int ifsess_sess_init(void);
59
60 static const char *trace_channel = "ifsession";
61
62 /* Necessary prototypes */
63 static void ifsess_resolve_dirs(config_rec *);
64 static void ifsess_resolve_server_dirs(server_rec *);
65
66 /* Support routines
67 */
68
ifsess_remove_param(xaset_t * set,int config_type,const char * name)69 static void ifsess_remove_param(xaset_t *set, int config_type,
70 const char *name) {
71 config_rec *c = NULL;
72 int lookup_type = -1;
73
74 if (config_type == CONF_DIR) {
75 pr_trace_msg(trace_channel, 9, "removing <Directory %s> config", name);
76 lookup_type = CONF_DIR;
77
78 } else {
79 pr_trace_msg(trace_channel, 9, "removing '%s' config", name);
80 }
81
82 c = find_config(set, lookup_type, name, TRUE);
83 while (c != NULL) {
84 xaset_t *fset;
85 xasetmember_t *member;
86
87 pr_signals_handle();
88
89 fset = c->set;
90 member = (xasetmember_t *) c;
91 xaset_remove(fset, member);
92
93 c = find_config(set, lookup_type, name, TRUE);
94 }
95 }
96
ifsess_dup_param(pool * dst_pool,xaset_t ** dst,config_rec * c,config_rec * parent)97 static void ifsess_dup_param(pool *dst_pool, xaset_t **dst, config_rec *c,
98 config_rec *parent) {
99 config_rec *dup_c = NULL;
100
101 if (c->config_type == CONF_DIR) {
102 pr_trace_msg(trace_channel, 9, "adding <Directory %s> config", c->name);
103
104 } else if (c->config_type == CONF_LIMIT) {
105 pr_trace_msg(trace_channel, 9, "adding <Limit> config");
106
107 } else {
108 pr_trace_msg(trace_channel, 9, "adding '%s' config", c->name);
109 }
110
111 if (!*dst) {
112 *dst = xaset_create(dst_pool, NULL);
113 }
114
115 dup_c = pr_config_add_set(dst, c->name, PR_CONFIG_FL_INSERT_HEAD);
116 dup_c->config_type = c->config_type;
117 dup_c->flags = c->flags;
118 dup_c->parent = parent;
119 dup_c->argc = c->argc;
120
121 if (c->argc) {
122 void **dst_argv = NULL, **src_argv = NULL;
123 int dst_argc;
124
125 dup_c->argv = pcalloc(dup_c->pool, (c->argc + 1) * sizeof(void *));
126
127 src_argv = c->argv;
128 dst_argv = dup_c->argv;
129 dst_argc = dup_c->argc;
130
131 while (dst_argc--) {
132 *dst_argv++ = *src_argv++;
133 }
134
135 if (dst_argv) {
136 *dst_argv++ = NULL;
137 }
138 }
139
140 if (c->subset) {
141 for (c = (config_rec *) c->subset->xas_list; c; c = c->next) {
142
143 /* If this directive does not allow multiple instances, make sure
144 * it is removed from the destination set first. The "source"
145 * directive then effectively replaces any directive there.
146 *
147 * Note that we only want to do this IF the config is NOT part of
148 * of a <Limit> section.
149 */
150 if (c->parent->config_type != CONF_LIMIT &&
151 c->config_type == CONF_PARAM &&
152 !(c->flags & CF_MERGEDOWN_MULTI) &&
153 !(c->flags & CF_MULTI)) {
154 pr_trace_msg(trace_channel, 15, "removing '%s' config because "
155 "c->flags does not contain MULTI or MERGEDOWN_MULTI", c->name);
156 ifsess_remove_param(dup_c->subset, c->config_type, c->name);
157 }
158
159 ifsess_dup_param(dst_pool, &dup_c->subset, c, dup_c);
160 }
161 }
162 }
163
ifsess_dup_set(pool * dst_pool,xaset_t * dst,xaset_t * src)164 static void ifsess_dup_set(pool *dst_pool, xaset_t *dst, xaset_t *src) {
165 config_rec *c, *next;
166
167 for (c = (config_rec *) src->xas_list; c; c = next) {
168 next = c->next;
169
170 /* Skip the context lists. */
171 if (c->config_type == IFSESS_CLASS_NUMBER ||
172 c->config_type == IFSESS_GROUP_NUMBER ||
173 c->config_type == IFSESS_USER_NUMBER ||
174 c->config_type == IFSESS_AUTHN_NUMBER) {
175 continue;
176 }
177
178 /* If this directive does not allow multiple instances, make sure
179 * it is removed from the destination set first. The "source"
180 * directive then effectively replaces any directive there.
181 *
182 * Note that we only want to do this IF the config is NOT part of
183 * of a <Limit> section.
184 */
185 if (c->parent->config_type != CONF_LIMIT &&
186 c->config_type == CONF_PARAM &&
187 !(c->flags & CF_MERGEDOWN_MULTI) &&
188 !(c->flags & CF_MULTI)) {
189 pr_trace_msg(trace_channel, 15, "removing '%s' config because "
190 "c->flags does not contain MULTI or MERGEDOWN_MULTI", c->name);
191 ifsess_remove_param(dst, c->config_type, c->name);
192 }
193
194 if (c->config_type == CONF_DIR) {
195 pr_trace_msg(trace_channel, 15, "removing old <Directory %s> config "
196 "because new <Directory %s> takes precedence", c->name, c->name);
197 ifsess_remove_param(dst, c->config_type, c->name);
198 }
199
200 ifsess_dup_param(dst_pool, &dst, c, NULL);
201 }
202 }
203
204 /* Similar to dir_interpolate(), except that we are cognizant of being
205 * chrooted, and so try to Do The Right Thing(tm).
206 */
ifsess_dir_interpolate(pool * p,const char * path)207 static char *ifsess_dir_interpolate(pool *p, const char *path) {
208 char *ret = (char *) path;
209
210 if (ret == NULL) {
211 errno = EINVAL;
212 return NULL;
213 }
214
215 if (*ret == '~') {
216 const char *user;
217 char *interp_dir = NULL, *ptr;
218
219 user = pstrdup(p, ret+1);
220 ptr = strchr(user, '/');
221
222 if (ptr != NULL) {
223 *ptr++ = '\0';
224 }
225
226 if (!*user) {
227 user = session.user;
228
229 if (ifsess_home_dir != NULL) {
230 /* We're chrooted; we already know the interpolated path. */
231 interp_dir = (char *) ifsess_home_dir;
232 }
233 }
234
235 if (interp_dir == NULL) {
236 struct passwd *pw;
237 struct stat st;
238 size_t interp_dirlen;
239
240 pw = pr_auth_getpwnam(p, user);
241 if (pw == NULL) {
242 errno = ENOENT;
243 return NULL;
244 }
245
246 if (pw->pw_dir == NULL) {
247 errno = EPERM;
248 return NULL;
249 }
250
251 interp_dir = pstrdup(p, pw->pw_dir);
252 interp_dirlen = strlen(interp_dir);
253
254 /* If the given directory is a symlink, follow it. Note that for
255 * proper handling of such paths, we need to ensure that the path does
256 * not end in a slash.
257 */
258 if (interp_dir[interp_dirlen] == '/') {
259 interp_dir[interp_dirlen--] = '\0';
260 }
261
262 if (pr_fsio_lstat(interp_dir, &st) == 0) {
263 if (S_ISLNK(st.st_mode)) {
264 char link_path[PR_TUNABLE_PATH_MAX+1];
265
266 memset(link_path, '\0', sizeof(link_path));
267 if (pr_fs_resolve_path(interp_dir, link_path, sizeof(link_path)-1,
268 FSIO_DIR_CHDIR) < 0) {
269 return NULL;
270 }
271
272 interp_dir = pstrdup(p, link_path);
273 }
274 }
275 }
276
277 ret = pdircat(p, interp_dir, ptr, NULL);
278 }
279
280 return ret;
281 }
282
283 /* Similar to resolve_deferred_dirs(), except that we need to recurse
284 * and resolve ALL <Directory> paths.
285 */
ifsess_resolve_dir(config_rec * c)286 static void ifsess_resolve_dir(config_rec *c) {
287 char *interp_dir = NULL, *real_dir = NULL, *orig_name = NULL;
288
289 if (pr_trace_get_level(trace_channel) >= 11) {
290 orig_name = pstrdup(c->pool, c->name);
291 }
292
293 /* Check for any expandable variables. */
294 c->name = (char *) path_subst_uservar(c->pool, (const char **) &c->name);
295
296 /* Handle any '~' interpolation. */
297 interp_dir = ifsess_dir_interpolate(c->pool, c->name);
298 if (interp_dir == NULL) {
299 /* This can happen when the '~' is just that, and does not refer
300 * to any known user.
301 */
302 interp_dir = c->name;
303 }
304
305 real_dir = dir_best_path(c->pool, interp_dir);
306 if (real_dir) {
307 c->name = real_dir;
308
309 } else {
310 real_dir = dir_canonical_path(c->pool, interp_dir);
311 if (real_dir) {
312 c->name = real_dir;
313 }
314 }
315
316 pr_trace_msg(trace_channel, 11,
317 "resolved <Directory %s> to <Directory %s>", orig_name, c->name);
318 }
319
ifsess_resolve_dirs(config_rec * c)320 void ifsess_resolve_dirs(config_rec *c) {
321 ifsess_resolve_dir(c);
322
323 if (c->subset != NULL) {
324 config_rec *subc;
325
326 for (subc = (config_rec *) c->subset->xas_list; subc; subc = subc->next) {
327 if (subc->config_type == CONF_DIR) {
328 ifsess_resolve_dirs(subc);
329 }
330 }
331 }
332 }
333
ifsess_resolve_server_dirs(server_rec * s)334 void ifsess_resolve_server_dirs(server_rec *s) {
335 config_rec *c;
336
337 if (s == NULL ||
338 s->conf == NULL) {
339 return;
340 }
341
342 for (c = (config_rec *) s->conf->xas_list; c; c = c->next) {
343 if (c->config_type == CONF_DIR) {
344 ifsess_resolve_dirs(c);
345 }
346 }
347 }
348
ifsess_sess_merge_class(void)349 static int ifsess_sess_merge_class(void) {
350 register unsigned int i = 0;
351 config_rec *c = NULL;
352 pool *tmp_pool = make_sub_pool(session.pool);
353 array_header *class_remove_list = make_array(tmp_pool, 1,
354 sizeof(config_rec *));
355
356 c = find_config(main_server->conf, -1, IFSESS_CLASS_TEXT, FALSE);
357 while (c != NULL) {
358 config_rec *list = NULL;
359
360 pr_signals_handle();
361
362 list = find_config(c->subset, IFSESS_CLASS_NUMBER, NULL, FALSE);
363 if (list != NULL) {
364 unsigned char mergein = FALSE;
365
366 #ifdef PR_USE_REGEX
367 if (*((unsigned char *) list->argv[1]) == PR_EXPR_EVAL_REGEX) {
368 pr_regex_t *pre = list->argv[2];
369
370 if (session.conn_class != NULL) {
371 pr_log_debug(DEBUG8, MOD_IFSESSION_VERSION
372 ": evaluating regexp pattern '%s' against subject '%s'",
373 pr_regexp_get_pattern(pre), session.conn_class->cls_name);
374
375 if (pr_regexp_exec(pre, session.conn_class->cls_name, 0, NULL, 0, 0,
376 0) == 0) {
377 mergein = TRUE;
378 }
379 }
380
381 } else
382 #endif /* regex support */
383
384 if (*((unsigned char *) list->argv[1]) == PR_EXPR_EVAL_OR &&
385 pr_expr_eval_class_or((char **) &list->argv[2]) == TRUE) {
386 mergein = TRUE;
387
388 } else if (*((unsigned char *) list->argv[1]) == PR_EXPR_EVAL_AND &&
389 pr_expr_eval_class_and((char **) &list->argv[2]) == TRUE) {
390 mergein = TRUE;
391 }
392
393 if (mergein) {
394 pr_log_debug(DEBUG2, MOD_IFSESSION_VERSION
395 ": merging <IfClass %s> directives in", (char *) list->argv[0]);
396 ifsess_dup_set(session.pool, main_server->conf, c->subset);
397
398 /* Add this config_rec pointer to the list of pointers to be
399 * removed later.
400 */
401 *((config_rec **) push_array(class_remove_list)) = c;
402
403 /* Do NOT call fixup_dirs() here; we need to wait until after
404 * authentication to do so (in which case, mod_auth will handle the
405 * call to fixup_dirs() for us).
406 */
407
408 ifsess_merged = TRUE;
409
410 } else {
411 pr_log_debug(DEBUG9, MOD_IFSESSION_VERSION
412 ": <IfClass %s> not matched, skipping", (char *) list->argv[0]);
413 }
414 }
415
416 c = find_config_next(c, c->next, -1, IFSESS_CLASS_TEXT, FALSE);
417 }
418
419 /* Now, remove any <IfClass> config_recs that have been merged in. */
420 for (i = 0; i < class_remove_list->nelts; i++) {
421 c = ((config_rec **) class_remove_list->elts)[i];
422 xaset_remove(main_server->conf, (xasetmember_t *) c);
423 }
424
425 destroy_pool(tmp_pool);
426 return 0;
427 }
428
429 /* Configuration handlers
430 */
431
start_ifctxt(cmd_rec * cmd)432 MODRET start_ifctxt(cmd_rec *cmd) {
433 config_rec *c = NULL;
434 int config_type = 0, eval_type = 0;
435 unsigned int argc = 0;
436 char *name = NULL;
437 void **argv = NULL;
438 array_header *acl = NULL;
439
440 CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
441
442 c = pr_parser_config_ctxt_open(cmd->argv[0]);
443
444 /* "Inherit" the parent's context type. */
445 c->config_type = (cmd->config && cmd->config->config_type != CONF_PARAM ?
446 cmd->config->config_type : cmd->server->config_type ?
447 cmd->server->config_type : CONF_ROOT);
448
449 if (strcmp(cmd->argv[0], IFSESS_CLASS_TEXT) == 0) {
450 name = "_IfClassList";
451 ifsess_ctx = config_type = IFSESS_CLASS_NUMBER;
452 eval_type = PR_EXPR_EVAL_OR;
453
454 if (cmd->argc-1 < 1) {
455 CONF_ERROR(cmd, "wrong number of parameters");
456 }
457
458 } else if (strcmp(cmd->argv[0], IFSESS_GROUP_TEXT) == 0) {
459 name = "_IfGroupList";
460 ifsess_ctx = config_type = IFSESS_GROUP_NUMBER;
461 eval_type = PR_EXPR_EVAL_AND;
462
463 if (cmd->argc-1 < 1) {
464 CONF_ERROR(cmd, "wrong number of parameters");
465 }
466
467 } else if (strcmp(cmd->argv[0], IFSESS_USER_TEXT) == 0) {
468 name = "_IfUserList";
469 ifsess_ctx = config_type = IFSESS_USER_NUMBER;
470 eval_type = PR_EXPR_EVAL_OR;
471
472 if (cmd->argc-1 < 1) {
473 CONF_ERROR(cmd, "wrong number of parameters");
474 }
475
476 } else if (strcmp(cmd->argv[0], IFSESS_AUTHN_TEXT) == 0) {
477 name = "_IfAuthenticatedList";
478 ifsess_ctx = config_type = IFSESS_AUTHN_NUMBER;
479 eval_type = PR_EXPR_EVAL_OR;
480
481 /* <IfAuthenticated> sections don't take any parameters. */
482 if (cmd->argc > 1) {
483 CONF_ERROR(cmd, "wrong number of parameters");
484 }
485 }
486
487 /* Is this a normal expression, an explicit AND, an explicit OR, or a
488 * regular expression?
489 */
490 if (cmd->argc-1 > 1) {
491 if (strncmp(cmd->argv[1], "AND", 4) == 0) {
492 eval_type = PR_EXPR_EVAL_AND;
493 argc = cmd->argc-2;
494 argv = cmd->argv+1;
495
496 } else if (strncmp(cmd->argv[1], "OR", 3) == 0) {
497 eval_type = PR_EXPR_EVAL_OR;
498 argc = cmd->argc-2;
499 argv = cmd->argv+1;
500
501 } else if (strncmp(cmd->argv[1], "regex", 6) == 0) {
502 #ifdef PR_USE_REGEX
503 pr_regex_t *pre = NULL;
504 int res = 0;
505
506 if (cmd->argc != 3)
507 CONF_ERROR(cmd, "wrong number of parameters");
508
509 pre = pr_regexp_alloc(&ifsession_module);
510
511 res = pr_regexp_compile(pre, cmd->argv[2], REG_EXTENDED|REG_NOSUB);
512 if (res != 0) {
513 char errstr[200] = {'\0'};
514
515 pr_regexp_error(res, pre, errstr, sizeof(errstr));
516 pr_regexp_free(NULL, pre);
517
518 CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": '", cmd->argv[2], "' failed "
519 "regex compilation: ", errstr, NULL));
520 }
521
522 eval_type = PR_EXPR_EVAL_REGEX;
523
524 c = add_config_param(name, 3, NULL, NULL, NULL);
525 c->config_type = config_type;
526 c->argv[0] = pstrdup(c->pool, cmd->arg);
527 c->argv[1] = pcalloc(c->pool, sizeof(unsigned char));
528 *((unsigned char *) c->argv[1]) = eval_type;
529 c->argv[2] = (void *) pre;
530
531 return PR_HANDLED(cmd);
532
533 #else
534 CONF_ERROR(cmd, "The 'regex' parameter cannot be used on this system, "
535 "as you do not have POSIX compliant regex support");
536 #endif /* regex support */
537
538 } else {
539 argc = cmd->argc-1;
540 argv = cmd->argv;
541 }
542
543 } else {
544 argc = cmd->argc-1;
545 argv = cmd->argv;
546 }
547
548 acl = pr_expr_create(cmd->tmp_pool, &argc, (char **) argv);
549 if (acl == NULL) {
550 CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error creating regex expression: ",
551 strerror(errno), NULL));
552 }
553
554 c = add_config_param(name, 0);
555
556 c->config_type = config_type;
557 c->argc = acl->nelts + 2;
558 c->argv = pcalloc(c->pool, (c->argc + 2) * sizeof(void *));
559 c->argv[0] = pstrdup(c->pool, cmd->arg);
560 c->argv[1] = pcalloc(c->pool, sizeof(unsigned char));
561 *((unsigned char *) c->argv[1]) = eval_type;
562
563 argv = c->argv + 2;
564
565 if (acl) {
566 while (acl->nelts--) {
567 *argv++ = pstrdup(c->pool, *((char **) acl->elts));
568 acl->elts = ((char **) acl->elts) + 1;
569 }
570 }
571
572 *argv = NULL;
573 return PR_HANDLED(cmd);
574 }
575
end_ifctxt(cmd_rec * cmd)576 MODRET end_ifctxt(cmd_rec *cmd) {
577 pr_parser_config_ctxt_close(NULL);
578
579 switch (ifsess_ctx) {
580 case IFSESS_CLASS_NUMBER:
581 if (strcasecmp("</IfClass>", cmd->argv[0]) == 0) {
582 ifsess_ctx = -1;
583 }
584 break;
585
586 case IFSESS_GROUP_NUMBER:
587 if (strcasecmp("</IfGroup>", cmd->argv[0]) == 0) {
588 ifsess_ctx = -1;
589 }
590 break;
591
592 case IFSESS_USER_NUMBER:
593 if (strcasecmp("</IfUser>", cmd->argv[0]) == 0) {
594 ifsess_ctx = -1;
595 }
596 break;
597
598 case IFSESS_AUTHN_NUMBER:
599 if (strcasecmp("</IfAuthenticated>", cmd->argv[0]) == 0) {
600 ifsess_ctx = -1;
601 }
602 break;
603 }
604
605 return PR_HANDLED(cmd);
606 }
607
608 /* Command handlers
609 */
610
ifsess_pre_pass(cmd_rec * cmd)611 MODRET ifsess_pre_pass(cmd_rec *cmd) {
612 config_rec *c;
613 const char *user = NULL, *group = NULL, *sess_user, *sess_group;
614 char *displaylogin = NULL;
615 array_header *gids = NULL, *groups = NULL, *sess_groups = NULL;
616 struct passwd *pw = NULL;
617 struct group *gr = NULL;
618 xaset_t *config_set = NULL;
619
620 /* Look for a DisplayLogin file which has an absolute path. If we find one,
621 * open a filehandle, such that that file can be displayed even if the
622 * session is chrooted. DisplayLogin files with relative paths will be
623 * handled after chroot, preserving the old behavior.
624 */
625
626 user = pr_table_get(session.notes, "mod_auth.orig-user", NULL);
627 if (user == NULL) {
628 return PR_DECLINED(cmd);
629 }
630
631 pw = pr_auth_getpwnam(cmd->tmp_pool, user);
632 if (pw == NULL) {
633 pr_trace_msg(trace_channel, 9,
634 "unable to lookup user '%s' (%s), skipping pre-PASS handling",
635 user, strerror(errno));
636 return PR_DECLINED(cmd);
637 }
638
639 gr = pr_auth_getgrgid(cmd->tmp_pool, pw->pw_gid);
640 if (gr != NULL) {
641 group = gr->gr_name;
642 }
643
644 (void) pr_auth_getgroups(cmd->tmp_pool, user, &gids, &groups);
645
646 /* Temporarily set session.user, session.group, session.groups, for the
647 * sake of the pr_eval_*() function calls.
648 */
649 sess_user = session.user;
650 sess_group = session.group;
651 sess_groups = session.groups;
652
653 session.user = user;
654 session.group = group;
655 session.groups = groups;
656
657 c = find_config(main_server->conf, -1, IFSESS_GROUP_TEXT, FALSE);
658 while (c) {
659 config_rec *list = NULL;
660
661 pr_signals_handle();
662
663 list = find_config(c->subset, IFSESS_GROUP_NUMBER, NULL, FALSE);
664 if (list != NULL) {
665 #ifdef PR_USE_REGEX
666 if (*((unsigned char *) list->argv[1]) == PR_EXPR_EVAL_REGEX) {
667 pr_regex_t *pre = list->argv[2];
668
669 if (session.group != NULL) {
670 if (pr_regexp_exec(pre, session.group, 0, NULL, 0, 0, 0) == 0) {
671 displaylogin = get_param_ptr(c->subset, "DisplayLogin", FALSE);
672 if (displaylogin != NULL) {
673 if (*displaylogin == '/') {
674 config_set = c->subset;
675 }
676 }
677 }
678 }
679
680 if (displaylogin == NULL &&
681 session.groups != NULL) {
682 register int j = 0;
683
684 for (j = session.groups->nelts-1; j >= 0; j--) {
685 char *suppl_group;
686
687 suppl_group = *(((char **) session.groups->elts) + j);
688
689 if (pr_regexp_exec(pre, suppl_group, 0, NULL, 0, 0, 0) == 0) {
690 displaylogin = get_param_ptr(c->subset, "DisplayLogin", FALSE);
691 if (displaylogin != NULL) {
692 if (*displaylogin == '/') {
693 config_set = c->subset;
694 }
695 }
696
697 break;
698 }
699 }
700 }
701
702 } else
703 #endif /* regex support */
704
705 if (*((unsigned char *) list->argv[1]) == PR_EXPR_EVAL_OR &&
706 pr_expr_eval_group_or((char **) &list->argv[2]) == TRUE) {
707 displaylogin = get_param_ptr(c->subset, "DisplayLogin", FALSE);
708 if (displaylogin != NULL) {
709 if (*displaylogin == '/') {
710 config_set = c->subset;
711 }
712 }
713
714 } else if (*((unsigned char *) list->argv[1]) == PR_EXPR_EVAL_AND &&
715 pr_expr_eval_group_and((char **) &list->argv[2]) == TRUE) {
716
717 displaylogin = get_param_ptr(c->subset, "DisplayLogin", FALSE);
718 if (displaylogin != NULL) {
719 if (*displaylogin == '/') {
720 config_set = c->subset;
721 }
722 }
723 }
724 }
725
726 c = find_config_next(c, c->next, -1, IFSESS_GROUP_TEXT, FALSE);
727 }
728
729 c = find_config(main_server->conf, -1, IFSESS_USER_TEXT, FALSE);
730 while (c) {
731 config_rec *list = NULL;
732
733 pr_signals_handle();
734
735 list = find_config(c->subset, IFSESS_USER_NUMBER, NULL, FALSE);
736 if (list != NULL) {
737 #ifdef PR_USE_REGEX
738 if (*((unsigned char *) list->argv[1]) == PR_EXPR_EVAL_REGEX) {
739 pr_regex_t *pre = list->argv[2];
740
741 if (pr_regexp_exec(pre, session.user, 0, NULL, 0, 0, 0) == 0) {
742 displaylogin = get_param_ptr(c->subset, "DisplayLogin", FALSE);
743 if (displaylogin != NULL) {
744 if (*displaylogin == '/') {
745 config_set = c->subset;
746 }
747 }
748 }
749
750 } else
751 #endif /* regex support */
752
753 if (*((unsigned char *) list->argv[1]) == PR_EXPR_EVAL_OR &&
754 pr_expr_eval_user_or((char **) &list->argv[2]) == TRUE) {
755 displaylogin = get_param_ptr(c->subset, "DisplayLogin", FALSE);
756 if (displaylogin != NULL) {
757 if (*displaylogin == '/') {
758 config_set = c->subset;
759 }
760 }
761
762 } else if (*((unsigned char *) list->argv[1]) == PR_EXPR_EVAL_AND &&
763 pr_expr_eval_user_and((char **) &list->argv[2]) == TRUE) {
764 displaylogin = get_param_ptr(c->subset, "DisplayLogin", FALSE);
765 if (displaylogin != NULL) {
766 if (*displaylogin == '/') {
767 config_set = c->subset;
768 }
769 }
770 }
771 }
772
773 c = find_config_next(c, c->next, -1, IFSESS_USER_TEXT, FALSE);
774 }
775
776 /* Restore the original session.user, session.group, session.groups values. */
777 session.user = sess_user;
778 session.group = sess_group;
779 session.groups = sess_groups;
780
781 if (displaylogin != NULL &&
782 config_set != NULL) {
783
784 displaylogin_fh = pr_fsio_open(displaylogin, O_RDONLY);
785 if (displaylogin_fh == NULL) {
786 pr_log_debug(DEBUG6,
787 MOD_IFSESSION_VERSION ": unable to open DisplayLogin file '%s': %s",
788 displaylogin, strerror(errno));
789
790 } else {
791 struct stat st;
792
793 if (pr_fsio_fstat(displaylogin_fh, &st) < 0) {
794 pr_log_debug(DEBUG6,
795 MOD_IFSESSION_VERSION ": unable to stat DisplayLogin file '%s': %s",
796 displaylogin, strerror(errno));
797 pr_fsio_close(displaylogin_fh);
798 displaylogin_fh = NULL;
799
800 } else {
801 if (S_ISDIR(st.st_mode)) {
802 errno = EISDIR;
803 pr_log_debug(DEBUG6,
804 MOD_IFSESSION_VERSION ": unable to use DisplayLogin file '%s': %s",
805 displaylogin, strerror(errno));
806 pr_fsio_close(displaylogin_fh);
807 displaylogin_fh = NULL;
808
809 } else {
810 /* Remove the directive from the set, since we'll be handling it. */
811 remove_config(config_set, "DisplayLogin", FALSE);
812 }
813 }
814 }
815 }
816
817 return PR_DECLINED(cmd);
818 }
819
ifsess_post_pass(cmd_rec * cmd)820 MODRET ifsess_post_pass(cmd_rec *cmd) {
821 register unsigned int i = 0;
822 config_rec *c = NULL;
823 int found = 0;
824 pool *tmp_pool = make_sub_pool(session.pool);
825 array_header *authn_remove_list = make_array(tmp_pool, 1,
826 sizeof(config_rec *));
827 array_header *group_remove_list = make_array(tmp_pool, 1,
828 sizeof(config_rec *));
829 array_header *user_remove_list = make_array(tmp_pool, 1,
830 sizeof(config_rec *));
831
832 /* Unfortunately, I can't assign my own context types for these custom
833 * contexts, otherwise the existing directives would not be allowed in
834 * them. Good to know for the future, though, when developing modules that
835 * want to have their own complete contexts (e.g. mod_time-3.0).
836 *
837 * However, I _can_ add a directive config_rec to these contexts that has
838 * its own custom config_type. And by using -1 as the context type when
839 * searching via find_config(), it will match any context as long as the
840 * name also matches. Note: using a type of -1 and a name of NULL will
841 * result in a scan of the whole in-memory db. Hmm...
842 */
843
844 c = find_config(main_server->conf, -1, IFSESS_AUTHN_TEXT, FALSE);
845 while (c) {
846 config_rec *list = NULL;
847
848 pr_signals_handle();
849
850 list = find_config(c->subset, IFSESS_AUTHN_NUMBER, NULL, FALSE);
851 if (list != NULL) {
852 pr_log_debug(DEBUG2, MOD_IFSESSION_VERSION
853 ": merging <IfAuthenticated> directives in");
854 ifsess_dup_set(session.pool, main_server->conf, c->subset);
855
856 /* Add this config_rec pointer to the list of pointers to be
857 * removed later.
858 */
859 *((config_rec **) push_array(authn_remove_list)) = c;
860
861 ifsess_resolve_server_dirs(main_server);
862 resolve_deferred_dirs(main_server);
863
864 /* We need to call fixup_dirs() twice: once for any added <Directory>
865 * sections that use absolute paths, and again for any added <Directory>
866 * sections that use deferred-resolution paths (e.g. "~").
867 */
868 fixup_dirs(main_server, CF_SILENT);
869 fixup_dirs(main_server, CF_DEFER|CF_SILENT);
870
871 ifsess_merged = TRUE;
872 }
873
874 c = find_config_next(c, c->next, -1, IFSESS_AUTHN_TEXT, FALSE);
875 }
876
877 /* Now, remove any <IfAuthenticated> config_recs that have been merged in. */
878 for (i = 0; i < authn_remove_list->nelts; i++) {
879 c = ((config_rec **) authn_remove_list->elts)[i];
880 xaset_remove(main_server->conf, (xasetmember_t *) c);
881 }
882
883 c = find_config(main_server->conf, -1, IFSESS_GROUP_TEXT, FALSE);
884 while (c) {
885 config_rec *list = NULL;
886
887 pr_signals_handle();
888
889 list = find_config(c->subset, IFSESS_GROUP_NUMBER, NULL, FALSE);
890 if (list != NULL) {
891 unsigned char mergein = FALSE;
892
893 #ifdef PR_USE_REGEX
894 if (*((unsigned char *) list->argv[1]) == PR_EXPR_EVAL_REGEX) {
895 pr_regex_t *pre = list->argv[2];
896
897 if (session.group != NULL) {
898 pr_log_debug(DEBUG8, MOD_IFSESSION_VERSION
899 ": evaluating regexp pattern '%s' against subject '%s'",
900 pr_regexp_get_pattern(pre), session.group);
901
902 if (pr_regexp_exec(pre, session.group, 0, NULL, 0, 0, 0) == 0) {
903 mergein = TRUE;
904 }
905 }
906
907 if (mergein == FALSE &&
908 session.groups != NULL) {
909 register int j = 0;
910
911 for (j = session.groups->nelts-1; j >= 0; j--) {
912 char *suppl_group;
913
914 suppl_group = *(((char **) session.groups->elts) + j);
915
916 pr_log_debug(DEBUG8, MOD_IFSESSION_VERSION
917 ": evaluating regexp pattern '%s' against subject '%s'",
918 pr_regexp_get_pattern(pre), suppl_group);
919
920 if (pr_regexp_exec(pre, suppl_group, 0, NULL, 0, 0, 0) == 0) {
921 mergein = TRUE;
922 break;
923 }
924 }
925 }
926
927 } else
928 #endif /* regex support */
929
930 if (*((unsigned char *) list->argv[1]) == PR_EXPR_EVAL_OR &&
931 pr_expr_eval_group_or((char **) &list->argv[2]) == TRUE) {
932 mergein = TRUE;
933
934 } else if (*((unsigned char *) list->argv[1]) == PR_EXPR_EVAL_AND &&
935 pr_expr_eval_group_and((char **) &list->argv[2]) == TRUE) {
936 mergein = TRUE;
937 }
938
939 if (mergein) {
940 pr_log_debug(DEBUG2, MOD_IFSESSION_VERSION
941 ": merging <IfGroup %s> directives in", (char *) list->argv[0]);
942 ifsess_dup_set(session.pool, main_server->conf, c->subset);
943
944 /* Add this config_rec pointer to the list of pointers to be
945 * removed later.
946 */
947 *((config_rec **) push_array(group_remove_list)) = c;
948
949 ifsess_resolve_server_dirs(main_server);
950 resolve_deferred_dirs(main_server);
951
952 /* We need to call fixup_dirs() twice: once for any added <Directory>
953 * sections that use absolute paths, and again for any added <Directory>
954 * sections that use deferred-resolution paths (e.g. "~").
955 */
956 fixup_dirs(main_server, CF_SILENT);
957 fixup_dirs(main_server, CF_DEFER|CF_SILENT);
958
959 ifsess_merged = TRUE;
960
961 } else {
962 pr_log_debug(DEBUG9, MOD_IFSESSION_VERSION
963 ": <IfGroup %s> not matched, skipping", (char *) list->argv[0]);
964 }
965 }
966
967 /* Note: it would be more efficient, memory-wise, to destroy the
968 * memory pool of the removed config_rec. However, the dup'd data
969 * from that config_rec may point to memory within the pool being
970 * freed; and once freed, that memory becomes fair game, and thus may
971 * (and probably will) be overwritten. This means that, for now,
972 * keep the removed config_rec's memory around, rather than calling
973 * destroy_pool(c->pool) if removed_c is TRUE.
974 */
975
976 c = find_config_next(c, c->next, -1, IFSESS_GROUP_TEXT, FALSE);
977 }
978
979 /* Now, remove any <IfGroup> config_recs that have been merged in. */
980 for (i = 0; i < group_remove_list->nelts; i++) {
981 c = ((config_rec **) group_remove_list->elts)[i];
982 xaset_remove(main_server->conf, (xasetmember_t *) c);
983 }
984
985 c = find_config(main_server->conf, -1, IFSESS_USER_TEXT, FALSE);
986 while (c) {
987 config_rec *list = NULL;
988
989 pr_signals_handle();
990
991 list = find_config(c->subset, IFSESS_USER_NUMBER, NULL, FALSE);
992 if (list != NULL) {
993 unsigned char mergein = FALSE;
994
995 #ifdef PR_USE_REGEX
996 if (*((unsigned char *) list->argv[1]) == PR_EXPR_EVAL_REGEX) {
997 pr_regex_t *pre = list->argv[2];
998
999 pr_log_debug(DEBUG8, MOD_IFSESSION_VERSION
1000 ": evaluating regexp pattern '%s' against subject '%s'",
1001 pr_regexp_get_pattern(pre), session.user);
1002
1003 if (pr_regexp_exec(pre, session.user, 0, NULL, 0, 0, 0) == 0) {
1004 mergein = TRUE;
1005 }
1006
1007 } else
1008 #endif /* regex support */
1009
1010 if (*((unsigned char *) list->argv[1]) == PR_EXPR_EVAL_OR &&
1011 pr_expr_eval_user_or((char **) &list->argv[2]) == TRUE) {
1012 mergein = TRUE;
1013
1014 } else if (*((unsigned char *) list->argv[1]) == PR_EXPR_EVAL_AND &&
1015 pr_expr_eval_user_and((char **) &list->argv[2]) == TRUE) {
1016 mergein = TRUE;
1017 }
1018
1019 if (mergein) {
1020 pr_log_debug(DEBUG2, MOD_IFSESSION_VERSION
1021 ": merging <IfUser %s> directives in", (char *) list->argv[0]);
1022 ifsess_dup_set(session.pool, main_server->conf, c->subset);
1023
1024 /* Add this config_rec pointer to the list of pointers to be
1025 * removed later.
1026 */
1027 *((config_rec **) push_array(user_remove_list)) = c;
1028
1029 ifsess_resolve_server_dirs(main_server);
1030 resolve_deferred_dirs(main_server);
1031
1032 /* We need to call fixup_dirs() twice: once for any added <Directory>
1033 * sections that use absolute paths, and again for any added <Directory>
1034 * sections that use deferred-resolution paths (e.g. "~").
1035 */
1036 fixup_dirs(main_server, CF_SILENT);
1037 fixup_dirs(main_server, CF_DEFER|CF_SILENT);
1038
1039 ifsess_merged = TRUE;
1040
1041 } else {
1042 pr_log_debug(DEBUG9, MOD_IFSESSION_VERSION
1043 ": <IfUser %s> not matched, skipping", (char *) list->argv[0]);
1044 }
1045 }
1046
1047 c = find_config_next(c, c->next, -1, IFSESS_USER_TEXT, FALSE);
1048 }
1049
1050 /* Now, remove any <IfUser> config_recs that have been merged in. */
1051 for (i = 0; i < user_remove_list->nelts; i++) {
1052 c = ((config_rec **) user_remove_list->elts)[i];
1053 xaset_remove(main_server->conf, (xasetmember_t *) c);
1054 }
1055
1056 destroy_pool(tmp_pool);
1057
1058 if (ifsess_merged) {
1059 /* Try to honor any <Limit LOGIN> sections that may have been merged in. */
1060 if (!login_check_limits(TOPLEVEL_CONF, FALSE, TRUE, &found)) {
1061 pr_log_debug(DEBUG3, MOD_IFSESSION_VERSION
1062 ": %s %s: Limit access denies login",
1063 session.anon_config ? "ANON" : C_USER, session.user);
1064
1065 pr_log_auth(PR_LOG_NOTICE, "%s %s: Limit access denies login.",
1066 session.anon_config ? "ANON" : C_USER, session.user);
1067 pr_session_disconnect(&ifsession_module, PR_SESS_DISCONNECT_CONFIG_ACL,
1068 "Denied by <Limit LOGIN>");
1069 }
1070
1071 /* Try to honor any DisplayLogin directives that may have been merged
1072 * in (Bug#3882).
1073 */
1074 if (displaylogin_fh != NULL) {
1075 if (pr_display_fh(displaylogin_fh, NULL, R_230, 0) < 0) {
1076 pr_log_debug(DEBUG6, "unable to display DisplayLogin file '%s': %s",
1077 displaylogin_fh->fh_path, strerror(errno));
1078 }
1079
1080 pr_fsio_close(displaylogin_fh);
1081 displaylogin_fh = NULL;
1082 }
1083 }
1084
1085 return PR_DECLINED(cmd);
1086 }
1087
1088 /* Event handlers
1089 */
1090
ifsess_chroot_ev(const void * event_data,void * user_data)1091 static void ifsess_chroot_ev(const void *event_data, void *user_data) {
1092 ifsess_home_dir = (const char *) event_data;
1093 }
1094
1095 #ifdef PR_SHARED_MODULE
ifsess_mod_unload_ev(const void * event_data,void * user_data)1096 static void ifsess_mod_unload_ev(const void *event_data, void *user_data) {
1097 if (strcmp("mod_ifsession.c", (const char *) event_data) == 0) {
1098 pr_event_unregister(&ifsession_module, NULL, NULL);
1099 }
1100 }
1101 #endif /* PR_SHARED_MODULE */
1102
ifsess_postparse_ev(const void * event_data,void * user_data)1103 static void ifsess_postparse_ev(const void *event_data, void *user_data) {
1104 /* Make sure that all mod_ifsession sections have been properly closed. */
1105
1106 if (ifsess_ctx == -1) {
1107 /* All sections properly closed; nothing to do. */
1108 return;
1109 }
1110
1111 switch (ifsess_ctx) {
1112 case IFSESS_CLASS_NUMBER:
1113 pr_log_pri(PR_LOG_WARNING,
1114 "error: unclosed <IfClass> context in config file");
1115 break;
1116
1117 case IFSESS_GROUP_NUMBER:
1118 pr_log_pri(PR_LOG_WARNING,
1119 "error: unclosed <IfGroup> context in config file");
1120 break;
1121
1122 case IFSESS_USER_NUMBER:
1123 pr_log_pri(PR_LOG_WARNING,
1124 "error: unclosed <IfUser> context in config file");
1125 break;
1126 }
1127
1128 pr_session_disconnect(&ifsession_module, PR_SESS_DISCONNECT_BAD_CONFIG, NULL);
1129 return;
1130 }
1131
1132 /* Initialization routines
1133 */
1134
ifsess_init(void)1135 static int ifsess_init(void) {
1136 #ifdef PR_SHARED_MODULE
1137 pr_event_register(&ifsession_module, "core.module-unload",
1138 ifsess_mod_unload_ev, NULL);
1139 #endif /* PR_SHARED_MODULE */
1140
1141 pr_event_register(&ifsession_module, "core.chroot",
1142 ifsess_chroot_ev, NULL);
1143 pr_event_register(&ifsession_module, "core.postparse",
1144 ifsess_postparse_ev, NULL);
1145
1146 return 0;
1147 }
1148
ifsess_sess_init(void)1149 static int ifsess_sess_init(void) {
1150 if (ifsess_sess_merge_class() < 0) {
1151 return -1;
1152 }
1153
1154 return 0;
1155 }
1156
1157 /* Module API tables
1158 */
1159
1160 static conftable ifsess_conftab[] = {
1161 { IFSESS_AUTHN_TEXT, start_ifctxt, NULL },
1162 { "</IfAuthenticated>", end_ifctxt, NULL },
1163 { IFSESS_CLASS_TEXT, start_ifctxt, NULL },
1164 { "</IfClass>", end_ifctxt, NULL },
1165 { IFSESS_GROUP_TEXT, start_ifctxt, NULL },
1166 { "</IfGroup>", end_ifctxt, NULL },
1167 { IFSESS_USER_TEXT, start_ifctxt, NULL },
1168 { "</IfUser>", end_ifctxt, NULL },
1169 { NULL }
1170 };
1171
1172 static cmdtable ifsess_cmdtab[] = {
1173 { PRE_CMD, C_PASS, G_NONE, ifsess_pre_pass, FALSE, FALSE },
1174 { POST_CMD, C_PASS, G_NONE, ifsess_post_pass, FALSE, FALSE },
1175 { 0, NULL }
1176 };
1177
1178 module ifsession_module = {
1179 NULL, NULL,
1180
1181 /* Module API version 2.0 */
1182 0x20,
1183
1184 /* Module name */
1185 "ifsession",
1186
1187 /* Module configuration handler table */
1188 ifsess_conftab,
1189
1190 /* Module command handler table */
1191 ifsess_cmdtab,
1192
1193 /* Module authentication handler table */
1194 NULL,
1195
1196 /* Module initialization function */
1197 ifsess_init,
1198
1199 /* Session initialization function */
1200 ifsess_sess_init,
1201
1202 /* Module version */
1203 MOD_IFSESSION_VERSION
1204 };
1205