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