The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
redis.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: 33c5b1728ce1bd90211e8bbfb691d185ed35b50e $
19  * @file redis.c
20  * @brief Common functions for interacting with Redis via hiredis
21  *
22  * @copyright 2015 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
23  * @copyright 2000,2006,2015 The FreeRADIUS server project
24  * @copyright 2011 TekSavvy Solutions (gabe@teksavvy.com)
25  */
26 #include <freeradius-devel/redis/base.h>
27 #include <freeradius-devel/util/debug.h>
28 #include <freeradius-devel/util/value.h>
29 
31  { L("array"), REDIS_REPLY_ARRAY },
32  { L("error"), REDIS_REPLY_ERROR },
33  { L("integer"), REDIS_REPLY_INTEGER },
34  { L("nil"), REDIS_REPLY_NIL },
35  { L("status"), REDIS_REPLY_STATUS },
36  { L("string"), REDIS_REPLY_STRING }
37 };
39 
41  { L("ask"), REDIS_RCODE_ASK },
42  { L("error"), REDIS_RCODE_ERROR },
43  { L("move"), REDIS_RCODE_MOVE },
44  { L("reconnect"), REDIS_RCODE_RECONNECT },
45  { L("success"), REDIS_RCODE_SUCCESS },
46  { L("try again"), REDIS_RCODE_TRY_AGAIN }
47 };
49 
50 /** Print the version of libhiredis the server was built against
51  *
52  */
54 {
55  INFO("libfreeradius-redis: libhiredis version: %i.%i.%i", HIREDIS_MAJOR, HIREDIS_MINOR, HIREDIS_PATCH);
56 }
57 
58 /** Check the reply for errors
59  *
60  * @param[in] conn used to issue the command.
61  * @param[in] reply to process.
62  * @return
63  * - REDIS_RCODE_TRY_AGAIN - If the operation should be retries.
64  * - REDIS_RCODE_MOVED - If the key has been permanently moved.
65  * - REDIS_RCODE_ASK - If the key has been temporarily moved.
66  * - REDIS_RCODE_SUCCESS - if no errors.
67  * - REDIS_RCODE_ERROR - on command/server error.
68  * - REDIS_RCODE_NO_SCRIPT - script specified by evalsha doesn't exist.
69  * - REDIS_RCODE_RECONNECT - on connection error (probably needs reconnecting).
70  */
72 {
73  size_t i = 0;
74 
75  if (!reply) switch (conn->handle->err) {
76  case REDIS_OK:
77  break;
78 
79  case REDIS_ERR_IO:
80  case REDIS_ERR_EOF:
81  case REDIS_ERR_OTHER:
82  fr_strerror_printf("Connection error: %s", conn->handle->errstr);
83  return REDIS_RCODE_RECONNECT;
84 
85  default:
86  case REDIS_ERR_PROTOCOL:
87  fr_strerror_printf("Command error: %s", conn->handle->errstr);
88  return REDIS_RCODE_ERROR;
89  }
90 
91  if (reply) switch (reply->type) {
92  case REDIS_REPLY_STATUS:
93  return REDIS_RCODE_SUCCESS;
94 
95  case REDIS_REPLY_ERROR:
96  /*
97  * May indicate the connection handle is bad.
98  */
99  fr_assert_msg(reply->str, "Error response contained no error string");
100 
101  fr_strerror_printf("Server error: %s", reply->str);
102  if (strncmp(REDIS_ERROR_MOVED_STR, reply->str, sizeof(REDIS_ERROR_MOVED_STR) - 1) == 0) {
103  return REDIS_RCODE_MOVE;
104  }
105  if (strncmp(REDIS_ERROR_ASK_STR, reply->str, sizeof(REDIS_ERROR_ASK_STR) - 1) == 0) {
106  return REDIS_RCODE_ASK;
107  }
108  if (strncmp(REDIS_ERROR_TRY_AGAIN_STR, reply->str, sizeof(REDIS_ERROR_TRY_AGAIN_STR) - 1) == 0) {
109  return REDIS_RCODE_TRY_AGAIN;
110  }
111  if (strncmp(REDIS_ERROR_NO_SCRIPT_STR, reply->str, sizeof(REDIS_ERROR_NO_SCRIPT_STR) - 1) == 0) {
112  return REDIS_RCODE_NO_SCRIPT;
113  }
114  return REDIS_RCODE_ERROR;
115 
116  /*
117  * Recurse to check for nested errors
118  */
119  case REDIS_REPLY_ARRAY:
120  for (i = 0; i < reply->elements; i++) {
121  int ret;
122 
123  ret = fr_redis_command_status(conn, reply->element[i]);
124  if (ret < 0) return ret;
125  }
126  break;
127 
128  default:
129  break;
130  }
131  return REDIS_RCODE_SUCCESS;
132 }
133 
134 /** Print the response data in a useful treelike form
135  *
136  * @param[in] lvl to print data at.
137  * @param[in] reply to print.
138  * @param[in] request The current request.
139  * @param[in] idx Response number.
140  */
141 void fr_redis_reply_print(fr_log_lvl_t lvl, redisReply *reply, request_t *request, int idx)
142 {
143  size_t i = 0;
144 
145  if (!reply) return;
146 
147  switch (reply->type) {
148  case REDIS_REPLY_ERROR:
149  ROPTIONAL(REDEBUG, ERROR, "(%i) error : %s", idx, reply->str);
150  break;
151 
152  case REDIS_REPLY_STATUS:
153  ROPTIONAL(RDEBUGX, DEBUGX, lvl, "(%i) status : %s", idx, reply->str);
154  break;
155 
156  case REDIS_REPLY_STRING:
157  ROPTIONAL(RDEBUGX, DEBUGX, lvl, "(%i) string : %s", idx, reply->str);
158  break;
159 
160  case REDIS_REPLY_INTEGER:
161  ROPTIONAL(RDEBUGX, DEBUGX, lvl, "(%i) integer : %lld", idx, reply->integer);
162  break;
163 
164  case REDIS_REPLY_NIL:
165  ROPTIONAL(RDEBUGX, DEBUGX, lvl, "(%i) nil", idx);
166  break;
167 
168  case REDIS_REPLY_ARRAY:
169  ROPTIONAL(RDEBUGX, DEBUGX, lvl, "(%i) array[%zu]", idx, reply->elements);
170  for (i = 0; i < reply->elements; i++) {
171  if (request) RINDENT();
172  fr_redis_reply_print(lvl, reply->element[i], request, i);
173  if (request) REXDENT();
174  }
175  break;
176  }
177 }
178 
179 /** Convert a string or integer type to #fr_value_box_t of specified type
180  *
181  * Will work with REDIS_REPLY_STRING (which is converted to #FR_TYPE_STRING
182  * then cast to dst_type), or REDIS_REPLY_INTEGER (which is converted to
183  * #FR_TYPE_UINT64, then cast to dst_type).
184  *
185  * @note Any unsupported types will trigger an assert. You must check the
186  * reply type prior to calling this function.
187  *
188  * @param[in,out] ctx to allocate any buffers in.
189  * @param[out] out Where to write the cast type.
190  * @param[in] reply to process.
191  * @param[in] dst_type to convert to. May be FR_TYPE_VOID
192  * to infer type.
193  * @param[in] dst_enumv Used to convert string types to
194  * integers for attribute with enumerated
195  * values.
196  * @param[in] box_error If true then REDIS_REPLY_ERROR will be
197  * copied to a box, otherwise we'll return
198  * and error with the contents of the error
199  * available on the thread local error stack.
200  * @param[in] shallow If true, we shallow copy strings.
201  * @return
202  * - 1 if we received a NIL reply.
203  * - 0 on success.
204  * - -1 on cast or parse failure.
205  */
206 int fr_redis_reply_to_value_box(TALLOC_CTX *ctx, fr_value_box_t *out, redisReply *reply,
207  fr_type_t dst_type, fr_dict_attr_t const *dst_enumv,
208  bool box_error, bool shallow)
209 {
211  fr_value_box_t *to_cast;
212 
213  if (dst_type != FR_TYPE_VOID) {
215  to_cast = &in;
216  } else {
217  to_cast = out;
218  }
219 
220  switch (reply->type) {
221  case REDIS_REPLY_NIL:
222  fr_value_box_init(out, FR_TYPE_NULL, NULL, false);
223  return 1;
224 
225  /*
226  * Try and convert the integer to the smallest
227  * and simplest type possible, to give the cast
228  * the greatest chance of success.
229  */
230  case REDIS_REPLY_INTEGER:
231  if (reply->integer < INT32_MIN) { /* 64bit signed */
232  fr_value_box(to_cast, (int64_t) reply->integer, true);
233  }
234  else if (reply->integer < INT16_MIN) { /* 32bit signed */
235  fr_value_box(to_cast, (int32_t) reply->integer, true);
236  }
237  else if (reply->integer < INT8_MIN) { /* 16bit signed */
238  fr_value_box(to_cast, (int16_t) reply->integer, true);
239  }
240  else if (reply->integer < 0) { /* 8bit signed */
241  fr_value_box(to_cast, (int8_t) reply->integer, true);
242  }
243  else if (reply->integer > UINT32_MAX) { /* 64bit unsigned */
244  fr_value_box(to_cast, (uint64_t) reply->integer, true);
245  }
246  else if (reply->integer > UINT16_MAX) { /* 32bit unsigned */
247  fr_value_box(to_cast, (uint32_t) reply->integer, true);
248  }
249  else if (reply->integer > UINT8_MAX) { /* 16bit unsigned */
250  fr_value_box(to_cast, (uint16_t) reply->integer, true);
251  }
252  else { /* 8bit unsigned */
253  fr_value_box(to_cast, (uint8_t) reply->integer, true);
254  }
255  break;
256 
257 #if HIREDIS_MAJOR >= 1
258  case REDIS_REPLY_DOUBLE:
259  /* reply->str is \0 terminated in this case */
260  fr_value_box(to_cast, strtod(reply->str, NULL), true);
261  break;
262 
263  case REDIS_REPLY_BOOL:
264  fr_value_box(to_cast, (bool)reply->integer, true);
265  break;
266 #endif
267 
268  case REDIS_REPLY_ERROR:
269  if (!box_error) {
270  fr_strerror_printf("Redis error: %pV",
271  fr_box_strvalue_len(reply->str, reply->len));
272  return -1;
273  }
274  FALL_THROUGH;
275 
276 #if HIREDIS_MAJOR >= 1
277  case REDIS_REPLY_BIGNUM: /* FIXME - Could try and convert to integer ? */
278 #endif
279  case REDIS_REPLY_STRING:
280  case REDIS_REPLY_STATUS:
281  fr_value_box_init(out, FR_TYPE_STRING, NULL, false);
282 
283  if (shallow) {
284  fr_value_box_bstrndup_shallow(to_cast, NULL, reply->str, reply->len, true);
285  } else {
286  if (fr_value_box_bstrndup(to_cast->talloced ? to_cast : ctx, to_cast, NULL,
287  reply->str, reply->len, true) < 0) return -1;
288  }
289  break;
290 
291 #if HIREDIS_MAJOR >= 1
292  case REDIS_REPLY_VERB:
293  {
294  fr_value_box_t *verb, *vtype;
295 
296  fr_value_box_init(out, FR_TYPE_GROUP, NULL, true);
297 
298  verb = fr_value_box_alloc(ctx, FR_TYPE_STRING, NULL);
299  if (unlikely(!verb)) {
300  fr_strerror_const("Out of memory");
301  return -1;
302  }
303  if (fr_value_box_bstrndup(verb, verb, NULL, reply->str, reply->len, true) < 0) return -1;
304  fr_value_box_list_insert_head(&out->vb_group, verb);
305 
306  vtype = fr_value_box_alloc(ctx, FR_TYPE_STRING, NULL);
307  if (unlikely(!vtype)) {
308  fr_strerror_const("Out of memory");
309  talloc_free(verb);
310  return -1;
311  }
312  if (fr_value_box_strdup(ctx, vtype, NULL, reply->vtype, true) < 0) return -1;
313  fr_value_box_list_insert_head(&out->vb_group, vtype);
314 
315  }
316  break;
317 #endif
318 
319 #if HIREDIS_MAJOR >= 1
320  case REDIS_REPLY_SET:
321  case REDIS_REPLY_MAP:
322  case REDIS_REPLY_PUSH:
323 #endif
324  case REDIS_REPLY_ARRAY:
325  {
326  fr_value_box_t *vb;
327  size_t i;
328 
329  fr_value_box_init(out, FR_TYPE_GROUP, NULL, true);
330 
331  for (i = 0; i < reply->elements; i++) {
332  vb = fr_value_box_alloc_null(ctx);
333  if (unlikely(!vb)) {
334  array_error:
335  fr_value_box_list_talloc_free(&out->vb_group);
336  return -1;
337  }
338  fr_value_box_list_insert_tail(&out->vb_group, vb);
339 
340  if (fr_redis_reply_to_value_box(vb, vb, reply->element[i],
341  FR_TYPE_VOID, NULL, box_error, shallow) < 0) goto array_error;
342  }
343  }
344  }
345 
346  if ((dst_type != FR_TYPE_VOID) && (fr_value_box_cast(ctx, out, dst_type, dst_enumv, to_cast) < 0)) return -1;
347 
348  return 0;
349 }
350 
351 /** Convert a pair of redis reply objects to a map
352  *
353  * The maps can then be applied using #map_to_request.
354  *
355  * @param[in,out] ctx to allocate maps in.
356  * @param[out] out Where to write the head of the new maps list.
357  * @param[in] request The current request.
358  * @param[in] key to process.
359  * @param[in] op to process.
360  * @param[in] value to process.
361  * @return
362  * - 0 on success.
363  * - -1 on failure.
364  */
365 int fr_redis_reply_to_map(TALLOC_CTX *ctx, map_list_t *out, request_t *request,
366  redisReply *key, redisReply *op, redisReply *value)
367 {
368  map_t *map = NULL;
369  ssize_t slen;
370 
371  if (key->type != REDIS_REPLY_STRING) {
372  REDEBUG("Bad key type, expected string, got %s",
373  fr_table_str_by_value(redis_reply_types, key->type, "<UNKNOWN>"));
374  error:
375  TALLOC_FREE(map);
376  return -1;
377  }
378 
379  if (op->type != REDIS_REPLY_STRING) {
380  REDEBUG("Bad key type, expected string, got %s",
381  fr_table_str_by_value(redis_reply_types, op->type, "<UNKNOWN>"));
382  goto error;
383  }
384 
385  RDEBUG3("Got key : %s", key->str);
386  RDEBUG3("Got op : %s", op->str);
387  RDEBUG3("Got value : %pV", fr_box_strvalue_len(value->str, value->len));
388 
389  MEM(map = talloc_zero(ctx, map_t));
390  slen = tmpl_afrom_attr_str(map, NULL, &map->lhs, key->str,
391  &(tmpl_rules_t){
392  .attr = {
393  .prefix = TMPL_ATTR_REF_PREFIX_NO,
394  .dict_def = request->dict,
395  .list_def = request_attr_request
396  }
397  });
398  if (slen <= 0) {
399  REMARKER(key->str, -slen, "%s", fr_strerror());
400  goto error;
401  }
402 
403  map->op = fr_table_value_by_str(fr_tokens_table, op->str, T_INVALID);
404  if (map->op == T_INVALID) {
405  REDEBUG("Invalid operator \"%s\"", op->str);
406  goto error;
407  }
408 
409  switch (value->type) {
410  case REDIS_REPLY_STRING:
411  case REDIS_REPLY_INTEGER:
412  {
414 
415  /* Logs own errors */
416  if (fr_redis_reply_to_value_box(map, &vb, value,
417  tmpl_attr_tail_da(map->lhs)->type, tmpl_attr_tail_da(map->lhs), false, false) < 0) {
418  RPEDEBUG("Failed converting Redis data");
419  goto error;
420  }
421 
422  /* This will only fail only memory allocation errors */
423  if (tmpl_afrom_value_box(map, &map->rhs, &vb, true) < 0) goto error;
424  }
425  break;
426 
427  default:
428  REDEBUG("Bad value type, expected string or integer, got %s",
429  fr_table_str_by_value(redis_reply_types, value->type, "<UNKNOWN>"));
430  goto error;
431 
432  }
433  MAP_VERIFY(map);
434 
435  map_list_insert_tail(out, map);
436 
437  return 0;
438 }
439 
440 /** Add a single map pair to an existing command string as three elements
441  *
442  * - Integer types will be encoded as integers.
443  * - Strings and octets will be encoded in their raw form.
444  * - Other types will be converted to their printable form and will be encoded as strings.
445  *
446  * @note lhs must be a #TMPL_TYPE_ATTR.
447  * @note rhs must be a #TMPL_TYPE_DATA.
448  *
449  * @param pool to allocate any buffers in.
450  * @param out Where to write pointers to the member of the tuple. Unused elements should be
451  * a multiple of three, and it should have at least three unused elements.
452  * @param out_len Where to write the size of the data pointed to by the equivalent index
453  * in the out array.
454  * @param map to convert.
455  * @return
456  * 0 on success.
457  * -1 on failure.
458  */
459 int fr_redis_tuple_from_map(TALLOC_CTX *pool, char const *out[], size_t out_len[], map_t *map)
460 {
461  char *new;
462 
463  char key_buf[256];
464  fr_sbuff_t key_buf_sbuff = FR_SBUFF_OUT(key_buf, sizeof(key_buf));
465  char *key;
466  size_t key_len;
467  ssize_t slen;
468 
469  fr_assert(tmpl_is_attr(map->lhs));
470  fr_assert(tmpl_is_data(map->rhs));
471 
472  slen = tmpl_print(&key_buf_sbuff, map->lhs, TMPL_ATTR_REF_PREFIX_NO, NULL);
473  if (slen < 0) {
474  fr_strerror_printf("Key too long. Must be < " STRINGIFY(sizeof(key_buf)) " "
475  "bytes, got %zu bytes", (size_t)(slen * -1));
476  return -1;
477  }
478  key_len = (size_t)slen;
479  key = talloc_bstrndup(pool, fr_sbuff_start(&key_buf_sbuff), key_len);
480  if (!key) return -1;
481 
482  switch (tmpl_value_type(map->rhs)) {
483  case FR_TYPE_STRING:
484  case FR_TYPE_OCTETS:
485  out[2] = tmpl_value(map->rhs)->datum.ptr;
486  out_len[2] = tmpl_value_length(map->rhs);
487  break;
488 
489  /*
490  * For everything else we get the string representation
491  */
492  default:
493  fr_value_box_aprint(pool, &new, tmpl_value(map->rhs), NULL);
494  if (!new) {
495  talloc_free(key);
496  return -1;
497  }
498  out[2] = new;
499  out_len[2] = talloc_array_length(new) - 1;
500  break;
501  }
502 
503  out[0] = key;
504  out_len[0] = key_len;
505  out[1] = fr_table_str_by_value(fr_tokens_table, map->op, NULL);
506  out_len[1] = strlen(out[1]);
507 
508  return 0;
509 }
510 
511 /** Simplifies handling of pipelined commands with Redis cluster
512  *
513  * Retrieve all available pipelined responses, and write them to the array.
514  *
515  * On encountering an error, all previously retrieved responses are freed, and the reply
516  * containing the error is written to the first element of out. All responses after the
517  * error are also freed.
518  *
519  * If the number of responses != pipelined, that's also an error, a very serious one,
520  * in libhiredis or Redis. We can't really do much here apart from error out.
521  *
522  * @param[out] pipelined Number of pipelined commands we sent to the server.
523  * @param[out] rcode Status of the first errored response, or REDIS_RCODE_SUCCESS
524  * if all responses were processed.
525  * @param[out] out Where to write the replies from pipelined commands.
526  * Will contain exactly 1 element on error WHICH MUST BE FREED,
527  * else the number passed in pipelined.
528  * @param[in] out_len number of elements in out.
529  * @param[in] conn the pipelined commands were issued on.
530  * @return
531  * - #REDIS_RCODE_SUCCESS on success.
532  * - #REDIS_RCODE_ERROR on command/response mismatch or command error.
533  * - REDIS_RCODE_* on other errors;
534  */
536  redisReply *out[], size_t out_len,
537  fr_redis_conn_t *conn)
538 {
539  size_t i;
540  redisReply **out_p = out;
542  redisReply *reply = NULL;
543 
544  fr_assert(out_len >= (size_t)*pipelined);
545 
546  fr_strerror_clear(); /* Clear any outstanding errors */
547 
548  if ((size_t) *pipelined > out_len) {
549  for (i = 0; i < (size_t)*pipelined; i++) {
550  if (redisGetReply(conn->handle, (void **)&reply) != REDIS_OK) break;
551  fr_redis_reply_free(&reply);
552  }
553 
554  *pipelined = 0; /* all outstanding responses should be cleared */
555 
556  fr_strerror_const("Too many pipelined commands");
557  out[0] = NULL;
558  return REDIS_RCODE_ERROR;
559  }
560 
561  for (i = 0; i < (size_t)*pipelined; i++) {
562  bool maybe_more = false;
563 
564  /*
565  * we don't need to check the return code here,
566  * as it's also stored in the conn->handle.
567  */
568  reply = NULL; /* redisGetReply doesn't NULLify reply on error *sigh* */
569  if (redisGetReply(conn->handle, (void **)&reply) == REDIS_OK) maybe_more = true;
570  status = fr_redis_command_status(conn, reply);
571  *out_p++ = reply;
572 
573  /*
574  * Bail out of processing responses,
575  * free the remaining ones (leaving this one intact)
576  * pass control back to the cluster code.
577  */
578  if (maybe_more && (status != REDIS_RCODE_SUCCESS)) {
579  size_t j;
580  error:
581  /*
582  * Append the hiredis error
583  */
584  if (conn->handle->errstr[0]) fr_strerror_printf_push("%s", conn->handle->errstr);
585 
586  /*
587  * Free everything that came before the bad reply
588  */
589  for (j = 0; j < i; j++) {
591  out[j] = NULL;
592  }
593 
594  /*
595  * ...and drain the rest of the pipelined responses
596  */
597  for (j = i + 1; j < (size_t)*pipelined; j++) {
598  redisReply *to_clear;
599 
600  if (redisGetReply(conn->handle, (void **)&to_clear) != REDIS_OK) break;
601  fr_redis_reply_free(&to_clear);
602  }
603 
604  out[0] = reply;
605 
606  *rcode = status;
607  *pipelined = 0; /* all outstanding responses should be cleared */
608 
609  return reply ? 1 : 0;
610  }
611  }
612 
613  if (i != (size_t)*pipelined) {
614  fr_strerror_printf("Expected %u responses, got %zu", *pipelined, i);
615  status = REDIS_RCODE_ERROR;
616  goto error;
617  }
618 
619  *rcode = status;
620 
621  *pipelined = 0; /* all outstanding responses should be cleared */
622 
623  return i;
624 }
625 
626 /** Get the version of Redis running on the remote server
627  *
628  * This can be useful for some modules, as it allows adaptive behaviour, or early termination.
629  *
630  * @param[out] out Where to write the version string.
631  * @param[in] out_len Length of the version string buffer.
632  * @param[in] conn Used to query the version string.
633  * @return
634  * - #REDIS_RCODE_SUCCESS on success.
635  * - #REDIS_RCODE_ERROR on command/response mismatch or command error.
636  * - REDIS_RCODE_* on other errors;
637  */
639 {
640  redisReply *reply;
641  fr_redis_rcode_t status;
642  char *p, *q;
643 
644  fr_assert(out_len > 0);
645  out[0] = '\0';
646 
647  reply = redisCommand(conn->handle, "INFO SERVER");
648  status = fr_redis_command_status(conn, reply);
649  if (status != REDIS_RCODE_SUCCESS) return status;
650 
651  if (reply->type != REDIS_REPLY_STRING) {
652  fr_strerror_printf("Bad value type, expected string or integer, got %s",
653  fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
654  error:
655  fr_redis_reply_free(&reply);
656  return REDIS_RCODE_ERROR;
657  }
658 
659  p = strstr(reply->str, "redis_version:");
660  if (!p) {
661  fr_strerror_const("Response did not contain version string");
662  goto error;
663  }
664 
665  p = strchr(p, ':');
666  fr_assert(p);
667  p++;
668 
669  q = strstr(p, "\r\n");
670  if (!q) q = p + strlen(p);
671 
672  if ((size_t)(q - p) >= out_len) {
673  fr_strerror_printf("Version string %zu bytes, expected < %zu bytes", q - p, out_len);
674  goto error;
675  }
676  strlcpy(out, p, (q - p) + 1);
677 
678  fr_redis_reply_free(&reply);
679 
680  return REDIS_RCODE_SUCCESS;
681 }
682 
683 /** Convert version string into a 32bit unsigned integer for comparisons
684  *
685  * @param[in] version string to parse.
686  * @return 32bit unsigned integer representing the version string.
687  */
688 uint32_t fr_redis_version_num(char const *version)
689 {
690  unsigned long num;
691  uint32_t ret;
692  char const *p = version;
693  char *q;
694 
695  num = strtoul(p, &q, 10);
696  if (num > UINT8_MAX) {
697  fr_strerror_printf("Major version number %lu greater than " STRINGIFY(UINT8_MAX), num);
698  return 0;
699  }
700 
701  if ((p == q) || (q[0] != '.')) {
702  fr_strerror_printf("Trailing garbage in Redis version \"%s\"", q);
703  return 0;
704  }
705  ret = num << 24;
706  p = q + 1;
707 
708  num = strtoul(p, &q, 10);
709  if (num > UINT8_MAX) {
710  fr_strerror_printf("Minor version number %lu greater than " STRINGIFY(UINT8_MAX), num);
711  return 0;
712  }
713 
714  if ((p == q) || (q[0] != '.')) {
715  fr_strerror_printf("Trailing garbage in Redis version \"%s\"", q);
716  return 0;
717  }
718  ret |= num << 16;
719  p = q + 1;
720 
721  num = strtoul(p, &q, 10);
722  if (num > UINT16_MAX) {
723  fr_strerror_printf("Minor version number %lu greater than " STRINGIFY(UINT16_MAX), num);
724  return 0;
725  }
726 
727  if ((p == q) || (q[0] != '\0')) {
728  fr_strerror_printf("Trailing garbage in Redis version \"%s\"", q);
729  return 0;
730  }
731 
732  return ret | num;
733 }
#define L(_str)
Helper for initialising arrays of string literals.
Definition: build.h:207
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
Definition: build.h:320
#define STRINGIFY(x)
Definition: build.h:195
#define unlikely(_x)
Definition: build.h:378
#define NUM_ELEMENTS(_t)
Definition: build.h:335
#define fr_assert_msg(_x, _msg,...)
Calls panic_action ifndef NDEBUG, else logs error and causes the server to exit immediately with code...
Definition: debug.h:208
#define ERROR(fmt,...)
Definition: dhcpclient.c:41
static fr_slen_t in
Definition: dict.h:645
Test enumeration values.
Definition: dict_test.h:92
#define REXDENT()
Exdent (unindent) R* messages by one level.
Definition: log.h:443
#define ROPTIONAL(_l_request, _l_global, _fmt,...)
Use different logging functions depending on whether request is NULL or not.
Definition: log.h:528
#define RDEBUG3(fmt,...)
Definition: log.h:343
#define RDEBUGX(_l, fmt,...)
Definition: log.h:340
#define REMARKER(_str, _marker_idx, _marker,...)
Output string with error marker, showing where format error occurred.
Definition: log.h:498
#define RPEDEBUG(fmt,...)
Definition: log.h:376
#define DEBUGX(_lvl, _fmt,...)
Definition: log.h:268
#define RINDENT()
Indent R* messages by one level.
Definition: log.h:430
talloc_free(reap)
fr_log_lvl_t
Definition: log.h:67
#define MAP_VERIFY(_x)
Definition: map.h:108
@ TMPL_ATTR_REF_PREFIX_NO
Attribute refs have no '&' prefix.
Definition: merged_model.c:230
unsigned short uint16_t
Definition: merged_model.c:31
fr_slen_t tmpl_print(fr_sbuff_t *out, tmpl_t const *vpt, tmpl_attr_prefix_t ar_prefix, fr_sbuff_escape_rules_t const *e_rules)
Definition: merged_model.c:237
fr_type_t
Definition: merged_model.c:80
@ FR_TYPE_STRING
String of printable characters.
Definition: merged_model.c:83
@ FR_TYPE_NULL
Invalid (uninitialised) attribute type.
Definition: merged_model.c:81
@ FR_TYPE_VOID
User data.
Definition: merged_model.c:127
@ FR_TYPE_OCTETS
Raw octets.
Definition: merged_model.c:84
@ FR_TYPE_GROUP
A grouping of other attributes.
Definition: merged_model.c:124
unsigned int uint32_t
Definition: merged_model.c:33
long int ssize_t
Definition: merged_model.c:24
unsigned char uint8_t
Definition: merged_model.c:30
unsigned long int size_t
Definition: merged_model.c:25
#define UINT8_MAX
Definition: merged_model.c:32
#define REDEBUG(fmt,...)
Definition: radclient.h:52
#define INFO(fmt,...)
Definition: radict.c:54
redisContext * handle
Hiredis context used when issuing commands.
Definition: base.h:101
#define REDIS_ERROR_TRY_AGAIN_STR
Definition: base.h:48
#define REDIS_ERROR_MOVED_STR
Definition: base.h:46
static void fr_redis_reply_free(redisReply **reply)
Wrap freeReplyObject so we consistently check for NULL pointers.
Definition: base.h:64
#define REDIS_ERROR_ASK_STR
Definition: base.h:47
#define REDIS_ERROR_NO_SCRIPT_STR
Definition: base.h:49
fr_redis_rcode_t
Codes are ordered inversely by priority.
Definition: base.h:87
@ REDIS_RCODE_RECONNECT
Transitory error, caller should retry the operation with a new connection.
Definition: base.h:91
@ REDIS_RCODE_SUCCESS
Operation was successful.
Definition: base.h:88
@ REDIS_RCODE_MOVE
Attempt operation on an alternative node with remap.
Definition: base.h:94
@ REDIS_RCODE_TRY_AGAIN
Try the operation again.
Definition: base.h:90
@ REDIS_RCODE_NO_SCRIPT
Script doesn't exist.
Definition: base.h:95
@ REDIS_RCODE_ASK
Attempt operation on an alternative node.
Definition: base.h:93
@ REDIS_RCODE_ERROR
Unrecoverable library/server error.
Definition: base.h:89
Connection handle, holding a redis context.
Definition: base.h:100
fr_table_num_sorted_t const redis_rcodes[]
Definition: redis.c:40
int fr_redis_reply_to_value_box(TALLOC_CTX *ctx, fr_value_box_t *out, redisReply *reply, fr_type_t dst_type, fr_dict_attr_t const *dst_enumv, bool box_error, bool shallow)
Convert a string or integer type to fr_value_box_t of specified type.
Definition: redis.c:206
int fr_redis_tuple_from_map(TALLOC_CTX *pool, char const *out[], size_t out_len[], map_t *map)
Add a single map pair to an existing command string as three elements.
Definition: redis.c:459
void fr_redis_reply_print(fr_log_lvl_t lvl, redisReply *reply, request_t *request, int idx)
Print the response data in a useful treelike form.
Definition: redis.c:141
size_t redis_reply_types_len
Definition: redis.c:38
void fr_redis_version_print(void)
Print the version of libhiredis the server was built against.
Definition: redis.c:53
uint32_t fr_redis_version_num(char const *version)
Convert version string into a 32bit unsigned integer for comparisons.
Definition: redis.c:688
fr_redis_rcode_t fr_redis_command_status(fr_redis_conn_t *conn, redisReply *reply)
Check the reply for errors.
Definition: redis.c:71
fr_table_num_sorted_t const redis_reply_types[]
Definition: redis.c:30
size_t redis_rcodes_len
Definition: redis.c:48
fr_redis_rcode_t fr_redis_pipeline_result(unsigned int *pipelined, fr_redis_rcode_t *rcode, redisReply *out[], size_t out_len, fr_redis_conn_t *conn)
Simplifies handling of pipelined commands with Redis cluster.
Definition: redis.c:535
fr_redis_rcode_t fr_redis_get_version(char *out, size_t out_len, fr_redis_conn_t *conn)
Get the version of Redis running on the remote server.
Definition: redis.c:638
int fr_redis_reply_to_map(TALLOC_CTX *ctx, map_list_t *out, request_t *request, redisReply *key, redisReply *op, redisReply *value)
Convert a pair of redis reply objects to a map.
Definition: redis.c:365
#define fr_sbuff_start(_sbuff_or_marker)
#define FR_SBUFF_OUT(_start, _len_or_end)
#define tmpl_value_length(_tmpl)
Definition: tmpl.h:933
#define tmpl_value(_tmpl)
Definition: tmpl.h:932
int tmpl_afrom_value_box(TALLOC_CTX *ctx, tmpl_t **out, fr_value_box_t *data, bool steal)
Create a tmpl_t from a fr_value_box_t.
static fr_dict_attr_t const * tmpl_attr_tail_da(tmpl_t const *vpt)
Return the last attribute reference da.
Definition: tmpl.h:796
#define tmpl_is_attr(vpt)
Definition: tmpl.h:213
ssize_t tmpl_afrom_attr_str(TALLOC_CTX *ctx, tmpl_attr_error_t *err, tmpl_t **out, char const *name, tmpl_rules_t const *rules))
Parse a string into a TMPL_TYPE_ATTR_* type tmpl_t.
#define tmpl_is_data(vpt)
Definition: tmpl.h:211
#define tmpl_value_type(_tmpl)
Definition: tmpl.h:934
Optional arguments passed to vp_tmpl functions.
Definition: tmpl.h:341
fr_assert(0)
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >=0)
size_t strlcpy(char *dst, char const *src, size_t siz)
Definition: strlcpy.c:34
Value pair map.
Definition: map.h:77
fr_token_t op
The operator that controls insertion of the dst attribute.
Definition: map.h:82
tmpl_t * lhs
Typically describes the attribute to add, modify or compare.
Definition: map.h:78
tmpl_t * rhs
Typically describes a literal value or a src attribute to copy or compare.
Definition: map.h:79
#define fr_table_value_by_str(_table, _name, _def)
Convert a string to a value using a sorted or ordered table.
Definition: table.h:134
#define fr_table_str_by_value(_table, _number, _def)
Convert an integer to a string.
Definition: table.h:253
An element in a lexicographically sorted array of name to num mappings.
Definition: table.h:45
char * talloc_bstrndup(TALLOC_CTX *ctx, char const *in, size_t inlen)
Binary safe strndup function.
Definition: talloc.c:452
fr_table_num_ordered_t const fr_tokens_table[]
Definition: token.c:33
@ T_INVALID
Definition: token.h:39
void fr_strerror_clear(void)
Clears all pending messages from the talloc pools.
Definition: strerror.c:577
char const * fr_strerror(void)
Get the last library error.
Definition: strerror.c:554
#define fr_strerror_printf(_fmt,...)
Log to thread local error buffer.
Definition: strerror.h:64
#define fr_strerror_printf_push(_fmt,...)
Add a message to an existing stack of messages at the tail.
Definition: strerror.h:84
#define fr_strerror_const(_msg)
Definition: strerror.h:223
int fr_value_box_cast(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t dst_type, fr_dict_attr_t const *dst_enumv, fr_value_box_t const *src)
Convert one type of fr_value_box_t to another.
Definition: value.c:3301
int fr_value_box_strdup(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_dict_attr_t const *enumv, char const *src, bool tainted)
Copy a nul terminated string to a fr_value_box_t.
Definition: value.c:3876
int fr_value_box_bstrndup(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_dict_attr_t const *enumv, char const *src, size_t len, bool tainted)
Copy a string to to a fr_value_box_t.
Definition: value.c:4097
void fr_value_box_bstrndup_shallow(fr_value_box_t *dst, fr_dict_attr_t const *enumv, char const *src, size_t len, bool tainted)
Assign a string to to a fr_value_box_t.
Definition: value.c:4181
#define fr_value_box_alloc(_ctx, _type, _enumv)
Allocate a value box of a specific type.
Definition: value.h:608
static fr_slen_t fr_value_box_aprint(TALLOC_CTX *ctx, char **out, fr_value_box_t const *data, fr_sbuff_escape_rules_t const *e_rules) 1(fr_value_box_print
#define FR_VALUE_BOX_INITIALISER_NULL(_vb)
A static initialiser for stack/globally allocated boxes.
Definition: value.h:475
#define fr_box_strvalue_len(_val, _len)
Definition: value.h:279
#define fr_value_box_init_null(_vb)
Initialise an empty/null box that will be filled later.
Definition: value.h:580
#define fr_value_box(_box, _var, _tainted)
Automagically fill in a box, determining the value type from the type of the C variable.
Definition: value.h:858
#define fr_value_box_alloc_null(_ctx)
Allocate a value box for later use with a value assignment function.
Definition: value.h:619
#define fr_value_box_init(_vb, _type, _enumv, _tainted)
Initialise a fr_value_box_t.
Definition: value.h:574
static size_t char ** out
Definition: value.h:984