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.
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.
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]
23 * Copyright (c) 2011 Gary Mills
25 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
26 * Use is subject to license terms.
27 * Copyright (c) 2016 by Delphix. All rights reserved.
30 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
31 /* All Rights Reserved */
33 #include <sys/resource.h>
35 #include <sys/types.h>
58 #define TMPFILE "_at" /* prefix for temporary files */
60 * Mode for creating files in ATDIR.
61 * Setuid bit on so that if an owner of a file gives that file
62 * away to someone else, the setuid bit will no longer be set.
63 * If this happens, atrun will not execute the file
65 #define ATMODE (S_ISUID | S_IRUSR | S_IRGRP | S_IROTH)
66 #define ROOT 0 /* user-id of super-user */
67 #define MAXTRYS 100 /* max trys to create at job file */
69 #define BADTIME "bad time specification"
70 #define BADQUEUE "queue name must be a single character a-z"
71 #define NOTCQUEUE "queue c is reserved for cron entries"
72 #define BADSHELL "because your login shell isn't /usr/bin/sh,"\
74 #define WARNSHELL "commands will be executed using %s\n"
75 #define CANTCD "can't change directory to the at directory"
76 #define CANTCHOWN "can't change the owner of your job to you"
77 #define CANTCHUID "can't change user identifier"
78 #define CANTCREATE "can't create a job for you"
79 #define INVALIDUSER "you are not a valid user (no entry in /etc/passwd)"
80 #define NOOPENDIR "can't open the at directory"
81 #define NOTALLOWED "you are not authorized to use at. Sorry."
83 "usage: at [-c|-k|-s] [-m] [-f file] [-p project] [-q queuename] "\
85 " at [-c|-k|-s] [-m] [-f file] [-p project] [-q queuename] "\
87 " at -l [-p project] [-q queuename] [at_job_id...]\n"\
88 " at -r at_job_id ...\n"
90 #define FORMAT "%a %b %e %H:%M:%S %Y"
93 static int atoi_for2(char *);
94 static int check_queue(char *, int);
95 static int list_jobs(int, char **, int, int);
96 static int remove_jobs(int, char **, char *);
97 static void usage(void);
98 static void catch(int);
99 static void copy(char *, FILE *, int);
100 static void atime(struct tm
*, struct tm
*);
101 static int not_this_project(char *);
102 static char *mkjobname(time_t);
103 static time_t parse_time(char *);
104 static time_t gtime(struct tm
*);
105 static void escapestr(const char *);
106 void atabort(char *)__NORETURN
;
108 extern int yyparse(void);
110 extern void audit_at_delete(char *, char *, int);
111 extern int audit_at_create(char *, int);
112 extern int audit_cron_is_anc_name(char *);
113 extern int audit_cron_delete_anc_file(char *, char *);
116 * Error in getdate(3G)
118 static char *errlist
[] = {
120 /* 1 */ "getdate: The DATEMSK environment variable is not set",
121 /* 2 */ "getdate: Error on \"open\" of the template file",
122 /* 3 */ "getdate: Error on \"stat\" of the template file",
123 /* 4 */ "getdate: The template file is not a regular file",
124 /* 5 */ "getdate: An error is encountered while reading the template",
125 /* 6 */ "getdate: Malloc(3C) failed",
126 /* 7 */ "getdate: There is no line in the template that matches the input",
127 /* 8 */ "getdate: Invalid input specification"
131 int mday
[12] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
133 struct tm
*tp
, at
, rt
;
134 static int cshflag
= 0;
135 static int kshflag
= 0;
136 static int shflag
= 0;
137 static int mflag
= 0;
138 static int pflag
= 0;
141 static char pname
[80];
142 static char pname1
[80];
143 static short jobtype
= ATEVENT
; /* set to 1 if batch job */
145 extern int per_errno
;
146 static projid_t project
;
149 main(int argc
, char **argv
)
164 char *jobfile
= NULL
; /* file containing job to be run */
165 char argpbuf
[LINE_MAX
], timebuf
[80];
170 struct project prj
, *pprj
;
171 char mybuf
[PROJECT_BUFSZ
];
172 char ipbuf
[PROJECT_BUFSZ
];
174 (void) setlocale(LC_ALL
, "");
175 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
176 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */
178 (void) textdomain(TEXT_DOMAIN
);
181 login
= getuser(user
);
186 atabort(INVALIDUSER
);
189 if (!allowed(login
, ATALLOW
, ATDENY
))
192 while ((c
= getopt(argc
, argv
, "cklmsrf:p:q:t:")) != EOF
)
213 if ((pprj
= getprojbyname(proj
, pprj
,
214 (void *)&mybuf
, sizeof (mybuf
))) != NULL
) {
215 project
= pprj
->pj_projid
;
216 if (inproj(login
, pprj
->pj_name
,
217 (void *)&ipbuf
, sizeof (ipbuf
)))
220 (void) fprintf(stderr
,
221 gettext("at: user %s is "
223 "project %s (%d)\n"),
224 login
, pprj
->pj_name
,
231 if (isdigit(proj
[0]) &&
232 (pprj
= getprojbyid(atoi(proj
), pprj
,
233 (void *)&mybuf
, sizeof (mybuf
))) != NULL
) {
234 project
= pprj
->pj_projid
;
235 if (inproj(login
, pprj
->pj_name
,
236 (void *)&ipbuf
, sizeof (ipbuf
)))
239 (void) fprintf(stderr
,
240 gettext("at: user %s is "
242 "project %s (%d)\n"),
243 login
, pprj
->pj_name
,
249 (void) fprintf(stderr
, gettext("at: project "
250 "%s not found.\n"), proj
);
255 if (optarg
[1] != '\0')
257 jobtype
= *optarg
- 'a';
258 if ((jobtype
< 0) || (jobtype
> 25))
271 when
= parse_time(optarg
);
280 if (lflag
+ rflag
> 1)
284 if (cshflag
|| kshflag
|| shflag
|| mflag
||
285 fflag
|| tflag
|| rflag
)
287 return (list_jobs(argc
, argv
, qflag
, jobtype
));
291 if (cshflag
|| kshflag
|| shflag
|| mflag
||
292 fflag
|| tflag
|| qflag
)
294 return (remove_jobs(argc
, argv
, login
));
297 if ((argc
+ tflag
== 0) && (jobtype
!= BATCHEVENT
))
300 if (cshflag
+ kshflag
+ shflag
> 1)
301 atabort("ambiguous shell request");
305 if (jobtype
== BATCHEVENT
)
308 if (when
== 0) { /* figure out what time to run the job */
309 int argplen
= sizeof (argpbuf
) - 1;
315 /* guard against buffer overflow */
316 argplen
-= strlen(argv
[i
]) + 1;
320 strcat(argp
, argv
[i
]);
324 if ((file
= getenv("DATEMSK")) == 0 || file
[0] == '\0') {
325 tp
= localtime(&now
);
327 * Fix for 1047182 - we have to let yyparse
328 * check bounds on mday[] first, then fixup
329 * the leap year case.
333 mday
[1] = 28 + leap(at
.tm_year
);
335 if (at
.tm_mday
> mday
[at
.tm_mon
])
342 if (localtime(&when
)->tm_isdst
)
343 when
-= (timezone
-altzone
);
345 } else { /* DATEMSK is set */
346 if ((ct
= getdate(argpbuf
)) == NULL
)
347 atabort(errlist
[getdate_err
]);
353 if (when
< now
) /* time has already past */
356 tflen
= strlen(ATDIR
) + 1 + strlen(TMPFILE
) +
357 10 + 1; /* 10 for an INT_MAX pid */
358 tfname
= xmalloc(tflen
);
359 snprintf(tfname
, tflen
, "%s/%s%d", ATDIR
, TMPFILE
, getpid());
361 /* catch INT, HUP, TERM and QUIT signals */
362 if (signal(SIGINT
, catch) == SIG_IGN
)
363 signal(SIGINT
, SIG_IGN
);
364 if (signal(SIGHUP
, catch) == SIG_IGN
)
365 signal(SIGHUP
, SIG_IGN
);
366 if (signal(SIGQUIT
, catch) == SIG_IGN
)
367 signal(SIGQUIT
, SIG_IGN
);
368 if (signal(SIGTERM
, catch) == SIG_IGN
)
369 signal(SIGTERM
, SIG_IGN
);
370 if ((fd
= open(tfname
, O_CREAT
|O_EXCL
|O_WRONLY
, ATMODE
)) < 0)
372 if (chown(tfname
, user
, getgid()) == -1) {
379 sprintf(pname
, "%s", PROTO
);
380 sprintf(pname1
, "%s.%c", PROTO
, 'a'+jobtype
);
383 * Open the input file with the user's permissions.
385 if (jobfile
!= NULL
) {
386 if ((seteuid(user
) < 0) ||
387 (inputfile
= fopen(jobfile
, "r")) == NULL
) {
389 fprintf(stderr
, "at: %s: %s\n", jobfile
, errmsg(errno
));
397 copy(jobfile
, inputfile
, when
);
398 while (rename(tfname
, job
= mkjobname(when
)) == -1) {
400 if (++try > MAXTRYS
/ 10) {
406 if (audit_at_create(job
, 0))
409 cron_sendmsg(ADD
, login
, strrchr(job
, '/')+1, AT
);
411 fprintf(stderr
, gettext(WARNSHELL
), Shell
);
412 cftime(timebuf
, FORMAT
, &when
);
413 fprintf(stderr
, gettext("job %s at %s\n"),
414 strrchr(job
, '/')+1, timebuf
);
415 if (when
- MINUTE
< HOUR
)
416 fprintf(stderr
, gettext(
417 "at: this job may not be executed at the proper time.\n"));
429 for (i
= 0; i
< MAXTRYS
; i
++) {
430 sprintf(name
, "%s/%ld.%c", ATDIR
, t
, 'a'+jobtype
);
431 /* fix for 1099183, 1116833 - create file here, avoid race */
432 if ((fd
= open(name
, O_CREAT
| O_EXCL
, ATMODE
)) > 0) {
438 atabort("queue full");
454 fprintf(stderr
, "at: %s\n", gettext(msg
));
472 * add time structures logically
475 atime(struct tm
*a
, struct tm
*b
)
477 if ((a
->tm_sec
+= b
->tm_sec
) >= 60) {
478 b
->tm_min
+= a
->tm_sec
/ 60;
481 if ((a
->tm_min
+= b
->tm_min
) >= 60) {
482 b
->tm_hour
+= a
->tm_min
/ 60;
485 if ((a
->tm_hour
+= b
->tm_hour
) >= 24) {
486 b
->tm_mday
+= a
->tm_hour
/ 24;
489 a
->tm_year
+= b
->tm_year
;
490 if ((a
->tm_mon
+= b
->tm_mon
) >= 12) {
491 a
->tm_year
+= a
->tm_mon
/ 12;
494 a
->tm_mday
+= b
->tm_mday
;
495 mday
[1] = 28 + leap(a
->tm_year
);
496 while (a
->tm_mday
> mday
[a
->tm_mon
]) {
497 a
->tm_mday
-= mday
[a
->tm_mon
++];
498 if (a
->tm_mon
> 11) {
500 mday
[1] = 28 + leap(++a
->tm_year
);
509 return (isleap(year
+ TM_YEAR_BASE
));
513 * return time from time structure
516 gtime(struct tm
*tptr
)
521 {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
525 for (i
= 1970; i
!= tptr
->tm_year
+TM_YEAR_BASE
; i
++)
526 tv
+= (365 + isleap(i
));
528 * We call isleap since leap() adds
529 * 1900 onto any value passed
532 if (!leap(tptr
->tm_year
) && at
.tm_mday
== 29 && at
.tm_mon
== 1)
533 atabort("bad date - not a leap year");
535 if ((leap(tptr
->tm_year
)) && tptr
->tm_mon
>= 2)
538 for (i
= 0; i
< tptr
->tm_mon
; ++i
)
540 tv
+= tptr
->tm_mday
- 1;
541 tv
= 24 * tv
+ tptr
->tm_hour
;
542 tv
= 60 * tv
+ tptr
->tm_min
;
543 tv
= 60 * tv
+ tptr
->tm_sec
;
548 * Escape a string to be used inside the job shell script.
551 escapestr(const char *str
)
554 (void) putchar('\'');
555 while ((c
= *str
++) != '\0') {
559 (void) fputs("'\\''", stdout
); /* ' -> '\'' */
561 (void) putchar('\'');
565 * make job file from proto + stdin
568 copy(char *jobfile
, FILE *inputfile
, int when
)
573 char dirbuf
[PATH_MAX
+ 1];
578 extern char **environ
;
579 uid_t realusr
, effeusr
;
583 struct project prj
, *pprj
;
584 char pbuf
[PROJECT_BUFSZ
];
585 char pbuf2
[PROJECT_BUFSZ
];
590 * If the inputfile is from a tty, then turn on prompting, and
591 * put out a prompt now, instead of waiting for a lot of file
592 * activity to complete.
594 ttyinput
= isatty(fileno(inputfile
));
596 fputs("at> ", stderr
);
602 * Determine what shell we should use to run the job. If the user
603 * didn't explicitly request that their current shell be over-
604 * ridden (shflag or cshflag), then we use the current shell.
607 Shell
= shell
= "/bin/csh";
609 Shell
= shell
= "/bin/ksh";
612 Shell
= shell
= "/bin/sh";
614 } else if (((Shell
= val
= getenv("SHELL")) != NULL
) &&
617 if ((strstr(val
, "/sh") != NULL
) ||
618 (strstr(val
, "/ksh") != NULL
))
621 /* SHELL is NULL or unset, therefore use default */
622 Shell
= shell
= _PATH_BSHELL
;
626 printf(": %s job\n", jobtype
? "batch" : "at");
627 printf(": jobname: %.127s\n", (jobfile
== NULL
) ? "stdin" : jobfile
);
628 printf(": notify by mail: %s\n", (mflag
) ? "yes" : "no");
631 (void) printf(": project: %d\n", project
);
634 * Check if current user is a member of current project.
635 * This check is done here to avoid setproject() failure
636 * later when the job gets executed. If current user does
637 * not belong to current project, user's default project
638 * will be used instead. This is achieved by not specifying
639 * the project (": project: <project>\n") in the job file.
641 if ((user
= getuser(getuid())) == NULL
)
642 atabort(INVALIDUSER
);
643 project
= getprojid();
644 pprj
= getprojbyid(project
, &prj
, pbuf
, sizeof (pbuf
));
646 if (inproj(user
, pprj
->pj_name
, pbuf2
, sizeof (pbuf2
)))
647 (void) printf(": project: %d\n", project
);
651 for (ep
= environ
; *ep
; ep
++) {
652 if ((val
= strchr(*ep
, '=')) == NULL
)
655 (void) printf("export %s; %s=", *ep
, *ep
);
657 (void) putchar('\n');
660 if ((pfp
= fopen(pname1
, "r")) == NULL
&&
661 (pfp
= fopen(pname
, "r")) == NULL
)
662 atabort("no prototype");
664 * Put in a line to run the proper shell using the rest of
665 * the file as input. Note that 'exec'ing the shell will
666 * cause sh() to leave a /tmp/sh### file around. (1053807)
668 printf("%s << '...the rest of this file is shell input'\n", shell
);
671 while ((c
= getc(pfp
)) != EOF
) {
674 else switch (c
= getc(pfp
)) {
679 * Must obtain current working directory as the user
685 /* change euid for getcwd */
686 if (seteuid(realusr
) < 0) {
689 if (getcwd(dirbuf
, sizeof (dirbuf
)) == NULL
) {
691 "can't obtain current working directory");
693 /* change back afterwards */
694 if (seteuid(effeusr
) < 0) {
704 if (getrlimit(RLIMIT_FSIZE
, &rlp
) == 0) {
705 if (rlp
.rlim_cur
== RLIM_INFINITY
)
706 printf("ulimit unlimited\n");
708 printf("ulimit %lld\n",
713 * fix for 1113572 - use fputs() so that a
714 * newline isn't appended to the one returned
715 * with fgets(); 1099381 - prompt for input.
717 while (fgets(line
, LINE_MAX
, inputfile
) != NULL
) {
720 fputs("at> ", stderr
);
722 if (ttyinput
) /* clean up the final output */
723 fputs("<EOT>\n", stderr
);
726 printf(":%lu", when
);
737 /* remove jobs that are specified */
739 remove_jobs(int argc
, char **argv
, char *login
)
748 atabort("Invalid user.\n");
753 if (chdir(ATDIR
) == -1)
755 for (i
= 0; i
< argc
; i
++)
756 if (strchr(argv
[i
], '/') != NULL
) {
757 fprintf(stderr
, "at: %s: not a valid job-id\n",
759 } else if (stat(argv
[i
], &buf
)) {
760 fprintf(stderr
, "at: %s: ", argv
[i
]);
762 } else if ((user
!= buf
.st_uid
) &&
763 (!cron_admin(pw
->pw_name
))) {
764 fprintf(stderr
, "at: you don't own %s\n",
768 if (cron_admin(pw
->pw_name
)) {
769 login
= getuser((uid_t
)buf
.st_uid
);
774 atabort(INVALIDUSER
);
777 cron_sendmsg(DELETE
, login
, argv
[i
], AT
);
779 audit_at_delete(argv
[i
], ATDIR
, r
);
787 list_jobs(int argc
, char **argv
, int qflag
, int queue
)
792 char *patdir
, *atdir
, *ptr
;
795 struct stat buf
, st1
, st2
;
796 struct dirent
*dentry
;
798 unsigned int atdirlen
;
800 struct passwd
*pwd
, pwds
;
802 char job_file
[PATH_MAX
];
804 pwd
= getpwuid_r(user
, &pwds
, buf_pwd
, sizeof (buf_pwd
));
806 atabort("Invalid user.\n");
809 /* list jobs for user */
810 if (chdir(ATDIR
) == -1)
813 atdirlen
= strlen(ATDIR
);
814 atdir
= xmalloc(atdirlen
+ 1);
815 strcpy(atdir
, ATDIR
);
816 patdir
= strrchr(atdir
, '/');
819 /* list all jobs for a user */
820 if (stat(ATDIR
, &st1
) != 0 || stat(atdir
, &st2
) != 0)
821 atabort("Can not get status of spooling"
823 if ((dir
= opendir(ATDIR
)) == NULL
)
826 if ((dentry
= readdir(dir
)) == NULL
)
828 if ((dentry
->d_ino
== st1
.st_ino
) ||
829 (dentry
->d_ino
== st2
.st_ino
))
831 if ((r
= audit_cron_is_anc_name(dentry
->d_name
)) == 1)
833 if (stat(dentry
->d_name
, &buf
)) {
834 unlink(dentry
->d_name
);
835 audit_cron_delete_anc_file(dentry
->d_name
,
839 if ((!cron_admin(pwd
->pw_name
)) &&
840 (buf
.st_uid
!= user
))
842 ptr
= dentry
->d_name
;
843 if (((t
= num(&ptr
)) == 0) || (*ptr
!= '.'))
845 strcpy(job_file
, patdir
);
846 strcat(job_file
, dentry
->d_name
);
847 if (pflag
&& not_this_project(job_file
))
849 ascftime(timebuf
, FORMAT
, localtime(&t
));
850 if ((cron_admin(pwd
->pw_name
)) &&
851 ((pw
= getpwuid(buf
.st_uid
)) != NULL
)) {
852 if (!qflag
|| (qflag
&&
853 check_queue(ptr
, queue
)))
854 printf("user = %s\t%s\t%s\n",
855 pw
->pw_name
, dentry
->d_name
,
858 if (!qflag
|| (qflag
&&
859 check_queue(ptr
, queue
)))
861 dentry
->d_name
, timebuf
);
863 (void) closedir(dir
);
864 } else /* list particular jobs for user */
865 for (i
= 0; i
< argc
; i
++) {
867 strlcpy(job_file
, patdir
, PATH_MAX
);
868 strlcat(job_file
, ptr
, PATH_MAX
);
869 if (((t
= num(&ptr
)) == 0) || (*ptr
!= '.')) {
870 fprintf(stderr
, gettext(
871 "at: invalid job name %s\n"), argv
[i
]);
873 } else if (stat(argv
[i
], &buf
)) {
874 fprintf(stderr
, "at: %s: ", argv
[i
]);
877 } else if ((user
!= buf
.st_uid
) &&
878 (!cron_admin(pwd
->pw_name
))) {
879 fprintf(stderr
, gettext(
880 "at: you don't own %s\n"), argv
[i
]);
882 } else if (pflag
&& not_this_project(job_file
)) {
885 if (!qflag
|| (qflag
&&
886 check_queue(ptr
, queue
))) {
887 ascftime(timebuf
, FORMAT
,
889 printf("%s\t%s\n", argv
[i
], timebuf
);
897 * open the command file and read the project id line
898 * compare to the project number provided via -p on the command line
899 * return 0 if they match, 1 if they don't match or an error occurs.
901 #define SKIPCOUNT 3 /* lines to skip to get to project line in file */
904 not_this_project(char *filename
)
910 if ((fp
= fopen(filename
, "r")) == NULL
)
913 for (i
= 0; i
< SKIPCOUNT
; i
++)
914 fscanf(fp
, "%*[^\n]\n");
916 fscanf(fp
, ": project: %d\n", &sproj
);
919 return (sproj
== project
? 0 : 1);
923 check_queue(char *name
, int queue
)
925 if ((name
[strlen(name
) - 1] - 'a') == queue
)
941 * time in the following format (defined by the touch(1) spec):
942 * [[CC]YY]MMDDhhmm[.SS]
944 if ((p
= strchr(t
, '.')) != NULL
) {
945 if (strchr(p
+1, '.') != NULL
)
947 seconds
= atoi_for2(p
+1);
951 memset(&tm
, 0, sizeof (struct tm
));
953 tm
.tm_year
= localtime(&when
)->tm_year
;
956 case 12: /* CCYYMMDDhhmm */
957 century
= atoi_for2(t
);
960 case 10: /* YYMMDDhhmm */
961 tm
.tm_year
= atoi_for2(t
);
967 tm
.tm_year
+= (century
- 19) * 100;
969 case 8: /* MMDDhhmm */
970 tm
.tm_mon
= atoi_for2(t
) - 1;
972 tm
.tm_mday
= atoi_for2(t
);
974 tm
.tm_hour
= atoi_for2(t
);
976 tm
.tm_min
= atoi_for2(t
);
984 if ((when
= mktime(&tm
)) == -1)
987 when
-= (timezone
-altzone
);
996 value
= (*p
- '0') * 10 + *(p
+1) - '0';
997 if ((value
< 0) || (value
> 99))
1005 fprintf(stderr
, USAGE
);