1 /* $OpenBSD: rcmdsh.c,v 1.20 2019/06/28 13:32:42 deraadt Exp $ */
2
3 /*
4 * Copyright (c) 2001, MagniComp
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in
14 * the documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the MagniComp nor the names of its contributors may
16 * be used to endorse or promote products derived from this software
17 * without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
23 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
28 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 /*
32 * This is an rcmd() replacement originally by
33 * Chris Siebenmann <cks@utcc.utoronto.ca>.
34 */
35
36 #include <sys/types.h>
37 #include <sys/socket.h>
38 #include <sys/wait.h>
39 #include <signal.h>
40 #include <errno.h>
41 #include <limits.h>
42 #include <netdb.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <pwd.h>
47 #include <paths.h>
48 #include <unistd.h>
49
50 /*
51 * This is a replacement rcmd() function that uses the ssh(1)
52 * program in place of a direct rcmd(3) function call so as to
53 * avoid having to be root. Note that rport is ignored.
54 */
55 int
rcmdsh(char ** ahost,int rport,const char * locuser,const char * remuser,const char * cmd,char * rshprog)56 rcmdsh(char **ahost, int rport, const char *locuser, const char *remuser,
57 const char *cmd, char *rshprog)
58 {
59 static char hbuf[HOST_NAME_MAX+1];
60 struct addrinfo hint, *res;
61 int sp[2];
62 pid_t cpid;
63 char *p, pwbuf[_PW_BUF_LEN];
64 struct passwd pwstore, *pw = NULL;
65
66 /* What rsh/shell to use. */
67 if (rshprog == NULL)
68 rshprog = _PATH_RSH;
69
70 /* locuser must exist on this host. */
71 getpwnam_r(locuser, &pwstore, pwbuf, sizeof(pwbuf), &pw);
72 if (pw == NULL) {
73 (void) fprintf(stderr, "rcmdsh: unknown user: %s\n", locuser);
74 return(-1);
75 }
76
77 /* Validate remote hostname. */
78 if (strcmp(*ahost, "localhost") != 0) {
79 memset(&hint, 0, sizeof(hint));
80 hint.ai_family = PF_UNSPEC;
81 hint.ai_flags = AI_CANONNAME;
82 if (getaddrinfo(*ahost, NULL, &hint, &res) == 0) {
83 if (res->ai_canonname) {
84 strlcpy(hbuf, res->ai_canonname, sizeof(hbuf));
85 *ahost = hbuf;
86 }
87 freeaddrinfo(res);
88 }
89 }
90
91 /* Get a socketpair we'll use for stdin and stdout. */
92 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1) {
93 perror("rcmdsh: socketpair");
94 return(-1);
95 }
96
97 cpid = fork();
98 if (cpid == -1) {
99 perror("rcmdsh: fork failed");
100 return(-1);
101 } else if (cpid == 0) {
102 /*
103 * Child. We use sp[1] to be stdin/stdout, and close sp[0].
104 */
105 (void) close(sp[0]);
106 if (dup2(sp[1], 0) == -1 || dup2(0, 1) == -1) {
107 perror("rcmdsh: dup2 failed");
108 _exit(255);
109 }
110 /* Fork again to lose parent. */
111 cpid = fork();
112 if (cpid == -1) {
113 perror("rcmdsh: fork to lose parent failed");
114 _exit(255);
115 }
116 if (cpid > 0)
117 _exit(0);
118
119 /* In grandchild here. Become local user for rshprog. */
120 if (setuid(pw->pw_uid)) {
121 (void) fprintf(stderr, "rcmdsh: setuid(%u): %s\n",
122 pw->pw_uid, strerror(errno));
123 _exit(255);
124 }
125
126 /*
127 * If remote host is "localhost" and local and remote user
128 * are the same, avoid running remote shell for efficiency.
129 */
130 if (!strcmp(*ahost, "localhost") && !strcmp(locuser, remuser)) {
131 char *argv[4];
132 if (pw->pw_shell[0] == '\0')
133 rshprog = _PATH_BSHELL;
134 else
135 rshprog = pw->pw_shell;
136 p = strrchr(rshprog, '/');
137 argv[0] = p ? p + 1 : rshprog;
138 argv[1] = "-c";
139 argv[2] = (char *)cmd;
140 argv[3] = NULL;
141 execvp(rshprog, argv);
142 } else if ((p = strchr(rshprog, ' ')) == NULL) {
143 /* simple case */
144 char *argv[6];
145 p = strrchr(rshprog, '/');
146 argv[0] = p ? p + 1 : rshprog;
147 argv[1] = "-l";
148 argv[2] = (char *)remuser;
149 argv[3] = *ahost;
150 argv[4] = (char *)cmd;
151 argv[5] = NULL;
152 execvp(rshprog, argv);
153 } else {
154 /* must pull args out of rshprog and dyn alloc argv */
155 char **argv, **ap;
156 int n;
157 for (n = 7; (p = strchr(++p, ' ')) != NULL; n++)
158 continue;
159 rshprog = strdup(rshprog);
160 ap = argv = calloc(sizeof(char *), n);
161 if (rshprog == NULL || argv == NULL) {
162 perror("rcmdsh");
163 _exit(255);
164 }
165 while ((p = strsep(&rshprog, " ")) != NULL) {
166 if (*p == '\0')
167 continue;
168 *ap++ = p;
169 }
170 if (ap != argv) /* all spaces?!? */
171 rshprog = argv[0];
172 if ((p = strrchr(argv[0], '/')) != NULL)
173 argv[0] = p + 1;
174 *ap++ = "-l";
175 *ap++ = (char *)remuser;
176 *ap++ = *ahost;
177 *ap++ = (char *)cmd;
178 *ap++ = NULL;
179 execvp(rshprog, argv);
180 }
181 (void) fprintf(stderr, "rcmdsh: execvp %s failed: %s\n",
182 rshprog, strerror(errno));
183 _exit(255);
184 } else {
185 /* Parent. close sp[1], return sp[0]. */
186 (void) close(sp[1]);
187 /* Reap child. */
188 while (waitpid(cpid, NULL, 0) == -1 && errno == EINTR)
189 ;
190 return(sp[0]);
191 }
192 /* NOTREACHED */
193 }
194 DEF_WEAK(rcmdsh);
195