The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
base.c
Go to the documentation of this file.
1/*
2 * This program is is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or (at
5 * your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/**
18 * $Id: 16bee413956a7252e2b5278b4e57ddc37a7d3ead $
19 * @file curl/base.c
20 * @brief Curl global initialisation
21 *
22 * @copyright 2020 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
23 */
24#include <freeradius-devel/curl/base.h>
25#include <freeradius-devel/curl/xlat.h>
26#ifdef WITH_TLS
27#include <freeradius-devel/tls/base.h>
28#endif
29
30#include <freeradius-devel/util/talloc.h>
31#include <freeradius-devel/util/syserror.h>
32#include <freeradius-devel/unlang/xlat_func.h>
33
34#include "attrs.h"
35
37static fr_dict_t const *dict_freeradius; /*internal dictionary for server*/
38
41 { .out = &attr_tls_certificate, .name = "TLS-Certificate", .type = FR_TYPE_TLV, .dict = &dict_freeradius },
42 { NULL }
43};
44
46 { .out = &dict_freeradius, .proto = "freeradius" },
47 { NULL }
48};
49
51 {L("allow"), CURLUSESSL_TRY },
52 {L("demand"), CURLUSESSL_ALL },
53 {L("never"), CURLUSESSL_NONE },
54};
56
58{
59 char const *ca_path = NULL;
60#if CURL_AT_LEAST_VERSION(7,70,0)
61 ca_path = curl_version_info(CURLVERSION_NOW)->capath;
62#endif
63 if (!ca_path) return 0;
64 MEM(*out = cf_pair_alloc(cs, rule->name1, ca_path, T_OP_EQ, T_BARE_WORD, quote));
65 return 0;
66}
67
70 { FR_CONF_OFFSET_FLAGS("ca_issuer_file", CONF_FLAG_FILE_INPUT, fr_curl_tls_t, ca_issuer_file) },
72 { FR_CONF_OFFSET_FLAGS("certificate_file", CONF_FLAG_FILE_INPUT, fr_curl_tls_t, certificate_file) },
73 { FR_CONF_OFFSET_FLAGS("private_key_file", CONF_FLAG_FILE_INPUT, fr_curl_tls_t, private_key_file) },
74 { FR_CONF_OFFSET_FLAGS("private_key_password", CONF_FLAG_SECRET, fr_curl_tls_t, private_key_password) },
75 { FR_CONF_OFFSET("random_file", fr_curl_tls_t, random_file) },
76 { FR_CONF_OFFSET_TYPE_FLAGS("require_cert", FR_TYPE_VOID, 0, fr_curl_tls_t, require_cert),
78 .uctx = &(cf_table_parse_ctx_t){
79 .table = fr_curl_sslcode_table,
81 },
82 .dflt = "allow" },
83 { FR_CONF_OFFSET("check_cert", fr_curl_tls_t, check_cert), .dflt = "yes" },
84 { FR_CONF_OFFSET("check_cert_cn", fr_curl_tls_t, check_cert_cn), .dflt = "yes" },
85 { FR_CONF_OFFSET("extract_cert_attrs", fr_curl_tls_t, extract_cert_attrs), .dflt = "no" },
86#ifdef WITH_TLS
87 { FR_CONF_OFFSET_FLAGS("keylog_file", CONF_FLAG_FILE_OUTPUT, fr_curl_tls_t, keylog_file) },
88#endif
90};
91
96
99 { FR_CONF_OFFSET("connect_timeout", fr_curl_conn_config_t, connect_timeout), .dflt = "3.0" },
101};
102
103#ifdef WITH_TLS
104static void _curl_easy_tls_keylog(const SSL *ssl, const char *line)
105{
106 fr_curl_tls_t const *conf = SSL_CTX_get_ex_data(SSL_get_SSL_CTX(ssl), FR_TLS_EX_INDEX_CURL_CONF);
107
108 FILE *fp = fopen(conf->keylog_file, "a");
109 if (!fp) {
110 RATE_LIMIT_GLOBAL(WARN, "Failed opening keylog file \"%s\" - %s", conf->keylog_file, fr_syserror(errno));
111 return;
112 }
113
114 /*
115 * POSIX states fprintf calls must not intermingle
116 * data being written to the same file, and as all
117 * keying material is written on the same line, this
118 * should be safe.
119 */
120 fprintf(fp, "%s\n", line);
121 fclose(fp);
122}
123
124static CURLcode _curl_easy_ssl_ctx_conf(UNUSED CURL *curl, void *ssl_ctx, void *clientp)
125{
126 SSL_CTX *ctx = ssl_ctx;
127 fr_curl_tls_t const *conf = clientp; /* May not be talloced */
128
129 SSL_CTX_set_ex_data(ctx, FR_TLS_EX_INDEX_CURL_CONF, UNCONST(void *, conf));
130
131 if (conf->keylog_file) {
132 SSL_CTX_set_keylog_callback(ctx, _curl_easy_tls_keylog);
133 }
134
135 return CURLE_OK;
136}
137#endif
138
140{
141 request_t *request = randle->request;
142
143 if (conf->certificate_file) FR_CURL_ROPTIONAL_SET_OPTION(CURLOPT_SSLCERT, conf->certificate_file);
144 if (conf->private_key_file) FR_CURL_ROPTIONAL_SET_OPTION(CURLOPT_SSLKEY, conf->private_key_file);
145 if (conf->private_key_password) FR_CURL_ROPTIONAL_SET_OPTION(CURLOPT_KEYPASSWD, conf->private_key_password);
146 if (conf->ca_file) FR_CURL_ROPTIONAL_SET_OPTION(CURLOPT_CAINFO, conf->ca_file);
147 if (conf->ca_issuer_file) FR_CURL_ROPTIONAL_SET_OPTION(CURLOPT_ISSUERCERT, conf->ca_issuer_file);
148 if (conf->ca_path) FR_CURL_ROPTIONAL_SET_OPTION(CURLOPT_CAPATH, conf->ca_path);
149#if !CURL_AT_LEAST_VERSION(7,84,0)
150 if (conf->random_file) FR_CURL_ROPTIONAL_SET_OPTION(CURLOPT_RANDOM_FILE, conf->random_file);
151#endif
152 FR_CURL_ROPTIONAL_SET_OPTION(CURLOPT_USE_SSL, conf->require_cert);
153
154 FR_CURL_ROPTIONAL_SET_OPTION(CURLOPT_SSL_VERIFYPEER, (conf->check_cert == true) ? 1L : 0L);
155 FR_CURL_ROPTIONAL_SET_OPTION(CURLOPT_SSL_VERIFYHOST, (conf->check_cert_cn == true) ? 2L : 0L);
156 if (conf->extract_cert_attrs) FR_CURL_ROPTIONAL_SET_OPTION(CURLOPT_CERTINFO, 1L);
157
158#ifdef WITH_TLS
159 if (conf->keylog_file) {
160 FR_CURL_ROPTIONAL_SET_OPTION(CURLOPT_SSL_CTX_FUNCTION, _curl_easy_ssl_ctx_conf);
161 FR_CURL_ROPTIONAL_SET_OPTION(CURLOPT_SSL_CTX_DATA, conf);
162 }
163#endif
164
165 return 0;
166error:
167 return -1;
168}
169
171{
172 CURL *candle = randle->candle;
173 CURLcode ret;
174 int i;
175 char buffer[265];
176 char *p , *q;
177 fr_pair_list_t cert_vps;
178
179 struct curl_certinfo *to_certinfo = NULL;
180
181 fr_pair_list_init(&cert_vps);
182
183 ret = curl_easy_getinfo(candle, CURLINFO_CERTINFO, &to_certinfo);
184 if (ret != CURLE_OK) {
185 REDEBUG("Getting certificate info failed: %i - %s", ret, curl_easy_strerror(ret));
186
187 return -1;
188 }
189
190 /*
191 * There doesn't seem to be any way to determine if
192 * the session uses ssl or not, so if no certs are
193 * returned, we assume it's not an ssl session.
194 */
195 if (!to_certinfo || to_certinfo->num_of_certs == 0) return 0;
196
197 RDEBUG2("Chain has %i certificate(s)", to_certinfo->num_of_certs);
198 for (i = 0; i < to_certinfo->num_of_certs; i++) {
199 struct curl_slist *cert_attrs;
200 fr_pair_t *container;
201
202 MEM(container = fr_pair_afrom_da(request->request_ctx, attr_tls_certificate));
203 fr_pair_append(&cert_vps, container);
204
205 RDEBUG2("Processing certificate %i",i);
206
207 for (cert_attrs = to_certinfo->certinfo[i];
208 cert_attrs;
209 cert_attrs = cert_attrs->next) {
210 fr_pair_t *vp;
211 fr_dict_attr_t const *da;
212
213 q = strchr(cert_attrs->data, ':');
214 if (!q) {
215 RWDEBUG("Malformed certinfo from libcurl: %s", cert_attrs->data);
216 continue;
217 }
218
219 strlcpy(buffer, cert_attrs->data, (q - cert_attrs->data) + 1);
220 for (p = buffer; *p != '\0'; p++) if (*p == ' ') *p = '-';
221
223 if (!da) {
224 RDEBUG3("Skipping %s += '%s'", buffer, q + 1);
225 RDEBUG3("If this value is required, define attribute \"%s\"", buffer);
226 continue;
227 }
228 MEM(vp = fr_pair_afrom_da(container, da));
229 fr_pair_value_from_str(vp, q + 1, strlen(q + 1), NULL, true);
230
231 fr_pair_append(&container->vp_group, vp);
232 }
233 /*
234 * Add a copy of the cert_vps to the request list.
235 */
236 if (!fr_pair_list_empty(&cert_vps)) {
237 /*
238 * Print out all the pairs we have so far
239 */
240 log_request_pair_list(L_DBG_LVL_2, request, NULL, &cert_vps, NULL);
241 fr_pair_list_append(&request->request_pairs, &cert_vps);
242 }
243 }
244 return 0;
245}
246
247/** Free the curl easy handle
248 *
249 * @param[in] arg curl easy handle to free.
250 */
251static int _curl_tmpl_handle(void *arg)
252{
253 curl_easy_cleanup(arg);
254 return 0;
255}
256
257/** Return a thread local curl easy handle
258 *
259 * This should only be used for calls into libcurl functions
260 * which don't operate on an active request, like the
261 * escape/unescape functions.
262 *
263 * @return
264 * - A thread local curl easy handle.
265 * - NULL on failure.
266 */
268{
269 static _Thread_local CURL *t_candle;
270
271 if (unlikely(t_candle == NULL)) {
272 CURL *candle;
273
274 MEM(candle = curl_easy_init());
275 fr_atexit_thread_local(t_candle, _curl_tmpl_handle, candle);
276 }
277
278 return t_candle;
279}
280
281/** Initialise global curl options
282 *
283 * libcurl is meant to performa reference counting, but still seems to
284 * leak lots of memory if we call curl_global_init many times.
285 */
286static int fr_curl_init(void)
287{
288 CURLcode ret;
289 curl_version_info_data *curlversion;
290
291#ifdef WITH_TLS
292 /*
293 * Use our OpenSSL init with the hope that
294 * the free function will also free the
295 * memory allocated during SSL init.
296 */
297 if (fr_openssl_init() < 0) return -1;
298#endif
299
300 if (fr_dict_autoload(curl_dict) < 0) {
301 PERROR("Failed loading dictionaries for curl");
302 return -1;
303 }
304
306 PERROR("Failed loading dictionaries for curl");
307 return -1;
308 }
309
310 ret = curl_global_init(CURL_GLOBAL_ALL);
311 if (ret != CURLE_OK) {
312 ERROR("CURL init returned error: %i - %s", ret, curl_easy_strerror(ret));
313 error:
315 return -1;
316 }
317
318 curlversion = curl_version_info(CURLVERSION_NOW);
319 if (strcmp(LIBCURL_VERSION, curlversion->version) != 0) {
320 WARN("libcurl version changed since the server was built");
321 WARN("linked: %s built: %s", curlversion->version, LIBCURL_VERSION);
322 }
323
324 INFO("libcurl version: %s", curl_version());
325
326 {
327 xlat_t *xlat;
328
329 /*
330 * Generic escape function for all CURL based modules
331 * Use CURL_URI_SAFE_FOR within the module.
332 */
333 xlat = xlat_func_register(NULL, "uri.escape", fr_curl_xlat_uri_escape, FR_TYPE_STRING);
334 if (unlikely(!xlat)) {
335 ERROR("Failed registering \"uri.escape\" xlat");
336 goto error;
337 }
341
342 /*
343 * Generic safe function for all CURL based modules
344 * Use CURL_URI_SAFE_FOR within the module.
345 */
346 xlat = xlat_func_register(NULL, "uri.safe", xlat_transparent, FR_TYPE_STRING);
347 if (unlikely(!xlat)) {
348 ERROR("Failed registering \"uri.safe\" xlat");
349 goto error;
350 }
354
355 /*
356 * Generic unescape function for all CURL based modules
357 */
358 xlat = xlat_func_register(NULL, "uri.unescape", fr_curl_xlat_uri_unescape, FR_TYPE_STRING);
359 if (unlikely(!xlat)) {
360 ERROR("Failed registering \"uri.unescape\" xlat");
361 goto error;
362 }
365 }
366
367 return 0;
368}
369
370static void fr_curl_free(void)
371{
373
374#ifdef WITH_TLS
375 fr_openssl_free();
376#endif
377 curl_global_cleanup();
378
379 xlat_func_unregister("uri.escape");
380 xlat_func_unregister("uri.safe");
381 xlat_func_unregister("uri.unescape");
382}
383
384/*
385 * Public symbol modules can reference to auto instantiate libcurl
386 */
388 .name = "curl",
389 .init = fr_curl_init,
390 .free = fr_curl_free
391};
static int const char char buffer[256]
Definition acutest.h:576
int const char int line
Definition acutest.h:702
#define fr_atexit_thread_local(_name, _free, _uctx)
Definition atexit.h:221
#define UNCONST(_type, _ptr)
Remove const qualification from a pointer.
Definition build.h:167
#define L(_str)
Helper for initialising arrays of string literals.
Definition build.h:209
#define unlikely(_x)
Definition build.h:381
#define UNUSED
Definition build.h:315
#define NUM_ELEMENTS(_t)
Definition build.h:337
int cf_table_parse_int(UNUSED TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, conf_parser_t const *rule)
Generic function for parsing conf pair values as int.
Definition cf_parse.c:1550
#define CONF_PARSER_TERMINATOR
Definition cf_parse.h:642
cf_parse_t func
Override default parsing behaviour for the specified type with a custom parsing function.
Definition cf_parse.h:596
#define FR_CONF_OFFSET(_name, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
Definition cf_parse.h:268
char const * name1
Name of the CONF_ITEM to parse.
Definition cf_parse.h:580
#define FR_CONF_OFFSET_FLAGS(_name, _flags, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
Definition cf_parse.h:256
#define FR_CONF_OFFSET_SUBSECTION(_name, _flags, _struct, _field, _subcs)
conf_parser_t which populates a sub-struct using a CONF_SECTION
Definition cf_parse.h:297
@ CONF_FLAG_FILE_OUTPUT
File matching value must exist, and must be writable.
Definition cf_parse.h:426
@ CONF_FLAG_SECRET
Only print value if debug level >= 3.
Definition cf_parse.h:422
@ CONF_FLAG_FILE_INPUT
File matching value must exist, and must be readable.
Definition cf_parse.h:424
#define FR_CONF_OFFSET_TYPE_FLAGS(_name, _type, _flags, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
Definition cf_parse.h:241
Defines a CONF_PAIR to C data type mapping.
Definition cf_parse.h:579
Configuration AVP similar to a fr_pair_t.
Definition cf_priv.h:70
A section grouping multiple CONF_PAIR.
Definition cf_priv.h:101
CONF_PAIR * cf_pair_alloc(CONF_SECTION *parent, char const *attr, char const *value, fr_token_t op, fr_token_t lhs_quote, fr_token_t rhs_quote)
Allocate a CONF_PAIR.
Definition cf_util.c:1279
#define FR_CURL_ROPTIONAL_SET_OPTION(_x, _y)
Definition base.h:56
request_t * request
Current request.
Definition base.h:104
CURL * candle
Request specific handle.
Definition base.h:102
Structure representing an individual request being passed to curl for processing.
Definition base.h:101
xlat_action_t fr_curl_xlat_uri_escape(UNUSED TALLOC_CTX *ctx, fr_dcursor_t *out, UNUSED xlat_ctx_t const *xctx, UNUSED request_t *request, fr_value_box_list_t *in)
xlat function to escape URI encoded strings
Definition xlat.c:45
xlat_arg_parser_t const fr_curl_xlat_uri_args[]
Definition xlat.c:32
xlat_action_t fr_curl_xlat_uri_unescape(UNUSED TALLOC_CTX *ctx, fr_dcursor_t *out, UNUSED xlat_ctx_t const *xctx, UNUSED request_t *request, fr_value_box_list_t *in)
xlat function to unescape URI encoded strings
Definition xlat.c:73
xlat_arg_parser_t const fr_curl_xlat_safe_args[]
Definition xlat.c:37
#define CURL_URI_SAFE_FOR
safe for value suitable for all users of the curl library
Definition xlat.h:36
#define MEM(x)
Definition debug.h:36
#define ERROR(fmt,...)
Definition dhcpclient.c:41
#define fr_dict_autofree(_to_free)
Definition dict.h:853
fr_dict_attr_t const * fr_dict_attr_by_name(fr_dict_attr_err_t *err, fr_dict_attr_t const *parent, char const *attr))
Locate a fr_dict_attr_t by its name.
Definition dict_util.c:3263
fr_dict_attr_t const ** out
Where to write a pointer to the resolved fr_dict_attr_t.
Definition dict.h:268
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
Definition dict.h:281
int fr_dict_attr_autoload(fr_dict_attr_autoload_t const *to_load)
Process a dict_attr_autoload element to load/verify a dictionary attribute.
Definition dict_util.c:4090
#define fr_dict_autoload(_to_load)
Definition dict.h:850
Specifies an attribute which must be present for the module to function.
Definition dict.h:267
Specifies a dictionary which must be loaded/loadable for the module to function.
Definition dict.h:280
char const * name
Name of library and section within global config.
Definition global_lib.h:39
Structure to define how to initialise libraries with global configuration.
Definition global_lib.h:38
static fr_table_num_sorted_t const fr_curl_sslcode_table[]
Definition base.c:50
static fr_dict_t const * dict_freeradius
Definition base.c:37
int fr_curl_response_certinfo(request_t *request, fr_curl_io_request_t *randle)
Definition base.c:170
static size_t fr_curl_sslcode_table_len
Definition base.c:55
static int fr_curl_init(void)
Initialise global curl options.
Definition base.c:286
static conf_parser_t reuse_curl_conn_config[]
Definition base.c:92
int fr_curl_easy_tls_init(fr_curl_io_request_t *randle, fr_curl_tls_t const *conf)
Definition base.c:139
static int tls_config_dflt_capath(CONF_PAIR **out, UNUSED void *parent, CONF_SECTION *cs, fr_token_t quote, conf_parser_t const *rule)
Definition base.c:57
fr_dict_attr_t const * attr_tls_certificate
Attribute definitions for lib curl.
Definition base.c:36
global_lib_autoinst_t fr_curl_autoinst
Definition base.c:387
static void fr_curl_free(void)
Definition base.c:370
CURL * fr_curl_tmp_handle(void)
Return a thread local curl easy handle.
Definition base.c:267
conf_parser_t fr_curl_conn_config[]
Definition base.c:97
fr_dict_attr_autoload_t curl_attr[]
Definition base.c:40
conf_parser_t fr_curl_tls_config[]
Definition base.c:68
static fr_dict_autoload_t curl_dict[]
Definition base.c:45
static int _curl_tmpl_handle(void *arg)
Free the curl easy handle.
Definition base.c:251
void log_request_pair_list(fr_log_lvl_t lvl, request_t *request, fr_pair_t const *parent, fr_pair_list_t const *vps, char const *prefix)
Print a fr_pair_list_t.
Definition log.c:830
#define PERROR(_fmt,...)
Definition log.h:228
#define RWDEBUG(fmt,...)
Definition log.h:361
#define RDEBUG3(fmt,...)
Definition log.h:343
#define RATE_LIMIT_GLOBAL(_log, _fmt,...)
Rate limit messages using a global limiting entry.
Definition log.h:641
@ L_DBG_LVL_2
2nd highest priority debug messages (-xx | -X).
Definition log.h:71
@ FR_TYPE_TLV
Contains nested attributes.
@ FR_TYPE_STRING
String of printable characters.
@ FR_TYPE_VOID
User data.
int fr_pair_append(fr_pair_list_t *list, fr_pair_t *to_add)
Add a VP to the end of the list.
Definition pair.c:1345
fr_pair_t * fr_pair_afrom_da(TALLOC_CTX *ctx, fr_dict_attr_t const *da)
Dynamically allocate a new attribute and assign a fr_dict_attr_t.
Definition pair.c:283
void fr_pair_list_init(fr_pair_list_t *list)
Initialise a pair list header.
Definition pair.c:46
int fr_pair_value_from_str(fr_pair_t *vp, char const *value, size_t inlen, fr_sbuff_unescape_rules_t const *uerules, bool tainted)
Convert string value to native attribute value.
Definition pair.c:2589
VQP attributes.
#define REDEBUG(fmt,...)
Definition radclient.h:52
#define RDEBUG2(fmt,...)
Definition radclient.h:54
#define WARN(fmt,...)
Definition radclient.h:47
#define INFO(fmt,...)
Definition radict.c:54
static rs_t * conf
Definition radsniff.c:53
#define FR_SLAB_CONFIG_CONF_PARSER
conf_parser_t entries to populate user configurable slab values
Definition slab.h:35
fr_pair_t * vp
size_t strlcpy(char *dst, char const *src, size_t siz)
Definition strlcpy.c:34
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition syserror.c:243
An element in a lexicographically sorted array of name to num mappings.
Definition table.h:49
enum fr_token fr_token_t
@ T_BARE_WORD
Definition token.h:120
@ T_OP_EQ
Definition token.h:83
xlat_action_t xlat_transparent(UNUSED TALLOC_CTX *ctx, fr_dcursor_t *out, UNUSED xlat_ctx_t const *xctx, request_t *request, fr_value_box_list_t *args)
bool fr_pair_list_empty(fr_pair_list_t const *list)
Is a valuepair list empty.
void fr_pair_list_append(fr_pair_list_t *dst, fr_pair_list_t *src)
Appends a list of fr_pair_t from a temporary list to a destination list.
static fr_slen_t parent
Definition pair.h:851
static size_t char ** out
Definition value.h:997
void xlat_func_flags_set(xlat_t *x, xlat_func_flags_t flags)
Specify flags that alter the xlat's behaviour.
Definition xlat_func.c:402
int xlat_func_args_set(xlat_t *x, xlat_arg_parser_t const args[])
Register the arguments of an xlat.
Definition xlat_func.c:365
xlat_t * xlat_func_register(TALLOC_CTX *ctx, char const *name, xlat_func_t func, fr_type_t return_type)
Register an xlat function.
Definition xlat_func.c:218
void xlat_func_unregister(char const *name)
Unregister an xlat function.
Definition xlat_func.c:519
#define xlat_func_safe_for_set(_xlat, _escaped)
Set the escaped values for output boxes.
Definition xlat_func.h:82
@ XLAT_FUNC_FLAG_PURE
Definition xlat_func.h:38