xref: /illumos-gate/usr/src/lib/libbsm/common/audit_cron.c (revision 3f1e69bef33050bee99ea1e9992af13fc467281f)
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 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <sys/types.h>
27 #include <sys/systeminfo.h>
28 #include <bsm/audit.h>
29 #include <bsm/libbsm.h>
30 #include <bsm/audit_uevents.h>
31 #include <bsm/audit_private.h>
32 #include <unistd.h>
33 #include <wait.h>
34 #include <fcntl.h>
35 #include <pwd.h>
36 #include <string.h>
37 #include <stdlib.h>
38 #include <errno.h>
39 #include <syslog.h>
40 #include <sys/stat.h>
41 #include <sys/socket.h>
42 #include <netinet/in.h>
43 #include <arpa/inet.h>
44 #include <libgen.h>
45 
46 #include <locale.h>
47 #include "generic.h"
48 
49 #define	F_AUID	"%u\n"
50 #define	F_SMASK	"%x\n"
51 #define	F_FMASK	"%x\n"
52 #define	F_PORT	"%lx\n"
53 #define	F_TYPE	"%x\n"
54 #define	F_MACH	"%x %x %x %x\n"
55 #define	F_ASID	"%u\n"
56 
57 #define	AU_SUFFIX	".au"
58 
59 #define	ANC_BAD_FILE	-1
60 #define	ANC_BAD_FORMAT	-2
61 
62 #define	AUDIT_CRON_TEXTBUF	256
63 static char	textbuf[AUDIT_CRON_TEXTBUF];
64 
65 int
66 audit_cron_mode()
67 {
68 	return (!cannot_audit(0));
69 }
70 
71 static void
72 audit_cron_syslog(const char *message) {
73 	static	int	is_open = 0;
74 
75 	if (!is_open) {
76 		openlog("Solaris_Audit", LOG_ODELAY, LOG_CRON);
77 		is_open = 1;
78 	}
79 	syslog(LOG_WARNING, "%s", message);
80 }
81 
82 /*
83  * audit_cron_getinfo returns the audit characteristics from the relevant
84  * auxiliary file, it if exists.  If not, it creates them from the crontab
85  * or atjob uid.
86  */
87 
88 static int
89 audit_cron_getinfo(char *fname, char *fname_aux, struct auditinfo_addr *info)
90 {
91 	int		fd;
92 	struct stat	st;
93 	au_mask_t mask;
94 	struct passwd	pwd;
95 	char		pwd_buff[1024];
96 	static char	*msg =
97 	    "Used defaults instead of ancilary audit file";
98 
99 	if ((fd = open(fname_aux, O_RDONLY)) == -1) {
100 		/* no syslog here; common case */
101 		goto make_it_up;
102 	}
103 	if (fstat(fd, &st) == -1) {
104 		/* no syslog here either; common case */
105 		goto delete_first;
106 	}
107 
108 	if (read(fd, textbuf, st.st_size) != st.st_size) {
109 		audit_cron_syslog(msg);
110 		goto delete_first;
111 	}
112 
113 	if (sscanf(textbuf,
114 	    F_AUID
115 	    F_SMASK
116 	    F_FMASK
117 	    F_PORT
118 	    F_TYPE
119 	    F_MACH
120 	    F_ASID,
121 	    &(info->ai_auid),
122 	    &(info->ai_mask.am_success),
123 	    &(info->ai_mask.am_failure),
124 	    &(info->ai_termid.at_port),
125 	    &(info->ai_termid.at_type),
126 	    &(info->ai_termid.at_addr[0]),
127 	    &(info->ai_termid.at_addr[1]),
128 	    &(info->ai_termid.at_addr[2]),
129 	    &(info->ai_termid.at_addr[3]),
130 	    &(info->ai_asid)) != 10) {
131 		audit_cron_syslog(msg);
132 		goto delete_first;
133 	}
134 	(void) close(fd);
135 	return (0);
136 
137 delete_first:
138 	(void) close(fd);
139 	if (unlink(fname_aux)) {
140 		if (errno != ENOENT)
141 			audit_cron_syslog(
142 			    "Failed to remove invalid ancilary audit file");
143 	}
144 	/* intentionally falls through */
145 
146 make_it_up:
147 	if (stat(fname, &st))
148 		return (-1);
149 
150 	/* port and IP are zero */
151 	(void) memset(&(info->ai_termid), 0, sizeof (au_tid_addr_t));
152 	info->ai_termid.at_type = AU_IPv4;
153 
154 	/* the caller is the child of cron which will run the job. */
155 	info->ai_asid = getpid();
156 
157 	info->ai_mask.am_success = 0;	/* cover error case */
158 	info->ai_mask.am_failure = 0;
159 
160 	if (strstr(fname, "crontabs") != NULL) {
161 		if (getpwnam_r(basename(fname), &pwd, pwd_buff,
162 		    sizeof (pwd_buff)) == NULL)
163 			return (-1); /* getpwnam_r sets errno */
164 	} else {
165 		if (getpwuid_r(st.st_uid, &pwd, pwd_buff, sizeof (pwd_buff)) ==
166 		    NULL)
167 			return (-1); /* getpwuid_r sets errno */
168 	}
169 
170 	info->ai_auid = pwd.pw_uid;
171 
172 	if (au_user_mask(pwd.pw_name, &mask)) {
173 		errno = EINVAL; /* pw_name lookup failed */
174 		return (-1);
175 	}
176 	info->ai_mask.am_success = mask.am_success;
177 	info->ai_mask.am_failure = mask.am_failure;
178 
179 	return (0);
180 }
181 
182 int
183 audit_cron_setinfo(char *fname, struct auditinfo_addr *info)
184 {
185 	int		fd, len, r;
186 	int		save_err;
187 
188 	r = chmod(fname, 0200);
189 	if (r == -1 && errno != ENOENT)
190 		return (-1);
191 
192 	if ((fd = open(fname, O_CREAT|O_WRONLY|O_TRUNC, 0200)) == -1)
193 		return (-1);
194 
195 	len = sprintf(textbuf,
196 	    F_AUID
197 	    F_SMASK
198 	    F_FMASK
199 	    F_PORT
200 	    F_TYPE
201 	    F_MACH
202 	    F_ASID,
203 	    info->ai_auid,
204 	    info->ai_mask.am_success,
205 	    info->ai_mask.am_failure,
206 	    info->ai_termid.at_port,
207 	    info->ai_termid.at_type,
208 	    info->ai_termid.at_addr[0],
209 	    info->ai_termid.at_addr[1],
210 	    info->ai_termid.at_addr[2],
211 	    info->ai_termid.at_addr[3],
212 	    info->ai_asid);
213 
214 	if (write(fd, textbuf, len) != len)
215 		goto audit_setinfo_clean;
216 
217 	if (fchmod(fd, 0400) == -1)
218 		goto audit_setinfo_clean;
219 
220 	(void) close(fd);
221 	return (0);
222 
223 audit_setinfo_clean:
224 	save_err = errno;
225 	(void) close(fd);
226 	(void) unlink(fname);
227 	errno = save_err;
228 	return (-1);
229 }
230 
231 char *
232 audit_cron_make_anc_name(char *fname)
233 {
234 	char *anc_name;
235 
236 	anc_name = (char *)malloc(strlen(fname) + strlen(AU_SUFFIX) + 1);
237 	if (anc_name == NULL)
238 		return (NULL);
239 
240 	(void) strcpy(anc_name, fname);
241 	(void) strcat(anc_name, AU_SUFFIX);
242 	return (anc_name);
243 }
244 
245 int
246 audit_cron_is_anc_name(char *name)
247 {
248 	int	pos;
249 
250 	pos = strlen(name) - strlen(AU_SUFFIX);
251 	if (pos <= 0)
252 		return (0);
253 
254 	if (strcmp(name + pos, AU_SUFFIX) == 0)
255 		return (1);
256 
257 	return (0);
258 }
259 
260 static void
261 audit_cron_session_failure(char *name, int type, char *err_str)
262 {
263 	const char	*mess;
264 
265 	if (type == 0)
266 		mess = dgettext(bsm_dom,
267 		"at-job session for user %s failed: ancillary file: %s");
268 	else
269 		mess = dgettext(bsm_dom,
270 		"crontab job session for user %s failed: ancillary file: %s");
271 
272 	(void) snprintf(textbuf, sizeof (textbuf), mess, name, err_str);
273 
274 	aug_save_event(AUE_cron_invoke);
275 	aug_save_sorf(4);
276 	aug_save_text(textbuf);
277 	(void) aug_audit();
278 }
279 
280 
281 int
282 audit_cron_session(
283 		char *name,
284 		char *path,
285 		uid_t uid,
286 		gid_t gid,
287 		char *at_jobname)
288 {
289 	struct auditinfo_addr	info;
290 	au_mask_t		mask;
291 	char			*anc_file, *fname;
292 	int			r = 0;
293 	char			full_path[PATH_MAX];
294 
295 	if (cannot_audit(0)) {
296 		return (0);
297 	}
298 
299 	/* get auditinfo from ancillary file */
300 	if (at_jobname == NULL) {
301 		/*
302 		 *	this is a cron-event, so we can get
303 		 *	filename from "name" arg
304 		 */
305 		fname = name;
306 		if (path != NULL) {
307 			if (strlen(path) + strlen(fname) + 2 > PATH_MAX) {
308 				errno = ENAMETOOLONG;
309 				r = -1;
310 			}
311 			(void) strcat(strcat(strcpy(full_path, path), "/"),
312 			    fname);
313 			fname = full_path;
314 		}
315 	} else {
316 		/* this is an at-event, use "at_jobname" */
317 		fname = at_jobname;
318 	}
319 
320 	if (r == 0) {
321 		anc_file = audit_cron_make_anc_name(fname);
322 		if (anc_file == NULL) {
323 			r = -1;
324 		} else {
325 			r = audit_cron_getinfo(fname, anc_file, &info);
326 		}
327 	}
328 
329 	if (r != 0) {
330 		char *err_str;
331 
332 		if (r == ANC_BAD_FORMAT)
333 			err_str = dgettext(bsm_dom, "bad format");
334 		else
335 			err_str = strerror(errno);
336 
337 		audit_cron_session_failure(name,
338 		    at_jobname == NULL,
339 		    err_str);
340 		if (anc_file != NULL)
341 			free(anc_file);
342 		return (r);
343 	}
344 
345 	free(anc_file);
346 	aug_init();
347 
348 	/* get current audit masks */
349 	if (au_user_mask(name, &mask) == 0) {
350 		info.ai_mask.am_success  |= mask.am_success;
351 		info.ai_mask.am_failure  |= mask.am_failure;
352 	}
353 
354 	/* save audit attributes for further use in current process */
355 	aug_save_auid(info.ai_auid);
356 	aug_save_asid(info.ai_asid);
357 	aug_save_tid_ex(info.ai_termid.at_port, info.ai_termid.at_addr,
358 	    info.ai_termid.at_type);
359 	aug_save_pid(getpid());
360 	aug_save_uid(uid);
361 	aug_save_gid(gid);
362 	aug_save_euid(uid);
363 	aug_save_egid(gid);
364 
365 	/* set mixed audit masks */
366 	return (setaudit_addr(&info, sizeof (info)));
367 }
368 
369 /*
370  * audit_cron_new_job - create audit record with an information
371  *			about new job started by cron.
372  *	args:
373  *	cmd  - command being run by cron daemon.
374  *	type - type of job (0 - at-job, 1 - crontab job).
375  *	event - not used. pointer to cron event structure.
376  */
377 /*ARGSUSED*/
378 void
379 audit_cron_new_job(char *cmd, int type, void *event)
380 {
381 	if (cannot_audit(0))
382 		return;
383 
384 	if (type == 0) {
385 		(void) snprintf(textbuf, sizeof (textbuf),
386 		    dgettext(bsm_dom, "at-job"));
387 	} else if (type == 1) {
388 		(void) snprintf(textbuf, sizeof (textbuf),
389 		    dgettext(bsm_dom, "batch-job"));
390 	} else if (type == 2) {
391 		(void) snprintf(textbuf, sizeof (textbuf),
392 		    dgettext(bsm_dom, "crontab-job"));
393 	} else if ((type > 2) && (type <= 25)) {	/* 25 from cron.h */
394 		(void) snprintf(textbuf, sizeof (textbuf),
395 		    dgettext(bsm_dom, "queue-job (%c)"), (type+'a'));
396 	} else {
397 		(void) snprintf(textbuf, sizeof (textbuf),
398 		    dgettext(bsm_dom, "unknown job type (%d)"), type);
399 	}
400 
401 	aug_save_event(AUE_cron_invoke);
402 	aug_save_sorf(0);
403 	aug_save_text(textbuf);
404 	aug_save_text1(cmd);
405 	(void) aug_audit();
406 }
407 
408 void
409 audit_cron_bad_user(char *name)
410 {
411 	if (cannot_audit(0))
412 		return;
413 
414 	(void) snprintf(textbuf, sizeof (textbuf),
415 	    dgettext(bsm_dom, "bad user %s"), name);
416 
417 	aug_save_event(AUE_cron_invoke);
418 	aug_save_sorf(2);
419 	aug_save_text(textbuf);
420 	(void) aug_audit();
421 }
422 
423 void
424 audit_cron_user_acct_expired(char *name)
425 {
426 	if (cannot_audit(0))
427 		return;
428 
429 	(void) snprintf(textbuf, sizeof (textbuf),
430 	    dgettext(bsm_dom,
431 	    "user %s account expired"), name);
432 
433 	aug_save_event(AUE_cron_invoke);
434 	aug_save_sorf(3);
435 	aug_save_text(textbuf);
436 	(void) aug_audit();
437 }
438 
439 int
440 audit_cron_create_anc_file(char *name, char *path, char *uname, uid_t uid)
441 {
442 	au_mask_t	msk;
443 	auditinfo_addr_t ai;
444 	int		pid;
445 	char		*anc_name;
446 	char		full_path[PATH_MAX];
447 
448 	if (cannot_audit(0))
449 		return (0);
450 
451 	if (name == NULL)
452 		return (0);
453 
454 	if (path != NULL) {
455 		if (strlen(path) + strlen(name) + 2 > PATH_MAX)
456 			return (-1);
457 		(void) strcat(strcat(strcpy(full_path, path), "/"), name);
458 		name = full_path;
459 	}
460 	anc_name = audit_cron_make_anc_name(name);
461 
462 	if (access(anc_name, F_OK) != 0) {
463 		if (au_user_mask(uname, &msk) != 0) {
464 			free(anc_name);
465 			return (-1);
466 		}
467 
468 		ai.ai_mask = msk;
469 		ai.ai_auid = uid;
470 		ai.ai_termid.at_port = 0;
471 		ai.ai_termid.at_type = AU_IPv4;
472 		ai.ai_termid.at_addr[0] = 0;
473 		ai.ai_termid.at_addr[1] = 0;
474 		ai.ai_termid.at_addr[2] = 0;
475 		ai.ai_termid.at_addr[3] = 0;
476 		/* generate new pid to use it as asid */
477 		pid = vfork();
478 		if (pid == -1) {
479 			free(anc_name);
480 			return (-1);
481 		}
482 		if (pid == 0)
483 			exit(0);
484 		else {
485 		/*
486 		 * we need to clear status of children for
487 		 * wait() call in "cron"
488 		 */
489 			int lock;
490 
491 			(void) waitpid(pid, &lock, 0);
492 		}
493 		ai.ai_asid = pid;
494 		if (audit_cron_setinfo(anc_name, &ai) != 0) {
495 			free(anc_name);
496 			return (-1);
497 		}
498 	}
499 
500 	free(anc_name);
501 	return (0);
502 }
503 
504 int
505 audit_cron_delete_anc_file(char *name, char *path)
506 {
507 	char	*anc_name;
508 	char	full_path[PATH_MAX];
509 	int	r;
510 
511 	if (name == NULL)
512 		return (0);
513 
514 	if (path != NULL) {
515 		if (strlen(path) + strlen(name) + 2 > PATH_MAX)
516 			return (-1);
517 		(void) strcat(strcat(strcpy(full_path, path), "/"), name);
518 		name = full_path;
519 	}
520 	anc_name = audit_cron_make_anc_name(name);
521 	r = unlink(anc_name);
522 	free(anc_name);
523 	return (r);
524 }
525