crond.c 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931
  1. /* vi: set sw=4 ts=4: */
  2. /*
  3. * crond -d[#] -c <crondir> -f -b
  4. *
  5. * run as root, but NOT setuid root
  6. *
  7. * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com)
  8. * (version 2.3.2)
  9. * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002
  10. *
  11. * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
  12. */
  13. #include "libbb.h"
  14. #include <syslog.h>
  15. /* glibc frees previous setenv'ed value when we do next setenv()
  16. * of the same variable. uclibc does not do this! */
  17. #if (defined(__GLIBC__) && !defined(__UCLIBC__)) /* || OTHER_SAFE_LIBC... */
  18. #define SETENV_LEAKS 0
  19. #else
  20. #define SETENV_LEAKS 1
  21. #endif
  22. #ifndef CRONTABS
  23. #define CRONTABS "/var/spool/cron/crontabs"
  24. #endif
  25. #ifndef TMPDIR
  26. #define TMPDIR "/var/spool/cron"
  27. #endif
  28. #ifndef SENDMAIL
  29. #define SENDMAIL "sendmail"
  30. #endif
  31. #ifndef SENDMAIL_ARGS
  32. #define SENDMAIL_ARGS "-ti", "oem"
  33. #endif
  34. #ifndef CRONUPDATE
  35. #define CRONUPDATE "cron.update"
  36. #endif
  37. #ifndef MAXLINES
  38. #define MAXLINES 256 /* max lines in non-root crontabs */
  39. #endif
  40. typedef struct CronFile {
  41. struct CronFile *cf_Next;
  42. struct CronLine *cf_LineBase;
  43. char *cf_User; /* username */
  44. smallint cf_Ready; /* bool: one or more jobs ready */
  45. smallint cf_Running; /* bool: one or more jobs running */
  46. smallint cf_Deleted; /* marked for deletion, ignore */
  47. } CronFile;
  48. typedef struct CronLine {
  49. struct CronLine *cl_Next;
  50. char *cl_Shell; /* shell command */
  51. pid_t cl_Pid; /* running pid, 0, or armed (-1) */
  52. #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  53. int cl_MailPos; /* 'empty file' size */
  54. smallint cl_MailFlag; /* running pid is for mail */
  55. char *cl_MailTo; /* whom to mail results */
  56. #endif
  57. /* ordered by size, not in natural order. makes code smaller: */
  58. char cl_Dow[7]; /* 0-6, beginning sunday */
  59. char cl_Mons[12]; /* 0-11 */
  60. char cl_Hrs[24]; /* 0-23 */
  61. char cl_Days[32]; /* 1-31 */
  62. char cl_Mins[60]; /* 0-59 */
  63. } CronLine;
  64. #define DaemonUid 0
  65. enum {
  66. OPT_l = (1 << 0),
  67. OPT_L = (1 << 1),
  68. OPT_f = (1 << 2),
  69. OPT_b = (1 << 3),
  70. OPT_S = (1 << 4),
  71. OPT_c = (1 << 5),
  72. OPT_d = (1 << 6) * ENABLE_DEBUG_CROND_OPTION,
  73. };
  74. #if ENABLE_DEBUG_CROND_OPTION
  75. #define DebugOpt (option_mask32 & OPT_d)
  76. #else
  77. #define DebugOpt 0
  78. #endif
  79. struct globals {
  80. unsigned LogLevel; /* = 8; */
  81. const char *LogFile;
  82. const char *CDir; /* = CRONTABS; */
  83. CronFile *FileBase;
  84. #if SETENV_LEAKS
  85. char *env_var_user;
  86. char *env_var_home;
  87. #endif
  88. };
  89. #define G (*(struct globals*)&bb_common_bufsiz1)
  90. #define LogLevel (G.LogLevel )
  91. #define LogFile (G.LogFile )
  92. #define CDir (G.CDir )
  93. #define FileBase (G.FileBase )
  94. #define env_var_user (G.env_var_user )
  95. #define env_var_home (G.env_var_home )
  96. #define INIT_G() do { \
  97. LogLevel = 8; \
  98. CDir = CRONTABS; \
  99. } while (0)
  100. static void CheckUpdates(void);
  101. static void SynchronizeDir(void);
  102. static int TestJobs(time_t t1, time_t t2);
  103. static void RunJobs(void);
  104. static int CheckJobs(void);
  105. static void RunJob(const char *user, CronLine *line);
  106. #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  107. static void EndJob(const char *user, CronLine *line);
  108. #else
  109. #define EndJob(user, line) ((line)->cl_Pid = 0)
  110. #endif
  111. static void DeleteFile(const char *userName);
  112. #define LVL5 "\x05"
  113. #define LVL7 "\x07"
  114. #define LVL8 "\x08"
  115. #define LVL9 "\x09"
  116. #define WARN9 "\x49"
  117. #define DIE9 "\xc9"
  118. /* level >= 20 is "error" */
  119. #define ERR20 "\x14"
  120. static void crondlog(const char *ctl, ...)
  121. {
  122. va_list va;
  123. int level = (ctl[0] & 0x1f);
  124. va_start(va, ctl);
  125. if (level >= (int)LogLevel) {
  126. /* Debug mode: all to (non-redirected) stderr, */
  127. /* Syslog mode: all to syslog (logmode = LOGMODE_SYSLOG), */
  128. if (!DebugOpt && LogFile) {
  129. /* Otherwise (log to file): we reopen log file at every write: */
  130. int logfd = open3_or_warn(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600);
  131. if (logfd >= 0)
  132. xmove_fd(logfd, STDERR_FILENO);
  133. }
  134. // TODO: ERR -> error, WARN -> warning, LVL -> info
  135. bb_verror_msg(ctl + 1, va, /* strerr: */ NULL);
  136. }
  137. va_end(va);
  138. if (ctl[0] & 0x80)
  139. exit(20);
  140. }
  141. int crond_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  142. int crond_main(int argc UNUSED_PARAM, char **argv)
  143. {
  144. unsigned opt;
  145. INIT_G();
  146. /* "-b after -f is ignored", and so on for every pair a-b */
  147. opt_complementary = "f-b:b-f:S-L:L-S" USE_DEBUG_CROND_OPTION(":d-l")
  148. ":l+:d+"; /* -l and -d have numeric param */
  149. opt = getopt32(argv, "l:L:fbSc:" USE_DEBUG_CROND_OPTION("d:"),
  150. &LogLevel, &LogFile, &CDir
  151. USE_DEBUG_CROND_OPTION(,&LogLevel));
  152. /* both -d N and -l N set the same variable: LogLevel */
  153. if (!(opt & OPT_f)) {
  154. /* close stdin, stdout, stderr.
  155. * close unused descriptors - don't need them. */
  156. bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, argv);
  157. }
  158. if (!DebugOpt && LogFile == NULL) {
  159. /* logging to syslog */
  160. openlog(applet_name, LOG_CONS | LOG_PID, LOG_CRON);
  161. logmode = LOGMODE_SYSLOG;
  162. }
  163. xchdir(CDir);
  164. //signal(SIGHUP, SIG_IGN); /* ? original crond dies on HUP... */
  165. setenv("SHELL", DEFAULT_SHELL, 1); /* once, for all future children */
  166. crondlog(LVL9 "crond (busybox "BB_VER") started, log level %d", LogLevel);
  167. SynchronizeDir();
  168. /* main loop - synchronize to 1 second after the minute, minimum sleep
  169. * of 1 second. */
  170. {
  171. time_t t1 = time(NULL);
  172. time_t t2;
  173. long dt;
  174. int rescan = 60;
  175. int sleep_time = 60;
  176. write_pidfile("/var/run/crond.pid");
  177. for (;;) {
  178. sleep((sleep_time + 1) - (time(NULL) % sleep_time));
  179. t2 = time(NULL);
  180. dt = (long)t2 - (long)t1;
  181. /*
  182. * The file 'cron.update' is checked to determine new cron
  183. * jobs. The directory is rescanned once an hour to deal
  184. * with any screwups.
  185. *
  186. * check for disparity. Disparities over an hour either way
  187. * result in resynchronization. A reverse-indexed disparity
  188. * less then an hour causes us to effectively sleep until we
  189. * match the original time (i.e. no re-execution of jobs that
  190. * have just been run). A forward-indexed disparity less then
  191. * an hour causes intermediate jobs to be run, but only once
  192. * in the worst case.
  193. *
  194. * when running jobs, the inequality used is greater but not
  195. * equal to t1, and less then or equal to t2.
  196. */
  197. if (--rescan == 0) {
  198. rescan = 60;
  199. SynchronizeDir();
  200. }
  201. CheckUpdates();
  202. if (DebugOpt)
  203. crondlog(LVL5 "wakeup dt=%ld", dt);
  204. if (dt < -60 * 60 || dt > 60 * 60) {
  205. crondlog(WARN9 "time disparity of %d minutes detected", dt / 60);
  206. } else if (dt > 0) {
  207. TestJobs(t1, t2);
  208. RunJobs();
  209. sleep(5);
  210. if (CheckJobs() > 0) {
  211. sleep_time = 10;
  212. } else {
  213. sleep_time = 60;
  214. }
  215. }
  216. t1 = t2;
  217. }
  218. }
  219. return 0; /* not reached */
  220. }
  221. #if SETENV_LEAKS
  222. /* We set environment *before* vfork (because we want to use vfork),
  223. * so we cannot use setenv() - repeated calls to setenv() may leak memory!
  224. * Using putenv(), and freeing memory after unsetenv() won't leak */
  225. static void safe_setenv4(char **pvar_val, const char *var, const char *val /*, int len*/)
  226. {
  227. const int len = 4; /* both var names are 4 char long */
  228. char *var_val = *pvar_val;
  229. if (var_val) {
  230. var_val[len] = '\0'; /* nuke '=' */
  231. unsetenv(var_val);
  232. free(var_val);
  233. }
  234. *pvar_val = xasprintf("%s=%s", var, val);
  235. putenv(*pvar_val);
  236. }
  237. #endif
  238. static void SetEnv(struct passwd *pas)
  239. {
  240. #if SETENV_LEAKS
  241. safe_setenv4(&env_var_user, "USER", pas->pw_name);
  242. safe_setenv4(&env_var_home, "HOME", pas->pw_dir);
  243. /* if we want to set user's shell instead: */
  244. /*safe_setenv(env_var_user, "SHELL", pas->pw_shell, 5);*/
  245. #else
  246. setenv("USER", pas->pw_name, 1);
  247. setenv("HOME", pas->pw_dir, 1);
  248. #endif
  249. /* currently, we use constant one: */
  250. /*setenv("SHELL", DEFAULT_SHELL, 1); - done earlier */
  251. }
  252. static void ChangeUser(struct passwd *pas)
  253. {
  254. /* careful: we're after vfork! */
  255. change_identity(pas); /* - initgroups, setgid, setuid */
  256. if (chdir(pas->pw_dir) < 0) {
  257. crondlog(LVL9 "can't chdir(%s)", pas->pw_dir);
  258. if (chdir(TMPDIR) < 0) {
  259. crondlog(DIE9 "can't chdir(%s)", TMPDIR); /* exits */
  260. }
  261. }
  262. }
  263. static const char DowAry[] ALIGN1 =
  264. "sun""mon""tue""wed""thu""fri""sat"
  265. /* "Sun""Mon""Tue""Wed""Thu""Fri""Sat" */
  266. ;
  267. static const char MonAry[] ALIGN1 =
  268. "jan""feb""mar""apr""may""jun""jul""aug""sep""oct""nov""dec"
  269. /* "Jan""Feb""Mar""Apr""May""Jun""Jul""Aug""Sep""Oct""Nov""Dec" */
  270. ;
  271. static void ParseField(char *user, char *ary, int modvalue, int off,
  272. const char *names, char *ptr)
  273. /* 'names' is a pointer to a set of 3-char abbreviations */
  274. {
  275. char *base = ptr;
  276. int n1 = -1;
  277. int n2 = -1;
  278. // this can't happen due to config_read()
  279. /*if (base == NULL)
  280. return;*/
  281. while (1) {
  282. int skip = 0;
  283. /* Handle numeric digit or symbol or '*' */
  284. if (*ptr == '*') {
  285. n1 = 0; /* everything will be filled */
  286. n2 = modvalue - 1;
  287. skip = 1;
  288. ++ptr;
  289. } else if (isdigit(*ptr)) {
  290. if (n1 < 0) {
  291. n1 = strtol(ptr, &ptr, 10) + off;
  292. } else {
  293. n2 = strtol(ptr, &ptr, 10) + off;
  294. }
  295. skip = 1;
  296. } else if (names) {
  297. int i;
  298. for (i = 0; names[i]; i += 3) {
  299. /* was using strncmp before... */
  300. if (strncasecmp(ptr, &names[i], 3) == 0) {
  301. ptr += 3;
  302. if (n1 < 0) {
  303. n1 = i / 3;
  304. } else {
  305. n2 = i / 3;
  306. }
  307. skip = 1;
  308. break;
  309. }
  310. }
  311. }
  312. /* handle optional range '-' */
  313. if (skip == 0) {
  314. goto err;
  315. }
  316. if (*ptr == '-' && n2 < 0) {
  317. ++ptr;
  318. continue;
  319. }
  320. /*
  321. * collapse single-value ranges, handle skipmark, and fill
  322. * in the character array appropriately.
  323. */
  324. if (n2 < 0) {
  325. n2 = n1;
  326. }
  327. if (*ptr == '/') {
  328. skip = strtol(ptr + 1, &ptr, 10);
  329. }
  330. /*
  331. * fill array, using a failsafe is the easiest way to prevent
  332. * an endless loop
  333. */
  334. {
  335. int s0 = 1;
  336. int failsafe = 1024;
  337. --n1;
  338. do {
  339. n1 = (n1 + 1) % modvalue;
  340. if (--s0 == 0) {
  341. ary[n1 % modvalue] = 1;
  342. s0 = skip;
  343. }
  344. if (--failsafe == 0) {
  345. goto err;
  346. }
  347. } while (n1 != n2);
  348. }
  349. if (*ptr != ',') {
  350. break;
  351. }
  352. ++ptr;
  353. n1 = -1;
  354. n2 = -1;
  355. }
  356. if (*ptr) {
  357. err:
  358. crondlog(WARN9 "user %s: parse error at %s", user, base);
  359. return;
  360. }
  361. if (DebugOpt && (LogLevel <= 5)) { /* like LVL5 */
  362. /* can't use crondlog, it inserts '\n' */
  363. int i;
  364. for (i = 0; i < modvalue; ++i)
  365. fprintf(stderr, "%d", (unsigned char)ary[i]);
  366. fputc('\n', stderr);
  367. }
  368. }
  369. static void FixDayDow(CronLine *line)
  370. {
  371. unsigned i;
  372. int weekUsed = 0;
  373. int daysUsed = 0;
  374. for (i = 0; i < ARRAY_SIZE(line->cl_Dow); ++i) {
  375. if (line->cl_Dow[i] == 0) {
  376. weekUsed = 1;
  377. break;
  378. }
  379. }
  380. for (i = 0; i < ARRAY_SIZE(line->cl_Days); ++i) {
  381. if (line->cl_Days[i] == 0) {
  382. daysUsed = 1;
  383. break;
  384. }
  385. }
  386. if (weekUsed != daysUsed) {
  387. if (weekUsed)
  388. memset(line->cl_Days, 0, sizeof(line->cl_Days));
  389. else /* daysUsed */
  390. memset(line->cl_Dow, 0, sizeof(line->cl_Dow));
  391. }
  392. }
  393. static void SynchronizeFile(const char *fileName)
  394. {
  395. struct parser_t *parser;
  396. struct stat sbuf;
  397. int maxLines;
  398. char *tokens[6];
  399. #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  400. char *mailTo = NULL;
  401. #endif
  402. if (!fileName)
  403. return;
  404. DeleteFile(fileName);
  405. parser = config_open(fileName);
  406. if (!parser)
  407. return;
  408. maxLines = (strcmp(fileName, "root") == 0) ? 65535 : MAXLINES;
  409. if (fstat(fileno(parser->fp), &sbuf) == 0 && sbuf.st_uid == DaemonUid) {
  410. CronFile *file = xzalloc(sizeof(CronFile));
  411. CronLine **pline;
  412. int n;
  413. file->cf_User = xstrdup(fileName);
  414. pline = &file->cf_LineBase;
  415. while (1) {
  416. CronLine *line;
  417. if (!--maxLines)
  418. break;
  419. n = config_read(parser, tokens, 6, 1, "# \t", PARSE_NORMAL | PARSE_KEEP_COPY);
  420. if (!n)
  421. break;
  422. if (DebugOpt)
  423. crondlog(LVL5 "user:%s entry:%s", fileName, parser->data);
  424. /* check if line is setting MAILTO= */
  425. if (0 == strncmp(tokens[0], "MAILTO=", 7)) {
  426. #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  427. free(mailTo);
  428. mailTo = (tokens[0][7]) ? xstrdup(&tokens[0][7]) : NULL;
  429. #endif /* otherwise just ignore such lines */
  430. continue;
  431. }
  432. /* check if a minimum of tokens is specified */
  433. if (n < 6)
  434. continue;
  435. *pline = line = xzalloc(sizeof(*line));
  436. /* parse date ranges */
  437. ParseField(file->cf_User, line->cl_Mins, 60, 0, NULL, tokens[0]);
  438. ParseField(file->cf_User, line->cl_Hrs, 24, 0, NULL, tokens[1]);
  439. ParseField(file->cf_User, line->cl_Days, 32, 0, NULL, tokens[2]);
  440. ParseField(file->cf_User, line->cl_Mons, 12, -1, MonAry, tokens[3]);
  441. ParseField(file->cf_User, line->cl_Dow, 7, 0, DowAry, tokens[4]);
  442. /*
  443. * fix days and dow - if one is not "*" and the other
  444. * is "*", the other is set to 0, and vise-versa
  445. */
  446. FixDayDow(line);
  447. #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  448. /* copy mailto (can be NULL) */
  449. line->cl_MailTo = xstrdup(mailTo);
  450. #endif
  451. /* copy command */
  452. line->cl_Shell = xstrdup(tokens[5]);
  453. if (DebugOpt) {
  454. crondlog(LVL5 " command:%s", tokens[5]);
  455. }
  456. pline = &line->cl_Next;
  457. //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]);
  458. }
  459. *pline = NULL;
  460. file->cf_Next = FileBase;
  461. FileBase = file;
  462. if (maxLines == 0) {
  463. crondlog(WARN9 "user %s: too many lines", fileName);
  464. }
  465. }
  466. config_close(parser);
  467. }
  468. static void CheckUpdates(void)
  469. {
  470. FILE *fi;
  471. char buf[256];
  472. fi = fopen_for_read(CRONUPDATE);
  473. if (fi != NULL) {
  474. unlink(CRONUPDATE);
  475. while (fgets(buf, sizeof(buf), fi) != NULL) {
  476. /* use first word only */
  477. SynchronizeFile(strtok(buf, " \t\r\n"));
  478. }
  479. fclose(fi);
  480. }
  481. }
  482. static void SynchronizeDir(void)
  483. {
  484. CronFile *file;
  485. /* Attempt to delete the database. */
  486. again:
  487. for (file = FileBase; file; file = file->cf_Next) {
  488. if (!file->cf_Deleted) {
  489. DeleteFile(file->cf_User);
  490. goto again;
  491. }
  492. }
  493. /*
  494. * Remove cron update file
  495. *
  496. * Re-chdir, in case directory was renamed & deleted, or otherwise
  497. * screwed up.
  498. *
  499. * scan directory and add associated users
  500. */
  501. unlink(CRONUPDATE);
  502. if (chdir(CDir) < 0) {
  503. crondlog(DIE9 "can't chdir(%s)", CDir);
  504. }
  505. {
  506. DIR *dir = opendir(".");
  507. struct dirent *den;
  508. if (!dir)
  509. crondlog(DIE9 "can't chdir(%s)", "."); /* exits */
  510. while ((den = readdir(dir)) != NULL) {
  511. if (strchr(den->d_name, '.') != NULL) {
  512. continue;
  513. }
  514. if (getpwnam(den->d_name)) {
  515. SynchronizeFile(den->d_name);
  516. } else {
  517. crondlog(LVL7 "ignoring %s", den->d_name);
  518. }
  519. }
  520. closedir(dir);
  521. }
  522. }
  523. /*
  524. * DeleteFile() - delete user database
  525. *
  526. * Note: multiple entries for same user may exist if we were unable to
  527. * completely delete a database due to running processes.
  528. */
  529. static void DeleteFile(const char *userName)
  530. {
  531. CronFile **pfile = &FileBase;
  532. CronFile *file;
  533. while ((file = *pfile) != NULL) {
  534. if (strcmp(userName, file->cf_User) == 0) {
  535. CronLine **pline = &file->cf_LineBase;
  536. CronLine *line;
  537. file->cf_Running = 0;
  538. file->cf_Deleted = 1;
  539. while ((line = *pline) != NULL) {
  540. if (line->cl_Pid > 0) {
  541. file->cf_Running = 1;
  542. pline = &line->cl_Next;
  543. } else {
  544. *pline = line->cl_Next;
  545. free(line->cl_Shell);
  546. free(line);
  547. }
  548. }
  549. if (file->cf_Running == 0) {
  550. *pfile = file->cf_Next;
  551. free(file->cf_User);
  552. free(file);
  553. } else {
  554. pfile = &file->cf_Next;
  555. }
  556. } else {
  557. pfile = &file->cf_Next;
  558. }
  559. }
  560. }
  561. /*
  562. * TestJobs()
  563. *
  564. * determine which jobs need to be run. Under normal conditions, the
  565. * period is about a minute (one scan). Worst case it will be one
  566. * hour (60 scans).
  567. */
  568. static int TestJobs(time_t t1, time_t t2)
  569. {
  570. int nJobs = 0;
  571. time_t t;
  572. /* Find jobs > t1 and <= t2 */
  573. for (t = t1 - t1 % 60; t <= t2; t += 60) {
  574. struct tm *tp;
  575. CronFile *file;
  576. CronLine *line;
  577. if (t <= t1)
  578. continue;
  579. tp = localtime(&t);
  580. for (file = FileBase; file; file = file->cf_Next) {
  581. if (DebugOpt)
  582. crondlog(LVL5 "file %s:", file->cf_User);
  583. if (file->cf_Deleted)
  584. continue;
  585. for (line = file->cf_LineBase; line; line = line->cl_Next) {
  586. if (DebugOpt)
  587. crondlog(LVL5 " line %s", line->cl_Shell);
  588. if (line->cl_Mins[tp->tm_min] && line->cl_Hrs[tp->tm_hour]
  589. && (line->cl_Days[tp->tm_mday] || line->cl_Dow[tp->tm_wday])
  590. && line->cl_Mons[tp->tm_mon]
  591. ) {
  592. if (DebugOpt) {
  593. crondlog(LVL5 " job: %d %s",
  594. (int)line->cl_Pid, line->cl_Shell);
  595. }
  596. if (line->cl_Pid > 0) {
  597. crondlog(LVL8 "user %s: process already running: %s",
  598. file->cf_User, line->cl_Shell);
  599. } else if (line->cl_Pid == 0) {
  600. line->cl_Pid = -1;
  601. file->cf_Ready = 1;
  602. ++nJobs;
  603. }
  604. }
  605. }
  606. }
  607. }
  608. return nJobs;
  609. }
  610. static void RunJobs(void)
  611. {
  612. CronFile *file;
  613. CronLine *line;
  614. for (file = FileBase; file; file = file->cf_Next) {
  615. if (!file->cf_Ready)
  616. continue;
  617. file->cf_Ready = 0;
  618. for (line = file->cf_LineBase; line; line = line->cl_Next) {
  619. if (line->cl_Pid >= 0)
  620. continue;
  621. RunJob(file->cf_User, line);
  622. crondlog(LVL8 "USER %s pid %3d cmd %s",
  623. file->cf_User, (int)line->cl_Pid, line->cl_Shell);
  624. if (line->cl_Pid < 0) {
  625. file->cf_Ready = 1;
  626. } else if (line->cl_Pid > 0) {
  627. file->cf_Running = 1;
  628. }
  629. }
  630. }
  631. }
  632. /*
  633. * CheckJobs() - check for job completion
  634. *
  635. * Check for job completion, return number of jobs still running after
  636. * all done.
  637. */
  638. static int CheckJobs(void)
  639. {
  640. CronFile *file;
  641. CronLine *line;
  642. int nStillRunning = 0;
  643. for (file = FileBase; file; file = file->cf_Next) {
  644. if (file->cf_Running) {
  645. file->cf_Running = 0;
  646. for (line = file->cf_LineBase; line; line = line->cl_Next) {
  647. int status, r;
  648. if (line->cl_Pid <= 0)
  649. continue;
  650. r = waitpid(line->cl_Pid, &status, WNOHANG);
  651. if (r < 0 || r == line->cl_Pid) {
  652. EndJob(file->cf_User, line);
  653. if (line->cl_Pid) {
  654. file->cf_Running = 1;
  655. }
  656. } else if (r == 0) {
  657. file->cf_Running = 1;
  658. }
  659. }
  660. }
  661. nStillRunning += file->cf_Running;
  662. }
  663. return nStillRunning;
  664. }
  665. #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  666. // TODO: sendmail should be _run-time_ option, not compile-time!
  667. static void
  668. ForkJob(const char *user, CronLine *line, int mailFd,
  669. const char *prog, const char *cmd, const char *arg,
  670. const char *mail_filename)
  671. {
  672. struct passwd *pas;
  673. pid_t pid;
  674. /* prepare things before vfork */
  675. pas = getpwnam(user);
  676. if (!pas) {
  677. crondlog(LVL9 "can't get uid for %s", user);
  678. goto err;
  679. }
  680. SetEnv(pas);
  681. pid = vfork();
  682. if (pid == 0) {
  683. /* CHILD */
  684. /* change running state to the user in question */
  685. ChangeUser(pas);
  686. if (DebugOpt) {
  687. crondlog(LVL5 "child running %s", prog);
  688. }
  689. if (mailFd >= 0) {
  690. xmove_fd(mailFd, mail_filename ? 1 : 0);
  691. dup2(1, 2);
  692. }
  693. execlp(prog, prog, cmd, arg, NULL);
  694. crondlog(ERR20 "can't exec, user %s cmd %s %s %s", user, prog, cmd, arg);
  695. if (mail_filename) {
  696. fdprintf(1, "Exec failed: %s -c %s\n", prog, arg);
  697. }
  698. _exit(EXIT_SUCCESS);
  699. }
  700. line->cl_Pid = pid;
  701. if (pid < 0) {
  702. /* FORK FAILED */
  703. crondlog(ERR20 "can't vfork");
  704. err:
  705. line->cl_Pid = 0;
  706. if (mail_filename) {
  707. unlink(mail_filename);
  708. }
  709. } else if (mail_filename) {
  710. /* PARENT, FORK SUCCESS
  711. * rename mail-file based on pid of process
  712. */
  713. char mailFile2[128];
  714. snprintf(mailFile2, sizeof(mailFile2), "%s/cron.%s.%d", TMPDIR, user, pid);
  715. rename(mail_filename, mailFile2); // TODO: xrename?
  716. }
  717. /*
  718. * Close the mail file descriptor.. we can't just leave it open in
  719. * a structure, closing it later, because we might run out of descriptors
  720. */
  721. if (mailFd >= 0) {
  722. close(mailFd);
  723. }
  724. }
  725. static void RunJob(const char *user, CronLine *line)
  726. {
  727. char mailFile[128];
  728. int mailFd = -1;
  729. line->cl_Pid = 0;
  730. line->cl_MailFlag = 0;
  731. if (line->cl_MailTo) {
  732. /* open mail file - owner root so nobody can screw with it. */
  733. snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, getpid());
  734. mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600);
  735. if (mailFd >= 0) {
  736. line->cl_MailFlag = 1;
  737. fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", line->cl_MailTo,
  738. line->cl_Shell);
  739. line->cl_MailPos = lseek(mailFd, 0, SEEK_CUR);
  740. } else {
  741. crondlog(ERR20 "cannot create mail file %s for user %s, "
  742. "discarding output", mailFile, user);
  743. }
  744. }
  745. ForkJob(user, line, mailFd, DEFAULT_SHELL, "-c", line->cl_Shell, mailFile);
  746. }
  747. /*
  748. * EndJob - called when job terminates and when mail terminates
  749. */
  750. static void EndJob(const char *user, CronLine *line)
  751. {
  752. int mailFd;
  753. char mailFile[128];
  754. struct stat sbuf;
  755. /* No job */
  756. if (line->cl_Pid <= 0) {
  757. line->cl_Pid = 0;
  758. return;
  759. }
  760. /*
  761. * End of job and no mail file
  762. * End of sendmail job
  763. */
  764. snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, line->cl_Pid);
  765. line->cl_Pid = 0;
  766. if (line->cl_MailFlag == 0) {
  767. return;
  768. }
  769. line->cl_MailFlag = 0;
  770. /*
  771. * End of primary job - check for mail file. If size has increased and
  772. * the file is still valid, we sendmail it.
  773. */
  774. mailFd = open(mailFile, O_RDONLY);
  775. unlink(mailFile);
  776. if (mailFd < 0) {
  777. return;
  778. }
  779. if (fstat(mailFd, &sbuf) < 0 || sbuf.st_uid != DaemonUid
  780. || sbuf.st_nlink != 0 || sbuf.st_size == line->cl_MailPos
  781. || !S_ISREG(sbuf.st_mode)
  782. ) {
  783. close(mailFd);
  784. return;
  785. }
  786. if (line->cl_MailTo)
  787. ForkJob(user, line, mailFd, SENDMAIL, SENDMAIL_ARGS, NULL);
  788. }
  789. #else /* crond without sendmail */
  790. static void RunJob(const char *user, CronLine *line)
  791. {
  792. struct passwd *pas;
  793. pid_t pid;
  794. /* prepare things before vfork */
  795. pas = getpwnam(user);
  796. if (!pas) {
  797. crondlog(LVL9 "can't get uid for %s", user);
  798. goto err;
  799. }
  800. SetEnv(pas);
  801. /* fork as the user in question and run program */
  802. pid = vfork();
  803. if (pid == 0) {
  804. /* CHILD */
  805. /* change running state to the user in question */
  806. ChangeUser(pas);
  807. if (DebugOpt) {
  808. crondlog(LVL5 "child running %s", DEFAULT_SHELL);
  809. }
  810. execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_Shell, NULL);
  811. crondlog(ERR20 "can't exec, user %s cmd %s %s %s", user,
  812. DEFAULT_SHELL, "-c", line->cl_Shell);
  813. _exit(EXIT_SUCCESS);
  814. }
  815. if (pid < 0) {
  816. /* FORK FAILED */
  817. crondlog(ERR20 "can't vfork");
  818. err:
  819. pid = 0;
  820. }
  821. line->cl_Pid = pid;
  822. }
  823. #endif /* ENABLE_FEATURE_CROND_CALL_SENDMAIL */