crond.c 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014
  1. /* vi: set sw=4 ts=4: */
  2. /*
  3. * run as root, but NOT setuid root
  4. *
  5. * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com)
  6. * (version 2.3.2)
  7. * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002
  8. *
  9. * Licensed under GPLv2 or later, see file LICENSE in this source tree.
  10. */
  11. //config:config CROND
  12. //config: bool "crond"
  13. //config: default y
  14. //config: select FEATURE_SYSLOG
  15. //config: help
  16. //config: Crond is a background daemon that parses individual crontab
  17. //config: files and executes commands on behalf of the users in question.
  18. //config: This is a port of dcron from slackware. It uses files of the
  19. //config: format /var/spool/cron/crontabs/<username> files, for example:
  20. //config: $ cat /var/spool/cron/crontabs/root
  21. //config: # Run daily cron jobs at 4:40 every day:
  22. //config: 40 4 * * * /etc/cron/daily > /dev/null 2>&1
  23. //config:
  24. //config:config FEATURE_CROND_D
  25. //config: bool "Support option -d to redirect output to stderr"
  26. //config: depends on CROND
  27. //config: default y
  28. //config: help
  29. //config: -d N sets loglevel (0:most verbose) and directs all output to stderr.
  30. //config:
  31. //config:config FEATURE_CROND_CALL_SENDMAIL
  32. //config: bool "Report command output via email (using sendmail)"
  33. //config: default y
  34. //config: depends on CROND
  35. //config: help
  36. //config: Command output will be sent to corresponding user via email.
  37. //config:
  38. //config:config FEATURE_CROND_DIR
  39. //config: string "crond spool directory"
  40. //config: default "/var/spool/cron"
  41. //config: depends on CROND || CRONTAB
  42. //config: help
  43. //config: Location of crond spool.
  44. //applet:IF_CROND(APPLET(crond, BB_DIR_USR_SBIN, BB_SUID_DROP))
  45. //kbuild:lib-$(CONFIG_CROND) += crond.o
  46. //usage:#define crond_trivial_usage
  47. //usage: "-fbS -l N " IF_FEATURE_CROND_D("-d N ") "-L LOGFILE -c DIR"
  48. //usage:#define crond_full_usage "\n\n"
  49. //usage: " -f Foreground"
  50. //usage: "\n -b Background (default)"
  51. //usage: "\n -S Log to syslog (default)"
  52. //usage: "\n -l N Set log level. Most verbose:0, default:8"
  53. //usage: IF_FEATURE_CROND_D(
  54. //usage: "\n -d N Set log level, log to stderr"
  55. //usage: )
  56. //usage: "\n -L FILE Log to FILE"
  57. //usage: "\n -c DIR Cron dir. Default:"CONFIG_FEATURE_CROND_DIR"/crontabs"
  58. #include "libbb.h"
  59. #include <syslog.h>
  60. /* glibc frees previous setenv'ed value when we do next setenv()
  61. * of the same variable. uclibc does not do this! */
  62. #if (defined(__GLIBC__) && !defined(__UCLIBC__)) /* || OTHER_SAFE_LIBC... */
  63. # define SETENV_LEAKS 0
  64. #else
  65. # define SETENV_LEAKS 1
  66. #endif
  67. #define CRON_DIR CONFIG_FEATURE_CROND_DIR
  68. #define CRONTABS CONFIG_FEATURE_CROND_DIR "/crontabs"
  69. #ifndef SENDMAIL
  70. # define SENDMAIL "sendmail"
  71. #endif
  72. #ifndef SENDMAIL_ARGS
  73. # define SENDMAIL_ARGS "-ti"
  74. #endif
  75. #ifndef CRONUPDATE
  76. # define CRONUPDATE "cron.update"
  77. #endif
  78. #ifndef MAXLINES
  79. # define MAXLINES 256 /* max lines in non-root crontabs */
  80. #endif
  81. typedef struct CronFile {
  82. struct CronFile *cf_next;
  83. struct CronLine *cf_lines;
  84. char *cf_username;
  85. smallint cf_wants_starting; /* bool: one or more jobs ready */
  86. smallint cf_has_running; /* bool: one or more jobs running */
  87. smallint cf_deleted; /* marked for deletion (but still has running jobs) */
  88. } CronFile;
  89. typedef struct CronLine {
  90. struct CronLine *cl_next;
  91. char *cl_cmd; /* shell command */
  92. pid_t cl_pid; /* >0:running, <0:needs to be started in this minute, 0:dormant */
  93. #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  94. int cl_empty_mail_size; /* size of mail header only, 0 if no mailfile */
  95. char *cl_mailto; /* whom to mail results, may be NULL */
  96. #endif
  97. char *cl_shell;
  98. /* ordered by size, not in natural order. makes code smaller: */
  99. char cl_Dow[7]; /* 0-6, beginning sunday */
  100. char cl_Mons[12]; /* 0-11 */
  101. char cl_Hrs[24]; /* 0-23 */
  102. char cl_Days[32]; /* 1-31 */
  103. char cl_Mins[60]; /* 0-59 */
  104. } CronLine;
  105. #define DAEMON_UID 0
  106. enum {
  107. OPT_l = (1 << 0),
  108. OPT_L = (1 << 1),
  109. OPT_f = (1 << 2),
  110. OPT_b = (1 << 3),
  111. OPT_S = (1 << 4),
  112. OPT_c = (1 << 5),
  113. OPT_d = (1 << 6) * ENABLE_FEATURE_CROND_D,
  114. };
  115. struct globals {
  116. unsigned log_level; /* = 8; */
  117. time_t crontab_dir_mtime;
  118. const char *log_filename;
  119. const char *crontab_dir_name; /* = CRONTABS; */
  120. CronFile *cron_files;
  121. #if SETENV_LEAKS
  122. char *env_var_user;
  123. char *env_var_home;
  124. char *env_var_shell;
  125. char *env_var_logname;
  126. #endif
  127. } FIX_ALIASING;
  128. #define G (*(struct globals*)&bb_common_bufsiz1)
  129. #define INIT_G() do { \
  130. G.log_level = 8; \
  131. G.crontab_dir_name = CRONTABS; \
  132. } while (0)
  133. /* Log levels:
  134. * 0 is the most verbose, default 8.
  135. * For some reason, in fact only 5, 7 and 8 are used.
  136. */
  137. static void crondlog(unsigned level, const char *msg, va_list va)
  138. {
  139. if (level >= G.log_level) {
  140. /*
  141. * We are called only for info meesages.
  142. * Warnings/errors use plain bb_[p]error_msg's, which
  143. * need not touch syslog_level
  144. * (they are ok with LOG_ERR default).
  145. */
  146. syslog_level = LOG_INFO;
  147. bb_verror_msg(msg, va, /* strerr: */ NULL);
  148. syslog_level = LOG_ERR;
  149. }
  150. }
  151. static void log5(const char *msg, ...)
  152. {
  153. va_list va;
  154. va_start(va, msg);
  155. crondlog(4, msg, va);
  156. va_end(va);
  157. }
  158. static void log7(const char *msg, ...)
  159. {
  160. va_list va;
  161. va_start(va, msg);
  162. crondlog(7, msg, va);
  163. va_end(va);
  164. }
  165. static void log8(const char *msg, ...)
  166. {
  167. va_list va;
  168. va_start(va, msg);
  169. crondlog(8, msg, va);
  170. va_end(va);
  171. }
  172. static const char DowAry[] ALIGN1 =
  173. "sun""mon""tue""wed""thu""fri""sat"
  174. ;
  175. static const char MonAry[] ALIGN1 =
  176. "jan""feb""mar""apr""may""jun""jul""aug""sep""oct""nov""dec"
  177. ;
  178. static void ParseField(char *user, char *ary, int modvalue, int off,
  179. const char *names, char *ptr)
  180. /* 'names' is a pointer to a set of 3-char abbreviations */
  181. {
  182. char *base = ptr;
  183. int n1 = -1;
  184. int n2 = -1;
  185. // this can't happen due to config_read()
  186. /*if (base == NULL)
  187. return;*/
  188. while (1) {
  189. int skip = 0;
  190. /* Handle numeric digit or symbol or '*' */
  191. if (*ptr == '*') {
  192. n1 = 0; /* everything will be filled */
  193. n2 = modvalue - 1;
  194. skip = 1;
  195. ++ptr;
  196. } else if (isdigit(*ptr)) {
  197. char *endp;
  198. if (n1 < 0) {
  199. n1 = strtol(ptr, &endp, 10) + off;
  200. } else {
  201. n2 = strtol(ptr, &endp, 10) + off;
  202. }
  203. ptr = endp; /* gcc likes temp var for &endp */
  204. skip = 1;
  205. } else if (names) {
  206. int i;
  207. for (i = 0; names[i]; i += 3) {
  208. /* was using strncmp before... */
  209. if (strncasecmp(ptr, &names[i], 3) == 0) {
  210. ptr += 3;
  211. if (n1 < 0) {
  212. n1 = i / 3;
  213. } else {
  214. n2 = i / 3;
  215. }
  216. skip = 1;
  217. break;
  218. }
  219. }
  220. }
  221. /* handle optional range '-' */
  222. if (skip == 0) {
  223. goto err;
  224. }
  225. if (*ptr == '-' && n2 < 0) {
  226. ++ptr;
  227. continue;
  228. }
  229. /*
  230. * collapse single-value ranges, handle skipmark, and fill
  231. * in the character array appropriately.
  232. */
  233. if (n2 < 0) {
  234. n2 = n1;
  235. }
  236. if (*ptr == '/') {
  237. char *endp;
  238. skip = strtol(ptr + 1, &endp, 10);
  239. ptr = endp; /* gcc likes temp var for &endp */
  240. }
  241. /*
  242. * fill array, using a failsafe is the easiest way to prevent
  243. * an endless loop
  244. */
  245. {
  246. int s0 = 1;
  247. int failsafe = 1024;
  248. --n1;
  249. do {
  250. n1 = (n1 + 1) % modvalue;
  251. if (--s0 == 0) {
  252. ary[n1 % modvalue] = 1;
  253. s0 = skip;
  254. }
  255. if (--failsafe == 0) {
  256. goto err;
  257. }
  258. } while (n1 != n2);
  259. }
  260. if (*ptr != ',') {
  261. break;
  262. }
  263. ++ptr;
  264. n1 = -1;
  265. n2 = -1;
  266. }
  267. if (*ptr) {
  268. err:
  269. bb_error_msg("user %s: parse error at %s", user, base);
  270. return;
  271. }
  272. /* can't use log5 (it inserts newlines), open-coding it */
  273. if (G.log_level <= 5 && logmode != LOGMODE_SYSLOG) {
  274. int i;
  275. for (i = 0; i < modvalue; ++i)
  276. fprintf(stderr, "%d", (unsigned char)ary[i]);
  277. bb_putchar_stderr('\n');
  278. }
  279. }
  280. static void FixDayDow(CronLine *line)
  281. {
  282. unsigned i;
  283. int weekUsed = 0;
  284. int daysUsed = 0;
  285. for (i = 0; i < ARRAY_SIZE(line->cl_Dow); ++i) {
  286. if (line->cl_Dow[i] == 0) {
  287. weekUsed = 1;
  288. break;
  289. }
  290. }
  291. for (i = 0; i < ARRAY_SIZE(line->cl_Days); ++i) {
  292. if (line->cl_Days[i] == 0) {
  293. daysUsed = 1;
  294. break;
  295. }
  296. }
  297. if (weekUsed != daysUsed) {
  298. if (weekUsed)
  299. memset(line->cl_Days, 0, sizeof(line->cl_Days));
  300. else /* daysUsed */
  301. memset(line->cl_Dow, 0, sizeof(line->cl_Dow));
  302. }
  303. }
  304. /*
  305. * delete_cronfile() - delete user database
  306. *
  307. * Note: multiple entries for same user may exist if we were unable to
  308. * completely delete a database due to running processes.
  309. */
  310. //FIXME: we will start a new job even if the old job is running
  311. //if crontab was reloaded: crond thinks that "new" job is different from "old"
  312. //even if they are in fact completely the same. Example
  313. //Crontab was:
  314. // 0-59 * * * * job1
  315. // 0-59 * * * * long_running_job2
  316. //User edits crontab to:
  317. // 0-59 * * * * job1_updated
  318. // 0-59 * * * * long_running_job2
  319. //Bug: crond can now start another long_running_job2 even if old one
  320. //is still running.
  321. //OTOH most other versions of cron do not wait for job termination anyway,
  322. //they end up with multiple copies of jobs if they don't terminate soon enough.
  323. static void delete_cronfile(const char *userName)
  324. {
  325. CronFile **pfile = &G.cron_files;
  326. CronFile *file;
  327. while ((file = *pfile) != NULL) {
  328. if (strcmp(userName, file->cf_username) == 0) {
  329. CronLine **pline = &file->cf_lines;
  330. CronLine *line;
  331. file->cf_has_running = 0;
  332. file->cf_deleted = 1;
  333. while ((line = *pline) != NULL) {
  334. if (line->cl_pid > 0) {
  335. file->cf_has_running = 1;
  336. pline = &line->cl_next;
  337. } else {
  338. *pline = line->cl_next;
  339. free(line->cl_cmd);
  340. free(line);
  341. }
  342. }
  343. if (file->cf_has_running == 0) {
  344. *pfile = file->cf_next;
  345. free(file->cf_username);
  346. free(file);
  347. continue;
  348. }
  349. }
  350. pfile = &file->cf_next;
  351. }
  352. }
  353. static void load_crontab(const char *fileName)
  354. {
  355. struct parser_t *parser;
  356. struct stat sbuf;
  357. int maxLines;
  358. char *tokens[6];
  359. #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  360. char *mailTo = NULL;
  361. #endif
  362. char *shell = NULL;
  363. delete_cronfile(fileName);
  364. if (!getpwnam(fileName)) {
  365. log7("ignoring file '%s' (no such user)", fileName);
  366. return;
  367. }
  368. parser = config_open(fileName);
  369. if (!parser)
  370. return;
  371. maxLines = (strcmp(fileName, "root") == 0) ? 65535 : MAXLINES;
  372. if (fstat(fileno(parser->fp), &sbuf) == 0 && sbuf.st_uid == DAEMON_UID) {
  373. CronFile *file = xzalloc(sizeof(CronFile));
  374. CronLine **pline;
  375. int n;
  376. file->cf_username = xstrdup(fileName);
  377. pline = &file->cf_lines;
  378. while (1) {
  379. CronLine *line;
  380. if (!--maxLines) {
  381. bb_error_msg("user %s: too many lines", fileName);
  382. break;
  383. }
  384. n = config_read(parser, tokens, 6, 1, "# \t", PARSE_NORMAL | PARSE_KEEP_COPY);
  385. if (!n)
  386. break;
  387. log5("user:%s entry:%s", fileName, parser->data);
  388. /* check if line is setting MAILTO= */
  389. if (is_prefixed_with(tokens[0], "MAILTO=")) {
  390. #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  391. free(mailTo);
  392. mailTo = (tokens[0][7]) ? xstrdup(&tokens[0][7]) : NULL;
  393. #endif /* otherwise just ignore such lines */
  394. continue;
  395. }
  396. if (is_prefixed_with(tokens[0], "SHELL=")) {
  397. free(shell);
  398. shell = xstrdup(&tokens[0][6]);
  399. continue;
  400. }
  401. //TODO: handle HOME= too? "man crontab" says:
  402. //name = value
  403. //
  404. //where the spaces around the equal-sign (=) are optional, and any subsequent
  405. //non-leading spaces in value will be part of the value assigned to name.
  406. //The value string may be placed in quotes (single or double, but matching)
  407. //to preserve leading or trailing blanks.
  408. //
  409. //Several environment variables are set up automatically by the cron(8) daemon.
  410. //SHELL is set to /bin/sh, and LOGNAME and HOME are set from the /etc/passwd
  411. //line of the crontab's owner. HOME and SHELL may be overridden by settings
  412. //in the crontab; LOGNAME may not.
  413. /* check if a minimum of tokens is specified */
  414. if (n < 6)
  415. continue;
  416. *pline = line = xzalloc(sizeof(*line));
  417. /* parse date ranges */
  418. ParseField(file->cf_username, line->cl_Mins, 60, 0, NULL, tokens[0]);
  419. ParseField(file->cf_username, line->cl_Hrs, 24, 0, NULL, tokens[1]);
  420. ParseField(file->cf_username, line->cl_Days, 32, 0, NULL, tokens[2]);
  421. ParseField(file->cf_username, line->cl_Mons, 12, -1, MonAry, tokens[3]);
  422. ParseField(file->cf_username, line->cl_Dow, 7, 0, DowAry, tokens[4]);
  423. /*
  424. * fix days and dow - if one is not "*" and the other
  425. * is "*", the other is set to 0, and vise-versa
  426. */
  427. FixDayDow(line);
  428. #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  429. /* copy mailto (can be NULL) */
  430. line->cl_mailto = xstrdup(mailTo);
  431. #endif
  432. line->cl_shell = xstrdup(shell);
  433. /* copy command */
  434. line->cl_cmd = xstrdup(tokens[5]);
  435. pline = &line->cl_next;
  436. //bb_error_msg("M[%s]F[%s][%s][%s][%s][%s][%s]", mailTo, tokens[0], tokens[1], tokens[2], tokens[3], tokens[4], tokens[5]);
  437. }
  438. *pline = NULL;
  439. file->cf_next = G.cron_files;
  440. G.cron_files = file;
  441. }
  442. config_close(parser);
  443. #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  444. free(mailTo);
  445. #endif
  446. free(shell);
  447. }
  448. static void process_cron_update_file(void)
  449. {
  450. FILE *fi;
  451. char buf[256];
  452. fi = fopen_for_read(CRONUPDATE);
  453. if (fi != NULL) {
  454. unlink(CRONUPDATE);
  455. while (fgets(buf, sizeof(buf), fi) != NULL) {
  456. /* use first word only */
  457. skip_non_whitespace(buf)[0] = '\0';
  458. load_crontab(buf);
  459. }
  460. fclose(fi);
  461. }
  462. }
  463. static void rescan_crontab_dir(void)
  464. {
  465. CronFile *file;
  466. /* Delete all files until we only have ones with running jobs (or none) */
  467. again:
  468. for (file = G.cron_files; file; file = file->cf_next) {
  469. if (!file->cf_deleted) {
  470. delete_cronfile(file->cf_username);
  471. goto again;
  472. }
  473. }
  474. /* Remove cron update file */
  475. unlink(CRONUPDATE);
  476. /* Re-chdir, in case directory was renamed & deleted */
  477. xchdir(G.crontab_dir_name);
  478. /* Scan directory and add associated users */
  479. {
  480. DIR *dir = opendir(".");
  481. struct dirent *den;
  482. /* xopendir exists, but "can't open '.'" is not informative */
  483. if (!dir)
  484. bb_error_msg_and_die("can't open '%s'", G.crontab_dir_name);
  485. while ((den = readdir(dir)) != NULL) {
  486. if (strchr(den->d_name, '.') != NULL) {
  487. continue;
  488. }
  489. load_crontab(den->d_name);
  490. }
  491. closedir(dir);
  492. }
  493. }
  494. #if SETENV_LEAKS
  495. /* We set environment *before* vfork (because we want to use vfork),
  496. * so we cannot use setenv() - repeated calls to setenv() may leak memory!
  497. * Using putenv(), and freeing memory after unsetenv() won't leak */
  498. static void safe_setenv(char **pvar_val, const char *var, const char *val)
  499. {
  500. char *var_val = *pvar_val;
  501. if (var_val) {
  502. bb_unsetenv_and_free(var_val);
  503. }
  504. *pvar_val = xasprintf("%s=%s", var, val);
  505. putenv(*pvar_val);
  506. }
  507. #endif
  508. static void set_env_vars(struct passwd *pas, const char *shell)
  509. {
  510. /* POSIX requires crond to set up at least HOME, LOGNAME, PATH, SHELL.
  511. * We assume crond inherited suitable PATH.
  512. */
  513. #if SETENV_LEAKS
  514. safe_setenv(&G.env_var_logname, "LOGNAME", pas->pw_name);
  515. safe_setenv(&G.env_var_user, "USER", pas->pw_name);
  516. safe_setenv(&G.env_var_home, "HOME", pas->pw_dir);
  517. safe_setenv(&G.env_var_shell, "SHELL", shell);
  518. #else
  519. xsetenv("LOGNAME", pas->pw_name);
  520. xsetenv("USER", pas->pw_name);
  521. xsetenv("HOME", pas->pw_dir);
  522. xsetenv("SHELL", shell);
  523. #endif
  524. }
  525. static void change_user(struct passwd *pas)
  526. {
  527. /* careful: we're after vfork! */
  528. change_identity(pas); /* - initgroups, setgid, setuid */
  529. if (chdir(pas->pw_dir) < 0) {
  530. bb_error_msg("can't change directory to '%s'", pas->pw_dir);
  531. xchdir(CRON_DIR);
  532. }
  533. }
  534. // TODO: sendmail should be _run-time_ option, not compile-time!
  535. #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  536. static pid_t
  537. fork_job(const char *user, int mailFd, CronLine *line, bool run_sendmail)
  538. {
  539. struct passwd *pas;
  540. const char *shell, *prog;
  541. smallint sv_logmode;
  542. pid_t pid;
  543. /* prepare things before vfork */
  544. pas = getpwnam(user);
  545. if (!pas) {
  546. bb_error_msg("can't get uid for %s", user);
  547. goto err;
  548. }
  549. shell = line->cl_shell ? line->cl_shell : DEFAULT_SHELL;
  550. prog = run_sendmail ? SENDMAIL : shell;
  551. set_env_vars(pas, shell);
  552. sv_logmode = logmode;
  553. pid = vfork();
  554. if (pid == 0) {
  555. /* CHILD */
  556. /* initgroups, setgid, setuid, and chdir to home or CRON_DIR */
  557. change_user(pas);
  558. log5("child running %s", prog);
  559. if (mailFd >= 0) {
  560. xmove_fd(mailFd, run_sendmail ? 0 : 1);
  561. dup2(1, 2);
  562. }
  563. /* crond 3.0pl1-100 puts tasks in separate process groups */
  564. bb_setpgrp();
  565. if (!run_sendmail)
  566. execlp(prog, prog, "-c", line->cl_cmd, (char *) NULL);
  567. else
  568. execlp(prog, prog, SENDMAIL_ARGS, (char *) NULL);
  569. /*
  570. * I want this error message on stderr too,
  571. * even if other messages go only to syslog:
  572. */
  573. logmode |= LOGMODE_STDIO;
  574. bb_error_msg_and_die("can't execute '%s' for user %s", prog, user);
  575. }
  576. logmode = sv_logmode;
  577. if (pid < 0) {
  578. bb_perror_msg("vfork");
  579. err:
  580. pid = 0;
  581. } /* else: PARENT, FORK SUCCESS */
  582. /*
  583. * Close the mail file descriptor.. we can't just leave it open in
  584. * a structure, closing it later, because we might run out of descriptors
  585. */
  586. if (mailFd >= 0) {
  587. close(mailFd);
  588. }
  589. return pid;
  590. }
  591. static void start_one_job(const char *user, CronLine *line)
  592. {
  593. char mailFile[128];
  594. int mailFd = -1;
  595. line->cl_pid = 0;
  596. line->cl_empty_mail_size = 0;
  597. if (line->cl_mailto) {
  598. /* Open mail file (owner is root so nobody can screw with it) */
  599. snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", CRON_DIR, user, getpid());
  600. mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600);
  601. if (mailFd >= 0) {
  602. fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", line->cl_mailto,
  603. line->cl_cmd);
  604. line->cl_empty_mail_size = lseek(mailFd, 0, SEEK_CUR);
  605. } else {
  606. bb_error_msg("can't create mail file %s for user %s, "
  607. "discarding output", mailFile, user);
  608. }
  609. }
  610. line->cl_pid = fork_job(user, mailFd, line, /*sendmail?*/ 0);
  611. if (mailFd >= 0) {
  612. if (line->cl_pid <= 0) {
  613. unlink(mailFile);
  614. } else {
  615. /* rename mail-file based on pid of process */
  616. char *mailFile2 = xasprintf("%s/cron.%s.%d", CRON_DIR, user, (int)line->cl_pid);
  617. rename(mailFile, mailFile2); // TODO: xrename?
  618. free(mailFile2);
  619. }
  620. }
  621. }
  622. /*
  623. * process_finished_job - called when job terminates and when mail terminates
  624. */
  625. static void process_finished_job(const char *user, CronLine *line)
  626. {
  627. pid_t pid;
  628. int mailFd;
  629. char mailFile[128];
  630. struct stat sbuf;
  631. pid = line->cl_pid;
  632. line->cl_pid = 0;
  633. if (pid <= 0) {
  634. /* No job */
  635. return;
  636. }
  637. if (line->cl_empty_mail_size <= 0) {
  638. /* End of job and no mail file, or end of sendmail job */
  639. return;
  640. }
  641. /*
  642. * End of primary job - check for mail file.
  643. * If size has changed and the file is still valid, we send it.
  644. */
  645. snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", CRON_DIR, user, (int)pid);
  646. mailFd = open(mailFile, O_RDONLY);
  647. unlink(mailFile);
  648. if (mailFd < 0) {
  649. return;
  650. }
  651. if (fstat(mailFd, &sbuf) < 0
  652. || sbuf.st_uid != DAEMON_UID
  653. || sbuf.st_nlink != 0
  654. || sbuf.st_size == line->cl_empty_mail_size
  655. || !S_ISREG(sbuf.st_mode)
  656. ) {
  657. close(mailFd);
  658. return;
  659. }
  660. line->cl_empty_mail_size = 0;
  661. /* if (line->cl_mailto) - always true if cl_empty_mail_size was nonzero */
  662. line->cl_pid = fork_job(user, mailFd, line, /*sendmail?*/ 1);
  663. }
  664. #else /* !ENABLE_FEATURE_CROND_CALL_SENDMAIL */
  665. static void start_one_job(const char *user, CronLine *line)
  666. {
  667. const char *shell;
  668. struct passwd *pas;
  669. pid_t pid;
  670. pas = getpwnam(user);
  671. if (!pas) {
  672. bb_error_msg("can't get uid for %s", user);
  673. goto err;
  674. }
  675. /* Prepare things before vfork */
  676. shell = line->cl_shell ? line->cl_shell : DEFAULT_SHELL;
  677. set_env_vars(pas, shell);
  678. /* Fork as the user in question and run program */
  679. pid = vfork();
  680. if (pid == 0) {
  681. /* CHILD */
  682. /* initgroups, setgid, setuid, and chdir to home or CRON_DIR */
  683. change_user(pas);
  684. log5("child running %s", shell);
  685. /* crond 3.0pl1-100 puts tasks in separate process groups */
  686. bb_setpgrp();
  687. execl(shell, shell, "-c", line->cl_cmd, (char *) NULL);
  688. bb_error_msg_and_die("can't execute '%s' for user %s", shell, user);
  689. }
  690. if (pid < 0) {
  691. bb_perror_msg("vfork");
  692. err:
  693. pid = 0;
  694. }
  695. line->cl_pid = pid;
  696. }
  697. #define process_finished_job(user, line) ((line)->cl_pid = 0)
  698. #endif /* !ENABLE_FEATURE_CROND_CALL_SENDMAIL */
  699. /*
  700. * Determine which jobs need to be run. Under normal conditions, the
  701. * period is about a minute (one scan). Worst case it will be one
  702. * hour (60 scans).
  703. */
  704. static void flag_starting_jobs(time_t t1, time_t t2)
  705. {
  706. time_t t;
  707. /* Find jobs > t1 and <= t2 */
  708. for (t = t1 - t1 % 60; t <= t2; t += 60) {
  709. struct tm *ptm;
  710. CronFile *file;
  711. CronLine *line;
  712. if (t <= t1)
  713. continue;
  714. ptm = localtime(&t);
  715. for (file = G.cron_files; file; file = file->cf_next) {
  716. log5("file %s:", file->cf_username);
  717. if (file->cf_deleted)
  718. continue;
  719. for (line = file->cf_lines; line; line = line->cl_next) {
  720. log5(" line %s", line->cl_cmd);
  721. if (line->cl_Mins[ptm->tm_min]
  722. && line->cl_Hrs[ptm->tm_hour]
  723. && (line->cl_Days[ptm->tm_mday] || line->cl_Dow[ptm->tm_wday])
  724. && line->cl_Mons[ptm->tm_mon]
  725. ) {
  726. log5(" job: %d %s",
  727. (int)line->cl_pid, line->cl_cmd);
  728. if (line->cl_pid > 0) {
  729. log8("user %s: process already running: %s",
  730. file->cf_username, line->cl_cmd);
  731. } else if (line->cl_pid == 0) {
  732. line->cl_pid = -1;
  733. file->cf_wants_starting = 1;
  734. }
  735. }
  736. }
  737. }
  738. }
  739. }
  740. static void start_jobs(void)
  741. {
  742. CronFile *file;
  743. CronLine *line;
  744. for (file = G.cron_files; file; file = file->cf_next) {
  745. if (!file->cf_wants_starting)
  746. continue;
  747. file->cf_wants_starting = 0;
  748. for (line = file->cf_lines; line; line = line->cl_next) {
  749. pid_t pid;
  750. if (line->cl_pid >= 0)
  751. continue;
  752. start_one_job(file->cf_username, line);
  753. pid = line->cl_pid;
  754. log8("USER %s pid %3d cmd %s",
  755. file->cf_username, (int)pid, line->cl_cmd);
  756. if (pid < 0) {
  757. file->cf_wants_starting = 1;
  758. }
  759. if (pid > 0) {
  760. file->cf_has_running = 1;
  761. }
  762. }
  763. }
  764. }
  765. /*
  766. * Check for job completion, return number of jobs still running after
  767. * all done.
  768. */
  769. static int check_completions(void)
  770. {
  771. CronFile *file;
  772. CronLine *line;
  773. int num_still_running = 0;
  774. for (file = G.cron_files; file; file = file->cf_next) {
  775. if (!file->cf_has_running)
  776. continue;
  777. file->cf_has_running = 0;
  778. for (line = file->cf_lines; line; line = line->cl_next) {
  779. int r;
  780. if (line->cl_pid <= 0)
  781. continue;
  782. r = waitpid(line->cl_pid, NULL, WNOHANG);
  783. if (r < 0 || r == line->cl_pid) {
  784. process_finished_job(file->cf_username, line);
  785. if (line->cl_pid == 0) {
  786. /* sendmail was not started for it */
  787. continue;
  788. }
  789. /* else: sendmail was started, job is still running, fall thru */
  790. }
  791. /* else: r == 0: "process is still running" */
  792. file->cf_has_running = 1;
  793. }
  794. //FIXME: if !file->cf_has_running && file->deleted: delete it!
  795. //otherwise deleted entries will stay forever, right?
  796. num_still_running += file->cf_has_running;
  797. }
  798. return num_still_running;
  799. }
  800. static void reopen_logfile_to_stderr(void)
  801. {
  802. if (G.log_filename) {
  803. int logfd = open_or_warn(G.log_filename, O_WRONLY | O_CREAT | O_APPEND);
  804. if (logfd >= 0)
  805. xmove_fd(logfd, STDERR_FILENO);
  806. }
  807. }
  808. int crond_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  809. int crond_main(int argc UNUSED_PARAM, char **argv)
  810. {
  811. time_t t2;
  812. unsigned rescan;
  813. unsigned sleep_time;
  814. unsigned opts;
  815. INIT_G();
  816. /* "-b after -f is ignored", and so on for every pair a-b */
  817. opt_complementary = "f-b:b-f:S-L:L-S" IF_FEATURE_CROND_D(":d-l")
  818. /* -l and -d have numeric param */
  819. ":l+" IF_FEATURE_CROND_D(":d+");
  820. opts = getopt32(argv, "l:L:fbSc:" IF_FEATURE_CROND_D("d:"),
  821. &G.log_level, &G.log_filename, &G.crontab_dir_name
  822. IF_FEATURE_CROND_D(,&G.log_level));
  823. /* both -d N and -l N set the same variable: G.log_level */
  824. if (!(opts & OPT_f)) {
  825. /* close stdin, stdout, stderr.
  826. * close unused descriptors - don't need them. */
  827. bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, argv);
  828. }
  829. if (!(opts & OPT_d) && G.log_filename == NULL) {
  830. /* logging to syslog */
  831. openlog(applet_name, LOG_CONS | LOG_PID, LOG_CRON);
  832. logmode = LOGMODE_SYSLOG;
  833. }
  834. //signal(SIGHUP, SIG_IGN); /* ? original crond dies on HUP... */
  835. reopen_logfile_to_stderr();
  836. xchdir(G.crontab_dir_name);
  837. log8("crond (busybox "BB_VER") started, log level %d", G.log_level);
  838. rescan_crontab_dir();
  839. write_pidfile(CONFIG_PID_FILE_PATH "/crond.pid");
  840. /* Main loop */
  841. t2 = time(NULL);
  842. rescan = 60;
  843. sleep_time = 60;
  844. for (;;) {
  845. struct stat sbuf;
  846. time_t t1;
  847. long dt;
  848. /* Synchronize to 1 minute, minimum 1 second */
  849. t1 = t2;
  850. sleep(sleep_time - (time(NULL) % sleep_time));
  851. t2 = time(NULL);
  852. dt = (long)t2 - (long)t1;
  853. reopen_logfile_to_stderr();
  854. /*
  855. * The file 'cron.update' is checked to determine new cron
  856. * jobs. The directory is rescanned once an hour to deal
  857. * with any screwups.
  858. *
  859. * Check for time jump. Disparities over an hour either way
  860. * result in resynchronization. A negative disparity
  861. * less than an hour causes us to effectively sleep until we
  862. * match the original time (i.e. no re-execution of jobs that
  863. * have just been run). A positive disparity less than
  864. * an hour causes intermediate jobs to be run, but only once
  865. * in the worst case.
  866. *
  867. * When running jobs, the inequality used is greater but not
  868. * equal to t1, and less then or equal to t2.
  869. */
  870. if (stat(G.crontab_dir_name, &sbuf) != 0)
  871. sbuf.st_mtime = 0; /* force update (once) if dir was deleted */
  872. if (G.crontab_dir_mtime != sbuf.st_mtime) {
  873. G.crontab_dir_mtime = sbuf.st_mtime;
  874. rescan = 1;
  875. }
  876. if (--rescan == 0) {
  877. rescan = 60;
  878. rescan_crontab_dir();
  879. }
  880. process_cron_update_file();
  881. log5("wakeup dt=%ld", dt);
  882. if (dt < -60 * 60 || dt > 60 * 60) {
  883. bb_error_msg("time disparity of %ld minutes detected", dt / 60);
  884. /* and we do not run any jobs in this case */
  885. } else if (dt > 0) {
  886. /* Usual case: time advances forward, as expected */
  887. flag_starting_jobs(t1, t2);
  888. start_jobs();
  889. sleep_time = 60;
  890. if (check_completions() > 0) {
  891. /* some jobs are still running */
  892. sleep_time = 10;
  893. }
  894. }
  895. /* else: time jumped back, do not run any jobs */
  896. } /* for (;;) */
  897. return 0; /* not reached */
  898. }