Newer
Older
#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;
}
p = strrchr(p, '/');
if (p != NULL) p++;
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)