mc/vfs/mcserv.c

1393 lines
28 KiB
C

/* Server for the Midnight Commander Virtual File System.
Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
2004, 2005, 2006, 2007 Free Software Foundation, Inc.
Written by:
Miguel de Icaza, 1995, 1997,
Andrej Borsenkow 1996.
This program 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 2 of the License, or
(at your option) any later version.
This program 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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/**
* \file
* \brief Source: server for the Midnight Commander Virtual File System
* \author Miguel de Icaza
* \author Andrej Borsenkow
* \date 1995, 1996, 1997
*
* \todo opendir instead of keeping its table of file handles could return
* the pointer and expect the client to send a proper value back each
* time :-) We should use syslog to register login/logout.
*/
/* {{{ Includes and global variables */
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#ifdef HAVE_LIMITS_H
# include <limits.h>
#endif
#ifndef NGROUPS_MAX
# include <sys/param.h>
# ifdef NGROUPS
# define NGROUPS_MAX NGROUPS
# endif
#endif
#include <grp.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <errno.h>
#include <signal.h>
#ifdef HAVE_GETOPT_H
# include <getopt.h>
#endif
/* Network include files */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifdef HAVE_PMAP_SET
# include <rpc/rpc.h>
# include <rpc/pmap_prot.h>
# ifdef HAVE_RPC_PMAP_CLNT_H
# include <rpc/pmap_clnt.h>
# endif
#endif
#if defined(HAVE_PAM)
# if !defined(HAVE_SECURITY_PAM_MISC_H)
# undef HAVE_PAM
# endif
#endif
/* Authentication include files */
#include <pwd.h>
#ifdef HAVE_PAM
# include <security/pam_misc.h>
# ifndef PAM_ESTABLISH_CRED
# define PAM_ESTABLISH_CRED PAM_CRED_ESTABLISH
# endif
#else
#endif /* !HAVE_PAM */
#ifdef HAVE_CRYPT_H
# include <crypt.h>
#endif /* !HAVE_CRYPT_H */
#ifdef HAVE_SHADOW_H
# include <shadow.h>
#else
# ifdef HAVE_SHADOW_SHADOW_H
# include <shadow/shadow.h>
# endif
#endif
/*
* GNU gettext defines printf to libintl_printf on platforms that lack
* a native printf(3) capable of all POSIX features.
*/
#undef ENABLE_NLS
#include "../src/global.h"
#include "../src/tty.h" /* enable/disable interrupt key */
#include "../src/wtools.h" /* message() */
#include "../src/main.h" /* print_vfs_message */
#include "utilvfs.h"
#include "vfs.h"
#include "mcfs.h"
#include "mcfsutil.h"
#include "tcputil.h"
#ifndef INADDR_NONE
# define INADDR_NONE (0xffffffffU)
#endif
/* replacement for g_free() from glib */
#undef g_free
#define g_free(x) do {if (x) free (x);} while (0)
/* We don't care about SIGPIPE */
int got_sigpipe = 0;
/* The socket from which we accept commands */
int msock;
/* Requested version number from client */
static int clnt_version;
/* If non zero, we accept further commands */
int logged_in = 0;
/* Home directory */
const char *home_dir = NULL;
char *up_dir = NULL;
/* Were we started from inetd? */
int inetd_started = 0;
/* Are we running as a daemon? */
int isDaemon = 0;
/* guess */
int verbose = 0;
/* ftp auth */
int ftp = 0;
/* port number in which we listen to connections,
* if zero, we try to contact the portmapper to get a port, and
* if it's not possible, then we use a hardcoded value
*/
int portnum = 0;
/* if the server will use rcmd based authentication (hosts.equiv .rhosts) */
int r_auth = 0;
#define OPENDIR_HANDLES 8
#define DO_QUIT_VOID() \
do { \
quit_server = 1; \
return_code = 1; \
return; \
} while (0)
/* Only used by get_port_number */
#define DO_QUIT_NONVOID(a) \
do { \
quit_server = 1; \
return_code = 1; \
return (a); \
} while (0)
char buffer[4096];
int debug = 1;
static int quit_server;
static int return_code;
/* }}} */
/* {{{ Misc routines */
static void
send_status (int status, int errno_number)
{
rpc_send (msock, RPC_INT, status, RPC_INT, errno_number, RPC_END);
errno = 0;
}
/* }}} */
/* {{{ File with handle operations */
static void
do_open (void)
{
int handle, flags, mode;
char *arg;
rpc_get (msock, RPC_STRING, &arg, RPC_INT, &flags, RPC_INT, &mode,
RPC_END);
handle = open (arg, flags, mode);
send_status (handle, errno);
g_free (arg);
}
static void
do_read (void)
{
int handle, count, n;
void *data;
rpc_get (msock, RPC_INT, &handle, RPC_INT, &count, RPC_END);
data = malloc (count);
if (!data) {
send_status (-1, ENOMEM);
return;
}
if (verbose)
printf ("count=%d\n", count);
n = read (handle, data, count);
if (verbose)
printf ("result=%d\n", n);
if (n < 0) {
send_status (-1, errno);
return;
}
send_status (n, 0);
rpc_send (msock, RPC_BLOCK, n, data, RPC_END);
g_free (data);
}
static void
do_write (void)
{
int handle, count, status, written = 0;
char buf[8192];
rpc_get (msock, RPC_INT, &handle, RPC_INT, &count, RPC_END);
status = 0;
while (count) {
int nbytes = count > 8192 ? 8192 : count;
rpc_get (msock, RPC_BLOCK, nbytes, buf, RPC_END);
status = write (handle, buf, nbytes);
if (status < 0) {
send_status (status, errno);
return;
}
/* FIXED: amount written must be returned to caller */
written += status;
if (status < nbytes) {
send_status (written, errno);
return;
}
count -= nbytes;
}
send_status (written, errno);
}
static void
do_lseek (void)
{
int handle, offset, whence, status;
rpc_get (msock, RPC_INT, &handle, RPC_INT, &offset, RPC_INT, &whence,
RPC_END);
status = lseek (handle, offset, whence);
send_status (status, errno);
}
static void
do_close (void)
{
int handle, status;
rpc_get (msock, RPC_INT, &handle, RPC_END);
status = close (handle);
send_status (status, errno);
}
/* }}} */
/* {{{ Stat family routines */
static void
send_time (int sock, time_t time)
{
if (clnt_version == 1) {
char *ct;
int month;
ct = ctime (&time);
ct[3] = ct[10] = ct[13] = ct[16] = ct[19] = 0;
/* Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec */
if (ct[4] == 'J') {
if (ct[5] == 'a') {
month = 0;
} else
month = (ct[6] == 'n') ? 5 : 6;
} else if (ct[4] == 'F') {
month = 1;
} else if (ct[4] == 'M') {
month = (ct[6] == 'r') ? 2 : 5;
} else if (ct[4] == 'A') {
month = (ct[5] == 'p') ? 3 : 7;
} else if (ct[4] == 'S') {
month = 8;
} else if (ct[4] == 'O') {
month = 9;
} else if (ct[4] == 'N') {
month = 10;
} else
month = 11;
rpc_send (msock, RPC_INT, atoi (&ct[17]), /* sec */
RPC_INT, atoi (&ct[14]), /* min */
RPC_INT, atoi (&ct[11]), /* hour */
RPC_INT, atoi (&ct[8]), /* mday */
RPC_INT, atoi (&ct[20]), /* year */
RPC_INT, month, /* month */
RPC_END);
} else {
long ltime = (long) time;
char buf[BUF_SMALL];
snprintf (buf, sizeof (buf), "%lx", ltime);
rpc_send (msock, RPC_STRING, buf, RPC_END);
}
}
static void
send_stat_info (struct stat *st)
{
long mylong;
int blocks =
#ifdef HAVE_STRUCT_STAT_ST_BLOCKS
st->st_blocks;
#else
st->st_size / 1024;
#endif
#ifdef HAVE_STRUCT_STAT_ST_RDEV
mylong = st->st_rdev;
#else
mylong = 0;
#endif
rpc_send (msock, RPC_INT, (long) mylong, RPC_INT, (long) st->st_ino,
RPC_INT, (long) st->st_mode, RPC_INT, (long) st->st_nlink,
RPC_INT, (long) st->st_uid, RPC_INT, (long) st->st_gid,
RPC_INT, (long) st->st_size, RPC_INT, (long) blocks,
RPC_END);
send_time (msock, st->st_atime);
send_time (msock, st->st_mtime);
send_time (msock, st->st_ctime);
}
static void
do_lstat (void)
{
struct stat st;
char *file;
int n;
rpc_get (msock, RPC_STRING, &file, RPC_END);
n = lstat (file, &st);
send_status (n, errno);
if (n >= 0)
send_stat_info (&st);
g_free (file);
}
static void
do_fstat (void)
{
int handle;
int n;
struct stat st;
rpc_get (msock, RPC_INT, &handle, RPC_END);
n = fstat (handle, &st);
send_status (n, errno);
if (n < 0)
return;
send_stat_info (&st);
}
static void
do_stat (void)
{
struct stat st;
int n;
char *file;
rpc_get (msock, RPC_STRING, &file, RPC_END);
n = stat (file, &st);
send_status (n, errno);
if (n >= 0)
send_stat_info (&st);
g_free (file);
}
/* }}} */
/* {{{ Directory lookup operations */
static struct {
int used;
DIR *dirs[OPENDIR_HANDLES];
char *names[OPENDIR_HANDLES];
} mcfs_DIR;
static void
close_handle (int handle)
{
if (mcfs_DIR.used > 0)
mcfs_DIR.used--;
if (mcfs_DIR.dirs[handle])
closedir (mcfs_DIR.dirs[handle]);
g_free (mcfs_DIR.names[handle]);
mcfs_DIR.dirs[handle] = 0;
mcfs_DIR.names[handle] = 0;
}
static void
do_opendir (void)
{
int handle, i;
char *arg;
DIR *p;
rpc_get (msock, RPC_STRING, &arg, RPC_END);
if (mcfs_DIR.used == OPENDIR_HANDLES) {
send_status (-1, ENFILE); /* Error */
g_free (arg);
return;
}
handle = -1;
for (i = 0; i < OPENDIR_HANDLES; i++) {
if (mcfs_DIR.dirs[i] == 0) {
handle = i;
break;
}
}
if (handle == -1) {
send_status (-1, EMFILE);
g_free (arg);
if (!inetd_started)
fprintf (stderr,
"OOPS! you have found a bug in mc - do_opendir()!\n");
return;
}
if (verbose)
printf ("handle=%d\n", handle);
p = opendir (arg);
if (p) {
mcfs_DIR.dirs[handle] = p;
mcfs_DIR.names[handle] = arg;
mcfs_DIR.used++;
/* Because 0 is an error value */
rpc_send (msock, RPC_INT, handle + 1, RPC_INT, 0, RPC_END);
} else {
send_status (-1, errno);
g_free (arg);
}
}
/* Sends the complete directory listing, as well as the stat information */
static void
do_readdir (void)
{
struct dirent *dirent;
struct stat st;
int handle, n;
rpc_get (msock, RPC_INT, &handle, RPC_END);
if (!handle) {
rpc_send (msock, RPC_INT, 0, RPC_END);
return;
}
/* We incremented it in opendir */
handle--;
while ((dirent = readdir (mcfs_DIR.dirs[handle]))) {
int fname_len;
char *fname;
int length = NLENGTH (dirent);
rpc_send (msock, RPC_INT, length, RPC_END);
rpc_send (msock, RPC_BLOCK, length, dirent->d_name, RPC_END);
fname_len =
strlen (mcfs_DIR.names[handle]) + strlen (dirent->d_name) + 2;
fname = malloc (fname_len);
snprintf (fname, fname_len, "%s/%s", mcfs_DIR.names[handle],
dirent->d_name);
n = lstat (fname, &st);
g_free (fname);
send_status (n, errno);
if (n >= 0)
send_stat_info (&st);
}
rpc_send (msock, RPC_INT, 0, RPC_END);
}
static void
do_closedir (void)
{
int handle;
rpc_get (msock, RPC_INT, &handle, RPC_END);
close_handle (handle - 1);
}
/* }}} */
/* {{{ Operations with one and two file name argument */
static void
do_chdir (void)
{
char *file;
int status;
rpc_get (msock, RPC_STRING, &file, RPC_END);
status = chdir (file);
send_status (status, errno);
g_free (file);
}
static void
do_rmdir (void)
{
char *file;
int status;
rpc_get (msock, RPC_STRING, &file, RPC_END);
status = rmdir (file);
send_status (status, errno);
g_free (file);
}
static void
do_mkdir (void)
{
char *file;
int mode, status;
rpc_get (msock, RPC_STRING, &file, RPC_INT, &mode, RPC_END);
status = mkdir (file, mode);
send_status (status, errno);
g_free (file);
}
static void
do_mknod (void)
{
char *file;
int mode, dev, status;
rpc_get (msock, RPC_STRING, &file, RPC_INT, &mode, RPC_INT, &dev,
RPC_END);
status = mknod (file, mode, dev);
send_status (status, errno);
g_free (file);
}
static void
do_readlink (void)
{
char buffer[2048];
char *file;
int n;
rpc_get (msock, RPC_STRING, &file, RPC_END);
n = readlink (file, buffer, 2048 - 1);
send_status (n, errno);
if (n >= 0) {
buffer[n] = 0;
rpc_send (msock, RPC_STRING, buffer, RPC_END);
}
g_free (file);
}
static void
do_unlink (void)
{
char *file;
int status;
rpc_get (msock, RPC_STRING, &file, RPC_END);
status = unlink (file);
send_status (status, errno);
g_free (file);
}
static void
do_rename (void)
{
char *f1, *f2;
int status;
rpc_get (msock, RPC_STRING, &f1, RPC_STRING, &f2, RPC_END);
status = rename (f1, f2);
send_status (status, errno);
g_free (f1);
g_free (f2);
}
static void
do_symlink (void)
{
char *f1, *f2;
int status;
rpc_get (msock, RPC_STRING, &f1, RPC_STRING, &f2, RPC_END);
status = symlink (f1, f2);
send_status (status, errno);
g_free (f1);
g_free (f2);
}
static void
do_link (void)
{
char *f1, *f2;
int status;
rpc_get (msock, RPC_STRING, &f1, RPC_STRING, &f2, RPC_END);
status = link (f1, f2);
send_status (status, errno);
g_free (f1);
g_free (f2);
}
/* }}} */
/* {{{ Misc commands */
static void
do_gethome (void)
{
rpc_send (msock, RPC_STRING, (home_dir) ? home_dir : "/", RPC_END);
}
static void
do_getupdir (void)
{
rpc_send (msock, RPC_STRING, (up_dir) ? up_dir : "/", RPC_END);
}
static void
do_chmod (void)
{
char *file;
int mode, status;
rpc_get (msock, RPC_STRING, &file, RPC_INT, &mode, RPC_END);
status = chmod (file, mode);
send_status (status, errno);
g_free (file);
}
static void
do_chown (void)
{
char *file;
int owner, group, status;
rpc_get (msock, RPC_STRING, &file, RPC_INT, &owner, RPC_INT, &group,
RPC_END);
status = chown (file, owner, group);
send_status (status, errno);
g_free (file);
}
static void
do_utime (void)
{
char *file;
int status;
long atime;
long mtime;
char *as;
char *ms;
struct utimbuf times;
rpc_get (msock, RPC_STRING, &file, RPC_STRING, &as, RPC_STRING, &ms,
RPC_END);
sscanf (as, "%lx", &atime);
sscanf (ms, "%lx", &mtime);
if (verbose)
printf ("Got a = %s, m = %s, comp a = %ld, m = %ld\n", as, ms,
atime, mtime);
g_free (as);
g_free (ms);
times.actime = (time_t) atime;
times.modtime = (time_t) mtime;
status = utime (file, &times);
send_status (status, errno);
g_free (file);
}
static void
do_quit (void)
{
quit_server = 1;
}
#ifdef HAVE_PAM
struct user_pass {
const char *username;
const char *password;
};
static int
mc_pam_conversation (int messages, const struct pam_message **msg,
struct pam_response **resp, void *appdata_ptr)
{
struct pam_response *r;
struct user_pass *up = appdata_ptr;
int status;
r = (struct pam_response *) malloc (sizeof (struct pam_response) *
messages);
if (!r)
return PAM_CONV_ERR;
*resp = r;
for (status = PAM_SUCCESS; messages--; msg++, r++) {
switch ((*msg)->msg_style) {
case PAM_PROMPT_ECHO_ON:
r->resp = strdup (up->username);
r->resp_retcode = PAM_SUCCESS;
break;
case PAM_PROMPT_ECHO_OFF:
r->resp = strdup (up->password);
r->resp_retcode = PAM_SUCCESS;
break;
case PAM_ERROR_MSG:
r->resp = NULL;
r->resp_retcode = PAM_SUCCESS;
break;
case PAM_TEXT_INFO:
r->resp = NULL;
r->resp_retcode = PAM_SUCCESS;
break;
}
}
return status;
}
static struct pam_conv conv = { &mc_pam_conversation, NULL };
/* Return 0 if authentication failed, 1 otherwise */
static int
mc_pam_auth (const char *username, const char *password)
{
pam_handle_t *pamh;
struct user_pass up;
int status;
up.username = username;
up.password = password;
conv.appdata_ptr = &up;
if ((status =
pam_start ("mcserv", username, &conv, &pamh)) != PAM_SUCCESS)
goto failed_pam;
if ((status = pam_authenticate (pamh, 0)) != PAM_SUCCESS)
goto failed_pam;
if ((status = pam_acct_mgmt (pamh, 0)) != PAM_SUCCESS)
goto failed_pam;
if ((status = pam_setcred (pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS)
goto failed_pam;
pam_end (pamh, status);
return 0;
failed_pam:
pam_end (pamh, status);
return 1;
}
#else /* !HAVE_PAM */
/* Keep reading until we find a \n */
static int
next_line (int socket)
{
char c;
while (1) {
if (read (socket, &c, 1) <= 0)
return 0;
if (c == '\n')
return 1;
}
}
static int
ftp_answer (int sock, const char *text)
{
char answer[4];
next_line (sock);
socket_read_block (sock, answer, 3);
answer[3] = 0;
if (strcmp (answer, text) == 0)
return 1;
return 0;
}
static int
send_string (int sock, const char *string)
{
return socket_write_block (sock, string, strlen (string));
}
static int
do_ftp_auth (const char *username, const char *password)
{
struct sockaddr_in local_address;
unsigned long inaddr;
int my_socket;
char answer[4];
memset ((char *) &local_address, 0, sizeof (local_address));
local_address.sin_family = AF_INET;
/* FIXME: extract the ftp port with the proper function */
local_address.sin_port = htons (21);
/* Convert localhost to usable format */
if ((inaddr = inet_addr ("127.0.0.1")) != INADDR_NONE)
memcpy ((char *) &local_address.sin_addr, (char *) &inaddr,
sizeof (inaddr));
if ((my_socket = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
if (!isDaemon)
fprintf (stderr, "do_auth: can't create socket\n");
return 0;
}
if (connect
(my_socket, (struct sockaddr *) &local_address,
sizeof (local_address)) < 0) {
fprintf (stderr,
"do_auth: can't connect to ftp daemon for authentication\n");
close (my_socket);
return 0;
}
send_string (my_socket, "user ");
send_string (my_socket, username);
send_string (my_socket, "\r\n");
if (!ftp_answer (my_socket, "331")) {
send_string (my_socket, "quit\r\n");
close (my_socket);
return 0;
}
next_line (my_socket); /* Eat all the line */
send_string (my_socket, "pass ");
send_string (my_socket, password);
send_string (my_socket, "\r\n");
socket_read_block (my_socket, answer, 3);
answer[3] = 0;
send_string (my_socket, "\r\n");
send_string (my_socket, "quit\r\n");
close (my_socket);
if (strcmp (answer, "230") == 0)
return 1;
return 0;
}
#ifdef HAVE_CRYPT
static int
do_classic_auth (const char *username, const char *password)
{
int ret = 0;
const char *encr_pwd = NULL;
struct passwd *pw;
#ifdef HAVE_SHADOW
struct spwd *spw;
#endif
if ((pw = getpwnam (username)) == 0)
return 0;
#ifdef HAVE_SHADOW
setspent ();
/* Password expiration is not checked! */
if ((spw = getspnam (username)) == NULL)
encr_pwd = "*";
else
encr_pwd = spw->sp_pwdp;
endspent ();
#else
encr_pwd = pw->pw_passwd;
#endif
if (strcmp (crypt (password, encr_pwd), encr_pwd) == 0)
ret = 1;
endpwent ();
return ret;
}
#endif /* HAVE_CRYPT */
#endif /* !HAVE_PAM */
/* Try to authenticate the user based on:
- PAM if the system has it, else it checks:
- pwdauth if the system supports it.
- conventional auth (check salt on /etc/passwd, crypt, and compare
- try to contact the local ftp server and login (if -f flag used)
*/
static int
do_auth (const char *username, const char *password)
{
int auth = 0;
struct passwd *this;
if (strcmp (username, "anonymous") == 0)
username = "ftp";
#ifdef HAVE_PAM
if (mc_pam_auth (username, password) == 0)
auth = 1;
#else /* if there is no pam */
#ifdef HAVE_PWDAUTH
if (pwdauth (username, password) == 0)
auth = 1;
else
#endif
#ifdef HAVE_CRYPT
if (do_classic_auth (username, password))
auth = 1;
else
#endif
if (ftp)
auth = do_ftp_auth (username, password);
#endif /* not pam */
if (!auth)
return 0;
this = getpwnam (username);
if (this == 0)
return 0;
if (chdir (this->pw_dir) == -1)
return 0;
if (this->pw_dir[strlen (this->pw_dir) - 1] == '/')
home_dir = strdup (this->pw_dir);
else {
char *new_home_dir = malloc (strlen (this->pw_dir) + 2);
if (new_home_dir) {
strcpy (new_home_dir, this->pw_dir);
strcat (new_home_dir, "/");
home_dir = new_home_dir;
} else
home_dir = "/";
}
if (setgid (this->pw_gid) == -1)
return 0;
#ifdef HAVE_INITGROUPS
#ifdef NGROUPS_MAX
if (NGROUPS_MAX > 1 && initgroups (this->pw_name, this->pw_gid))
return 0;
#endif
#endif
#if defined (HAVE_SETUID)
if (setuid (this->pw_uid))
return 0;
#elif defined (HAVE_SETREUID)
if (setreuid (this->pw_uid, this->pw_uid))
return 0;
#endif
/* If the setuid call failed, then deny access */
/* This should fix the problem on those machines with strange setups */
if (getuid () != this->pw_uid)
return 0;
if (strcmp (username, "ftp") == 0)
chroot (this->pw_dir);
endpwent ();
return auth;
}
#if 0
static int
do_rauth (int socket)
{
struct sockaddr_in from;
struct hostent *hp;
if (getpeername (0, (struct sockaddr *) &from, &fromlen) < 0)
return 0;
from.sin_port = ntohs ((unsigned short) from.sin_port);
/* Strange, this should not happend */
if (from.sin_family != AF_INET)
return 0;
hp = gethostbyaddr ((char *) &fromp.sin_addr, sizeof (struct in_addr),
fromp.sin_family);
}
#endif
static int
do_rauth (int msock)
{
return 0;
}
static void
login_reply (int logged_in)
{
rpc_send (msock, RPC_INT, logged_in ? MC_LOGINOK : MC_INVALID_PASS,
RPC_END);
}
/* FIXME: Implement the anonymous login */
static void
do_login (void)
{
char *username;
char *password;
int result;
rpc_get (msock, RPC_LIMITED_STRING, &up_dir, RPC_LIMITED_STRING,
&username, RPC_END);
if (verbose)
printf ("username: %s\n", username);
if (r_auth) {
logged_in = do_rauth (msock);
if (logged_in) {
login_reply (logged_in);
return;
}
}
rpc_send (msock, RPC_INT, MC_NEED_PASSWORD, RPC_END);
rpc_get (msock, RPC_INT, &result, RPC_END);
if (result == MC_QUIT)
DO_QUIT_VOID ();
if (result != MC_PASS) {
if (verbose)
printf ("do_login: Unknown response: %d\n", result);
DO_QUIT_VOID ();
}
rpc_get (msock, RPC_LIMITED_STRING, &password, RPC_END);
logged_in = do_auth (username, password);
endpwent ();
login_reply (logged_in);
}
/* }}} */
/* {{{ Server and dispatching functions */
/* This structure must be kept in synch with mcfs.h enums */
static const struct _command {
const char *command;
void (*callback) (void);
} commands[] = {
{
"open", do_open}, {
"close", do_close}, {
"read", do_read}, {
"write", do_write}, {
"opendir", do_opendir}, {
"readdir", do_readdir}, {
"closedir", do_closedir}, {
"stat ", do_stat}, {
"lstat ", do_lstat}, {
"fstat", do_fstat}, {
"chmod", do_chmod}, {
"chown", do_chown}, {
"readlink ", do_readlink}, {
"unlink", do_unlink}, {
"rename", do_rename}, {
"chdir ", do_chdir}, {
"lseek", do_lseek}, {
"rmdir", do_rmdir}, {
"symlink", do_symlink}, {
"mknod", do_mknod}, {
"mkdir", do_mkdir}, {
"link", do_link}, {
"gethome", do_gethome}, {
"getupdir", do_getupdir}, {
"login", do_login}, {
"quit", do_quit}, {
"utime", do_utime}};
static int ncommands = sizeof (commands) / sizeof (struct _command);
static void
exec_command (int command)
{
if (command < 0 || command >= ncommands
|| commands[command].command == 0) {
fprintf (stderr, "Got unknown command: %d\n", command);
DO_QUIT_VOID ();
}
if (verbose)
printf ("Command: %s\n", commands[command].command);
(*commands[command].callback) ();
}
static void
check_version (void)
{
int version;
rpc_get (msock, RPC_INT, &version, RPC_END);
if (version >= 1 && version <= RPC_PROGVER)
rpc_send (msock, RPC_INT, MC_VERSION_OK, RPC_END);
else
rpc_send (msock, RPC_INT, MC_VERSION_MISMATCH, RPC_END);
clnt_version = version;
}
/* This routine is called by rpc_get/rpc_send when the connection is closed */
void
tcp_invalidate_socket (int sock)
{
if (verbose)
printf ("Connection closed\n");
DO_QUIT_VOID ();
}
static void
server (int sock)
{
int command;
msock = sock;
quit_server = 0;
check_version ();
do {
if (rpc_get (sock, RPC_INT, &command, RPC_END)
&& (logged_in || command == MC_LOGIN))
exec_command (command);
} while (!quit_server);
}
/* }}} */
/* {{{ Net support code */
static const char *
get_client (int portnum)
{
int sock, newsocket;
unsigned int clilen;
struct sockaddr_in client_address, server_address;
int yes = 1;
if ((sock = socket (AF_INET, SOCK_STREAM, 0)) < 0)
return "Cannot create socket";
/* Use this to debug: */
if (setsockopt
(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &yes, sizeof (yes)) < 0)
return "setsockopt failed";
memset ((char *) &server_address, 0, sizeof (server_address));
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl (INADDR_ANY);
server_address.sin_port = htons (portnum);
if (bind
(sock, (struct sockaddr *) &server_address,
sizeof (server_address)) < 0)
return "Cannot bind";
listen (sock, 5);
for (;;) {
int child;
clilen = sizeof (client_address);
newsocket =
accept (sock, (struct sockaddr *) &client_address, &clilen);
if (isDaemon && (child = fork ())) {
int status;
close (newsocket);
waitpid (child, &status, 0);
continue;
}
if (isDaemon && fork ())
exit (0);
server (newsocket);
close (newsocket);
return 0;
}
}
#ifdef HAVE_PMAP_SET
static void
signal_int_handler (int sig)
{
(void) sig;
pmap_unset (RPC_PROGNUM, RPC_PROGVER);
}
#endif
#ifndef IPPORT_RESERVED
#define IPPORT_RESERVED 1024
#endif
static int
get_port_number (void)
{
int port = 0;
#ifdef HAVE_RRESVPORT
int start_port = IPPORT_RESERVED;
port = rresvport (&start_port);
if (port == -1) {
if (geteuid () == 0) {
fprintf (stderr,
"Cannot bind the server on a reserved port\n");
DO_QUIT_NONVOID (-1);
}
port = 0;
}
#endif
if (port)
return port;
port = mcserver_port;
return port;
}
static void
register_port (int portnum, int abort_if_fail)
{
#ifdef HAVE_PMAP_SET
/* Register our service with the portmapper */
/* protocol: pmap_set (prognum, versnum, protocol, portp) */
if (pmap_set (RPC_PROGNUM, RPC_PROGVER, IPPROTO_TCP, portnum))
signal (SIGINT, signal_int_handler);
else {
fprintf (stderr, "Cannot register service with portmapper\n");
if (abort_if_fail)
exit (1);
}
#else
if (abort_if_fail) {
fprintf (stderr,
"This system lacks port registration, try using the -p\n"
"flag to force installation at a given port");
}
#endif
}
/* }}} */
int
main (int argc, char *argv[])
{
const char *result;
int c;
while ((c = getopt (argc, argv, "fdiqp:v")) != -1) {
switch (c) {
case 'd':
isDaemon = 1;
verbose = 0;
break;
case 'v':
verbose = 1;
break;
case 'f':
ftp = 1;
break;
case 'q':
verbose = 0;
break;
case 'p':
portnum = atoi (optarg);
break;
case 'i':
inetd_started = 1;
break;
case 'r':
r_auth = 1;
break;
default:
fprintf (stderr,
"Usage is: mcserv [options] [-p portnum]\n\n"
"options are:\n" "-d become a daemon (sets -q)\n"
"-q quiet mode\n"
/* "-r use rhost based authentication\n" */
#ifndef HAVE_PAM
"-f force ftp authentication\n"
#endif
"-v verbose mode\n"
"-p to specify a port number to listen\n");
exit (0);
}
}
if (isDaemon && fork ())
exit (0);
if (portnum == 0)
portnum = get_port_number ();
if (portnum != -1) {
register_port (portnum, 0);
if (verbose)
printf ("Using port %d\n", portnum);
if ((result = get_client (portnum)))
perror (result);
#ifdef HAVE_PMAP_SET
if (!isDaemon)
pmap_unset (RPC_PROGNUM, RPC_PROGVER);
#endif
}
exit (return_code);
}
/* FIXME: This function should not be used in mcserv */
void
vfs_die (const char *m)
{
fputs (m, stderr);
exit (1);
}