/* * This function attempts to revert the relabeling done to the tty. * fd - referencing the opened ttyn * ttyn - name of tty to restore * * Returns zero on success, non-zero otherwise */ int selinux_restore_tty(void) { int retval = 0; security_context_t chk_tty_context = NULL; debug_decl(selinux_restore_tty, SUDO_DEBUG_SELINUX) if (se_state.ttyfd == -1 || se_state.new_tty_context == NULL) goto skip_relabel; /* Verify that the tty still has the context set by sudo. */ if ((retval = fgetfilecon(se_state.ttyfd, &chk_tty_context)) < 0) { sudo_warn(U_("unable to fgetfilecon %s"), se_state.ttyn); goto skip_relabel; } if ((retval = strcmp(chk_tty_context, se_state.new_tty_context))) { sudo_warnx(U_("%s changed labels"), se_state.ttyn); goto skip_relabel; } if ((retval = fsetfilecon(se_state.ttyfd, se_state.tty_context)) < 0) sudo_warn(U_("unable to restore context for %s"), se_state.ttyn); skip_relabel: if (se_state.ttyfd != -1) { close(se_state.ttyfd); se_state.ttyfd = -1; } if (chk_tty_context != NULL) { freecon(chk_tty_context); chk_tty_context = NULL; } debug_return_int(retval); }
/* * Set the exec and tty contexts in preparation for fork/exec. * Must run as root, before the uid change. * If ptyfd is not -1, it indicates we are running * in a pty and do not need to reset std{in,out,err}. * Returns 0 on success and -1 on failure. */ int selinux_setup(const char *role, const char *type, const char *ttyn, int ptyfd) { int ret = -1; debug_decl(selinux_setup, SUDO_DEBUG_SELINUX) /* Store the caller's SID in old_context. */ if (getprevcon(&se_state.old_context)) { sudo_warn(U_("failed to get old_context")); goto done; } se_state.enforcing = security_getenforce(); if (se_state.enforcing < 0) { sudo_warn(U_("unable to determine enforcing mode.")); goto done; } #ifdef DEBUG sudo_warnx("your old context was %s", se_state.old_context); #endif se_state.new_context = get_exec_context(se_state.old_context, role, type); if (!se_state.new_context) { #ifdef HAVE_LINUX_AUDIT audit_role_change(se_state.old_context, "?", se_state.ttyn, 0); #endif goto done; } if (relabel_tty(ttyn, ptyfd) < 0) { sudo_warn(U_("unable to set tty context to %s"), se_state.new_context); goto done; } #ifdef DEBUG if (se_state.ttyfd != -1) { sudo_warnx("your old tty context is %s", se_state.tty_context); sudo_warnx("your new tty context is %s", se_state.new_tty_context); } #endif #ifdef HAVE_LINUX_AUDIT audit_role_change(se_state.old_context, se_state.new_context, se_state.ttyn, 1); #endif ret = 0; done: debug_return_int(ret); }
void selinux_execve(const char *path, char *const argv[], char *const envp[], int noexec) { char **nargv; const char *sesh; int argc, serrno; debug_decl(selinux_execve, SUDO_DEBUG_SELINUX) sesh = sudo_conf_sesh_path(); if (sesh == NULL) { sudo_warnx("internal error: sesh path not set"); errno = EINVAL; debug_return; } if (setexeccon(se_state.new_context)) { sudo_warn(U_("unable to set exec context to %s"), se_state.new_context); if (se_state.enforcing) debug_return; } #ifdef HAVE_SETKEYCREATECON if (setkeycreatecon(se_state.new_context)) { sudo_warn(U_("unable to set key creation context to %s"), se_state.new_context); if (se_state.enforcing) debug_return; } #endif /* HAVE_SETKEYCREATECON */ /* * Build new argv with sesh as argv[0]. * If argv[0] ends in -noexec, sesh will disable execute * for the command it runs. */ for (argc = 0; argv[argc] != NULL; argc++) continue; nargv = sudo_emallocarray(argc + 2, sizeof(char *)); if (noexec) nargv[0] = *argv[0] == '-' ? "-sesh-noexec" : "sesh-noexec"; else nargv[0] = *argv[0] == '-' ? "-sesh" : "sesh"; nargv[1] = (char *)path; memcpy(&nargv[2], &argv[1], argc * sizeof(char *)); /* copies NULL */ /* sesh will handle noexec for us. */ sudo_execve(sesh, nargv, envp, false); serrno = errno; free(nargv); errno = serrno; debug_return; }
static int audit_role_change(const security_context_t old_context, const security_context_t new_context, const char *ttyn, int result) { int au_fd, rc = -1; char *message; debug_decl(audit_role_change, SUDO_DEBUG_SELINUX) au_fd = audit_open(); if (au_fd == -1) { /* Kernel may not have audit support. */ if (errno != EINVAL && errno != EPROTONOSUPPORT && errno != EAFNOSUPPORT ) sudo_fatal(U_("unable to open audit system")); } else { /* audit role change using the same format as newrole(1) */ rc = asprintf(&message, "newrole: old-context=%s new-context=%s", old_context, new_context); if (rc == -1) sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); rc = audit_log_user_message(au_fd, AUDIT_USER_ROLE_CHANGE, message, NULL, NULL, ttyn, result); if (rc <= 0) sudo_warn(U_("unable to send audit message")); free(message); close(au_fd); } debug_return_int(rc); }
/* * Unlimit the number of processes since Linux's setuid() will * apply resource limits when changing uid and return EAGAIN if * nproc would be exceeded by the uid switch. */ static void unlimit_nproc(void) { #ifdef __linux__ struct rlimit rl; debug_decl(unlimit_nproc, SUDOERS_DEBUG_UTIL) if (getrlimit(RLIMIT_NPROC, &nproclimit) != 0) sudo_warn("getrlimit"); rl.rlim_cur = rl.rlim_max = RLIM_INFINITY; if (setrlimit(RLIMIT_NPROC, &rl) != 0) { rl.rlim_cur = rl.rlim_max = nproclimit.rlim_max; if (setrlimit(RLIMIT_NPROC, &rl) != 0) sudo_warn("setrlimit"); } debug_return; #endif /* __linux__ */ }
int linux_audit_command(char *argv[], int result) { int au_fd, rc = -1; char *command, *cp, **av; size_t size, n; debug_decl(linux_audit_command, SUDOERS_DEBUG_AUDIT) /* Don't return an error if auditing is not configured. */ if ((au_fd = linux_audit_open()) < 0) debug_return_int(au_fd == AUDIT_NOT_CONFIGURED ? 0 : -1); /* Convert argv to a flat string. */ for (size = 0, av = argv; *av != NULL; av++) size += strlen(*av) + 1; command = cp = sudo_emalloc(size); for (av = argv; *av != NULL; av++) { n = strlcpy(cp, *av, size - (cp - command)); if (n >= size - (cp - command)) { sudo_warnx(U_("internal error, %s overflow"), __func__); goto done; } cp += n; *cp++ = ' '; } *--cp = '\0'; /* Log command, ignoring ECONNREFUSED on error. */ if (audit_log_user_command(au_fd, AUDIT_USER_CMD, command, NULL, result) <= 0) { if (errno != ECONNREFUSED) { sudo_warn(U_("unable to send audit message")); goto done; } } rc = 0; done: sudo_efree(command); debug_return_int(rc); }
/* * Open audit connection if possible. * Returns audit fd on success and -1 on failure. */ static int linux_audit_open(void) { static int au_fd = -1; debug_decl(linux_audit_open, SUDOERS_DEBUG_AUDIT) if (au_fd != -1) debug_return_int(au_fd); au_fd = audit_open(); if (au_fd == -1) { /* Kernel may not have audit support. */ if (errno == EINVAL || errno == EPROTONOSUPPORT || errno == EAFNOSUPPORT) au_fd = AUDIT_NOT_CONFIGURED; else sudo_warn(U_("unable to open audit system")); } else { (void)fcntl(au_fd, F_SETFD, FD_CLOEXEC); } debug_return_int(au_fd); }
/* * Exit codes defined in sudo_exec.h: * SESH_SUCCESS (0) ... successful operation * SESH_ERR_FAILURE (1) ... unspecified error * SESH_ERR_INVALID (30) ... invalid -e arg value * SESH_ERR_BAD_PATHS (31) ... odd number of paths * SESH_ERR_NO_FILES (32) ... copy error, no files copied * SESH_ERR_SOME_FILES (33) ... copy error, no files copied */ int main(int argc, char *argv[], char *envp[]) { int ret; debug_decl(main, SUDO_DEBUG_MAIN) initprogname(argc > 0 ? argv[0] : "sesh"); setlocale(LC_ALL, ""); bindtextdomain(PACKAGE_NAME, LOCALEDIR); textdomain(PACKAGE_NAME); if (argc < 2) sudo_fatalx(U_("requires at least one argument")); /* Read sudo.conf and initialize the debug subsystem. */ if (sudo_conf_read(NULL, SUDO_CONF_DEBUG) == -1) exit(EXIT_FAILURE); sudo_debug_register(getprogname(), NULL, NULL, sudo_conf_debug_files(getprogname())); if (strcmp(argv[1], "-e") == 0) { ret = sesh_sudoedit(argc, argv); } else { bool login_shell, noexec = false; char *cp, *cmnd; int fd = -1; /* If the first char of argv[0] is '-', we are running a login shell. */ login_shell = argv[0][0] == '-'; /* If argv[0] ends in -noexec, pass the flag to sudo_execve() */ if ((cp = strrchr(argv[0], '-')) != NULL && cp != argv[0]) noexec = strcmp(cp, "-noexec") == 0; /* If argv[1] is --execfd=%d, extract the fd to exec with. */ if (strncmp(argv[1], "--execfd=", 9) == 0) { const char *errstr; cp = argv[1] + 9; fd = strtonum(cp, 0, INT_MAX, &errstr); if (errstr != NULL) sudo_fatalx(U_("invalid file descriptor number: %s"), cp); argv++; argc--; } /* Shift argv and make a copy of the command to execute. */ argv++; argc--; if ((cmnd = strdup(argv[0])) == NULL) sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); /* If invoked as a login shell, modify argv[0] accordingly. */ if (login_shell) { if ((cp = strrchr(argv[0], '/')) == NULL) sudo_fatal(U_("unable to run %s as a login shell"), argv[0]); *cp = '-'; argv[0] = cp; } sudo_execve(fd, cmnd, argv, envp, noexec); sudo_warn(U_("unable to execute %s"), cmnd); ret = SESH_ERR_FAILURE; } sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, ret); _exit(ret); }
static int sesh_sudoedit(int argc, char *argv[]) { int i, oflags_dst, post, ret = SESH_ERR_FAILURE; int fd_src = -1, fd_dst = -1, follow = 0; ssize_t nread, nwritten; struct stat sb; struct timespec times[2]; char buf[BUFSIZ]; debug_decl(sesh_sudoedit, SUDO_DEBUG_EDIT) /* Check for -h flag (don't follow links). */ if (strcmp(argv[2], "-h") == 0) { argv++; argc--; follow = O_NOFOLLOW; } if (argc < 3) debug_return_int(SESH_ERR_FAILURE); /* * We need to know whether we are performing the copy operation * before or after the editing. Without this we would not know * which files are temporary and which are the originals. * post = 0 ... before * post = 1 ... after */ if (strcmp(argv[2], "0") == 0) post = 0; else if (strcmp(argv[2], "1") == 0) post = 1; else /* invalid value */ debug_return_int(SESH_ERR_INVALID); /* Align argv & argc to the beggining of the file list. */ argv += 3; argc -= 3; /* no files specified, nothing to do */ if (argc == 0) debug_return_int(SESH_SUCCESS); /* odd number of paths specified */ if (argc & 1) debug_return_int(SESH_ERR_BAD_PATHS); /* * Use O_EXCL if we are not in the post editing stage * so that it's ensured that the temporary files are * created by us and that we are not opening any symlinks. */ oflags_dst = O_WRONLY|O_TRUNC|O_CREAT|(post ? follow : O_EXCL); for (i = 0; i < argc - 1; i += 2) { const char *path_src = argv[i]; const char *path_dst = argv[i + 1]; /* * Try to open the source file for reading. If it * doesn't exist, that's OK, we'll create an empty * destination file. */ if ((fd_src = open(path_src, O_RDONLY|follow, 0600)) < 0) { if (errno != ENOENT) { sudo_warn("%s", path_src); if (post) { ret = SESH_ERR_SOME_FILES; goto nocleanup; } else goto cleanup_0; } } if ((fd_dst = open(path_dst, oflags_dst, post ? 0644 : 0600)) < 0) { /* error - cleanup */ sudo_warn("%s", path_dst); if (post) { ret = SESH_ERR_SOME_FILES; goto nocleanup; } else goto cleanup_0; } if (fd_src != -1) { while ((nread = read(fd_src, buf, sizeof(buf))) > 0) { if ((nwritten = write(fd_dst, buf, nread)) != nread) { sudo_warn("%s", path_src); if (post) { ret = SESH_ERR_SOME_FILES; goto nocleanup; } else goto cleanup_0; } } } if (fd_dst != -1) { if (!post) { if (fd_src == -1 || fstat(fd_src, &sb) != 0) memset(&sb, 0, sizeof(sb)); /* Make mtime on temp file match src. */ mtim_get(&sb, times[0]); times[1].tv_sec = times[0].tv_sec; times[1].tv_nsec = times[0].tv_nsec; if (futimens(fd_dst, times) == -1) { if (utimensat(AT_FDCWD, path_dst, times, 0) == -1) sudo_warn("%s", path_dst); } } close(fd_dst); } if (fd_src != -1) close(fd_src); fd_dst = fd_src = -1; } ret = SESH_SUCCESS; if (post) { /* Remove temporary files (post=1) */ for (i = 0; i < argc - 1; i += 2) unlink(argv[i]); } nocleanup: if (fd_dst != -1) close(fd_dst); if (fd_src != -1) close(fd_src); return(ret); cleanup_0: /* Remove temporary files (post=0) */ for (i = 0; i < argc - 1; i += 2) unlink(argv[i + 1]); if (fd_dst != -1) close(fd_dst); if (fd_src != -1) close(fd_src); return(SESH_ERR_NO_FILES); }
void set_project(struct passwd *pw) { struct project proj; char buf[PROJECT_BUFSZ]; int errval; debug_decl(set_project, SUDO_DEBUG_UTIL) /* * Collect the default project for the user and settaskid */ setprojent(); if (getdefaultproj(pw->pw_name, &proj, buf, sizeof(buf)) != NULL) { errval = setproject(proj.pj_name, pw->pw_name, TASK_NORMAL); switch(errval) { case 0: break; case SETPROJ_ERR_TASK: switch (errno) { case EAGAIN: sudo_warnx(U_("resource control limit has been reached")); break; case ESRCH: sudo_warnx(U_("user \"%s\" is not a member of project \"%s\""), pw->pw_name, proj.pj_name); break; case EACCES: sudo_warnx(U_("the invoking task is final")); break; default: sudo_warnx(U_("could not join project \"%s\""), proj.pj_name); } case SETPROJ_ERR_POOL: switch (errno) { case EACCES: sudo_warnx(U_("no resource pool accepting default bindings " "exists for project \"%s\""), proj.pj_name); break; case ESRCH: sudo_warnx(U_("specified resource pool does not exist for " "project \"%s\""), proj.pj_name); break; default: sudo_warnx(U_("could not bind to default resource pool for " "project \"%s\""), proj.pj_name); } break; default: if (errval <= 0) { sudo_warnx(U_("setproject failed for project \"%s\""), proj.pj_name); } else { sudo_warnx(U_("warning, resource control assignment failed for " "project \"%s\""), proj.pj_name); } } } else { sudo_warn("getdefaultproj"); } endprojent(); debug_return; }
/* * Returns a new security context based on the old context and the * specified role and type. */ security_context_t get_exec_context(security_context_t old_context, const char *role, const char *type) { security_context_t new_context = NULL; context_t context = NULL; char *typebuf = NULL; debug_decl(get_exec_context, SUDO_DEBUG_SELINUX) /* We must have a role, the type is optional (we can use the default). */ if (!role) { sudo_warnx(U_("you must specify a role for type %s"), type); errno = EINVAL; goto bad; } if (!type) { if (get_default_type(role, &typebuf)) { sudo_warnx(U_("unable to get default type for role %s"), role); errno = EINVAL; goto bad; } type = typebuf; } /* * Expand old_context into a context_t so that we extract and modify * its components easily. */ context = context_new(old_context); /* * Replace the role and type in "context" with the role and * type we will be running the command as. */ if (context_role_set(context, role)) { sudo_warn(U_("failed to set new role %s"), role); goto bad; } if (context_type_set(context, type)) { sudo_warn(U_("failed to set new type %s"), type); goto bad; } /* * Convert "context" back into a string and verify it. */ if ((new_context = strdup(context_str(context))) == NULL) { sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); goto bad; } if (security_check_context(new_context) < 0) { sudo_warnx(U_("%s is not a valid context"), new_context); errno = EINVAL; goto bad; } #ifdef DEBUG sudo_warnx("Your new context is %s", new_context); #endif context_free(context); debug_return_ptr(new_context); bad: free(typebuf); context_free(context); freecon(new_context); debug_return_ptr(NULL); }
/* * This function attempts to relabel the tty. If this function fails, then * the contexts are free'd and -1 is returned. On success, 0 is returned * and tty_context and new_tty_context are set. * * This function will not fail if it can not relabel the tty when selinux is * in permissive mode. */ static int relabel_tty(const char *ttyn, int ptyfd) { security_context_t tty_con = NULL; security_context_t new_tty_con = NULL; struct stat sb; int fd; debug_decl(relabel_tty, SUDO_DEBUG_SELINUX) se_state.ttyfd = ptyfd; /* It is perfectly legal to have no tty. */ if (ptyfd == -1 && ttyn == NULL) debug_return_int(0); /* If sudo is not allocating a pty for the command, open current tty. */ if (ptyfd == -1) { se_state.ttyfd = open(ttyn, O_RDWR|O_NOCTTY|O_NONBLOCK); if (se_state.ttyfd == -1 || fstat(se_state.ttyfd, &sb) == -1) { sudo_warn(U_("unable to open %s, not relabeling tty"), ttyn); goto bad; } if (!S_ISCHR(sb.st_mode)) { sudo_warn(U_("%s is not a character device, not relabeling tty"), ttyn); goto bad; } (void)fcntl(se_state.ttyfd, F_SETFL, fcntl(se_state.ttyfd, F_GETFL, 0) & ~O_NONBLOCK); } if (fgetfilecon(se_state.ttyfd, &tty_con) < 0) { sudo_warn(U_("unable to get current tty context, not relabeling tty")); goto bad; } if (tty_con) { security_class_t tclass = string_to_security_class("chr_file"); if (tclass == 0) { sudo_warn(U_("unknown security class \"chr_file\", not relabeling tty")); goto bad; } if (security_compute_relabel(se_state.new_context, tty_con, tclass, &new_tty_con) < 0) { sudo_warn(U_("unable to get new tty context, not relabeling tty")); goto bad; } } if (new_tty_con != NULL) { if (fsetfilecon(se_state.ttyfd, new_tty_con) < 0) { sudo_warn(U_("unable to set new tty context")); goto bad; } } if (ptyfd != -1) { /* Reopen pty that was relabeled, std{in,out,err} are reset later. */ se_state.ttyfd = open(ttyn, O_RDWR|O_NOCTTY, 0); if (se_state.ttyfd == -1 || fstat(se_state.ttyfd, &sb) == -1) { sudo_warn(U_("unable to open %s"), ttyn); goto bad; } if (!S_ISCHR(sb.st_mode)) { sudo_warn(U_("%s is not a character device, not relabeling tty"), ttyn); goto bad; } if (dup2(se_state.ttyfd, ptyfd) == -1) { sudo_warn("dup2"); goto bad; } } else { /* Re-open tty to get new label and reset std{in,out,err} */ close(se_state.ttyfd); se_state.ttyfd = open(ttyn, O_RDWR|O_NOCTTY|O_NONBLOCK); if (se_state.ttyfd == -1 || fstat(se_state.ttyfd, &sb) == -1) { sudo_warn(U_("unable to open %s"), ttyn); goto bad; } if (!S_ISCHR(sb.st_mode)) { sudo_warn(U_("%s is not a character device, not relabeling tty"), ttyn); goto bad; } (void)fcntl(se_state.ttyfd, F_SETFL, fcntl(se_state.ttyfd, F_GETFL, 0) & ~O_NONBLOCK); for (fd = STDIN_FILENO; fd <= STDERR_FILENO; fd++) { if (isatty(fd) && dup2(se_state.ttyfd, fd) == -1) { sudo_warn("dup2"); goto bad; } } } /* Retain se_state.ttyfd so we can restore label when command finishes. */ (void)fcntl(se_state.ttyfd, F_SETFD, FD_CLOEXEC); se_state.ttyn = ttyn; se_state.tty_context = tty_con; se_state.new_tty_context = new_tty_con; debug_return_int(0); bad: if (se_state.ttyfd != -1 && se_state.ttyfd != ptyfd) { close(se_state.ttyfd); se_state.ttyfd = -1; } freecon(tty_con); debug_return_int(se_state.enforcing ? -1 : 0); }
struct log_info * parse_logfile(const char *logfile) { FILE *fp; char *buf = NULL, *cp, *ep; const char *errstr; size_t bufsize = 0, cwdsize = 0, cmdsize = 0; struct log_info *li = NULL; debug_decl(parse_logfile, SUDO_DEBUG_UTIL) fp = fopen(logfile, "r"); if (fp == NULL) { sudo_warn(U_("unable to open %s"), logfile); goto bad; } /* * ID file has three lines: * 1) a log info line * 2) cwd * 3) command with args */ if ((li = calloc(1, sizeof(*li))) == NULL) sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); if (getdelim(&buf, &bufsize, '\n', fp) == -1 || getdelim(&li->cwd, &cwdsize, '\n', fp) == -1 || getdelim(&li->cmd, &cmdsize, '\n', fp) == -1) { sudo_warn(U_("%s: invalid log file"), logfile); goto bad; } /* Strip the newline from the cwd and command. */ li->cwd[strcspn(li->cwd, "\n")] = '\0'; li->cmd[strcspn(li->cmd, "\n")] = '\0'; /* * Crack the log line (rows and cols not present in old versions). * timestamp:user:runas_user:runas_group:tty:rows:cols * XXX - probably better to use strtok and switch on the state. */ buf[strcspn(buf, "\n")] = '\0'; cp = buf; /* timestamp */ if ((ep = strchr(cp, ':')) == NULL) { sudo_warn(U_("%s: time stamp field is missing"), logfile); goto bad; } *ep = '\0'; li->tstamp = strtonum(cp, 0, TIME_T_MAX, &errstr); if (errstr != NULL) { sudo_warn(U_("%s: time stamp %s: %s"), logfile, cp, errstr); goto bad; } /* user */ cp = ep + 1; if ((ep = strchr(cp, ':')) == NULL) { sudo_warn(U_("%s: user field is missing"), logfile); goto bad; } if ((li->user = strndup(cp, (size_t)(ep - cp))) == NULL) sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); /* runas user */ cp = ep + 1; if ((ep = strchr(cp, ':')) == NULL) { sudo_warn(U_("%s: runas user field is missing"), logfile); goto bad; } if ((li->runas_user = strndup(cp, (size_t)(ep - cp))) == NULL) sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); /* runas group */ cp = ep + 1; if ((ep = strchr(cp, ':')) == NULL) { sudo_warn(U_("%s: runas group field is missing"), logfile); goto bad; } if (cp != ep) { if ((li->runas_group = strndup(cp, (size_t)(ep - cp))) == NULL) sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); } /* tty, followed by optional rows + columns */ cp = ep + 1; if ((ep = strchr(cp, ':')) == NULL) { /* just the tty */ if ((li->tty = strdup(cp)) == NULL) sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); } else { /* tty followed by rows + columns */ if ((li->tty = strndup(cp, (size_t)(ep - cp))) == NULL) sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); cp = ep + 1; /* need to NULL out separator to use strtonum() */ if ((ep = strchr(cp, ':')) != NULL) { *ep = '\0'; } li->rows = strtonum(cp, 1, INT_MAX, &errstr); if (errstr != NULL) { sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, "%s: tty rows %s: %s", logfile, cp, errstr); } if (ep != NULL) { cp = ep + 1; li->cols = strtonum(cp, 1, INT_MAX, &errstr); if (errstr != NULL) { sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, "%s: tty cols %s: %s", logfile, cp, errstr); } } } fclose(fp); free(buf); debug_return_ptr(li); bad: if (fp != NULL) fclose(fp); free(buf); free_log_info(li); debug_return_ptr(NULL); }