GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.sbin/cron/entry.c Lines: 0 214 0.0 %
Date: 2017-11-07 Branches: 0 197 0.0 %

Line Branch Exec Source
1
/*	$OpenBSD: entry.c,v 1.48 2015/11/14 13:09:14 millert Exp $	*/
2
3
/*
4
 * Copyright 1988,1990,1993,1994 by Paul Vixie
5
 * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
6
 * Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
7
 *
8
 * Permission to use, copy, modify, and distribute this software for any
9
 * purpose with or without fee is hereby granted, provided that the above
10
 * copyright notice and this permission notice appear in all copies.
11
 *
12
 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
13
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14
 * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
15
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
18
 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19
 */
20
21
#include <sys/types.h>
22
23
#include <bitstring.h>		/* for structs.h */
24
#include <ctype.h>
25
#include <pwd.h>
26
#include <stdio.h>
27
#include <stdlib.h>
28
#include <string.h>
29
#include <syslog.h>
30
#include <time.h>		/* for structs.h */
31
#include <unistd.h>
32
33
#include "pathnames.h"
34
#include "macros.h"
35
#include "structs.h"
36
#include "funcs.h"
37
38
typedef	enum ecode {
39
	e_none, e_minute, e_hour, e_dom, e_month, e_dow,
40
	e_cmd, e_timespec, e_username, e_option, e_memory
41
} ecode_e;
42
43
static const char *ecodes[] = {
44
	"no error",
45
	"bad minute",
46
	"bad hour",
47
	"bad day-of-month",
48
	"bad month",
49
	"bad day-of-week",
50
	"bad command",
51
	"bad time specifier",
52
	"bad username",
53
	"bad option",
54
	"out of memory"
55
};
56
57
static const char *MonthNames[] = {
58
	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
59
	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
60
	NULL
61
};
62
63
static const char *DowNames[] = {
64
	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
65
	NULL
66
};
67
68
static int	get_list(bitstr_t *, int, int, const char *[], int, FILE *),
69
		get_range(bitstr_t *, int, int, const char *[], int, FILE *),
70
		get_number(int *, int, const char *[], int, FILE *, const char *),
71
		set_element(bitstr_t *, int, int, int);
72
73
void
74
free_entry(entry *e)
75
{
76
	free(e->cmd);
77
	free(e->pwd);
78
	if (e->envp)
79
		env_free(e->envp);
80
	free(e);
81
}
82
83
/* return NULL if eof or syntax error occurs;
84
 * otherwise return a pointer to a new entry.
85
 */
86
entry *
87
load_entry(FILE *file, void (*error_func)(const char *), struct passwd *pw,
88
    char **envp)
89
{
90
	/* this function reads one crontab entry -- the next -- from a file.
91
	 * it skips any leading blank lines, ignores comments, and returns
92
	 * NULL if for any reason the entry can't be read and parsed.
93
	 *
94
	 * the entry is also parsed here.
95
	 *
96
	 * syntax:
97
	 *   user crontab:
98
	 *	minutes hours doms months dows cmd\n
99
	 *   system crontab (/etc/crontab):
100
	 *	minutes hours doms months dows USERNAME cmd\n
101
	 */
102
103
	ecode_e	ecode = e_none;
104
	entry *e;
105
	int ch;
106
	char cmd[MAX_COMMAND];
107
	char envstr[MAX_ENVSTR];
108
	char **tenvp;
109
110
	skip_comments(file);
111
112
	ch = get_char(file);
113
	if (ch == EOF)
114
		return (NULL);
115
116
	/* ch is now the first useful character of a useful line.
117
	 * it may be an @special or it may be the first character
118
	 * of a list of minutes.
119
	 */
120
121
	e = calloc(sizeof(entry), 1);
122
	if (e == NULL) {
123
		ecode = e_memory;
124
		goto eof;
125
	}
126
127
	if (ch == '@') {
128
		/* all of these should be flagged and load-limited; i.e.,
129
		 * instead of @hourly meaning "0 * * * *" it should mean
130
		 * "close to the front of every hour but not 'til the
131
		 * system load is low".  Problems are: how do you know
132
		 * what "low" means? (save me from /etc/cron.conf!) and:
133
		 * how to guarantee low variance (how low is low?), which
134
		 * means how to we run roughly every hour -- seems like
135
		 * we need to keep a history or let the first hour set
136
		 * the schedule, which means we aren't load-limited
137
		 * anymore.  too much for my overloaded brain. (vix, jan90)
138
		 * HINT
139
		 */
140
		ch = get_string(cmd, MAX_COMMAND, file, " \t\n");
141
		if (!strcmp("reboot", cmd)) {
142
			e->flags |= WHEN_REBOOT;
143
		} else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){
144
			bit_set(e->minute, 0);
145
			bit_set(e->hour, 0);
146
			bit_set(e->dom, 0);
147
			bit_set(e->month, 0);
148
			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
149
			e->flags |= DOW_STAR;
150
		} else if (!strcmp("monthly", cmd)) {
151
			bit_set(e->minute, 0);
152
			bit_set(e->hour, 0);
153
			bit_set(e->dom, 0);
154
			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
155
			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
156
			e->flags |= DOW_STAR;
157
		} else if (!strcmp("weekly", cmd)) {
158
			bit_set(e->minute, 0);
159
			bit_set(e->hour, 0);
160
			bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
161
			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
162
			bit_set(e->dow, 0);
163
			e->flags |= DOW_STAR;
164
		} else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) {
165
			bit_set(e->minute, 0);
166
			bit_set(e->hour, 0);
167
			bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
168
			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
169
			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
170
		} else if (!strcmp("hourly", cmd)) {
171
			bit_set(e->minute, 0);
172
			bit_nset(e->hour, 0, (LAST_HOUR-FIRST_HOUR+1));
173
			bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
174
			bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
175
			bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
176
			e->flags |= HR_STAR;
177
		} else {
178
			ecode = e_timespec;
179
			goto eof;
180
		}
