Пример #1
0
static void load_crontab(const char *fileName)
{
	struct parser_t *parser;
	struct stat sbuf;
	int maxLines;
	char *tokens[6];
#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
	char *mailTo = NULL;
#endif
	char *shell = NULL;

	delete_cronfile(fileName);

	if (!getpwnam(fileName)) {
		log7("ignoring file '%s' (no such user)", fileName);
		return;
	}

	parser = config_open(fileName);
	if (!parser)
		return;

	maxLines = (strcmp(fileName, "root") == 0) ? 65535 : MAXLINES;

	if (fstat(fileno(parser->fp), &sbuf) == 0 && sbuf.st_uid == DAEMON_UID) {
		CronFile *file = xzalloc(sizeof(CronFile));
		CronLine **pline;
		int n;

		file->cf_username = xstrdup(fileName);
		pline = &file->cf_lines;

		while (1) {
			CronLine *line;

			if (!--maxLines) {
				bb_error_msg("user %s: too many lines", fileName);
				break;
			}

			n = config_read(parser, tokens, 6, 1, "# \t", PARSE_NORMAL | PARSE_KEEP_COPY);
			if (!n)
				break;

			log5("user:%s entry:%s", fileName, parser->data);

			/* check if line is setting MAILTO= */
			if (is_prefixed_with(tokens[0], "MAILTO=")) {
#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
				free(mailTo);
				mailTo = (tokens[0][7]) ? xstrdup(&tokens[0][7]) : NULL;
#endif /* otherwise just ignore such lines */
				continue;
			}
			if (is_prefixed_with(tokens[0], "SHELL=")) {
				free(shell);
				shell = xstrdup(&tokens[0][6]);
				continue;
			}
//TODO: handle HOME= too? "man crontab" says:
//name = value
//
//where the spaces around the equal-sign (=) are optional, and any subsequent
//non-leading spaces in value will be part of the value assigned to name.
//The value string may be placed in quotes (single or double, but matching)
//to preserve leading or trailing blanks.
//
//Several environment variables are set up automatically by the cron(8) daemon.
//SHELL is set to /bin/sh, and LOGNAME and HOME are set from the /etc/passwd
//line of the crontab's owner. HOME and SHELL may be overridden by settings
//in the crontab; LOGNAME may not.

			/* check if a minimum of tokens is specified */
			if (n < 6)
				continue;
			*pline = line = xzalloc(sizeof(*line));
			/* parse date ranges */
			ParseField(file->cf_username, line->cl_Mins, 60, 0, NULL, tokens[0]);
			ParseField(file->cf_username, line->cl_Hrs, 24, 0, NULL, tokens[1]);
			ParseField(file->cf_username, line->cl_Days, 32, 0, NULL, tokens[2]);
			ParseField(file->cf_username, line->cl_Mons, 12, -1, MonAry, tokens[3]);
			ParseField(file->cf_username, line->cl_Dow, 7, 0, DowAry, tokens[4]);
			/*
			 * fix days and dow - if one is not "*" and the other
			 * is "*", the other is set to 0, and vise-versa
			 */
			FixDayDow(line);
#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
			/* copy mailto (can be NULL) */
			line->cl_mailto = xstrdup(mailTo);
#endif
			line->cl_shell = xstrdup(shell);
			/* copy command */
			line->cl_cmd = xstrdup(tokens[5]);
			pline = &line->cl_next;
//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]);
		}
		*pline = NULL;

		file->cf_next = G.cron_files;
		G.cron_files = file;
	}
	config_close(parser);
#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
	free(mailTo);
#endif
	free(shell);
}
Пример #2
0
static void SynchronizeFile(const char *fileName)
{
	int maxEntries = MAXLINES;
	int maxLines;
	char buf[1024];

	if (strcmp(fileName, "root") == 0) {
		maxEntries = 65535;
	}
	maxLines = maxEntries * 10;

	if (fileName) {
		FILE *fi;

		DeleteFile(fileName);

		fi = fopen(fileName, "r");
		if (fi != NULL) {
			struct stat sbuf;

			if (fstat(fileno(fi), &sbuf) == 0 && sbuf.st_uid == DaemonUid) {
				CronFile *file = calloc(1, sizeof(CronFile));
				CronLine **pline;

				file->cf_User = strdup(fileName);
				pline = &file->cf_LineBase;

				while (fgets(buf, sizeof(buf), fi) != NULL && --maxLines) {
					CronLine line;
					char *ptr;

					trim(buf);
					if (buf[0] == 0 || buf[0] == '#') {
						continue;
					}
					if (--maxEntries == 0) {
						break;
					}
					memset(&line, 0, sizeof(line));

#ifdef FEATURE_DEBUG_OPT
					if (DebugOpt) {
						crondlog("\111User %s Entry %s\n", fileName, buf);
					}
#endif

					/* parse date ranges */
					ptr = ParseField(file->cf_User, line.cl_Mins, 60, 0, NULL, buf);
					ptr = ParseField(file->cf_User, line.cl_Hrs, 24, 0, NULL, ptr);
					ptr = ParseField(file->cf_User, line.cl_Days, 32, 0, NULL, ptr);
					ptr = ParseField(file->cf_User, line.cl_Mons, 12, -1, MonAry, ptr);
					ptr = ParseField(file->cf_User, line.cl_Dow, 7, 0, DowAry, ptr);

					/* check failure */
					if (ptr == NULL) {
						continue;
					}

					/*
					 * fix days and dow - if one is not * and the other
					 * is *, the other is set to 0, and vise-versa
					 */

					FixDayDow(&line);

					*pline = calloc(1, sizeof(CronLine));
					**pline = line;

					/* copy command */
					(*pline)->cl_Shell = strdup(ptr);

#ifdef FEATURE_DEBUG_OPT
					if (DebugOpt) {
						crondlog("\111    Command %s\n", ptr);
					}
#endif

					pline = &((*pline)->cl_Next);
				}
				*pline = NULL;

				file->cf_Next = FileBase;
				FileBase = file;

				if (maxLines == 0 || maxEntries == 0) {
					crondlog("\111Maximum number of lines reached for user %s\n", fileName);
				}
			}
			fclose(fi);
		}
	}
}
Пример #3
0
static void SynchronizeFile(const char *fileName)
{
	FILE *fi;
	struct stat sbuf;
	int maxEntries;
	int maxLines;
	char buf[1024];
#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
	char *mailTo = NULL;
#endif

	if (!fileName)
		return;

	DeleteFile(fileName);
	fi = fopen(fileName, "r");
	if (!fi)
		return;

	maxEntries = MAXLINES;
	if (strcmp(fileName, "root") == 0) {
		maxEntries = 65535;
	}
	maxLines = maxEntries * 10;

	if (fstat(fileno(fi), &sbuf) == 0 && sbuf.st_uid == DaemonUid) {
		CronFile *file = xzalloc(sizeof(CronFile));
		CronLine **pline;

		file->cf_User = xstrdup(fileName);
		pline = &file->cf_LineBase;

		while (fgets(buf, sizeof(buf), fi) != NULL && --maxLines) {
			CronLine *line;
			char *ptr;

			trim(buf);
			if (buf[0] == '\0' || buf[0] == '#') {
				continue;
			}
			if (--maxEntries == 0) {
				break;
			}
			if (DebugOpt) {
				crondlog(LVL5 "user:%s entry:%s", fileName, buf);
			}
			/* check if line is setting MAILTO= */
			if (0 == strncmp("MAILTO=", buf, 7)) {
#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
				free(mailTo);
				mailTo = (buf[7]) ? xstrdup(buf+7) : NULL;
#endif /* otherwise just ignore such lines */
				continue;
			}
			*pline = line = xzalloc(sizeof(CronLine));
			/* parse date ranges */
			ptr = ParseField(file->cf_User, line->cl_Mins, 60, 0, NULL, buf);
			ptr = ParseField(file->cf_User, line->cl_Hrs, 24, 0, NULL, ptr);
			ptr = ParseField(file->cf_User, line->cl_Days, 32, 0, NULL, ptr);
			ptr = ParseField(file->cf_User, line->cl_Mons, 12, -1, MonAry, ptr);
			ptr = ParseField(file->cf_User, line->cl_Dow, 7, 0, DowAry, ptr);
			/* check failure */
			if (ptr == NULL) {
				free(line);
				continue;
			}
			/*
			 * fix days and dow - if one is not "*" and the other
			 * is "*", the other is set to 0, and vise-versa
			 */
			FixDayDow(line);
#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
			/* copy mailto (can be NULL) */
			line->cl_MailTo = xstrdup(mailTo);
#endif
			/* copy command */
			line->cl_Shell = xstrdup(ptr);
			if (DebugOpt) {
				crondlog(LVL5 " command:%s", ptr);
			}
			pline = &line->cl_Next;
		}
		*pline = NULL;

		file->cf_Next = FileBase;
		FileBase = file;

		if (maxLines == 0 || maxEntries == 0) {
			crondlog(WARN9 "user %s: too many lines", fileName);
		}
	}
	fclose(fi);
}
Пример #4
0
static void load_crontab(const char *fileName)
{
	struct parser_t *parser;
	struct stat sbuf;
	int maxLines;
	char *tokens[6];
#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
	char *mailTo = NULL;
#endif
	char *shell = NULL;

	delete_cronfile(fileName);

	if (!getpwnam(fileName)) {
		log7("ignoring file '%s' (no such user)", fileName);
		return;
	}

	parser = config_open(fileName);
	if (!parser)
		return;

	maxLines = (strcmp(fileName, "root") == 0) ? 65535 : MAXLINES;

	if (fstat(fileno(parser->fp), &sbuf) == 0 && sbuf.st_uid == DAEMON_UID) {
		CronFile *file = xzalloc(sizeof(CronFile));
		CronLine **pline;
		int n;

		file->cf_username = xstrdup(fileName);
		pline = &file->cf_lines;

		while (1) {
			CronLine *line;

			if (!--maxLines) {
				bb_error_msg("user %s: too many lines", fileName);
				break;
			}

			n = config_read(parser, tokens, 6, 1, "# \t", PARSE_NORMAL | PARSE_KEEP_COPY);
			if (!n)
				break;

			log5("user:%s entry:%s", fileName, parser->data);

			/* check if line is setting MAILTO= */
			if (is_prefixed_with(tokens[0], "MAILTO=")) {
#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
				free(mailTo);
				mailTo = (tokens[0][7]) ? xstrdup(&tokens[0][7]) : NULL;
#endif /* otherwise just ignore such lines */
				continue;
			}
			if (is_prefixed_with(tokens[0], "SHELL=")) {
				free(shell);
				shell = xstrdup(&tokens[0][6]);
				continue;
			}
//TODO: handle HOME= too? "man crontab" says:
//name = value
//
//where the spaces around the equal-sign (=) are optional, and any subsequent
//non-leading spaces in value will be part of the value assigned to name.
//The value string may be placed in quotes (single or double, but matching)
//to preserve leading or trailing blanks.
//
//Several environment variables are set up automatically by the cron(8) daemon.
//SHELL is set to /bin/sh, and LOGNAME and HOME are set from the /etc/passwd
//line of the crontab's owner. HOME and SHELL may be overridden by settings
//in the crontab; LOGNAME may not.

#if ENABLE_FEATURE_CROND_SPECIAL_TIMES
			if (tokens[0][0] == '@') {
				/*
				 * "@daily /a/script/to/run PARAM1 PARAM2..."
				 */
				typedef struct SpecialEntry {
					const char *name;
					const char tokens[8];
				} SpecialEntry;
				static const SpecialEntry SpecAry[] = {
					/*              hour  day   month weekday */
					{ "yearly",     "0\0" "1\0" "1\0" "*" },
					{ "annually",   "0\0" "1\0" "1\0" "*" },
					{ "monthly",    "0\0" "1\0" "*\0" "*" },
					{ "weekly",     "0\0" "*\0" "*\0" "0" },
					{ "daily",      "0\0" "*\0" "*\0" "*" },
					{ "midnight",   "0\0" "*\0" "*\0" "*" },
					{ "hourly",     "*\0" "*\0" "*\0" "*" },
					{ "reboot",     ""                    },
				};
				const SpecialEntry *e = SpecAry;

				if (n < 2)
					continue;
				for (;;) {
					if (strcmp(e->name, tokens[0] + 1) == 0) {
						/*
						 * tokens[1] is only the first word of command,
						 * can'r use it.
						 * find the entire command in unmodified string:
						 */
						tokens[5] = skip_whitespace(
							skip_non_whitespace(
							skip_whitespace(parser->data)));
						if (e->tokens[0]) {
							char *et = (char*)e->tokens;
							/* minute is "0" for all specials */
							tokens[0] = (char*)"0";
							tokens[1] = et;
							tokens[2] = et + 2;
							tokens[3] = et + 4;
							tokens[4] = et + 6;
						}
						goto got_it;
					}
					if (!e->tokens[0])
						break;
					e++;
				}
				continue; /* bad line (unrecognized '@foo') */
			}
#endif
			/* check if a minimum of tokens is specified */
			if (n < 6)
				continue;
 IF_FEATURE_CROND_SPECIAL_TIMES(
  got_it:
 )
			*pline = line = xzalloc(sizeof(*line));
#if ENABLE_FEATURE_CROND_SPECIAL_TIMES
			if (tokens[0][0] == '@') { /* "@reboot" line */
				file->cf_wants_starting = 1;
				line->cl_pid = START_ME_REBOOT; /* wants to start */
				/* line->cl_Mins/Hrs/etc stay zero: never match any time */
			} else
#endif
			{
				/* parse date ranges */
				ParseField(file->cf_username, line->cl_Mins, 60, 0, NULL, tokens[0]);
				ParseField(file->cf_username, line->cl_Hrs, 24, 0, NULL, tokens[1]);
				ParseField(file->cf_username, line->cl_Days, 32, 0, NULL, tokens[2]);
				ParseField(file->cf_username, line->cl_Mons, 12, -1, MonAry, tokens[3]);
				ParseField(file->cf_username, line->cl_Dow, 7, 0, DowAry, tokens[4]);
				/*
				 * fix days and dow - if one is not "*" and the other
				 * is "*", the other is set to 0, and vise-versa
				 */
				FixDayDow(line);
			}
#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
			/* copy mailto (can be NULL) */
			line->cl_mailto = xstrdup(mailTo);
#endif
			line->cl_shell = xstrdup(shell);
			/* copy command */
			line->cl_cmd = xstrdup(tokens[5]);
			pline = &line->cl_next;
//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]);
		}
		*pline = NULL;

		file->cf_next = G.cron_files;
		G.cron_files = file;
	}
Пример #5
0
void
SynchronizeFile(const char *dpath, const char *fileName, const char *userName)
{
	CronFile **pfile;
	CronFile *file;
	int maxEntries;
	int maxLines;
	char buf[RW_BUFFER]; /* max length for crontab lines */
	char *path;
	FILE *fi;

	/*
	 * Limit entries
	 */
	if (strcmp(userName, "root") == 0)
		maxEntries = 65535;
	else
		maxEntries = MAXLINES;
	maxLines = maxEntries * 10;

	/*
	 * Delete any existing copy of this CronFile
	 */
	pfile = &FileBase;
	while ((file = *pfile) != NULL) {
		if (file->cf_Deleted == 0 && strcmp(file->cf_DPath, dpath) == 0 &&
				strcmp(file->cf_FileName, fileName) == 0
		   ) {
			DeleteFile(pfile);
		} else {
			pfile = &file->cf_Next;
		}
	}

	if (!(path = concat(dpath, "/", fileName, NULL))) {
		errno = ENOMEM;
		perror("SynchronizeFile");
		exit(1);
	}
	if ((fi = fopen(path, "r")) != NULL) {
		struct stat sbuf;

		if (fstat(fileno(fi), &sbuf) == 0 && sbuf.st_uid == DaemonUid) {
			CronFile *file = calloc(1, sizeof(CronFile));
			CronLine **pline;
			time_t tnow = time(NULL);
			tnow -= tnow % 60;

			file->cf_UserName = strdup(userName);
			file->cf_FileName = strdup(fileName);
			file->cf_DPath = strdup(dpath);
			pline = &file->cf_LineBase;

			/* fgets reads at most size-1 chars until \n or EOF, then adds a\0; \n if present is stored in buf */
			while (fgets(buf, sizeof(buf), fi) != NULL && --maxLines) {
				CronLine line;
				char *ptr = buf;
				int len;

				while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n')
					++ptr;

				len = strlen(ptr);
				if (len && ptr[len-1] == '\n')
					ptr[--len] = 0;

				if (*ptr == 0 || *ptr == '#')
					continue;

				if (--maxEntries == 0)
					break;

				memset(&line, 0, sizeof(line));

				if (DebugOpt)
					printlogf(LOG_DEBUG, "User %s Entry %s\n", userName, buf);

				if (*ptr == '@') {
					/*
					 * parse @hourly, etc
					 */
					int	j;
					line.cl_Delay = -1;
					ptr += 1;
					for (j = 0; FreqAry[j]; ++j) {
						if (strncmp(ptr, FreqAry[j], strlen(FreqAry[j])) == 0) {
							break;
						}
					}
					if (FreqAry[j]) {
						ptr += strlen(FreqAry[j]);
						switch(j) {
							case 0:
								/* noauto */
								line.cl_Freq = -2;
								line.cl_Delay = 0;
								break;
							case 1:
								/* reboot */
								line.cl_Freq = -1;
								line.cl_Delay = 0;
								break;
							case 2:
								line.cl_Freq = HOURLY_FREQ;
								break;
							case 3:
								line.cl_Freq = DAILY_FREQ;
								break;
							case 4:
								line.cl_Freq = WEEKLY_FREQ;
								break;
							case 5:
								line.cl_Freq = MONTHLY_FREQ;
								break;
							case 6:
								line.cl_Freq = YEARLY_FREQ;
								break;
							/* else line.cl_Freq will remain 0 */
						}
					}

					if (!line.cl_Freq || (*ptr != ' ' && *ptr != '\t')) {
						printlogf(LOG_WARNING, "failed parsing crontab for user %s: %s\n", userName, buf);
						continue;
					}

					if (line.cl_Delay < 0) {
						/*
						 * delays on @daily, @hourly, etc are 1/20 of the frequency
						 * so they don't all start at once
						 * this also affects how they behave when the job returns EAGAIN
						 */
						line.cl_Delay = line.cl_Freq / 20;
						line.cl_Delay -= line.cl_Delay % 60;
						if (line.cl_Delay == 0)
							line.cl_Delay = 60;
						/* all minutes are permitted */
						for (j=0; j<60; ++j)
							line.cl_Mins[j] = 1;
						for (j=0; j<24; ++j)
							line.cl_Hrs[j] = 1;
						for (j=1; j<32; ++j)
							/* days are numbered 1..31 */
							line.cl_Days[j] = 1;
						for (j=0; j<12; ++j)
							line.cl_Mons[j] = 1;
					}

					while (*ptr == ' ' || *ptr == '\t')
						++ptr;

				} else {
					/*
					 * parse date ranges
					 */

					ptr = ParseField(file->cf_UserName, line.cl_Mins, 60, 0, 1,
							NULL, ptr);
					ptr = ParseField(file->cf_UserName, line.cl_Hrs,  24, 0, 1,
							NULL, ptr);
					ptr = ParseField(file->cf_UserName, line.cl_Days, 32, 0, 1,
							NULL, ptr);
					ptr = ParseField(file->cf_UserName, line.cl_Mons, 12, -1, 1,
							MonAry, ptr);
					ptr = ParseField(file->cf_UserName, line.cl_Dow, 7, 0, 31,
							DowAry, ptr);
					/*
					 * check failure
					 */

					if (ptr == NULL)
						continue;

					/*
					 * fix days and dow - if one is not * and the other
					 * is *, the other is set to 0, and vise-versa
					 */

					FixDayDow(&line);
				}

				/* check for ID=... and AFTER=... and FREQ=... */
				do {
					if (strncmp(ptr, ID_TAG, strlen(ID_TAG)) == 0) {
						if (line.cl_JobName) {
							/* only assign ID_TAG once */
							printlogf(LOG_WARNING, "failed parsing crontab for user %s: repeated %s\n", userName, ptr);
							ptr = NULL;
						} else {
							ptr += strlen(ID_TAG);
							/*
							 * name = strsep(&ptr, seps):
							 * return name = ptr, and if ptr contains sep chars, overwrite first with 0 and point ptr to next char
							 *                    else set ptr=NULL
							 */
							if (!(line.cl_Description = concat("job ", strsep(&ptr, " \t"), NULL))) {
								errno = ENOMEM;
								perror("SynchronizeFile");
								exit(1);
							}
							line.cl_JobName = line.cl_Description + 4;
							if (!ptr)
								printlogf(LOG_WARNING, "failed parsing crontab for user %s: no command after %s%s\n", userName, ID_TAG, line.cl_JobName);
						}
					} else if (strncmp(ptr, FREQ_TAG, strlen(FREQ_TAG)) == 0) {
						if (line.cl_Freq) {
							/* only assign FREQ_TAG once */
							printlogf(LOG_WARNING, "failed parsing crontab for user %s: repeated %s\n", userName, ptr);
							ptr = NULL;
						} else {
							char *base = ptr;
							ptr += strlen(FREQ_TAG);
							ptr = ParseInterval(&line.cl_Freq, ptr);
							if (ptr && *ptr == '/')
								ptr = ParseInterval(&line.cl_Delay, ++ptr);
							else
								line.cl_Delay = line.cl_Freq;
							if (!ptr) {
								printlogf(LOG_WARNING, "failed parsing crontab for user %s: %s\n", userName, base);
							} else if (*ptr != ' ' && *ptr != '\t') {
								printlogf(LOG_WARNING, "failed parsing crontab for user %s: no command after %s\n", userName, base);
								ptr = NULL;
							}
						}
					} else if (strncmp(ptr, WAIT_TAG, strlen(WAIT_TAG)) == 0) {
						if (line.cl_Waiters) {
							/* only assign WAIT_TAG once */
							printlogf(LOG_WARNING, "failed parsing crontab for user %s: repeated %s\n", userName, ptr);
							ptr = NULL;
						} else {
							short more = 1;
							char *name;
							ptr += strlen(WAIT_TAG);
							do {
								CronLine *job, **pjob;
								if (strcspn(ptr,",") < strcspn(ptr," \t"))
									name = strsep(&ptr, ",");
								else {
									more = 0;
									name = strsep(&ptr, " \t");
								}
								if (!ptr || *ptr == 0) {
									/* unexpectedly this was the last token in buf; so abort */
									printlogf(LOG_WARNING, "failed parsing crontab for user %s: no command after %s%s\n", userName, WAIT_TAG, name);
									ptr = NULL;
								} else {
									int waitfor = 0;
									char *w, *wsave;
									if ((w = strchr(name, '/')) != NULL) {
										wsave = w++;
										w = ParseInterval(&waitfor, w);
										if (!w || *w != 0) {
											printlogf(LOG_WARNING, "failed parsing crontab for user %s: %s%s\n", userName, WAIT_TAG, name);
											ptr = NULL;
										} else
											/* truncate name */
											*wsave = 0;
									}
									if (ptr) {
										/* look for a matching CronLine */
										pjob = &file->cf_LineBase;
										while ((job = *pjob) != NULL) {
											if (job->cl_JobName && strcmp(job->cl_JobName, name) == 0) {
												CronWaiter *waiter = malloc(sizeof(CronWaiter));
												CronNotifier *notif = malloc(sizeof(CronNotifier));
												waiter->cw_Flag = -1;
												waiter->cw_MaxWait = waitfor;
												waiter->cw_NotifLine = job;
												waiter->cw_Notifier = notif;
												waiter->cw_Next = line.cl_Waiters;	/* add to head of line.cl_Waiters */
												line.cl_Waiters = waiter;
												notif->cn_Waiter = waiter;
												notif->cn_Next = job->cl_Notifs;	/* add to head of job->cl_Notifs */
												job->cl_Notifs = notif;
												break;
											} else
												pjob = &job->cl_Next;
										}
										if (!job) {
											printlogf(LOG_WARNING, "failed parsing crontab for user %s: unknown job %s\n", userName, name);
											/* we can continue parsing this line, we just don't install any CronWaiter for the requested job */
										}
									}
								}
							} while (ptr && more);
						}
					} else
						break;
					if (!ptr)
						break;
					while (*ptr == ' ' || *ptr == '\t')
						++ptr;
				} while (!line.cl_JobName || !line.cl_Waiters || !line.cl_Freq);

				if (line.cl_JobName && (!ptr || *line.cl_JobName == 0)) {
					/* we're aborting, or ID= was empty */
					free(line.cl_Description);
					line.cl_Description = NULL;
					line.cl_JobName = NULL;
				}
				if (ptr && line.cl_Delay > 0 && !line.cl_JobName) {
					printlogf(LOG_WARNING, "failed parsing crontab for user %s: writing timestamp requires job %s to be named\n", userName, ptr);
					ptr = NULL;
				}
				if (!ptr) {
					/* couldn't parse so we abort; free any cl_Waiters */
					if (line.cl_Waiters) {
						CronWaiter **pwaiters, *waiters;
						pwaiters = &line.cl_Waiters;
						while ((waiters = *pwaiters) != NULL) {
							*pwaiters = waiters->cw_Next;
							/* leave the Notifier allocated but disabled */
							waiters->cw_Notifier->cn_Waiter = NULL;
							free(waiters);
						}
					}
					continue;
				}
				/* now we've added any ID=... or AFTER=... */

				/*
				 * copy command string
				 */
				line.cl_Shell = strdup(ptr);

				if (line.cl_Delay > 0) {
					if (!(line.cl_Timestamp = concat(TSDir, "/", userName, ".", line.cl_JobName, NULL))) {
						errno = ENOMEM;
						perror("SynchronizeFile");
						exit(1);
					}
					line.cl_NotUntil = tnow + line.cl_Delay;
				}

				if (line.cl_JobName) {
					if (DebugOpt)
						printlogf(LOG_DEBUG, "    Command %s Job %s\n", line.cl_Shell, line.cl_JobName);
				} else {
					/* when cl_JobName is NULL, we point cl_Description to cl_Shell */
					line.cl_Description = line.cl_Shell;
					if (DebugOpt)
						printlogf(LOG_DEBUG, "    Command %s\n", line.cl_Shell);
				}

				*pline = calloc(1, sizeof(CronLine));
				/* copy working CronLine to newly allocated one */
				**pline = line;

				pline = &((*pline)->cl_Next);
			}

			*pline = NULL;

			file->cf_Next = FileBase;
			FileBase = file;

			if (maxLines == 0 || maxEntries == 0)
				printlogf(LOG_WARNING, "maximum number of lines reached for user %s\n", userName);
		}
		fclose(fi);
	}
	free(path);
}