crond.c 25 KB

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