Newer
Older
/* Synchronet FTP server */
/****************************************************************************
* @format.tab-size 4 (Plain Text/Source Code File Header) *
* @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) *
* *
* Copyright Rob Swindell - http://www.synchro.net/copyright.html *
* *
* 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. *
* See the GNU General Public License for more details: gpl.txt or *
* http://www.fsf.org/copyleft/gpl.html *
* *
* For Synchronet coding style and modification guidelines, see *
* http://www.synchro.net/source.html *
* *
* Note: If this box doesn't appear square, then you need to fix your tabs. *
****************************************************************************/
#include <stdlib.h> /* ltoa in GNU C lib */
#include <stdarg.h> /* va_list, varargs */
#include <string.h> /* strrchr */
#include <fcntl.h> /* O_WRONLY, O_RDONLY, etc. */
#include <errno.h> /* EACCES */
#include <ctype.h> /* toupper */
#include <sys/types.h>
#include <sys/stat.h>
#undef SBBS /* this shouldn't be defined unless building sbbs.dll/libsbbs.so */
#include "sbbs.h"
#include "text.h" /* TOTAL_TEXT */
#include "filedat.h"
#include "xpprintf.h" // vasprintf
#include "md5.h"
#include "sauce.h"
#include "git_branch.h"
#include "git_hash.h"
#define FTP_SERVER "Synchronet FTP Server"
static const char* server_abbrev = "ftp";
#define ANONYMOUS "anonymous"
#define BBS_VIRTUAL_PATH "bbs:/""/" /* this is actually bbs:<slash><slash> */
#define LOCAL_FSYS_DIR "local:"
#define BBS_FSYS_DIR "bbs:"
#define BBS_HIDDEN_ALIAS "hidden"
#define TIMEOUT_THREAD_WAIT 60 /* Seconds */
#define TIMEOUT_SOCKET_LISTEN 30 /* Seconds */
#define XFER_REPORT_INTERVAL 60 /* Seconds */
#define INDEX_FNAME_LEN 15
#define NAME_LEN 15 /* User name length for listings */
#define MLSX_TYPE (1 << 0)
#define MLSX_PERM (1 << 1)
#define MLSX_SIZE (1 << 2)
#define MLSX_MODIFY (1 << 3)
#define MLSX_OWNER (1 << 4)
#define MLSX_UNIQUE (1 << 5)
#define MLSX_CREATE (1 << 6)
static ftp_startup_t* startup = NULL;
static scfg_t scfg;
static struct mqtt mqtt;
static struct xpms_set * ftp_set = NULL;
static protected_uint32_t active_clients;
static protected_uint32_t thread_count;
static volatile uint32_t client_highwater = 0;
static volatile time_t uptime = 0;
static volatile ulong served = 0;
static bool terminate_server = FALSE;
static char * text[TOTAL_TEXT];
static str_list_t pause_semfiles;
static str_list_t recycle_semfiles;
static str_list_t shutdown_semfiles;
static link_list_t current_connections;
#ifdef SOCKET_DEBUG
static BYTE socket_debug[0x10000] = {0};
#define SOCKET_DEBUG_CTRL (1 << 0) /* 0x01 */
#define SOCKET_DEBUG_SEND (1 << 1) /* 0x02 */
#define SOCKET_DEBUG_READLINE (1 << 2) /* 0x04 */
#define SOCKET_DEBUG_ACCEPT (1 << 3) /* 0x08 */
#define SOCKET_DEBUG_SENDTHREAD (1 << 4) /* 0x10 */
#define SOCKET_DEBUG_TERMINATE (1 << 5) /* 0x20 */
#define SOCKET_DEBUG_RECV_CHAR (1 << 6) /* 0x40 */
#define SOCKET_DEBUG_FILEXFER (1 << 7) /* 0x80 */
char* genvpath(int lib, int dir, char* str);
SOCKET socket;
union xp_sockaddr client_addr;
socklen_t client_addr_len;
static const char *ftp_mon[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun"
, "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
if (access(dir, 0) == 0)
BOOL dir_op(scfg_t* cfg, user_t* user, client_t* client, uint dirnum)
return user_is_dirop(cfg, dirnum, user, client);
static int lputs(int level, const char* str)
{
mqtt_lputs(&mqtt, TOPIC_SERVER, level, str);
if (level <= LOG_ERR) {
SAFEPRINTF2(errmsg, "%-4s %s", server_abbrev, str);
errorlog(&scfg, &mqtt, level, startup == NULL ? NULL : startup->host_name, errmsg);
if (startup != NULL && startup->errormsg != NULL)
startup->errormsg(startup->cbdata, level, errmsg);
}
if (startup == NULL || startup->lputs == NULL || str == NULL || level > startup->log_level)
return 0;
if (IsBadCodePtr((FARPROC)startup->lputs))
return startup->lputs(startup->cbdata, level, str);
#if defined(__GNUC__) // Catch printf-format errors with lprintf
static int lprintf(int level, const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
#endif
static int lprintf(int level, const char *fmt, ...)
va_start(argptr, fmt);
vsnprintf(sbuf, sizeof(sbuf), fmt, argptr);
sbuf[sizeof(sbuf) - 1] = 0;
va_end(argptr);
return lputs(level, sbuf);
}
#ifdef _WINSOCKAPI_
static WSADATA WSAData;
#define SOCKLIB_DESC WSAData.szDescription
static BOOL WSAInitialized = FALSE;
static BOOL winsock_startup(void)
{
int status; /* Status Code */
if ((status = WSAStartup(MAKEWORD(1, 1), &WSAData)) == 0) {
lprintf(LOG_DEBUG, "%s %s", WSAData.szDescription, WSAData.szSystemStatus);
WSAInitialized = TRUE;
lprintf(LOG_CRIT, "!WinSock startup ERROR %d", status);
#else /* No WINSOCK */
#define winsock_startup() (TRUE)
#define SOCKLIB_DESC NULL
static char* server_host_name(void)
{
return startup->host_name[0] ? startup->host_name : scfg.sys_inetaddr;
}
static void set_state(enum server_state state)
static int curr_state;
if (state == curr_state)
return;
if (startup != NULL) {
if (startup->set_state != NULL)
startup->set_state(startup->cbdata, state);
curr_state = state;
}
static void update_clients(void)
{
if (startup != NULL) {
uint32_t count = protected_uint32_value(active_clients);
if (startup->clients != NULL)
startup->clients(startup->cbdata, count);
}
static void client_on(SOCKET sock, client_t* client, BOOL update)
listAddNodeData(¤t_connections, client->addr, strlen(client->addr) + 1, sock, LAST_NODE);
if (startup != NULL && startup->client_on != NULL)
startup->client_on(startup->cbdata, TRUE, sock, client, update);
mqtt_client_on(&mqtt, TRUE, sock, client, update);
}
static void client_off(SOCKET sock)
{
listRemoveTaggedNode(¤t_connections, sock, /* free_data */ TRUE);
if (startup != NULL && startup->client_on != NULL)
startup->client_on(startup->cbdata, FALSE, sock, NULL, FALSE);
mqtt_client_on(&mqtt, FALSE, sock, NULL, FALSE);
static void thread_up(BOOL setuid)
if (startup != NULL) {
if (startup->thread_up != NULL)
startup->thread_up(startup->cbdata, TRUE, setuid);
static int32_t thread_down(void)
int32_t count = protected_uint32_adjust_fetch(&thread_count, -1);
if (startup != NULL) {
if (startup->thread_up != NULL)
startup->thread_up(startup->cbdata, FALSE, FALSE);
return count;
if (startup != NULL && startup->socket_open != NULL)
startup->socket_open(startup->cbdata, TRUE);
if (set_socket_options(&scfg, sock, "FTP", error, sizeof(error)))
lprintf(LOG_ERR, "%04d !ERROR %s", sock, error);
}
static void ftp_close_socket_cb(SOCKET sock, void *cbdata)
{
if (startup != NULL && startup->socket_open != NULL)
startup->socket_open(startup->cbdata, FALSE);
}
static SOCKET ftp_open_socket(int domain, int type)
{
sock = socket(domain, type, IPPROTO_IP);
if (sock != INVALID_SOCKET)
}
#ifdef __BORLANDC__
#pragma argsused
#endif
static int ftp_close_socket(SOCKET* sock, CRYPT_SESSION *sess, int line)
destroy_session(lprintf, *sess);
if ((*sock) == INVALID_SOCKET) {
lprintf(LOG_WARNING, "0000 !INVALID_SOCKET in close_socket from line %u", line);
shutdown(*sock, SHUT_RDWR); /* required on Unix */
result = closesocket(*sock);
if (startup != NULL && startup->socket_open != NULL)
startup->socket_open(startup->cbdata, FALSE);
if (result != 0) {
if (SOCKET_ERRNO != ENOTSOCK)
lprintf(LOG_WARNING, "%04d !ERROR %d closing socket from line %u", *sock, SOCKET_ERRNO, line);
*sock = INVALID_SOCKET;
#define GCES(status, sock, session, estr, action) do { \
int GCES_level; \
get_crypt_error_string(status, session, &estr, action, &GCES_level); \
if (estr != NULL) { \
lprintf(GCES_level, "%04d TLS %s", sock, estr); \
free_crypt_attrstr(estr); \
estr = NULL; \
} \
#if defined(__GNUC__) // Catch printf-format errors with sockprintf
static int sockprintf(SOCKET sock, CRYPT_SESSION sess, char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
#endif
static int sockprintf(SOCKET sock, CRYPT_SESSION sess, char *fmt, ...)
int len;
int maxlen;
int result;
char sbuf[1024];
char * estr;
va_start(argptr, fmt);
len = vsnprintf(sbuf, maxlen = sizeof(sbuf) - 2, fmt, argptr);
va_end(argptr);
if (len < 0 || len > maxlen) /* format error or output truncated */
len = maxlen;
if (startup != NULL && startup->options & FTP_OPT_DEBUG_TX)
lprintf(LOG_DEBUG, "%04d TX%s: %.*s", sock, sess != -1 ? "S" : "", len, sbuf);
memcpy(sbuf + len, "\r\n", 2);
len += 2;
if (sock == INVALID_SOCKET) {
lprintf(LOG_WARNING, "!INVALID SOCKET in call to sockprintf");
if (!socket_writable(sock, 300000)) {
lprintf(LOG_WARNING, "%04d !WARNING socket not ready for write", sock);
if (sess != -1) {
int tls_sent;
int sent = 0;
while (sent < len) {
result = cryptPushData(sess, sbuf + sent, len - sent, &tls_sent);
if (result != CRYPT_ERROR_TIMEOUT)
return 0;
while ((result = sendsocket(sock, sbuf, len)) != len) {
if (result == SOCKET_ERROR) {
if (SOCKET_ERRNO == EWOULDBLOCK) {
if (SOCKET_ERRNO == ECONNRESET)
lprintf(LOG_WARNING, "%04d Connection reset by peer on send", sock);
else if (SOCKET_ERRNO == ECONNABORTED)
lprintf(LOG_WARNING, "%04d Connection aborted by peer on send", sock);
lprintf(LOG_WARNING, "%04d !ERROR %d sending", sock, SOCKET_ERRNO);
lprintf(LOG_WARNING, "%04d !ERROR: short send: %u instead of %u", sock, result, len);
void recverror(SOCKET socket, int rd, int line)
if (rd == 0)
lprintf(LOG_NOTICE, "%04d Socket closed by peer on receive (line %u)"
, socket, line);
else if (rd == SOCKET_ERROR) {
if (SOCKET_ERRNO == ECONNRESET)
lprintf(LOG_NOTICE, "%04d Connection reset by peer on receive (line %u)"
, socket, line);
else if (SOCKET_ERRNO == ECONNABORTED)
lprintf(LOG_NOTICE, "%04d Connection aborted by peer on receive (line %u)"
, socket, line);
lprintf(LOG_NOTICE, "%04d !ERROR %d receiving on socket (line %u)"
, socket, SOCKET_ERRNO, line);
lprintf(LOG_WARNING, "%04d !ERROR: recv on socket returned unexpected value: %d (line %u)"
, socket, rd, line);
static int sock_recvbyte(SOCKET sock, CRYPT_SESSION sess, char *buf, time_t *lastactive)
int len = 0;
int ret;
int i;
if (ftp_set == NULL || terminate_server) {
sockprintf(sock, sess, "421 Server downed, aborting.");
lprintf(LOG_WARNING, "%04d Server downed, aborting", sock);
if ((ret = cryptSetAttribute(sess, CRYPT_OPTION_NET_READTIMEOUT, 0)) != CRYPT_OK)
GCES(ret, sock, sess, estr, "setting read timeout");
/* Successive reads will be with the full timeout after a socket_readable() */
cryptSetAttribute(sess, CRYPT_OPTION_NET_READTIMEOUT, startup->max_inactivity);
if (!first) {
GCES(ret, sock, sess, estr, "popping data");
return -1;
}
break;
if ((time(NULL) - (*lastactive)) > startup->max_inactivity) {
lprintf(LOG_WARNING, "%04d Disconnecting due to to inactivity", sock);
sockprintf(sock, sess, "421 Disconnecting due to inactivity (%u seconds)."
, startup->max_inactivity);

rswindell
committed
if (!socket_readable(sock, startup->max_inactivity * 1000)) {
if ((time(NULL) - (*lastactive)) > startup->max_inactivity) {
lprintf(LOG_WARNING, "%04d Disconnecting due to to inactivity", sock);
sockprintf(sock, sess, "421 Disconnecting due to inactivity (%u seconds)."
, startup->max_inactivity);
if (!socket_readable(sock, startup->max_inactivity * 1000)) {
if ((time(NULL) - (*lastactive)) > startup->max_inactivity) {
lprintf(LOG_WARNING, "%04d Disconnecting due to to inactivity", sock);
sockprintf(sock, sess, "421 Disconnecting due to inactivity (%u seconds)."
, startup->max_inactivity);
socket_debug[sock] |= SOCKET_DEBUG_RECV_CHAR;
i = recv(sock, buf, 1, 0);
socket_debug[sock] &= ~SOCKET_DEBUG_RECV_CHAR;
}
}
int sockreadline(SOCKET socket, CRYPT_SESSION sess, char* buf, int len, time_t* lastactive)
{
char ch;
int i, rd = 0;
if (socket == INVALID_SOCKET) {
lprintf(LOG_WARNING, "INVALID SOCKET in call to sockreadline");
while (rd < len - 1) {
recverror(socket, i, __LINE__);

rswindell
committed
}
if (ch == '\n' /* && rd>=1 */) { /* Mar-9-2003: terminate on sole LF */
if (rd > 0 && buf[rd - 1] == '\r')
buf[rd - 1] = 0;
else
lprintf(LOG_INFO, "FTP Server terminate");
terminate_server = TRUE;
bool ftp_remove(SOCKET sock, int line, const char* fname, const char* username, int err_level)
{
if (fexist(fname) && (ret = remove(fname)) != 0) {
if (fexist(fname)) { // In case there was a race condition (other host deleted file first)
char error[256];
lprintf(err_level, "%04d <%s> !ERROR %d (%s) (line %d) removing file: %s"
, sock, username, errno, safe_strerror(errno, error, sizeof error), line, fname);
}
return ret == 0;
}
SOCKET ctrl_sock;
CRYPT_SESSION ctrl_sess;
SOCKET* data_sock;
CRYPT_SESSION* data_sess;
BOOL* inprogress;
BOOL* aborted;
BOOL delfile;
BOOL tmpfile;
BOOL credits;
BOOL append;
off_t filepos;
char filename[MAX_PATH + 1];
time_t* lastactive;
user_t* user;
client_t* client;
int dir;
char* desc;
} xfer_t;
static void send_thread(void* arg)
{
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
char buf[8192];
char str[256];
char errstr[256];
char tmp[128];
char username[128];
char host_ip[INET6_ADDRSTRLEN];
int i;
int rd;
int wr;
long mod;
uint64_t l;
off_t total = 0;
off_t last_total = 0;
ulong dur;
ulong cps;
off_t length;
BOOL error = FALSE;
FILE* fp;
file_t f;
xfer_t xfer;
time_t now;
time_t start;
time_t last_report;
user_t uploader;
union xp_sockaddr addr;
socklen_t addr_len;
char * estr;
xfer = *(xfer_t*)arg;
free(arg);
SetThreadName("sbbs/ftpSend");
thread_up(TRUE /* setuid */);
length = flength(xfer.filename);
if (length < 1) {
if (xfer.tmpfile) {
if (!(startup->options & FTP_OPT_KEEP_TEMP_FILES))
ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias, LOG_ERR);
sockprintf(xfer.ctrl_sock, xfer.ctrl_sess, "450 No files");
lprintf(LOG_WARNING, "%04d <%s> !DATA cannot send file (%s) with size of %" PRIdOFF " bytes"
, xfer.ctrl_sock, xfer.user->alias, xfer.filename, length);
sockprintf(xfer.ctrl_sock, xfer.ctrl_sess, "450 Invalid file size: %" PRIdOFF, length);
ftp_close_socket(xfer.data_sock, xfer.data_sess, __LINE__);
*xfer.inprogress = FALSE;
thread_down();
return;
}
if ((fp = fnopen(NULL, xfer.filename, O_RDONLY | O_BINARY)) == NULL /* non-shareable open failed */
&& (fp = fopen(xfer.filename, "rb")) == NULL) { /* shareable open failed */
lprintf(LOG_ERR, "%04d <%s> !DATA ERROR %d (%s) line %d opening %s"
, xfer.ctrl_sock, xfer.user->alias, errno, safe_strerror(errno, errstr, sizeof errstr), __LINE__, xfer.filename);
sockprintf(xfer.ctrl_sock, xfer.ctrl_sess, "450 ERROR %d (%s) opening %s", errno, safe_strerror(errno, errstr, sizeof errstr), xfer.filename);
if (xfer.tmpfile && !(startup->options & FTP_OPT_KEEP_TEMP_FILES))
ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias, LOG_ERR);
ftp_close_socket(xfer.data_sock, xfer.data_sess, __LINE__);
*xfer.inprogress = FALSE;
thread_down();
#ifdef SOCKET_DEBUG_SENDTHREAD
socket_debug[xfer.ctrl_sock] |= SOCKET_DEBUG_SENDTHREAD;
*xfer.aborted = FALSE;
if (xfer.filepos < 0)
xfer.filepos = 0;
if (startup->options & FTP_OPT_DEBUG_DATA || xfer.filepos)
lprintf(LOG_DEBUG, "%04d <%s> DATA socket %d sending %s from offset %" PRIdOFF
, xfer.ctrl_sock, xfer.user->alias, *xfer.data_sock, xfer.filename, xfer.filepos);
fseeko(fp, xfer.filepos, SEEK_SET);
last_report = start = time(NULL);
while ((xfer.filepos + total) < length) {
/* Periodic progress report */
if (total && now >= last_report + XFER_REPORT_INTERVAL) {
if (xfer.filepos)
sprintf(str, " from offset %" PRIdOFF, xfer.filepos);
str[0] = 0;
lprintf(LOG_INFO, "%04d <%s> DATA Sent %" PRIdOFF " bytes (%" PRIdOFF " total) of %s (%lu cps)%s"
, xfer.ctrl_sock, xfer.user->alias, total, length, xfer.filename
, (ulong)((total - last_total) / (now - last_report))
, str);
last_total = total;
last_report = now;
if (*xfer.aborted == TRUE) {
lprintf(LOG_WARNING, "%04d <%s> !DATA Transfer aborted", xfer.ctrl_sock, xfer.user->alias);
sockprintf(xfer.ctrl_sock, xfer.ctrl_sess, "426 Transfer aborted.");
error = TRUE;
if (ftp_set == NULL || terminate_server) {
lprintf(LOG_WARNING, "%04d <%s> !DATA Transfer locally aborted", xfer.ctrl_sock, xfer.user->alias);
sockprintf(xfer.ctrl_sock, xfer.ctrl_sess, "426 Transfer locally aborted.");
error = TRUE;
/* Check socket for writability */
if (!socket_writable(*xfer.data_sock, 1000))
fseeko(fp, xfer.filepos + total, SEEK_SET);
rd = fread(buf, sizeof(char), sizeof(buf), fp);
if (rd < 1) /* EOF or READ error */
socket_debug[xfer.ctrl_sock] |= SOCKET_DEBUG_SEND;
if (*xfer.data_sess != -1) {
int status = cryptPushData(*xfer.data_sess, buf, rd, &wr);
if (status != CRYPT_OK) {
GCES(status, *xfer.data_sock, *xfer.data_sess, estr, "pushing data");
wr = -1;
}
else {
status = cryptFlushData(*xfer.data_sess);
if (status != CRYPT_OK) {
GCES(status, *xfer.data_sock, *xfer.data_sess, estr, "flushing data");
wr = sendsocket(*xfer.data_sock, buf, rd);
socket_debug[xfer.ctrl_sock] &= ~SOCKET_DEBUG_SEND;
if (wr < 1) {
if (wr == SOCKET_ERROR) {
if (SOCKET_ERRNO == EWOULDBLOCK) {
/*lprintf(LOG_WARNING,"%04d DATA send would block, retrying",xfer.ctrl_sock);*/
YIELD();
continue;
}
else if (SOCKET_ERRNO == ECONNRESET)
lprintf(LOG_WARNING, "%04d <%s> DATA Connection reset by peer, sending on socket %d"
, xfer.ctrl_sock, xfer.user->alias, *xfer.data_sock);
else if (SOCKET_ERRNO == ECONNABORTED)
lprintf(LOG_WARNING, "%04d <%s> DATA Connection aborted by peer, sending on socket %d"
, xfer.ctrl_sock, xfer.user->alias, *xfer.data_sock);
lprintf(LOG_WARNING, "%04d <%s> !DATA ERROR %d sending on data socket %d"
, xfer.ctrl_sock, xfer.user->alias, SOCKET_ERRNO, *xfer.data_sock);
/* Send NAK */
sockprintf(xfer.ctrl_sock, xfer.ctrl_sess, "426 Error %d sending on DATA channel"
, SOCKET_ERRNO);
error = TRUE;
if (wr == 0) {
lprintf(LOG_WARNING, "%04d <%s> !DATA socket %d disconnected", xfer.ctrl_sock, xfer.user->alias, *xfer.data_sock);
sockprintf(xfer.ctrl_sock, xfer.ctrl_sess, "426 DATA channel disconnected");
error = TRUE;
lprintf(LOG_ERR, "%04d <%s> !DATA ERROR %d (%d) sending on socket %d"
, xfer.ctrl_sock, xfer.user->alias, wr, SOCKET_ERRNO, *xfer.data_sock);
sockprintf(xfer.ctrl_sock, xfer.ctrl_sess, "451 DATA send error");
error = TRUE;
total += wr;
*xfer.lastactive = time(NULL);
//YIELD();
if ((i = ferror(fp)) != 0)
lprintf(LOG_ERR, "%04d <%s> !DATA FILE ERROR %d (errno %d %s)"
, xfer.ctrl_sock, xfer.user->alias, i, errno, safe_strerror(errno, errstr, sizeof errstr));
ftp_close_socket(xfer.data_sock, xfer.data_sess, __LINE__); /* Signal end of file */
if (startup->options & FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG, "%04d <%s> DATA socket closed", xfer.ctrl_sock, xfer.user->alias);
if (!error) {
dur = (long)(time(NULL) - start);
cps = (ulong)(dur ? total / dur : total * 2);
lprintf(LOG_INFO, "%04d <%s> DATA Transfer successful: %" PRIdOFF " bytes sent in %lu seconds (%lu cps)"
, xfer.ctrl_sock
, xfer.user->alias
, total, dur, cps);
sockprintf(xfer.ctrl_sock, xfer.ctrl_sess, "226 Download complete (%lu cps).", cps);
if (xfer.dir >= 0 && !xfer.tmpfile) {
memset(&f, 0, sizeof(f));
if (!loadfile(&scfg, xfer.dir, getfname(xfer.filename), &f, file_detail_normal)) {
lprintf(LOG_ERR, "%04d <%s> DATA downloaded: %s (not found in filebase!)"
, xfer.ctrl_sock
, xfer.user->alias
, xfer.filename);
} else {
f.hdr.times_downloaded++;
f.hdr.last_downloaded = time32(NULL);
updatefile(&scfg, &f);
lprintf(LOG_INFO, "%04d <%s> DATA downloaded: %s (%u times total)"
, xfer.ctrl_sock
, xfer.user->alias
, xfer.filename
, f.hdr.times_downloaded);
/**************************/
/* Update Uploader's Info */
/**************************/
uploader.number = 0;
if (f.from_ext != NULL)
uploader.number = atoi(f.from_ext);
if (uploader.number == 0)
uploader.number = matchuser(&scfg, f.from, TRUE /*sysop_alias*/);
if (uploader.number
&& uploader.number != xfer.user->number
&& getuserdat(&scfg, &uploader) == 0
&& uploader.firston < (time_t)f.hdr.when_imported.time) {
l = f.cost;
if (!(scfg.dir[f.dir]->misc & DIR_CDTDL)) /* Don't give credits on d/l */
l = 0;
if (scfg.dir[f.dir]->misc & DIR_CDTMIN && cps) { /* Give min instead of cdt */
mod = ((ulong)(l * (scfg.dir[f.dir]->dn_pct / 100.0)) / cps) / 60;
adjustuserval(&scfg, uploader.number, USER_MIN, mod);
sprintf(tmp, "%lu minute", mod);
mod = (ulong)(l * (scfg.dir[f.dir]->dn_pct / 100.0));
adjustuserval(&scfg, uploader.number, USER_CDT, mod);
u32toac(mod, tmp, ',');
if (!(scfg.dir[f.dir]->misc & DIR_QUIET)) {
const char* prefix = xfer.filepos ? "partially FTP-" : "FTP-";
addr_len = sizeof(addr);
if (uploader.level >= SYSOP_LEVEL
&& getpeername(xfer.ctrl_sock, &addr.addr, &addr_len) == 0
&& inet_addrtop(&addr, host_ip, sizeof(host_ip)) != NULL)
SAFEPRINTF2(username, "%s [%s]", xfer.user->alias, host_ip);
SAFECOPY(username, xfer.user->alias);
/* Inform uploader of downloaded file */
if (mod == 0)
safe_snprintf(str, sizeof(str), text[FreeDownloadUserMsg]
, getfname(xfer.filename)
, prefix
, username);
safe_snprintf(str, sizeof(str), text[DownloadUserMsg]
, getfname(xfer.filename)
, prefix
, username, tmp);
putsmsg(&scfg, uploader.number, str);
}
mqtt_file_download(&mqtt, xfer.user, f.dir, f.name, total, xfer.client);
smb_freefilemem(&f);
if (!xfer.tmpfile && !xfer.delfile && !(scfg.dir[f.dir]->misc & DIR_NOSTAT))
user_downloaded(&scfg, xfer.user, 1, total);
if (xfer.dir >= 0 && !download_is_free(&scfg, xfer.dir, xfer.user, xfer.client))
subtract_cdt(&scfg, xfer.user, xfer.credits);
}
}
fclose(fp);
if (ftp_set != NULL && !terminate_server)
*xfer.inprogress = FALSE;
if (xfer.tmpfile) {
if (!(startup->options & FTP_OPT_KEEP_TEMP_FILES))
ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias, LOG_ERR);
else if (xfer.delfile && !error)
ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias, LOG_WARNING);
#if defined(SOCKET_DEBUG_SENDTHREAD)
socket_debug[xfer.ctrl_sock] &= ~SOCKET_DEBUG_SENDTHREAD;
thread_down();
}
static void receive_thread(void* arg)
{
char str[128];
char errstr[256];
char buf[8192];
char extdesc[LEN_EXTDESC + 1] = "";
char tmp[MAX_PATH + 1];
int rd;
off_t total = 0;
off_t last_total = 0;
ulong dur;
ulong cps;
BOOL error = FALSE;
BOOL filedat;
FILE* fp;
file_t f;
xfer_t xfer;
time_t now;
time_t start;
time_t last_report;
char * estr;
xfer = *(xfer_t*)arg;
free(arg);
SetThreadName("sbbs/ftpReceive");
thread_up(TRUE /* setuid */);
if ((fp = fopen(xfer.filename, xfer.append ? "ab" : "wb")) == NULL) {
lprintf(LOG_ERR, "%04d <%s> !DATA ERROR %d (%s) line %d opening %s"
, xfer.ctrl_sock, xfer.user->alias, errno, safe_strerror(errno, errstr, sizeof errstr), __LINE__, xfer.filename);
sockprintf(xfer.ctrl_sock, xfer.ctrl_sess, "450 ERROR %d (%s) opening %s", errno, safe_strerror(errno, errstr, sizeof errstr), xfer.filename);
ftp_close_socket(xfer.data_sock, xfer.data_sess, __LINE__);
*xfer.inprogress = FALSE;
thread_down();
if (xfer.append)
xfer.filepos = filelength(fileno(fp));
xfer.filepos = 0;
*xfer.aborted = FALSE;
if (xfer.filepos || startup->options & FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG, "%04d <%s> DATA socket %d receiving %s from offset %" PRIdOFF
, xfer.ctrl_sock, xfer.user->alias, *xfer.data_sock, xfer.filename, xfer.filepos);
fseeko(fp, xfer.filepos, SEEK_SET);
// Determine the maximum file size to allow, accounting for minimum free space
char path[MAX_PATH + 1];
SAFECOPY(path, xfer.filename);
*getfname(path) = '\0';
int64_t avail = getfreediskspace(path, 1);
if (avail <= scfg.min_dspace)
avail = 0;
else
avail -= scfg.min_dspace;
int64_t max_fsize = xfer.filepos + avail;
if (startup->max_fsize > 0 && startup->max_fsize < max_fsize)
max_fsize = startup->max_fsize;
if (startup->options & FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG, "%04d <%s> DATA Limiting uploaded file size to %" PRIu64 " (%s) bytes"
, xfer.ctrl_sock, xfer.user->alias, max_fsize
, byte_estimate_to_str(max_fsize, tmp, sizeof(tmp), 1, 1));
last_report = start = time(NULL);
while (1) {
/* Periodic progress report */
if (total && now >= last_report + XFER_REPORT_INTERVAL) {
if (xfer.filepos)
sprintf(str, " from offset %" PRIdOFF, xfer.filepos);
else
str[0] = 0;
lprintf(LOG_INFO, "%04d <%s> DATA Received %" PRIdOFF " bytes of %s (%lu cps)%s"
, xfer.ctrl_sock
, xfer.user->alias
, total, xfer.filename
, (ulong)((total - last_total) / (now - last_report))
, str);
last_total = total;
last_report = now;
if (xfer.filepos + total > max_fsize) {
lprintf(LOG_WARNING, "%04d <%s> !DATA received %" PRIdOFF " bytes of %s exceeds maximum allowed (%" PRIu64 " bytes)"
, xfer.ctrl_sock, xfer.user->alias, xfer.filepos + total, xfer.filename, startup->max_fsize);
sockprintf(xfer.ctrl_sock, xfer.ctrl_sess, "552 File size exceeds maximum allowed (%" PRIu64 " bytes)", startup->max_fsize);
error = TRUE;
break;
}
if (*xfer.aborted == TRUE) {
lprintf(LOG_WARNING, "%04d <%s> !DATA Transfer aborted", xfer.ctrl_sock, xfer.user->alias);

rswindell
committed
/* Send NAK */
sockprintf(xfer.ctrl_sock, xfer.ctrl_sess, "426 Transfer aborted.");
error = TRUE;
if (ftp_set == NULL || terminate_server) {
lprintf(LOG_WARNING, "%04d <%s> !DATA Transfer locally aborted", xfer.ctrl_sock, xfer.user->alias);

rswindell
committed
/* Send NAK */
sockprintf(xfer.ctrl_sock, xfer.ctrl_sess, "426 Transfer locally aborted.");
error = TRUE;
/* Check socket for readability */
if (!socket_readable(*xfer.data_sock, 1000))
#if defined(SOCKET_DEBUG_RECV_BUF)
socket_debug[xfer.ctrl_sock] |= SOCKET_DEBUG_RECV_BUF;
if (*xfer.data_sess != -1) {
int status = cryptPopData(*xfer.data_sess, buf, sizeof(buf), &rd);
if (status != CRYPT_OK) {
GCES(status, *xfer.data_sock, *xfer.data_sess, estr, "popping data");
if (status != CRYPT_ERROR_COMPLETE)
rd = SOCKET_ERROR;
rd = recv(*xfer.data_sock, buf, sizeof(buf), 0);
#if defined(SOCKET_DEBUG_RECV_BUF)
socket_debug[xfer.ctrl_sock] &= ~SOCKET_DEBUG_RECV_BUF;
if (rd < 1) {
if (rd == 0) { /* Socket closed */
if (startup->options & FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG, "%04d <%s> DATA socket %d closed by client"
, xfer.ctrl_sock, xfer.user->alias, *xfer.data_sock);
if (rd == SOCKET_ERROR) {
if (SOCKET_ERRNO == EWOULDBLOCK) {
/*lprintf(LOG_WARNING,"%04d DATA recv would block, retrying",xfer.ctrl_sock);*/
YIELD();
continue;
}
else if (SOCKET_ERRNO == ECONNRESET)
lprintf(LOG_WARNING, "%04d <%s> DATA Connection reset by peer, receiving on socket %d"
, xfer.ctrl_sock, xfer.user->alias, *xfer.data_sock);
else if (SOCKET_ERRNO == ECONNABORTED)
lprintf(LOG_WARNING, "%04d <%s> DATA Connection aborted by peer, receiving on socket %d"
, xfer.ctrl_sock, xfer.user->alias, *xfer.data_sock);
lprintf(LOG_WARNING, "%04d <%s> !DATA ERROR %d receiving on data socket %d"
, xfer.ctrl_sock, xfer.user->alias, SOCKET_ERRNO, *xfer.data_sock);

rswindell
committed
/* Send NAK */
sockprintf(xfer.ctrl_sock, xfer.ctrl_sess, "426 Error %d receiving on DATA channel"
, SOCKET_ERRNO);
error = TRUE;
lprintf(LOG_ERR, "%04d <%s> !DATA ERROR recv returned %d on socket %d"
, xfer.ctrl_sock, xfer.user->alias, rd, *xfer.data_sock);

rswindell
committed
/* Send NAK */
sockprintf(xfer.ctrl_sock, xfer.ctrl_sess, "451 Unexpected socket error: %d", rd);
error = TRUE;
fwrite(buf, 1, rd, fp);
total += rd;
*xfer.lastactive = time(NULL);
YIELD();

rswindell
committed
fclose(fp);
ftp_close_socket(xfer.data_sock, xfer.data_sess, __LINE__);
if (error && startup->options & FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG, "%04d <%s> DATA socket %d closed", xfer.ctrl_sock, xfer.user->alias, *xfer.data_sock);
if (xfer.filepos + total < startup->min_fsize) {
lprintf(LOG_WARNING, "%04d <%s> DATA received %" PRIdOFF " bytes for %s, less than minimum required (%" PRIu64 " bytes)"
, xfer.ctrl_sock, xfer.user->alias, xfer.filepos + total, xfer.filename, startup->min_fsize);
sockprintf(xfer.ctrl_sock, xfer.ctrl_sess, "550 File size less than minimum required (%" PRIu64 " bytes)"
, startup->min_fsize);
error = TRUE;
}
if (error) {
if (!xfer.append)
ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias, LOG_ERR);
} else {
dur = (long)(time(NULL) - start);
cps = (ulong)(dur ? total / dur : total * 2);
lprintf(LOG_INFO, "%04d <%s> DATA Transfer successful: %" PRIdOFF " bytes received in %lu seconds (%lu cps)"
, xfer.ctrl_sock
, xfer.user->alias
, total, dur, cps);
if (xfer.dir >= 0) {
memset(&f, 0, sizeof(f));
smb_hfield_str(&f, SMB_FILENAME, getfname(xfer.filename));
smb_hfield_str(&f, SENDER, xfer.user->alias);
filedat = findfile(&scfg, xfer.dir, f.name, NULL);
if (scfg.dir[f.dir]->misc & DIR_AONLY) /* Forced anonymous */
f.hdr.attr |= MSG_ANONYMOUS;
off_t cdt = flength(xfer.filename);
smb_hfield_bin(&f, SMB_COST, cdt);
char fdesc[LEN_FDESC + 1] = "";
/* Description specified with DESC command? */
if (xfer.desc != NULL)
SAFECOPY(fdesc, xfer.desc);
/* Necessary for DIR and LIB ARS keyword support in subsequent chk_ar()'s */
SAFECOPY(xfer.user->curdir, scfg.dir[f.dir]->code);
if (scfg.dir[f.dir]->misc & DIR_DIZ) {
lprintf(LOG_DEBUG, "%04d <%s> DATA Extracting DIZ from: %s", xfer.ctrl_sock, xfer.user->alias, xfer.filename);
if (extract_diz(&scfg, &f, /* diz_fnames */ NULL, tmp, sizeof(tmp))) {
struct sauce_charinfo sauce;
lprintf(LOG_DEBUG, "%04d <%s> DATA Parsing DIZ: %s", xfer.ctrl_sock, xfer.user->alias, tmp);
char* lines = read_diz(tmp, &sauce);
format_diz(lines, extdesc, sizeof(extdesc), sauce.width, sauce.ice_color);
if (!fdesc[0]) { /* use for normal description */
prep_file_desc(extdesc, fdesc); /* strip control chars and dupe chars */
file_sauce_hfields(&f, &sauce);
ftp_remove(xfer.ctrl_sock, __LINE__, tmp, xfer.user->alias, LOG_ERR);
lprintf(LOG_DEBUG, "%04d <%s> DATA DIZ does not exist in: %s", xfer.ctrl_sock, xfer.user->alias, xfer.filename);
} /* FILE_ID.DIZ support */
smb_new_hfield_str(&f, SMB_FILEDESC, fdesc);
if (filedat) {
if (updatefile(&scfg, &f))
lprintf(LOG_INFO, "%04d <%s> DATA updated file: %s"
, xfer.ctrl_sock, xfer.user->alias, f.name);
lprintf(LOG_ERR, "%04d <%s> !DATA ERROR updating file (%s) in database"
, xfer.ctrl_sock, xfer.user->alias, f.name);
/* need to update the index here */
} else {
if (addfile(&scfg, &f, extdesc, /* metatdata: */ NULL, xfer.client))
lprintf(LOG_INFO, "%04d <%s> DATA uploaded file: %s"
, xfer.ctrl_sock, xfer.user->alias, f.name);
lprintf(LOG_ERR, "%04d <%s> !DATA ERROR adding file (%s) to database"
, xfer.ctrl_sock, xfer.user->alias, f.name);
if (scfg.dir[f.dir]->upload_sem[0])
ftouch(scfg.dir[f.dir]->upload_sem);
/**************************/
/* Update Uploader's Info */
/**************************/
user_uploaded(&scfg, xfer.user, (!xfer.append && xfer.filepos == 0) ? 1:0, total);
if (scfg.dir[f.dir]->up_pct && scfg.dir[f.dir]->misc & DIR_CDTUL) { /* credit for upload */
if (scfg.dir[f.dir]->misc & DIR_CDTMIN && cps) /* Give min instead of cdt */
xfer.user->min = (uint32_t)adjustuserval(&scfg, xfer.user->number, USER_MIN
, ((ulong)(total * (scfg.dir[f.dir]->up_pct / 100.0)) / cps) / 60);
xfer.user->cdt = adjustuserval(&scfg, xfer.user->number, USER_CDT
, cdt * (uint64_t)(scfg.dir[f.dir]->up_pct / 100.0));
if (!(scfg.dir[f.dir]->misc & DIR_NOSTAT))
mqtt_file_upload(&mqtt, xfer.user, f.dir, f.name, total, xfer.client);
smb_freefilemem(&f);

rswindell
committed
/* Send ACK */
sockprintf(xfer.ctrl_sock, xfer.ctrl_sess, "226 Upload complete (%lu cps).", cps);
if (ftp_set != NULL && !terminate_server)
*xfer.inprogress = FALSE;
// Returns TRUE upon error?!?
static BOOL start_tls(SOCKET *sock, CRYPT_SESSION *sess, BOOL resp)
{
char *estr = NULL;
if (!ssl_sync(&scfg, lprintf)) {
lprintf(LOG_CRIT, "!ssl_sync() failure trying to enable TLS support");
if (resp)
sockprintf(*sock, *sess, "431 TLS not available");
if ((status = cryptCreateSession(sess, CRYPT_UNUSED, CRYPT_SESSION_TLS_SERVER)) != CRYPT_OK) {
GCES(status, *sock, CRYPT_UNUSED, estr, "creating session");
if (resp)
sockprintf(*sock, *sess, "431 TLS not available");
if ((status = cryptSetAttribute(*sess, CRYPT_SESSINFO_TLS_OPTIONS, CRYPT_TLSOPTION_MINVER_TLS12)) != CRYPT_OK) {
GCES(status, *sock, *sess, estr, "setting TLS minver");
cryptDestroySession(*sess);
*sess = -1;
sockprintf(*sock, *sess, "431 TLS not available");
return FALSE;
}
if ((status = add_private_key(&scfg, lprintf, *sess)) != CRYPT_OK) {
GCES(status, *sock, *sess, estr, "setting private key");
destroy_session(lprintf, *sess);
if (resp)
sockprintf(*sock, *sess, "431 TLS not available");
(void)setsockopt(*sock, IPPROTO_TCP, TCP_NODELAY, (char*)&nodelay, sizeof(nodelay));
nb = 0;
ioctlsocket(*sock, FIONBIO, &nb);
if ((status = cryptSetAttribute(*sess, CRYPT_SESSINFO_NETWORKSOCKET, *sock)) != CRYPT_OK) {
GCES(status, *sock, *sess, estr, "setting network socket");
destroy_session(lprintf, *sess);
if (resp)
sockprintf(*sock, *sess, "431 TLS not available");
return TRUE;
}
if (resp)
sockprintf(*sock, -1, "234 Ready to start TLS");
if ((status = cryptSetAttribute(*sess, CRYPT_SESSINFO_ACTIVE, 1)) != CRYPT_OK) {
GCES(status, *sock, *sess, estr, "setting session active");
if ((status = cryptSetAttribute(*sess, CRYPT_OPTION_NET_READTIMEOUT, startup->max_inactivity)) != CRYPT_OK) {
GCES(status, *sock, *sess, estr, "setting read timeout");
static void filexfer(union xp_sockaddr* addr, SOCKET ctrl_sock, CRYPT_SESSION ctrl_sess, SOCKET pasv_sock, CRYPT_SESSION pasv_sess, SOCKET* data_sock
, CRYPT_SESSION *data_sess, char* filename, off_t filepos, BOOL* inprogress, BOOL* aborted
, BOOL delfile, BOOL tmpfile
, time_t* lastactive
, user_t* user
, client_t* client
, int dir
, BOOL receiving
, BOOL credits
, BOOL append
, char* desc, BOOL protected)
int result;
ulong l;
socklen_t addr_len;
union xp_sockaddr server_addr;
BOOL reuseaddr;
xfer_t* xfer;
char host_ip[INET6_ADDRSTRLEN];
if ((*inprogress) == TRUE) {
lprintf(LOG_WARNING, "%04d <%s> !DATA TRANSFER already in progress", ctrl_sock, user->alias);
sockprintf(ctrl_sock, ctrl_sess, "425 Transfer already in progress.");
if (tmpfile && !(startup->options & FTP_OPT_KEEP_TEMP_FILES))
ftp_remove(ctrl_sock, __LINE__, filename, user->alias, LOG_ERR);
if (*data_sock != INVALID_SOCKET)
ftp_close_socket(data_sock, data_sess, __LINE__);
if (pasv_sock == INVALID_SOCKET) { /* !PASV */
if ((*data_sock = socket(addr->addr.sa_family, SOCK_STREAM, IPPROTO_IP)) == INVALID_SOCKET) {
lprintf(LOG_ERR, "%04d <%s> !DATA ERROR %d opening socket", ctrl_sock, user->alias, SOCKET_ERRNO);
sockprintf(ctrl_sock, ctrl_sess, "425 Error %d opening socket", SOCKET_ERRNO);
if (tmpfile && !(startup->options & FTP_OPT_KEEP_TEMP_FILES))
ftp_remove(ctrl_sock, __LINE__, filename, user->alias, LOG_ERR);
if (startup->socket_open != NULL)
startup->socket_open(startup->cbdata, TRUE);
if (startup->options & FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG, "%04d <%s> DATA socket %d opened", ctrl_sock, user->alias, *data_sock);
/* Use port-1 for all data connections */
reuseaddr = TRUE;
(void)setsockopt(*data_sock, SOL_SOCKET, SO_REUSEADDR, (char*)&reuseaddr, sizeof(reuseaddr));
if ((result = getsockname(ctrl_sock, &server_addr.addr, &addr_len)) != 0) {
lprintf(LOG_CRIT, "%04d <%s> !DATA ERROR %d (%d) getting address/port of command socket (%u)"
, ctrl_sock, user->alias, result, SOCKET_ERRNO, pasv_sock);
inet_setaddrport(&server_addr, inet_addrport(&server_addr) - 1); /* 20? */
result = bind(*data_sock, &server_addr.addr, addr_len);
if (result != 0) {
inet_setaddrport(&server_addr, 0); /* any user port */
result = bind(*data_sock, &server_addr.addr, addr_len);
}
if (result != 0) {
lprintf(LOG_ERR, "%04d <%s> DATA ERROR %d (%d) binding socket %d"
, ctrl_sock, user->alias, result, SOCKET_ERRNO, *data_sock);
sockprintf(ctrl_sock, ctrl_sess, "425 Error %d binding socket", SOCKET_ERRNO);
if (tmpfile && !(startup->options & FTP_OPT_KEEP_TEMP_FILES))
ftp_remove(ctrl_sock, __LINE__, filename, user->alias, LOG_ERR);
*inprogress = FALSE;
ftp_close_socket(data_sock, data_sess, __LINE__);
result = connect(*data_sock, &addr->addr, xp_sockaddr_len(addr));
if (result != 0) {
lprintf(LOG_WARNING, "%04d <%s> !DATA ERROR %d (%d) connecting to client %s port %u on socket %d"
, ctrl_sock, user->alias, result, SOCKET_ERRNO
, host_ip, inet_addrport(addr), *data_sock);
sockprintf(ctrl_sock, ctrl_sess, "425 Error %d connecting to socket", SOCKET_ERRNO);
if (tmpfile && !(startup->options & FTP_OPT_KEEP_TEMP_FILES))
ftp_remove(ctrl_sock, __LINE__, filename, user->alias, LOG_ERR);
*inprogress = FALSE;
ftp_close_socket(data_sock, data_sess, __LINE__);
if (startup->options & FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG, "%04d <%s> DATA socket %d connected to %s port %u"
, ctrl_sock, user->alias, *data_sock, host_ip, inet_addrport(addr));
if (protected) {
if (start_tls(data_sock, data_sess, FALSE) || *data_sess == -1) {
lprintf(LOG_DEBUG, "%04d <%s> !DATA ERROR activating TLS"
, ctrl_sock, user->alias);
sockprintf(ctrl_sock, ctrl_sess, "425 Error activating TLS");
if (tmpfile && !(startup->options & FTP_OPT_KEEP_TEMP_FILES))
ftp_remove(ctrl_sock, __LINE__, filename, user->alias, LOG_ERR);
*inprogress = FALSE;
ftp_close_socket(data_sock, data_sess, __LINE__);
if (startup->options & FTP_OPT_DEBUG_DATA) {
addr_len = sizeof(*addr);
if ((result = getsockname(pasv_sock, &addr->addr, &addr_len)) != 0)
lprintf(LOG_CRIT, "%04d <%s> PASV !DATA ERROR %d (%d) getting address/port of passive socket (%u)"
, ctrl_sock, user->alias, result, SOCKET_ERRNO, pasv_sock);
else
lprintf(LOG_DEBUG, "%04d <%s> PASV DATA socket %d listening on %s port %u"
, ctrl_sock, user->alias, pasv_sock, host_ip, inet_addrport(addr));
if (!socket_readable(pasv_sock, TIMEOUT_SOCKET_LISTEN * 1000)) {
lprintf(LOG_WARNING, "%04d <%s> PASV !WARNING socket not readable"
, ctrl_sock, user->alias);
sockprintf(ctrl_sock, ctrl_sess, "425 Error %d selecting socket for connection", SOCKET_ERRNO);
if (tmpfile && !(startup->options & FTP_OPT_KEEP_TEMP_FILES))
ftp_remove(ctrl_sock, __LINE__, filename, user->alias, LOG_ERR);
return;
}
addr_len = sizeof(*addr);
socket_debug[ctrl_sock] |= SOCKET_DEBUG_ACCEPT;
*data_sock = accept(pasv_sock, &addr->addr, &addr_len);
socket_debug[ctrl_sock] &= ~SOCKET_DEBUG_ACCEPT;
if (*data_sock == INVALID_SOCKET) {
lprintf(LOG_WARNING, "%04d <%s> PASV !DATA ERROR %d accepting connection on socket %d"
, ctrl_sock, user->alias, SOCKET_ERRNO, pasv_sock);
sockprintf(ctrl_sock, ctrl_sess, "425 Error %d accepting connection", SOCKET_ERRNO);
if (tmpfile && !(startup->options & FTP_OPT_KEEP_TEMP_FILES))
ftp_remove(ctrl_sock, __LINE__, filename, user->alias, LOG_ERR);
if (startup->socket_open != NULL)
startup->socket_open(startup->cbdata, TRUE);
if (startup->options & FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG, "%04d <%s> PASV DATA socket %d connected to %s port %u"
, ctrl_sock, user->alias, *data_sock, host_ip, inet_addrport(addr));
if (protected) {
if (start_tls(data_sock, data_sess, FALSE) || *data_sess == -1) {
lprintf(LOG_WARNING, "%04d <%s> PASV !DATA ERROR starting TLS", pasv_sock, user->alias);
sockprintf(ctrl_sock, ctrl_sess, "425 Error negotiating TLS");
if (tmpfile && !(startup->options & FTP_OPT_KEEP_TEMP_FILES))
ftp_remove(ctrl_sock, __LINE__, filename, user->alias, LOG_ERR);
do {
if (ioctlsocket(*data_sock, FIONBIO, &l) != 0) {
lprintf(LOG_ERR, "%04d <%s> !DATA ERROR %d disabling socket blocking"
, ctrl_sock, user->alias, SOCKET_ERRNO);
sockprintf(ctrl_sock, ctrl_sess, "425 Error %d disabling socket blocking"
, SOCKET_ERRNO);
break;
}
if ((xfer = malloc(sizeof(xfer_t))) == NULL) {
lprintf(LOG_CRIT, "%04d <%s> !DATA MALLOC FAILURE LINE %d", ctrl_sock, user->alias, __LINE__);
sockprintf(ctrl_sock, ctrl_sess, "425 MALLOC FAILURE");
break;
}
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
memset(xfer, 0, sizeof(xfer_t));
xfer->ctrl_sock = ctrl_sock;
xfer->ctrl_sess = ctrl_sess;
xfer->data_sock = data_sock;
xfer->data_sess = data_sess;
xfer->inprogress = inprogress;
xfer->aborted = aborted;
xfer->delfile = delfile;
xfer->tmpfile = tmpfile;
xfer->append = append;
xfer->filepos = filepos;
xfer->credits = credits;
xfer->lastactive = lastactive;
xfer->user = user;
xfer->client = client;
xfer->dir = dir;
xfer->desc = desc;
SAFECOPY(xfer->filename, filename);
(void)protected_uint32_adjust(&thread_count, 1);
if (receiving)
result = _beginthread(receive_thread, 0, (void*)xfer);
else
result = _beginthread(send_thread, 0, (void*)xfer);
if (result != -1)
return; /* success */
/* failure */
if (tmpfile && !(startup->options & FTP_OPT_KEEP_TEMP_FILES))
ftp_remove(ctrl_sock, __LINE__, filename, user->alias, LOG_ERR);
/* convert "user name" to "user.name" or "mr. user" to "mr._user" */
char* dotname(char* in, char* out)
{
strcpy(out, "(null)");
return out;
}
if (strchr(in, '.') == NULL)
ch = '.';
ch = '_';
for (i = 0; in[i]; i++)
if (in[i] <= ' ')
out[i] = ch;
out[i] = in[i];
out[i] = 0;
static BOOL can_list(lib_t *lib, dir_t *dir, user_t *user, client_t *client)
if (!chk_ar(&scfg, lib->ar, user, client))
return FALSE;
if (dir->dirnum == scfg.sysop_dir)
return TRUE;
if (dir->dirnum == scfg.upload_dir)
return TRUE;
if (chk_ar(&scfg, dir->ar, user, client))
return TRUE;
return FALSE;
}
static int getdir_from_vpath(scfg_t* cfg, const char* vpath, user_t* user, client_t* client, BOOL include_upload_only)
{
int dir = -1;
int lib = -1;
char* filename = NULL;
enum parsed_vpath result = parse_vpath(cfg, vpath, &lib, &dir, &filename);
if (result == PARSED_VPATH_DIR || result == PARSED_VPATH_FULL) {
if ((include_upload_only && (dir == cfg->sysop_dir || dir == cfg->upload_dir))
|| user_can_access_dir(cfg, dir, user, client))
return dir;
}
return -1;
}
static BOOL ftpalias(char* fullalias, char* filename, user_t* user, client_t* client, int* curdir)
{
char* p;
char* tp;
char* fname = "";
char line[512];
char alias[512];
char aliasfile[MAX_PATH + 1];
int dir = -1;
FILE* fp;
BOOL result = FALSE;
SAFECOPY(alias, fullalias);
p = getfname(alias);
if (p) {
if (p != alias)
*(p - 1) = 0;
if (*p) {
if (filename == NULL && p != alias) // CWD command and a filename specified
for (int i = 0; i < scfg.total_dirs; ++i) {
if (scfg.dir[i]->vshortcut[0] == '\0')
continue;
if (!user_can_access_dir(&scfg, i, user, client))
continue;
if (stricmp(scfg.dir[i]->vshortcut, alias) == 0) {
if (curdir != NULL)
*curdir = i;
if (p != NULL && filename != NULL) {
if (*p)
sprintf(filename, "%s%s", scfg.dir[i]->path, p);
else
sprintf(filename, "%s%s", scfg.dir[i]->path, fname);
}
return TRUE;
}
}
SAFEPRINTF(aliasfile, "%sftpalias.cfg", scfg.ctrl_dir);
if ((fp = fopen(aliasfile, "r")) == NULL)
while (!feof(fp)) {
if (!fgets(line, sizeof(line), fp))
break;
p = line; /* alias */
SKIP_WHITESPACE(p);
if (*p == ';') /* comment */
continue;
tp = p; /* terminator */
FIND_WHITESPACE(tp);
if (stricmp(p, alias)) /* Not a match */
continue;
p = tp + 1; /* filename */
SKIP_WHITESPACE(p);
tp = p; /* terminator */
FIND_WHITESPACE(tp);
if (filename == NULL /* CWD? */ && (*lastchar(p) != '/' || (*fname != 0 && strcmp(fname, alias)))) {
fclose(fp);
return FALSE;
if (!strnicmp(p, BBS_VIRTUAL_PATH, strlen(BBS_VIRTUAL_PATH))) {
if ((dir = getdir_from_vpath(&scfg, p + strlen(BBS_VIRTUAL_PATH), user, client, true)) < 0) {
lprintf(LOG_WARNING, "0000 <%s> !Invalid virtual path: %s", user->alias, p);
/* invalid or no access */
continue;
}
if (p != NULL && filename != NULL) {
if (*p)
sprintf(filename, "%s%s", scfg.dir[dir]->path, p);
sprintf(filename, "%s%s", scfg.dir[dir]->path, fname);
} else if (filename != NULL)
strcpy(filename, p);
result = TRUE; /* success */
break;
}
fclose(fp);
if (curdir != NULL)
*curdir = dir;
/*
* Parses a path into *curlib, *curdir, and sets *pp to point to the filename
*/
static int parsepath(char** pp, user_t* user, client_t* client, int* curlib, int* curdir)
{
char filename[MAX_PATH + 1];
int lib = *curlib;
int dir = *curdir;
char * p = *pp;
char * tmp;
char * fname = strchr(p, 0);
int ret = 0;
size_t len;
if (*p == '/') {
lib = -1;
dir = -1;
while (*p) {
/* Relative path stuff */
if (strcmp(p, "..") == 0) {
if (dir >= 0)
dir = -1;
else if (lib >= 0)
lib = -1;
else
ret = -1;
p += 2;
}
else if (strncmp(p, "../", 3) == 0) {
if (dir >= 0)
dir = -1;
else if (lib >= 0)
lib = -1;
else
ret = -1;
p += 3;
}
else if (strcmp(p, ".") == 0)
else if (strncmp(p, "./", 2) == 0)
p += 2;
/* Path component */
else if (lib < 0) {
for (lib = 0; lib < scfg.total_libs; lib++) {
if (!chk_ar(&scfg, scfg.lib[lib]->ar, user, client))
len = strlen(scfg.lib[lib]->vdir);
if (strlen(p) < len)
continue;
if (p[len] != 0 && p[len] != '/')
continue;
if (!strnicmp(scfg.lib[lib]->vdir, p, len)) {
p += len;
if (*p)
p++;
break;
}
}
if (lib == scfg.total_libs) {
tmp = strchr(filename, '/');
if (tmp != NULL)
*tmp = 0;
if (ftpalias(filename, filename, user, client, &dir) == TRUE && dir >= 0) {
lib = scfg.dir[dir]->lib;
if (strchr(p, '/') != NULL) {
p = strchr(p, '/');
p++;
}
else
p = strchr(p, 0);
}
else {
ret = -1;
lib = -1;
if (strchr(p, '/') != NULL) {
p = strchr(p, '/');
p++;
}
else
p = strchr(p, 0);
for (dir = 0; dir < scfg.total_dirs; dir++) {
if (scfg.dir[dir]->lib != lib)
continue;
if (!can_list(scfg.lib[lib], scfg.dir[dir], user, client))
continue;
len = strlen(scfg.dir[dir]->vdir);
if (strlen(p) < len)
continue;
if (p[len] != 0 && p[len] != '/')
continue;
if (!strnicmp(scfg.dir[dir]->vdir, p, len)) {
p += len;
if (*p)
p++;
break;
}
}
if (dir == scfg.total_dirs) {
ret = -1;
dir = -1;
if (strchr(p, '/') != NULL) {
p = strchr(p, '/');
p++;
}
else
p = strchr(p, 0);
if (strchr(p, '/') != NULL) {
ret = -1;
p = strchr(p, '/');
p++;
}
else {
fname = p;
p += strlen(fname);
}
*curdir = dir;
*curlib = lib;
*pp = fname;
return ret;
}
char* root_dir(char* path)
{
char* p;
static char root[MAX_PATH + 1];
SAFECOPY(root, path);
if (!strncmp(root, "\\\\", 2)) { /* network path */
p = strchr(root + 2, '\\');
if (p)
p = strchr(p + 1, '\\');
if (p)
*(p + 1) = 0; /* truncate at \\computer\sharename\ */
else if (!strncmp(root + 1, ":/", 2) || !strncmp(root + 1, ":\\", 2))
root[3] = 0;
else if (*root == '/' || *root == '\\')
root[1] = 0;
char* genvpath(int lib, int dir, char* str)
strcpy(str, "/");
if (lib < 0)
strcat(str, scfg.lib[lib]->vdir);
strcat(str, "/");
if (dir < 0)
strcat(str, scfg.dir[dir]->vdir);
strcat(str, "/");
void ftp_printfile(SOCKET sock, CRYPT_SESSION sess, const char* name, unsigned code)
{
char path[MAX_PATH + 1];
char buf[512];
FILE* fp;
unsigned i;
SAFEPRINTF2(path, "%sftp%s.txt", scfg.text_dir, name);
if ((fp = fopen(path, "rb")) != NULL) {
i = 0;
while (!feof(fp)) {
if (!fgets(buf, sizeof(buf), fp))
break;
truncsp(buf);
if (!i)
sockprintf(sock, sess, "%u-%s", code, buf);
else
sockprintf(sock, sess, " %s", buf);
i++;
}
fclose(fp);
}
}
static BOOL ftp_hacklog(char* prot, char* user, char* text, char* host, union xp_sockaddr* addr)
{
#ifdef _WIN32
if (startup->sound.hack[0] && !sound_muted(&scfg))
PlaySound(startup->sound.hack, NULL, SND_ASYNC | SND_FILENAME);
#endif
return hacklog(&scfg, &mqtt, prot, user, text, host, addr);
}
/****************************************************************************/
/* Consecutive failed login (possible password hack) attempt tracking */
/****************************************************************************/
static BOOL badlogin(SOCKET sock, CRYPT_SESSION sess, ulong* login_attempts
, char* user, char* passwd, client_t* client, union xp_sockaddr* addr)
{
char tmp[128];
ulong count;
login_attempt_t attempt;
if (addr != NULL) {
count = loginFailure(startup->login_attempt_list, addr, client->protocol, user, passwd, &attempt);
if (count > 1)
lprintf(LOG_NOTICE, "%04d [%s] !%lu " STR_FAILED_LOGIN_ATTEMPTS " in %s"
, sock, client->addr, count, duration_estimate_to_vstr(attempt.time - attempt.first, tmp, sizeof tmp, 1, 1));
mqtt_user_login_fail(&mqtt, client, user);
if (startup->login_attempt.hack_threshold && count >= startup->login_attempt.hack_threshold)
ftp_hacklog("FTP LOGIN", user, passwd, client->host, addr);
if (startup->login_attempt.filter_threshold && count >= startup->login_attempt.filter_threshold) {
char reason[128];
snprintf(reason, sizeof reason, "%lu " STR_FAILED_LOGIN_ATTEMPTS " in %s"
, count, duration_estimate_to_str(attempt.time - attempt.first, tmp, sizeof tmp, 1, 1));
filter_ip(&scfg, client->protocol, reason, client->host, client->addr, user, /* fname: */ NULL, startup->login_attempt.filter_duration);
if (count > *login_attempts)
*login_attempts = count;
} else
(*login_attempts)++;
mswait(startup->login_attempt.delay); /* As recommended by RFC2577 */
if ((*login_attempts) >= 3) {
sockprintf(sock, sess, "421 Too many failed login attempts.");
ftp_printfile(sock, sess, "badlogin", 530);
sockprintf(sock, sess, "530 Invalid login.");
}
static char* ftp_tmpfname(char* fname, char* ext, SOCKET sock)
safe_snprintf(fname, MAX_PATH, "%sSBBS_FTP.%x%x%x%lx.%s"
, scfg.temp_dir, getpid(), sock, rand(), (ulong)clock(), ext);
#if defined(__GNUC__) // Catch printf-format errors
static BOOL send_mlsx(FILE *fp, SOCKET sock, CRYPT_SESSION sess, const char *format, ...) __attribute__ ((format (printf, 4, 5)));
#endif
static BOOL send_mlsx(FILE *fp, SOCKET sock, CRYPT_SESSION sess, const char *format, ...)
{
va_list va;
char * str;
int rval;
if (fp == NULL && sock == INVALID_SOCKET)
return FALSE;
va_start(va, format);
rval = vasprintf(&str, format, va);
va_end(va);
if (rval == -1)
return FALSE;
if (fp != NULL)
fprintf(fp, "%s\r\n", str);
else
sockprintf(sock, sess, " %s", str);
free(str);
return TRUE;
}
static char *get_unique(const char *path, char *uniq)
{
BYTE digest[MD5_DIGEST_SIZE];
if (path == NULL)
return NULL;
MD5_calc(digest, path, strlen(path));
MD5_hex(uniq, digest);
return uniq;
}
static BOOL send_mlsx_entry(FILE *fp, SOCKET sock, CRYPT_SESSION sess, unsigned feats, const char *type, const char *perm, uint64_t size, time_t modify, const char *owner, const char *unique, time_t ul, const char *fname)
char line[1024];
char * end;
BOOL need_owner = FALSE;
end = line;
*end = 0;
if (type != NULL && (feats & MLSX_TYPE))
end += sprintf(end, "Type=%s;", type);
if (perm != NULL && (feats & MLSX_PERM))
end += sprintf(end, "Perm=%s;", perm);
if (size != UINT64_MAX && (feats & MLSX_SIZE))
end += sprintf(end, "Size=%" PRIu64 ";", size);
if (modify > 0 && (feats & MLSX_MODIFY)) {
end += sprintf(end, "Modify=%04d%02d%02d%02d%02d%02d;",
t.tm_year + 1900, t.tm_mon + 1, t.tm_mday,
t.tm_hour, t.tm_min, t.tm_sec);
if (unique != NULL && (feats & MLSX_UNIQUE))
end += sprintf(end, "Unique=%s;", unique);
end += sprintf(end, "Create=%04d%02d%02d%02d%02d%02d;",
t.tm_year + 1900, t.tm_mon + 1, t.tm_mday,
t.tm_hour, t.tm_min, t.tm_sec);
// Owner can contain percents, so let send_mlsx() deal with it
if (owner != NULL && (feats & MLSX_OWNER)) {
strcat(end, "UNIX.ownername=%s;");
need_owner = TRUE;
}
strcat(end, " %s");
if (need_owner)
return send_mlsx(fp, sock, sess, line, owner, fname == NULL ? "" : fname);
return send_mlsx(fp, sock, sess, line, fname == NULL ? "" : fname);
static BOOL write_local_mlsx(FILE *fp, SOCKET sock, CRYPT_SESSION sess, unsigned feats, const char *path, BOOL full_path)
{
const char *type;
char permstr[11];
char * p;
BOOL is_file = FALSE;
if (stat(path, &st) != 0)
if (!strcmp(path, "."))
else if (!strcmp(path, ".."))
type = "pdir";
else if (*lastchar(path) == '/') /* is directory */
type = "dir";
else {
is_file = TRUE;
}
// TODO: Check for deletability 'd'
// TODO: Check for renamability 'f'
p = permstr;
if (is_file) {
if (access(path, W_OK) == 0) {
// Can append ('a') and write ('w')
*(p++) = 'a';
*(p++) = 'w';
}
if (access(path, R_OK) == 0) {
// Can read ('r')
}
}
else {
// TODO: Check these on Windows...
if (access(path, W_OK) == 0) {
// Can create files ('c'), directories ('m') and delete files ('p')
*(p++) = 'c';
*(p++) = 'm';
*(p++) = 'p';
}
if (access(path, R_OK) == 0) {
// Can change to the directory ('e'), and list files ('l')
*(p++) = 'e';
*(p++) = 'l';
}
}
return send_mlsx_entry(fp, sock, sess, feats, type, permstr, (uint64_t)st.st_size, st.st_mtime, NULL, NULL, st.st_ctime, full_path ? path : getfname(path));
}
/*
* Nobody can do anything but list files and change to dirs.
*/
static void get_libperm(lib_t *lib, user_t *user, client_t *client, char *permstr)
{
char *p = permstr;
if (chk_ar(&scfg, lib->ar, user, client)) {
//*(p++) = 'a'; // File may be appended to
//*(p++) = 'c'; // Files may be created in dir
//*(p++) = 'd'; // Item may be depeted (dir or file)
*(p++) = 'e'; // Can change to the dir
//*(p++) = 'f'; // Item may be renamed
*(p++) = 'l'; // Directory contents can be listed
//*(p++) = 'm'; // New subdirectories may be created
//*(p++) = 'p'; // Files/Dirs in directory may be deleted
//*(p++) = 'r'; // File may be retrieved
//*(p++) = 'w'; // File may be overwritten
}
}
static BOOL can_upload(lib_t *lib, dir_t *dir, user_t *user, client_t *client)
{
if (!chk_ar(&scfg, lib->ar, user, client))
return FALSE;
if (user->rest & FLAG('U'))
return FALSE;
if (dir_op(&scfg, user, client, dir->dirnum))
return TRUE;
// The rest can only upload if there's room
if (dir->maxfiles && getfiles(&scfg, dir->dirnum) >= dir->maxfiles)
return FALSE;
if (dir->dirnum == scfg.sysop_dir)
return TRUE;
if (dir->dirnum == scfg.upload_dir)
return TRUE;
if (!chk_ar(&scfg, lib->ul_ar, user, client))
return FALSE;
if (chk_ar(&scfg, dir->ul_ar, user, client))
return TRUE;
if ((user->exempt & FLAG('U')))
return TRUE;
return FALSE;
}
static BOOL can_delete_files(lib_t *lib, dir_t *dir, user_t *user, client_t *client)
{
if (!chk_ar(&scfg, lib->ar, user, client))
return FALSE;
if (user->rest & FLAG('D'))
return FALSE;
if (!chk_ar(&scfg, dir->ar, user, client))
return FALSE;
if (dir_op(&scfg, user, client, dir->dirnum))
return TRUE;
if (user->exempt & FLAG('R'))
return TRUE;
return FALSE;
}
static void get_dirperm(lib_t *lib, dir_t *dir, user_t *user, client_t *client, char *permstr)
{
char *p = permstr;
//*(p++) = 'a'; // File may be appended to
if (can_upload(lib, dir, user, client))
*(p++) = 'c'; // Files may be created in dir
//*(p++) = 'd'; // Item may be depeted (dir or file)
if (can_list(lib, dir, user, client)) {
*(p++) = 'e'; // Can change to the dir
//*(p++) = 'f'; // Item may be renamed
*(p++) = 'l'; // Directory contents can be listed
}
//*(p++) = 'm'; // New subdirectories may be created
if (can_delete_files(lib, dir, user, client))
*(p++) = 'p'; // Files/Dirs in directory may be deleted
//*(p++) = 'r'; // File may be retrieved
//*(p++) = 'w'; // File may be overwritten
}
static BOOL can_append(lib_t *lib, dir_t *dir, user_t *user, client_t *client, file_t *file)
{
if (!chk_ar(&scfg, lib->ar, user, client))
return FALSE;
if (user->rest & FLAG('U'))
return FALSE;
if (dir->dirnum != scfg.sysop_dir && dir->dirnum != scfg.upload_dir && !chk_ar(&scfg, dir->ar, user, client))
return FALSE;
if (!dir_op(&scfg, user, client, dir->dirnum) && !(user->exempt & FLAG('U'))) {
if (!chk_ar(&scfg, dir->ul_ar, user, client) || !chk_ar(&scfg, lib->ul_ar, user, client))
return FALSE;
}
if (file->from == NULL || stricmp(file->from, user->alias) != 0)
return FALSE;
return TRUE;
}
static BOOL can_delete(lib_t *lib, dir_t *dir, user_t *user, client_t *client, file_t *file)
{
if (user->rest & FLAG('D'))
return FALSE;
if (!chk_ar(&scfg, lib->ar, user, client))
return FALSE;
if (!chk_ar(&scfg, dir->ar, user, client))
return FALSE;
if (!dir_op(&scfg, user, client, dir->dirnum))
return FALSE;
if (!(user->exempt & FLAG('R')))
return FALSE;
return TRUE;
}
static BOOL can_download(lib_t *lib, dir_t *dir, user_t *user, client_t *client, file_t *file)
{
return user_can_download(&scfg, dir->dirnum, user, client, /* reason */ NULL);
}
static void get_fileperm(lib_t *lib, dir_t *dir, user_t *user, client_t *client, file_t *file, char *permstr)
{
char *p = permstr;
if (can_append(lib, dir, user, client, file))
*(p++) = 'a'; // File may be appended to
//*(p++) = 'c'; // Files may be created in dir
if (can_delete(lib, dir, user, client, file))
*(p++) = 'd'; // Item may be depeted (dir or file)
//*(p++) = 'e'; // Can change to the dir
//*(p++) = 'f'; // Item may be renamed
//*(p++) = 'l'; // Directory contents can be listed
//*(p++) = 'm'; // New subdirectories may be created
//*(p++) = 'p'; // Files/Dirs in directory may be deleted
if (can_download(lib, dir, user, client, file))
*(p++) = 'r'; // File may be retrieved
//*(p++) = 'w'; // File may be overwritten
*p = 0;
}
static char* get_owner_name(file_t *file, char *namestr, size_t size)
{
char *p;
if (file) {
if (file->hdr.attr & MSG_ANONYMOUS)
strlcpy(namestr, ANONYMOUS, size);
strlcpy(namestr, file->from, size);
strlcpy(namestr, scfg.sys_id, size);
// Now ensure it's an RCHAR string.
for (p = namestr; *p; p++) {
if (*p >= '!' && *p <= ')')
continue;
else if (*p >= '+' && *p <= ':')
continue;
else if (*p >= '?' && *p <= 'Z')
continue;
else if (*p == '\\')
continue;
else if (*p == '^')
continue;
else if (*p == '_')
continue;
else if (*p >= 'a' && *p <= 'z')
continue;
else if (*p == ' ')
*p = '.';
else
*p = '_';
}
static void ctrl_thread(void* arg)
{
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
unsigned mlsx_feats = (MLSX_TYPE | MLSX_PERM | MLSX_SIZE | MLSX_MODIFY | MLSX_OWNER | MLSX_UNIQUE | MLSX_CREATE);
char buf[512];
char str[128];
char uniq[33];
char owner[33];
char error[256];
char* cmd;
char* p;
char* np;
char* tp;
char* dp;
char* ap;
char* filespec;
char* mode = "active";
char old_char;
char password[64];
char fname[MAX_PATH + 1];
char qwkfile[MAX_PATH + 1];
char aliasfile[MAX_PATH + 1];
char aliaspath[MAX_PATH + 1];
char mls_path[MAX_PATH + 1];
char * mls_fname;
char permstr[11];
char aliasline[512];
char desc[501] = "";
char sys_pass[128];
char host_name[256];
char host_ip[INET6_ADDRSTRLEN];
char data_ip[INET6_ADDRSTRLEN];
uint16_t data_port;
char path[MAX_PATH + 1];
char local_dir[MAX_PATH + 1];
char ren_from[MAX_PATH + 1] = "";
WORD port;
uint32_t ip_addr;
socklen_t addr_len;
unsigned h1, h2, h3, h4;
u_short p1, p2; /* For PORT command */
int i;
int rd;
int result;
int lib;
int dir;
int curlib = -1;
int curdir = -1;
int orglib;
int orgdir;
fmutex_t mutex_file = fmutex_init();
long filepos = 0L;
long timeleft;
ulong l;
ulong login_attempts = 0;
uint64_t avail; /* disk space */
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
ulong count;
BOOL detail;
BOOL success;
BOOL getdate;
BOOL getsize;
BOOL delecmd;
BOOL delfile;
BOOL tmpfile;
BOOL credits;
BOOL filedat = FALSE;
BOOL transfer_inprogress;
BOOL transfer_aborted;
BOOL sysop = FALSE;
BOOL local_fsys = FALSE;
BOOL alias_dir;
BOOL append;
BOOL reuseaddr;
FILE* fp;
FILE* alias_fp;
SOCKET sock;
SOCKET tmp_sock;
SOCKET pasv_sock = INVALID_SOCKET;
CRYPT_SESSION pasv_sess = -1;
SOCKET data_sock = INVALID_SOCKET;
CRYPT_SESSION data_sess = -1;
HOSTENT* host;
union xp_sockaddr addr;
union xp_sockaddr data_addr;
union xp_sockaddr pasv_addr;
ftp_t ftp = *(ftp_t*)arg;
user_t user;
time_t t;
time_t now;
time_t logintime = 0;
time_t lastactive;
time_t file_date;
off_t file_size;
node_t node;
client_t client;
struct tm tm;
struct tm cur_tm;
login_attempt_t attempted;
CRYPT_SESSION sess = -1;
BOOL got_pbsz = FALSE;
BOOL protection = FALSE;
SetThreadName("sbbs/ftpControl");
thread_up(TRUE /* setuid */);
lastactive = time(NULL);
/* Default data port is ctrl port-1 */
data_port = inet_addrport(&data_addr) - 1;
lprintf(LOG_DEBUG, "%04d Session thread started", sock);
free(arg);
if (startup->sound.answer[0] && !sound_muted(&scfg))
PlaySound(startup->sound.answer, NULL, SND_ASYNC | SND_FILENAME);

rswindell
committed
transfer_inprogress = FALSE;
transfer_aborted = FALSE;
if ((i = ioctlsocket(sock, FIONBIO, &l)) != 0) {
lprintf(LOG_ERR, "%04d !ERROR %d (%d) disabling socket blocking"
, sock, i, SOCKET_ERRNO);
sockprintf(sock, sess, "425 Error %d disabling socket blocking"
, SOCKET_ERRNO);
ftp_close_socket(&sock, &sess, __LINE__);
thread_down();
return;
}
memset(&user, 0, sizeof(user));
union xp_sockaddr local_addr;
memset(&local_addr, 0, sizeof(local_addr));
addr_len = sizeof(local_addr);
if (getsockname(sock, (struct sockaddr *)&local_addr, &addr_len) != 0) {
lprintf(LOG_CRIT, "%04d [%s] !ERROR %d getting local address/port of socket"
, sock, host_ip, SOCKET_ERRNO);
ftp_close_socket(&sock, &sess, __LINE__);
thread_down();
return;
}
char local_ip[INET6_ADDRSTRLEN];
inet_addrtop(&local_addr, local_ip, sizeof local_ip);
lprintf(LOG_INFO, "%04d [%s] Connection accepted on %s port %u from port %u"
, sock, host_ip, local_ip, inet_addrport(&local_addr), inet_addrport(&ftp.client_addr));
if (!(startup->options & FTP_OPT_NO_HOST_LOOKUP)) {
getnameinfo(&ftp.client_addr.addr, sizeof(ftp.client_addr), host_name, sizeof(host_name), NULL, 0, NI_NAMEREQD);
lprintf(LOG_INFO, "%04d [%s] Hostname: %s", sock, host_ip, host_name);
ulong banned = loginBanned(&scfg, startup->login_attempt_list, sock, host_name, startup->login_attempt, &attempted);
char ban_duration[128];
lprintf(LOG_NOTICE, "%04d [%s] !TEMPORARY BAN (%lu login attempts, last: %s) - remaining: %s"
, sock, host_ip, attempted.count - attempted.dupes, attempted.user
, duration_estimate_to_vstr(banned, ban_duration, sizeof ban_duration, 1, 1));
sockprintf(sock, sess, "550 Access denied.");
ftp_close_socket(&sock, &sess, __LINE__);
thread_down();
return;
}
struct trash trash;
if (trashcan2(&scfg, host_ip, NULL, "ip", &trash)) {
char details[128];
lprintf(LOG_NOTICE, "%04d [%s] !CLIENT BLOCKED in ip.can %s", sock, host_ip, trash_details(&trash, details, sizeof details));
sockprintf(sock, sess, "550 Access denied.");
ftp_close_socket(&sock, &sess, __LINE__);
thread_down();
return;
}
if (trashcan2(&scfg, host_name, NULL, "host", &trash)) {
char details[128];
lprintf(LOG_NOTICE, "%04d [%s] !CLIENT BLOCKED in host.can: %s %s", sock, host_ip, host_name, trash_details(&trash, details, sizeof details));
sockprintf(sock, sess, "550 Access denied.");
ftp_close_socket(&sock, &sess, __LINE__);
thread_down();
return;
}
/* For PASV mode */
addr_len = sizeof(pasv_addr);
if ((result = getsockname(sock, &pasv_addr.addr, &addr_len)) != 0) {
lprintf(LOG_CRIT, "%04d !ERROR %d (%d) getting address/por of socket", sock, result, SOCKET_ERRNO);
sockprintf(sock, sess, "425 Error %d getting address/port", SOCKET_ERRNO);
ftp_close_socket(&sock, &sess, __LINE__);
uint32_t client_count = protected_uint32_adjust(&active_clients, 1);
if (client_count > client_highwater) {
client_highwater = client_count;
if (client_highwater > 1)
lprintf(LOG_NOTICE, "%04d New active client highwater mark: %u"
, sock, client_highwater);
mqtt_pub_uintval(&mqtt, TOPIC_SERVER, "highwater", client_highwater);
}
update_clients();
/* Initialize client display */
client.size = sizeof(client);
client.time = time32(NULL);
SAFECOPY(client.addr, host_ip);
SAFECOPY(client.host, host_name);
client.port = inet_addrport(&ftp.client_addr);
SAFECOPY(client.protocol, "FTP");
SAFECOPY(client.user, STR_UNKNOWN_USER);
client.usernum = 0;
client_on(sock, &client, FALSE /* update */);
if (startup->login_attempt.throttle
&& (login_attempts = loginAttempts(startup->login_attempt_list, &ftp.client_addr)) > 1) {
lprintf(LOG_DEBUG, "%04d Throttling suspicious connection from: %s (%lu login attempts)"
, sock, host_ip, login_attempts);
mswait(login_attempts * startup->login_attempt.throttle);
}
sockprintf(sock, sess, "220-%s (%s)", scfg.sys_name, server_host_name());
sockprintf(sock, sess, " Synchronet FTP Server %s%c-%s Ready"
, VERSION, REVISION, PLATFORM_DESC);
sprintf(str, "%sftplogin.txt", scfg.text_dir);
if ((fp = fopen(str, "rb")) != NULL) {
while (!feof(fp)) {
if (!fgets(buf, sizeof(buf), fp))
sockprintf(sock, sess, " %s", buf);
sockprintf(sock, sess, "220 Please enter your user name.");
socket_debug[sock] |= SOCKET_DEBUG_CTRL;
socket_debug[sock] |= SOCKET_DEBUG_READLINE;
rd = sockreadline(sock, sess, buf, sizeof(buf), &lastactive);
socket_debug[sock] &= ~SOCKET_DEBUG_READLINE;
if (rd < 1) {
if (transfer_inprogress == TRUE) {
lprintf(LOG_WARNING, "%04d <%s> !Aborting transfer due to CTRL socket receive error", sock, user.number ? user.alias : host_ip);
transfer_aborted = TRUE;
}
truncsp(buf);
lastactive = time(NULL);
cmd = buf;
while (((BYTE)*cmd) == TELNET_IAC) {
lprintf(LOG_DEBUG, "%04d <%s> RX%s: Telnet cmd: %s", sock, user.number ? user.alias : host_ip, sess == -1 ? "" : "S", telnet_cmd_desc(*cmd));
while (*cmd && *cmd < ' ') {
lprintf(LOG_DEBUG, "%04d <%s> RX%s: %d (0x%02X)", sock, user.number ? user.alias : host_ip, sess == -1 ? "" : "S", (BYTE)*cmd, (BYTE)*cmd);
if (startup->options & FTP_OPT_DEBUG_RX)
lprintf(LOG_DEBUG, "%04d <%s> RX%s: %s", sock, user.number ? user.alias : host_ip, sess == -1 ? "" : "S", cmd);
if (!stricmp(cmd, "NOOP")) {
sockprintf(sock, sess, "200 NOOP command successful.");
if (!stricmp(cmd, "HELP SITE") || !stricmp(cmd, "SITE HELP")) {
sockprintf(sock, sess, "214-The following SITE commands are recognized (* => unimplemented):");
sockprintf(sock, sess, " HELP VER WHO UPTIME");
if (user.level >= SYSOP_LEVEL)
sockprintf(sock, sess,
" RECYCLE [ALL]");
if (sysop)
sockprintf(sock, sess,
" EXEC <cmd>");
sockprintf(sock, sess, "214 Direct comments to sysop@%s.", scfg.sys_inetaddr);
if (!strnicmp(cmd, "HELP", 4)) {
sockprintf(sock, sess, "214-The following commands are recognized (* => unimplemented, # => extension):");
sockprintf(sock, sess, " USER PASS CWD XCWD CDUP XCUP PWD XPWD");
sockprintf(sock, sess, " QUIT REIN PORT PASV LIST NLST NOOP HELP");
sockprintf(sock, sess, " SIZE MDTM RETR STOR REST ALLO ABOR SYST");
sockprintf(sock, sess, " TYPE STRU MODE SITE RNFR* RNTO* DELE* DESC#");
sockprintf(sock, sess, " FEAT# OPTS# EPRT EPSV AUTH# PBSZ# PROT# CCC#");
sockprintf(sock, sess, " MLSD#");
sockprintf(sock, sess, "214 Direct comments to sysop@%s.", scfg.sys_inetaddr);
if (!stricmp(cmd, "FEAT")) {
sockprintf(sock, sess, "211-The following additional (post-RFC949) features are supported:");
sockprintf(sock, sess, " DESC");
sockprintf(sock, sess, " MDTM");
sockprintf(sock, sess, " SIZE");
sockprintf(sock, sess, " REST STREAM");
sockprintf(sock, sess, " AUTH TLS");
sockprintf(sock, sess, " PBSZ");
sockprintf(sock, sess, " PROT");
sockprintf(sock, sess, " MLST Type%s;Perm%s;Size%s;Modify%s;UNIX.ownername%s;Unique%s;Create%s",
(mlsx_feats & MLSX_TYPE) ? "*" : "",
(mlsx_feats & MLSX_PERM) ? "*" : "",
(mlsx_feats & MLSX_SIZE) ? "*" : "",
(mlsx_feats & MLSX_MODIFY) ? "*" : "",
(mlsx_feats & MLSX_OWNER) ? "*" : "",
(mlsx_feats & MLSX_UNIQUE) ? "*" : "",
(mlsx_feats & MLSX_CREATE) ? "*" : ""
);
sockprintf(sock, sess, " TVFS");
sockprintf(sock, sess, "211 End");
if (!strnicmp(cmd, "OPTS MLST", 9)) {
if (cmd[9] == 0) {
mlsx_feats = 0;
continue;
}
if (cmd[9] != ' ') {
sockprintf(sock, sess, "501 Option not supported.");
continue;
}
mlsx_feats = 0;
for (p = cmd; *p; p++)
*p = toupper(*p);
if (strstr(cmd, "TYPE;"))
mlsx_feats |= MLSX_TYPE;
if (strstr(cmd, "PERM;"))
mlsx_feats |= MLSX_PERM;
if (strstr(cmd, "SIZE;"))
mlsx_feats |= MLSX_SIZE;
if (strstr(cmd, "MODIFY;"))
mlsx_feats |= MLSX_MODIFY;
if (strstr(cmd, "UNIX.OWNERNAME;"))
mlsx_feats |= MLSX_OWNER;
if (strstr(cmd, "UNIQUE;"))
mlsx_feats |= MLSX_UNIQUE;
if (strstr(cmd, "CREATE;"))
mlsx_feats |= MLSX_CREATE;
sockprintf(sock, sess, "200 %s%s%s%s%s%s%s",
(mlsx_feats & MLSX_TYPE) ? "Type;" : "",
(mlsx_feats & MLSX_PERM) ? "Perm;" : "",
(mlsx_feats & MLSX_SIZE) ? "Size;" : "",
(mlsx_feats & MLSX_MODIFY) ? "Modify;" : "",
(mlsx_feats & MLSX_OWNER) ? "UNIX.ownername;" : "",
(mlsx_feats & MLSX_UNIQUE) ? "Unique;" : "",
(mlsx_feats & MLSX_CREATE) ? "Create;" : ""
);
if (!strnicmp(cmd, "OPTS", 4)) {
sockprintf(sock, sess, "501 Option not supported.");
if (!stricmp(cmd, "QUIT")) {
ftp_printfile(sock, sess, "bye", 221);
sockprintf(sock, sess, "221 Goodbye. Closing control connection.");
if (!strnicmp(cmd, "USER ", 5)) {
sysop = FALSE;
user.number = 0;
fmutex_close(&mutex_file);
SKIP_WHITESPACE(p);
SAFECOPY(user.alias, p);
user.number = find_login_id(&scfg, user.alias);
if (!user.number && (stricmp(user.alias, "anonymous") == 0 || stricmp(user.alias, "ftp") == 0))
user.number = matchuser(&scfg, "guest", FALSE);
if (user.number && getuserdat(&scfg, &user) == 0 && user.pass[0] == 0)
sockprintf(sock, sess, "331 User name okay, give your full e-mail address as password.");
sockprintf(sock, sess, "331 User name okay, need password.");
user.number = 0;
if (!strnicmp(cmd, "PASS ", 5) && user.alias[0]) {
user.number = 0;
fmutex_close(&mutex_file);
SKIP_WHITESPACE(p);
SAFECOPY(password, p);
uint usernum = find_login_id(&scfg, user.alias);
if (usernum == 0) {
if (scfg.sys_misc & SM_ECHO_PW)
lprintf(LOG_NOTICE, "%04d !UNKNOWN USER: '%s' (password: %s)", sock, user.alias, p);
lprintf(LOG_NOTICE, "%04d !UNKNOWN USER: '%s'", sock, user.alias);
if (badlogin(sock, sess, &login_attempts, user.alias, p, &client, &ftp.client_addr))
user.number = usernum;
if ((i = getuserdat(&scfg, &user)) != 0) {
lprintf(LOG_ERR, "%04d <%s> !ERROR %d (errno %d %s) getting data for user #%d"
, sock, user.alias, i, errno, safe_strerror(errno, error, sizeof error), usernum);
sockprintf(sock, sess, "530 Database error %d", i);
if (user.misc & (DELETED | INACTIVE)) {
lprintf(LOG_NOTICE, "%04d <%s> !DELETED or INACTIVE user #%d"
, sock, user.alias, user.number);
user.number = 0;
if (badlogin(sock, sess, &login_attempts, NULL, NULL, NULL, NULL))
if (user.rest & FLAG('T')) {
lprintf(LOG_NOTICE, "%04d <%s> !T RESTRICTED user #%d"
, sock, user.alias, user.number);
user.number = 0;
if (badlogin(sock, sess, &login_attempts, NULL, NULL, NULL, NULL))
if (user.ltoday >= scfg.level_callsperday[user.level]
&& !(user.exempt & FLAG('L'))) {
lprintf(LOG_NOTICE, "%04d <%s> !MAXIMUM LOGONS (%d) reached for level %u"
, sock, user.alias, scfg.level_callsperday[user.level], user.level);
sockprintf(sock, sess, "530 Maximum logons per day reached.");
user.number = 0;
if (user.rest & FLAG('L') && user.ltoday >= 1) {
lprintf(LOG_NOTICE, "%04d <%s> !L RESTRICTED user already on today"
, sock, user.alias);
sockprintf(sock, sess, "530 Maximum logons per day reached.");
user.number = 0;
if (!chk_ars(&scfg, startup->login_ars, &user, &client)) {
lprintf(LOG_NOTICE, "%04d <%s> !Insufficient server access: %s"
, sock, user.alias, startup->login_ars);
sockprintf(sock, sess, "530 Insufficient server access.");
user.number = 0;
continue;
}
SAFEPRINTF2(sys_pass, "%s:%s", user.pass, scfg.sys_pass);
if (!user.pass[0]) { /* Guest/Anonymous */
if (trashcan(&scfg, password, "email")) {
lprintf(LOG_NOTICE, "%04d <%s> !BLOCKED e-mail address: %s", sock, user.alias, password);
user.number = 0;
if (badlogin(sock, sess, &login_attempts, NULL, NULL, NULL, NULL))
continue;
}
lprintf(LOG_INFO, "%04d <%s> identity: %s", sock, user.alias, password);
putuserstr(&scfg, user.number, USER_NETMAIL, password);
else if (user.level >= SYSOP_LEVEL && !stricmp(password, sys_pass)) {
if (scfg.sys_misc & SM_R_SYSOP) {
lprintf(LOG_INFO, "%04d <%s> Sysop access granted", sock, user.alias);
sysop = TRUE;
} else
lprintf(LOG_NOTICE, "%04d <%s> Remote sysop access disabled", sock, user.alias);
else if (stricmp(password, user.pass)) {
if (scfg.sys_misc & SM_ECHO_PW)
lprintf(LOG_NOTICE, "%04d <%s> !FAILED Password attempt: '%s' expected '%s'"
, sock, user.alias, password, user.pass);

rswindell
committed
else
lprintf(LOG_NOTICE, "%04d <%s> !FAILED Password attempt"
, sock, user.alias);
user.number = 0;
if (badlogin(sock, sess, &login_attempts, user.alias, password, &client, &ftp.client_addr))
if (user.rest & FLAG('Q')) { // QWKnet accont
snprintf(mutex_file.name, sizeof mutex_file.name, "%suser/%04u.ftp", scfg.data_dir, user.number);
if (!fmutex_open(&mutex_file, startup->host_name, /* max_age: */ 60 * 60)) {
lprintf(LOG_NOTICE, "%04d <%s> QWKnet account already logged-in to FTP server: %s (since %s)"
, sock, user.alias, mutex_file.name, time_as_hhmm(&scfg, mutex_file.time, str));
sockprintf(sock, sess, "421 QWKnet accounts are limited to one concurrent FTP session");
user.number = 0;
break;
}
}
SAFECOPY(client.user, user.alias);
loginSuccess(startup->login_attempt_list, &ftp.client_addr);
} else { /* anonymous */
SAFEPRINTF2(client.user, "%s <%.32s>", user.alias, password);
client.usernum = user.number;
client_on(sock, &client, TRUE /* update */);
lprintf(LOG_INFO, "%04d [%s] <%s> logged-in (%u today, %u total)"
, sock, host_ip, user.alias, user.ltoday + 1, user.logons + 1);
logintime = time(NULL);
timeleft = (long)gettimeleft(&scfg, &user, logintime);
ftp_printfile(sock, sess, "hello", 230);
if (sysop)
sockprintf(sock, sess, "230-Sysop access granted.");
sockprintf(sock, sess, "230-%s logged in.", user.alias);
if (!(user.exempt & FLAG('D')) && user_available_credits(&user) > 0)
sockprintf(sock, sess, "230-You have %" PRIu64 " download credits."
, user_available_credits(&user));
sockprintf(sock, sess, "230 You are allowed %lu minutes of use for this session."
, timeleft / 60);
sprintf(qwkfile, "%sfile/%04d.qwk", scfg.data_dir, user.number);
/* Adjust User Total Logons/Logons Today */
user.logons++;
user.ltoday++;
SAFECOPY(user.connection, "FTP");
SAFECOPY(user.comp, host_name);
SAFECOPY(user.ipaddr, host_ip);
user.logontime = (time32_t)logintime;
if ((result = putuserdat(&scfg, &user)) != 0)
lprintf(LOG_ERR, "%04d [%s] <%s> !Error %d (errno %d %s) writing user data for user #%d"
, sock, host_ip, user.alias, result, errno, safe_strerror(errno, error, sizeof error), user.number);
mqtt_user_login(&mqtt, &client);
#ifdef _WIN32
if (startup->sound.login[0] && !sound_muted(&scfg))
PlaySound(startup->sound.login, NULL, SND_ASYNC | SND_FILENAME);
if (!stricmp(cmd, "AUTH TLS")) {
sockprintf(sock, sess, "534 Already in TLS mode");
if (startup->options & FTP_OPT_NO_FTPS) {
lprintf(LOG_NOTICE, "%04d [%s] AUTH TLS rejected because FTPS support is disabled", sock, host_ip);
sockprintf(sock, sess, "431 TLS not available");
continue;
}
if (start_tls(&sock, &sess, TRUE) || sess == -1) {
lprintf(LOG_WARNING, "%04d [%s] failed to initialize TLS successfully", sock, host_ip);
}
user.number = 0;
sysop = FALSE;
filepos = 0;
lprintf(LOG_INFO, "%04d [%s] initialized TLS successfully", sock, host_ip);
SAFECOPY(client.protocol, "FTPS");
client_on(sock, &client, /* update: */ TRUE);
sockprintf(sock, sess, "504 TLS is the only AUTH supported");
if (!stricmp(cmd, "PBSZ 0") && sess != -1) {
sockprintf(sock, sess, "200 OK");
sockprintf(sock, sess, "503 Need AUTH TLS first");
if (strspn(cmd + 5, "0123456789") == strlen(cmd + 5)) {
sockprintf(sock, sess, "200 PBSZ=0");
sockprintf(sock, sess, "501 Unable to parse buffer size");
continue;
}
if (!strnicmp(cmd, "PROT ", 5)) {
if (sess == -1) {
sockprintf(sock, sess, "503 No AUTH yet");
if (!strnicmp(cmd, "PROT P", 6) && sess != -1 && got_pbsz) {
sockprintf(sock, sess, "200 Accepted");
if (!strnicmp(cmd, "PROT C", 6) && sess != -1 && got_pbsz) {
sockprintf(sock, sess, "200 Accepted");
sockprintf(sock, sess, "536 Only C and P are supported in TLS mode");
if (!stricmp(cmd, "CCC")) {
sockprintf(sock, sess, "533 Not in TLS mode");
sockprintf(sock, sess, "200 Accepted");
destroy_session(lprintf, sess);
if (!user.number) {
sockprintf(sock, sess, "530 Please login with USER and PASS.");
if (!(user.rest & FLAG('G')))
getuserdat(&scfg, &user); /* get current user data */
if ((timeleft = (long)gettimeleft(&scfg, &user, logintime)) < 1L) {
sockprintf(sock, sess, "421 Sorry, you've run out of time.");
lprintf(LOG_WARNING, "%04d <%s> Out of time, disconnecting", sock, user.alias);
break;
}
/********************************/
/* These commands require login */
/********************************/
if (!stricmp(cmd, "REIN")) {
lprintf(LOG_INFO, "%04d <%s> reinitialized control session", sock, user.alias);
user.number = 0;
sysop = FALSE;
filepos = 0;
sockprintf(sock, sess, "220 Control session re-initialized. Ready for re-login.");
destroy_session(lprintf, sess);
sess = -1;
}
got_pbsz = FALSE;
protection = FALSE;
if (!stricmp(cmd, "SITE WHO")) {
sockprintf(sock, sess, "211-Active Telnet Nodes:");
for (i = 0; i < scfg.sys_nodes && i < scfg.sys_lastnode; i++) {
if ((result = getnodedat(&scfg, i + 1, &node, FALSE, NULL)) != 0) {
sockprintf(sock, sess, " Error %d getting data for Telnet Node %d", result, i + 1);
if (node.status == NODE_INUSE)
sockprintf(sock, sess, " Node %3d: %s", i + 1, username(&scfg, node.useron, str));
sockprintf(sock, sess, "211 End (%d active FTP clients)", protected_uint32_value(active_clients));
continue;
}
if (!stricmp(cmd, "SITE VER")) {
sockprintf(sock, sess, "211 %s", ftp_ver());
if (!stricmp(cmd, "SITE UPTIME")) {
sockprintf(sock, sess, "211 %s (%lu served)", sectostr((uint)(time(NULL) - uptime), str), served);
continue;
}
if (!stricmp(cmd, "SITE RECYCLE") && user.level >= SYSOP_LEVEL) {
startup->recycle_now = TRUE;
sockprintf(sock, sess, "211 server will recycle when not in-use");
continue;
}
if (!stricmp(cmd, "SITE RECYCLE ALL") && user.level >= SYSOP_LEVEL) {
refresh_cfg(&scfg);
sockprintf(sock, sess, "211 ALL servers/nodes will recycle when not in-use");
continue;
}
if (!strnicmp(cmd, "SITE EXEC ", 10) && sysop) {
p = cmd + 10;
SKIP_WHITESPACE(p);
#ifdef __unix__
fp = popen(p, "r");
if (fp == NULL)
sockprintf(sock, sess, "500 Error %d opening pipe to: %s", errno, p);
while (!feof(fp)) {
if (fgets(str, sizeof(str), fp) == NULL)
sockprintf(sock, sess, "200-%s", str);
sockprintf(sock, sess, "200 %s returned %d", p, pclose(fp));
}
#else
sockprintf(sock, sess, "200 system(%s) returned %d", p, system(p));
if (!stricmp(cmd, "SITE DEBUG")) {
sockprintf(sock, sess, "211-Debug");
for (i = 0; i < sizeof(socket_debug); i++)
if (socket_debug[i] != 0)
sockprintf(sock, sess, "211-socket %d = 0x%X", i, socket_debug[i]);
sockprintf(sock, sess, "211 End");
continue;
}
if (strnicmp(cmd, "PORT ", 5) == 0 || strnicmp(cmd, "EPRT ", 5) == 0 || strnicmp(cmd, "LPRT ", 5) == 0) {
if (pasv_sock != INVALID_SOCKET) {
ftp_close_socket(&pasv_sock, &pasv_sess, __LINE__);
memcpy(&data_addr, &ftp.client_addr, ftp.client_addr_len);
SKIP_WHITESPACE(p);
if (strnicmp(cmd, "PORT ", 5) == 0) {
sscanf(p, "%u,%u,%u,%u,%hd,%hd", &h1, &h2, &h3, &h4, &p1, &p2);
data_addr.in.sin_family = AF_INET;
data_addr.in.sin_addr.s_addr = htonl((h1 << 24) | (h2 << 16) | (h3 << 8) | h4);
data_port = (p1 << 8) | p2;
} else if (strnicmp(cmd, "EPRT ", 5) == 0) { /* EPRT */
char delim = *p;
int prot;
char addr_str[INET6_ADDRSTRLEN];
prot = strtol(p, NULL, /* base: */ 10);
switch (prot) {
FIND_CHAR(p, delim);
if (*p)
ap = p;
old_char = *p;
*p = 0;
data_addr.in.sin_addr.s_addr = inet_addr(ap);
*p = old_char;
if (*p)
data_port = atoi(p);
data_addr.in.sin_family = AF_INET;
FIND_CHAR(p, delim);
if (*p)
addr_str[sizeof(addr_str) - 1] = 0;
tp = addr_str;
*tp = 0;
if (inet_ptoaddr(addr_str, &data_addr, sizeof(data_addr)) == NULL) {
lprintf(LOG_WARNING, "%04d <%s> !Unable to parse IPv6 address: %s", sock, user.alias, addr_str);
sockprintf(sock, sess, "522 Unable to parse IPv6 address (1)");
FIND_CHAR(p, delim);
if (*p)
data_port = atoi(p);
data_addr.in6.sin6_family = AF_INET6;
lprintf(LOG_WARNING, "%04d <%s> !UNSUPPORTED protocol: %d", sock, user.alias, prot);
sockprintf(sock, sess, "522 Network protocol not supported, use (1)");
else { /* LPRT */
if (sscanf(p, "%u,%u", &h1, &h2) != 2) {
lprintf(LOG_ERR, "%04d <%s> !Unable to parse LPRT: %s", sock, user.alias, p);
sockprintf(sock, sess, "521 Address family not supported");
FIND_CHAR(p, ',');
if (*p)
FIND_CHAR(p, ',');
if (*p)
switch (h1) {
case 4: /* IPv4 */
if (h2 != 4) {
lprintf(LOG_ERR, "%04d <%s> !Unable to parse LPRT: %s", sock, user.alias, p);
sockprintf(sock, sess, "501 IPv4 Address is the wrong length");
for (h1 = 0; h1 < h2; h1++) {
((unsigned char *)(&data_addr.in.sin_addr))[h1] = atoi(p);
FIND_CHAR(p, ',');
if (*p)
lprintf(LOG_ERR, "%04d <%s> !Unable to parse LPRT %s", sock, user.alias, p);
sockprintf(sock, sess, "501 IPv4 Port is the wrong length");
FIND_CHAR(p, ',');
if (*p)
for (h1 = 0; h1 < 2; h1++) {
((unsigned char *)(&data_port))[1 - h1] = atoi(p);
FIND_CHAR(p, ',');
if (*p)
data_addr.in.sin_family = AF_INET;
case 6: /* IPv6 */
if (h2 != 16) {
lprintf(LOG_ERR, "%04d <%s> !Unable to parse LPRT: %s", sock, user.alias, p);
sockprintf(sock, sess, "501 IPv6 Address is the wrong length");
for (h1 = 0; h1 < h2; h1++) {
((unsigned char *)(&data_addr.in6.sin6_addr))[h1] = atoi(p);
FIND_CHAR(p, ',');
if (*p)
if (atoi(p) != 2) {
lprintf(LOG_ERR, "%04d <%s> !Unable to parse LPRT: %s", sock, user.alias, p);
sockprintf(sock, sess, "501 IPv6 Port is the wrong length");
FIND_CHAR(p, ',');
if (*p)
for (h1 = 0; h1 < 2; h1++) {
((unsigned char *)(&data_port))[1 - h1] = atoi(p);
FIND_CHAR(p, ',');
if (*p)
data_addr.in6.sin6_family = AF_INET6;
lprintf(LOG_ERR, "%04d <%s> !Unable to parse LPRT: %s", sock, user.alias, p);
sockprintf(sock, sess, "521 Address family not supported");
bool bounce_allowed = (startup->options & FTP_OPT_ALLOW_BOUNCE) && !(user.rest & FLAG('G'));
if (data_port < IPPORT_RESERVED
|| (strcmp(data_ip, host_ip) != 0 && !bounce_allowed)) {
lprintf(LOG_WARNING, "%04d <%s> !SUSPECTED BOUNCE ATTACK ATTEMPT to %s port %u"
, sock, user.alias
, data_ip, data_port);
ftp_hacklog("FTP BOUNCE", user.alias, cmd, host_name, &ftp.client_addr);
sockprintf(sock, sess, "504 Bad port number.");
continue; /* As recommended by RFC2577 */
}
sockprintf(sock, sess, "200 PORT Command successful.");
mode = "active";
if (stricmp(cmd, "PASV") == 0 || stricmp(cmd, "P@SW") == 0 /* Kludge required for SMC Barricade V1.2 */
|| stricmp(cmd, "EPSV") == 0 || strnicmp(cmd, "EPSV ", 5) == 0 || stricmp(cmd, "LPSV") == 0) {
if (pasv_sock != INVALID_SOCKET)
ftp_close_socket(&pasv_sock, &pasv_sess, __LINE__);
if ((pasv_sock = ftp_open_socket(pasv_addr.addr.sa_family, SOCK_STREAM)) == INVALID_SOCKET) {
lprintf(LOG_WARNING, "%04d <%s> !PASV ERROR %d opening socket", sock, user.alias, SOCKET_ERRNO);
sockprintf(sock, sess, "425 Error %d opening PASV data socket", SOCKET_ERRNO);
reuseaddr = FALSE;
if ((result = setsockopt(pasv_sock, SOL_SOCKET, SO_REUSEADDR, (char*)&reuseaddr, sizeof(reuseaddr))) != 0) {
lprintf(LOG_WARNING, "%04d <%s> !PASV ERROR %d disabling REUSEADDR socket option"
, sock, user.alias, SOCKET_ERRNO);
sockprintf(sock, sess, "425 Error %d disabling REUSEADDR socket option", SOCKET_ERRNO);
continue;
}
if (startup->options & FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG, "%04d <%s> PASV DATA socket %d opened", sock, user.alias, pasv_sock);
for (port = startup->pasv_port_low; port <= startup->pasv_port_high; port++) {
if (startup->options & FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG, "%04d <%s> PASV DATA trying to bind socket to port %u"
, sock, user.alias, port);
if ((result = bind(pasv_sock, &pasv_addr.addr, xp_sockaddr_len(&pasv_addr))) == 0)
break;
if (port == startup->pasv_port_high)
break;
if (result != 0) {
lprintf(LOG_ERR, "%04d <%s> !PASV ERROR %d (%d) binding socket to port %u"
, sock, user.alias, result, SOCKET_ERRNO, port);
sockprintf(sock, sess, "425 Error %d binding data socket", SOCKET_ERRNO);
ftp_close_socket(&pasv_sock, &pasv_sess, __LINE__);
if (startup->options & FTP_OPT_DEBUG_DATA)
lprintf(LOG_DEBUG, "%04d <%s> PASV DATA socket %d bound to port %u", sock, user.alias, pasv_sock, port);
addr_len = sizeof(addr);
if ((result = getsockname(pasv_sock, &addr.addr, &addr_len)) != 0) {
lprintf(LOG_CRIT, "%04d <%s> !PASV ERROR %d (%d) getting address/port of socket"
, sock, user.alias, result, SOCKET_ERRNO);
sockprintf(sock, sess, "425 Error %d getting address/port", SOCKET_ERRNO);
ftp_close_socket(&pasv_sock, &pasv_sess, __LINE__);
if ((result = listen(pasv_sock, 1)) != 0) {
lprintf(LOG_ERR, "%04d <%s> !PASV ERROR %d (%d) listening on port %u"
, sock, user.alias, result, SOCKET_ERRNO, port);
sockprintf(sock, sess, "425 Error %d listening on data socket", SOCKET_ERRNO);
ftp_close_socket(&pasv_sock, &pasv_sess, __LINE__);
port = inet_addrport(&addr);
if (strnicmp(cmd, "EPSV", 4) == 0)
sockprintf(sock, sess, "229 Entering Extended Passive Mode (|||%hu|)", port);
else if (stricmp(cmd, "LPSV") == 0) {
switch (addr.addr.sa_family) {
sockprintf(sock, sess, "228 Entering Long Passive Mode (4, 4, %d, %d, %d, %d, 2, %d, %d)"
, ((unsigned char *)&(addr.in.sin_addr))[0]
, ((unsigned char *)&(addr.in.sin_addr))[1]
, ((unsigned char *)&(addr.in.sin_addr))[2]
, ((unsigned char *)&(addr.in.sin_addr))[3]
, ((unsigned char *)&(addr.in.sin_port))[0]
, ((unsigned char *)&(addr.in.sin_port))[1]);
sockprintf(sock, sess, "228 Entering Long Passive Mode (6, 16, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, 2, %d, %d)"
, ((unsigned char *)&(addr.in6.sin6_addr))[0]
, ((unsigned char *)&(addr.in6.sin6_addr))[1]
, ((unsigned char *)&(addr.in6.sin6_addr))[2]
, ((unsigned char *)&(addr.in6.sin6_addr))[3]
, ((unsigned char *)&(addr.in6.sin6_addr))[4]
, ((unsigned char *)&(addr.in6.sin6_addr))[5]
, ((unsigned char *)&(addr.in6.sin6_addr))[6]
, ((unsigned char *)&(addr.in6.sin6_addr))[7]
, ((unsigned char *)&(addr.in6.sin6_addr))[8]
, ((unsigned char *)&(addr.in6.sin6_addr))[9]
, ((unsigned char *)&(addr.in6.sin6_addr))[10]
, ((unsigned char *)&(addr.in6.sin6_addr))[11]
, ((unsigned char *)&(addr.in6.sin6_addr))[12]
, ((unsigned char *)&(addr.in6.sin6_addr))[13]
, ((unsigned char *)&(addr.in6.sin6_addr))[14]
, ((unsigned char *)&(addr.in6.sin6_addr))[15]
, ((unsigned char *)&(addr.in6.sin6_port))[0]
, ((unsigned char *)&(addr.in6.sin6_port))[1]);
break;
}
}
else {
/* Choose IP address to use in passive response */
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
if (startup->options & FTP_OPT_LOOKUP_PASV_IP
&& (host = gethostbyname(server_host_name())) != NULL
&& host->h_addr_list[0] != NULL)
ip_addr = ntohl(*((in_addr_t*)host->h_addr_list[0]));
if (ip_addr == 0 && (ip_addr = startup->pasv_ip_addr.s_addr) == 0)
ip_addr = ntohl(pasv_addr.in.sin_addr.s_addr);
if (startup->options & FTP_OPT_DEBUG_DATA)
lprintf(LOG_INFO, "%04d <%s> PASV DATA IP address in response: %u.%u.%u.%u (subject to NAT)"
, sock
, user.alias
, (ip_addr >> 24) & 0xff
, (ip_addr >> 16) & 0xff
, (ip_addr >> 8) & 0xff
, ip_addr & 0xff
);
sockprintf(sock, sess, "227 Entering Passive Mode (%u,%u,%u,%u,%hu,%hu)"
, (ip_addr >> 24) & 0xff
, (ip_addr >> 16) & 0xff
, (ip_addr >> 8) & 0xff
, ip_addr & 0xff
, (ushort)((port >> 8) & 0xff)
, (ushort)(port & 0xff)
);
if (!strnicmp(cmd, "TYPE ", 5)) {
sockprintf(sock, sess, "200 All files sent in BINARY mode.");
if (!strnicmp(cmd, "ALLO", 4)) {
p = cmd + 5;
SKIP_WHITESPACE(p);
if (*p)
l = atol(p);
l = 0;
if (local_fsys)
avail = getfreediskspace(local_dir, 0);
avail = getfreediskspace(scfg.data_dir, 0); /* Change to temp_dir? */
if (l && l > avail)
sockprintf(sock, sess, "504 Only %" PRIu64 " bytes available.", avail);
sockprintf(sock, sess, "200 %" PRIu64 " bytes available.", avail);
if (!strnicmp(cmd, "REST", 4)) {
p = cmd + 4;
SKIP_WHITESPACE(p);
if (*p)
filepos = atol(p);
filepos = 0;
sockprintf(sock, sess, "350 Restarting at %ld. Send STORE or RETRIEVE to initiate transfer."
, filepos);
if (!strnicmp(cmd, "MODE ", 5)) {
p = cmd + 5;
SKIP_WHITESPACE(p);
if (toupper(*p) != 'S')
sockprintf(sock, sess, "504 Only STREAM mode supported.");
sockprintf(sock, sess, "200 STREAM mode.");
if (!strnicmp(cmd, "STRU ", 5)) {
p = cmd + 5;
SKIP_WHITESPACE(p);
if (toupper(*p) != 'F')
sockprintf(sock, sess, "504 Only FILE structure supported.");
sockprintf(sock, sess, "200 FILE structure.");
if (!stricmp(cmd, "SYST")) {
sockprintf(sock, sess, "215 UNIX Type: L8");
if (!stricmp(cmd, "ABOR")) {
if (!transfer_inprogress)
sockprintf(sock, sess, "226 No transfer in progress.");
lprintf(LOG_WARNING, "%04d <%s> aborting transfer"
, sock, user.alias);
transfer_aborted = TRUE;
YIELD(); /* give send thread time to abort */
sockprintf(sock, sess, "226 Transfer aborted.");
if (!strnicmp(cmd, "SMNT ", 5) && sysop && !(startup->options & FTP_OPT_NO_LOCAL_FSYS)) {
p = cmd + 5;
SKIP_WHITESPACE(p);
if (!stricmp(p, BBS_FSYS_DIR))
local_fsys = FALSE;
if (!direxist(p)) {
sockprintf(sock, sess, "550 Directory does not exist.");
lprintf(LOG_WARNING, "%04d <%s> !attempted to mount invalid directory: '%s'"
, sock, user.alias, p);
local_fsys = TRUE;
SAFECOPY(local_dir, p);
sockprintf(sock, sess, "250 %s file system mounted."
, local_fsys ? "Local" : "BBS");
lprintf(LOG_INFO, "%04d <%s> mounted %s file system"
, sock, user.alias, local_fsys ? "local" : "BBS");
continue;
}
/****************************/
/* Local File System Access */
/****************************/
if (sysop && local_fsys && !(startup->options & FTP_OPT_NO_LOCAL_FSYS)) {
if (local_dir[0]
&& local_dir[strlen(local_dir) - 1] != '\\'
&& local_dir[strlen(local_dir) - 1] != '/')
strcat(local_dir, "/");
if (!strnicmp(cmd, "MLS", 3)) {
if (cmd[3] == 'T' || cmd[3] == 'D') {
if (cmd[3] == 'D') {
if ((fp = fopen(ftp_tmpfname(fname, "lst", sock), "w+b")) == NULL) {
lprintf(LOG_ERR, "%04d <%s> !ERROR %d (%s) line %d opening %s"
, sock, user.alias, errno, safe_strerror(errno, error, sizeof error), __LINE__, fname);
sockprintf(sock, sess, "451 Insufficient system storage");
continue;
}
}
SKIP_WHITESPACE(p);
if (!local_dir[0])
strcpy(local_dir, "/");
SAFEPRINTF2(path, "%s%s", local_dir, filespec);
p = FULLPATH(NULL, path, 0);
strcpy(path, p);
free(p);
if (cmd[3] == 'T') {
if (access(path, 0) == -1) {
sockprintf(sock, sess, "550 No such path %s", path);
continue;
}
sockprintf(sock, sess, "250- Listing %s", path);
}
else {
if (access(path, 0) == -1) {
sockprintf(sock, sess, "550 No such path %s", path);
continue;
}
if (!isdir(path)) {
sockprintf(sock, sess, "501 Not a directory");
continue;
}
sockprintf(sock, sess, "150 Directory of %s", path);
backslash(path);
strcat(path, "*");
}
lprintf(LOG_INFO, "%04d <%s> MLSx listing: local %s in %s mode", sock, user.alias, path, mode);
now = time(NULL);
if (localtime_r(&now, &cur_tm) == NULL)
memset(&cur_tm, 0, sizeof(cur_tm));
if (cmd[3] == 'T') {
write_local_mlsx(NULL, sock, sess, mlsx_feats, path, TRUE);
sockprintf(sock, sess, "250 End");
}
else {
time_t start = time(NULL);
glob(path, GLOB_MARK, NULL, &g);
for (i = 0; i < (int)g.gl_pathc; i++) {
char fpath[MAX_PATH + 1];
SAFECOPY(fpath, g.gl_pathv[i]);
if (*lastchar(fpath) == '/')
*lastchar(fpath) = 0;
write_local_mlsx(fp, INVALID_SOCKET, -1, mlsx_feats, fpath, FALSE);
}
lprintf(LOG_INFO, "%04d <%s> %s-listing (%ld bytes) of local %s (%lu files) created in %" PRId64 " seconds"
, sock, user.alias, cmd, ftell(fp), path
, (ulong)g.gl_pathc, (int64_t)(time(NULL) - start));
globfree(&g);
fclose(fp);
filexfer(&data_addr, sock, sess, pasv_sock, pasv_sess, &data_sock, &data_sess, fname, 0L
, &transfer_inprogress, &transfer_aborted
, TRUE /* delfile */
, TRUE /* tmpfile */
, &lastactive, &user, &client, -1, FALSE, FALSE, FALSE, NULL, protection);
}
if (!strnicmp(cmd, "LIST", 4) || !strnicmp(cmd, "NLST", 4)) {
if (!strnicmp(cmd, "LIST", 4))
detail = TRUE;
if ((fp = fopen(ftp_tmpfname(fname, "lst", sock), "w+b")) == NULL) {
lprintf(LOG_ERR, "%04d <%s> !ERROR %d (%s) line %d opening %s"
, sock, user.alias, errno, safe_strerror(errno, error, sizeof error), __LINE__, fname);
sockprintf(sock, sess, "451 Insufficient system storage");
continue;
}
SKIP_WHITESPACE(p);
if (*p == '-') { /* -Letc */
FIND_WHITESPACE(p);
SKIP_WHITESPACE(p);
filespec = p;
if (*filespec == 0)
filespec = "*";
SAFEPRINTF2(path, "%s%s", local_dir, filespec);
lprintf(LOG_INFO, "%04d <%s> %slisting: local %s in %s mode"
, sock, user.alias, detail ? "detailed ":"", path, mode);
sockprintf(sock, sess, "150 Directory of %s%s", local_dir, filespec);
now = time(NULL);
if (localtime_r(&now, &cur_tm) == NULL)
memset(&cur_tm, 0, sizeof(cur_tm));
time_t start = time(NULL);
glob(path, GLOB_MARK, NULL, &g);
for (i = 0; i < (int)g.gl_pathc; i++) {
char fpath[MAX_PATH + 1];
SAFECOPY(fpath, g.gl_pathv[i]);
if (*lastchar(fpath) == '/')
if (stat(fpath, &st) != 0)
if (localtime_r(&st.st_mtime, &tm) == NULL)
memset(&tm, 0, sizeof(tm));
fprintf(fp, "%crw-r--r-- 1 %-8s local %9" PRId64 " %s %2d "
, *lastchar(g.gl_pathv[i]) == '/' ? 'd':'-'
, scfg.sys_id
, (int64_t)st.st_size
, ftp_mon[tm.tm_mon], tm.tm_mday);
if (tm.tm_year == cur_tm.tm_year)
fprintf(fp, "%02d:%02d %s\r\n"
, tm.tm_hour, tm.tm_min
, getfname(fpath));
fprintf(fp, "%5d %s\r\n"
, 1900 + tm.tm_year
, getfname(fpath));
fprintf(fp, "%s\r\n", getfname(fpath));
lprintf(LOG_INFO, "%04d <%s> %slisting (%ld bytes) of local %s (%lu files) created in %" PRId64 " seconds"
, sock, user.alias, detail ? "detailed ":"", ftell(fp), path
, (ulong)g.gl_pathc, (int64_t)(time(NULL) - start));

rswindell
committed
globfree(&g);
filexfer(&data_addr, sock, sess, pasv_sock, pasv_sess, &data_sock, &data_sess, fname, 0L
, &transfer_inprogress, &transfer_aborted
, TRUE /* delfile */
, TRUE /* tmpfile */
, &lastactive, &user, &client, -1, FALSE, FALSE, FALSE, NULL, protection);
continue;
} /* Local LIST/NLST */
if (!strnicmp(cmd, "CWD ", 4) || !strnicmp(cmd, "XCWD ", 5)) {
if (!strnicmp(cmd, "CWD ", 4))
p = cmd + 4;
SKIP_WHITESPACE(p);
tp = p;
if (*tp == '/' || *tp == '\\') /* /local: and /bbs: are valid */
if (!strnicmp(tp, BBS_FSYS_DIR, strlen(BBS_FSYS_DIR))) {
local_fsys = FALSE;
sockprintf(sock, sess, "250 CWD command successful (BBS file system mounted).");
lprintf(LOG_INFO, "%04d <%s> mounted BBS file system", sock, user.alias);
if (!strnicmp(tp, LOCAL_FSYS_DIR, strlen(LOCAL_FSYS_DIR))) {
tp += strlen(LOCAL_FSYS_DIR); /* already mounted */
p = tp;
if (p[1] == ':' || !strncmp(p, "\\\\", 2))
SAFECOPY(path, p);
else if (*p == '/' || *p == '\\') {
SAFEPRINTF2(path, "%s%s", root_dir(local_dir), p + 1);
p = FULLPATH(NULL, path, 0);
SAFECOPY(path, p);
free(p);
}
SAFEPRINTF2(fname, "%s%s", local_dir, p);
FULLPATH(path, fname, sizeof(path));
if (!direxist(path)) {
sockprintf(sock, sess, "550 Directory does not exist (%s).", path);
lprintf(LOG_WARNING, "%04d <%s> !attempted to change to an invalid directory: '%s'"
, sock, user.alias, path);
SAFECOPY(local_dir, path);
sockprintf(sock, sess, "250 CWD command successful (%s).", local_dir);
}
continue;
} /* Local CWD */
if (!stricmp(cmd, "CDUP") || !stricmp(cmd, "XCUP")) {
SAFEPRINTF(path, "%s..", local_dir);
if (FULLPATH(local_dir, path, sizeof(local_dir)) == NULL)
sockprintf(sock, sess, "550 Directory does not exist.");
sockprintf(sock, sess, "200 CDUP command successful.");
if (!stricmp(cmd, "PWD") || !stricmp(cmd, "XPWD")) {
if (strlen(local_dir) > 3)
local_dir[strlen(local_dir) - 1] = 0; /* truncate '/' */
sockprintf(sock, sess, "257 \"%s\" is current directory."
, local_dir);
continue;
} /* Local PWD */
if (!strnicmp(cmd, "MKD ", 4) || !strnicmp(cmd, "XMKD", 4)) {
p = cmd + 4;
SKIP_WHITESPACE(p);
if (*p == '/') /* absolute */
SAFEPRINTF2(fname, "%s%s", root_dir(local_dir), p + 1);
else /* relative */
SAFEPRINTF2(fname, "%s%s", local_dir, p);
if (MKDIR(fname) == 0) {
sockprintf(sock, sess, "257 \"%s\" directory created", fname);
lprintf(LOG_NOTICE, "%04d <%s> created directory: %s", sock, user.alias, fname);
sockprintf(sock, sess, "521 Error %d creating directory: %s", errno, fname);
lprintf(LOG_WARNING, "%04d <%s> !ERROR %d (%s) attempting to create directory: %s"
, sock, user.alias, errno, safe_strerror(errno, error, sizeof error), fname);
if (!strnicmp(cmd, "RMD ", 4) || !strnicmp(cmd, "XRMD", 4)) {
p = cmd + 4;
SKIP_WHITESPACE(p);
if (*p == '/') /* absolute */
SAFEPRINTF2(fname, "%s%s", root_dir(local_dir), p + 1);
else /* relative */
SAFEPRINTF2(fname, "%s%s", local_dir, p);
if (rmdir(fname) == 0) {
sockprintf(sock, sess, "250 \"%s\" directory removed", fname);
lprintf(LOG_NOTICE, "%04d <%s> removed directory: %s", sock, user.alias, fname);
sockprintf(sock, sess, "450 Error %d removing directory: %s", errno, fname);
lprintf(LOG_WARNING, "%04d <%s> !ERROR %d (%s) removing directory: %s"
, sock, user.alias, errno, safe_strerror(errno, error, sizeof error), fname);
if (!strnicmp(cmd, "RNFR ", 5)) {
p = cmd + 5;
SKIP_WHITESPACE(p);
if (*p == '/') /* absolute */
SAFEPRINTF2(ren_from, "%s%s", root_dir(local_dir), p + 1);
else /* relative */
SAFEPRINTF2(ren_from, "%s%s", local_dir, p);
if (!fexist(ren_from)) {
sockprintf(sock, sess, "550 File not found: %s", ren_from);
lprintf(LOG_WARNING, "%04d <%s> !ERROR renaming %s (not found)"
, sock, user.alias, ren_from);
sockprintf(sock, sess, "350 File exists, ready for destination name");
if (!strnicmp(cmd, "RNTO ", 5)) {
p = cmd + 5;
SKIP_WHITESPACE(p);
if (*p == '/') /* absolute */
SAFEPRINTF2(fname, "%s%s", root_dir(local_dir), p + 1);
else /* relative */
SAFEPRINTF2(fname, "%s%s", local_dir, p);
if (rename(ren_from, fname) == 0) {
sockprintf(sock, sess, "250 \"%s\" renamed to \"%s\"", ren_from, fname);
lprintf(LOG_NOTICE, "%04d <%s> renamed %s to %s", sock, user.alias, ren_from, fname);
sockprintf(sock, sess, "450 Error %d renaming file: %s", errno, ren_from);
lprintf(LOG_WARNING, "%04d <%s> !ERRROR %d (%s) renaming file: %s"
, sock, user.alias, errno, safe_strerror(errno, error, sizeof error), ren_from);
}
continue;
}
if (!strnicmp(cmd, "RETR ", 5) || !strnicmp(cmd, "SIZE ", 5)
|| !strnicmp(cmd, "MDTM ", 5) || !strnicmp(cmd, "DELE ", 5)) {
p = cmd + 5;
SKIP_WHITESPACE(p);
if (!strnicmp(p, LOCAL_FSYS_DIR, strlen(LOCAL_FSYS_DIR)))
p += strlen(LOCAL_FSYS_DIR); /* already mounted */
if (p[1] == ':') /* drive specified */
SAFECOPY(fname, p);
else if (*p == '/') /* absolute, current drive */
SAFEPRINTF2(fname, "%s%s", root_dir(local_dir), p + 1);
else /* relative */
SAFEPRINTF2(fname, "%s%s", local_dir, p);
if (!fexist(fname)) {
lprintf(LOG_WARNING, "%04d <%s> !File not found: %s", sock, user.alias, fname);
sockprintf(sock, sess, "550 File not found: %s", fname);
if (!strnicmp(cmd, "SIZE ", 5)) {
sockprintf(sock, sess, "213 %" PRIuOFF, flength(fname));
if (!strnicmp(cmd, "MDTM ", 5)) {
t = fdate(fname);
if (gmtime_r(&t, &tm) == NULL) /* specifically use GMT/UTC representation */
memset(&tm, 0, sizeof(tm));
sockprintf(sock, sess, "213 %u%02u%02u%02u%02u%02u"
, 1900 + tm.tm_year, tm.tm_mon + 1, tm.tm_mday
, tm.tm_hour, tm.tm_min, tm.tm_sec);
if (!strnicmp(cmd, "DELE ", 5)) {
if (ftp_remove(sock, __LINE__, fname, user.alias, LOG_ERR) == true) {
sockprintf(sock, sess, "250 \"%s\" removed successfully.", fname);
lprintf(LOG_NOTICE, "%04d <%s> deleted file: %s", sock, user.alias, fname);
sockprintf(sock, sess, "450 Error %d removing file: %s", errno, fname);
lprintf(LOG_WARNING, "%04d <%s> !ERROR %d (%s) deleting file: %s"
, sock, user.alias, errno, safe_strerror(errno, error, sizeof error), fname);
}
continue;
}
/* RETR */
lprintf(LOG_INFO, "%04d <%s> downloading: %s (%" PRIuOFF " bytes) in %s mode"
, sock, user.alias, fname, flength(fname)
, mode);
sockprintf(sock, sess, "150 Opening BINARY mode data connection for file transfer.");
filexfer(&data_addr, sock, sess, pasv_sock, pasv_sess, &data_sock, &data_sess, fname, filepos
, &transfer_inprogress, &transfer_aborted, FALSE, FALSE
, &lastactive, &user, &client, -1, FALSE, FALSE, FALSE, NULL, protection);
continue;
} /* Local RETR/SIZE/MDTM */
if (!strnicmp(cmd, "STOR ", 5) || !strnicmp(cmd, "APPE ", 5)) {
p = cmd + 5;
SKIP_WHITESPACE(p);
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
if (!strnicmp(p, LOCAL_FSYS_DIR, strlen(LOCAL_FSYS_DIR)))
p += strlen(LOCAL_FSYS_DIR); /* already mounted */
if (p[1] == ':') /* drive specified */
SAFECOPY(fname, p);
else if (*p == '/') /* absolute, current drive */
SAFEPRINTF2(fname, "%s%s", root_dir(local_dir), p + 1);
else /* relative */
SAFEPRINTF2(fname, "%s%s", local_dir, p);
lprintf(LOG_INFO, "%04d <%s> uploading: %s in %s mode", sock, user.alias, fname
, mode);
sockprintf(sock, sess, "150 Opening BINARY mode data connection for file transfer.");
filexfer(&data_addr, sock, sess, pasv_sock, pasv_sess, &data_sock, &data_sess, fname, filepos
, &transfer_inprogress, &transfer_aborted, FALSE, FALSE
, &lastactive
, &user
, &client
, -1 /* dir */
, TRUE /* uploading */
, FALSE /* credits */
, !strnicmp(cmd, "APPE", 4) ? TRUE : FALSE /* append */
, NULL /* desc */
, protection
);
filepos = 0;
continue;
} /* Local STOR */
}
if (!strnicmp(cmd, "MLS", 3)) {
if (cmd[3] == 'D' || cmd[3] == 'T') {
dir = curdir;
lib = curlib;
l = 0;
if (cmd[4] != 0)
lprintf(LOG_DEBUG, "%04d <%s> MLSx: %s", sock, user.alias, cmd);
/* path specified? */
if (*p == ' ')
p++;
if (parsepath(&p, &user, &client, &lib, &dir) == -1) {
sockprintf(sock, sess, "550 No such path");
continue;
}
if (strchr(p, '/')) {
sockprintf(sock, sess, "550 No such path");
continue;
}
if (cmd[3] == 'T') {
if (cmd[4])
strcpy(mls_path, cmd + 5);
else
strcpy(mls_path, p);
}
else {
if (*p) {
sockprintf(sock, sess, "501 Not a directory");
continue;
}
strcpy(mls_path, p);
}
fp = NULL;
if (cmd[3] == 'D') {
if ((fp = fopen(ftp_tmpfname(fname, "lst", sock), "w+b")) == NULL) {
lprintf(LOG_ERR, "%04d <%s> !ERROR %d (%s) line %d opening %s"
, sock, user.alias, errno, safe_strerror(errno, error, sizeof error), __LINE__, fname);
sockprintf(sock, sess, "451 Insufficient system storage");
continue;
}
sockprintf(sock, sess, "150 Opening ASCII mode data connection for MLSD.");
now = time(NULL);
if (localtime_r(&now, &cur_tm) == NULL)
memset(&cur_tm, 0, sizeof(cur_tm));
/* ASCII Index File */
if (startup->options & FTP_OPT_INDEX_FILE && startup->index_file_name[0]
&& (cmd[3] == 'D' || strcmp(startup->index_file_name, mls_fname) == 0)) {
if (cmd[3] == 'T')
sockprintf(sock, sess, "250- Listing %s", startup->index_file_name);
get_owner_name(NULL, str, sizeof str);
send_mlsx_entry(fp, sock, sess, mlsx_feats, "file", "r", UINT64_MAX, 0, str, NULL, 0, cmd[3] == 'T' ? mls_path : startup->index_file_name);
l++;
}
if (lib < 0) { /* Root dir */
if (cmd[3] == 'T' && !*mls_fname) {
sockprintf(sock, sess, "250- Listing root");
get_owner_name(NULL, str, sizeof str);
strcpy(aliaspath, "/");
send_mlsx_entry(fp, sock, sess, mlsx_feats, "dir", (startup->options & FTP_OPT_ALLOW_QWK) ? "elc" : "el", UINT64_MAX, 0, str, NULL, 0, aliaspath);
l++;
/* RFC 3659: "if no object is named ... MLST to send a one-line response, describing the current directory itself" */
send_mlsx_entry(fp, sock, sess, mlsx_feats, "cdir", (startup->options & FTP_OPT_ALLOW_QWK) ? "elc" : "el", UINT64_MAX, 0, str, NULL, 0, "/");
lprintf(LOG_INFO, "%04d <%s> %s listing: root in %s mode", sock, user.alias, cmd, mode);
if (startup->options & FTP_OPT_ALLOW_QWK) {
SAFEPRINTF(str, "%s.qwk", scfg.sys_id);
if (cmd[3] == 'D' || strcmp(str, mls_fname) == 0) {
if (cmd[3] == 'T')
sockprintf(sock, sess, "250- Listing %s", str);
get_owner_name(NULL, owner, sizeof owner);
send_mlsx_entry(fp, sock, sess, mlsx_feats, "file", "r", flength(qwkfile), fdate(qwkfile), owner, NULL, 0, cmd[3] == 'T' ? mls_path : str);
l++;
}
}
for (int i = 0; i < scfg.total_dirs; ++i) {
if (scfg.dir[i]->vshortcut[0] == '\0')
continue;
if (!user_can_access_dir(&scfg, i, &user, &client))
continue;
SAFEPRINTF2(aliaspath, "/%s/%s", scfg.lib[scfg.dir[i]->lib]->vdir, scfg.dir[i]->vdir);
get_unique(aliaspath, uniq);
if (cmd[3] == 'D')
send_mlsx_entry(fp, sock, sess, mlsx_feats, "dir", "el", UINT64_MAX, /* modify_date: */ 0, /* owner: */ scfg.lib[scfg.dir[i]->lib]->vdir, uniq, 0, scfg.dir[i]->vshortcut);
else {
if (strcmp(mls_fname, scfg.dir[i]->vshortcut) != 0)
continue;
send_mlsx_entry(fp, sock, sess, mlsx_feats, "dir", "el", UINT64_MAX, /* modify_date: */ 0, /* owner: */ scfg.lib[scfg.dir[i]->lib]->vdir, uniq, 0, aliaspath[0] ? aliaspath : mls_path);
}
}
sprintf(aliasfile, "%sftpalias.cfg", scfg.ctrl_dir);
if ((alias_fp = fopen(aliasfile, "r")) != NULL) {
while (!feof(alias_fp)) {
if (!fgets(aliasline, sizeof(aliasline), alias_fp))
p = aliasline; /* alias pointer */
if (*p == ';') /* comment */
tp = p; /* terminator pointer */
np = tp + 1; /* filename pointer */
tp = np; /* terminator pointer */
dp = tp + 1; /* description pointer */
SKIP_WHITESPACE(dp);
truncsp(dp);
if (stricmp(dp, BBS_HIDDEN_ALIAS) == 0)
aliaspath[0] = 0;
if (!strnicmp(np, BBS_VIRTUAL_PATH, strlen(BBS_VIRTUAL_PATH))) {
if ((dir = getdir_from_vpath(&scfg, np + strlen(BBS_VIRTUAL_PATH), &user, &client, true)) < 0) {
lprintf(LOG_WARNING, "%04d <%s> !Invalid virtual path:%s", sock, user.alias, np);
continue; /* No access or invalid virtual path */
}
tp = strrchr(np, '/');
if (tp == NULL)
if (*tp) {
SAFEPRINTF2(aliasfile, "%s%s", scfg.dir[dir]->path, tp);
np = aliasfile;
SAFEPRINTF3(aliaspath, "/%s/%s/%s", scfg.lib[scfg.dir[dir]->lib]->vdir, scfg.dir[dir]->vdir, tp);
alias_dir = TRUE;
SAFEPRINTF2(aliaspath, "/%s/%s", scfg.lib[scfg.dir[dir]->lib]->vdir, scfg.dir[dir]->vdir);
if (!alias_dir && !fexist(np)) {
lprintf(LOG_WARNING, "%04d <%s> !Missing aliased file: %s", sock, user.alias, np);
continue;
}
get_unique(aliaspath, uniq);
if (cmd[3] == 'D') {
if (alias_dir == TRUE)
send_mlsx_entry(fp, sock, sess, mlsx_feats, "dir", "el", UINT64_MAX, /* modify_date: */ 0, /* owner: */ scfg.lib[scfg.dir[dir]->lib]->vdir, uniq, 0, p);
else
send_mlsx_entry(fp, sock, sess, mlsx_feats, "file", "r", (uint64_t)flength(np), fdate(np), get_owner_name(NULL, owner, sizeof owner), uniq, 0, p);
if (strcmp(mls_fname, p) != 0)
if (alias_dir == TRUE)
send_mlsx_entry(fp, sock, sess, mlsx_feats, "dir", "el", UINT64_MAX, /* modify_date: */ 0, /* owner: */ scfg.lib[scfg.dir[dir]->lib]->vdir, uniq, 0, aliaspath[0] ? aliaspath : mls_path);
else
send_mlsx_entry(fp, sock, sess, mlsx_feats, "file", "r", (uint64_t)flength(np), fdate(np), get_owner_name(NULL, owner, sizeof owner), uniq, 0, mls_path);
}
}
}
/* Library folders */
for (i = 0; i < scfg.total_libs; i++) {
if (!chk_ar(&scfg, scfg.lib[i]->ar, &user, &client))
continue;
if (cmd[3] != 'D' && strcmp(scfg.lib[i]->vdir, mls_fname) != 0)
continue;
if (cmd[3] == 'T')
sockprintf(sock, sess, "250- Listing %s", scfg.lib[i]->vdir);
get_libperm(scfg.lib[i], &user, &client, permstr);
get_owner_name(NULL, str, sizeof str);
send_mlsx_entry(fp, sock, sess, mlsx_feats, "dir", permstr, UINT64_MAX, 0, str, NULL, 0, cmd[3] == 'T' ? mls_path : scfg.lib[i]->vdir);
l++;
}
} else if (dir < 0) {
if (cmd[3] == 'T' && !*mls_fname) {
sockprintf(sock, sess, "250- Listing %s", scfg.lib[lib]->vdir);
get_owner_name(NULL, str, sizeof str);
SAFEPRINTF(aliaspath, "/%s", scfg.lib[lib]->vdir);
send_mlsx_entry(fp, sock, sess, mlsx_feats, "dir", "el", UINT64_MAX, 0, str, NULL, 0, aliaspath);
l++;
}
get_owner_name(NULL, str, sizeof str);
send_mlsx_entry(fp, sock, sess, mlsx_feats, "pdir", (startup->options & FTP_OPT_ALLOW_QWK) ? "elc" : "el", UINT64_MAX, 0, str, NULL, 0, "/");
SAFEPRINTF(aliaspath, "/%s", scfg.lib[lib]->vdir);
send_mlsx_entry(fp, sock, sess, mlsx_feats, "cdir", (startup->options & FTP_OPT_ALLOW_QWK) ? "elc" : "el", UINT64_MAX, 0, str, NULL, 0, aliaspath);
lprintf(LOG_INFO, "%04d <%s> %s listing: %s library in %s mode"
, sock, user.alias, cmd, scfg.lib[lib]->vdir, mode);
for (i = 0; i < scfg.total_dirs; i++) {
if (scfg.dir[i]->lib != lib)
continue;
if (i != (int)scfg.sysop_dir && i != (int)scfg.upload_dir
&& !chk_ar(&scfg, scfg.dir[i]->ar, &user, &client))
continue;
if (cmd[3] != 'D' && strcmp(scfg.dir[i]->vdir, mls_fname) != 0)
continue;
if (cmd[3] == 'T')
sockprintf(sock, sess, "250- Listing %s", scfg.dir[i]->vdir);
get_dirperm(scfg.lib[lib], scfg.dir[i], &user, &client, permstr);
get_owner_name(NULL, str, sizeof str);
SAFEPRINTF2(aliaspath, "/%s/%s", scfg.lib[lib]->vdir, scfg.dir[i]->vdir);
get_unique(aliaspath, uniq);
send_mlsx_entry(fp, sock, sess, mlsx_feats, "dir", permstr, UINT64_MAX, 0, str, uniq, 0, cmd[3] == 'T' ? mls_path : scfg.dir[i]->vdir);
l++;
}
} else if (chk_ar(&scfg, scfg.dir[dir]->ar, &user, &client)) {
lprintf(LOG_INFO, "%04d <%s> %s listing: /%s/%s directory in %s mode"
, sock, user.alias, cmd, scfg.lib[lib]->vdir, scfg.dir[dir]->vdir, mode);
if (cmd[3] == 'T' && !*mls_fname) {
sockprintf(sock, sess, "250- Listing %s/%s", scfg.lib[lib]->vdir, scfg.dir[dir]->vdir);
get_owner_name(NULL, str, sizeof str);
SAFEPRINTF2(aliaspath, "/%s/%s", scfg.lib[lib]->vdir, scfg.dir[dir]->vdir);
get_unique(aliaspath, uniq);
send_mlsx_entry(fp, sock, sess, mlsx_feats, "dir", (startup->options & FTP_OPT_ALLOW_QWK) ? "elc" : "el", UINT64_MAX, 0, str, uniq, 0, aliaspath);
l++;
}
if (cmd[3] == 'D') {
get_libperm(scfg.lib[lib], &user, &client, permstr);
get_owner_name(NULL, str, sizeof str);
SAFEPRINTF(aliaspath, "/%s", scfg.lib[lib]->vdir);
send_mlsx_entry(fp, sock, sess, mlsx_feats, "pdir", permstr, UINT64_MAX, 0, str, NULL, 0, aliaspath);
SAFEPRINTF2(aliaspath, "/%s/%s", scfg.lib[lib]->vdir, scfg.dir[dir]->vdir);
get_unique(aliaspath, uniq);
send_mlsx_entry(fp, sock, sess, mlsx_feats, "cdir", permstr, UINT64_MAX, 0, str, NULL, 0, aliaspath);
smb_t smb;
if ((result = smb_open_dir(&scfg, &smb, dir)) != SMB_SUCCESS) {
lprintf(LOG_ERR, "ERROR %d (%s) opening %s", result, smb.last_error, smb.file);
continue;
}
time_t start = time(NULL);
size_t file_count = 0;
file_t* file_list = loadfiles(&smb
, /* filespec */ NULL, /* time: */ 0, file_detail_normal, scfg.dir[dir]->sort, &file_count);
for (size_t i = 0; i < file_count; i++) {
file_t* f = &file_list[i];
if (cmd[3] != 'D' && strcmp(f->name, mls_fname) != 0)
continue;
if (cmd[3] == 'T')
sockprintf(sock, sess, "250- Listing %s", p);
get_fileperm(scfg.lib[lib], scfg.dir[dir], &user, &client, f, permstr);
get_owner_name(f, str, sizeof str);
SAFEPRINTF3(aliaspath, "/%s/%s/%s", scfg.lib[lib]->vdir, scfg.dir[dir]->vdir, f->name);
get_unique(aliaspath, uniq);
f->size = f->cost;
f->time = f->hdr.when_imported.time;
if (scfg.dir[dir]->misc & DIR_FCHK) {
struct stat st;
if (stat(getfilepath(&scfg, f, path), &st) != 0)
continue;
f->size = st.st_size;
f->time = st.st_mtime;
f->hdr.when_imported.time = (uint32_t)st.st_ctime;
}
send_mlsx_entry(fp, sock, sess, mlsx_feats, "file", permstr, f->size, f->time, str, uniq, f->hdr.when_imported.time, cmd[3] == 'T' ? mls_path : f->name);
l++;
}
lprintf(LOG_INFO, "%04d <%s> %s listing (%ld bytes) of /%s/%s (%lu files) created in %" PRId64 " seconds"
, sock, user.alias, cmd, ftell(fp), scfg.lib[lib]->vdir, scfg.dir[dir]->vdir
, (ulong)file_count, (int64_t)(time(NULL) - start));
freefiles(file_list, file_count);
smb_close(&smb);
lprintf(LOG_INFO, "%04d <%s> %s listing: /%s/%s directory in %s mode (empty - no access)"
, sock, user.alias, cmd, scfg.lib[lib]->vdir, scfg.dir[dir]->vdir, mode);
if (cmd[3] == 'D') {
fclose(fp);
filexfer(&data_addr, sock, sess, pasv_sock, pasv_sess, &data_sock, &data_sess, fname, 0L
, &transfer_inprogress, &transfer_aborted
, TRUE /* delfile */
, TRUE /* tmpfile */
, &lastactive, &user, &client, dir, FALSE, FALSE, FALSE, NULL, protection);
}
else {
if (l == 0)
sockprintf(sock, sess, "550 No such path");
else
sockprintf(sock, sess, "250 End");
}
continue;
}
}
if (!strnicmp(cmd, "LIST", 4) || !strnicmp(cmd, "NLST", 4)) {
dir = curdir;
lib = curlib;
if (cmd[4] != 0)
lprintf(LOG_DEBUG, "%04d <%s> LIST/NLST: %s", sock, user.alias, cmd);
SKIP_WHITESPACE(p);
if (*p == '-') { /* -Letc */
FIND_WHITESPACE(p);
SKIP_WHITESPACE(p);
if ((fp = fopen(ftp_tmpfname(fname, "lst", sock), "w+b")) == NULL) {
lprintf(LOG_ERR, "%04d <%s> !ERROR %d (%s) line %d opening %s"
, sock, user.alias, errno, safe_strerror(errno, error, sizeof error), __LINE__, fname);
sockprintf(sock, sess, "451 Insufficient system storage");
sockprintf(sock, sess, "150 Opening ASCII mode data connection for /bin/ls.");
if (parsepath(&p, &user, &client, &lib, &dir) == -1) {
/* Empty list */
fclose(fp);
filexfer(&data_addr, sock, sess, pasv_sock, pasv_sess, &data_sock, &data_sess, fname, 0L
, &transfer_inprogress, &transfer_aborted
, TRUE /* delfile */
, TRUE /* tmpfile */
, &lastactive, &user, &client, dir, FALSE, FALSE, FALSE, NULL, protection);
filespec = p;
if (*filespec == 0)
filespec = "*";
if (!strnicmp(cmd, "LIST", 4))
detail = TRUE;
detail = FALSE;
now = time(NULL);
if (localtime_r(&now, &cur_tm) == NULL)
memset(&cur_tm, 0, sizeof(cur_tm));
if (startup->options & FTP_OPT_INDEX_FILE && startup->index_file_name[0]
&& wildmatchi(startup->index_file_name, filespec, FALSE)) {
if (detail)
fprintf(fp, "-r--r--r-- 1 %-*s %-8s %9ld %s %2d %02d:%02d %s\r\n"
, NAME_LEN
, scfg.sys_id
, lib < 0 ? scfg.sys_id : dir < 0
? scfg.lib[lib]->vdir : scfg.dir[dir]->vdir
, 512L
, ftp_mon[cur_tm.tm_mon], cur_tm.tm_mday, cur_tm.tm_hour, cur_tm.tm_min
, startup->index_file_name);
fprintf(fp, "%s\r\n", startup->index_file_name);
if (lib < 0) { /* Root dir */
lprintf(LOG_INFO, "%04d <%s> %slisting: root in %s mode", sock, user.alias, detail ? "detailed ":"", mode);
if (startup->options & FTP_OPT_ALLOW_QWK) {
SAFEPRINTF(str, "%s.qwk", scfg.sys_id);
if (wildmatchi(str, filespec, FALSE)) {
if (detail) {
if (fexistcase(qwkfile)) {
t = fdate(qwkfile);
l = (ulong)flength(qwkfile);
} else {
t = time(NULL);
l = 10240;
};
if (localtime_r(&t, &tm) == NULL)
memset(&tm, 0, sizeof(tm));
fprintf(fp, "-r--r--r-- 1 %-*s %-8s %9ld %s %2d %02d:%02d %s\r\n"
, NAME_LEN
, scfg.sys_id
, scfg.sys_id
, l
, ftp_mon[tm.tm_mon], tm.tm_mday, tm.tm_hour, tm.tm_min
, str);
} else
fprintf(fp, "%s\r\n", str);
for (int i = 0; i < scfg.total_dirs; ++i) {
if (scfg.dir[i]->vshortcut[0] == '\0')
continue;
if (!wildmatchi(scfg.dir[i]->vshortcut, filespec, FALSE))
continue;
if (!user_can_access_dir(&scfg, i, &user, &client))
continue;
if (detail) {
fprintf(fp, "drwxrwxrwx 1 %-*s %-8s %9ld %s %2d %02d:%02d %s\r\n"
, NAME_LEN
, scfg.sys_id
, scfg.lib[scfg.dir[i]->lib]->vdir
, 512L
, ftp_mon[cur_tm.tm_mon], cur_tm.tm_mday, cur_tm.tm_hour, cur_tm.tm_min
, scfg.dir[i]->vshortcut);
} else
fprintf(fp, "%s\r\n", scfg.dir[i]->vshortcut);
}
sprintf(aliasfile, "%sftpalias.cfg", scfg.ctrl_dir);
if ((alias_fp = fopen(aliasfile, "r")) != NULL) {
while (!feof(alias_fp)) {
if (!fgets(aliasline, sizeof(aliasline), alias_fp))
p = aliasline; /* alias pointer */
SKIP_WHITESPACE(p);
if (*p == ';') /* comment */
tp = p; /* terminator pointer */
FIND_WHITESPACE(tp);
np = tp + 1; /* filename pointer */
SKIP_WHITESPACE(np);
tp = np; /* terminator pointer */
FIND_WHITESPACE(tp);
dp = tp + 1; /* description pointer */
SKIP_WHITESPACE(dp);
truncsp(dp);
if (stricmp(dp, BBS_HIDDEN_ALIAS) == 0)
continue;
if (!wildmatchi(p, filespec, FALSE))
continue;
if (!strnicmp(np, BBS_VIRTUAL_PATH, strlen(BBS_VIRTUAL_PATH))) {
if ((dir = getdir_from_vpath(&scfg, np + strlen(BBS_VIRTUAL_PATH), &user, &client, true)) < 0) {
lprintf(LOG_WARNING, "%04d <%s> !Invalid virtual path: %s", sock, user.alias, np);
continue; /* No access or invalid virtual path */
tp = strrchr(np, '/');
if (tp == NULL)
if (*tp) {
SAFEPRINTF2(aliasfile, "%s%s", scfg.dir[dir]->path, tp);
np = aliasfile;
if (!alias_dir && !fexist(np)) {
lprintf(LOG_WARNING, "%04d <%s> !Missing aliased file: %s", sock, user.alias, np);
if (alias_dir == TRUE) {
fprintf(fp, "drwxrwxrwx 1 %-*s %-8s %9ld %s %2d %02d:%02d %s\r\n"
, NAME_LEN
, scfg.sys_id
, scfg.lib[scfg.dir[dir]->lib]->vdir
, 512L
, ftp_mon[cur_tm.tm_mon], cur_tm.tm_mday, cur_tm.tm_hour, cur_tm.tm_min
, p);
t = fdate(np);
if (localtime_r(&t, &tm) == NULL)
memset(&tm, 0, sizeof(tm));
fprintf(fp, "-r--r--r-- 1 %-*s %-8s %9" PRIdOFF " %s %2d %02d:%02d %s\r\n"
, NAME_LEN
, scfg.sys_id
, scfg.sys_id
, flength(np)
, ftp_mon[tm.tm_mon], tm.tm_mday, tm.tm_hour, tm.tm_min
, p);
fprintf(fp, "%s\r\n", p);
}
fclose(alias_fp);
}
/* Library folders */
for (i = 0; i < scfg.total_libs; i++) {
if (!chk_ar(&scfg, scfg.lib[i]->ar, &user, &client))
if (!wildmatchi(scfg.lib[i]->vdir, filespec, FALSE))
continue;
if (detail)
fprintf(fp, "dr-xr-xr-x 1 %-*s %-8s %9ld %s %2d %02d:%02d %s\r\n"
, NAME_LEN
, scfg.sys_id
, scfg.sys_id
, 512L
, ftp_mon[cur_tm.tm_mon], cur_tm.tm_mday, cur_tm.tm_hour, cur_tm.tm_min
, scfg.lib[i]->vdir);
fprintf(fp, "%s\r\n", scfg.lib[i]->vdir);
} else if (dir < 0) {
lprintf(LOG_INFO, "%04d <%s> %slisting: %s library in %s mode"
, sock, user.alias, detail ? "detailed ":"", scfg.lib[lib]->vdir, mode);
for (i = 0; i < scfg.total_dirs; i++) {
if (scfg.dir[i]->lib != lib)
if (i != (int)scfg.sysop_dir && i != (int)scfg.upload_dir
&& !chk_ar(&scfg, scfg.dir[i]->ar, &user, &client))
if (!wildmatchi(scfg.dir[i]->vdir, filespec, FALSE))
continue;
if (detail)
fprintf(fp, "drwxrwxrwx 1 %-*s %-8s %9ld %s %2d %02d:%02d %s\r\n"
, NAME_LEN
, scfg.sys_id
, scfg.lib[lib]->vdir
, 512L
, ftp_mon[cur_tm.tm_mon], cur_tm.tm_mday, cur_tm.tm_hour, cur_tm.tm_min
, scfg.dir[i]->vdir);
fprintf(fp, "%s\r\n", scfg.dir[i]->vdir);
} else if (chk_ar(&scfg, scfg.dir[dir]->ar, &user, &client)) {
lprintf(LOG_INFO, "%04d <%s> %slisting: /%s/%s directory in %s mode"
, sock, user.alias, detail ? "detailed ":""
, scfg.lib[lib]->vdir, scfg.dir[dir]->vdir, mode);
smb_t smb;
if ((result = smb_open_dir(&scfg, &smb, dir)) != SMB_SUCCESS) {
lprintf(LOG_ERR, "ERROR %d (%s) opening %s", result, smb.last_error, smb.file);
continue;
}
time_t start = time(NULL);
size_t file_count = 0;
Loading
Loading full blame...