mirror of
https://git.FreeBSD.org/src.git
synced 2024-12-19 10:53:58 +00:00
cbc25559e4
sys/capability.h now. Submitted by: tmm Obtained from: TrustedBSD Project
577 lines
14 KiB
C
577 lines
14 KiB
C
/*-
|
|
* Copyright (c) 2000 Robert N. M. Watson
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
* $FreeBSD$
|
|
*/
|
|
/*
|
|
* TrustedBSD Project - support for POSIX.1e process capabilities
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include "namespace.h"
|
|
#include <sys/capability.h>
|
|
#include "un-namespace.h"
|
|
#include <sys/errno.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
/*
|
|
* this one is in sys/capability.h now:
|
|
* static const size_t CAP_MAX_BUF_LEN = 1024;
|
|
*/
|
|
static const size_t CAP_MAX_SMALL_BUF_LEN = 64;
|
|
|
|
static const char *CAP_FLAGS[8] = {
|
|
"", /* 000 */
|
|
"e", /* 001 */
|
|
"i", /* 010 */
|
|
"ei", /* 011 */
|
|
"p", /* 100 */
|
|
"ep", /* 101 */
|
|
"ip", /* 110 */
|
|
"eip", /* 111 */
|
|
};
|
|
|
|
static const char *CAP_SEP = ": \t";
|
|
static const char *CAP_OPERATION = "=-+";
|
|
|
|
struct cap_info {
|
|
char *ci_name;
|
|
cap_value_t ci_cap;
|
|
};
|
|
|
|
static const struct cap_info cap_info_array[] = {
|
|
{"CAP_CHOWN", CAP_CHOWN},
|
|
{"CAP_DAC_EXECUTE", CAP_DAC_EXECUTE},
|
|
{"CAP_DAC_WRITE", CAP_DAC_WRITE},
|
|
{"CAP_DAC_READ_SEARCH", CAP_DAC_READ_SEARCH},
|
|
{"CAP_FOWNER", CAP_FOWNER},
|
|
{"CAP_FSETID", CAP_FSETID},
|
|
{"CAP_KILL", CAP_KILL},
|
|
{"CAP_LINK_DIR", CAP_LINK_DIR},
|
|
{"CAP_SETFCAP", CAP_SETFCAP},
|
|
{"CAP_SETGID", CAP_SETGID},
|
|
{"CAP_SETUID", CAP_SETUID},
|
|
{"CAP_MAC_DOWNGRADE", CAP_MAC_DOWNGRADE},
|
|
{"CAP_MAC_READ", CAP_MAC_READ},
|
|
{"CAP_MAC_RELABEL_SUBJ", CAP_MAC_RELABEL_SUBJ},
|
|
{"CAP_MAC_UPGRADE", CAP_MAC_UPGRADE},
|
|
{"CAP_MAC_WRITE", CAP_MAC_WRITE},
|
|
{"CAP_INF_NOFLOAT_OBJ", CAP_INF_NOFLOAT_OBJ},
|
|
{"CAP_INF_NOFLOAT_SUBJ", CAP_INF_NOFLOAT_SUBJ},
|
|
{"CAP_INF_RELABEL_OBJ", CAP_INF_RELABEL_OBJ},
|
|
{"CAP_INF_RELABEL_SUBJ", CAP_INF_RELABEL_SUBJ},
|
|
{"CAP_AUDIT_CONTROL", CAP_AUDIT_CONTROL},
|
|
{"CAP_AUDIT_WRITE", CAP_AUDIT_WRITE},
|
|
{"CAP_SETPCAP", CAP_SETPCAP},
|
|
{"CAP_SYS_SETFFLAG", CAP_SYS_SETFFLAG},
|
|
{"CAP_LINUX_IMMUTABLE", CAP_SYS_SETFFLAG},
|
|
{"CAP_NET_BIND_SERVICE", CAP_NET_BIND_SERVICE},
|
|
{"CAP_NET_BROADCAST", CAP_NET_BROADCAST},
|
|
{"CAP_NET_ADMIN", CAP_NET_ADMIN},
|
|
{"CAP_NET_RAW", CAP_NET_RAW},
|
|
{"CAP_IPC_LOCK", CAP_IPC_LOCK},
|
|
{"CAP_IPC_OWNER", CAP_IPC_OWNER},
|
|
{"CAP_SYS_MODULE", CAP_SYS_MODULE},
|
|
{"CAP_SYS_RAWIO", CAP_SYS_RAWIO},
|
|
{"CAP_SYS_CHROOT", CAP_SYS_CHROOT},
|
|
{"CAP_SYS_PTRACE", CAP_SYS_PTRACE},
|
|
{"CAP_SYS_PACCT", CAP_SYS_PACCT},
|
|
{"CAP_SYS_ADMIN", CAP_SYS_ADMIN},
|
|
{"CAP_SYS_BOOT", CAP_SYS_BOOT},
|
|
{"CAP_SYS_NICE", CAP_SYS_NICE},
|
|
{"CAP_SYS_RESOURCE", CAP_SYS_RESOURCE},
|
|
{"CAP_SYS_TIME", CAP_SYS_TIME},
|
|
{"CAP_SYS_TTY_CONFIG", CAP_SYS_TTY_CONFIG},
|
|
{"CAP_MKNOD", CAP_MKNOD},
|
|
{"", CAP_ALL_OFF},
|
|
{"all", CAP_ALL_ON},
|
|
};
|
|
|
|
static const int cap_info_array_len = sizeof(cap_info_array) /
|
|
sizeof(cap_info_array[0]);
|
|
|
|
static const cap_value_t cap_list[] = {
|
|
CAP_CHOWN,
|
|
CAP_DAC_EXECUTE,
|
|
CAP_DAC_WRITE,
|
|
CAP_DAC_READ_SEARCH,
|
|
CAP_FOWNER,
|
|
CAP_FSETID,
|
|
CAP_KILL,
|
|
CAP_LINK_DIR,
|
|
CAP_SETFCAP,
|
|
CAP_SETGID,
|
|
CAP_SETUID,
|
|
CAP_MAC_DOWNGRADE,
|
|
CAP_MAC_READ,
|
|
CAP_MAC_RELABEL_SUBJ,
|
|
CAP_MAC_UPGRADE,
|
|
CAP_MAC_WRITE,
|
|
CAP_INF_NOFLOAT_OBJ,
|
|
CAP_INF_NOFLOAT_SUBJ,
|
|
CAP_INF_RELABEL_OBJ,
|
|
CAP_INF_RELABEL_SUBJ,
|
|
CAP_AUDIT_CONTROL,
|
|
CAP_AUDIT_WRITE,
|
|
CAP_SETPCAP,
|
|
CAP_SYS_SETFFLAG,
|
|
CAP_NET_BIND_SERVICE,
|
|
CAP_NET_BROADCAST,
|
|
CAP_NET_ADMIN,
|
|
CAP_NET_RAW,
|
|
CAP_IPC_LOCK,
|
|
CAP_IPC_OWNER,
|
|
CAP_SYS_MODULE,
|
|
CAP_SYS_RAWIO,
|
|
CAP_SYS_CHROOT,
|
|
CAP_SYS_PTRACE,
|
|
CAP_SYS_PACCT,
|
|
CAP_SYS_ADMIN,
|
|
CAP_SYS_BOOT,
|
|
CAP_SYS_NICE,
|
|
CAP_SYS_RESOURCE,
|
|
CAP_SYS_TIME,
|
|
CAP_SYS_TTY_CONFIG,
|
|
CAP_MKNOD,
|
|
};
|
|
|
|
static const int cap_list_len = sizeof(cap_list) / sizeof(cap_list[0]);
|
|
|
|
static void
|
|
cap_set(cap_t cap_p, cap_flag_t flags, cap_flag_value_t fvalue,
|
|
cap_value_t cap_value)
|
|
{
|
|
|
|
if (flags & CAP_EFFECTIVE) {
|
|
if (fvalue == CAP_SET)
|
|
cap_p->c_effective |= cap_value;
|
|
else
|
|
cap_p->c_effective &= ~cap_value;
|
|
}
|
|
if (flags & CAP_INHERITABLE) {
|
|
if (fvalue == CAP_SET)
|
|
cap_p->c_inheritable |= cap_value;
|
|
else
|
|
cap_p->c_inheritable &= ~cap_value;
|
|
}
|
|
if (flags & CAP_PERMITTED) {
|
|
if (fvalue == CAP_SET)
|
|
cap_p->c_permitted |= cap_value;
|
|
else
|
|
cap_p->c_permitted &= ~cap_value;
|
|
}
|
|
}
|
|
|
|
static int
|
|
cap_is_set(cap_t cap_p, cap_flag_t cap_flag, cap_value_t cap_value)
|
|
{
|
|
int seen = 0;
|
|
|
|
if (cap_flag & CAP_EFFECTIVE)
|
|
seen |= (cap_p->c_effective & cap_value);
|
|
if (cap_flag & CAP_INHERITABLE)
|
|
seen |= (cap_p->c_inheritable & cap_value);
|
|
if (cap_flag & CAP_PERMITTED)
|
|
seen |= (cap_p->c_permitted & cap_value);
|
|
|
|
return (seen);
|
|
}
|
|
|
|
static cap_flag_value_t
|
|
cap_value_to_flags(cap_t cap_p, cap_value_t cap_value)
|
|
{
|
|
cap_flag_t flags = 0;
|
|
|
|
if (cap_p->c_effective & cap_value)
|
|
flags |= CAP_EFFECTIVE;
|
|
if (cap_p->c_inheritable & cap_value)
|
|
flags |= CAP_INHERITABLE;
|
|
if (cap_p->c_permitted & cap_value)
|
|
flags |= CAP_PERMITTED;
|
|
|
|
return (flags);
|
|
}
|
|
|
|
static const char *
|
|
cap_flags_to_string(cap_flag_t flags)
|
|
{
|
|
|
|
return (CAP_FLAGS[flags]);
|
|
}
|
|
|
|
static int
|
|
cap_string_to_flags(const char *string, cap_flag_t *flags)
|
|
{
|
|
const char *c = string;
|
|
|
|
*flags = 0;
|
|
while (*c != '\0') {
|
|
switch (*c) {
|
|
case 'e':
|
|
*flags |= CAP_EFFECTIVE;
|
|
break;
|
|
case 'i':
|
|
*flags |= CAP_INHERITABLE;
|
|
break;
|
|
case 'p':
|
|
*flags |= CAP_PERMITTED;
|
|
break;
|
|
default:
|
|
return (EINVAL);
|
|
}
|
|
c++;
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static const char *
|
|
cap_to_string(cap_value_t cap)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < cap_info_array_len; i++) {
|
|
if (cap_info_array[i].ci_cap == cap)
|
|
return (cap_info_array[i].ci_name);
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
static int
|
|
cap_from_string(const char *string, cap_value_t *cap)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < cap_info_array_len; i++) {
|
|
if (!strcasecmp(cap_info_array[i].ci_name, string)) {
|
|
*cap = cap_info_array[i].ci_cap;
|
|
return (0);
|
|
}
|
|
}
|
|
|
|
return (EINVAL);
|
|
}
|
|
|
|
char *
|
|
cap_to_text(cap_t cap_p, ssize_t *len_p)
|
|
{
|
|
cap_value_t cap_value;
|
|
cap_flag_t cap_flag, most_flag;
|
|
const char *flag_s, *value_s, *prefix_s;
|
|
char *buf, minibuf[CAP_MAX_SMALL_BUF_LEN], operation;
|
|
|
|
int num_effective, num_inheritable, num_permitted;
|
|
int most_effective, most_inheritable, most_permitted;
|
|
int count, any_so_far;
|
|
|
|
buf = (char *)malloc(CAP_MAX_BUF_LEN);
|
|
if (buf == NULL) {
|
|
errno = ENOMEM;
|
|
return (NULL);
|
|
}
|
|
buf[0] = '\0';
|
|
|
|
/*
|
|
* For the sake of prettiness, first walk each flag to see if it's
|
|
* set for cap_list_len/2 or more. If so, list it as a plus, and
|
|
* do the remainder as negative, as needed. This will tend to
|
|
* collapse a lot of the common all= cases.
|
|
*/
|
|
num_effective = num_inheritable = num_permitted = 0;
|
|
for (count = 0; count < cap_list_len; count++) {
|
|
cap_value = cap_list[count];
|
|
if (cap_is_set(cap_p, CAP_EFFECTIVE, cap_value))
|
|
num_effective++;
|
|
if (cap_is_set(cap_p, CAP_INHERITABLE, cap_value))
|
|
num_inheritable++;
|
|
if (cap_is_set(cap_p, CAP_PERMITTED, cap_value))
|
|
num_permitted++;
|
|
}
|
|
|
|
most_effective = (num_effective > cap_list_len / 2);
|
|
most_inheritable = (num_inheritable > cap_list_len / 2);
|
|
most_permitted = (num_permitted > cap_list_len / 2);
|
|
|
|
most_flag = 0;
|
|
if (most_effective)
|
|
most_flag |= CAP_EFFECTIVE;
|
|
if (most_inheritable)
|
|
most_flag |= CAP_INHERITABLE;
|
|
if (most_permitted)
|
|
most_flag |= CAP_PERMITTED;
|
|
|
|
any_so_far = 0;
|
|
if (most_flag != 0) {
|
|
if ((strlcat(buf, "all=", CAP_MAX_BUF_LEN) >=
|
|
CAP_MAX_BUF_LEN) ||
|
|
(strlcat(buf, CAP_FLAGS[most_flag],
|
|
CAP_MAX_BUF_LEN) >= CAP_MAX_BUF_LEN)) {
|
|
free(buf);
|
|
errno = ENOMEM;
|
|
return (NULL);
|
|
}
|
|
any_so_far = 1;
|
|
}
|
|
|
|
/*
|
|
* For each capability value, determine how that value relates
|
|
* to the most common case, and (depending on CAP_PRINT_RELATIVE)
|
|
* either print out the value's flag set relative to the most
|
|
* common, or its absolute flag set.
|
|
*/
|
|
for (count = 0; count < cap_list_len; count++) {
|
|
cap_value = cap_list[count];
|
|
cap_flag = cap_value_to_flags(cap_p, cap_value);
|
|
/*
|
|
* Determine which, if any, flags need to be printed
|
|
* for this capability. First, if the flags on the
|
|
* capability are equal to the "most" flags, just skip
|
|
* it.
|
|
*/
|
|
if (cap_flag == most_flag)
|
|
continue;
|
|
|
|
#if CAP_PRINT_RELATIVE
|
|
/*
|
|
* If the flags are a strict superset of the "most"
|
|
* flags, print it as a "+" case. If they're a
|
|
* strict subset, print as a "-" case. Otherwise,
|
|
* specify as an "=" case.
|
|
*/
|
|
if ((cap_flag | most_flag) == cap_flag) {
|
|
/* Strict superset, use "+". */
|
|
operation = '+';
|
|
cap_flag = cap_flag & ~most_flag;
|
|
flag_s = cap_flags_to_string(cap_flag);
|
|
} else if ((cap_flag | most_flag) == most_flag) {
|
|
/* Strict subset, use "-". */
|
|
operation = '-';
|
|
cap_flag = most_flag & ~cap_flag;
|
|
flag_s = cap_flags_to_string(cap_flag);
|
|
} else {
|
|
#endif
|
|
/* Mixed, use an "=" case */
|
|
operation = '=';
|
|
flag_s = cap_flags_to_string(cap_flag);
|
|
#if CAP_PRINT_RELATIVE
|
|
}
|
|
#endif
|
|
/*
|
|
* Now assemble clause, and append to the string being
|
|
* built.
|
|
*/
|
|
if (any_so_far)
|
|
prefix_s = ":";
|
|
else
|
|
prefix_s = "";
|
|
value_s = cap_to_string(cap_value);
|
|
if ((snprintf(minibuf, sizeof(minibuf), "%s%s%c%s", prefix_s,
|
|
value_s, operation, flag_s) >= sizeof(minibuf)) ||
|
|
(strlcat(buf, minibuf, CAP_MAX_BUF_LEN) >=
|
|
CAP_MAX_BUF_LEN)) {
|
|
free(buf);
|
|
errno = ENOMEM;
|
|
return (NULL);
|
|
}
|
|
}
|
|
|
|
if (len_p)
|
|
*len_p = strlen(buf);
|
|
return (buf);
|
|
}
|
|
|
|
cap_t
|
|
cap_from_text(const char *buf_p)
|
|
{
|
|
cap_value_t cap_value_v, cap_value_set_v;
|
|
cap_flag_t cap_action_v;
|
|
cap_t cap;
|
|
char *mybuf, *cur;
|
|
char *clause_s, *cap_value_s, *cap_value_list_s;
|
|
char *cap_action_list_s, *cap_action_s;
|
|
char *next_operation_p, operation, next_operation;
|
|
|
|
cap = cap_init();
|
|
if (cap == NULL)
|
|
return ((cap_t)NULL);
|
|
|
|
mybuf = strdup(buf_p);
|
|
if (mybuf == NULL) {
|
|
errno = ENOMEM;
|
|
goto err1;
|
|
}
|
|
|
|
/*
|
|
* clase [SEP clause [SEP clause ...]]
|
|
* Split into "clauses", which are separated by a : or whitespace.
|
|
*
|
|
* clause = [caplist]actionlist
|
|
* caplist = capabilityname[,capabilityname[, ...]]
|
|
* actionlist = op[flags][op[flags]]
|
|
* Split clauses into a (possibly null) capability name list, and a
|
|
* set of one or more {op,flags} pairs.
|
|
*
|
|
* Each assignment is then applied to a running "state" to
|
|
* produce an end-result in the internal representation.
|
|
* Parsing failure at any time releases resources and results
|
|
* in EINVAL.
|
|
*/
|
|
cur = mybuf;
|
|
while ((clause_s = strsep(&cur, CAP_SEP)) != NULL) {
|
|
/*
|
|
* Identify and NULL the first operation so that we
|
|
* can parse the capability name list, but save
|
|
* for later when we iterate over the operation list.
|
|
*/
|
|
cap_action_list_s = clause_s;
|
|
next_operation_p = strpbrk(cap_action_list_s, CAP_OPERATION);
|
|
if (next_operation_p == NULL)
|
|
goto err2;
|
|
operation = *next_operation_p;
|
|
cap_value_list_s = strsep(&cap_action_list_s, CAP_OPERATION);
|
|
if (cap_value_list_s == NULL || cap_action_list_s == NULL)
|
|
goto err2;
|
|
/*
|
|
* cap_value_list_s now points at the NULL-terminated list
|
|
* of capability values, if any.
|
|
* cap_action_list_s now points to the NULL-terminated list
|
|
* of actions.
|
|
*
|
|
* First, parse the value list to generate a value set
|
|
* refering to the combined contents of the value list.
|
|
*/
|
|
cap_value_set_v = 0;
|
|
while ((cap_value_s = strsep(&cap_value_list_s, ",")) != NULL) {
|
|
/*
|
|
* Convert value string into internal representation.
|
|
* Reject if not a valid capability identifier.
|
|
*/
|
|
if (cap_from_string(cap_value_s, &cap_value_v))
|
|
goto err2;
|
|
cap_value_set_v |= cap_value_v;
|
|
}
|
|
|
|
/*
|
|
* While the current operation is non-0, parse its flags,
|
|
* apply the actions, and then repeat. The first set
|
|
* is assured above when the capability list is split off.
|
|
*/
|
|
while (operation != 0) {
|
|
/*
|
|
* Identify and save the next operation, then NULL
|
|
* it to find the end of the current flags.
|
|
*/
|
|
next_operation_p = strpbrk(cap_action_list_s,
|
|
CAP_OPERATION);
|
|
if (next_operation_p)
|
|
next_operation = *next_operation_p;
|
|
else
|
|
next_operation = 0;
|
|
cap_action_s = strsep(&cap_action_list_s,
|
|
CAP_OPERATION);
|
|
/*
|
|
* Convert string form of flags to internal
|
|
* representation, reject if not possible.
|
|
*/
|
|
if (cap_string_to_flags(cap_action_s, &cap_action_v))
|
|
goto err2;
|
|
|
|
/*
|
|
* Now, based on operation apply actionlist flags
|
|
* to the capability value set built earlier from
|
|
* the capability list.
|
|
*/
|
|
switch (operation) {
|
|
case '=':
|
|
/*
|
|
* Remove current flags for the value set,
|
|
* replace with new flags.
|
|
*
|
|
* Spec requires that an "=" operation with
|
|
* no value set be treated as an "=" operation
|
|
* with a value set equivilent to "all".
|
|
*/
|
|
if (cap_value_set_v == CAP_ALL_OFF) {
|
|
cap_set(cap, CAP_EFFECTIVE|
|
|
CAP_INHERITABLE|CAP_PERMITTED,
|
|
CAP_CLEAR, CAP_ALL_ON);
|
|
cap_set(cap, cap_action_v, CAP_SET,
|
|
CAP_ALL_ON);
|
|
} else {
|
|
cap_set(cap, CAP_EFFECTIVE|
|
|
CAP_INHERITABLE|CAP_PERMITTED,
|
|
CAP_CLEAR, cap_value_set_v);
|
|
cap_set(cap, cap_action_v, CAP_SET,
|
|
cap_value_set_v);
|
|
}
|
|
break;
|
|
case '+':
|
|
/*
|
|
* Add current flags to value set.
|
|
*
|
|
* Spec requires that a "+" operation with
|
|
* no value set be rejected.
|
|
*/
|
|
if (cap_value_set_v == CAP_ALL_OFF)
|
|
goto err2;
|
|
cap_set(cap, cap_action_v, CAP_SET,
|
|
cap_value_set_v);
|
|
break;
|
|
case '-':
|
|
/*
|
|
* Subtract current flags from value set.
|
|
*
|
|
* Spec requires that a "-" operation with
|
|
* no value set be treated as a "-" operation
|
|
* with a value set equivilent to "all".
|
|
*/
|
|
if (cap_value_set_v == CAP_ALL_OFF)
|
|
cap_set(cap, cap_action_v, CAP_CLEAR,
|
|
CAP_ALL_ON);
|
|
else
|
|
cap_set(cap, cap_action_v, CAP_CLEAR,
|
|
cap_value_set_v);
|
|
break;
|
|
default:
|
|
goto err2;
|
|
}
|
|
operation = next_operation;
|
|
}
|
|
}
|
|
|
|
return (cap);
|
|
err2:
|
|
errno = EINVAL;
|
|
free(mybuf);
|
|
err1:
|
|
cap_free(cap);
|
|
return ((cap_t)NULL);
|
|
}
|
|
|