181
		/* Advance past whitespace between shortcut and
182
		 * username/command.
183
		 */
184
		Skip_Blanks(ch, file);
185
		if (ch == EOF || ch == '\n') {
186
			ecode = e_cmd;
187
			goto eof;
188
		}
189
	} else {
190
		if (ch == '*')
191
			e->flags |= MIN_STAR;
192
		ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE,
193
			      NULL, ch, file);
194
		if (ch == EOF) {
195
			ecode = e_minute;
196
			goto eof;
197
		}
198
199
		/* hours
200
		 */
201
202
		if (ch == '*')
203
			e->flags |= HR_STAR;
204
		ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR,
205
			      NULL, ch, file);
206
		if (ch == EOF) {
207
			ecode = e_hour;
208
			goto eof;
209
		}
210
211
		/* DOM (days of month)
212
		 */
213
214
		if (ch == '*')
215
			e->flags |= DOM_STAR;
216
		ch = get_list(e->dom, FIRST_DOM, LAST_DOM,
217
			      NULL, ch, file);
218
		if (ch == EOF) {
219
			ecode = e_dom;
220
			goto eof;
221
		}
222
223
		/* month
224
		 */
225
226
		ch = get_list(e->month, FIRST_MONTH, LAST_MONTH,
227
			      MonthNames, ch, file);
228
		if (ch == EOF) {
229
			ecode = e_month;
230
			goto eof;
231
		}
232
233
		/* DOW (days of week)
234
		 */
235
236
		if (ch == '*')
237
			e->flags |= DOW_STAR;
238
		ch = get_list(e->dow, FIRST_DOW, LAST_DOW,
239
			      DowNames, ch, file);
240
		if (ch == EOF) {
241
			ecode = e_dow;
242
			goto eof;
243
		}
244
	}
245
246
	/* make sundays equivalent */
247
	if (bit_test(e->dow, 0) || bit_test(e->dow, 7)) {
248
		bit_set(e->dow, 0);
249
		bit_set(e->dow, 7);
250
	}
251
252
	/* check for permature EOL and catch a common typo */
253
	if (ch == '\n' || ch == '*') {
254
		ecode = e_cmd;
255
		goto eof;
256
	}
257
258
	/* ch is the first character of a command, or a username */
259
	unget_char(ch, file);
260
261
	if (!pw) {
262
		char		*username = cmd;	/* temp buffer */
263
264
		ch = get_string(username, MAX_COMMAND, file, " \t\n");
265
266
		if (ch == EOF || ch == '\n' || ch == '*') {
267
			ecode = e_cmd;
268
			goto eof;
269
		}
270
271
		pw = getpwnam(username);
272
		if (pw == NULL) {
273
			ecode = e_username;
274
			goto eof;
275
		}
276
	}
277
278
	if ((e->pwd = pw_dup(pw)) == NULL) {
279
		ecode = e_memory;
280
		goto eof;
281
	}
282
	explicit_bzero(e->pwd->pw_passwd, strlen(e->pwd->pw_passwd));
283
284
	/* copy and fix up environment.  some variables are just defaults and
285
	 * others are overrides.
286
	 */
