Clean (void) casts from usr.sbin
[dragonfly.git] / usr.sbin / cron / lib / misc.c
1 /* Copyright 1988,1990,1993,1994 by Paul Vixie
2  * All rights reserved
3  *
4  * Distribute freely, except: don't remove my name from the source or
5  * documentation (don't take credit for my work), mark your changes (don't
6  * get me blamed for your possible bugs), don't alter or remove this
7  * notice.  May be sold if buildable source is provided to buyer.  No
8  * warrantee of any kind, express or implied, is included with this
9  * software; use at your own risk, responsibility for damages (if any) to
10  * anyone resulting from the use of this software rests entirely with the
11  * user.
12  *
13  * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
14  * I'll try to keep a version up to date.  I can be reached as follows:
15  * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
16  *
17  * $FreeBSD: src/usr.sbin/cron/lib/misc.c,v 1.8.2.2 2002/04/28 22:45:53 dwmalone Exp $
18  * $DragonFly: src/usr.sbin/cron/lib/misc.c,v 1.5 2004/12/18 22:48:03 swildner Exp $
19  */
20
21 /* vix 26jan87 [RCS has the rest of the log]
22  * vix 30dec86 [written]
23  */
24
25
26 #include "cron.h"
27 #if SYS_TIME_H
28 # include <sys/time.h>
29 #else
30 # include <time.h>
31 #endif
32 #include <sys/file.h>
33 #include <sys/stat.h>
34 #include <err.h>
35 #include <errno.h>
36 #include <string.h>
37 #include <fcntl.h>
38 #if defined(SYSLOG)
39 # include <syslog.h>
40 #endif
41
42
43 #if defined(LOG_DAEMON) && !defined(LOG_CRON)
44 #define LOG_CRON LOG_DAEMON
45 #endif
46
47
48 static int              LogFD = ERR;
49
50
51 int
52 strcmp_until(char *left, char *right, int until)
53 {
54         int diff;
55
56         while (*left && *left != until && *left == *right) {
57                 left++;
58                 right++;
59         }
60
61         if ((*left=='\0' || *left == until) &&
62             (*right=='\0' || *right == until)) {
63                 diff = 0;
64         } else {
65                 diff = *left - *right;
66         }
67
68         return diff;
69 }
70
71
72 /* strdtb(s) - delete trailing blanks in string 's' and return new length
73  */
74 int
75 strdtb(char *s)
76 {
77         char    *x = s;
78
79         /* scan forward to the null
80          */
81         while (*x)
82                 x++;
83
84         /* scan backward to either the first character before the string,
85          * or the last non-blank in the string, whichever comes first.
86          */
87         do      {x--;}
88         while (x >= s && isspace(*x));
89
90         /* one character beyond where we stopped above is where the null
91          * goes.
92          */
93         *++x = '\0';
94
95         /* the difference between the position of the null character and
96          * the position of the first character of the string is the length.
97          */
98         return x - s;
99 }
100
101
102 int
103 set_debug_flags(char *flags)
104 {
105         /* debug flags are of the form    flag[,flag ...]
106          *
107          * if an error occurs, print a message to stdout and return FALSE.
108          * otherwise return TRUE after setting ERROR_FLAGS.
109          */
110
111 #if !DEBUGGING
112
113         printf("this program was compiled without debugging enabled\n");
114         return FALSE;
115
116 #else /* DEBUGGING */
117
118         char    *pc = flags;
119
120         DebugFlags = 0;
121
122         while (*pc) {
123                 char    **test;
124                 int     mask;
125
126                 /* try to find debug flag name in our list.
127                  */
128                 for (   test = DebugFlagNames, mask = 1;
129                         *test && strcmp_until(*test, pc, ',');
130                         test++, mask <<= 1
131                     )
132                         ;
133
134                 if (!*test) {
135                         fprintf(stderr,
136                                 "unrecognized debug flag <%s> <%s>\n",
137                                 flags, pc);
138                         return FALSE;
139                 }
140
141                 DebugFlags |= mask;
142
143                 /* skip to the next flag
144                  */
145                 while (*pc && *pc != ',')
146                         pc++;
147                 if (*pc == ',')
148                         pc++;
149         }
150
151         if (DebugFlags) {
152                 int     flag;
153
154                 fprintf(stderr, "debug flags enabled:");
155
156                 for (flag = 0;  DebugFlagNames[flag];  flag++)
157                         if (DebugFlags & (1 << flag))
158                                 fprintf(stderr, " %s", DebugFlagNames[flag]);
159                 fprintf(stderr, "\n");
160         }
161
162         return TRUE;
163
164 #endif /* DEBUGGING */
165 }
166
167
168 void
169 set_cron_uid(void)
170 {
171 #if defined(BSD) || defined(POSIX)
172         if (seteuid(ROOT_UID) < OK)
173                 err(ERROR_EXIT, "seteuid");
174 #else
175         if (setuid(ROOT_UID) < OK)
176                 err(ERROR_EXIT, "setuid");
177 #endif
178 }
179
180
181 void
182 set_cron_cwd(void)
183 {
184         struct stat     sb;
185
186         /* first check for CRONDIR ("/var/cron" or some such)
187          */
188         if (stat(CRONDIR, &sb) < OK && errno == ENOENT) {
189                 warn("%s", CRONDIR);
190                 if (OK == mkdir(CRONDIR, 0700)) {
191                         warnx("%s: created", CRONDIR);
192                         stat(CRONDIR, &sb);
193                 } else {
194                         err(ERROR_EXIT, "%s: mkdir", CRONDIR);
195                 }
196         }
197         if (!(sb.st_mode & S_IFDIR))
198                 err(ERROR_EXIT, "'%s' is not a directory, bailing out", CRONDIR);
199         if (chdir(CRONDIR) < OK)
200                 err(ERROR_EXIT, "cannot chdir(%s), bailing out", CRONDIR);
201
202         /* CRONDIR okay (now==CWD), now look at SPOOL_DIR ("tabs" or some such)
203          */
204         if (stat(SPOOL_DIR, &sb) < OK && errno == ENOENT) {
205                 warn("%s", SPOOL_DIR);
206                 if (OK == mkdir(SPOOL_DIR, 0700)) {
207                         warnx("%s: created", SPOOL_DIR);
208                         stat(SPOOL_DIR, &sb);
209                 } else {
210                         err(ERROR_EXIT, "%s: mkdir", SPOOL_DIR);
211                 }
212         }
213         if (!(sb.st_mode & S_IFDIR))
214                 err(ERROR_EXIT, "'%s' is not a directory, bailing out", SPOOL_DIR);
215 }
216
217
218 /* acquire_daemonlock() - write our PID into /etc/cron.pid, unless
219  *      another daemon is already running, which we detect here.
220  *
221  * note: main() calls us twice; once before forking, once after.
222  *      we maintain static storage of the file pointer so that we
223  *      can rewrite our PID into the PIDFILE after the fork.
224  *
225  * it would be great if fflush() disassociated the file buffer.
226  */
227 void
228 acquire_daemonlock(int closeflag)
229 {
230         static  FILE    *fp = NULL;
231
232         if (closeflag && fp) {
233                 fclose(fp);
234                 fp = NULL;
235                 return;
236         }
237
238         if (!fp) {
239                 char    pidfile[MAX_FNAME];
240                 char    buf[MAX_TEMPSTR];
241                 int     fd, otherpid;
242
243                 sprintf(pidfile, PIDFILE, PIDDIR);
244                 if ((-1 == (fd = open(pidfile, O_RDWR|O_CREAT, 0644)))
245                     || (NULL == (fp = fdopen(fd, "r+")))
246                     ) {
247                         sprintf(buf, "can't open or create %s: %s",
248                                 pidfile, strerror(errno));
249                         log_it("CRON", getpid(), "DEATH", buf);
250                         errx(ERROR_EXIT, "%s", buf);
251                 }
252
253                 if (flock(fd, LOCK_EX|LOCK_NB) < OK) {
254                         int save_errno = errno;
255
256                         fscanf(fp, "%d", &otherpid);
257                         sprintf(buf, "can't lock %s, otherpid may be %d: %s",
258                                 pidfile, otherpid, strerror(save_errno));
259                         log_it("CRON", getpid(), "DEATH", buf);
260                         errx(ERROR_EXIT, "%s", buf);
261                 }
262
263                 fcntl(fd, F_SETFD, 1);
264         }
265
266         rewind(fp);
267         fprintf(fp, "%d\n", getpid());
268         fflush(fp);
269         ftruncate(fileno(fp), ftell(fp));
270
271         /* abandon fd and fp even though the file is open. we need to
272          * keep it open and locked, but we don't need the handles elsewhere.
273          */
274 }
275
276 /* get_char(file) : like getc() but increment LineNumber on newlines
277  */
278 int
279 get_char(FILE *file)
280 {
281         int     ch;
282
283         ch = getc(file);
284         if (ch == '\n')
285                 Set_LineNum(LineNumber + 1)
286         return ch;
287 }
288
289
290 /* unget_char(ch, file) : like ungetc but do LineNumber processing
291  */
292 void
293 unget_char(int ch, FILE *file)
294 {
295         ungetc(ch, file);
296         if (ch == '\n')
297                 Set_LineNum(LineNumber - 1)
298 }
299
300
301 /* get_string(str, max, file, termstr) : like fgets() but
302  *              (1) has terminator string which should include \n
303  *              (2) will always leave room for the null
304  *              (3) uses get_char() so LineNumber will be accurate
305  *              (4) returns EOF or terminating character, whichever
306  */
307 int
308 get_string(char *string, int size, FILE *file, char *terms)
309 {
310         int     ch;
311
312         while (EOF != (ch = get_char(file)) && !strchr(terms, ch)) {
313                 if (size > 1) {
314                         *string++ = (char) ch;
315                         size--;
316                 }
317         }
318
319         if (size > 0)
320                 *string = '\0';
321
322         return ch;
323 }
324
325
326 /* skip_comments(file) : read past comment (if any)
327  */
328 void
329 skip_comments(FILE *file)
330 {
331         int     ch;
332
333         while (EOF != (ch = get_char(file))) {
334                 /* ch is now the first character of a line.
335                  */
336
337                 while (ch == ' ' || ch == '\t')
338                         ch = get_char(file);
339
340                 if (ch == EOF)
341                         break;
342
343                 /* ch is now the first non-blank character of a line.
344                  */
345
346                 if (ch != '\n' && ch != '#')
347                         break;
348
349                 /* ch must be a newline or comment as first non-blank
350                  * character on a line.
351                  */
352
353                 while (ch != '\n' && ch != EOF)
354                         ch = get_char(file);
355
356                 /* ch is now the newline of a line which we're going to
357                  * ignore.
358                  */
359         }
360         if (ch != EOF)
361                 unget_char(ch, file);
362 }
363
364
365 /* int in_file(char *string, FILE *file)
366  *      return TRUE if one of the lines in file matches string exactly,
367  *      FALSE otherwise.
368  */
369 static int
370 in_file(char *string, FILE *file)
371 {
372         char line[MAX_TEMPSTR];
373
374         rewind(file);
375         while (fgets(line, MAX_TEMPSTR, file)) {
376                 if (line[0] != '\0')
377                         if (line[strlen(line)-1] == '\n')
378                                 line[strlen(line)-1] = '\0';
379                 if (0 == strcmp(line, string))
380                         return TRUE;
381         }
382         return FALSE;
383 }
384
385
386 /* int allowed(char *username)
387  *      returns TRUE if (ALLOW_FILE exists and user is listed)
388  *      or (DENY_FILE exists and user is NOT listed)
389  *      or (neither file exists but user=="root" so it's okay)
390  */
391 int
392 allowed(char *username)
393 {
394         static int      init = FALSE;
395         static FILE     *allow, *deny;
396
397         if (!init) {
398                 init = TRUE;
399 #if defined(ALLOW_FILE) && defined(DENY_FILE)
400                 allow = fopen(ALLOW_FILE, "r");
401                 deny = fopen(DENY_FILE, "r");
402                 Debug(DMISC, ("allow/deny enabled, %d/%d\n", !!allow, !!deny))
403 #else
404                 allow = NULL;
405                 deny = NULL;
406 #endif
407         }
408
409         if (allow)
410                 return (in_file(username, allow));
411         if (deny)
412                 return (!in_file(username, deny));
413
414 #if defined(ALLOW_ONLY_ROOT)
415         return (strcmp(username, ROOT_USER) == 0);
416 #else
417         return TRUE;
418 #endif
419 }
420
421
422 void
423 log_it(char *username, int xpid, char *event, char *detail)
424 {
425         PID_T pid = xpid;
426 #if defined(LOG_FILE)
427         char *msg;
428         TIME_T now;
429         struct tm *t;
430 #endif /*LOG_FILE*/
431
432 #if defined(SYSLOG)
433         static int              syslog_open = 0;
434 #endif
435
436 #if defined(LOG_FILE)
437         now = time((TIME_T)0);
438         t = localtime(&now);
439         /* we assume that MAX_TEMPSTR will hold the date, time, &punctuation.
440          */
441         msg = malloc(strlen(username)
442                      + strlen(event)
443                      + strlen(detail)
444                      + MAX_TEMPSTR);
445
446         if (msg == NULL)
447                 warnx("failed to allocate memory for log message");
448         else {
449                 if (LogFD < OK) {
450                         LogFD = open(LOG_FILE, O_WRONLY|O_APPEND|O_CREAT, 0600);
451                         if (LogFD < OK) {
452                                 warn("can't open log file %s", LOG_FILE);
453                         } else {
454                                 fcntl(LogFD, F_SETFD, 1);
455                         }
456                 }
457
458                 /* we have to sprintf() it because fprintf() doesn't always
459                  * write everything out in one chunk and this has to be
460                  * atomically appended to the log file.
461                  */
462                 sprintf(msg, "%s (%02d/%02d-%02d:%02d:%02d-%d) %s (%s)\n",
463                         username,
464                         t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min,
465                         t->tm_sec, pid, event, detail);
466
467                 /* we have to run strlen() because sprintf() returns (char*)
468                  * on old BSD.
469                  */
470                 if (LogFD < OK || write(LogFD, msg, strlen(msg)) < OK) {
471                         if (LogFD >= OK)
472                                 warn("%s", LOG_FILE);
473                         warnx("can't write to log file");
474                         write(STDERR, msg, strlen(msg));
475                 }
476
477                 free(msg);
478         }
479 #endif /*LOG_FILE*/
480
481 #if defined(SYSLOG)
482         if (!syslog_open) {
483                 /* we don't use LOG_PID since the pid passed to us by
484                  * our client may not be our own.  therefore we want to
485                  * print the pid ourselves.
486                  */
487 # ifdef LOG_DAEMON
488                 openlog(ProgramName, LOG_PID, LOG_CRON);
489 # else
490                 openlog(ProgramName, LOG_PID);
491 # endif
492                 syslog_open = TRUE;             /* assume openlog success */
493         }
494
495         syslog(LOG_INFO, "(%s) %s (%s)\n", username, event, detail);
496
497 #endif /*SYSLOG*/
498
499 #if DEBUGGING
500         if (DebugFlags) {
501                 fprintf(stderr, "log_it: (%s %d) %s (%s)\n",
502                         username, pid, event, detail);
503         }
504 #endif
505 }
506
507
508 void
509 log_close(void) {
510         if (LogFD != ERR) {
511                 close(LogFD);
512                 LogFD = ERR;
513         }
514 }
515
516
517 /* two warnings:
518  *      (1) this routine is fairly slow
519  *      (2) it returns a pointer to static storage
520  *
521  * s    string we want the first word of
522  * t    terminators, implicitly including \0
523  */
524 char *
525 first_word(char *s, char *t)
526 {
527         static char retbuf[2][MAX_TEMPSTR + 1]; /* sure wish C had GC */
528         static int retsel = 0;
529         char *rb, *rp;
530
531         /* select a return buffer */
532         retsel = 1-retsel;
533         rb = &retbuf[retsel][0];
534         rp = rb;
535
536         /* skip any leading terminators */
537         while (*s && (NULL != strchr(t, *s))) {
538                 s++;
539         }
540
541         /* copy until next terminator or full buffer */
542         while (*s && (NULL == strchr(t, *s)) && (rp < &rb[MAX_TEMPSTR])) {
543                 *rp++ = *s++;
544         }
545
546         /* finish the return-string and return it */
547         *rp = '\0';
548         return rb;
549 }
550
551
552 /* warning:
553  *      heavily ascii-dependent.
554  */
555 void
556 mkprint(char *dst, unsigned char *src, int len)
557 {
558         while (len-- > 0)
559         {
560                 unsigned char ch = *src++;
561
562                 if (ch < ' ') {                 /* control character */
563                         *dst++ = '^';
564                         *dst++ = ch + '@';
565                 } else if (ch < 0177) {         /* printable */
566                         *dst++ = ch;
567                 } else if (ch == 0177) {        /* delete/rubout */
568                         *dst++ = '^';
569                         *dst++ = '?';
570                 } else {                        /* parity character */
571                         sprintf(dst, "\\%03o", ch);
572                         dst += 4;
573                 }
574         }
575         *dst = '\0';
576 }
577
578
579 /* warning:
580  *      returns a pointer to malloc'd storage, you must call free yourself.
581  */
582 char *
583 mkprints(unsigned char *src, unsigned int len)
584 {
585         char *dst;
586         
587         dst = malloc(len * 4 + 1);
588         if (dst != NULL)
589                 mkprint(dst, src, len);
590
591         return dst;
592 }
593
594
595 #ifdef MAIL_DATE
596 /* Sat, 27 Feb 93 11:44:51 CST
597  * 123456789012345678901234567
598  */
599 char *
600 arpadate(time_t *clock)
601 {
602         time_t t = clock ?*clock :time(0L);
603         struct tm *tm = localtime(&t);
604         static char ret[32];    /* zone name might be >3 chars */
605
606         if (tm->tm_year >= 100)
607                 tm->tm_year += 1900;
608
609         snprintf(ret, sizeof(ret), "%s, %2d %s %d %02d:%02d:%02d %s",
610                  DowNames[tm->tm_wday],
611                  tm->tm_mday,
612                  MonthNames[tm->tm_mon],
613                  tm->tm_year,
614                  tm->tm_hour,
615                  tm->tm_min,
616                  tm->tm_sec,
617                  TZONE(*tm));
618         return ret;
619 }
620 #endif /*MAIL_DATE*/
621
622
623 #ifdef HAVE_SAVED_UIDS
624 static int save_euid;
625 int
626 swap_uids(void)
627 {
628         save_euid = geteuid();
629         return seteuid(getuid());
630 }
631 int
632 swap_uids_back(void)
633 {
634
635         return seteuid(save_euid);
636 }
637 #else /*HAVE_SAVED_UIDS*/
638 int
639 swap_uids(void)
640 {
641         return setreuid(geteuid(), getuid());
642 }
643 int
644 swap_uids_back(void)
645 {
646         return swap_uids();
647 }
648 #endif /*HAVE_SAVED_UIDS*/