Home | History | Annotate | Download | only in common
      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