Added extra/crond.service for systemd
[dcron.git] / crontab.c
blob39a90568a9195955be2596caa25ec7d04618c474
2 /*
3 * CRONTAB.C
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
14 #include "defs.h"
16 Prototype void printlogf(int level, const char *ctl, ...);
18 void Usage(void);
19 int GetReplaceStream(const char *user, const char *file);
20 void EditFile(const char *user, const char *file);
22 const char *CDir = CRONTABS;
23 int UserId;
26 int
27 main(int ac, char **av)
29 enum { NONE, EDIT, LIST, REPLACE, DELETE } option = NONE;
30 struct passwd *pas;
31 char *repFile = NULL;
32 int repFd = 0;
33 int i;
34 char caller[SMALL_BUFFER]; /* user that ran program */
36 UserId = getuid();
37 if ((pas = getpwuid(UserId)) == NULL) {
38 perror("getpwuid");
39 exit(1);
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);
45 exit(1);
48 opterr = 0;
49 while ((i=getopt(ac,av,"ledu:c:")) != -1) {
50 switch(i) {
51 case 'l':
52 if (option != NONE)
53 Usage();
54 else
55 option = LIST;
56 break;
57 case 'e':
58 if (option != NONE)
59 Usage();
60 else
61 option = EDIT;
62 break;
63 case 'd':
64 if (option != NONE)
65 Usage();
66 else
67 option = DELETE;
68 break;
69 case 'u':
70 /* getopt guarantees optarg != 0 here */
71 if (*optarg != 0 && getuid() == geteuid()) {
72 pas = getpwnam(optarg);
73 if (pas) {
74 UserId = pas->pw_uid;
75 /* paranoia */
76 if ((pas = getpwuid(UserId)) == NULL) {
77 perror("getpwuid");
78 exit(1);
80 } else {
81 printlogf(0, "user '%s' unknown", optarg);
82 exit(1);
84 } else {
85 printlogf(0, "-u option: superuser only");
86 exit(1);
88 break;
89 case 'c':
90 /* getopt guarantees optarg != 0 here */
91 if (*optarg != 0 && getuid() == geteuid()) {
92 CDir = optarg;
93 } else {
94 printlogf(0, "-c option: superuser only");
95 exit(1);
97 break;
98 default:
99 /* unrecognized -X */
100 option = NONE;
104 if (option == NONE && optind == ac - 1) {
105 if (av[optind][0] != '-') {
106 option = REPLACE;
107 repFile = av[optind];
108 optind++;
109 } else if (av[optind][1] == 0) {
110 option = REPLACE;
111 optind++;
114 if (option == NONE || optind != ac) {
115 Usage();
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 printlogf(0, "unable to read replacement file %s", repFile);
126 exit(1);
131 * Change directory to our crontab directory
134 if (chdir(CDir) < 0) {
135 printlogf(0, "cannot change dir to %s: %s", CDir, strerror(errno));
136 exit(1);
140 * Handle options as appropriate
143 switch(option) {
144 case LIST:
146 FILE *fi;
147 char buf[RW_BUFFER];
149 if ((fi = fopen(pas->pw_name, "r"))) {
150 while (fgets(buf, sizeof(buf), fi) != NULL)
151 fputs(buf, stdout);
152 fclose(fi);
153 } else {
154 fprintf(stderr, "no crontab for %s\n", pas->pw_name);
155 /* no error code */
158 break;
159 case EDIT:
161 FILE *fi;
162 int fd;
163 int n;
164 char tmp[] = TMPDIR "/crontab.XXXXXX";
165 char buf[RW_BUFFER];
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)
177 write(fd, buf, n);
179 EditFile(caller, tmp);
180 remove(tmp);
181 lseek(fd, 0L, 0);
182 repFd = fd;
183 } else {
184 printlogf(0, "unable to create %s: %s", tmp, strerror(errno));
185 exit(1);
189 option = REPLACE;
190 /* fall through */
191 case REPLACE:
193 char buf[RW_BUFFER];
194 char path[SMALL_BUFFER];
195 int fd;
196 int n;
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) {
204 write(fd, buf, n);
206 close(fd);
207 rename(path, pas->pw_name);
208 } else {
209 fprintf(stderr, "unable to create %s/%s: %s\n",
210 CDir,
211 path,
212 strerror(errno)
215 close(repFd);
217 break;
218 case DELETE:
219 remove(pas->pw_name);
220 break;
221 case NONE:
222 default:
223 break;
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) {
232 FILE *fo;
233 struct stat st;
235 while ((fo = fopen(CRONUPDATE, "a"))) {
236 fprintf(fo, "%s\n", pas->pw_name);
237 fflush(fo);
238 if (fstat(fileno(fo), &st) != 0 || st.st_nlink != 0) {
239 fclose(fo);
240 break;
242 fclose(fo);
243 /* loop */
245 if (fo == NULL) {
246 fprintf(stderr, "unable to append to %s/%s\n", CDir, CRONUPDATE);
249 exit(0);
250 /* not reached */
253 void
254 printlogf(int level, const char *ctl, ...)
256 va_list va;
257 char buf[LOG_BUFFER];
259 va_start(va, ctl);
260 vsnprintf(buf, sizeof(buf), ctl, va);
261 write(2, buf, strlen(buf));
262 va_end(va);
265 void
266 Usage(void)
269 * parse error
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");
278 exit(2);
282 GetReplaceStream(const char *user, const char *file)
284 int filedes[2];
285 int pid;
286 int fd;
287 int n;
288 char buf[RW_BUFFER];
290 if (pipe(filedes) < 0) {
291 perror("pipe");
292 return(-1);
294 if ((pid = fork()) < 0) {
295 perror("fork");
296 return(-1);
298 if (pid > 0) {
300 * PARENT
301 * Read from pipe[0], return it (or -1 if it's empty)
304 close(filedes[1]);
305 if (read(filedes[0], buf, 1) != 1) {
306 close(filedes[0]);
307 filedes[0] = -1;
309 return(filedes[0]);
313 * CHILD
314 * Read from fd for "$file", write to pipe[1]
317 close(filedes[0]);
319 if (ChangeUser(user, NULL) < 0)
320 exit(0);
322 fd = open(file, O_RDONLY);
323 if (fd < 0) {
324 printlogf(0, "unable to open %s: %s", file, strerror(errno));
325 exit(1);
327 buf[0] = 0;
328 write(filedes[1], buf, 1);
329 while ((n = read(fd, buf, sizeof(buf))) > 0) {
330 write(filedes[1], buf, n);
332 exit(0);
335 void
336 EditFile(const char *user, const char *file)
338 int pid;
340 if ((pid = fork()) == 0) {
342 * CHILD - change user and run editor on "$file"
344 const char *ptr;
345 char visual[SMALL_BUFFER];
347 if (ChangeUser(user, TMPDIR) < 0)
348 exit(0);
349 if ((ptr = getenv("EDITOR")) == NULL || strlen(ptr) >= sizeof(visual))
350 if ((ptr = getenv("VISUAL")) == NULL || strlen(ptr) >= sizeof(visual))
351 ptr = PATH_VI;
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);
358 exit(1);
360 if (pid < 0) {
362 * PARENT - failure
364 perror("fork");
365 exit(1);
367 waitpid(pid, NULL, 0);