The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
cron.c
Go to the documentation of this file.
1 /*
2  * This program 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
5  * (at 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 crons.
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: b546c9ac1d50e7613bc82dd6fbab643c09d9f484 $
19  * @file proto_cron.c
20  * @brief CRON master protocol handler.
21  *
22  * @copyright 2021 Network RADIUS SAS (legal@networkradius.com)
23  */
24 #include <freeradius-devel/server/base.h>
25 #include <freeradius-devel/io/listen.h>
26 #include <freeradius-devel/unlang/base.h>
27 #include <freeradius-devel/util/debug.h>
28 #include "proto_cron.h"
29 
30 extern fr_app_t proto_cron;
31 static int type_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, conf_parser_t const *rule);
32 static int time_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, conf_parser_t const *rule);
33 
34 static conf_parser_t const limit_config[] = {
35  /*
36  * For performance tweaking. NOT for normal humans.
37  */
38  { FR_CONF_OFFSET("max_packet_size", proto_cron_t, max_packet_size) } ,
39  { FR_CONF_OFFSET("num_messages", proto_cron_t, num_messages) } ,
40 
42 };
43 
44 /** How to parse a CRON listen section
45  *
46  */
47 static conf_parser_t const proto_cron_config[] = {
49  type), .func = type_parse },
50 
52  .func = time_parse },
53 
55 
56  { FR_CONF_OFFSET("priority", proto_cron_t, priority) },
57 
58  { FR_CONF_POINTER("limit", 0, CONF_FLAG_SUBSECTION, NULL), .subcs = (void const *) limit_config },
60 };
61 
62 static fr_dict_t const *dict_cron;
63 
66  { .out = &dict_cron, .proto = "freeradius" },
67  { NULL }
68 };
69 
70 /** Wrapper around dl_instance which translates the packet-type into a submodule name
71  *
72  * @param[in] ctx to allocate data in (instance of proto_cron).
73  * @param[out] out Where to write a dl_module_inst_t containing the module handle and instance.
74  * @param[in] parent Base structure address.
75  * @param[in] ci #CONF_PAIR specifying the name of the type module.
76  * @param[in] rule unused.
77  * @return
78  * - 0 on success.
79  * - -1 on failure.
80  */
81 static int type_parse(UNUSED TALLOC_CTX *ctx, void *out, void *parent, CONF_ITEM *ci, UNUSED conf_parser_t const *rule)
82 {
83  proto_cron_t *inst = talloc_get_type_abort(parent, proto_cron_t);
84  fr_dict_enum_t const *type_enum;
85  CONF_PAIR *cp = cf_item_to_pair(ci);
86  char const *value = cf_pair_value(cp);
87 
88  *((char const **) out) = value;
89 
90  inst->dict = virtual_server_namespace_by_ci(ci);
91  if (!inst->dict) {
92  cf_log_err(ci, "Please define 'namespace' in this virtual server");
93  return -1;
94  }
95 
96  inst->attr_packet_type = fr_dict_attr_by_name(NULL, fr_dict_root(inst->dict), "Packet-Type");
97  if (!inst->attr_packet_type) {
98  cf_log_err(ci, "Failed to find 'Packet-Type' attribute");
99  return -1;
100  }
101 
102  if (!value) {
103  cf_log_err(ci, "No value given for 'type'");
104  return -1;
105  }
106 
107  type_enum = fr_dict_enum_by_name(inst->attr_packet_type, value, -1);
108  if (!type_enum) {
109  cf_log_err(ci, "Invalid type \"%s\"", value);
110  return -1;
111  }
112 
113  inst->code = type_enum->value->vb_uint32;
114  return 0;
115 }
116 
117 /*
118  * Parse a basic field with sanity checks.
119  *
120  * Note that we don't (yet) convert this into an internal data
121  * structure. Instead, we're just checking if the basic format
122  * is OK.
123  */
124 static int parse_field(CONF_ITEM *ci, char const **start, char const *name, unsigned int min, unsigned int max)
125 {
126  char const *p;
127  char *end = NULL;
128  unsigned int num, last = 0;
129  bool last_is_set = false;
130  bool wildcard = false;
131 
132  p = *start;
134 
135  if (!*p) {
136  cf_log_err(ci, "Missing field for %s", name);
137  return -1;
138  }
139 
140  /*
141  * See 'man 5 crontab' for the format.
142  */
143  while (p) {
144  /*
145  * Allow wildcards, but only once.
146  */
147  if (*p == '*') {
148  if (wildcard) {
149  cf_log_err(ci, "Cannot use two wildcards for %s at %s", name, p);
150  return -1;
151  }
152 
153  end = UNCONST(char *, p) + 1;
154  wildcard = true;
155  goto check_step;
156  }
157 
158  /*
159  * If there's already a "*", we can't have another one.
160  */
161  if (wildcard) {
162  cf_log_err(ci, "Cannot use wildcard and numbers for %s at %s", name, p);
163  return -1;
164  }
165 
166  /*
167  * If it's not a wildcard, it MUST be a number,
168  * which is between min and max.
169  */
170  num = strtoul(p, &end, 10);
171  if ((num < min) || (num > max)) {
172  cf_log_err(ci, "Number is invalid or out of bounds (%d..%d) for %s at %s",
173  min, max, name, p);
174  return -1;
175  }
176 
177  /*
178  * Don't allow the same number to be specified
179  * multiple times.
180  */
181  if (!last_is_set) {
182  last_is_set = true;
183 
184  } else if (num <= last) {
185  cf_log_err(ci, "Number overlaps with previous value of %u, for %s at %s",
186  last, name, p);
187  return -1;
188  }
189  last = num;
190 
191  /*
192  * Ranges are allowed, with potential steps
193  */
194  if (*end == '-') {
195  unsigned int next;
196 
197  p = end + 1;
198  next = strtoul(p, &end, 10);
199  if (next <= num) {
200  cf_log_err(ci, "End of range number overlaps with previous value of %u, for %s at %s",
201  num, name, p);
202  return -1;
203  }
204 
205  if (next > max) {
206  cf_log_err(ci, "End of range number is invalid or out of bounds (%d..%d) for %s at %s",
207  min, max, name, p);
208  return -1;
209  }
210 
211  last = next;
212 
213  check_step:
214  /*
215  * Allow /N
216  */
217  if (*end == '/') {
218  p = end + 1;
219 
220  num = strtoul(p, &end, 10);
221  if (num > 65535) {
222  cf_log_err(ci, "Failed parsing step value for %s at %s", name, p);
223  return -1;
224  }
225  }
226  } /* end of range specifier */
227 
228  /*
229  * We can specify multiple fields, separated by a comma.
230  */
231  if (*end == ',') {
232  p = end + 1;
233  continue;
234  }
235 
236  /*
237  * EOS or space is end of field.
238  */
239  if (!(!*end || isspace((uint8_t) *end))) {
240  cf_log_err(ci, "Unexpected text for %s at %s", name, end);
241  return -1;
242  }
243  }
244 
245  *start = end;
246  return 0;
247 }
248 
249 /*
250  * Special names, including our own extensions.
251  */
253  { L("annually"), "0 0 1 1 *" },
254  { L("daily"), "0 0 * * *" },
255  { L("hourly"), "0 * * * *" },
256  { L("midnight"), "0 0 * * *" },
257  { L("monthly"), "0 0 1 * *" },
258  { L("reboot"), "+" },
259  { L("shutdown"), "-" },
260  { L("startup"), "+" },
261  { L("weekly"), "0 0 * * 0" },
262  { L("yearly"), "0 0 1 1 *" },
263 };
265 
266 /** Wrapper around dl_instance which checks the syntax of a cron job
267  *
268  * @param[in] ctx to allocate data in (instance of proto_cron).
269  * @param[out] out Where to write a dl_module_inst_t containing the module handle and instance.
270  * @param[in] parent Base structure address.
271  * @param[in] ci #CONF_PAIR specifying the name of the type module.
272  * @param[in] rule unused.
273  * @return
274  * - 0 on success.
275  * - -1 on failure.
276  *
277  * https://github.com/staticlibs/ccronexpr/blob/master/ccronexpr.c
278  */
279 static int time_parse(UNUSED TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, UNUSED conf_parser_t const *rule)
280 {
281 // proto_cron_t *inst = talloc_get_type_abort(parent, proto_cron_t);
282  CONF_PAIR *cp = cf_item_to_pair(ci);
283  char const *value = cf_pair_value(cp);
284  char const *p;
285 
286  p = value;
287 
288  /*
289  * Check for special names.
290  */
291  if (*p == '@') {
292  p = fr_table_value_by_str(time_names, p + 1, NULL);
293  if (!p) {
294  cf_log_err(ci, "Invalid time name '%s'", value);
295  return -1;
296  }
297 
298  /*
299  * Over-write the special names with standard
300  * ones, so that the rest of the parser is simpler.
301  */
302  *((char const **) out) = p;
303  return 0;
304  }
305 
306  *((char const **) out) = value;
307 
308  if (parse_field(ci, &p, "minute", 0, 59) < 0) return -1;
309  if (parse_field(ci, &p, "hour", 0, 59) < 0) return -1;
310  if (parse_field(ci, &p, "day of month", 1, 31) < 0) return -1;
311  if (parse_field(ci, &p, "month", 1,12) < 0) return -1;
312  if (parse_field(ci, &p, "day of week", 0, 6) < 0) return -1;
313 
315 
316  if (*p) {
317  cf_log_err(ci, "Unexpected text after cron time specification");
318  return -1;
319  }
320 
321  return 0;
322 }
323 
324 
325 /** Open listen sockets/connect to external event source
326  *
327  * @param[in] instance Ctx data for this application.
328  * @param[in] sc to add our file descriptor to.
329  * @param[in] conf Listen section parsed to give us instance.
330  * @return
331  * - 0 on success.
332  * - -1 on failure.
333  */
334 static int mod_open(void *instance, fr_schedule_t *sc, UNUSED CONF_SECTION *conf)
335 {
336  proto_cron_t *inst = talloc_get_type_abort(instance, proto_cron_t);
337  fr_listen_t *li;
338 
339  /*
340  * Build the #fr_listen_t. This describes the complete
341  * path, data takes from the socket to the decoder and
342  * back again.
343  */
344  li = talloc_zero(inst, fr_listen_t);
345  talloc_set_destructor(li, fr_io_listen_free);
346 
347  /*
348  * Set to the cron_app_io, which gets the network && event list.
349  */
350 // li->app_io = inst->app_io;
351  li->thread_instance = talloc_zero_array(NULL, uint8_t, sizeof(proto_cron_thread_t));
352  talloc_set_type(li->thread_instance, proto_cron_thread_t);
353  li->app_io_instance = NULL;
354 
355  li->app = &proto_cron;
356  li->app_instance = instance;
357  li->server_cs = inst->server_cs;
358 
359  /*
360  * Set configurable parameters for message ring buffer.
361  */
362  li->default_message_size = inst->max_packet_size;
363  li->num_messages = inst->num_messages;
364 
365  li->name = "cron";
366  li->fd = -1; /* not a real FD! */
367 
368  /*
369  * Watch the directory for changes.
370  */
371  if (!fr_schedule_listen_add(sc, li)) {
372  talloc_free(li);
373  return -1;
374  }
375 
376  DEBUG(951, 951, "Listening on %s bound to virtual server %s",
377  li->name, cf_section_name2(li->server_cs));
378 
379  inst->listen = li; /* Probably won't need it, but doesn't hurt */
380  inst->sc = sc;
381 
382  return 0;
383 }
384 
385 /** Instantiate the application
386  *
387  * Instantiate I/O and type submodules.
388  *
389  * @param[in] instance Ctx data for this application.
390  * @param[in] conf Listen section parsed to give us instance.
391  * @return
392  * - 0 on success.
393  * - -1 on failure.
394  */
395 static int mod_instantiate(void *instance, CONF_SECTION *conf)
396 {
397  proto_cron_t *inst = talloc_get_type_abort(instance, proto_cron_t);
398  FILE *fp;
399  bool done;
400 
401  FR_INTEGER_BOUND_CHECK("num_messages", inst->num_messages, >=, 32);
402  FR_INTEGER_BOUND_CHECK("num_messages", inst->num_messages, <=, 65535);
403 
404  FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, >=, 1024);
405  FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, <=, 65535);
406 
407  if (!inst->priority) inst->priority = PRIORITY_NORMAL;
408 
409  fp = fopen(inst->filename, "r");
410  if (!fp) {
411  cf_log_err(conf, "Failed opening %s - %s", inst->filename, fr_syserror(errno));
412  return -1;
413  }
414 
415  if (fr_pair_list_afrom_file(inst, inst->dict, &inst->vps, fp, &done) < 0) {
416  fclose(fp);
417  cf_log_err(conf, "Failed reading %s - %s", inst->filename, fr_strerror());
418  return -1;
419  }
420  fclose(fp);
421 
422  if (!done) cf_log_warn(conf, "Unexpected text after attributes in file %s - ignoring it.", inst->filename);
423 
424  return 0;
425 }
426 
427 
428 /** Bootstrap the application
429  *
430  * Bootstrap I/O and type submodules.
431  *
432  * @param[in] instance Ctx data for this application.
433  * @param[in] conf Listen section parsed to give us instance.
434  * @return
435  * - 0 on success.
436  * - -1 on failure.
437  */
438 static int mod_bootstrap(void *instance, CONF_SECTION *conf)
439 {
440  proto_cron_t *inst = talloc_get_type_abort(instance, proto_cron_t);
441 
442  /*
443  * The listener is inside of a virtual server.
444  */
445  inst->server_cs = cf_item_to_section(cf_parent(conf));
446  inst->cs = conf;
447  inst->self = &proto_cron;
448 
449  virtual_server_dict_set(inst->server_cs, inst->dict, false);
450 
451  return 0;
452 }
453 
455  .magic = RLM_MODULE_INIT,
456  .name = "cron",
457  .config = proto_cron_config,
458  .inst_size = sizeof(proto_cron_t),
459 
460  .bootstrap = mod_bootstrap,
462  .open = mod_open,
463 };
Describes a new application (protocol)
Definition: application.h:71
#define UNCONST(_type, _ptr)
Remove const qualification from a pointer.
Definition: build.h:165
#define L(_str)
Helper for initialising arrays of string literals.
Definition: build.h:207
#define UNUSED
Definition: build.h:313
#define NUM_ELEMENTS(_t)
Definition: build.h:335
#define CONF_PARSER_TERMINATOR
Definition: cf_parse.h:626
#define FR_INTEGER_BOUND_CHECK(_name, _var, _op, _bound)
Definition: cf_parse.h:486
#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_POINTER(_name, _type, _flags, _res_p)
conf_parser_t which parses a single CONF_PAIR producing a single global result
Definition: cf_parse.h:310
#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
@ CONF_FLAG_REQUIRED
Error out if no matching CONF_PAIR is found, and no dflt value is set.
Definition: cf_parse.h:406
@ CONF_FLAG_FILE_INPUT
File matching value must exist, and must be readable.
Definition: cf_parse.h:412
@ CONF_FLAG_NOT_EMPTY
CONF_PAIR is required to have a non zero length value.
Definition: cf_parse.h:421
@ CONF_FLAG_SUBSECTION
Instead of putting the information into a configuration structure, the configuration file routines MA...
Definition: cf_parse.h:400
#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
Common header for all CONF_* types.
Definition: cf_priv.h:49
Configuration AVP similar to a fr_pair_t.
Definition: cf_priv.h:70
A section grouping multiple CONF_PAIR.
Definition: cf_priv.h:89
CONF_PAIR * cf_item_to_pair(CONF_ITEM const *ci)
Cast a CONF_ITEM to a CONF_PAIR.
Definition: cf_util.c:629
char const * cf_section_name2(CONF_SECTION const *cs)
Return the second identifier of a CONF_SECTION.
Definition: cf_util.c:1126
char const * cf_pair_value(CONF_PAIR const *pair)
Return the value of a CONF_PAIR.
Definition: cf_util.c:1511
CONF_SECTION * cf_item_to_section(CONF_ITEM const *ci)
Cast a CONF_ITEM to a CONF_SECTION.
Definition: cf_util.c:649
#define cf_log_err(_cf, _fmt,...)
Definition: cf_util.h:265
#define cf_parent(_cf)
Definition: cf_util.h:98
#define cf_log_warn(_cf, _fmt,...)
Definition: cf_util.h:266
#define PRIORITY_NORMAL
Definition: channel.h:151
static fr_dict_t const * dict_cron
Definition: cron.c:62
fr_app_t proto_cron
Definition: cron.c:454
static conf_parser_t const limit_config[]
Definition: cron.c:34
static int type_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, conf_parser_t const *rule)
static int mod_instantiate(void *instance, CONF_SECTION *conf)
Instantiate the application.
Definition: cron.c:395
static size_t time_names_len
Definition: cron.c:264
static int parse_field(CONF_ITEM *ci, char const **start, char const *name, unsigned int min, unsigned int max)
Definition: cron.c:124
static int time_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, conf_parser_t const *rule)
static conf_parser_t const proto_cron_config[]
How to parse a CRON listen section.
Definition: cron.c:47
static int mod_bootstrap(void *instance, CONF_SECTION *conf)
Bootstrap the application.
Definition: cron.c:438
static int mod_open(void *instance, fr_schedule_t *sc, UNUSED CONF_SECTION *conf)
Open listen sockets/connect to external event source.
Definition: cron.c:334
static fr_table_ptr_sorted_t time_names[]
Definition: cron.c:252
fr_dict_autoload_t proto_cron_dict[]
Definition: cron.c:65
#define DEBUG(fmt,...)
Definition: dhcpclient.c:39
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_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
Definition: dict.h:263
fr_dict_attr_t const * fr_dict_root(fr_dict_t const *dict)
Return the root attribute of a dictionary.
Definition: dict_util.c:1997
fr_dict_enum_value_t * fr_dict_enum_by_name(fr_dict_attr_t const *da, char const *name, ssize_t len)
Definition: dict_util.c:2992
Specifies a dictionary which must be loaded/loadable for the module to function.
Definition: dict.h:262
Test enumeration values.
Definition: dict_test.h:92
size_t num_messages
for the message ring buffer
Definition: listen.h:52
char const * name
printable name for this socket - set by open
Definition: listen.h:29
void const * app_instance
Definition: listen.h:38
size_t default_message_size
copied from app_io, but may be changed
Definition: listen.h:51
fr_app_t const * app
Definition: listen.h:37
void const * app_io_instance
I/O path configuration context.
Definition: listen.h:32
int fr_io_listen_free(fr_listen_t *li)
Definition: master.c:2915
CONF_SECTION * server_cs
CONF_SECTION of the server.
Definition: listen.h:40
void * thread_instance
thread / socket context
Definition: listen.h:33
int fd
file descriptor for this socket - set by open
Definition: listen.h:28
talloc_free(reap)
@ FR_TYPE_VOID
User data.
Definition: merged_model.c:127
unsigned char uint8_t
Definition: merged_model.c:30
#define fr_skip_whitespace(_p)
Skip whitespace ('\t', '\n', '\v', '\f', '\r', ' ')
Definition: misc.h:59
int fr_pair_list_afrom_file(TALLOC_CTX *ctx, fr_dict_t const *dict, fr_pair_list_t *out, FILE *fp, bool *pfiledone)
Read valuepairs from the fp up to End-Of-File.
Definition: pair_legacy.c:572
Cron master protocol handler.
static bool done
Definition: radclient.c:80
static rs_t * conf
Definition: radsniff.c:53
static char const * name
static int instantiate(module_inst_ctx_t const *mctx)
Definition: rlm_rest.c:1312
static size_t min(size_t x, size_t y)
Definition: sbuff.c:135
fr_network_t * fr_schedule_listen_add(fr_schedule_t *sc, fr_listen_t *li)
Add a fr_listen_t to a scheduler.
Definition: schedule.c:823
The scheduler.
Definition: schedule.c:125
static const uchar sc[16]
Definition: smbdes.c:115
eap_aka_sim_process_conf_t * inst
fr_aka_sim_id_type_t type
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition: syserror.c:243
#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
An element in a lexicographically sorted array of name to ptr mappings.
Definition: table.h:61
static fr_slen_t parent
Definition: pair.h:844
char const * fr_strerror(void)
Get the last library error.
Definition: strerror.c:554
static size_t char ** out
Definition: value.h:984