crond.c 29 KB

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