crond.c 29 KB

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