47ae8457 |
/*
* OpenVPN -- An application to securely tunnel IP networks
* over a single TCP/UDP port, with support for SSL/TLS-based
* session authentication and key exchange,
* packet encryption, packet authentication, and
* packet compression.
* |
49979459 |
* Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net> |
47ae8457 |
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* |
caa54ac3 |
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
47ae8457 |
*/
/* packet filter functions */
|
c110b289 |
#ifdef HAVE_CONFIG_H
#include "config.h"
#elif defined(_MSC_VER)
#include "config-msvc.h"
#endif
|
47ae8457 |
#include "syshead.h"
#if defined(ENABLE_PF)
#include "init.h"
#include "memdbg.h" |
a54f37d8 |
#include "pf.h" |
63dc03d0 |
#include "ssl_verify.h" |
47ae8457 |
|
90efcacb |
|
47ae8457 |
static void |
81d882d5 |
pf_destroy(struct pf_set *pfs) |
47ae8457 |
{ |
81d882d5 |
if (pfs)
{
if (pfs->cns.hash_table)
{
hash_free(pfs->cns.hash_table);
}
{
struct pf_cn_elem *l = pfs->cns.list;
while (l)
{
struct pf_cn_elem *next = l->next;
free(l->rule.cn);
free(l);
l = next;
}
}
{
struct pf_subnet *l = pfs->sns.list;
while (l)
{
struct pf_subnet *next = l->next;
free(l);
l = next;
}
}
free(pfs); |
47ae8457 |
}
}
static bool |
81d882d5 |
add_client(const char *line, const char *prefix, const int line_num, struct pf_cn_elem ***next, const bool exclude) |
47ae8457 |
{ |
81d882d5 |
struct pf_cn_elem *e;
ALLOC_OBJ_CLEAR(e, struct pf_cn_elem);
e->rule.exclude = exclude;
e->rule.cn = string_alloc(line, NULL);
**next = e;
*next = &e->next;
return true; |
47ae8457 |
}
static bool |
81d882d5 |
add_subnet(const char *line, const char *prefix, const int line_num, struct pf_subnet ***next, const bool exclude) |
47ae8457 |
{ |
81d882d5 |
struct in_addr network;
in_addr_t netmask = 0;
if (strcmp(line, "unknown"))
{
int netbits = 32;
char *div = strchr(line, '/');
if (div)
{
*div++ = '\0';
if (sscanf(div, "%d", &netbits) != 1)
{
msg(D_PF_INFO, "PF: %s/%d: bad '/n' subnet specifier: '%s'", prefix, line_num, div);
return false;
}
if (netbits < 0 || netbits > 32)
{
msg(D_PF_INFO, "PF: %s/%d: bad '/n' subnet specifier: must be between 0 and 32: '%s'", prefix, line_num, div);
return false;
}
}
if (openvpn_inet_aton(line, &network) != OIA_IP)
{
msg(D_PF_INFO, "PF: %s/%d: bad network address: '%s'", prefix, line_num, line);
return false;
}
netmask = netbits_to_netmask(netbits);
if ((network.s_addr & htonl(netmask)) != network.s_addr) |
d2ad24c0 |
{ |
81d882d5 |
network.s_addr &= htonl(netmask);
msg(M_WARN, "WARNING: PF: %s/%d: incorrect subnet %s/%d changed to %s/%d", prefix, line_num, line, netbits, inet_ntoa(network), netbits); |
d2ad24c0 |
} |
47ae8457 |
} |
81d882d5 |
else |
47ae8457 |
{ |
81d882d5 |
/* match special "unknown" tag for addresses unrecognized by mroute */
network.s_addr = htonl(0);
netmask = IPV4_NETMASK_HOST; |
47ae8457 |
}
|
81d882d5 |
{
struct pf_subnet *e;
ALLOC_OBJ_CLEAR(e, struct pf_subnet);
e->rule.exclude = exclude;
e->rule.network = ntohl(network.s_addr);
e->rule.netmask = netmask;
**next = e;
*next = &e->next;
return true;
} |
47ae8457 |
}
static uint32_t |
81d882d5 |
cn_hash_function(const void *key, uint32_t iv) |
47ae8457 |
{ |
81d882d5 |
return hash_func((uint8_t *)key, strlen((char *)key) + 1, iv); |
47ae8457 |
}
static bool |
81d882d5 |
cn_compare_function(const void *key1, const void *key2) |
47ae8457 |
{ |
81d882d5 |
return !strcmp((const char *)key1, (const char *)key2); |
47ae8457 |
}
static bool |
81d882d5 |
genhash(struct pf_cn_set *cns, const char *prefix, const int n_clients) |
47ae8457 |
{ |
81d882d5 |
struct pf_cn_elem *e;
bool status = true;
int n_buckets = n_clients;
if (n_buckets < 16)
{
n_buckets = 16;
}
cns->hash_table = hash_init(n_buckets, 0, cn_hash_function, cn_compare_function);
for (e = cns->list; e != NULL; e = e->next)
{
if (!hash_add(cns->hash_table, e->rule.cn, &e->rule, false))
{
msg(D_PF_INFO, "PF: %s: duplicate common name in [clients] section: '%s'", prefix, e->rule.cn);
status = false;
}
}
return status; |
47ae8457 |
}
static struct pf_set * |
81d882d5 |
pf_init(const struct buffer_list *bl, const char *prefix, const bool allow_kill) |
47ae8457 |
{ |
81d882d5 |
#define MODE_UNDEF 0
#define MODE_CLIENTS 1
#define MODE_SUBNETS 2
int mode = MODE_UNDEF;
int line_num = 0;
int n_clients = 0;
int n_subnets = 0;
int n_errors = 0;
struct pf_set *pfs = NULL;
char line[PF_MAX_LINE_LEN];
ALLOC_OBJ_CLEAR(pfs, struct pf_set);
if (bl)
{
struct pf_cn_elem **cl = &pfs->cns.list;
struct pf_subnet **sl = &pfs->sns.list;
struct buffer_entry *be;
for (be = bl->head; be != NULL; be = be->next)
{
++line_num;
strncpynt(line, BSTR(&be->buf), sizeof(line));
rm_trailing_chars(line, "\r\n\t ");
if (line[0] == '\0' || line[0] == '#')
{
}
else if (line[0] == '+' || line[0] == '-')
{
bool exclude = (line[0] == '-');
if (line[1] =='\0')
{
msg(D_PF_INFO, "PF: %s/%d: no data after +/-: '%s'", prefix, line_num, line);
++n_errors;
}
else if (mode == MODE_CLIENTS)
{
if (add_client(&line[1], prefix, line_num, &cl, exclude))
{
++n_clients;
}
else
{
++n_errors;
}
}
else if (mode == MODE_SUBNETS)
{
if (add_subnet(&line[1], prefix, line_num, &sl, exclude))
{
++n_subnets;
}
else
{
++n_errors;
}
}
else if (mode == MODE_UNDEF)
{
}
else
{
ASSERT(0);
}
}
else if (line[0] == '[')
{
if (!strcasecmp(line, "[clients accept]"))
{
mode = MODE_CLIENTS;
pfs->cns.default_allow = true;
}
else if (!strcasecmp(line, "[clients drop]"))
{
mode = MODE_CLIENTS;
pfs->cns.default_allow = false;
}
else if (!strcasecmp(line, "[subnets accept]"))
{
mode = MODE_SUBNETS;
pfs->sns.default_allow = true;
}
else if (!strcasecmp(line, "[subnets drop]"))
{
mode = MODE_SUBNETS;
pfs->sns.default_allow = false;
}
else if (!strcasecmp(line, "[end]"))
{
goto done;
}
else if (allow_kill && !strcasecmp(line, "[kill]"))
{
goto kill;
}
else
{
mode = MODE_UNDEF;
msg(D_PF_INFO, "PF: %s/%d unknown tag: '%s'", prefix, line_num, line);
++n_errors;
}
}
else
{
msg(D_PF_INFO, "PF: %s/%d line must begin with '+', '-', or '[' : '%s'", prefix, line_num, line);
++n_errors;
}
}
++n_errors;
msg(D_PF_INFO, "PF: %s: missing [end]", prefix);
}
else
{
msg(D_PF_INFO, "PF: %s: cannot open", prefix);
++n_errors;
}
done:
if (bl)
{
if (!n_errors)
{
if (!genhash(&pfs->cns, prefix, n_clients))
{
++n_errors;
}
}
if (n_errors)
{
msg(D_PF_INFO, "PF: %s rejected due to %d error(s)", prefix, n_errors);
}
}
if (n_errors)
{
pf_destroy(pfs);
pfs = NULL;
}
return pfs;
kill:
pf_destroy(pfs);
ALLOC_OBJ_CLEAR(pfs, struct pf_set);
pfs->kill = true;
return pfs; |
47ae8457 |
}
|
90efcacb |
#ifdef PLUGIN_PF
static struct pf_set * |
81d882d5 |
pf_init_from_file(const char *fn) |
90efcacb |
{ |
81d882d5 |
struct buffer_list *bl = buffer_list_file(fn, PF_MAX_LINE_LEN);
if (bl) |
90efcacb |
{ |
81d882d5 |
struct pf_set *pfs = pf_init(bl, fn, true);
buffer_list_free(bl);
return pfs; |
90efcacb |
} |
81d882d5 |
else |
90efcacb |
{ |
81d882d5 |
msg(D_PF_INFO|M_ERRNO, "PF: %s: cannot open", fn);
return NULL; |
90efcacb |
}
}
#endif
#ifdef ENABLE_DEBUG |
47ae8457 |
static const char * |
81d882d5 |
drop_accept(const bool accept) |
47ae8457 |
{ |
81d882d5 |
return accept ? "ACCEPT" : "DROP"; |
47ae8457 |
}
|
90efcacb |
static const char * |
81d882d5 |
pct_name(const int type) |
90efcacb |
{ |
81d882d5 |
switch (type) |
90efcacb |
{ |
81d882d5 |
case PCT_SRC:
return "SRC";
case PCT_DEST:
return "DEST";
default:
return "???"; |
90efcacb |
}
} |
47ae8457 |
static void |
81d882d5 |
pf_cn_test_print(const char *prefix,
const int type,
const char *prefix2,
const char *cn,
const bool allow,
const struct pf_cn *rule) |
47ae8457 |
{ |
81d882d5 |
if (rule) |
47ae8457 |
{ |
81d882d5 |
dmsg(D_PF_DEBUG, "PF: %s/%s/%s %s %s rule=[%s %s]",
prefix, prefix2, pct_name(type),
cn, drop_accept(allow),
rule->cn, drop_accept(!rule->exclude)); |
47ae8457 |
} |
81d882d5 |
else |
47ae8457 |
{ |
81d882d5 |
dmsg(D_PF_DEBUG, "PF: %s/%s/%s %s %s",
prefix, prefix2, pct_name(type),
cn, drop_accept(allow)); |
47ae8457 |
}
}
static void |
81d882d5 |
pf_addr_test_print(const char *prefix,
const char *prefix2,
const struct context *src,
const struct mroute_addr *dest,
const bool allow,
const struct ipv4_subnet *rule) |
47ae8457 |
{ |
81d882d5 |
struct gc_arena gc = gc_new();
if (rule)
{
dmsg(D_PF_DEBUG, "PF: %s/%s %s %s %s rule=[%s/%s %s]",
prefix,
prefix2,
tls_common_name(src->c2.tls_multi, false),
mroute_addr_print_ex(dest, MAPF_SHOW_ARP, &gc),
drop_accept(allow),
print_in_addr_t(rule->network, 0, &gc),
print_in_addr_t(rule->netmask, 0, &gc),
drop_accept(!rule->exclude));
}
else
{
dmsg(D_PF_DEBUG, "PF: %s/%s %s %s %s",
prefix,
prefix2,
tls_common_name(src->c2.tls_multi, false),
mroute_addr_print_ex(dest, MAPF_SHOW_ARP, &gc),
drop_accept(allow));
}
gc_free(&gc); |
47ae8457 |
}
|
81d882d5 |
#endif /* ifdef ENABLE_DEBUG */ |
47ae8457 |
static inline struct pf_cn * |
81d882d5 |
lookup_cn_rule(struct hash *h, const char *cn, const uint32_t cn_hash) |
47ae8457 |
{ |
81d882d5 |
struct hash_element *he = hash_lookup_fast(h, hash_bucket(h, cn_hash), cn, cn_hash);
if (he)
{
return (struct pf_cn *) he->value;
}
else
{
return NULL;
} |
47ae8457 |
}
|
90efcacb |
bool |
81d882d5 |
pf_cn_test(struct pf_set *pfs, const struct tls_multi *tm, const int type, const char *prefix) |
47ae8457 |
{ |
81d882d5 |
if (pfs && !pfs->kill)
{
const char *cn;
uint32_t cn_hash;
if (tls_common_name_hash(tm, &cn, &cn_hash))
{
const struct pf_cn *rule = lookup_cn_rule(pfs->cns.hash_table, cn, cn_hash);
if (rule)
{ |
90efcacb |
#ifdef ENABLE_DEBUG |
81d882d5 |
if (check_debug_level(D_PF_DEBUG))
{
pf_cn_test_print("PF_CN_MATCH", type, prefix, cn, !rule->exclude, rule);
} |
47ae8457 |
#endif |
81d882d5 |
if (!rule->exclude)
{
return true;
}
else
{
return false;
}
}
else
{ |
90efcacb |
#ifdef ENABLE_DEBUG |
81d882d5 |
if (check_debug_level(D_PF_DEBUG))
{
pf_cn_test_print("PF_CN_DEFAULT", type, prefix, cn, pfs->cns.default_allow, NULL);
} |
47ae8457 |
#endif |
81d882d5 |
if (pfs->cns.default_allow)
{
return true;
}
else
{
return false;
}
}
} |
47ae8457 |
} |
90efcacb |
#ifdef ENABLE_DEBUG |
81d882d5 |
if (check_debug_level(D_PF_DEBUG))
{
pf_cn_test_print("PF_CN_FAULT", type, prefix, tls_common_name(tm, false), false, NULL);
} |
47ae8457 |
#endif |
81d882d5 |
return false; |
47ae8457 |
}
bool |
81d882d5 |
pf_addr_test_dowork(const struct context *src, const struct mroute_addr *dest, const char *prefix) |
47ae8457 |
{ |
81d882d5 |
struct pf_set *pfs = src->c2.pf.pfs;
if (pfs && !pfs->kill)
{
const in_addr_t addr = in_addr_t_from_mroute_addr(dest);
const struct pf_subnet *se = pfs->sns.list;
while (se)
{
if ((addr & se->rule.netmask) == se->rule.network)
{ |
90efcacb |
#ifdef ENABLE_DEBUG |
81d882d5 |
if (check_debug_level(D_PF_DEBUG))
{
pf_addr_test_print("PF_ADDR_MATCH", prefix, src, dest, !se->rule.exclude, &se->rule);
} |
47ae8457 |
#endif |
81d882d5 |
return !se->rule.exclude;
}
se = se->next;
} |
90efcacb |
#ifdef ENABLE_DEBUG |
81d882d5 |
if (check_debug_level(D_PF_DEBUG))
{
pf_addr_test_print("PF_ADDR_DEFAULT", prefix, src, dest, pfs->sns.default_allow, NULL);
} |
47ae8457 |
#endif |
81d882d5 |
return pfs->sns.default_allow; |
47ae8457 |
} |
81d882d5 |
else |
47ae8457 |
{ |
90efcacb |
#ifdef ENABLE_DEBUG |
81d882d5 |
if (check_debug_level(D_PF_DEBUG))
{
pf_addr_test_print("PF_ADDR_FAULT", prefix, src, dest, false, NULL);
} |
90efcacb |
#endif |
81d882d5 |
return false; |
47ae8457 |
}
}
|
90efcacb |
#ifdef PLUGIN_PF |
47ae8457 |
void |
81d882d5 |
pf_check_reload(struct context *c) |
47ae8457 |
{ |
81d882d5 |
const int slow_wakeup = 15;
const int fast_wakeup = 1;
const int wakeup_transition = 60;
bool reloaded = false;
if (c->c2.pf.enabled
&& c->c2.pf.filename
&& event_timeout_trigger(&c->c2.pf.reload, &c->c2.timeval, ETT_DEFAULT))
{
platform_stat_t s;
if (!platform_stat(c->c2.pf.filename, &s))
{
if (s.st_mtime > c->c2.pf.file_last_mod)
{
struct pf_set *pfs = pf_init_from_file(c->c2.pf.filename);
if (pfs)
{
if (c->c2.pf.pfs)
{
pf_destroy(c->c2.pf.pfs);
}
c->c2.pf.pfs = pfs;
reloaded = true;
if (pf_kill_test(pfs))
{
c->sig->signal_received = SIGTERM;
c->sig->signal_text = "pf-kill";
}
}
c->c2.pf.file_last_mod = s.st_mtime;
}
}
{
int wakeup = slow_wakeup;
if (!c->c2.pf.pfs && c->c2.pf.n_check_reload < wakeup_transition)
{
wakeup = fast_wakeup;
}
event_timeout_init(&c->c2.pf.reload, wakeup, now);
reset_coarse_timers(c);
c->c2.pf.n_check_reload++;
} |
47ae8457 |
} |
90efcacb |
#ifdef ENABLE_DEBUG |
81d882d5 |
if (reloaded && check_debug_level(D_PF_DEBUG))
{
pf_context_print(&c->c2.pf, "pf_check_reload", D_PF_DEBUG);
} |
47ae8457 |
#endif
} |
81d882d5 |
#endif /* ifdef PLUGIN_PF */ |
90efcacb |
#ifdef MANAGEMENT_PF
bool |
81d882d5 |
pf_load_from_buffer_list(struct context *c, const struct buffer_list *config) |
90efcacb |
{ |
81d882d5 |
struct pf_set *pfs = pf_init(config, "[SERVER-PF]", false);
if (pfs) |
90efcacb |
{ |
81d882d5 |
if (c->c2.pf.pfs)
{
pf_destroy(c->c2.pf.pfs);
}
c->c2.pf.pfs = pfs;
return true;
}
else
{
return false; |
90efcacb |
}
}
#endif |
47ae8457 |
void |
81d882d5 |
pf_init_context(struct context *c) |
47ae8457 |
{ |
90efcacb |
#ifdef PLUGIN_PF |
81d882d5 |
if (plugin_defined(c->plugins, OPENVPN_PLUGIN_ENABLE_PF)) |
47ae8457 |
{ |
b7bea782 |
c->c2.pf.filename = platform_create_temp_file(c->options.tmp_dir, "pf",
&c->c2.gc); |
d2342067 |
if (c->c2.pf.filename) |
81d882d5 |
{ |
d2342067 |
setenv_str(c->c2.es, "pf_file", c->c2.pf.filename); |
495e3cec |
|
81d882d5 |
if (plugin_call(c->plugins, OPENVPN_PLUGIN_ENABLE_PF, NULL, NULL, c->c2.es) == OPENVPN_PLUGIN_FUNC_SUCCESS)
{
event_timeout_init(&c->c2.pf.reload, 1, now);
c->c2.pf.enabled = true; |
90efcacb |
#ifdef ENABLE_DEBUG |
81d882d5 |
if (check_debug_level(D_PF_DEBUG))
{
pf_context_print(&c->c2.pf, "pf_init_context#1", D_PF_DEBUG);
} |
47ae8457 |
#endif |
81d882d5 |
} |
492e42d3 |
}
if (!c->c2.pf.enabled)
{
msg(M_WARN, "WARNING: failed to init PF plugin, rejecting client.");
register_signal(c, SIGUSR1, "plugin-pf-init-failed");
return; |
81d882d5 |
} |
47ae8457 |
} |
81d882d5 |
#endif /* ifdef PLUGIN_PF */ |
90efcacb |
#ifdef MANAGEMENT_PF |
81d882d5 |
if (!c->c2.pf.enabled && management_enable_pf(management)) |
90efcacb |
{ |
81d882d5 |
c->c2.pf.enabled = true; |
90efcacb |
#ifdef ENABLE_DEBUG |
81d882d5 |
if (check_debug_level(D_PF_DEBUG))
{
pf_context_print(&c->c2.pf, "pf_init_context#2", D_PF_DEBUG);
} |
90efcacb |
#endif
}
#endif |
47ae8457 |
}
void |
81d882d5 |
pf_destroy_context(struct pf_context *pfc) |
47ae8457 |
{ |
90efcacb |
#ifdef PLUGIN_PF |
81d882d5 |
if (pfc->filename) |
47ae8457 |
{ |
81d882d5 |
platform_unlink(pfc->filename); |
47ae8457 |
} |
90efcacb |
#endif |
81d882d5 |
if (pfc->pfs)
{
pf_destroy(pfc->pfs);
} |
47ae8457 |
}
|
90efcacb |
#ifdef ENABLE_DEBUG |
47ae8457 |
static void |
81d882d5 |
pf_subnet_set_print(const struct pf_subnet_set *s, const int lev) |
47ae8457 |
{ |
81d882d5 |
struct gc_arena gc = gc_new();
if (s) |
47ae8457 |
{ |
81d882d5 |
struct pf_subnet *e; |
47ae8457 |
|
81d882d5 |
msg(lev, " ----- struct pf_subnet_set -----");
msg(lev, " default_allow=%s", drop_accept(s->default_allow)); |
47ae8457 |
|
81d882d5 |
for (e = s->list; e != NULL; e = e->next)
{
msg(lev, " %s/%s %s",
print_in_addr_t(e->rule.network, 0, &gc),
print_in_addr_t(e->rule.netmask, 0, &gc),
drop_accept(!e->rule.exclude));
} |
47ae8457 |
} |
81d882d5 |
gc_free(&gc); |
47ae8457 |
}
static void |
81d882d5 |
pf_cn_set_print(const struct pf_cn_set *s, const int lev) |
47ae8457 |
{ |
81d882d5 |
if (s)
{
struct hash_iterator hi;
struct hash_element *he;
msg(lev, " ----- struct pf_cn_set -----");
msg(lev, " default_allow=%s", drop_accept(s->default_allow));
if (s->hash_table)
{
hash_iterator_init(s->hash_table, &hi);
while ((he = hash_iterator_next(&hi)))
{
struct pf_cn *e = (struct pf_cn *)he->value;
msg(lev, " %s %s",
e->cn,
drop_accept(!e->exclude));
}
msg(lev, " ----------");
{
struct pf_cn_elem *ce;
for (ce = s->list; ce != NULL; ce = ce->next)
{
struct pf_cn *e = lookup_cn_rule(s->hash_table, ce->rule.cn, cn_hash_function(ce->rule.cn, 0));
if (e)
{
msg(lev, " %s %s",
e->cn,
drop_accept(!e->exclude));
}
else
{
msg(lev, " %s LOOKUP FAILED", ce->rule.cn);
}
}
}
} |
47ae8457 |
}
}
static void |
81d882d5 |
pf_set_print(const struct pf_set *pfs, const int lev) |
47ae8457 |
{ |
81d882d5 |
if (pfs) |
47ae8457 |
{ |
81d882d5 |
msg(lev, " ----- struct pf_set -----");
msg(lev, " kill=%d", pfs->kill);
pf_subnet_set_print(&pfs->sns, lev);
pf_cn_set_print(&pfs->cns, lev); |
47ae8457 |
}
}
void |
81d882d5 |
pf_context_print(const struct pf_context *pfc, const char *prefix, const int lev) |
47ae8457 |
{ |
81d882d5 |
msg(lev, "----- %s : struct pf_context -----", prefix);
if (pfc) |
47ae8457 |
{ |
81d882d5 |
msg(lev, "enabled=%d", pfc->enabled); |
90efcacb |
#ifdef PLUGIN_PF |
81d882d5 |
msg(lev, "filename='%s'", np(pfc->filename));
msg(lev, "file_last_mod=%u", (unsigned int)pfc->file_last_mod);
msg(lev, "n_check_reload=%u", pfc->n_check_reload);
msg(lev, "reload=[%d,%u,%u]", pfc->reload.defined, pfc->reload.n, (unsigned int)pfc->reload.last); |
90efcacb |
#endif |
81d882d5 |
pf_set_print(pfc->pfs, lev); |
47ae8457 |
} |
81d882d5 |
msg(lev, "--------------------"); |
47ae8457 |
}
|
81d882d5 |
#endif /* ifdef ENABLE_DEBUG */ |
47ae8457 |
|
81d882d5 |
#endif /* if defined(ENABLE_PF) */ |