write "fake" timestamp file when none exists yet
[yacron.git] / crontab.c
blob8ccc6c902a2b78e692551b877a397c927eeedf6c
2 /*
3 * CRONTAB.C
5 * CRONTAB
7 * usually setuid root, -c option only works if getuid() == geteuid()
9 * Copyright 1994 Matthew Dillon (dillon@apollo.backplane.com)
10 * Copyright 2009 James Pryor <profjim@jimpryor.net>
11 * May be distributed under the GNU General Public License
14 #include "defs.h"
16 const char *CDir = CRONTABS;
17 int UserId;
18 /* not used in this program, but subs.c needs */
19 short LogLevel = LOG_NOTICE;
20 short ForegroundOpt = 0;
21 short LoggerOpt = 0;
22 const char *LogFile = LOG_FILE;
23 char *TempDir = TMPDIR;
25 void EditFile(const char *user, const char *file);
26 int GetReplaceStream(const char *user, const char *file);
28 int
29 main(int ac, char **av)
31 enum { NONE, EDIT, LIST, REPLACE, DELETE } option = NONE;
32 struct passwd *pas;
33 char *repFile = NULL;
34 int repFd = 0;
35 int i;
36 char caller[256]; /* user that ran program */
38 UserId = getuid();
39 if ((pas = getpwuid(UserId)) == NULL) {
40 perror("getpwuid");
41 exit(1);
43 snprintf(caller, sizeof(caller), "%s", pas->pw_name);
45 i = 1;
46 if (ac > 1) {
47 if (av[1][0] == '-' && av[1][1] == 0) {
48 option = REPLACE;
49 ++i;
50 } else if (av[1][0] != '-') {
51 option = REPLACE;
52 ++i;
53 repFile = av[1];
57 opterr = 0;
59 while ((i=getopt(ac,av,"ledu:c:")) != EOF) {
60 switch(i) {
61 case 'l':
62 option = LIST;
63 break;
64 case 'e':
65 option = EDIT;
66 break;
67 case 'd':
68 option = DELETE;
69 break;
70 case 'u':
71 if (*optarg != 0 && getuid() == geteuid()) {
72 pas = getpwnam(optarg);
73 if (pas) {
74 UserId = pas->pw_uid;
75 } else {
76 errx(1, "user %s unknown", optarg);
78 } else {
79 errx(1, "only the superuser may specify a user");
81 break;
82 case 'c':
83 if (*optarg != 0 && getuid() == geteuid()) {
84 CDir = optarg;
85 } else {
86 errx(1, "-c option: superuser only");
88 break;
89 default:
90 option = NONE;
94 if (option == NONE) {
96 * parse error
98 printf("crontab " VERSION "\n");
99 printf("crontab file <opts> replace crontab from file\n");
100 printf("crontab - <opts> replace crontab from stdin\n");
101 printf("crontab -u user specify user\n");
102 printf("crontab -l [user] list crontab for user\n");
103 printf("crontab -e [user] edit crontab for user\n");
104 printf("crontab -d [user] delete crontab for user\n");
105 printf("crontab -c dir specify crontab directory\n");
106 exit(2);
110 * Get password entry
113 if ((pas = getpwuid(UserId)) == NULL) {
114 perror("getpwuid");
115 exit(1);
119 * If there is a replacement file, obtain a secure descriptor to it.
122 if (repFile) {
123 repFd = GetReplaceStream(caller, repFile);
124 if (repFd < 0) {
125 errx(1, "unable to read replacement file");
130 * Change directory to our crontab directory
133 if (chdir(CDir) < 0) {
134 errx(1, "cannot change dir to %s: %s", CDir, strerror(errno));
138 * Handle options as appropriate
141 switch(option) {
142 case LIST:
144 FILE *fi;
145 char buf[1024];
147 if ((fi = fopen(pas->pw_name, "r"))) {
148 while (fgets(buf, sizeof(buf), fi) != NULL)
149 fputs(buf, stdout);
150 fclose(fi);
151 } else {
152 fprintf(stderr, "no crontab for %s\n", pas->pw_name);
155 break;
156 case EDIT:
158 FILE *fi;
159 int fd;
160 int n;
161 char tmp[] = TMPDIR "/crontab.XXXXXX";
162 char buf[1024];
164 if ((fd = mkstemp(tmp)) >= 0) {
165 chown(tmp, getuid(), getgid());
166 if ((fi = fopen(pas->pw_name, "r"))) {
167 while ((n = fread(buf, 1, sizeof(buf), fi)) > 0)
168 write(fd, buf, n);
170 EditFile(caller, tmp);
171 remove(tmp);
172 lseek(fd, 0L, 0);
173 repFd = fd;
174 } else {
175 errx(1, "unable to create %s", tmp);
179 option = REPLACE;
180 /* fall through */
181 case REPLACE:
183 char buf[1024];
184 char path[1024];
185 int fd;
186 int n;
188 snprintf(path, sizeof(path), "%s.new", pas->pw_name);
189 if ((fd = open(path, O_CREAT|O_TRUNC|O_EXCL|O_APPEND|O_WRONLY, 0600)) >= 0) {
190 while ((n = read(repFd, buf, sizeof(buf))) > 0) {
191 write(fd, buf, n);
193 close(fd);
194 rename(path, pas->pw_name);
195 } else {
196 fprintf(stderr, "unable to create %s/%s: %s\n",
197 CDir,
198 path,
199 strerror(errno)
202 close(repFd);
204 break;
205 case DELETE:
206 remove(pas->pw_name);
207 break;
208 case NONE:
209 default:
210 break;
214 * Bump notification file. Handle window where crond picks file up
215 * before we can write our entry out.
218 if (option == REPLACE || option == DELETE) {
219 FILE *fo;
220 struct stat st;
222 while ((fo = fopen(CRONUPDATE, "a"))) {
223 fprintf(fo, "%s\n", pas->pw_name);
224 fflush(fo);
225 if (fstat(fileno(fo), &st) != 0 || st.st_nlink != 0) {
226 fclose(fo);
227 break;
229 fclose(fo);
230 /* loop */
232 if (fo == NULL) {
233 fprintf(stderr, "unable to append to %s/%s\n", CDir, CRONUPDATE);
236 (volatile void)exit(0);
237 /* not reached */
241 GetReplaceStream(const char *user, const char *file)
243 int filedes[2];
244 int pid;
245 int fd;
246 int n;
247 char buf[1024];
249 if (pipe(filedes) < 0) {
250 perror("pipe");
251 return(-1);
253 if ((pid = fork()) < 0) {
254 perror("fork");
255 return(-1);
257 if (pid > 0) {
259 * PARENT
262 close(filedes[1]);
263 if (read(filedes[0], buf, 1) != 1) {
264 close(filedes[0]);
265 filedes[0] = -1;
267 return(filedes[0]);
271 * CHILD
274 close(filedes[0]);
276 if (ChangeUser(user, 0) < 0)
277 exit(0);
279 fd = open(file, O_RDONLY);
280 if (fd < 0)
281 errx(0, "unable to open %s", file);
282 buf[0] = 0;
283 write(filedes[1], buf, 1);
284 while ((n = read(fd, buf, sizeof(buf))) > 0) {
285 write(filedes[1], buf, n);
287 exit(0);
290 void
291 EditFile(const char *user, const char *file)
293 int pid;
295 if ((pid = fork()) == 0) {
297 * CHILD - change user and run editor
299 const char *ptr;
300 char visual[1024];
302 if (ChangeUser(user, 1) < 0)
303 exit(0);
304 if ((ptr = getenv("EDITOR")) == NULL || strlen(ptr) > 256)
305 if ((ptr = getenv("VISUAL")) == NULL || strlen(ptr) > 256)
306 ptr = PATH_VI;
308 snprintf(visual, sizeof(visual), "%s %s", ptr, file);
309 execl("/bin/sh", "/bin/sh", "-c", visual, NULL);
310 perror("exec");
311 exit(0);
313 if (pid < 0) {
315 * PARENT - failure
317 perror("fork");
318 exit(1);
320 wait4(pid, NULL, 0, NULL);