/*
* Copyright (C) 1999-2004 Joachim Wieland <joe@mcknight.de>
*
* 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.,
* 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*/
#include <ctype.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <unistd.h>
#ifdef HAVE_GETOPT_LONG
#include <getopt.h>
#else
#include "support/getopt.h"
#include "support/getopt.c"
#include "support/getopt1.c"
#endif
#include "jftpgw.h"
void reset_loginfo(struct loginfo_st*);
void print_version(void);
void print_help(void);
struct clientinfo clntinfo;
struct serverinfo srvinfo;
extern struct loginfo_st loginfo;
extern struct log_cmd_st lcs;
extern struct uidstruct runasuser;
extern struct connliststruct* connected_clients;
extern struct slist_t* passcmd_white_list, *passcmd_black_list;
extern int should_read_config;
int timeout;
static struct option const long_option_arr[] =
{
{ "single", no_argument, NULL, 's' },
{ "inetd", no_argument, NULL, 'i' },
#ifdef HAVE_LIBWRAP
{ "tcpwrap", no_argument, NULL, 't' },
#endif
{ "encrypt", no_argument, NULL, 'e' },
{ "version", no_argument, NULL, 'v' },
{ "version", no_argument, NULL, 'V' },
{ "help", no_argument, NULL, 'h' },
{ "configfile", required_argument, NULL, 'f' },
{ NULL, 0, NULL, 0 }
};
int main(int argc, char** argv) {
/* We bind to 0.0.0.0, but our real IP is the one returned by the
* PASV command */
struct sigaction sa, cf;
int ret;
#ifdef RLIMIT_CORE
/* set RLIMIT_CORE to 0 */
struct rlimit rl = { 0, 0 };
if (setrlimit(RLIMIT_CORE, &rl) < 0) {
perror("setrlimit");
return -1;
}
#endif
/* set the name of the config file to an initial value, it may be
* overwritten later
* */
srvinfo.conffilename = strdup(DEFAULTCONFFILE);
enough_mem(srvinfo.conffilename);
/* No login done yet */
clntinfo.login.stage = LOGIN_ST_NOT_CONNECTED;
/* default: Multithread, i.e. fork for each connection */
srvinfo.multithread = 1;
srvinfo.tcp_wrapper = 0;
srvinfo.servertype = SERVERTYPE_STANDALONE;
srvinfo.main_server_pid = 0;
srvinfo.chrooted = 0;
if (strrchr(argv[0], '/')) {
srvinfo.binaryname = strdup(strrchr(argv[0], '/') + 1);
} else {
srvinfo.binaryname = strdup(argv[0]);
}
enough_mem(srvinfo.binaryname);
/* parse the command line */
while ((ret=getopt_long(argc, argv, "isetvVhf:", long_option_arr, NULL)) != EOF) {
switch (ret) {
case 0: break;
case 'i':
srvinfo.servertype = SERVERTYPE_INETD;
srvinfo.multithread = 0;
break;
case 's':
srvinfo.multithread = 0;
break;
#ifdef HAVE_LIBWRAP
case 't':
srvinfo.tcp_wrapper = 1;
break;
#endif
case 'e':
encrypt_password();
exit(0);
case 'v':
case 'V':
print_version();
exit(0);
case 'h':
print_help();
exit(0);
case 'f':
if (set_conffilename(optarg) < 0) {
jlog(2, "something is wrong with the name of the configuration file");
return 1;
}
break;
default:
break;
}
}
/* Read the configuration */
memset(&loginfo, 0, sizeof(struct loginfo_st));
loginfo.debuglevel = 6;
srvinfo.ready_to_serve = SVR_LAUNCH_CMDLINE;
srvinfo.chrootdir_saved = (char*) 0;
ret = read_config(srvinfo.conffilename);
if (ret) {
return 1;
}
/* Drop privileges right after the start of the program. Right after
* reading the configuration file */
if (stage_action("start") < 0) {
return -1;
}
/* init the logfiles */
if (changeid(PRIV, UID, "log_init()") < 0) { return -1; }
if (log_init() < 0) {
changeid(UNPRIV, EUID, "log_init()");
return -1;
}
if (changeid(UNPRIV, EUID, "log_init()") < 0) { return -1; }
srvinfo.ready_to_serve = SVR_LAUNCH_LOGFILES;
/* initiate a few values */
clntinfo.transparent = TRANSPARENT_NO;
clntinfo.destinationport = 0;
clntinfo.transfermode_havetoconvert = CONV_NOTCONVERT;
clntinfo.transfermode_client = TRANSFER_BINARY;
clntinfo.transfermode_server = TRANSFER_BINARY;
/* Install the signal handlers */
/* for SIGCHLD install just the reap function
*
* the register/unregister thing is installed after we've bound
* successfully */
sa.sa_handler = reap_chld_info;
sigemptyset (&sa.sa_mask);
#ifndef WINDOWS
sa.sa_flags = SA_RESTART;
#endif
sigaction (SIGCHLD, &sa, 0);
cf.sa_handler = read_default_conf;
should_read_config = 0;
sigemptyset(&cf.sa_mask);
#ifndef WINDOWS
cf.sa_flags = SA_RESTART;
#endif
sigaction (SIGHUP, &cf, 0);
cf.sa_handler = terminate;
sigemptyset(&cf.sa_mask);
sigaction (SIGTERM, &cf, 0);
sigaction (SIGQUIT, &cf, 0);
sigaction (SIGABRT, &cf, 0);
/* Ignore SIGALRM */
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGALRM, &sa, 0);
clntinfo.user = clntinfo.pass = clntinfo.destination = (char*) 0;
clntinfo.before_forward.user = clntinfo.before_forward.destination
= (char*) 0;
clntinfo.anon_user = (char*) 0;
clntinfo.throughput = 0;
clntinfo.boundsocket_list = (int*) 0;
clntinfo.server_ip = clntinfo.client_ip = clntinfo.addr_to_server
= clntinfo.addr_to_client = (unsigned long int) UINT_MAX;
atexit(closedescriptors);
if (srvinfo.servertype == SERVERTYPE_STANDALONE &&
(ret = waitclient(config_get_option("listen"),
&clntinfo)) < 0) {
/* An error occured */
return ret;
}
if (srvinfo.servertype == SERVERTYPE_INETD) {
close(1);
dup2(0, 10);
close(0);
if (inetd_connected(10, &clntinfo) < 0) {
return -1;
}
}
ret = handle_login(&clntinfo);
jlog(8, "Exited from handle_login, ret: %d", ret);
if (!ret) {
ret = handle_cmds(&clntinfo);
jlog(8, "Exited from handle_cmds");
}
jlog(8, "Exiting");
if (ret) {
return 1;
} else {
return 0;
}
}
int daemonize()
{
int childpid;
if( (childpid = fork()) < 0) return(-1);
else if(childpid > 0) exit(0);
errno = 0;
chdir("/");
setsid();
return(0);
}
void read_default_conf(int signo) {
should_read_config = 1;
}
int reread_config() {
int ret;
sigset_t sigset, oldset;
jlog(6, "SIGHUP received - Rereading config file");
/* block other SIGHUP signals */
sigemptyset(&sigset);
sigemptyset(&oldset);
sigaddset(&sigset, SIGCHLD);
while ((ret = sigprocmask(SIG_BLOCK, &sigset,
&oldset)) < 0 && errno == EINTR) {}
if (ret < 0) {
jlog(2, "Error blocking signals: %s", strerror(errno));
}
config_delete_config();
destroy_active_portrange();
destroy_passive_portrange();
ret = read_config(srvinfo.conffilename);
if (ret) {
/* the following line is a BADF if we had a SIGHUP */
jlog(1, "Error rereading config file. Exiting.");
exit(2);
}
/* re-register all connected clients */
config_counter_add_connected(connected_clients);
reset_loginfo(&loginfo);
if (stage_action("reread") < 0) {
return -1;
}
should_read_config = 0;
while ((ret = sigprocmask(SIG_UNBLOCK, &sigset,
&oldset)) < 0 && errno == EINTR) {}
if (ret < 0) {
jlog(2, "Error unblocking signal mask: %s", strerror(errno));
return -1;
}
return 0;
}
void terminate (int signo) {
/* exit is not POSIX-reentrant but in SVR4 SVID */
exit(0);
}
int stage_action(const char* stage) {
char* reason = char_append("stage_action() in stage ", stage);
changeid(PRIV, UID, reason);
free(reason);
if (change_root(stage) < 0) {
changeid(UNPRIV, EUID, "change_root() failed");
return -1;
}
if (dropprivileges(stage) < 0) {
changeid(UNPRIV, EUID, "dropprivileges() failed");
return -1;
}
changeid(UNPRIV, EUID, "log_init()");
return 0;
}
void print_version(void) {
printf(PACKAGE" v"JFTPGW_VERSION);
#ifdef HAVE_CRYPT
printf(" - crypt support enabled");
#else
printf(" - without crypt support");
#endif
#ifdef HAVE_LINUX_NETFILTER_IPV4_H
printf(" - netfilter support enabled");
#else
printf(" - without netfilter support");
#endif
printf("\n");
#ifdef HAVE_LIBWRAP
printf("libwrap support enabled");
#else
printf("without libwrap support");
#endif
#ifdef HAVE_SIOCGIFADDR
printf(" - can get IPs from interfaces");
#else
printf(" - can't get IPs from interfaces");
#endif
#ifdef HAVE_ICMP_SUPPORT
printf(" - ICMP support");
#else
printf(" - no ICMP support");
#endif
printf("\n");
}
void print_help(void) {
print_version();
printf("usage: jftpgw [OPTION]\n\n");
printf("Valid options:\n");
printf(" -h, --help Display this help text\n");
printf(" -e, --encrypt Use jftpgw to obtain an encrypted password\n");
printf(" -f, --configfile file Load file instead of default config file\n");
printf(" -s, --single Run jftpgw single threaded (do not fork)\n");
printf(" -i, --inetd Run jftpgw from inetd superserver\n");
#ifdef HAVE_LIBWRAP
printf(" -t, --tcpwrap Use libwrap for access control\n");
#endif
printf(" -V, -v, --version Display the version\n");
printf("\nReport bugs to Joachim Wieland <joe@mcknight.de>\n");
}
void removepidfile(void) {
const char* option;
if (getpid() != srvinfo.main_server_pid) {
/* the program has not become a daemon */
return;
}
option = config_get_option("pidfile");
if (option) {
int i;
if (changeid(PRIV, UID,
"Changing ID to root (unlink pidfile)") < 0) {
return;
}
i = unlink(option);
if (i) {
jlog(3, "Could not unlink the pidfile %s", option);
}
if (changeid(UNPRIV, EUID,
"Changing id back (deleting pidfile)") < 0) {
return;
}
}
}
void sayterminating(void) {
jlog(6, "jftpgw terminating");
}
void closedescriptors(void) {
int i;
jlog(9, "In closedescriptors()");
/* free the log info structure. Free the members, the structure for
* itself is on the stack */
/* lcs.cmd must not be freed, it's lcs.cmd = buffer; */
/* the same for lcs.filename */
if (lcs.svrip) { free(lcs.svrip); }
if (lcs.clntip) { free(lcs.clntip); }
if (lcs.ifipclnt) { free(lcs.ifipclnt); }
if (lcs.ifipsvr) { free(lcs.ifipsvr); }
if (lcs.userlogin) { free(lcs.userlogin); }
if (lcs.usereffective) { free(lcs.usereffective); }
if (lcs.userforwarded) { free(lcs.userforwarded); }
if (srvinfo.conffilename) { free(srvinfo.conffilename); }
/* close the logfiles and delete the structures */
reset_loginfo(&loginfo);
free(srvinfo.chrootdir_saved);
config_delete_config();
config_delete_backup();
/* maybe these variables are not yet allocated */
if (clntinfo.user) {
free(clntinfo.user);
}
if (clntinfo.pass) {
free(clntinfo.pass);
}
if (clntinfo.destination) {
free(clntinfo.destination);
}
if (runasuser.username) {
free(runasuser.username);
runasuser.username = 0;
}
if (runasuser.groupname) {
free(runasuser.groupname);
runasuser.groupname = 0;
}
if (clntinfo.anon_user) {
free(clntinfo.anon_user);
clntinfo.anon_user = (char*) 0;
}
if (clntinfo.before_forward.user) {
free(clntinfo.before_forward.user);
clntinfo.before_forward.user = (char*) 0;
}
if (clntinfo.before_forward.destination) {
free(clntinfo.before_forward.destination);
clntinfo.before_forward.destination = (char*) 0;
}
free_errstr();
free(srvinfo.binaryname);
slist_destroy(passcmd_white_list);
slist_destroy(passcmd_black_list);
destroy_active_portrange();
destroy_passive_portrange();
if (clntinfo.boundsocket_list) {
for (i = 0; i < clntinfo.boundsocket_niface; i++) {
close(clntinfo.boundsocket_list[i]);
}
free(clntinfo.boundsocket_list);
}
close(clntinfo.clientsocket);
close(clntinfo.serversocket);
}
char* chrooted_path(const char* path) {
char* newpath;
const char* p, *start;
const char* chrootdir = config_get_option("changerootdir");
if (!chrootdir) {
/* Try the saved path */
chrootdir = srvinfo.chrootdir_saved;
} else {
if (!srvinfo.chrootdir_saved) {
/* save it */
srvinfo.chrootdir_saved = strdup(chrootdir);
enough_mem(srvinfo.chrootdir_saved);
}
}
/* okay, maybe we are chrooted, see if the logfile path lies in the
* chrooted dir */
if (path && chrootdir && srvinfo.chrooted) {
p = strstr(path, chrootdir);
if (p == path) {
/* Yeah, we're inside the chrooted dir, strip it
* off.
* Calculate where to split, make sure, the new path
* starts with a slash. */
start = path + strlen(chrootdir);
while (start > path && *start == '/') {
start --;
}
start++;
newpath = strdup(start);
enough_mem(newpath);
/* just for the beauty... */
char_squeeze(newpath, '/');
return newpath;
}
}
/* maybe we are not chrooted or the chrootdir is not part of the
* path...*/
newpath = strdup(path);
enough_mem(newpath);
/* just for the beauty... */
char_squeeze(newpath, '/');
return newpath;
}
syntax highlighted by Code2HTML, v. 0.9.1