The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
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: 7428ff6b3783cd24315d43c52ce8bfa0bea90d82 $
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/unlang/xlat_func.h>
32 
33 #include "attrs.h"
34 
36 static fr_dict_t const *dict_freeradius; /*internal dictionary for server*/
37 
40  { .out = &attr_tls_certificate, .name = "TLS-Certificate", .type = FR_TYPE_TLV, .dict = &dict_freeradius },
41  { NULL }
42 };
43 
45  { .out = &dict_freeradius, .proto = "freeradius" },
46  { NULL }
47 };
48 
50  {L("allow"), CURLUSESSL_TRY },
51  {L("demand"), CURLUSESSL_ALL },
52  {L("never"), CURLUSESSL_NONE },
53 };
55 
58  { FR_CONF_OFFSET_FLAGS("ca_issuer_file", CONF_FLAG_FILE_INPUT, fr_curl_tls_t, ca_issuer_file) },
60  { FR_CONF_OFFSET_FLAGS("certificate_file", CONF_FLAG_FILE_INPUT, fr_curl_tls_t, certificate_file) },
61  { FR_CONF_OFFSET_FLAGS("private_key_file", CONF_FLAG_FILE_INPUT, fr_curl_tls_t, private_key_file) },
62  { FR_CONF_OFFSET_FLAGS("private_key_password", CONF_FLAG_SECRET, fr_curl_tls_t, private_key_password) },
63  { FR_CONF_OFFSET("random_file", fr_curl_tls_t, random_file) },
64  { FR_CONF_OFFSET_TYPE_FLAGS("require_cert", FR_TYPE_VOID, 0, fr_curl_tls_t, require_cert),
65  .func = cf_table_parse_int,
66  .uctx = &(cf_table_parse_ctx_t){
67  .table = fr_curl_sslcode_table,
69  },
70  .dflt = "allow" },
71  { FR_CONF_OFFSET("check_cert", fr_curl_tls_t, check_cert), .dflt = "yes" },
72  { FR_CONF_OFFSET("check_cert_cn", fr_curl_tls_t, check_cert_cn), .dflt = "yes" },
73  { FR_CONF_OFFSET("extract_cert_attrs", fr_curl_tls_t, extract_cert_attrs), .dflt = "no" },
75 };
76 
78  { FR_CONF_OFFSET("min", fr_slab_config_t, min_elements), .dflt = "10" },
79  { FR_CONF_OFFSET("max", fr_slab_config_t, max_elements), .dflt = "100" },
80  { FR_CONF_OFFSET("cleanup_interval", fr_slab_config_t, interval), .dflt = "30s" },
82 };
83 
86  { FR_CONF_OFFSET("connect_timeout", fr_curl_conn_config_t, connect_timeout), .dflt = "3.0" },
88 };
89 
91 {
92  request_t *request = randle->request;
93 
94  if (conf->certificate_file) FR_CURL_ROPTIONAL_SET_OPTION(CURLOPT_SSLCERT, conf->certificate_file);
95  if (conf->private_key_file) FR_CURL_ROPTIONAL_SET_OPTION(CURLOPT_SSLKEY, conf->private_key_file);
96  if (conf->private_key_password) FR_CURL_ROPTIONAL_SET_OPTION(CURLOPT_KEYPASSWD, conf->private_key_password);
97  if (conf->ca_file) FR_CURL_ROPTIONAL_SET_OPTION(CURLOPT_CAINFO, conf->ca_file);
98  if (conf->ca_issuer_file) FR_CURL_ROPTIONAL_SET_OPTION(CURLOPT_ISSUERCERT, conf->ca_issuer_file);
99  if (conf->ca_path) FR_CURL_ROPTIONAL_SET_OPTION(CURLOPT_CAPATH, conf->ca_path);
100 #if !CURL_AT_LEAST_VERSION(7,84,0)
101  if (conf->random_file) FR_CURL_ROPTIONAL_SET_OPTION(CURLOPT_RANDOM_FILE, conf->random_file);
102 #endif
103  FR_CURL_ROPTIONAL_SET_OPTION(CURLOPT_USE_SSL, conf->require_cert);
104 
105  FR_CURL_ROPTIONAL_SET_OPTION(CURLOPT_SSL_VERIFYPEER, (conf->check_cert == true) ? 1L : 0L);
106  FR_CURL_ROPTIONAL_SET_OPTION(CURLOPT_SSL_VERIFYHOST, (conf->check_cert_cn == true) ? 2L : 0L);
107  if (conf->extract_cert_attrs) FR_CURL_ROPTIONAL_SET_OPTION(CURLOPT_CERTINFO, 1L);
108 
109  return 0;
110 error:
111  return -1;
112 }
113 
115 {
116  CURL *candle = randle->candle;
117  CURLcode ret;
118  int i;
119  char buffer[265];
120  char *p , *q;
121  fr_pair_list_t cert_vps;
122 
123  /*
124  * Examples and documentation show cert_info being
125  * a struct curl_certinfo *, but CPP checks require
126  * it to be a struct curl_slist *.
127  *
128  * https://curl.haxx.se/libcurl/c/certinfo.html
129  */
130  union {
131  struct curl_slist *to_info;
132  struct curl_certinfo *to_certinfo;
133  } ptr;
134  ptr.to_info = NULL;
135 
136  fr_pair_list_init(&cert_vps);
137 
138  ret = curl_easy_getinfo(candle, CURLINFO_CERTINFO, &ptr.to_info);
139  if (ret != CURLE_OK) {
140  REDEBUG("Getting certificate info failed: %i - %s", ret, curl_easy_strerror(ret));
141 
142  return -1;
143  }
144 
145  /*
146  * There doesn't seem to be any way to determine if
147  * the session uses ssl or not, so if no certs are
148  * returned, we assume it's not an ssl session.
149  */
150  if (ptr.to_certinfo->num_of_certs == 0) return 0;
151 
152  RDEBUG2("Chain has %i certificate(s)", ptr.to_certinfo->num_of_certs);
153  for (i = 0; i < ptr.to_certinfo->num_of_certs; i++) {
154  struct curl_slist *cert_attrs;
155  fr_pair_t *container;
156 
157  MEM(container = fr_pair_afrom_da(request->request_ctx, attr_tls_certificate));
158  fr_pair_append(&cert_vps, container);
159 
160  RDEBUG2("Processing certificate %i",i);
161 
162  for (cert_attrs = ptr.to_certinfo->certinfo[i];
163  cert_attrs;
164  cert_attrs = cert_attrs->next) {
165  fr_pair_t *vp;
166  fr_dict_attr_t const *da;
167 
168  q = strchr(cert_attrs->data, ':');
169  if (!q) {
170  RWDEBUG("Malformed certinfo from libcurl: %s", cert_attrs->data);
171  continue;
172  }
173 
174  strlcpy(buffer, cert_attrs->data, (q - cert_attrs->data) + 1);
175  for (p = buffer; *p != '\0'; p++) if (*p == ' ') *p = '-';
176 
178  if (!da) {
179  RDEBUG3("Skipping %s += '%s'", buffer, q + 1);
180  RDEBUG3("If this value is required, define attribute \"%s\"", buffer);
181  continue;
182  }
183  MEM(vp = fr_pair_afrom_da(container, da));
184  fr_pair_value_from_str(vp, q + 1, strlen(q + 1), NULL, true);
185 
186  fr_pair_append(&container->vp_group, vp);
187  }
188  /*
189  * Add a copy of the cert_vps to the request list.
190  */
191  if (!fr_pair_list_empty(&cert_vps)) {
192  /*
193  * Print out all the pairs we have so far
194  */
195  log_request_pair_list(L_DBG_LVL_2, request, NULL, &cert_vps, NULL);
196  fr_pair_list_append(&request->request_pairs, &cert_vps);
197  }
198  }
199  return 0;
200 }
201 
202 /** Free the curl easy handle
203  *
204  * @param[in] arg curl easy handle to free.
205  */
206 static int _curl_tmpl_handle(void *arg)
207 {
208  curl_easy_cleanup(arg);
209  return 0;
210 }
211 
212 /** Return a thread local curl easy handle
213  *
214  * This should only be used for calls into libcurl functions
215  * which don't operate on an active request, like the
216  * escape/unescape functions.
217  *
218  * @return
219  * - A thread local curl easy handle.
220  * - NULL on failure.
221  */
223 {
224  static _Thread_local CURL *t_candle;
225 
226  if (unlikely(t_candle == NULL)) {
227  CURL *candle;
228 
229  MEM(candle = curl_easy_init());
230  fr_atexit_thread_local(t_candle, _curl_tmpl_handle, candle);
231  }
232 
233  return t_candle;
234 }
235 
236 /** Initialise global curl options
237  *
238  * libcurl is meant to performa reference counting, but still seems to
239  * leak lots of memory if we call curl_global_init many times.
240  */
241 static int fr_curl_init(void)
242 {
243  CURLcode ret;
244  curl_version_info_data *curlversion;
245 
246 #ifdef WITH_TLS
247  /*
248  * Use our OpenSSL init with the hope that
249  * the free function will also free the
250  * memory allocated during SSL init.
251  */
252  if (fr_openssl_init() < 0) return -1;
253 #endif
254 
255  if (fr_dict_autoload(curl_dict) < 0) {
256  PERROR("Failed loading dictionaries for curl");
257  return -1;
258  }
259 
260  if (fr_dict_attr_autoload(curl_attr) < 0) {
261  PERROR("Failed loading dictionaries for curl");
262  return -1;
263  }
264 
265  ret = curl_global_init(CURL_GLOBAL_ALL);
266  if (ret != CURLE_OK) {
267  ERROR("CURL init returned error: %i - %s", ret, curl_easy_strerror(ret));
268  error:
270  return -1;
271  }
272 
273  curlversion = curl_version_info(CURLVERSION_NOW);
274  if (strcmp(LIBCURL_VERSION, curlversion->version) != 0) {
275  WARN("libcurl version changed since the server was built");
276  WARN("linked: %s built: %s", curlversion->version, LIBCURL_VERSION);
277  }
278 
279  INFO("libcurl version: %s", curl_version());
280 
281  {
282  xlat_t *xlat;
283 
284  /*
285  * Generic escape function for all CURL based modules
286  * Use CURL_URI_SAFE_FOR within the module.
287  */
288  xlat = xlat_func_register(NULL, "uri.escape", fr_curl_xlat_uri_escape, FR_TYPE_STRING);
289  if (unlikely(!xlat)) {
290  ERROR("Failed registering \"uri.escape\" xlat");
291  goto error;
292  }
296 
297  /*
298  * Generic safe function for all CURL based modules
299  * Use CURL_URI_SAFE_FOR within the module.
300  */
301  xlat = xlat_func_register(NULL, "uri.safe", xlat_transparent, FR_TYPE_STRING);
302  if (unlikely(!xlat)) {
303  ERROR("Failed registering \"uri.safe\" xlat");
304  goto error;
305  }
309 
310  /*
311  * Generic unescape function for all CURL based modules
312  */
313  xlat = xlat_func_register(NULL, "uri.unescape", fr_curl_xlat_uri_unescape, FR_TYPE_STRING);
314  if (unlikely(!xlat)) {
315  ERROR("Failed registering \"uri.unescape\" xlat");
316  goto error;
317  }
320  }
321 
322  return 0;
323 }
324 
325 static void fr_curl_free(void)
326 {
328 
329 #ifdef WITH_TLS
330  fr_openssl_free();
331 #endif
332  curl_global_cleanup();
333 
334  xlat_func_unregister("uri.escape");
335  xlat_func_unregister("uri.safe");
336  xlat_func_unregister("uri.unescape");
337 }
338 
339 /*
340  * Public symbol modules can reference to auto instantiate libcurl
341  */
343  .name = "curl",
344  .init = fr_curl_init,
345  .free = fr_curl_free
346 };
static int const char char buffer[256]
Definition: acutest.h:574
#define fr_atexit_thread_local(_name, _free, _uctx)
Definition: atexit.h:221
#define L(_str)
Helper for initialising arrays of string literals.
Definition: build.h:207
#define unlikely(_x)
Definition: build.h:378
#define NUM_ELEMENTS(_t)
Definition: build.h:335
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:1474
#define CONF_PARSER_TERMINATOR
Definition: cf_parse.h:626
#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
#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_SECRET
Only print value if debug level >= 3.
Definition: cf_parse.h:410
@ CONF_FLAG_FILE_INPUT
File matching value must exist, and must be readable.
Definition: cf_parse.h:412
#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:563
#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, UNUSED 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:47
xlat_arg_parser_t const fr_curl_xlat_uri_args[]
Definition: xlat.c:34
xlat_arg_parser_t const fr_curl_xlat_safe_args[]
Definition: xlat.c:39
xlat_action_t fr_curl_xlat_uri_unescape(UNUSED TALLOC_CTX *ctx, UNUSED 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:77
#define CURL_URI_SAFE_FOR
safe for value suitable for all users of the curl library
Definition: xlat.h:36
#define ERROR(fmt,...)
Definition: dhcpclient.c:41
#define fr_dict_autofree(_to_free)
Definition: dict.h:674
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:2860
fr_dict_attr_t const ** out
Where to write a pointer to the resolved fr_dict_attr_t.
Definition: dict.h:250
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
Definition: dict.h:263
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:3647
#define fr_dict_autoload(_to_load)
Definition: dict.h:671
Specifies an attribute which must be present for the module to function.
Definition: dict.h:249
Specifies a dictionary which must be loaded/loadable for the module to function.
Definition: dict.h:262
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:49
static fr_dict_t const * dict_freeradius
Definition: base.c:36
int fr_curl_response_certinfo(request_t *request, fr_curl_io_request_t *randle)
Definition: base.c:114
static size_t fr_curl_sslcode_table_len
Definition: base.c:54
static int fr_curl_init(void)
Initialise global curl options.
Definition: base.c:241
static conf_parser_t reuse_curl_conn_config[]
Definition: base.c:77
int fr_curl_easy_tls_init(fr_curl_io_request_t *randle, fr_curl_tls_t const *conf)
Definition: base.c:90
fr_dict_attr_t const * attr_tls_certificate
Attribute definitions for lib curl.
Definition: base.c:35
global_lib_autoinst_t fr_curl_autoinst
Definition: base.c:342
static void fr_curl_free(void)
Definition: base.c:325
conf_parser_t fr_curl_conn_config[]
Definition: base.c:84
fr_dict_attr_autoload_t curl_attr[]
Definition: base.c:39
CURL * fr_curl_tmp_handle(void)
Return a thread local curl easy handle.
Definition: base.c:222
conf_parser_t fr_curl_tls_config[]
Definition: base.c:56
static fr_dict_autoload_t curl_dict[]
Definition: base.c:44
static int _curl_tmpl_handle(void *arg)
Free the curl easy handle.
Definition: base.c:206
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:821
#define PERROR(_fmt,...)
Definition: log.h:228
#define RWDEBUG(fmt,...)
Definition: log.h:361
#define RDEBUG3(fmt,...)
Definition: log.h:343
@ L_DBG_LVL_2
2nd highest priority debug messages (-xx | -X).
Definition: log.h:71
@ FR_TYPE_TLV
Contains nested attributes.
Definition: merged_model.c:118
@ FR_TYPE_STRING
String of printable characters.
Definition: merged_model.c:83
@ FR_TYPE_VOID
User data.
Definition: merged_model.c:127
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:278
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:1340
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:2586
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
Tuneable parameters for slabs.
Definition: slab.h:42
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >=0)
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
An element in a lexicographically sorted array of name to num mappings.
Definition: table.h:45
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.
Definition: pair_inline.c:125
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.
Definition: pair_inline.c:182
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:415
int xlat_func_args_set(xlat_t *x, xlat_arg_parser_t const args[])
Register the arguments of an xlat.
Definition: xlat_func.c:360
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:195
void xlat_func_unregister(char const *name)
Unregister an xlat function.
Definition: xlat_func.c:531
#define xlat_func_safe_for_set(_xlat, _escaped)
Set the escaped values for output boxes.
Definition: xlat_func.h:80
@ XLAT_FUNC_FLAG_PURE
Definition: xlat_func.h:38