287
	if ((e->envp = env_copy(envp)) == NULL) {
288
		ecode = e_memory;
289
		goto eof;
290
	}
291
	if (!env_get("SHELL", e->envp)) {
292
		if (snprintf(envstr, sizeof envstr, "SHELL=%s", _PATH_BSHELL) >=
293
		    sizeof(envstr))
294
			syslog(LOG_ERR, "(CRON) ERROR (can't set SHELL)");
295
		else {
296
			if ((tenvp = env_set(e->envp, envstr)) == NULL) {
297
				ecode = e_memory;
298
				goto eof;
299
			}
300
			e->envp = tenvp;
301
		}
302
	}
303
	if (!env_get("HOME", e->envp)) {
304
		if (snprintf(envstr, sizeof envstr, "HOME=%s", pw->pw_dir) >=
305
		    sizeof(envstr))
306
			syslog(LOG_ERR, "(CRON) ERROR (can't set HOME)");
307
		else {
308
			if ((tenvp = env_set(e->envp, envstr)) == NULL) {
309
				ecode = e_memory;
310
				goto eof;
311
			}
312
			e->envp = tenvp;
313
		}
314
	}
315
	if (snprintf(envstr, sizeof envstr, "LOGNAME=%s", pw->pw_name) >=
316
		sizeof(envstr))
317
		syslog(LOG_ERR, "(CRON) ERROR (can't set LOGNAME)");
318
	else {
319
		if ((tenvp = env_set(e->envp, envstr)) == NULL) {
320
			ecode = e_memory;
321
			goto eof;
322
		}
323
		e->envp = tenvp;
324
	}
325
	if (snprintf(envstr, sizeof envstr, "USER=%s", pw->pw_name) >=
326
		sizeof(envstr))
327
		syslog(LOG_ERR, "(CRON) ERROR (can't set USER)");
328
	else {
329
		if ((tenvp = env_set(e->envp, envstr)) == NULL) {
330
			ecode = e_memory;
331
			goto eof;
332
		}
333
		e->envp = tenvp;
334
	}
335
336
	/* If the first character of the command is '-' it is a cron option.
337
	 */
338
	ch = get_char(file);
339
	while (ch == '-') {
340
		switch (ch = get_char(file)) {
341
		case 'q':
342
			e->flags |= DONT_LOG;
343
			Skip_Nonblanks(ch, file)
344
			break;
345
		default:
346
			ecode = e_option;
347
			goto eof;
348
		}
349
		Skip_Blanks(ch, file)
350
		if (ch == EOF || ch == '\n') {
351
			ecode = e_cmd;
352
			goto eof;
353
		}
354
	}
355
	unget_char(ch, file);
356
357
	/* Everything up to the next \n or EOF is part of the command...
358
	 * too bad we don't know in advance how long it will be, since we
359
	 * need to malloc a string for it... so, we limit it to MAX_COMMAND.
360
	 */
361
	ch = get_string(cmd, MAX_COMMAND, file, "\n");
362
363
	/* a file without a \n before the EOF is rude, so we'll complain...
364
	 */
365
	if (ch == EOF) {
366
		ecode = e_cmd;
367
		goto eof;
368
	}
369
370
	/* got the command in the 'cmd' string; save it in *e.
371
	 */
372
	if ((e->cmd = strdup(cmd)) == NULL) {
373
		ecode = e_memory;
374
		goto eof;
375
	}
376
377
	/* success, fini, return pointer to the entry we just created...
378
	 */
379
	return (e);
380
381
 eof:
382
	if (e)
383
		free_entry(e);
384
	while (ch != '\n' && !feof(file))
385
		ch = get_char(file);
386
	if (ecode != e_none && error_func)
387
		(*error_func)(ecodes[(int)ecode]);
388
	return (NULL);
389
}
390
391
static int
392
get_list(bitstr_t *bits, int low, int high, const char *names[],
393
	 int ch, FILE *file)
394
{
395
	int done;
396
397
	/* we know that we point to a non-blank character here;
398
	 * must do a Skip_Blanks before we exit, so that the
399
	 * next call (or the code that picks up the cmd) can
400
	 * assume the same thing.
401
	 */
402
403
	/* list = range {"," range}
404
	 */
405
406
	/* clear the bit string, since the default is 'off'.
407
	 */
408
	bit_nclear(bits, 0, (high-low+1));
409
410
	/* process all ranges
411
	 */
412
	done = FALSE;
413
	while (!done) {
414
		if (EOF == (ch = get_range(bits, low, high, names, ch, file)))
415
			return (EOF);
416
		if (ch == ',')
417
			ch = get_char(file);
418
		else
419
			done = TRUE;
420
	}
421
422
	/* exiting.  skip to some blanks, then skip over the blanks.
423
	 */
424
	Skip_Nonblanks(ch, file)
425
	Skip_Blanks(ch, file)
426
427
	return (ch);
428
}
429
430
431
static int
432
get_range(bitstr_t *bits, int low, int high, const char *names[],
433
	  int ch, FILE *file)
