1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 */
25 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
26 /* All Rights Reserved */
27
28
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <sys/types.h>
32 #include <sys/wait.h>
33 #include <errno.h>
34 #include <signal.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <fcntl.h>
39 #include <ctype.h>
40 #include <pwd.h>
41 #include <unistd.h>
42 #include <locale.h>
43 #include <nl_types.h>
44 #include <langinfo.h>
45 #include <libintl.h>
46 #include <security/pam_appl.h>
47 #include <limits.h>
48 #include <libzoneinfo.h>
49 #include "cron.h"
50 #include "getresponse.h"
51
52 #if defined(XPG4)
53 #define VIPATH "/usr/xpg4/bin/vi"
54 #elif defined(XPG6)
55 #define VIPATH "/usr/xpg6/bin/vi"
56 #else
57 #define _XPG_NOTDEFINED
58 #define VIPATH "vi"
59 #endif
60
61 #define TMPFILE "_cron" /* prefix for tmp file */
62 #define CRMODE 0600 /* mode for creating crontabs */
63
64 #define BADCREATE \
65 "can't create your crontab file in the crontab directory."
66 #define BADOPEN "can't open your crontab file."
67 #define BADSHELL \
68 "because your login shell isn't /usr/bin/sh, you can't use cron."
69 #define WARNSHELL "warning: commands will be executed using /usr/bin/sh\n"
70 #define BADUSAGE \
71 "usage:\n" \
72 "\tcrontab [file]\n" \
73 "\tcrontab -e [username]\n" \
74 "\tcrontab -l [username]\n" \
75 "\tcrontab -r [username]"
76 #define INVALIDUSER "you are not a valid user (no entry in /etc/passwd)."
77 #define NOTALLOWED "you are not authorized to use cron. Sorry."
78 #define NOTROOT \
79 "you must be super-user to access another user's crontab file"
80 #define AUDITREJECT "The audit context for your shell has not been set."
81 #define EOLN "unexpected end of line."
82 #define UNEXPECT "unexpected character found in line."
83 #define OUTOFBOUND "number out of bounds."
84 #define ERRSFND "errors detected in input, no crontab file generated."
85 #define ED_ERROR \
86 " The editor indicates that an error occurred while you were\n"\
87 " editing the crontab data - usually a minor typing error.\n\n"
88 #define BADREAD "error reading your crontab file"
89 #define ED_PROMPT \
90 " Edit again, to ensure crontab information is intact (%s/%s)?\n"\
91 " ('%s' will discard edits.)"
92 #define NAMETOOLONG "login name too long"
93 #define BAD_TZ "Timezone unrecognized in: %s"
94 #define BAD_SHELL "Invalid shell specified: %s"
95 #define BAD_HOME "Unable to access directory: %s\t%s\n"
96
97 extern int per_errno;
98
99 extern int audit_crontab_modify(char *, char *, int);
100 extern int audit_crontab_delete(char *, int);
101 extern int audit_crontab_not_allowed(uid_t, char *);
102
103 int err;
104 int cursor;
105 char *cf;
106 char *tnam;
107 char edtemp[5+13+1];
108 char line[CTLINESIZE];
109 static char login[UNAMESIZE];
110
111 static int next_field(int, int);
112 static void catch(int);
113 static void crabort(char *);
114 static void cerror(char *);
115 static void copycron(FILE *);
116
117 int
main(int argc,char ** argv)118 main(int argc, char **argv)
119 {
120 int c, r;
121 int rflag = 0;
122 int lflag = 0;
123 int eflag = 0;
124 int errflg = 0;
125 char *pp;
126 FILE *fp, *tmpfp;
127 struct stat stbuf;
128 struct passwd *pwp;
129 time_t omodtime;
130 char *editor;
131 uid_t ruid;
132 pid_t pid;
133 int stat_loc;
134 int ret;
135 char real_login[UNAMESIZE];
136 int tmpfd = -1;
137 pam_handle_t *pamh;
138 int pam_error;
139 char *buf;
140 size_t buflen;
141
142 (void) setlocale(LC_ALL, "");
143 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
144 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */
145 #endif
146 (void) textdomain(TEXT_DOMAIN);
147
148 if (init_yes() < 0) {
149 (void) fprintf(stderr, gettext(ERR_MSG_INIT_YES),
150 strerror(errno));
151 exit(1);
152 }
153
154 while ((c = getopt(argc, argv, "elr")) != EOF)
155 switch (c) {
156 case 'e':
157 eflag++;
158 break;
159 case 'l':
160 lflag++;
161 break;
162 case 'r':
163 rflag++;
164 break;
165 case '?':
166 errflg++;
167 break;
168 }
169
170 if (eflag + lflag + rflag > 1)
171 errflg++;
172
173 argc -= optind;
174 argv += optind;
175 if (errflg || argc > 1)
176 crabort(BADUSAGE);
177
178 ruid = getuid();
179 if ((pwp = getpwuid(ruid)) == NULL)
180 crabort(INVALIDUSER);
181
182 if (strlcpy(real_login, pwp->pw_name, sizeof (real_login))
183 >= sizeof (real_login))
184 crabort(NAMETOOLONG);
185
186 if ((eflag || lflag || rflag) && argc == 1) {
187 if ((pwp = getpwnam(*argv)) == NULL)
188 crabort(INVALIDUSER);
189
190 if (!cron_admin(real_login)) {
191 if (pwp->pw_uid != ruid)
192 crabort(NOTROOT);
193 else
194 pp = getuser(ruid);
195 } else
196 pp = *argv++;
197 } else {
198 pp = getuser(ruid);
199 }
200
201 if (pp == NULL) {
202 if (per_errno == 2)
203 crabort(BADSHELL);
204 else
205 crabort(INVALIDUSER);
206 }
207 if (strlcpy(login, pp, sizeof (login)) >= sizeof (login))
208 crabort(NAMETOOLONG);
209 if (!allowed(login, CRONALLOW, CRONDENY))
210 crabort(NOTALLOWED);
211
212 /* Do account validation check */
213 pam_error = pam_start("cron", pp, NULL, &pamh);
214 if (pam_error != PAM_SUCCESS) {
215 crabort((char *)pam_strerror(pamh, pam_error));
216 }
217 pam_error = pam_acct_mgmt(pamh, PAM_SILENT);
218 if (pam_error != PAM_SUCCESS) {
219 (void) fprintf(stderr, gettext("Warning - Invalid account: "
220 "'%s' not allowed to execute cronjobs\n"), pp);
221 }
222 (void) pam_end(pamh, PAM_SUCCESS);
223
224
225 /* check for unaudited shell */
226 if (audit_crontab_not_allowed(ruid, pp))
227 crabort(AUDITREJECT);
228
229 cf = xmalloc(strlen(CRONDIR)+strlen(login)+2);
230 strcat(strcat(strcpy(cf, CRONDIR), "/"), login);
231
232 if (rflag) {
233 r = unlink(cf);
234 cron_sendmsg(DELETE, login, login, CRON);
235 audit_crontab_delete(cf, r);
236 exit(0);
237 }
238 if (lflag) {
239 if ((fp = fopen(cf, "r")) == NULL)
240 crabort(BADOPEN);
241 while (fgets(line, CTLINESIZE, fp) != NULL)
242 fputs(line, stdout);
243 fclose(fp);
244 exit(0);
245 }
246 if (eflag) {
247 if ((fp = fopen(cf, "r")) == NULL) {
248 if (errno != ENOENT)
249 crabort(BADOPEN);
250 }
251 (void) strcpy(edtemp, "/tmp/crontabXXXXXX");
252 tmpfd = mkstemp(edtemp);
253 if (fchown(tmpfd, ruid, -1) == -1) {
254 (void) close(tmpfd);
255 crabort("fchown of temporary file failed");
256 }
257 (void) close(tmpfd);
258 /*
259 * Fork off a child with user's permissions,
260 * to edit the crontab file
261 */
262 if ((pid = fork()) == (pid_t)-1)
263 crabort("fork failed");
264 if (pid == 0) { /* child process */
265 /* give up super-user privileges. */
266 setuid(ruid);
267 if ((tmpfp = fopen(edtemp, "w")) == NULL)
268 crabort("can't create temporary file");
269 if (fp != NULL) {
270 /*
271 * Copy user's crontab file to temporary file.
272 */
273 while (fgets(line, CTLINESIZE, fp) != NULL) {
274 fputs(line, tmpfp);
275 if (ferror(tmpfp)) {
276 fclose(fp);
277 fclose(tmpfp);
278 crabort("write error on"
279 "temporary file");
280 }
281 }
282 if (ferror(fp)) {
283 fclose(fp);
284 fclose(tmpfp);
285 crabort(BADREAD);
286 }
287 fclose(fp);
288 }
289 if (fclose(tmpfp) == EOF)
290 crabort("write error on temporary file");
291 if (stat(edtemp, &stbuf) < 0)
292 crabort("can't stat temporary file");
293 omodtime = stbuf.st_mtime;
294 #ifdef _XPG_NOTDEFINED
295 editor = getenv("VISUAL");
296 if (editor == NULL) {
297 #endif
298 editor = getenv("EDITOR");
299 if (editor == NULL)
300 editor = VIPATH;
301 #ifdef _XPG_NOTDEFINED
302 }
303 #endif
304 buflen = strlen(editor) + strlen(edtemp) + 2;
305 buf = xmalloc(buflen);
306 (void) snprintf(buf, buflen, "%s %s", editor, edtemp);
307
308 sleep(1);
309
310 while (1) {
311 ret = system(buf);
312
313 /* sanity checks */
314 if ((tmpfp = fopen(edtemp, "r")) == NULL)
315 crabort("can't open temporary file");
316 if (fstat(fileno(tmpfp), &stbuf) < 0)
317 crabort("can't stat temporary file");
318 if (stbuf.st_size == 0)
319 crabort("temporary file empty");
320 if (omodtime == stbuf.st_mtime) {
321 (void) unlink(edtemp);
322 fprintf(stderr, gettext(
323 "The crontab file was not"
324 " changed.\n"));
325 exit(1);
326 }
327 if ((ret) && (errno != EINTR)) {
328 /*
329 * Some editors (like 'vi') can return
330 * a non-zero exit status even though
331 * everything is okay. Need to check.
332 */
333 fprintf(stderr, gettext(ED_ERROR));
334 fflush(stderr);
335 if (isatty(fileno(stdin))) {
336 /* Interactive */
337 fprintf(stdout,
338 gettext(ED_PROMPT),
339 yesstr, nostr, nostr);
340 fflush(stdout);
341
342 if (yes()) {
343 /* Edit again */
344 continue;
345 } else {
346 /* Dump changes */
347 (void) unlink(edtemp);
348 exit(1);
349 }
350 } else {
351 /*
352 * Non-interactive, dump changes
353 */
354 (void) unlink(edtemp);
355 exit(1);
356 }
357 }
358 exit(0);
359 } /* while (1) */
360 }
361
362 /* fix for 1125555 - ignore common signals while waiting */
363 (void) signal(SIGINT, SIG_IGN);
364 (void) signal(SIGHUP, SIG_IGN);
365 (void) signal(SIGQUIT, SIG_IGN);
366 (void) signal(SIGTERM, SIG_IGN);
367 wait(&stat_loc);
368 if ((stat_loc & 0xFF00) != 0)
369 exit(1);
370
371 /*
372 * unlink edtemp as 'ruid'. The file contents will be held
373 * since we open the file descriptor 'tmpfp' before calling
374 * unlink.
375 */
376 if (((ret = seteuid(ruid)) < 0) ||
377 ((tmpfp = fopen(edtemp, "r")) == NULL) ||
378 (unlink(edtemp) == -1)) {
379 fprintf(stderr, "crontab: %s: %s\n",
380 edtemp, errmsg(errno));
381 if ((ret < 0) || (tmpfp == NULL))
382 (void) unlink(edtemp);
383 exit(1);
384 } else
385 seteuid(0);
386
387 copycron(tmpfp);
388 } else {
389 if (argc == 0)
390 copycron(stdin);
391 else if (seteuid(getuid()) != 0 || (fp = fopen(argv[0], "r"))
392 == NULL)
393 crabort(BADOPEN);
394 else {
395 seteuid(0);
396 copycron(fp);
397 }
398 }
399 cron_sendmsg(ADD, login, login, CRON);
400 /*
401 * if (per_errno == 2)
402 * fprintf(stderr, gettext(WARNSHELL));
403 */
404 return (0);
405 }
406
407 static void
copycron(fp)408 copycron(fp)
409 FILE *fp;
410 {
411 FILE *tfp;
412 char pid[6], *tnam_end;
413 int t;
414 char buf[LINE_MAX];
415
416 sprintf(pid, "%-5d", getpid());
417 tnam = xmalloc(strlen(CRONDIR)+strlen(TMPFILE)+7);
418 strcat(strcat(strcat(strcpy(tnam, CRONDIR), "/"), TMPFILE), pid);
419 /* cut trailing blanks */
420 tnam_end = strchr(tnam, ' ');
421 if (tnam_end != NULL)
422 *tnam_end = 0;
423 /* catch SIGINT, SIGHUP, SIGQUIT signals */
424 if (signal(SIGINT, catch) == SIG_IGN)
425 signal(SIGINT, SIG_IGN);
426 if (signal(SIGHUP, catch) == SIG_IGN) signal(SIGHUP, SIG_IGN);
427 if (signal(SIGQUIT, catch) == SIG_IGN) signal(SIGQUIT, SIG_IGN);
428 if (signal(SIGTERM, catch) == SIG_IGN) signal(SIGTERM, SIG_IGN);
429 if ((t = creat(tnam, CRMODE)) == -1) crabort(BADCREATE);
430 if ((tfp = fdopen(t, "w")) == NULL) {
431 unlink(tnam);
432 crabort(BADCREATE);
433 }
434 err = 0; /* if errors found, err set to 1 */
435 while (fgets(line, CTLINESIZE, fp) != NULL) {
436 cursor = 0;
437 while (line[cursor] == ' ' || line[cursor] == '\t')
438 cursor++;
439 /* fix for 1039689 - treat blank line like a comment */
440 if (line[cursor] == '#' || line[cursor] == '\n')
441 goto cont;
442
443 if (strncmp(&line[cursor], ENV_TZ, strlen(ENV_TZ)) == 0) {
444 char *x;
445
446 strncpy(buf, &line[cursor + strlen(ENV_TZ)],
447 sizeof (buf));
448 if ((x = strchr(buf, '\n')) != NULL)
449 *x = NULL;
450
451 if (isvalid_tz(buf, NULL, _VTZ_ALL)) {
452 goto cont;
453 } else {
454 err = 1;
455 fprintf(stderr, BAD_TZ, &line[cursor]);
456 continue;
457 }
458 } else if (strncmp(&line[cursor], ENV_SHELL,
459 strlen(ENV_SHELL)) == 0) {
460 char *x;
461
462 strncpy(buf, &line[cursor + strlen(ENV_SHELL)],
463 sizeof (buf));
464 if ((x = strchr(buf, '\n')) != NULL)
465 *x = NULL;
466
467 if (isvalid_shell(buf)) {
468 goto cont;
469 } else {
470 err = 1;
471 fprintf(stderr, BAD_SHELL, &line[cursor]);
472 continue;
473 }
474 } else if (strncmp(&line[cursor], ENV_HOME,
475 strlen(ENV_HOME)) == 0) {
476 char *x;
477
478 strncpy(buf, &line[cursor + strlen(ENV_HOME)],
479 sizeof (buf));
480 if ((x = strchr(buf, '\n')) != NULL)
481 *x = NULL;
482 if (chdir(buf) == 0) {
483 goto cont;
484 } else {
485 err = 1;
486 fprintf(stderr, BAD_HOME, &line[cursor],
487 strerror(errno));
488 continue;
489 }
490 }
491
492 if (next_field(0, 59)) continue;
493 if (next_field(0, 23)) continue;
494 if (next_field(1, 31)) continue;
495 if (next_field(1, 12)) continue;
496 if (next_field(0, 06)) continue;
497 if (line[++cursor] == '\0') {
498 cerror(EOLN);
499 continue;
500 }
501 cont:
502 if (fputs(line, tfp) == EOF) {
503 unlink(tnam);
504 crabort(BADCREATE);
505 }
506 }
507 fclose(fp);
508 fclose(tfp);
509
510 /* audit differences between old and new crontabs */
511 audit_crontab_modify(cf, tnam, err);
512
513 if (!err) {
514 /* make file tfp the new crontab */
515 unlink(cf);
516 if (link(tnam, cf) == -1) {
517 unlink(tnam);
518 crabort(BADCREATE);
519 }
520 } else {
521 crabort(ERRSFND);
522 }
523 unlink(tnam);
524 }
525
526 static int
next_field(lower,upper)527 next_field(lower, upper)
528 int lower, upper;
529 {
530 int num, num2;
531
532 while ((line[cursor] == ' ') || (line[cursor] == '\t')) cursor++;
533 if (line[cursor] == '\0') {
534 cerror(EOLN);
535 return (1);
536 }
537 if (line[cursor] == '*') {
538 cursor++;
539 if ((line[cursor] != ' ') && (line[cursor] != '\t')) {
540 cerror(UNEXPECT);
541 return (1);
542 }
543 return (0);
544 }
545 while (TRUE) {
546 if (!isdigit(line[cursor])) {
547 cerror(UNEXPECT);
548 return (1);
549 }
550 num = 0;
551 do {
552 num = num*10 + (line[cursor]-'0');
553 } while (isdigit(line[++cursor]));
554 if ((num < lower) || (num > upper)) {
555 cerror(OUTOFBOUND);
556 return (1);
557 }
558 if (line[cursor] == '-') {
559 if (!isdigit(line[++cursor])) {
560 cerror(UNEXPECT);
561 return (1);
562 }
563 num2 = 0;
564 do {
565 num2 = num2*10 + (line[cursor]-'0');
566 } while (isdigit(line[++cursor]));
567 if ((num2 < lower) || (num2 > upper)) {
568 cerror(OUTOFBOUND);
569 return (1);
570 }
571 }
572 if ((line[cursor] == ' ') || (line[cursor] == '\t')) break;
573 if (line[cursor] == '\0') {
574 cerror(EOLN);
575 return (1);
576 }
577 if (line[cursor++] != ',') {
578 cerror(UNEXPECT);
579 return (1);
580 }
581 }
582 return (0);
583 }
584
585 static void
cerror(msg)586 cerror(msg)
587 char *msg;
588 {
589 fprintf(stderr, gettext("%scrontab: error on previous line; %s\n"),
590 line, msg);
591 err = 1;
592 }
593
594
595 static void
catch(int x)596 catch(int x)
597 {
598 unlink(tnam);
599 exit(1);
600 }
601
602 static void
crabort(msg)603 crabort(msg)
604 char *msg;
605 {
606 int sverrno;
607
608 if (strcmp(edtemp, "") != 0) {
609 sverrno = errno;
610 (void) unlink(edtemp);
611 errno = sverrno;
612 }
613 if (tnam != NULL) {
614 sverrno = errno;
615 (void) unlink(tnam);
616 errno = sverrno;
617 }
618 fprintf(stderr, "crontab: %s\n", gettext(msg));
619 exit(1);
620 }
621