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