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