/
niceSOFT
/
inetutils
Обзор
Документация
Войти
/
niceSOFT
/
inetutils
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/rexecd.c
765 строк
19 KB
Simon Josefsson
maint: Run 'make update-copyright'
20 янв 2026, 11:50
Не верифицирован
20 янв 2026, 11:50
1ce61cd
Код
Авторство
О чём код?
/* Copyright (C) 1993-2026 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 <attribute.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 MAYBE_UNUSED, struct argp_state *state MAYBE_UNUSED) { 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 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 seldom * 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 MAYBE_UNUSED) { 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 */