1 /*
2  * ProFTPD - FTP server daemon
3  * Copyright (c) 1997, 1998 Public Flood Software
4  * Copyright (c) 2003-2020 The ProFTPD Project team
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, the copyright holders give permission to link
21  * this program with OpenSSL and distribute the resulting executable without
22  * including the source code for OpenSSL in the source distribution.
23  */
24 
25 /* Use POSIX "capabilities" in modern operating systems (currently, only
26  * Linux is supported) to severely limit the process's access. After user
27  * authentication, this module _completely_ gives up root privileges, except
28  * for the bare minimum functionality that is required. VERY highly
29  * recommended for security-consious admins. See README.capabilities for more
30  * information.
31  *
32  * ----- DO NOT MODIFY THE LINES BELOW -----
33  * $Libraries: -lcap$
34  */
35 
36 #include <stdio.h>
37 #include <stdlib.h>
38 
39 #include "conf.h"
40 
41 #ifdef LINUX
42 # ifdef __powerpc__
43 #  define _LINUX_BYTEORDER_GENERIC_H
44 # endif
45 
46 # ifdef HAVE_LINUX_CAPABILITY_H
47 #  include <linux/capability.h>
48 # endif /* HAVE_LINUX_CAPABILITY_H */
49 # ifdef HAVE_SYS_CAPABILITY_H
50 #  include <sys/capability.h>
51 # endif /* HAVE_SYS_CAPABILITY_H */
52 
53 /* What are these for? */
54 # undef WNOHANG
55 # undef WUNTRACED
56 #endif /* LINUX */
57 
58 #include "privs.h"
59 
60 #ifdef HAVE_SYS_PRCTL_H
61 # include <sys/prctl.h>
62 #endif
63 
64 #define MOD_CAP_VERSION		"mod_cap/1.1"
65 
66 static cap_t capabilities = 0;
67 static unsigned char have_capabilities = FALSE;
68 static unsigned char use_capabilities = TRUE;
69 
70 #define CAP_USE_CHOWN		0x0001
71 #define CAP_USE_DAC_OVERRIDE	0x0002
72 #define CAP_USE_DAC_READ_SEARCH	0x0004
73 #define CAP_USE_SETUID		0x0008
74 #define CAP_USE_AUDIT_WRITE	0x0010
75 #define CAP_USE_FOWNER		0x0020
76 #define CAP_USE_FSETID		0x0040
77 
78 /* CAP_CHOWN and CAP_SETUID are enabled by default. */
79 static unsigned int cap_flags = (CAP_USE_CHOWN|CAP_USE_SETUID);
80 
81 module cap_module;
82 
83 /* Necessary prototypes */
84 static int cap_sess_init(void);
85 
86 /* log current capabilities */
lp_debug(void)87 static void lp_debug(void) {
88   char *res;
89   ssize_t len;
90   cap_t caps;
91 
92   caps = cap_get_proc();
93   if (caps == NULL) {
94     pr_log_pri(PR_LOG_ERR, MOD_CAP_VERSION ": cap_get_proc failed: %s",
95       strerror(errno));
96     return;
97   }
98 
99   res = cap_to_text(caps, &len);
100   if (res == NULL) {
101     pr_log_pri(PR_LOG_ERR, MOD_CAP_VERSION ": cap_to_text failed: %s",
102       strerror(errno));
103 
104     if (cap_free(caps) < 0) {
105       pr_log_pri(PR_LOG_NOTICE, MOD_CAP_VERSION
106         ": error freeing cap at line %d: %s", __LINE__ - 2, strerror(errno));
107     }
108 
109     return;
110   }
111 
112   pr_log_debug(DEBUG1, MOD_CAP_VERSION ": capabilities '%s'", res);
113   (void) cap_free(res);
114 
115   if (cap_free(caps) < 0) {
116     pr_log_pri(PR_LOG_NOTICE, MOD_CAP_VERSION
117       ": error freeing cap at line %d: %s", __LINE__ - 2, strerror(errno));
118   }
119 }
120 
121 /* create a new capability structure */
lp_init_cap(void)122 static int lp_init_cap(void) {
123 
124   capabilities = cap_init();
125   if (capabilities == NULL) {
126     pr_log_pri(PR_LOG_ERR, MOD_CAP_VERSION ": initializing cap failed: %s",
127       strerror(errno));
128     return -1;
129   }
130 
131   have_capabilities = TRUE;
132   return 0;
133 }
134 
135 /* free the capability structure */
lp_free_cap(void)136 static void lp_free_cap(void) {
137   if (have_capabilities) {
138     if (cap_free(capabilities) < 0) {
139       pr_log_pri(PR_LOG_NOTICE, MOD_CAP_VERSION
140         ": error freeing cap at line %d: %s", __LINE__ - 2, strerror(errno));
141     }
142   }
143 }
144 
145 /* add a capability to a given set */
lp_add_cap(cap_value_t cap,cap_flag_t set)146 static int lp_add_cap(cap_value_t cap, cap_flag_t set) {
147   if (cap_set_flag(capabilities, set, 1, &cap, CAP_SET) == -1) {
148     pr_log_pri(PR_LOG_ERR, MOD_CAP_VERSION ": cap_set_flag failed: %s",
149       strerror(errno));
150     return -1;
151   }
152 
153   return 0;
154 }
155 
156 /* send the capabilities to the kernel */
lp_set_cap(void)157 static int lp_set_cap(void) {
158   if (cap_set_proc(capabilities) == -1) {
159     pr_log_pri(PR_LOG_ERR, MOD_CAP_VERSION ": cap_set_proc failed: %s",
160       strerror(errno));
161     return -1;
162   }
163 
164   return 0;
165 }
166 
167 /* Configuration handlers
168  */
169 
170 /* usage: CapabilitiesRootRevoke on|off */
set_caprootrevoke(cmd_rec * cmd)171 MODRET set_caprootrevoke(cmd_rec *cmd) {
172   int b = -1;
173   config_rec *c = NULL;
174 
175   CHECK_ARGS(cmd, 1);
176   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
177 
178   b = get_boolean(cmd, 1);
179   if (b == -1) {
180     CONF_ERROR(cmd, "expected Boolean parameter");
181   }
182 
183   c = add_config_param(cmd->argv[0], 1, NULL);
184   c->argv[0] = pcalloc(c->pool, sizeof(int));
185   *((int *) c->argv[0]) = b;
186 
187   return PR_HANDLED(cmd);
188 }
189 
set_caps(cmd_rec * cmd)190 MODRET set_caps(cmd_rec *cmd) {
191   unsigned int flags = 0;
192   config_rec *c = NULL;
193   register unsigned int i = 0;
194 
195   if (cmd->argc - 1 < 1) {
196     CONF_ERROR(cmd, "need at least one parameter");
197   }
198 
199   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
200 
201   /* CAP_CHOWN and CAP_SETUID are enabled by default. */
202   flags |= (CAP_USE_CHOWN|CAP_USE_SETUID);
203 
204   for (i = 1; i < cmd->argc; i++) {
205     char *cap, *ptr;
206 
207     cap = ptr = cmd->argv[i];
208     ptr++;
209 
210     if (*cap != '+' &&
211         *cap != '-') {
212       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": bad option: '", cap, "'",
213         NULL));
214     }
215 
216     if (strcasecmp(ptr, "CAP_CHOWN") == 0) {
217       if (*cap == '-') {
218         flags &= ~CAP_USE_CHOWN;
219       }
220 
221     } else if (strcasecmp(ptr, "CAP_DAC_OVERRIDE") == 0) {
222       if (*cap == '+') {
223         flags |= CAP_USE_DAC_OVERRIDE;
224       }
225 
226     } else if (strcasecmp(ptr, "CAP_DAC_READ_SEARCH") == 0) {
227       if (*cap == '+') {
228         flags |= CAP_USE_DAC_READ_SEARCH;
229       }
230 
231     } else if (strcasecmp(ptr, "CAP_FOWNER") == 0) {
232       if (*cap == '+') {
233         flags |= CAP_USE_FOWNER;
234       }
235 
236     } else if (strcasecmp(ptr, "CAP_FSETID") == 0) {
237       if (*cap == '+') {
238         flags |= CAP_USE_FSETID;
239       }
240 
241     } else if (strcasecmp(ptr, "CAP_SETUID") == 0) {
242       if (*cap == '-') {
243         flags &= ~CAP_USE_SETUID;
244       }
245 
246     } else {
247       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown capability: '",
248         ptr, "'", NULL));
249     }
250   }
251 
252   c = add_config_param(cmd->argv[0], 1, NULL);
253   c->argv[0] = pcalloc(c->pool, sizeof(unsigned int));
254   *((unsigned int *) c->argv[0]) = flags;
255 
256   /* Make sure to set this flag, so that mod_ifsession handles these
257    * config_recs properly.
258    */
259   c->flags |= CF_MULTI;
260 
261   return PR_HANDLED(cmd);
262 }
263 
set_capengine(cmd_rec * cmd)264 MODRET set_capengine(cmd_rec *cmd) {
265   int bool = -1;
266   config_rec *c = NULL;
267 
268   CHECK_ARGS(cmd, 1);
269   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
270 
271   bool = get_boolean(cmd, 1);
272   if (bool == -1)
273     CONF_ERROR(cmd, "expecting Boolean parameter");
274 
275   c = add_config_param(cmd->argv[0], 1, NULL);
276   c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
277   *((unsigned char *) c->argv[0]) = bool;
278 
279   return PR_HANDLED(cmd);
280 }
281 
282 /* Command handlers
283  */
284 
285 /* The POST_CMD handler for "PASS" is only called after PASS has
286  * successfully completed, which means authentication is successful,
287  * so we can "tweak" our root access down to almost nothing.
288  */
cap_post_pass(cmd_rec * cmd)289 MODRET cap_post_pass(cmd_rec *cmd) {
290   int cap_root_revoke = TRUE, res;
291   config_rec *c;
292   unsigned char *cap_engine = NULL;
293   uid_t dst_uid = PR_ROOT_UID;
294 
295   if (!use_capabilities)
296     return PR_DECLINED(cmd);
297 
298   /* Check to see if we have been disabled via e.g. mod_ifsession. */
299   cap_engine = get_param_ptr(main_server->conf, "CapabilitiesEngine", FALSE);
300   if (cap_engine != NULL) {
301     use_capabilities = *cap_engine;
302     if (use_capabilities == FALSE) {
303       return PR_DECLINED(cmd);
304     }
305   }
306 
307   /* Check for which specific capabilities to include/exclude. */
308   c = find_config(main_server->conf, CONF_PARAM, "CapabilitiesSet", FALSE);
309   if (c != NULL) {
310     cap_flags = *((unsigned int *) c->argv[0]);
311 
312     if (!(cap_flags & CAP_USE_CHOWN)) {
313       pr_log_debug(DEBUG3, MOD_CAP_VERSION
314         ": removing CAP_CHOWN capability");
315     }
316 
317     if (cap_flags & CAP_USE_DAC_OVERRIDE) {
318       pr_log_debug(DEBUG3, MOD_CAP_VERSION
319         ": adding CAP_DAC_OVERRIDE capability");
320     }
321 
322     if (cap_flags & CAP_USE_DAC_READ_SEARCH) {
323       pr_log_debug(DEBUG3, MOD_CAP_VERSION
324         ": adding CAP_DAC_READ_SEARCH capability");
325     }
326 
327     if (cap_flags & CAP_USE_FOWNER) {
328       pr_log_debug(DEBUG3, MOD_CAP_VERSION
329         ": adding CAP_FOWNER capability");
330     }
331 
332     if (cap_flags & CAP_USE_FSETID) {
333       pr_log_debug(DEBUG3, MOD_CAP_VERSION
334         ": adding CAP_FSETID capability");
335     }
336 
337     if (!(cap_flags & CAP_USE_SETUID)) {
338       pr_log_debug(DEBUG3, MOD_CAP_VERSION
339         ": removing CAP_SETUID capability");
340     }
341   }
342 
343   pr_signals_block();
344 
345 #ifndef PR_DEVEL_COREDUMP
346   /* glibc2.1 is BROKEN, seteuid() no longer lets one set euid to uid,
347    * so we can't use PRIVS_ROOT/PRIVS_RELINQUISH. setreuid() is the
348    * workaround.
349    */
350   if (setreuid(session.uid, PR_ROOT_UID) < 0) {
351     int xerrno = errno;
352     const char *proto;
353 
354     pr_signals_unblock();
355 
356     proto = pr_session_get_protocol(0);
357 
358     /* If this is for an SSH2 connection, don't log the error if it is
359      * an EPERM.
360      */
361     if (strncmp(proto, "ssh2", 5) != 0 ||
362         xerrno != EPERM) {
363       pr_log_pri(PR_LOG_ERR, MOD_CAP_VERSION ": setreuid(%s, %s) failed: %s",
364         pr_uid2str(cmd->tmp_pool, session.uid),
365         pr_uid2str(cmd->tmp_pool, PR_ROOT_UID),
366         strerror(xerrno));
367     }
368 
369     return PR_DECLINED(cmd);
370   }
371 #endif /* PR_DEVEL_COREDUMP */
372 
373   /* The only capability we need is CAP_NET_BIND_SERVICE (bind
374    * ports < 1024).  Everything else can be discarded.  We set this
375    * in CAP_PERMITTED set only, as when we switch away from root
376    * we lose CAP_EFFECTIVE anyhow, and must reset it.
377    */
378 
379   res = lp_init_cap();
380   if (res != -1) {
381     res = lp_add_cap(CAP_NET_BIND_SERVICE, CAP_PERMITTED);
382   }
383 
384   /* Add the CAP_CHOWN capability, unless explicitly configured not to. */
385   if (res != -1 &&
386       (cap_flags & CAP_USE_CHOWN)) {
387     res = lp_add_cap(CAP_CHOWN, CAP_PERMITTED);
388   }
389 
390   if (res != -1 &&
391       (cap_flags & CAP_USE_DAC_OVERRIDE)) {
392     res = lp_add_cap(CAP_DAC_OVERRIDE, CAP_PERMITTED);
393   }
394 
395   if (res != -1 &&
396       (cap_flags & CAP_USE_DAC_READ_SEARCH)) {
397     res = lp_add_cap(CAP_DAC_READ_SEARCH, CAP_PERMITTED);
398   }
399 
400   if (res != -1 &&
401       (cap_flags & CAP_USE_SETUID)) {
402     res = lp_add_cap(CAP_SETUID, CAP_PERMITTED);
403     if (res != -1) {
404       res = lp_add_cap(CAP_SETGID, CAP_PERMITTED);
405     }
406   }
407 
408 #ifdef CAP_AUDIT_WRITE
409   if (res != -1 &&
410       (cap_flags & CAP_USE_AUDIT_WRITE)) {
411     res = lp_add_cap(CAP_AUDIT_WRITE, CAP_PERMITTED);
412   }
413 #endif
414 
415   if (res != -1 &&
416       (cap_flags & CAP_USE_FOWNER)) {
417     res = lp_add_cap(CAP_FOWNER, CAP_PERMITTED);
418   }
419 
420 #ifdef CAP_FSETID
421   if (res != -1 &&
422       (cap_flags & CAP_USE_FSETID)) {
423     res = lp_add_cap(CAP_FSETID, CAP_PERMITTED);
424   }
425 #endif
426 
427   if (res != -1)
428     res = lp_set_cap();
429 
430 #ifdef PR_SET_KEEPCAPS
431   /* Make sure that when we switch our IDs, we still keep the capabilities
432    * we've set.
433    */
434   if (prctl(PR_SET_KEEPCAPS, 1) < 0) {
435     pr_log_pri(PR_LOG_ERR,
436       MOD_CAP_VERSION ": prctl(PR_SET_KEEPCAPS) failed: %s", strerror(errno));
437   }
438 #endif /* PR_SET_KEEPCAPS */
439 
440   /* Unless the config requests it, drop root privs completely. */
441   c = find_config(main_server->conf, CONF_PARAM, "CapabilitiesRootRevoke",
442     FALSE);
443   if (c != NULL) {
444     cap_root_revoke = *((int *) c->argv[0]);
445   }
446 
447   if (cap_root_revoke == TRUE) {
448     dst_uid = session.uid;
449 
450   } else {
451     pr_log_debug(DEBUG4, MOD_CAP_VERSION
452       ": CapabilitiesRootRevoke off, not dropping root privs");
453   }
454 
455   if (setreuid(dst_uid, session.uid) == -1) {
456     pr_log_pri(PR_LOG_ERR, MOD_CAP_VERSION ": setreuid(%s, %s) failed: %s",
457       pr_uid2str(cmd->tmp_pool, dst_uid),
458       pr_uid2str(cmd->tmp_pool, session.uid),
459       strerror(errno));
460     lp_free_cap();
461     pr_signals_unblock();
462     pr_session_disconnect(&cap_module, PR_SESS_DISCONNECT_BY_APPLICATION, NULL);
463   }
464   pr_signals_unblock();
465 
466   pr_log_debug(DEBUG9, MOD_CAP_VERSION
467     ": uid = %s, euid = %s, gid = %s, egid = %s",
468     pr_uid2str(cmd->tmp_pool, getuid()),
469     pr_uid2str(cmd->tmp_pool, geteuid()),
470     pr_gid2str(cmd->tmp_pool, getgid()),
471     pr_gid2str(cmd->tmp_pool, getegid()));
472 
473   /* Now our only capabilities consist of CAP_NET_BIND_SERVICE (and other
474    * configured caps), however in order to actually be able to bind to
475    * low-numbered ports, we need the capability to be in the effective set.
476    */
477 
478   if (res != -1) {
479     res = lp_add_cap(CAP_NET_BIND_SERVICE, CAP_EFFECTIVE);
480   }
481 
482   /* Add the CAP_CHOWN capability, unless explicitly configured not to. */
483   if (res != -1 &&
484       (cap_flags & CAP_USE_CHOWN)) {
485     res = lp_add_cap(CAP_CHOWN, CAP_EFFECTIVE);
486   }
487 
488   if (res != -1 &&
489       (cap_flags & CAP_USE_DAC_OVERRIDE)) {
490     res = lp_add_cap(CAP_DAC_OVERRIDE, CAP_EFFECTIVE);
491   }
492 
493   if (res != -1 &&
494       (cap_flags & CAP_USE_DAC_READ_SEARCH)) {
495     res = lp_add_cap(CAP_DAC_READ_SEARCH, CAP_EFFECTIVE);
496   }
497 
498   if (res != -1
499       && (cap_flags & CAP_USE_SETUID)) {
500     res = lp_add_cap(CAP_SETUID, CAP_EFFECTIVE);
501     if (res != -1) {
502       res = lp_add_cap(CAP_SETGID, CAP_EFFECTIVE);
503     }
504   }
505 
506 #ifdef CAP_AUDIT_WRITE
507   if (res != -1 &&
508       (cap_flags & CAP_USE_AUDIT_WRITE)) {
509     res = lp_add_cap(CAP_AUDIT_WRITE, CAP_EFFECTIVE);
510   }
511 #endif
512 
513   if (res != -1 &&
514       (cap_flags & CAP_USE_FOWNER)) {
515     res = lp_add_cap(CAP_FOWNER, CAP_EFFECTIVE);
516   }
517 
518 #ifdef CAP_FSETID
519   if (res != -1 &&
520       (cap_flags & CAP_USE_FSETID)) {
521     res = lp_add_cap(CAP_FSETID, CAP_EFFECTIVE);
522   }
523 #endif
524 
525   if (res != -1)
526     res = lp_set_cap();
527 
528   lp_free_cap();
529 
530   if (res != -1) {
531     /* That's it!  Disable all further id switching */
532     session.disable_id_switching = TRUE;
533     lp_debug();
534 
535   } else {
536     pr_log_pri(PR_LOG_WARNING, MOD_CAP_VERSION ": attempt to configure "
537       "capabilities failed, reverting to normal operation");
538   }
539 
540   return PR_DECLINED(cmd);
541 }
542 
543 /* Event listeners
544  */
545 
cap_sess_reinit_ev(const void * event_data,void * user_data)546 static void cap_sess_reinit_ev(const void *event_data, void *user_data) {
547   int res;
548 
549   /* A HOST command changed the main_server pointer, reinitialize ourselves. */
550 
551   pr_event_unregister(&cap_module, "core.session-reinit", cap_sess_reinit_ev);
552 
553   have_capabilities = FALSE;
554   use_capabilities = TRUE;
555   cap_flags = 0;
556 
557   res = cap_sess_init();
558   if (res < 0) {
559     pr_session_disconnect(&cap_module,
560       PR_SESS_DISCONNECT_SESSION_INIT_FAILED, NULL);
561   }
562 }
563 
564 /* Initialization routines
565  */
566 
cap_sess_init(void)567 static int cap_sess_init(void) {
568   pr_event_register(&cap_module, "core.session-reinit", cap_sess_reinit_ev,
569     NULL);
570 
571   /* Check to see if the lowering of capabilities has been disabled in the
572    * configuration file.
573    */
574   if (use_capabilities) {
575     unsigned char *cap_engine;
576 
577     cap_engine = get_param_ptr(main_server->conf, "CapabilitiesEngine", FALSE);
578     if (cap_engine &&
579         *cap_engine == FALSE) {
580       pr_log_debug(DEBUG3, MOD_CAP_VERSION
581         ": lowering of capabilities disabled");
582       use_capabilities = FALSE;
583     }
584   }
585 
586   if (use_capabilities) {
587     int use_setuid = FALSE;
588 
589     /* We need to check for things which want to revoke root privs altogether:
590      * mod_exec, mod_sftp, and the RootRevoke directive.  Revoking root privs
591      * completely requires the SETUID/SETGID capabilities.
592      */
593 
594     if (use_setuid == FALSE &&
595         pr_module_exists("mod_sftp.c")) {
596       config_rec *c;
597 
598       c = find_config(main_server->conf, CONF_PARAM, "SFTPEngine", FALSE);
599       if (c &&
600           *((int *) c->argv[0]) == TRUE) {
601         use_setuid = TRUE;
602       }
603     }
604 
605     if (use_setuid == FALSE &&
606         pr_module_exists("mod_exec.c")) {
607       config_rec *c;
608 
609       c = find_config(main_server->conf, CONF_PARAM, "ExecEngine", FALSE);
610       if (c &&
611           *((unsigned char *) c->argv[0]) == TRUE) {
612         use_setuid = TRUE;
613       }
614     }
615 
616     if (use_setuid == FALSE) {
617       config_rec *c;
618 
619       c = find_config(main_server->conf, CONF_PARAM, "RootRevoke", FALSE);
620       if (c &&
621           *((unsigned char *) c->argv[0]) == TRUE) {
622         use_setuid = TRUE;
623       }
624     }
625 
626     if (use_setuid) {
627       cap_flags |= CAP_USE_SETUID;
628       pr_log_debug(DEBUG3, MOD_CAP_VERSION
629         ": adding CAP_SETUID and CAP_SETGID capabilities");
630     }
631 
632 #ifdef CAP_AUDIT_WRITE
633     if (pr_module_exists("mod_auth_pam.c")) {
634       cap_flags |= CAP_USE_AUDIT_WRITE;
635       pr_log_debug(DEBUG3, MOD_CAP_VERSION
636         ": adding CAP_AUDIT_WRITE capability");
637     }
638 #endif
639   }
640 
641   return 0;
642 }
643 
cap_module_init(void)644 static int cap_module_init(void) {
645   cap_t res;
646 
647   /* Attempt to determine if we are running on a kernel that supports
648    * capabilities. This allows binary distributions to include the module
649    * even if it may not work.
650    */
651   res = cap_get_proc();
652   if (res == NULL &&
653       errno == ENOSYS) {
654     pr_log_debug(DEBUG2, MOD_CAP_VERSION
655       ": kernel does not support capabilities, disabling module");
656     use_capabilities = FALSE;
657   }
658 
659   if (res != 0 &&
660       cap_free(res) < 0) {
661     pr_log_pri(PR_LOG_NOTICE, MOD_CAP_VERSION
662       ": error freeing cap at line %d: %s", __LINE__ - 2, strerror(errno));
663   }
664 
665   return 0;
666 }
667 
668 
669 /* Module API tables
670  */
671 
672 static conftable cap_conftab[] = {
673   { "CapabilitiesEngine",	set_capengine,		NULL },
674   { "CapabilitiesRootRevoke",	set_caprootrevoke,	NULL },
675   { "CapabilitiesSet",		set_caps,		NULL },
676   { NULL, NULL, NULL }
677 };
678 
679 static cmdtable cap_cmdtab[] = {
680   { POST_CMD,	C_PASS,	G_NONE,	cap_post_pass,	FALSE, FALSE },
681   { 0, NULL }
682 };
683 
684 module cap_module = {
685   NULL, NULL,
686 
687   /* Module API version */
688   0x20,
689 
690   /* Module name */
691   "cap",
692 
693   /* Module configuration handler table */
694   cap_conftab,
695 
696   /* Module command handler table */
697   cap_cmdtab,
698 
699   /* Module authentication handler table */
700   NULL,
701 
702   /* Module initialization */
703   cap_module_init,
704 
705   /* Session initialization */
706   cap_sess_init,
707 
708   /* Module version */
709   MOD_CAP_VERSION
710 };
711