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