/* audit_logging.c -- * Copyright 2005-2008,2010,2011,2013 Red Hat Inc., Durham, North Carolina. * All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Authors: * Steve Grubb */ #include "config.h" #include #include #include #include #include #include #include // inet6 addrlen #include // gethostbyname #include // inet_ntop #include #include // PATH_MAX #include "libaudit.h" #include "private.h" #define TTY_PATH 32 #define MAX_USER (UT_NAMESIZE * 2) + 8 // NOTE: The kernel fills in pid, uid, and loginuid of sender. Therefore, // these routines do not need to send them. /* * resolve's the hostname - caller must pass a INET6_ADDRSTRLEN byte buffer * Returns string w/ numerical address, or "?" on failure */ static void _resolve_addr(char buf[], const char *host) { struct addrinfo *ai; struct addrinfo hints; int e; buf[0] = '?'; buf[1] = 0; /* Short circuit this lookup if NULL, or empty */ if (host == NULL || *host == 0) return; memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_ADDRCONFIG; hints.ai_socktype = SOCK_STREAM; e = getaddrinfo(host, NULL, &hints, &ai); if (e != 0) { audit_msg(LOG_ERR, "resolve_addr: cannot resolve hostname %s (%s)", host, gai_strerror(e)); return; } // What to do if more than 1 addr? inet_ntop(ai->ai_family, ai->ai_family == AF_INET ? (void *) &((struct sockaddr_in *)ai->ai_addr)->sin_addr : (void *) &((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr, buf, INET6_ADDRSTRLEN); freeaddrinfo(ai); } /* * This function checks a string to see if it needs encoding. It * return 1 if needed and 0 if not */ int audit_value_needs_encoding(const char *str, unsigned int size) { unsigned int i; if (str == NULL) return 0; for (i=0; i 0x7f because str[] is signed. if (str[i] == '"' || str[i] < 0x21 || str[i] == 0x7F) return 1; } return 0; } /* * This function does encoding of "untrusted" names just like the kernel */ char *audit_encode_value(char *final, const char *buf, unsigned int size) { unsigned int i; char *ptr = final; const char *hex = "0123456789ABCDEF"; if (final == NULL) return NULL; if (buf == NULL) { *final = 0; return final; } for (i=0; i>4]; /* Upper nibble */ *ptr++ = hex[buf[i] & 0x0F]; /* Lower nibble */ } *ptr = 0; return final; } char *audit_encode_nv_string(const char *name, const char *value, unsigned int vlen) { char *str; if (vlen == 0 && value) vlen = strlen(value); if (value && audit_value_needs_encoding(value, vlen)) { char *tmp = malloc(2*vlen + 1); if (tmp) { audit_encode_value(tmp, value, vlen); if (asprintf(&str, "%s=%s", name, tmp) < 0) str = NULL; free(tmp); } else str = NULL; } else if (asprintf(&str, "%s=\"%s\"", name, value ? value : "?") < 0) str = NULL; return str; } /* * Get the executable's name */ static char *_get_exename(char *exename, int size) { int res; char tmp[PATH_MAX+1]; /* get the name of the current executable */ if ((res = readlink("/proc/self/exe", tmp, PATH_MAX)) == -1) { strcpy(exename, "\"?\""); audit_msg(LOG_ERR, "get_exename: cannot determine executable"); } else { tmp[res] = '\0'; if (audit_value_needs_encoding(tmp, res)) return audit_encode_value(exename, tmp, res); snprintf(exename, size, "\"%s\"", tmp); } return exename; } /* * Get the command line name * NOTE: at the moment, this only escapes what the user sent */ static char *_get_commname(const char *comm, char *commname, unsigned int size) { unsigned int len; if (comm == NULL) { strcpy(commname, "\"?\""); return commname; } len = strlen(comm); if (audit_value_needs_encoding(comm, len)) audit_encode_value(commname, comm, len); else snprintf(commname, size, "\"%s\"", comm); return commname; } static int check_ttyname(const char *ttyn) { struct stat statbuf; if (lstat(ttyn, &statbuf) || !S_ISCHR(statbuf.st_mode) || (statbuf.st_nlink > 1 && strncmp(ttyn, "/dev/", 5))) { audit_msg(LOG_ERR, "FATAL: bad tty %s", ttyn); return 1; } return 0; } static const char *_get_tty(char *tname, int size) { int rc, i, found = 0; for (i=0; i<3 && !found; i++) { rc = ttyname_r(i, tname, size); if (rc == 0 && tname[0] != '\0') found = 1; } if (!found) return NULL; if (check_ttyname(tname)) return NULL; if (strncmp(tname, "/dev/", 5) == 0) return &tname[5]; return tname; } /* * This function will log a message to the audit system using a predefined * message format. This function should be used by all console apps that do * not manipulate accounts or groups. * * audit_fd - The fd returned by audit_open * type - type of message, ex: AUDIT_USER, AUDIT_USYS_CONFIG, AUDIT_USER_LOGIN * message - the message being sent * hostname - the hostname if known * addr - The network address of the user * tty - The tty of the user * result - 1 is "success" and 0 is "failed" * * It returns the sequence number which is > 0 on success or <= 0 on error. */ int audit_log_user_message(int audit_fd, int type, const char *message, const char *hostname, const char *addr, const char *tty, int result) { char buf[MAX_AUDIT_MESSAGE_LENGTH]; char addrbuf[INET6_ADDRSTRLEN]; static char exename[PATH_MAX*2]=""; char ttyname[TTY_PATH]; const char *success; int ret; if (audit_fd < 0) return 0; if (result) success = "success"; else success = "failed"; /* If hostname is empty string, make it NULL ptr */ if (hostname && *hostname == 0) hostname = NULL; addrbuf[0] = 0; if (addr == NULL || strlen(addr) == 0) _resolve_addr(addrbuf, hostname); else strncat(addrbuf, addr, sizeof(addrbuf)-1); if (exename[0] == 0) _get_exename(exename, sizeof(exename)); if (tty == NULL) tty = _get_tty(ttyname, TTY_PATH); else if (*tty == 0) tty = NULL; snprintf(buf, sizeof(buf), "%s exe=%s hostname=%s addr=%s terminal=%s res=%s", message, exename, hostname ? hostname : "?", addrbuf, tty ? tty : "?", success ); errno = 0; ret = audit_send_user_message( audit_fd, type, HIDE_IT, buf ); if ((ret < 1) && errno == 0) errno = ret; return ret; } /* * This function will log a message to the audit system using a predefined * message format. This function should be used by all console apps that do * not manipulate accounts or groups and are executing a script. An example * would be python or crond wanting to say what they are executing. * * audit_fd - The fd returned by audit_open * type - type of message, ex: AUDIT_USER, AUDIT_USYS_CONFIG, AUDIT_USER_LOGIN * message - the message being sent * comm - the program command line name * hostname - the hostname if known * addr - The network address of the user * tty - The tty of the user * result - 1 is "success" and 0 is "failed" * * It returns the sequence number which is > 0 on success or <= 0 on error. */ int audit_log_user_comm_message(int audit_fd, int type, const char *message, const char *comm, const char *hostname, const char *addr, const char *tty, int result) { char buf[MAX_AUDIT_MESSAGE_LENGTH]; char addrbuf[INET6_ADDRSTRLEN]; static char exename[PATH_MAX*2]=""; char commname[PATH_MAX*2]; char ttyname[TTY_PATH]; const char *success; int ret; if (audit_fd < 0) return 0; if (result) success = "success"; else success = "failed"; /* If hostname is empty string, make it NULL ptr */ if (hostname && *hostname == 0) hostname = NULL; addrbuf[0] = 0; if (addr == NULL || strlen(addr) == 0) _resolve_addr(addrbuf, hostname); else strncat(addrbuf, addr, sizeof(addrbuf)-1); if (exename[0] == 0) _get_exename(exename, sizeof(exename)); if (tty == NULL) tty = _get_tty(ttyname, TTY_PATH); else if (*tty == 0) tty = NULL; _get_commname(comm, commname, sizeof(commname)); snprintf(buf, sizeof(buf), "%s comm=%s exe=%s hostname=%s addr=%s terminal=%s res=%s", message, commname, exename, hostname ? hostname : "?", addrbuf, tty ? tty : "?", success ); errno = 0; ret = audit_send_user_message( audit_fd, type, HIDE_IT, buf ); if ((ret < 1) && errno == 0) errno = ret; return ret; } /* * This function will log a message to the audit system using a predefined * message format. It should be used for all account manipulation operations. * Parameter usage is as follows: * * audit_fd - The fd returned by audit_open * type - type of message: AUDIT_USER_CHAUTHTOK for changing any account * attributes. * pgname - program's name * op - operation. "adding user", "changing finger info", "deleting group" * name - user's account or group name. If not available use NULL. * id - uid or gid that the operation is being performed on. This is used * only when user is NULL. * host - The hostname if known * addr - The network address of the user * tty - The tty of the user * result - 1 is "success" and 0 is "failed" * * It returns the sequence number which is > 0 on success or <= 0 on error. */ int audit_log_acct_message(int audit_fd, int type, const char *pgname, const char *op, const char *name, unsigned int id, const char *host, const char *addr, const char *tty, int result) { const char *success; char buf[MAX_AUDIT_MESSAGE_LENGTH]; char addrbuf[INET6_ADDRSTRLEN]; static char exename[PATH_MAX*2] = ""; char ttyname[TTY_PATH]; int ret; if (audit_fd < 0) return 0; if (result) success = "success"; else success = "failed"; /* If hostname is empty string, make it NULL ptr */ if (host && *host == 0) host = NULL; addrbuf[0] = 0; if (addr == NULL || strlen(addr) == 0) _resolve_addr(addrbuf, host); else strncat(addrbuf, addr, sizeof(addrbuf)-1); if (pgname == NULL) { if (exename[0] == 0) _get_exename(exename, sizeof(exename)); } else if (pgname[0] != '"') snprintf(exename, sizeof(exename), "\"%s\"", pgname); else snprintf(exename, sizeof(exename), "%s", pgname); if (tty == NULL) tty = _get_tty(ttyname, TTY_PATH); else if (*tty == 0) tty = NULL; if (name && id == -1) { char user[MAX_USER]; const char *format; size_t len; user[0] = 0; strncat(user, name, MAX_USER-1); len = strnlen(user, UT_NAMESIZE); user[len] = 0; if (audit_value_needs_encoding(name, len)) { audit_encode_value(user, name, len); format = "op=%s acct=%s exe=%s hostname=%s addr=%s terminal=%s res=%s"; } else format = "op=%s acct=\"%s\" exe=%s hostname=%s addr=%s terminal=%s res=%s"; snprintf(buf, sizeof(buf), format, op, user, exename, host ? host : "?", addrbuf, tty ? tty : "?", success ); } else snprintf(buf, sizeof(buf), "op=%s id=%u exe=%s hostname=%s addr=%s terminal=%s res=%s", op, id, exename, host ? host : "?", addrbuf, tty ? tty : "?", success ); errno = 0; ret = audit_send_user_message(audit_fd, type, REAL_ERR, buf); if ((ret < 1) && errno == 0) errno = ret; return ret; } /* * This function will log a message to the audit system using a predefined * message format. This function should be used by all apps that are SE Linux * object managers. * * audit_fd - The fd returned by audit_open * type - type of message, ex: AUDIT_USER, AUDIT_USYS_CONFIG, AUDIT_USER_LOGIN * message - the message being sent * hostname - the hostname if known * addr - The network address of the user * tty - The tty of the user * uid - The auid of the person related to the avc message * * It returns the sequence number which is > 0 on success or <= 0 on error. */ int audit_log_user_avc_message(int audit_fd, int type, const char *message, const char *hostname, const char *addr, const char *tty, uid_t uid) { char buf[MAX_AUDIT_MESSAGE_LENGTH]; char addrbuf[INET6_ADDRSTRLEN]; static char exename[PATH_MAX*2] = ""; char ttyname[TTY_PATH]; int retval; if (audit_fd < 0) return 0; /* If hostname is empty string, make it NULL ptr */ if (hostname && *hostname == 0) hostname = NULL; addrbuf[0] = 0; if (addr == NULL || strlen(addr) == 0) _resolve_addr(addrbuf, hostname); else strncat(addrbuf, addr, sizeof(addrbuf)-1); if (exename[0] == 0) _get_exename(exename, sizeof(exename)); if (tty == NULL) tty = _get_tty(ttyname, TTY_PATH); else if (*tty == 0) tty = NULL; snprintf(buf, sizeof(buf), "%s exe=%s sauid=%d hostname=%s addr=%s terminal=%s", message, exename, uid, hostname ? hostname : "?", addrbuf, tty ? tty : "?" ); errno = 0; retval = audit_send_user_message( audit_fd, type, REAL_ERR, buf ); if (retval == -EPERM && getuid() != 0) { syslog(LOG_ERR, "Can't send to audit system: %s %s", audit_msg_type_to_name(type), buf); return 0; } if ((retval < 1) && errno == 0) errno = retval; return retval; } /* * This function will log a message to the audit system using a predefined * message format. It should be used for all SE linux user and role * manipulation operations. * Parameter usage is as follows: * * type - type of message: AUDIT_ROLE_ASSIGN/REMOVE for changing any SE Linux * user or role attributes. * pgname - program's name * op - operation. "adding-user", "adding-role", "deleting-user", "deleting-role" * name - user's account. If not available use NULL. * id - uid that the operation is being performed on. This is used * only when name is NULL. * new_seuser - the new seuser that the login user is getting * new_role - the new_role that the login user is getting * new_range - the new mls range that the login user is getting * old_seuser - the old seuser that the login usr had * old_role - the old role that the login user had * old_range - the old mls range that the login usr had * host - The hostname if known * addr - The network address of the user * tty - The tty of the user * result - 1 is "success" and 0 is "failed" * * It returns the sequence number which is > 0 on success or <= 0 on error. */ int audit_log_semanage_message(int audit_fd, int type, const char *pgname, const char *op, const char *name, unsigned int id, const char *new_seuser, const char *new_role, const char *new_range, const char *old_seuser, const char *old_role, const char *old_range, const char *host, const char *addr, const char *tty, int result) { const char *success; char buf[MAX_AUDIT_MESSAGE_LENGTH]; char addrbuf[INET6_ADDRSTRLEN]; static char exename[PATH_MAX*2] = ""; char ttyname[TTY_PATH]; int ret; if (audit_fd < 0) return 0; if (result) success = "success"; else success = "failed"; /* If hostname is empty string, make it NULL ptr */ if (host && *host == 0) host = NULL; addrbuf[0] = 0; if (addr == NULL || strlen(addr) == 0) _resolve_addr(addrbuf, host); else strncat(addrbuf, addr, sizeof(addrbuf)-1); if (pgname == NULL || strlen(pgname) == 0) { if (exename[0] == 0) _get_exename(exename, sizeof(exename)); pgname = exename; } if (tty == NULL || strlen(tty) == 0) tty = _get_tty(ttyname, TTY_PATH); else if (*tty == 0) tty = NULL; if (name && strlen(name) > 0) { size_t len; const char *format; char user[MAX_USER]; user[0] = 0; strncat(user, name, MAX_USER-1); len = strnlen(user, UT_NAMESIZE); user[len] = 0; if (audit_value_needs_encoding(name, len)) { audit_encode_value(user, name, len); format = "op=%s acct=%s old-seuser=%s old-role=%s old-range=%s new-seuser=%s new-role=%s new-range=%s exe=%s hostname=%s addr=%s terminal=%s res=%s"; } else format = "op=%s acct=\"%s\" old-seuser=%s old-role=%s old-range=%s new-seuser=%s new-role=%s new-range=%s exe=%s hostname=%s addr=%s terminal=%s res=%s"; snprintf(buf, sizeof(buf), format, op, user, old_seuser && strlen(old_seuser) ? old_seuser : "?", old_role && strlen(old_role) ? old_role : "?", old_range && strlen(old_range) ? old_range : "?", new_seuser && strlen(new_seuser) ? new_seuser : "?", new_role && strlen(new_role) ? new_role : "?", new_range && strlen(new_range) ? new_range : "?", pgname, host && strlen(host) ? host : "?", addrbuf, tty && strlen(tty) ? tty : "?", success ); } else snprintf(buf, sizeof(buf), "op=%s id=%u old-seuser=%s old-role=%s old-range=%s new-seuser=%s new-role=%s new-range=%s exe=%s hostname=%s addr=%s terminal=%s res=%s", op, id, old_seuser && strlen(old_seuser) ? old_seuser : "?", old_role && strlen(old_role) ? old_role : "?", old_range && strlen(old_range) ? old_range : "?", new_seuser && strlen(new_seuser) ? new_seuser : "?", new_role && strlen(new_role) ? new_role : "?", new_range && strlen(new_range) ? new_range : "?", pgname, host && strlen(host) ? host : "?", addrbuf, tty && strlen(tty) ? tty : "?", success ); errno = 0; ret = audit_send_user_message(audit_fd, type, REAL_ERR, buf); if ((ret < 1) && errno == 0) errno = ret; return ret; } /* * This function will log a message to the audit system using a predefined * message format. This function should be used by all console apps that do * not manipulate accounts or groups. * * audit_fd - The fd returned by audit_open * type - type of message, ex: AUDIT_USER_CMD * command - the command line being logged * tty - The tty of the user * result - 1 is "success" and 0 is "failed" * * It returns the sequence number which is > 0 on success or <= 0 on error. */ int audit_log_user_command(int audit_fd, int type, const char *command, const char *tty, int result) { char *p; char buf[MAX_AUDIT_MESSAGE_LENGTH]; char commname[PATH_MAX*2]; char cwdname[PATH_MAX*2]; char ttyname[TTY_PATH]; char format[64]; const char *success; char *cmd; int ret, cwdenc=0, cmdenc=0; unsigned int len; if (audit_fd < 0) return 0; if (result) success = "success"; else success = "failed"; if (tty == NULL) tty = _get_tty(ttyname, TTY_PATH); else if (*tty == 0) tty = NULL; /* Trim leading spaces */ while (*command == ' ') command++; cmd = strdup(command); if (cmd == NULL) return -1; // We borrow the commname buffer if (getcwd(commname, PATH_MAX) == NULL) strcpy(commname, "?"); len = strlen(commname); if (audit_value_needs_encoding(commname, len)) { audit_encode_value(cwdname, commname, len); cwdenc = 1; } else strcpy(cwdname, commname); len = strlen(cmd); // Trim the trailing carriage return and spaces while (len && (cmd[len-1] == 0x0A || cmd[len-1] == ' ')) { cmd[len-1] = 0; len--; } if (len >= PATH_MAX) { cmd[PATH_MAX] = 0; len = PATH_MAX-1; } if (audit_value_needs_encoding(cmd, len)) { audit_encode_value(commname, cmd, len); cmdenc = 1; } if (cmdenc == 0) strcpy(commname, cmd); free(cmd); // Make the format string if (cwdenc) p=stpcpy(format, "cwd=%s "); else p=stpcpy(format, "cwd=\"%s\" "); if (cmdenc) p = stpcpy(p, "cmd=%s "); else p = stpcpy(p, "cmd=\"%s\" "); strcpy(p, "terminal=%s res=%s"); // now use the format string to make the event snprintf(buf, sizeof(buf), format, cwdname, commname, tty ? tty : "?", success ); errno = 0; ret = audit_send_user_message( audit_fd, type, HIDE_IT, buf ); if ((ret < 1) && errno == 0) errno = ret; return ret; }