5 * crontab [-u user] [-c dir] [-l|-e|-d|file|-]
6 * usually run as setuid root
7 * -u and -c options only work if getuid() == geteuid()
9 * Copyright 1994 Matthew Dillon (dillon@apollo.backplane.com)
10 * Copyright 2009-2011 James Pryor <profjim@jimpryor.net>
11 * May be distributed under the GNU General Public License
16 Prototype
void printlogf(int level
, const char *ctl
, ...);
19 int GetReplaceStream(const char *user
, const char *file
);
20 void EditFile(const char *user
, const char *file
);
22 const char *CDir
= CRONTABS
;
27 main(int ac
, char **av
)
29 enum { NONE
, EDIT
, LIST
, REPLACE
, DELETE
} option
= NONE
;
34 char caller
[SMALL_BUFFER
]; /* user that ran program */
37 if ((pas
= getpwuid(UserId
)) == NULL
) {
41 /* [v]snprintf write at most size including \0; they'll null-terminate, even when they truncate */
42 /* return value >= size means result was truncated */
43 if (snprintf(caller
, sizeof(caller
), "%s", pas
->pw_name
) >= sizeof(caller
)) {
44 printlogf(0, "username '%s' too long", caller
);
49 while ((i
=getopt(ac
,av
,"ledu:c:")) != -1) {
70 /* getopt guarantees optarg != 0 here */
71 if (*optarg
!= 0 && getuid() == geteuid()) {
72 pas
= getpwnam(optarg
);
76 if ((pas
= getpwuid(UserId
)) == NULL
) {
81 printlogf(0, "user '%s' unknown", optarg
);
85 printlogf(0, "-u option: superuser only");
90 /* getopt guarantees optarg != 0 here */
91 if (*optarg
!= 0 && getuid() == geteuid()) {
94 printlogf(0, "-c option: superuser only");
104 if (option
== NONE
&& optind
== ac
- 1) {
105 if (av
[optind
][0] != '-') {
107 repFile
= av
[optind
];
109 } else if (av
[optind
][1] == 0) {
114 if (option
== NONE
|| optind
!= ac
) {
119 * If there is a replacement file, obtain a secure descriptor to it.
123 repFd
= GetReplaceStream(caller
, repFile
);
125 printlogf(0, "unable to read replacement file %s", repFile
);
131 * Change directory to our crontab directory
134 if (chdir(CDir
) < 0) {
135 printlogf(0, "cannot change dir to %s: %s", CDir
, strerror(errno
));
140 * Handle options as appropriate
149 if ((fi
= fopen(pas
->pw_name
, "r"))) {
150 while (fgets(buf
, sizeof(buf
), fi
) != NULL
)
154 fprintf(stderr
, "no crontab for %s\n", pas
->pw_name
);
164 char tmp
[] = TMPDIR
"/crontab.XXXXXX";
168 * Create temp file with perm 0600 and O_EXCL flag, ensuring that this call creates the file
169 * Read from fi for "$CDir/$USER", write to fd for temp file
170 * EditFile changes user if necessary, and runs editor on temp file
171 * Then we delete the temp file, keeping its fd as repFd
173 if ((fd
= mkstemp(tmp
)) >= 0) {
174 chown(tmp
, getuid(), getgid());
175 if ((fi
= fopen(pas
->pw_name
, "r"))) {
176 while ((n
= fread(buf
, 1, sizeof(buf
), fi
)) > 0)
179 EditFile(caller
, tmp
);
184 printlogf(0, "unable to create %s: %s", tmp
, strerror(errno
));
194 char path
[SMALL_BUFFER
];
199 * Read from repFd, write to fd for "$CDir/$USER.new"
201 snprintf(path
, sizeof(path
), "%s.new", pas
->pw_name
);
202 if ((fd
= open(path
, O_CREAT
|O_TRUNC
|O_EXCL
|O_APPEND
|O_WRONLY
, 0600)) >= 0) {
203 while ((n
= read(repFd
, buf
, sizeof(buf
))) > 0) {
207 rename(path
, pas
->pw_name
);
209 fprintf(stderr
, "unable to create %s/%s: %s\n",
219 remove(pas
->pw_name
);
227 * Bump notification file. Handle window where crond picks file up
228 * before we can write our entry out.
231 if (option
== REPLACE
|| option
== DELETE
) {
235 while ((fo
= fopen(CRONUPDATE
, "a"))) {
236 fprintf(fo
, "%s\n", pas
->pw_name
);
238 if (fstat(fileno(fo
), &st
) != 0 || st
.st_nlink
!= 0) {
246 fprintf(stderr
, "unable to append to %s/%s\n", CDir
, CRONUPDATE
);
254 printlogf(int level
, const char *ctl
, ...)
257 char buf
[LOG_BUFFER
];
260 vsnprintf(buf
, sizeof(buf
), ctl
, va
);
261 write(2, buf
, strlen(buf
));
271 printf("crontab " VERSION
"\n");
272 printf("crontab file [-u user] replace crontab from file\n");
273 printf("crontab - [-u user] replace crontab from stdin\n");
274 printf("crontab -l [-u user] list crontab\n");
275 printf("crontab -e [-u user] edit crontab\n");
276 printf("crontab -d [-u user] delete crontab\n");
277 printf("crontab -c dir <opts> specify crontab directory\n");
282 GetReplaceStream(const char *user
, const char *file
)
290 if (pipe(filedes
) < 0) {
294 if ((pid
= fork()) < 0) {
301 * Read from pipe[0], return it (or -1 if it's empty)
305 if (read(filedes
[0], buf
, 1) != 1) {
314 * Read from fd for "$file", write to pipe[1]
319 if (ChangeUser(user
, NULL
) < 0)
322 fd
= open(file
, O_RDONLY
);
324 printlogf(0, "unable to open %s: %s", file
, strerror(errno
));
328 write(filedes
[1], buf
, 1);
329 while ((n
= read(fd
, buf
, sizeof(buf
))) > 0) {
330 write(filedes
[1], buf
, n
);
336 EditFile(const char *user
, const char *file
)
340 if ((pid
= fork()) == 0) {
342 * CHILD - change user and run editor on "$file"
345 char visual
[SMALL_BUFFER
];
347 if (ChangeUser(user
, TMPDIR
) < 0)
349 if ((ptr
= getenv("EDITOR")) == NULL
|| strlen(ptr
) >= sizeof(visual
))
350 if ((ptr
= getenv("VISUAL")) == NULL
|| strlen(ptr
) >= sizeof(visual
))
353 /* [v]snprintf write at most size including \0; they'll null-terminate, even when they truncate */
354 /* return value >= size means result was truncated */
355 if (snprintf(visual
, sizeof(visual
), "%s %s", ptr
, file
) < sizeof(visual
))
356 execl("/bin/sh", "/bin/sh", "-c", visual
, NULL
);
357 printlogf(0, "couldn't exec %s", visual
);
367 waitpid(pid
, NULL
, 0);