/*
* 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 "jftpgw.h"
#include "cmds.h"
extern int timeout;
/* these two functions reside in cmds.c */
int getuserdest(const char*, struct clientinfo*);
int getpasswd(const char*, struct clientinfo*);
int login(struct clientinfo*, int);
int transfer_initiate(struct conn_info_st* conn_info, int retrieve_from_cache){
int ret;
char *t;
time_t transfer_start;
ret = transfer_negotiate(conn_info->clntinfo);
if (ret == -1) {
/* dramatic error */
return 1;
}
if (ret == 0) {
/* if there was no error, transfer the file or
* listing */
transfer_start = time(NULL);
ret = transfer_transmit(conn_info->clntinfo);
conn_info->lcs->transfer_duration = time(NULL) - transfer_start;
} else {
/* there was an error in transfer_negotiate but it
* was not dramatic
*/
ret = 0;
}
if (ret == TRNSMT_SUCCESS) {
/* the transfer was okay */
if (retrieve_from_cache) {
say(conn_info->clntinfo->clientsocket,
"226 Transfer complete\r\n");
conn_info->lcs->respcode = 226;
conn_info->lcs->complete = 1;
} else {
t = passall(conn_info->clntinfo->serversocket,
conn_info->clntinfo->clientsocket);
conn_info->lcs->respcode = respcode(t);
conn_info->lcs->complete = conn_info->lcs->respcode == 226;
free(t);
}
} else {
if (ret == TRNSMT_ABORTED) {
/* an aborted transfer, everything's fine */
}
if (ret == TRNSMT_NOERRORMSG) {
/* do not generate an error message */
}
if (ret == TRNSMT_ERROR) {
/* generate error message */
if (timeout) {
jlog(2, "Timeout in %s line %d\n", __FILE__
,__LINE__);
err_time_readline(
conn_info->clntinfo->clientsocket);
} else {
err_readline(conn_info->clntinfo->serversocket);
}
return 1;
}
}
return ret;
}
int std_quit(const char* args, struct conn_info_st* conn_info) {
int ss = conn_info->clntinfo->serversocket;
int cs = conn_info->clntinfo->clientsocket;
/* Are we already connected to a server? */
if (conn_info->clntinfo->login.stage >= LOGIN_ST_CONNECTED) {
char* response;
say(ss, "QUIT\r\n");
response = passall(ss, cs);
conn_info->lcs->respcode = respcode(response);
free(response);
} else {
/* Generate an own goodbye message if we are
* not yet connected */
say(cs, "221 Goodbye...\r\n");
}
return CMD_QUIT;
}
int std_pasv(const char* args, struct conn_info_st* conn_info) {
char* answer = 0;
int ret;
if (conn_info->clntinfo->servermode == PASSIVE ||
conn_info->clntinfo->servermode == ASCLIENT) {
ret = pasvserver(conn_info->clntinfo);
if (ret == 0) {
conn_info->lcs->respcode = 227;
}
} else {
ret = activeserver(&answer, conn_info->clntinfo);
conn_info->lcs->respcode = respcode(answer);
free(answer);
}
if ( ! ret ) {
ret |= pasvclient(conn_info->clntinfo);
}
if (ret) {
if (errno == EPIPE) {
/* The remote server has closed the connection */
return CMD_ABORT;
}
return CMD_ERROR;
}
return CMD_HANDLED;
}
int std_port(const char* args, struct conn_info_st* conn_info) {
int ret = 0;
size_t sendbufsize;
int cs = conn_info->clntinfo->clientsocket;
struct sockaddr_in sin;
char *answer = 0;
char *sendbuf = 0;
if (portcommandcheck(args, &sin, conn_info->clntinfo) < 0) {
/* an error was already reported */
return -1;
}
conn_info->clntinfo->portcmd = strdup(args);
enough_mem(conn_info->clntinfo->portcmd);
if (conn_info->clntinfo->servermode == ACTIVE ||
conn_info->clntinfo->servermode == ASCLIENT) {
ret = activeserver(&answer, conn_info->clntinfo);
if (ret < 0) {
free(answer);
return CMD_ERROR;
}
/* Repeat the answer of the server */
sendbufsize = strlen(answer) + 3;
sendbuf = (char*) malloc(sendbufsize);
enough_mem(sendbuf);
snprintf(sendbuf, sendbufsize, "%s\r\n", answer);
say(cs, sendbuf);
conn_info->lcs->respcode = respcode(sendbuf);
free(sendbuf);
free(answer);
} else {
ret |= pasvserver(conn_info->clntinfo);
if (!ret) {
conn_info->lcs->respcode = 200;
say(cs, "200 PORT command successful.\r\n");
} else {
return CMD_ERROR;
}
}
return CMD_HANDLED;
}
int std_stor(const char* args, struct conn_info_st* conn_info) {
/* chop of the "STOR "/"STOU "/"APPE " prefix */
char* space = strchr(args, ' ');
if (space) {
conn_info->lcs->filename = space + 1;
} else {
conn_info->lcs->filename = args;
}
conn_info->lcs->direction = 'i';
conn_info->clntinfo->mode = STOR;
/* check the transfer mode - if they differ, change the mode to the
* server such that it matches the one of the client */
if (conn_info->clntinfo->transfermode_client !=
conn_info->clntinfo->transfermode_server) {
/* switch the transfer mode to TRANSFER_BINARY */
char type;
struct message answer;
if (conn_info->clntinfo->transfermode_client
== TRANSFER_ASCII) {
type = 'A';
conn_info->clntinfo->transfermode_server
= TRANSFER_ASCII;
} else {
type = 'I';
conn_info->clntinfo->transfermode_server
= TRANSFER_BINARY;
}
sayf(conn_info->clntinfo->serversocket, "TYPE %c\r\n", type);
answer = readall(conn_info->clntinfo->serversocket);
jlog(9, "switched type to %c for storing a file: %s", type, answer.lastmsg);
free(answer.fullmsg);
}
if (passcmd(args, conn_info->clntinfo) < 0) {
return CMD_ERROR;
}
if (conn_info->lcs->respcode != 125 && conn_info->lcs->respcode != 150) {
return CMD_ERROR;
}
if (transfer_initiate(conn_info, 0)) {
return CMD_ERROR;
}
return CMD_HANDLED;
}
int std_retr(const char* args, struct conn_info_st* conn_info) {
struct cache_filestruct cfs;
struct message answer;
int retrieve_from_cache = 0;
int ret;
char* last = (char*) 0;
/* chop off the "RETR " prefix */
char* space = strchr(args, ' ');
if (space) {
conn_info->lcs->filename = space + 1;
} else {
conn_info->lcs->filename = args;
}
conn_info->lcs->direction = 'o';
/* check the transfer mode */
/* we always want to have a binary connection to the server if we're
* retrieving a file and the cache is used */
if (conn_info->clntinfo->transfermode_server == TRANSFER_ASCII
&& config_get_bool("cache") == 1) {
/* switch the transfer mode to TRANSFER_BINARY */
say(conn_info->clntinfo->serversocket, "TYPE I\r\n");
conn_info->clntinfo->transfermode_server = TRANSFER_BINARY;
answer = readall(conn_info->clntinfo->serversocket);
jlog(9, "switched type to binary for retrieving a file (cache is set to on): %s", answer.lastmsg);
free(answer.fullmsg);
}
if (conn_info->clntinfo->transfermode_server == TRANSFER_BINARY
&& config_get_bool("cache") == 0
&& conn_info->clntinfo->transfermode_client == TRANSFER_ASCII) {
/* switch the transfer mode to TRANSFER_ASCII on the
* server side as well */
say(conn_info->clntinfo->serversocket, "TYPE A\r\n");
conn_info->clntinfo->transfermode_server
= TRANSFER_ASCII;
answer = readall(conn_info->clntinfo->serversocket);
jlog(9, "switched type to ascii for retrieving a file (cache is set to off): %s", answer.lastmsg);
free(answer.fullmsg);
}
if (conn_info->clntinfo->transfermode_client == TRANSFER_ASCII
&& conn_info->clntinfo->transfermode_server == TRANSFER_BINARY) {
/* we'll have to convert to ascii */
conn_info->clntinfo->transfermode_havetoconvert
= CONV_TOASCII;
} else {
conn_info->clntinfo->transfermode_havetoconvert
= CONV_NOTCONVERT;
}
if (config_get_bool("cache")) {
cfs = cache_gather_info(conn_info->lcs->filename,
conn_info->clntinfo);
/* try to read the file from the cache */
if ((conn_info->clntinfo->cachefd = cache_readfd(cfs)) < 0) {
/* okay, it is not in, so try to create it */
jlog(9, "File %s not in cache",
conn_info->lcs->filename);
conn_info->clntinfo->fromcache = 0;
if ((conn_info->clntinfo->cachefd
= cache_writefd(cfs)) < 0) {;
conn_info->clntinfo->tocache = 0;
} else {
conn_info->clntinfo->tocache = 1;
}
} else {
jlog(9, "File %s was in cache",
conn_info->lcs->filename);
conn_info->clntinfo->fromcache = 1;
conn_info->clntinfo->tocache = 0;
}
free(cfs.filepath);
free(cfs.filename);
} else {
/* no cache active */
jlog(9, "caching not active");
conn_info->clntinfo->fromcache = 0;
conn_info->clntinfo->tocache = 0;
}
if (conn_info->clntinfo->fromcache) {
/* do not send the RETR command to the server */
}
/* pass the request to the server if we do not have the file in the
* cache */
if ( ! conn_info->clntinfo->fromcache ) {
sayf(conn_info->clntinfo->serversocket, "RETR %s\r\n",
conn_info->lcs->filename);
last = passall(conn_info->clntinfo->serversocket,
conn_info->clntinfo->clientsocket);
if (last) {
jlog(9, "Send (client - %d): %s",
conn_info->clntinfo->clientsocket, last);
}
if (!last) {
if (timeout) {
jlog(2, "Timeout in %s line %d\n", __FILE__
,__LINE__);
err_time_readline(conn_info->clntinfo->clientsocket);
} else {
err_readline(conn_info->clntinfo->clientsocket);
}
return CMD_ERROR;
}
if (!checkdigits(last, 150) && !checkdigits(last, 125)) {
jlog(4, "Server returned invalid response: %s", last);
if (conn_info->clntinfo->fromcache) {
close(conn_info->clntinfo->cachefd);
conn_info->clntinfo->cachefd = -1;
} else if (conn_info->clntinfo->tocache) {
close(conn_info->clntinfo->cachefd);
conn_info->clntinfo->cachefd = -1;
cfs = cache_gather_info(
conn_info->lcs->filename,
conn_info->clntinfo);
cache_delete(cfs, 1);
}
/* say(conn_info->clntinfo->clientsocket, last); */
return CMD_ERROR;
}
} else {
/* we pretend to be the server */
sayf(conn_info->clntinfo->clientsocket,
"150 Opening data connection for %s\r\n",
conn_info->lcs->filename);
retrieve_from_cache = 1;
}
/* Okay, everything is fine, establish a connection */
ret = transfer_initiate(conn_info, retrieve_from_cache);
if (ret != TRNSMT_SUCCESS && ret != TRNSMT_ABORTED) {
return CMD_ERROR;
}
if (ret == TRNSMT_SUCCESS && conn_info->lcs->respcode == 226) {
/* add to cache */
if (conn_info->clntinfo->tocache) {
struct cache_filestruct cfs;
cfs = cache_gather_info(conn_info->lcs->filename,
conn_info->clntinfo);
cache_add(cfs);
free(cfs.filepath);
free(cfs.filename);
}
} else {
if (conn_info->clntinfo->tocache) {
/* delete again from cache - should not
* happen */
struct cache_filestruct cfs;
cfs = cache_gather_info(conn_info->lcs->filename,
conn_info->clntinfo);
cache_delete(cfs, 1);
free(cfs.filepath);
free(cfs.filename);
}
}
conn_info->clntinfo->fromcache = 0;
conn_info->clntinfo->tocache = 0;
return CMD_HANDLED;
}
int std_list(const char* args, struct conn_info_st* conn_info) {
if (passcmd(args, conn_info->clntinfo) < 0) {
return CMD_ERROR;
}
if (conn_info->lcs->respcode != 125 && conn_info->lcs->respcode != 150) {
/* the sockets are closed by transfer_cleanup */
return CMD_ERROR;
}
/* This is a listing of the server that is treated like a transfer
* but is not converted from binary to ascii */
conn_info->clntinfo->serverlisting = 1;
if (transfer_initiate(conn_info, 0)) {
return CMD_ERROR;
}
return CMD_HANDLED;
}
/* a simple function that determines the transfer mode (ascii or image) */
int std_type(const char* args, struct conn_info_st* conn_info) {
char* space = strrchr(args, ' ');
struct message answer;
/* just register the desired type. If we're downloading we always
* keep a binary connection to the server and convert to ascii if
* necessary, if we're uploading however we pass the type through
* to the server. The reason for this is the cache. All files should
* reside in the binary format in the cache */
if (space) {
if (*(space + 1) == 'A' || *(space + 1) == 'a') {
conn_info->clntinfo->transfermode_client
= TRANSFER_ASCII;
conn_info->lcs->type = 'a';
} else {
conn_info->clntinfo->transfermode_client
= TRANSFER_BINARY;
conn_info->lcs->type = 'b';
}
}
if (conn_info->clntinfo->transfermode_client == TRANSFER_ASCII
&& config_get_bool("cache") == 0) {
/* switch the server to ASCII as well */
say(conn_info->clntinfo->serversocket, "TYPE A\r\n");
conn_info->clntinfo->transfermode_server = TRANSFER_ASCII;
} else if (conn_info->clntinfo->transfermode_client == TRANSFER_ASCII
&& config_get_bool("cache") == 1) {
/* keep a binary connection to the server */
say(conn_info->clntinfo->serversocket, "TYPE I\r\n");
conn_info->clntinfo->transfermode_server = TRANSFER_BINARY;
} else {
/* default to a binary connection */
say(conn_info->clntinfo->serversocket, "TYPE I\r\n");
conn_info->clntinfo->transfermode_server = TRANSFER_BINARY;
}
answer = readall(conn_info->clntinfo->serversocket);
free(answer.fullmsg);
/* tell the client about the new status */
sayf(conn_info->clntinfo->clientsocket, "200 Type set to %c\r\n",
conn_info->lcs->type == 'a' ? 'A' : 'I');
conn_info->lcs->respcode = 200;
return CMD_HANDLED;
}
int std_loggedin(const char* args, struct conn_info_st* conn_info) {
say(conn_info->clntinfo->clientsocket,
"503 You are already logged in!\r\n");
return CMD_HANDLED;
}
/* disable EPSV to avoid timeout.
* Problem Report by Ken'ichi Fukamachi <fukachan@fml.org>
*
* In the following case, ftp tries to run in EPSV mode but jftpgw
* cannot understand this, so timeout occurs.
*
* NetBSD ftpd (lukemftpd) --- jftpgw --- NetBSD ftp (lukemftp)
*
* To avoid EPSV mode, disable this mode by jftpgw.
*
*/
int std_epsv(const char* args, struct conn_info_st* conn_info) {
int cs = conn_info->clntinfo->clientsocket;
conn_info->lcs->respcode = 500;
say(cs, "500 'EPSV': command not understood.\r\n");
return CMD_HANDLED;
}
syntax highlighted by Code2HTML, v. 0.9.1