I want to know how rsh runs any command. I am using netkit-rsh-0.17 package. My OS is centOS.
In rshd directory, rshd.c
performs the task to run any command on server.
In this file, doit()
is the main function who performs all the task.
Questions,
pwd->pw_dir
, pwd->pw_uid
, pwd->pw_shell
means in this code?pv
does in this.Explain me by using rsh localhost ulimit -n
command.
doit()
static void
doit(struct sockaddr_in *fromp)
{
char cmdbuf[ARG_MAX+1];
const char *theshell, *shellname;
char locuser[16], remuser[16];
struct passwd *pwd;
int sock = -1;
const char *hostname;
u_short port;
int pv[2], pid, ifd;
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
signal(SIGTERM, SIG_DFL);
alarm(60);
port = getint();
alarm(0);
if (port != 0) {
int lport = IPPORT_RESERVED - 1;
sock = rresvport(&lport);
if (sock < 0) {
syslog(LOG_ERR, "can't get stderr port: %m");
exit(1);
}
if (port >= IPPORT_RESERVED) {
syslog(LOG_ERR, "2nd port not reserved\n");
exit(1);
}
fromp->sin_port = htons(port);
if (connect(sock, (struct sockaddr *)fromp,
sizeof(*fromp)) < 0) {
syslog(LOG_INFO, "connect second port: %m");
exit(1);
}
}
#if 0
/* We're running from inetd; socket is already on 0, 1, 2 */
dup2(f, 0);
dup2(f, 1);
dup2(f, 2);
#endif
getstr(remuser, sizeof(remuser), "remuser");
getstr(locuser, sizeof(locuser), "locuser");
getstr(cmdbuf, sizeof(cmdbuf), "command");
if (!strcmp(locuser, "root")) paranoid = 1;
hostname = findhostname(fromp, remuser, locuser, cmdbuf);
setpwent();
pwd = doauth(remuser, hostname, locuser);
if (pwd == NULL) {
fail("Permission denied.\n",
remuser, hostname, locuser, cmdbuf);
}
if (chdir(pwd->pw_dir) < 0) {
chdir("/");
/*
* error("No remote directory.\n");
* exit(1);
*/
}
if (pwd->pw_uid != 0 && !access(_PATH_NOLOGIN, F_OK)) {
error("Logins currently disabled.\n");
exit(1);
}
(void) write(2, "\0", 1);
sent_null = 1;
if (port) {
if (pipe(pv) < 0) {
error("Can't make pipe.\n");
exit(1);
}
pid = fork();
if (pid == -1) {
error("Can't fork; try again.\n");
exit(1);
}
if (pid) {
close(0);
close(1);
close(2);
close(pv[1]);
stderr_parent(sock, pv[0], pid);
/* NOTREACHED */
}
setpgrp();
close(sock);
close(pv[0]);
dup2(pv[1], 2);
close(pv[1]);
}
theshell = pwd->pw_shell;
if (!theshell || !*theshell) {
/* shouldn't we deny access? */
theshell = _PATH_BSHELL;
}
#if BSD > 43
if (setlogin(pwd->pw_name) < 0) {
syslog(LOG_ERR, "setlogin() failed: %m");
}
#endif
#ifndef USE_PAM
/* if PAM, already done */
if (setgid(pwd->pw_gid)) {
syslog(LOG_ERR, "setgid: %m");
exit(1);
}
if (initgroups(pwd->pw_name, pwd->pw_gid)) {
syslog(LOG_ERR, "initgroups: %m");
exit(1);
}
#endif
if (setuid(pwd->pw_uid)) {
syslog(LOG_ERR, "setuid: %m");
exit(1);
}
environ = envinit;
strncat(homedir, pwd->pw_dir, sizeof(homedir)-6);
homedir[sizeof(homedir)-1] = 0;
strcat(path, _PATH_DEFPATH);
strncat(shell, theshell, sizeof(shell)-7);
shell[sizeof(shell)-1] = 0;
strncat(username, pwd->pw_name, sizeof(username)-6);
username[sizeof(username)-1] = 0;
shellname = strrchr(theshell, '/');
if (shellname) shellname++;
else shellname = theshell;
endpwent();
if (paranoid) {
syslog(LOG_INFO|LOG_AUTH, "%s@%s as %s: cmd='%s'",
remuser, hostname, locuser, cmdbuf);
}
/*
* Close all fds, in case libc has left fun stuff like
* /etc/shadow open.
*/
for (ifd = getdtablesize()-1; ifd > 2; ifd--) close(ifd);
execl(theshell, shellname, "-c", cmdbuf, 0);
perror(theshell);
exit(1);
}
struct passwd
is documented in POSIX, in pwd.h
. It is a structure used to store the /etc/passwd
entries for a given user. The three you mention are these:
uid_t pw_uid
Numerical user ID.char *pw_dir
Initial working directory. (Home directory.)char *pw_shell
Program to use as shell. (Default shell for the user.)
The function doauth
referenced in the code above probably either calls getpwent
or simulates that to fill in the appropriate values for the user on the remote system.
pv
is pair of file descriptors representing connected pipes, set up by pipe()
. pv[0]
is the "read side", pv[1]
the "write side". Anything written to pv[1]
can be read from pv[0]
.
In the code above, the parent process does:
close(pv[1]);
stderr_parent(sock, pv[0], pid);
which closes the write side, and, I'm guessing, wires the read side to (one of) the sockets used to communicate between the hosts.
The child process on the other hand does this:
close(pv[0]); // close the read side
dup2(pv[1], 2); // clone the write side to fd n° 2 (stderr)
close(pv[1]); // close the original write side (now only
// writable through fd n° 2
So basically, the child's stderr
stream is now connected to a network stream back to the client.
The rest of the code essentially sanitizes the environment (environment variables and working directory), checks permissions, sets the appropriate uid
/gid
and finally executes the command that the user wanted to run using execl()
via a shell. The actual command run on the remote system will be something like /bin/sh -c <user command string>
.
So with your example, assuming for example that your user's shell in /etc/passwd
is /bin/bash
, the execl
call will result in running this:
/bin/bash -c 'ulimit -n'
(Quotes since the user command is a single argument in the execl
call, it is not tokenized.)