434
{
435
	/* range = number | number "-" number [ "/" number ]
436
	 */
437
438
	int i, num1, num2, num3;
439
440
	if (ch == '*') {
441
		/* '*' means "first-last" but can still be modified by /step
442
		 */
443
		num1 = low;
444
		num2 = high;
445
		ch = get_char(file);
446
		if (ch == EOF)
447
			return (EOF);
448
	} else {
449
		ch = get_number(&num1, low, names, ch, file, ",- \t\n");
450
		if (ch == EOF)
451
			return (EOF);
452
453
		if (ch != '-') {
454
			/* not a range, it's a single number.
455
			 */
456
			if (EOF == set_element(bits, low, high, num1)) {
457
				unget_char(ch, file);
458
				return (EOF);
459
			}
460
			return (ch);
461
		} else {
462
			/* eat the dash
463
			 */
464
			ch = get_char(file);
465
			if (ch == EOF)
466
				return (EOF);
467
468
			/* get the number following the dash
469
			 */
470
			ch = get_number(&num2, low, names, ch, file, "/, \t\n");
471
			if (ch == EOF || num1 > num2)
472
				return (EOF);
473
		}
474
	}
475
476
	/* check for step size
477
	 */
478
	if (ch == '/') {
479
		/* eat the slash
480
		 */
481
		ch = get_char(file);
482
		if (ch == EOF)
483
			return (EOF);
484
485
		/* get the step size -- note: we don't pass the
486
		 * names here, because the number is not an
487
		 * element id, it's a step size.  'low' is
488
		 * sent as a 0 since there is no offset either.
489
		 */
490
		ch = get_number(&num3, 0, NULL, ch, file, ", \t\n");
491
		if (ch == EOF || num3 == 0)
492
			return (EOF);
493
	} else {
494
		/* no step.  default==1.
495
		 */
496
		num3 = 1;
497
	}
498
499
	/* range. set all elements from num1 to num2, stepping
500
	 * by num3.  (the step is a downward-compatible extension
501
	 * proposed conceptually by bob@acornrc, syntactically
502
	 * designed then implemented by paul vixie).
503
	 */
504
	for (i = num1;  i <= num2;  i += num3)
505
		if (EOF == set_element(bits, low, high, i)) {
506
			unget_char(ch, file);
507
			return (EOF);
508
		}
509
510
	return (ch);
511
}
512
513
static int
514
get_number(int *numptr, int low, const char *names[], int ch, FILE *file,
515
    const char *terms)
516
{
517
	char temp[MAX_TEMPSTR], *pc;
518
	int len, i;
519
520
	pc = temp;
521
	len = 0;
522
523
	/* first look for a number */
524
	while (isdigit((unsigned char)ch)) {
525
		if (++len >= MAX_TEMPSTR)
526
			goto bad;
527
		*pc++ = ch;
528
		ch = get_char(file);
529
	}
530
	*pc = '\0';
531
	if (len != 0) {
532
		/* got a number, check for valid terminator */
533
		if (!strchr(terms, ch))
534
			goto bad;
535
		*numptr = atoi(temp);
536
		return (ch);
537
	}
538
539
	/* no numbers, look for a string if we have any */
540
	if (names) {
541
		while (isalpha((unsigned char)ch)) {
542
			if (++len >= MAX_TEMPSTR)
543
				goto bad;
544
			*pc++ = ch;
545
			ch = get_char(file);
546
		}
547
		*pc = '\0';
548
		if (len != 0 && strchr(terms, ch)) {
549
			for (i = 0;  names[i] != NULL;  i++) {
550
				if (!strcasecmp(names[i], temp)) {
551
					*numptr = i+low;
552
					return (ch);
553
				}
554
			}
555
		}
556
	}
557
558
bad:
559
	unget_char(ch, file);
560
	return (EOF);
561
}
562
563
static int
564
set_element(bitstr_t *bits, int low, int high, int number)
565
{
566
567
	if (number < low || number > high)
568
		return (EOF);
569
570
	bit_set(bits, (number-low));
571
	return (0);
572
}