/
niceSOFT
/
inetutils
Обзор
Документация
Войти
/
niceSOFT
/
inetutils
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
v2.1
src/rexecd.c
768 строк
19 KB
Alfred M. Szmidt
Happy GNU 2021 year!
01 янв 2021, 13:35
01 янв 2021, 13:35
911413e
Код
Авторство
О чём код?
/* Copyright (C) 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Free Software Foundation, Inc. This file is part of GNU Inetutils. GNU Inetutils is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GNU Inetutils 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see `http://www.gnu.org/licenses/'. */ /* * Copyright (c) 1983, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* Implementation of PAM support for a service `rexec' * was done by Mats Erik Andersson. * * Sample PAM configuration with variations for different systems: * * rexec auth requisite pam_nologin.so * rexec auth required pam_unix.so * # rexec auth requisite pam_authtok_get.so * # rexec auth required pam_unix_cred.so * # rexec auth required pam_unix_auth.so * # rexec auth required pam_listfile.so item=user sense=allow \ * file=/etc/rexec.allow onerr=fail * * rexec account required pam_unix.so * rexec account required pam_time.so * # rexec account requisite pam_roles.so * # rexec account required pam_unix_account.so * # rexec account required pam_list.so allow=/etc/rexec.allow * * rexec password required pam_deny.so */ #include <config.h> #include <sys/types.h> #include <sys/param.h> #include <sys/ioctl.h> #ifdef HAVE_SYS_FILIO_H # include <sys/filio.h> #endif #include <sys/socket.h> #include <sys/time.h> #include <time.h> #include <netinet/in.h> #include <errno.h> #include <netdb.h> #include <pwd.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <grp.h> #ifdef HAVE_CRYPT_H # include <crypt.h> #endif #include <sys/select.h> #include <stdarg.h> #ifdef HAVE_SHADOW_H # include <shadow.h> #endif #include <syslog.h> #ifdef HAVE_SECURITY_PAM_APPL_H # include <security/pam_appl.h> #endif #ifdef HAVE_GETPWNAM_R # include <xalloc.h> #endif #include <progname.h> #include <argp.h> #include <error.h> #include <unused-parameter.h> #include "libinetutils.h" void die (int code, const char *fmt, ...); int doit (int f, struct sockaddr *fromp, socklen_t fromlen); #ifdef WITH_PAM static int pam_rc = PAM_AUTH_ERR; /* doit() and die() */ static char *password_pam = NULL; /* doit() and rexec_conv() */ static int pam_flags = PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK; static pam_handle_t *pam_handle = NULL; /* doit() and die() */ static int rexec_conv (int, const struct pam_message **, struct pam_response **, void *); static struct pam_conv pam_conv = { rexec_conv, NULL }; #endif /* WITH_PAM */ static int logging = 0; static struct argp_option options[] = { { "logging", 'l', NULL, 0, "logging of requests and errors", 1 }, { NULL, 0, NULL, 0, NULL, 0 } }; static error_t parse_opt (int key, char *arg _GL_UNUSED_PARAMETER, struct argp_state *state _GL_UNUSED_PARAMETER) { switch (key) { case 'l': ++logging; break; default: return ARGP_ERR_UNKNOWN; } return 0; } const char doc[] = #ifdef WITH_PAM "Remote execution daemon, using PAM module 'rexec'."; #else /* !WITH_PAM */ "Remote execution daemon."; #endif static struct argp argp = { options, parse_opt, NULL, doc, NULL, NULL, NULL }; /* * remote execute server: * username\0 * password\0 * command\0 * data */ int main (int argc, char **argv) { struct sockaddr_storage from; socklen_t fromlen; int sockfd = STDIN_FILENO; int index; set_program_name (argv[0]); iu_argp_init ("rexecd", default_program_authors); argp_parse (&argp, argc, argv, 0, &index, NULL); openlog ("rexecd", LOG_PID | LOG_ODELAY | LOG_NOWAIT, LOG_DAEMON); if (argc > index) /* Record this complaint locally. */ syslog (LOG_NOTICE, "%d extra arguments", argc - index); fromlen = sizeof (from); if (getpeername (sockfd, (struct sockaddr *) &from, &fromlen) < 0) { syslog (LOG_ERR, "getpeername: %m"); error (EXIT_FAILURE, errno, "getpeername"); } doit (sockfd, (struct sockaddr *) &from, fromlen); exit (EXIT_SUCCESS); } /* Set lengths of USER and LOGNAME longer than * the REXEC protocol prescribes. */ char username[32 + sizeof ("USER=")] = "USER="; char logname[32 + sizeof ("LOGNAME=")] = "LOGNAME="; /* Identical to USER. */ char homedir[256 + sizeof ("HOME=")] = "HOME="; char shell[256 + sizeof ("SHELL=")] = "SHELL="; char path[sizeof (PATH_DEFPATH) + sizeof ("PATH=")] = "PATH="; char remotehost[128 + sizeof ("RHOST=")] = "RHOST="; #ifndef WITH_PAM char *envinit[] = { homedir, shell, path, username, logname, remotehost, NULL }; #endif extern char **environ; char *getstr (const char *); #ifndef WITH_PAM static char * get_user_password (struct passwd *pwd) { char *pw_text = pwd->pw_passwd; #ifdef HAVE_SHADOW_H struct spwd *spwd = getspnam (pwd->pw_name); if (spwd) pw_text = spwd->sp_pwdp; #endif return pw_text; } #endif /* !WITH_PAM */ int doit (int f, struct sockaddr *fromp, socklen_t fromlen) { char *cmdbuf, *cp; char *user, *pass; #ifndef WITH_PAM char *namep, *pw_password; #endif #ifdef HAVE_GETPWNAM_R char *pwbuf; int pwbuflen; struct passwd *pwd, pwstor; #else /* !HAVE_GETPWNAM_R */ struct passwd *pwd; #endif char rhost[INET6_ADDRSTRLEN]; int s, ret; in_port_t port; int pv[2], pid, cc; fd_set readfrom, ready; char buf[BUFSIZ], sig; int one = 1; #ifdef HAVE_GETPWNAM_R pwbuflen = sysconf (_SC_GETPW_R_SIZE_MAX); if (pwbuflen <= 0) pwbuflen = 1024; /* Guessing only. */ pwbuf = xmalloc (pwbuflen); #endif /* HAVE_GETPWNAM_R */ signal (SIGINT, SIG_DFL); signal (SIGQUIT, SIG_DFL); signal (SIGTERM, SIG_DFL); #ifdef DEBUG { int t = open (PATH_TTY, O_RDWR); if (t >= 0) { ioctl (t, TIOCNOTTY, (char *) 0); close (t); } } #endif if (f != STDIN_FILENO) { dup2 (f, STDIN_FILENO); dup2 (f, STDOUT_FILENO); dup2 (f, STDERR_FILENO); } ret = getnameinfo (fromp, fromlen, rhost, sizeof (rhost), NULL, 0, NI_NUMERICHOST); if (ret) { syslog (LOG_WARNING, "getnameinfo: %m"); strncpy (rhost, "(unknown)", sizeof (rhost)); } if (logging > 1) syslog (LOG_INFO, "request from \"%s\"", rhost); alarm (60); port = 0; for (;;) { char c; if (read (f, &c, 1) != 1) { if (logging) syslog (LOG_ERR, "main socket: %m"); exit (EXIT_FAILURE); } if (c == 0) break; port = port * 10 + c - '0'; } alarm (0); if (port != 0) { s = socket (fromp->sa_family, SOCK_STREAM, 0); if (s < 0) { if (logging) syslog (LOG_ERR, "stderr socket: %m"); exit (EXIT_FAILURE); } setsockopt (s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof (one)); alarm (60); switch (fromp->sa_family) { case AF_INET: ((struct sockaddr_in *) fromp)->sin_port = htons (port); break; case AF_INET6: ((struct sockaddr_in6 *) fromp)->sin6_port = htons (port); break; default: syslog (LOG_ERR, "unknown address family %d", fromp->sa_family); exit (EXIT_FAILURE); } if (connect (s, fromp, fromlen) < 0) { /* Use LOG_NOTICE since the remote part might cause * this error by blocking. We are less probable. */ if (logging) syslog (LOG_NOTICE, "connect: %m"); exit (EXIT_FAILURE); } alarm (0); } user = getstr ("username"); pass = getstr ("password"); cmdbuf = getstr ("command"); setpwent (); #ifdef HAVE_GETPWNAM_R ret = getpwnam_r (user, &pwstor, pwbuf, pwbuflen, &pwd); if (ret || pwd == NULL) #else /* !HAVE_GETPWNAM_R */ pwd = getpwnam (user); if (pwd == NULL) #endif /* HAVE_GETPWNAM_R */ { if (logging) syslog (LOG_WARNING | LOG_AUTH, "no user named \"%s\"", user); die (EXIT_FAILURE, "Login incorrect."); } endpwent (); #ifndef WITH_PAM /* Last need of elevated privilege. */ pw_password = get_user_password (pwd); if (*pw_password != '\0') { namep = crypt (pass, pw_password); if (strcmp (namep, pw_password)) { if (logging) syslog (LOG_WARNING | LOG_AUTH, "password failure for \"%s\"", user); die (EXIT_FAILURE, "Password incorrect."); } } #else /* WITH_PAM */ /* Failure at this stage should not disclose server side causes, * but only fail almost silently. Use "Try again" for now. */ password_pam = pass; /* Needed by pam_conv(). */ pam_rc = pam_start ("rexec", user, &pam_conv, &pam_handle); if (pam_rc != PAM_SUCCESS) die (EXIT_FAILURE, "Try again."); pam_rc = pam_set_item (pam_handle, PAM_RHOST, rhost); if (pam_rc != PAM_SUCCESS) die (EXIT_FAILURE, "Try again."); pam_rc = pam_authenticate (pam_handle, pam_flags); if (pam_rc != PAM_SUCCESS) { switch (pam_rc) { case PAM_ABORT: /* This is potentially severe. No communication! */ pam_end (pam_handle, pam_rc); exit (EXIT_FAILURE); break; case PAM_AUTHINFO_UNAVAIL: case PAM_CRED_INSUFFICIENT: case PAM_USER_UNKNOWN: die (EXIT_FAILURE, "Login incorrect."); break; case PAM_AUTH_ERR: case PAM_MAXTRIES: default: die (EXIT_FAILURE, "Password incorrect."); break; } } pam_rc = pam_acct_mgmt (pam_handle, pam_flags); if (pam_rc != PAM_SUCCESS) { switch (pam_rc) { case PAM_NEW_AUTHTOK_REQD: case PAM_PERM_DENIED: die (EXIT_FAILURE, "Password incorrect."); break; case PAM_ACCT_EXPIRED: case PAM_AUTH_ERR: case PAM_USER_UNKNOWN: default: die (EXIT_FAILURE, "Login incorrect."); break; } } #endif /* WITH_PAM */ #ifdef HAVE_SETLOGIN if (setlogin (pwd->pw_name) < 0) syslog (LOG_ERR, "setlogin() failed: %m"); #endif /* Step down from superuser personality. * * The changing of group membership will seldomly * fail, but a relevant message is passed just in * case. These messages are non-standard. */ if (setgid ((gid_t) pwd->pw_gid) < 0) { #ifdef WITH_PAM pam_rc = PAM_ABORT; #endif syslog (LOG_DEBUG | LOG_AUTH, "setgid(gid = %d): %m", pwd->pw_gid); die (EXIT_FAILURE, "Failed group protections."); } #ifdef HAVE_INITGROUPS if (initgroups (pwd->pw_name, pwd->pw_gid) < 0) { # ifdef WITH_PAM pam_rc = PAM_ABORT; # endif syslog (LOG_DEBUG | LOG_AUTH, "initgroups(%s, %d): %m", pwd->pw_name, pwd->pw_gid); die (EXIT_FAILURE, "Failed group protections."); } #endif #ifdef WITH_PAM pam_rc = pam_setcred (pam_handle, PAM_SILENT | PAM_ESTABLISH_CRED); if (pam_rc != PAM_SUCCESS) { syslog (LOG_ERR | LOG_AUTH, "pam_setcred: %s", pam_strerror (pam_handle, pam_rc)); pam_rc = PAM_SUCCESS; /* Only report the above anomaly. */ } #endif /* WITH_PAM */ if (setuid ((uid_t) pwd->pw_uid) < 0) { #ifdef WITH_PAM pam_rc = PAM_ABORT; #endif syslog (LOG_DEBUG | LOG_AUTH, "setuid(uid = %d): %m", pwd->pw_uid); die (EXIT_FAILURE, "Failed user identity."); } if (port) { pipe (pv); pid = fork (); if (pid == -1) { if (logging) syslog (LOG_ERR, "forking for \"%s\": %m", user); #ifdef WITH_PAM pam_rc = PAM_ABORT; #endif die (EXIT_FAILURE, "Try again."); } if (pid) { #ifdef WITH_PAM /* This process steps down from PAM now, * the child continues communication. */ pam_handle = NULL; # ifdef PAM_DATA_SILENT pam_rc |= PAM_DATA_SILENT; # endif #endif /* WITH_PAM */ close (STDIN_FILENO); close (STDOUT_FILENO); close (STDERR_FILENO); close (f); close (pv[1]); FD_ZERO (&readfrom); FD_SET (s, &readfrom); FD_SET (pv[0], &readfrom); ioctl (pv[1], FIONBIO, (char *) &one); /* should set s nbio! */ do { int maxfd = s; ready = readfrom; if (pv[0] > maxfd) maxfd = pv[0]; select (maxfd + 1, (fd_set *) & ready, (fd_set *) NULL, (fd_set *) NULL, (struct timeval *) NULL); if (FD_ISSET (s, &ready)) { if (read (s, &sig, 1) <= 0) FD_CLR (s, &readfrom); else killpg (pid, sig); } if (FD_ISSET (pv[0], &ready)) { cc = read (pv[0], buf, sizeof (buf)); if (cc <= 0) { shutdown (s, 1 + 1); FD_CLR (pv[0], &readfrom); } else write (s, buf, cc); } } while (FD_ISSET (pv[0], &readfrom) || FD_ISSET (s, &readfrom)); exit (EXIT_SUCCESS); } /* Parent process. */ #ifdef HAVE_SETLOGIN /* Not sufficient to call setpgid() on BSD systems. */ if (getsid ((pid_t) 0) != getpid ()) if (setsid () < 0) syslog (LOG_ERR, "setsid() failed: %m"); #elif defined HAVE_SETPGID /* !HAVE_SETLOGIN */ setpgid (0, getpid ()); #endif close (s); close (pv[0]); dup2 (pv[1], STDERR_FILENO); close (pv[1]); } if (f > 2) close (f); /* Last point of failure due to incorrect user settings. */ if (chdir (pwd->pw_dir) < 0) { if (logging) syslog (LOG_NOTICE | LOG_AUTH, "\"%s\" uses invalid \"%s\"", user, pwd->pw_dir); #ifdef WITH_PAM pam_rc = PAM_ABORT; #endif die (EXIT_FAILURE, "No remote directory."); } #ifdef WITH_PAM /* Refresh knowledge of user, which might have been * remapped by the PAM stack during conversation. */ free (user); pam_rc = pam_get_item (pam_handle, PAM_USER, (const void **) &user); if (pam_rc != PAM_SUCCESS) die (EXIT_FAILURE, "Try again."); # ifdef HAVE_GETPWNAM_R ret = getpwnam_r (user, &pwstor, pwbuf, pwbuflen, &pwd); if (ret || pwd == NULL) # else /* !HAVE_GETPWNAM_R */ pwd = getpwnam (user); if (pwd == NULL) # endif /* HAVE_GETPWNAM_R */ { syslog (LOG_ERR | LOG_AUTH, "no user named \"%s\"", user); die (EXIT_FAILURE, "Login incorrect."); } #endif /* WITH_PAM */ strcat (path, PATH_DEFPATH); strncat (homedir, pwd->pw_dir, sizeof (homedir) - sizeof ("HOME=") - 1); strncat (shell, pwd->pw_shell, sizeof (shell) - sizeof ("SHELL=") - 1); strncat (username, pwd->pw_name, sizeof (username) - sizeof ("USER=") - 1); strncat (logname, pwd->pw_name, sizeof (logname) - sizeof ("LOGNAME=") - 1); strncat (remotehost, rhost, sizeof (remotehost) - sizeof ("RHOST=") - 1); #ifdef WITH_PAM if (pam_getenv (pam_handle, "PATH") == NULL) (void) pam_putenv (pam_handle, path); if (pam_getenv (pam_handle, "HOME") == NULL) (void) pam_putenv (pam_handle, homedir); if (pam_getenv (pam_handle, "SHELL") == NULL) (void) pam_putenv (pam_handle, shell); if (pam_getenv (pam_handle, "USER") == NULL) (void) pam_putenv (pam_handle, username); if (pam_getenv (pam_handle, "LOGNAME") == NULL) (void) pam_putenv (pam_handle, logname); if (pam_getenv (pam_handle, "RHOST") == NULL) (void) pam_putenv (pam_handle, remotehost); environ = pam_getenvlist (pam_handle); #else /* !WITH_PAM */ environ = envinit; #endif /* !WITH_PAM */ if (*pwd->pw_shell == '\0') pwd->pw_shell = PATH_BSHELL; cp = strrchr (pwd->pw_shell, '/'); if (cp) cp++; else cp = pwd->pw_shell; /* This is the 7th step in the protocol standard. * All authentication has been successful, and the * execution can be handed over to the requested shell. */ write (STDERR_FILENO, "\0", 1); if (logging) syslog (LOG_INFO, "accepted user \"%s\" from %s", user, rhost); execl (pwd->pw_shell, cp, "-c", cmdbuf, NULL); if (logging) syslog (LOG_ERR, "execl fails for \"%s\": %m", user); #ifdef WITH_PAM pam_end (pam_handle, PAM_SUCCESS); #endif error (EXIT_FAILURE, errno, "executing %s", pwd->pw_shell); return -1; } void die (int code, const char *fmt, ...) { va_list ap; char buf[BUFSIZ]; int n; va_start (ap, fmt); buf[0] = 1; /* Error condition. */ n = vsnprintf (buf + 1, sizeof buf - 1, fmt, ap); va_end (ap); if (n + 1 > (int) sizeof buf) n = sizeof buf - 1; buf[n++] = '\n'; write (STDERR_FILENO, buf, n); #ifdef WITH_PAM if (pam_handle != NULL) pam_end (pam_handle, pam_rc); #endif exit (code); } char * getstr (const char *err) { size_t buf_len = 100; char *buf = malloc (buf_len), *end = buf; if (!buf) die (EXIT_FAILURE, "Out of space reading %s", err); do { /* Oh this is efficient, oh yes. [But what can be done?] */ int rd = read (STDIN_FILENO, end, 1); if (rd <= 0) { if (rd == 0) die (EXIT_FAILURE, "EOF reading %s", err); else error (EXIT_FAILURE, 0, "%s", err); } end += rd; if ((buf + buf_len - end) < (ssize_t) (buf_len >> 3)) { /* Not very much room left in our buffer, grow it. */ size_t end_offs = end - buf; buf_len += buf_len; buf = realloc (buf, buf_len); if (!buf) die (EXIT_FAILURE, "Out of space reading %s", err); end = buf + end_offs; } } while (*(end - 1)); return buf; } #ifdef WITH_PAM /* Call back function for passing user's password * to any PAM module requesting this information. */ static int rexec_conv (int num, const struct pam_message **pam_msg, struct pam_response **pam_resp, void *data _GL_UNUSED_PARAMETER) { struct pam_response *resp; /* Reject composite call-backs. */ if (num <= 0 || num > 1) return PAM_CONV_ERR; /* We only accept password reporting. */ if (pam_msg[0]->msg_style != PAM_PROMPT_ECHO_OFF) return PAM_CONV_ERR; /* Allocate a single response structure, * as we are ignoring composite calls. * * This is an external call-back, so we check * for successful allocation ourselves. */ resp = malloc (sizeof (*resp)); if (resp == NULL) return PAM_BUF_ERR; /* Prepare response to a single PAM_PROMPT_ECHO_OFF. */ resp->resp_retcode = 0; resp->resp = strdup (password_pam); if (resp->resp == NULL) { free (resp); return PAM_BUF_ERR; } *pam_resp = resp; return PAM_SUCCESS; } #endif /* WITH_PAM */