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