1/*
2 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
3 * Use is subject to license terms.
4 * Copyright (c) 2016 by Delphix. All rights reserved.
5 */
6
7/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
8/*	  All Rights Reserved  	*/
9
10
11/*
12 * Copyright (c) 1983 Regents of the University of California.
13 * All rights reserved.  The Berkeley software License Agreement
14 * specifies the terms and conditions for redistribution.
15 */
16
17/*
18 *	synopsis: atrm [-f] [-i] [-a] [[job #] [user] ...]
19 *
20 *
21 *	Remove "at" jobs.
22 */
23
24#include <stdio.h>
25#include <pwd.h>
26#include <ctype.h>
27#include <sys/types.h>
28#include <dirent.h>
29#include <sys/file.h>
30#include <sys/stat.h>
31#include <errno.h>
32#include <unistd.h>
33#include <locale.h>
34#include <strings.h>
35#include <stdlib.h>
36#include <libintl.h>
37#include "cron.h"
38#include "getresponse.h"
39
40extern time_t	num();
41extern char	*errmsg();
42extern void	audit_at_delete(char *, char *, int);
43
44#define	SUPERUSER	0			/* is user super-user? */
45#define	CANTCD		"can't change directory to the at directory"
46#define	NOREADDIR	"can't read the at directory"
47
48uid_t user;					/* person requesting removal */
49int fflag = 0;					/* suppress announcements? */
50int iflag = 0;					/* run interactively? */
51
52char login[UNAMESIZE];
53char login_authchk[UNAMESIZE]; /* used for authorization checks */
54
55#define	INVALIDUSER	"you are not a valid user (no entry in /etc/passwd)"
56#define	NOTALLOWED	"you are not authorized to use at.  Sorry."
57#define	NAMETOOLONG	"login name too long"
58
59static void usage(void);
60static void atabortperror(char *msg);
61static void atabort(char *msg);
62static void atperror(char *msg);
63static void atperror2(char *msg, char *name);
64static void aterror(char *msg);
65static void powner(char *file);
66
67int	getjoblist(struct dirent ***, struct stat ***, int (*)());
68int	removentry(char *, struct stat *, uid_t);
69
70int
71main(int argc, char **argv)
72{
73	int i;				/* for loop index */
74	int numjobs;			/* # of jobs in spooling area */
75	int allflag = 0;		/* remove all jobs belonging to user? */
76	int jobexists;			/* does a requested job exist? */
77	char *pp;
78	struct dirent **namelist;	/* names of jobs in spooling area */
79	struct stat **statlist;
80	struct passwd *pwd;
81
82	/*
83	 * If job number, user name, or "-" is not specified, just print
84	 * usage info and exit.
85	 */
86	(void) setlocale(LC_ALL, "");
87	(void) textdomain(TEXT_DOMAIN);
88	if (argc < 2)
89		usage();
90
91	--argc; ++argv;
92
93	pp = getuser((user = getuid()));
94	if (pp == NULL)
95		atabort(INVALIDUSER);
96	if (strlcpy(login, pp, sizeof (login)) >= sizeof (login))
97		atabort(NAMETOOLONG);
98	if (strlcpy(login_authchk, pp, sizeof (login_authchk))
99	    >= sizeof (NAMETOOLONG))
100		atabort(INVALIDUSER);
101	if (!allowed(login, ATALLOW, ATDENY))
102		atabort(NOTALLOWED);
103
104	/*
105	 * Process command line flags.
106	 * Special case the "-" option so that others may be grouped.
107	 */
108	while (argc > 0 && **argv == '-') {
109		*(*argv)++;
110		while (**argv) {
111			switch (*(*argv)++) {
112
113			case 'a':	++allflag;
114					break;
115
116			case 'f':	++fflag;
117					break;
118
119			case 'i':	++iflag;
120					break;
121
122			default:	usage();
123			}
124		}
125		++argv; --argc;
126	}
127
128	/*
129	 * If all jobs are to be removed and extra command line arguments
130	 * are given, print usage info and exit.
131	 */
132	if (allflag && argc)
133		usage();
134
135	/*
136	 * If only certain jobs are to be removed and no job #'s or user
137	 * names are specified, print usage info and exit.
138	 */
139	if (!allflag && !argc)
140		usage();
141
142	/*
143	 * If interactive removal and quiet removal are requested, override
144	 * quiet removal and run interactively.
145	 */
146	if (iflag && fflag)
147		fflag = 0;
148
149
150	/*
151	 * Move to spooling directory and get a list of the files in the
152	 * spooling area.
153	 */
154	numjobs = getjoblist(&namelist, &statlist, strcmp);
155	/*
156	 * If all jobs belonging to the user are to be removed, compare
157	 * the user's id to the owner of the file. If they match, remove
158	 * the file. If the user is the super-user, don't bother comparing
159	 * the id's. After all files are removed, exit (status 0).
160	 */
161	if (allflag) {
162		for (i = 0; i < numjobs; ++i) {
163			if (cron_admin(login_authchk) ||
164			    user == statlist[i]->st_uid)
165				(void) removentry(namelist[i]->d_name,
166				    statlist[i], user);
167		}
168		exit(0);
169	}
170
171	/*
172	 * If only certain jobs are to be removed, interpret each command
173	 * line argument. A check is done to see if it is a user's name or
174	 * a job number (inode #). If it's a user's name, compare the argument
175	 * to the files owner. If it's a job number, compare the argument to
176	 * the file name. In either case, if a match occurs, try to
177	 * remove the file.
178	 */
179
180	while (argc--) {
181		jobexists = 0;
182		for (i = 0; i < numjobs; ++i) {
183
184			/* if the inode number is 0, this entry was removed */
185			if (statlist[i]->st_ino == 0)
186				continue;
187
188			/*
189			 * if argv is a username, compare their uid to
190			 * the uid of the owner of the file......
191			 */
192			if (pwd = getpwnam(*argv)) {
193				if (statlist[i]->st_uid != pwd->pw_uid)
194					continue;
195			/*
196			 * otherwise, we assume that the argv is a job # and
197			 * thus compare argv to the file name.
198			 */
199			} else {
200				if (strcmp(namelist[i]->d_name, *argv))
201					continue;
202			}
203			++jobexists;
204			/*
205			 * if the entry is ultimately removed, don't
206			 * try to remove it again later.
207			 */
208			if (removentry(namelist[i]->d_name, statlist[i],
209			    user)) {
210				statlist[i]->st_ino = 0;
211			}
212		}
213
214		/*
215		 * If a requested argument doesn't exist, print a message.
216		 */
217		if (!jobexists && !fflag) {
218			fprintf(stderr, "atrm: %s: no such job number\n",
219			    *argv);
220		}
221		++argv;
222	}
223	return (0);
224}
225
226/*
227 * Print usage info and exit.
228 */
229static void
230usage(void)
231{
232	fprintf(stderr, "usage: atrm [-f] [-i] [-a] [[job #] [user] ...]\n");
233	exit(1);
234}
235
236
237/*
238 * Remove an entry from the queue. The access of the file is checked for
239 * write permission (since all jobs are mode 644). If access is granted,
240 * unlink the file. If the fflag (suppress announcements) is not set,
241 * print the job number that we are removing and the result of the access
242 * check (either "permission denied" or "removed"). If we are running
243 * interactively (iflag), prompt the user before we unlink the file. If
244 * the super-user is removing jobs, inform them who owns each file before
245 * it is removed.  Return TRUE if file removed, else FALSE.
246 */
247int
248removentry(char *filename, struct stat *statptr, uid_t user)
249{
250	struct passwd *pwd;
251	char *pp;
252	int r;
253
254	if (init_yes() < 0) {
255		(void) fprintf(stderr, gettext(ERR_MSG_INIT_YES),
256		    strerror(errno));
257		exit(1);
258	}
259
260	if (!fflag)
261		printf("%s: ", filename);
262
263	if (user != statptr->st_uid && !cron_admin(login_authchk)) {
264
265		if (!fflag) {
266			printf("permission denied\n");
267		}
268		return (0);
269
270	} else {
271		if (iflag) {
272			if (cron_admin(login_authchk)) {
273				printf("\t(owned by ");
274				powner(filename);
275				printf(") ");
276			}
277			printf(gettext("remove it? "));
278			if (yes() == 0)
279				return (0);
280		}
281
282		if (cron_admin(login_authchk)) {
283			pp = getuser((uid_t)statptr->st_uid);
284			if (pp == NULL)
285				atabort(INVALIDUSER);
286			if (strlcpy(login, pp, sizeof (login)) >=
287			    sizeof (login))
288				atabort(NAMETOOLONG);
289		}
290		cron_sendmsg(DELETE, login, filename, AT);
291		if ((r = unlink(filename)) < 0) {
292			if (!fflag) {
293				fputs("could not remove\n", stdout);
294				(void) fprintf(stderr, "atrm: %s: %s\n",
295				    filename, errmsg(errno));
296			}
297			audit_at_delete(filename, NULL, r);
298			return (0);
299		}
300		audit_at_delete(filename, NULL, r);
301		if (!fflag && !iflag)
302			printf("removed\n");
303		return (1);
304	}
305}
306
307/*
308 * Print the owner of the job. This is the owner of the spoolfile.
309 * If we run into trouble getting the name, we'll just print "???".
310 */
311static void
312powner(char *file)
313{
314	struct stat statb;
315	char *getname();
316
317	if (stat(file, &statb) < 0) {
318		printf("%s", "???");
319		(void) fprintf(stderr, "atrm: Couldn't stat spoolfile %s: %s\n",
320		    file, errmsg(errno));
321		return;
322	}
323
324	printf("%s", getname(statb.st_uid));
325}
326
327
328int
329getjoblist(struct dirent ***namelistp, struct stat ***statlistp,
330    int (*sortfunc)())
331{
332	int numjobs;
333	struct dirent **namelist;
334	int i;
335	struct stat *statptr;	/* pointer to file stat structure */
336	struct stat **statlist;
337	extern int filewanted();	/* should a file be listed in queue? */
338	if (chdir(ATDIR) < 0)
339		atabortperror(CANTCD);
340
341	/*
342	 * Get a list of the files in the spooling area.
343	 */
344	if ((numjobs = scandir(".", namelistp, filewanted, sortfunc)) < 0)
345		atabortperror(NOREADDIR);
346
347	if ((statlist =
348	    (struct stat **)malloc(numjobs * sizeof (struct stat ***)))
349	    == NULL)
350		atabort("Out of memory");
351
352	namelist = *namelistp;
353
354	/*
355	 * Build an array of pointers to the file stats for all jobs in
356	 * the spooling area.
357	 */
358	for (i = 0; i < numjobs; ++i) {
359		statptr = (struct stat *)malloc(sizeof (struct stat));
360		if (statptr == NULL)
361			atabort("Out of memory");
362		if (stat(namelist[i]->d_name, statptr) < 0) {
363			atperror2("Can't stat", namelist[i]->d_name);
364			continue;
365		}
366		statlist[i] = statptr;
367	}
368
369	*statlistp = statlist;
370	return (numjobs);
371}
372
373/*
374 * Get the full login name of a person using their user id.
375 */
376char *
377getname(uid_t uid)
378{
379	struct passwd *pwdinfo;		/* password info structure */
380
381
382	if ((pwdinfo = getpwuid(uid)) == 0)
383		return ("???");
384	return (pwdinfo->pw_name);
385}
386
387static void
388aterror(char *msg)
389{
390	fprintf(stderr, "atrm: %s\n", msg);
391}
392
393static void
394atperror(char *msg)
395{
396	fprintf(stderr, "atrm: %s: %s\n", msg, errmsg(errno));
397}
398
399static void
400atperror2(char *msg, char *name)
401{
402	fprintf(stderr, "atrm: %s %s: %s\n", msg, name, errmsg(errno));
403}
404
405static void
406atabort(char *msg)
407{
408	aterror(msg);
409	exit(1);
410}
411
412static void
413atabortperror(char *msg)
414{
415	atperror(msg);
416	exit(1);
417}
418