ftpsrvr.c 160 KB
Newer Older
1 2 3 4 5 6
/* 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)		*
 *																			*
7
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
8 9 10 11 12 13 14 15 16 17 18 19 20 21
 *																			*
 * 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.	*
 ****************************************************************************/

rswindell's avatar
rswindell committed
22
/* ANSI C Library headers */
rswindell's avatar
rswindell committed
23
#include <stdio.h>
24 25 26 27 28 29
#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 */
30 31
#include <sys/types.h>
#include <sys/stat.h>
rswindell's avatar
rswindell committed
32 33

/* Synchronet-specific headers */
34
#undef SBBS	/* this shouldn't be defined unless building sbbs.dll/libsbbs.so */
35
#include "sbbs.h"
36
#include "text.h"			/* TOTAL_TEXT */
rswindell's avatar
rswindell committed
37
#include "ftpsrvr.h"
38
#include "telnet.h"
deuce's avatar
deuce committed
39
#include "multisock.h"
deuce's avatar
deuce committed
40 41
#include "ssl.h"
#include "cryptlib.h"
42
#include "xpprintf.h"		// vasprintf
43
#include "md5.h"
44
#include "ver.h"
45 46 47

/* Constants */

48
#define FTP_SERVER				"Synchronet FTP Server"
49 50

#define STATUS_WFC				"Listening"
51
#define ANONYMOUS				"anonymous"
52 53 54 55

#define BBS_VIRTUAL_PATH		"bbs:/""/"	/* this is actually bbs:<slash><slash> */
#define LOCAL_FSYS_DIR			"local:"
#define BBS_FSYS_DIR			"bbs:"
56
#define BBS_HIDDEN_ALIAS		"hidden"
57

58
#define TIMEOUT_THREAD_WAIT		60		/* Seconds */
59

60 61
#define TIMEOUT_SOCKET_LISTEN	30		/* Seconds */

62 63 64 65 66 67
#define XFER_REPORT_INTERVAL	60		/* Seconds */

#define INDEX_FNAME_LEN			15

#define	NAME_LEN				15		/* User name length for listings */

deuce's avatar
deuce committed
68 69 70 71 72
#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)
73
#define MLSX_UNIQUE	(1<<5)
deuce's avatar
deuce committed
74
#define MLSX_CREATE	(1<<6)
deuce's avatar
deuce committed
75

76
static ftp_startup_t*	startup=NULL;
77
static scfg_t	scfg;
deuce's avatar
deuce committed
78
static struct xpms_set *ftp_set = NULL;
79 80
static protected_uint32_t active_clients;
static protected_uint32_t thread_count;
81 82 83
static volatile time_t	uptime=0;
static volatile ulong	served=0;
static volatile BOOL	terminate_server=FALSE;
84
static char 	*text[TOTAL_TEXT];
85 86
static str_list_t recycle_semfiles;
static str_list_t shutdown_semfiles;
87
static link_list_t current_connections;
88

89
#ifdef SOCKET_DEBUG
90
	static BYTE 	socket_debug[0x10000]={0};
91

92 93 94 95 96 97 98 99
	#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 */
100
#endif
101

102
char* genvpath(int lib, int dir, char* str);
103 104 105

typedef struct {
	SOCKET			socket;
deuce's avatar
deuce committed
106 107
	union xp_sockaddr	client_addr;
	socklen_t		client_addr_len;
108 109
} ftp_t;

110

111
static const char *ftp_mon[]={"Jan","Feb","Mar","Apr","May","Jun"
112 113 114 115 116 117 118 119 120 121
            ,"Jul","Aug","Sep","Oct","Nov","Dec"};

BOOL direxist(char *dir)
{
	if(access(dir,0)==0)
		return(TRUE);
	else
		return(FALSE);
}

rswindell's avatar
rswindell committed
122
BOOL dir_op(scfg_t* cfg, user_t* user, client_t* client, uint dirnum)
123
{
124
	return(user->level>=SYSOP_LEVEL
125
		|| (cfg->dir[dirnum]->op_ar!=NULL && cfg->dir[dirnum]->op_ar[0] && chk_ar(cfg,cfg->dir[dirnum]->op_ar,user,client)));
126 127
}

128 129 130
#if defined(__GNUC__)	// Catch printf-format errors with lprintf
static int lprintf(int level, const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
#endif
131
static int lprintf(int level, const char *fmt, ...)
132 133 134 135 136
{
	va_list argptr;
	char sbuf[1024];

    va_start(argptr,fmt);
137 138
    vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
	sbuf[sizeof(sbuf)-1]=0;
139 140
    va_end(argptr);

141
	if(level <= LOG_ERR) {
142
		char errmsg[sizeof(sbuf)+16];
143
		SAFEPRINTF(errmsg, "ftp  %s", sbuf);
144
		errorlog(&scfg, level, startup==NULL ? NULL:startup->host_name, errmsg);
145
		if(startup!=NULL && startup->errormsg!=NULL)
146
			startup->errormsg(startup->cbdata,level,errmsg);
147
	}
148 149 150 151 152 153 154 155 156 157

    if(startup==NULL || startup->lputs==NULL || level > startup->log_level)
		return(0);

#if defined(_WIN32)
	if(IsBadCodePtr((FARPROC)startup->lputs))
		return(0);
#endif

    return startup->lputs(startup->cbdata,level,sbuf);
158 159 160 161 162
}

#ifdef _WINSOCKAPI_

static WSADATA WSAData;
163 164
#define SOCKLIB_DESC WSAData.szDescription

165
static BOOL WSAInitialized=FALSE;
166 167 168 169 170 171

static BOOL winsock_startup(void)
{
	int		status;             /* Status Code */

    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
172
		lprintf(LOG_DEBUG,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
173
		WSAInitialized=TRUE;
174 175 176
		return (TRUE);
	}

177
    lprintf(LOG_CRIT,"!WinSock startup ERROR %d", status);
178 179 180
	return (FALSE);
}

181
#else /* No WINSOCK */
182

183
#define winsock_startup()	(TRUE)
184
#define SOCKLIB_DESC		NULL
185 186 187

#endif

188 189 190 191 192
static char* server_host_name(void)
{
	return startup->host_name[0] ? startup->host_name : scfg.sys_inetaddr;
}

193 194 195
static void status(char* str)
{
	if(startup!=NULL && startup->status!=NULL)
196
	    startup->status(startup->cbdata,str);
197 198 199 200 201
}

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
202
		startup->clients(startup->cbdata,protected_uint32_value(active_clients));
203 204
}

205
static void client_on(SOCKET sock, client_t* client, BOOL update)
206
{
207 208
	if(!update)
		listAddNodeData(&current_connections, client->addr, strlen(client->addr) + 1, sock, LAST_NODE);
209
	if(startup!=NULL && startup->client_on!=NULL)
210
		startup->client_on(startup->cbdata,TRUE,sock,client,update);
211 212 213 214
}

static void client_off(SOCKET sock)
{
215
	listRemoveTaggedNode(&current_connections, sock, /* free_data */TRUE);
216
	if(startup!=NULL && startup->client_on!=NULL)
217
		startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
218 219
}

220
static void thread_up(BOOL setuid)
221 222
{
	if(startup!=NULL && startup->thread_up!=NULL)
223
		startup->thread_up(startup->cbdata,TRUE, setuid);
224 225
}

226
static int32_t thread_down(void)
227
{
228
	int32_t count = protected_uint32_adjust(&thread_count,-1);
229
	if(startup!=NULL && startup->thread_up!=NULL)
230
		startup->thread_up(startup->cbdata,FALSE, FALSE);
231
	return count;
232 233
}

deuce's avatar
deuce committed
234
static void ftp_open_socket_cb(SOCKET sock, void *cbdata)
235
{
236
	char	error[256];
237

deuce's avatar
deuce committed
238
	if(startup!=NULL && startup->socket_open!=NULL)
239
		startup->socket_open(startup->cbdata,TRUE);
deuce's avatar
deuce committed
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
	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)
{
	SOCKET	sock;

	sock=socket(domain, type, IPPROTO_IP);
	if(sock != INVALID_SOCKET)
		ftp_open_socket_cb(sock, NULL);
257 258 259 260 261 262
	return(sock);
}

#ifdef __BORLANDC__
#pragma argsused
#endif
deuce's avatar
deuce committed
263
static int ftp_close_socket(SOCKET* sock, CRYPT_SESSION *sess, int line)
264 265 266
{
	int		result;

deuce's avatar
deuce committed
267 268 269 270 271
	if (*sess != -1) {
		cryptDestroySession(*sess);
		*sess = -1;
	}

272
	if((*sock)==INVALID_SOCKET) {
273
		lprintf(LOG_WARNING,"0000 !INVALID_SOCKET in close_socket from line %u",line);
274 275 276
		return(-1);
	}

277 278
	shutdown(*sock,SHUT_RDWR);	/* required on Unix */

279
	result=closesocket(*sock);
280
	if(startup!=NULL && startup->socket_open!=NULL) 
281
		startup->socket_open(startup->cbdata,FALSE);
282

283 284
	if(result!=0) {
		if(ERROR_VALUE!=ENOTSOCK)
285
			lprintf(LOG_WARNING,"%04d !ERROR %d closing socket from line %u",*sock,ERROR_VALUE,line);
286
	}
287 288 289 290 291
	*sock=INVALID_SOCKET;

	return(result);
}

292
#define GCES(status, sock, session, estr, action) do {                  \
deuce's avatar
deuce committed
293 294
	int GCES_level;                                                     \
	get_crypt_error_string(status, session, &estr, action, &GCES_level);\
295
	if (estr != NULL) {                                                 \
296
		lprintf(GCES_level, "%04d TLS %s", sock, estr);                 \
297 298
		free_crypt_attrstr(estr);										\
		estr=NULL;														\
deuce's avatar
deuce committed
299 300 301 302
	}                                                                   \
} while (0)


303 304 305
#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
deuce's avatar
deuce committed
306
static int sockprintf(SOCKET sock, CRYPT_SESSION sess, char *fmt, ...)
307 308
{
	int		len;
309
	int		maxlen;
310 311 312
	int		result;
	va_list argptr;
	char	sbuf[1024];
313 314
	fd_set	socket_set;
	struct timeval tv;
deuce's avatar
deuce committed
315
	char	*estr;
316 317

    va_start(argptr,fmt);
318 319 320
    len=vsnprintf(sbuf,maxlen=sizeof(sbuf)-2,fmt,argptr);
    va_end(argptr);

321
	if(len<0 || len>maxlen) /* format error or output truncated */
322
		len=maxlen;
323
	if(startup!=NULL && startup->options&FTP_OPT_DEBUG_TX)
deuce's avatar
deuce committed
324
		lprintf(LOG_DEBUG,"%04d TX%s: %.*s", sock, sess != -1 ? "S" : "", len, sbuf);
325
	memcpy(sbuf+len,"\r\n",2);
326
	len+=2;
327

328
	if(sock==INVALID_SOCKET) {
329
		lprintf(LOG_WARNING,"!INVALID SOCKET in call to sockprintf");
330 331 332
		return(0);
	}

333
	/* Check socket for writability (using select) */
334
	tv.tv_sec=300;
335 336 337 338 339 340
	tv.tv_usec=0;

	FD_ZERO(&socket_set);
	FD_SET(sock,&socket_set);

	if((result=select(sock+1,NULL,&socket_set,NULL,&tv))<1) {
341
		if(result==0)
342
			lprintf(LOG_WARNING,"%04d !TIMEOUT selecting socket for send"
343 344
				,sock);
		else
345
			lprintf(LOG_WARNING,"%04d !ERROR %d selecting socket for send"
346
				,sock, ERROR_VALUE);
347 348
		return(0);
	}
deuce's avatar
deuce committed
349 350 351 352 353 354 355 356 357

	if (sess != -1) {
		int tls_sent;
		int sent = 0;
		while (sent < len) {
			result = cryptPushData(sess, sbuf+sent, len-sent, &tls_sent);
			if (result == CRYPT_OK)
				sent += tls_sent;
			else {
deuce's avatar
deuce committed
358
				GCES(result, sock, sess, estr, "sending data");
359 360
				if (result != CRYPT_ERROR_TIMEOUT)
					return 0;
361
			}
deuce's avatar
deuce committed
362 363
			result = cryptFlushData(sess);
			if (result != CRYPT_OK) {
deuce's avatar
deuce committed
364
				GCES(result, sock, sess, estr, "flushing data");
deuce's avatar
deuce committed
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
				return 0;
			}
		}
	}
	else {
		while((result=sendsocket(sock,sbuf,len))!=len) {
			if(result==SOCKET_ERROR) {
				if(ERROR_VALUE==EWOULDBLOCK) {
					YIELD();
					continue;
				}
				if(ERROR_VALUE==ECONNRESET) 
					lprintf(LOG_WARNING,"%04d Connection reset by peer on send",sock);
				else if(ERROR_VALUE==ECONNABORTED)
					lprintf(LOG_WARNING,"%04d Connection aborted by peer on send",sock);
				else
					lprintf(LOG_WARNING,"%04d !ERROR %d sending",sock,ERROR_VALUE);
				return(0);
			}
			lprintf(LOG_WARNING,"%04d !ERROR: short send: %u instead of %u",sock,result,len);
385 386 387 388 389 390
		}
	}
	return(len);
}


391
/* Returns the directory index of a virtual lib/dir path (e.g. main/games/filename) */
rswindell's avatar
rswindell committed
392
int getdir(char* p, user_t* user, client_t* client)
393 394 395
{
	char*	tp;
	char	path[MAX_PATH+1];
rswindell's avatar
rswindell committed
396 397
	uint	dir;
	uint	lib;
398

rswindell's avatar
rswindell committed
399
	SAFECOPY(path,p);
400 401 402 403 404 405 406 407 408 409
	p=path;

	if(*p=='/') 
		p++;
	else if(!strncmp(p,"./",2))
		p+=2;

	tp=strchr(p,'/');
	if(tp) *tp=0;
	for(lib=0;lib<scfg.total_libs;lib++) {
rswindell's avatar
rswindell committed
410
		if(!chk_ar(&scfg,scfg.lib[lib]->ar,user,client))
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
			continue;
		if(!stricmp(scfg.lib[lib]->sname,p))
			break;
	}
	if(lib>=scfg.total_libs) 
		return(-1);

	if(tp!=NULL)
		p=tp+1;

	tp=strchr(p,'/');
	if(tp) *tp=0;
	for(dir=0;dir<scfg.total_dirs;dir++) {
		if(scfg.dir[dir]->lib!=lib)
			continue;
		if(dir!=scfg.sysop_dir && dir!=scfg.upload_dir 
rswindell's avatar
rswindell committed
427
			&& !chk_ar(&scfg,scfg.dir[dir]->ar,user,client))
428
			continue;
429
		if(!stricmp(scfg.dir[dir]->code_suffix,p))
430 431 432 433 434 435 436 437
			break;
	}
	if(dir>=scfg.total_dirs) 
		return(-1);

	return(dir);
}

438
void recverror(SOCKET socket, int rd, int line)
439 440
{
	if(rd==0) 
441
		lprintf(LOG_NOTICE,"%04d Socket closed by peer on receive (line %u)"
442
			,socket, line);
443
	else if(rd==SOCKET_ERROR) {
rswindell's avatar
rswindell committed
444
		if(ERROR_VALUE==ECONNRESET) 
445
			lprintf(LOG_NOTICE,"%04d Connection reset by peer on receive (line %u)"
446
				,socket, line);
447
		else if(ERROR_VALUE==ECONNABORTED) 
448
			lprintf(LOG_NOTICE,"%04d Connection aborted by peer on receive (line %u)"
449
				,socket, line);
450
		else
451
			lprintf(LOG_NOTICE,"%04d !ERROR %d receiving on socket (line %u)"
452
				,socket, ERROR_VALUE, line);
453
	} else
454
		lprintf(LOG_WARNING,"%04d !ERROR: recv on socket returned unexpected value: %d (line %u)"
455
			,socket, rd, line);
456 457
}

deuce's avatar
deuce committed
458
static int sock_recvbyte(SOCKET sock, CRYPT_SESSION sess, char *buf, time_t *lastactive)
459
{
deuce's avatar
deuce committed
460
	int len=0;
461
	fd_set	socket_set;
deuce's avatar
deuce committed
462 463 464
	struct	timeval	tv;
	int ret;
	int i;
deuce's avatar
deuce committed
465
	char *estr;
466
	BOOL first = TRUE;
467

deuce's avatar
deuce committed
468 469 470
	if(ftp_set==NULL || terminate_server) {
		sockprintf(sock,sess,"421 Server downed, aborting.");
		lprintf(LOG_WARNING,"%04d Server downed, aborting",sock);
471 472
		return(0);
	}
deuce's avatar
deuce committed
473 474
	if (sess > -1) {
		/* Try a read with no timeout first. */
deuce's avatar
deuce committed
475 476
		if ((ret = cryptSetAttribute(sess, CRYPT_OPTION_NET_READTIMEOUT, 0)) != CRYPT_OK)
			GCES(ret, sock, sess, estr, "setting read timeout");
deuce's avatar
deuce committed
477 478 479 480 481 482 483 484
		while (1) {
			ret = cryptPopData(sess, buf, 1, &len);
			/* Successive reads will be with the full timeout after a select() */
			cryptSetAttribute(sess, CRYPT_OPTION_NET_READTIMEOUT, startup->max_inactivity);
			switch(ret) {
				case CRYPT_OK:
					break;
				case CRYPT_ERROR_TIMEOUT:
485 486 487 488 489
					if (!first) {
						GCES(ret, sock, sess, estr, "popping data");
						return -1;
					}
					break;
deuce's avatar
deuce committed
490 491 492
				case CRYPT_ERROR_COMPLETE:
					return 0;
				default:
deuce's avatar
deuce committed
493
					GCES(ret, sock, sess, estr, "popping data");
deuce's avatar
deuce committed
494 495 496 497
					if (ret < -1)
						return ret;
					return -2;
			}
498
			first = FALSE;
deuce's avatar
deuce committed
499 500 501 502 503 504 505 506 507
			if (len)
				return len;
			
			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);
				return(0);
			}
508

deuce's avatar
deuce committed
509 510
			tv.tv_sec=startup->max_inactivity;
			tv.tv_usec=0;
511

deuce's avatar
deuce committed
512 513
			FD_ZERO(&socket_set);
			FD_SET(sock,&socket_set);
514

deuce's avatar
deuce committed
515
			i=select(sock+1,&socket_set,NULL,NULL,&tv);
516

deuce's avatar
deuce committed
517 518 519 520 521 522 523 524 525 526 527 528 529
			if(i<1) {
				if(i==0) {
					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);
						return(0);
					}
					continue;
				}
				recverror(sock,i,__LINE__);
				return(i);
			}
530
		}
deuce's avatar
deuce committed
531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550
	}
	else {
		while (1) {
			tv.tv_sec=startup->max_inactivity;
			tv.tv_usec=0;

			FD_ZERO(&socket_set);
			FD_SET(sock,&socket_set);

			i=select(sock+1,&socket_set,NULL,NULL,&tv);

			if(i<1) {
				if(i==0) {
					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);
						return(0);
					}
					continue;
551
				}
deuce's avatar
deuce committed
552
				return(i);
553
			}
deuce's avatar
deuce committed
554 555 556 557 558 559 560 561
	#ifdef SOCKET_DEBUG_RECV_CHAR
			socket_debug[sock]|=SOCKET_DEBUG_RECV_CHAR;
	#endif
			i=recv(sock, buf, 1, 0);
	#ifdef SOCKET_DEBUG_RECV_CHAR
			socket_debug[sock]&=~SOCKET_DEBUG_RECV_CHAR;
	#endif
			return i;
562
		}
deuce's avatar
deuce committed
563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580
	}
}

int sockreadline(SOCKET socket, CRYPT_SESSION sess, char* buf, int len, time_t* lastactive)
{
	char	ch;
	int		i,rd=0;

	buf[0]=0;

	if(socket==INVALID_SOCKET) {
		lprintf(LOG_WARNING,"INVALID SOCKET in call to sockreadline");
		return(0);
	}

	while(rd<len-1) {
		i = sock_recvbyte(socket, sess, &ch, lastactive);

581 582 583
		if(i<1) {
			if (sess != -1)
				recverror(socket,i,__LINE__);
584 585
			return(i);
		}
586
		if(ch=='\n' /* && rd>=1 */) { /* Mar-9-2003: terminate on sole LF */
587 588 589 590
			break;
		}	
		buf[rd++]=ch;
	}
591 592 593 594
	if(rd>0 && buf[rd-1]=='\r')
		buf[rd-1]=0;
	else
		buf[rd]=0;
deuce's avatar
deuce committed
595

596 597 598
	return(rd);
}

599
void DLLCALL ftp_terminate(void)
600
{
deuce's avatar
deuce committed
601
   	lprintf(LOG_INFO,"FTP Server terminate");
602
	terminate_server=TRUE;
603 604
}

605
int ftp_remove(SOCKET sock, int line, const char* fname, const char* username)
606 607 608
{
	int ret=0;

609 610
	if(fexist(fname) && (ret=remove(fname))!=0) {
		if(fexist(fname))	// In case there was a race condition (other host deleted file first)
611
			lprintf(LOG_ERR,"%04d <%s> !ERROR %d (%s) (line %d) removing file: %s", sock, username, errno, STRERROR(errno), line, fname);
612
	}
613 614
	return ret;
}
615 616

typedef struct {
rswindell's avatar
rswindell committed
617
	SOCKET		ctrl_sock;
deuce's avatar
deuce committed
618
	CRYPT_SESSION	ctrl_sess;
rswindell's avatar
rswindell committed
619
	SOCKET*		data_sock;
deuce's avatar
deuce committed
620
	CRYPT_SESSION*	data_sess;
rswindell's avatar
rswindell committed
621 622 623 624 625 626
	BOOL*		inprogress;
	BOOL*		aborted;
	BOOL		delfile;
	BOOL		tmpfile;
	BOOL		credits;
	BOOL		append;
627
	off_t		filepos;
rswindell's avatar
rswindell committed
628 629 630 631 632 633
	char		filename[MAX_PATH+1];
	time_t*		lastactive;
	user_t*		user;
	client_t*	client;
	int			dir;
	char*		desc;
634 635 636 637 638
} xfer_t;

static void send_thread(void* arg)
{
	char		buf[8192];
639
	char		fname[MAX_PATH+1];
640
	char		str[256];
641
	char		tmp[128];
642
	char		username[128];
deuce's avatar
deuce committed
643
	char		host_ip[INET6_ADDRSTRLEN];
644
	int			i;
645 646
	int			rd;
	int			wr;
647 648
	long		mod;
	ulong		l;
649 650
	off_t		total=0;
	off_t		last_total=0;
651 652
	ulong		dur;
	ulong		cps;
653
	off_t		length;
654 655 656 657 658 659 660
	BOOL		error=FALSE;
	FILE*		fp;
	file_t		f;
	xfer_t		xfer;
	time_t		now;
	time_t		start;
	time_t		last_report;
661
	user_t		uploader;
deuce's avatar
deuce committed
662
	union xp_sockaddr	addr;
663
	socklen_t	addr_len;
664 665
	fd_set		socket_set;
	struct timeval tv;
deuce's avatar
deuce committed
666
	char		*estr;
667 668

	xfer=*(xfer_t*)arg;
669
	free(arg);
670

671
	SetThreadName("sbbs/ftpSend");
672
	thread_up(TRUE /* setuid */);
673

674 675
	length=flength(xfer.filename);

676 677 678 679 680 681 682 683 684 685 686 687
	if(length < 1) {
		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);
		if(xfer.tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
			ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias);
		ftp_close_socket(xfer.data_sock,xfer.data_sess,__LINE__);
		*xfer.inprogress=FALSE;
		thread_down();
		return;
	}

688 689
	if((fp=fnopen(NULL,xfer.filename,O_RDONLY|O_BINARY))==NULL	/* non-shareable open failed */
		&& (fp=fopen(xfer.filename,"rb"))==NULL) {				/* shareable open failed */
690 691
		lprintf(LOG_ERR,"%04d <%s> !DATA ERROR %d (%s) line %d opening %s"
			,xfer.ctrl_sock, xfer.user->alias, errno, strerror(errno), __LINE__, xfer.filename);
rswindell's avatar
rswindell committed
692
		sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"450 ERROR %d (%s) opening %s", errno, strerror(errno), xfer.filename);
rswindell's avatar
rswindell committed
693
		if(xfer.tmpfile && !(startup->options&FTP_OPT_KEEP_TEMP_FILES))
694
			ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias);
deuce's avatar
deuce committed
695
		ftp_close_socket(xfer.data_sock,xfer.data_sess,__LINE__);
696
		*xfer.inprogress=FALSE;
697
		thread_down();
698 699 700
		return;
	}

701
#ifdef SOCKET_DEBUG_SENDTHREAD
rswindell's avatar
rswindell committed
702 703 704
			socket_debug[xfer.ctrl_sock]|=SOCKET_DEBUG_SENDTHREAD;
#endif

705
	*xfer.aborted=FALSE;
706 707
	if(xfer.filepos < 0)
		xfer.filepos = 0;
708
	if(startup->options&FTP_OPT_DEBUG_DATA || xfer.filepos)
709
		lprintf(LOG_DEBUG,"%04d <%s> DATA socket %d sending %s from offset %"PRIdOFF
710
			,xfer.ctrl_sock, xfer.user->alias, *xfer.data_sock,xfer.filename,xfer.filepos);
711 712 713

	fseek(fp,xfer.filepos,SEEK_SET);
	last_report=start=time(NULL);
714
	while((xfer.filepos+total)<length) {
715

716
		now=time(NULL);
717 718 719 720

		/* Periodic progress report */
		if(total && now>=last_report+XFER_REPORT_INTERVAL) {
			if(xfer.filepos)
721
				sprintf(str," from offset %"PRIdOFF,xfer.filepos);
722 723
			else
				str[0]=0;
724
			lprintf(LOG_INFO,"%04d <%s> DATA Sent %"PRIdOFF" bytes (%"PRIdOFF" total) of %s (%lu cps)%s"
725
				,xfer.ctrl_sock, xfer.user->alias, total,length,xfer.filename
726
				,(ulong)((total-last_total)/(now-last_report))
727 728 729 730 731
				,str);
			last_total=total;
			last_report=now;
		}

732
		if(*xfer.aborted==TRUE) {
733
			lprintf(LOG_WARNING,"%04d <%s> !DATA Transfer aborted",xfer.ctrl_sock, xfer.user->alias);
deuce's avatar
deuce committed
734
			sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"426 Transfer aborted.");
735 736 737
			error=TRUE;
			break;
		}
deuce's avatar
deuce committed
738
		if(ftp_set==NULL || terminate_server) {
739
			lprintf(LOG_WARNING,"%04d <%s> !DATA Transfer locally aborted",xfer.ctrl_sock, xfer.user->alias);
deuce's avatar
deuce committed
740
			sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"426 Transfer locally aborted.");
741 742 743
			error=TRUE;
			break;
		}
744 745

		/* Check socket for writability (using select) */
746
		tv.tv_sec=1;
747 748 749 750 751 752 753
		tv.tv_usec=0;

		FD_ZERO(&socket_set);
		FD_SET(*xfer.data_sock,&socket_set);

		i=select((*xfer.data_sock)+1,NULL,&socket_set,NULL,&tv);
		if(i==SOCKET_ERROR) {
754 755
			lprintf(LOG_WARNING,"%04d <%s> !DATA ERROR %d selecting socket %d for send"
				,xfer.ctrl_sock, xfer.user->alias, ERROR_VALUE, *xfer.data_sock);
deuce's avatar
deuce committed
756
			sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"426 Transfer error.");
757 758 759
			error=TRUE;
			break;
		}
deuce's avatar
deuce committed
760
		if(i<1)
761 762
			continue;

763
		fseek(fp,xfer.filepos+total,SEEK_SET);
764
		rd=fread(buf,sizeof(char),sizeof(buf),fp);
765
		if(rd<1) /* EOF or READ error */
766
			break;
767

768
#ifdef SOCKET_DEBUG_SEND
769 770
		socket_debug[xfer.ctrl_sock]|=SOCKET_DEBUG_SEND;
#endif
deuce's avatar
deuce committed
771 772 773
		if (*xfer.data_sess != -1) {
			int status = cryptPushData(*xfer.data_sess, buf, rd, &wr);
			if (status != CRYPT_OK) {
deuce's avatar
deuce committed
774
				GCES(status, *xfer.data_sock, *xfer.data_sess, estr, "pushing data");
deuce's avatar
deuce committed
775 776 777 778 779
				wr = -1;
			}
			else {
				status = cryptFlushData(*xfer.data_sess);
				if (status != CRYPT_OK) {
deuce's avatar
deuce committed
780
					GCES(status, *xfer.data_sock, *xfer.data_sess, estr, "flushing data");
deuce's avatar
deuce committed
781 782 783 784 785 786
					wr = -1;
				}
			}
		}
		else
			wr=sendsocket(*xfer.data_sock,buf,rd);
787
#ifdef SOCKET_DEBUG_SEND
788 789
		socket_debug[xfer.ctrl_sock]&=~SOCKET_DEBUG_SEND;
#endif
790
		if(wr<1) {
791
			if(wr==SOCKET_ERROR) {
792
				if(ERROR_VALUE==EWOULDBLOCK) {
793
					/*lprintf(LOG_WARNING,"%04d DATA send would block, retrying",xfer.ctrl_sock);*/
794
					YIELD();
795 796 797
					continue;
				}
				else if(ERROR_VALUE==ECONNRESET) 
798 799
					lprintf(LOG_WARNING,"%04d <%s> DATA Connection reset by peer, sending on socket %d"
						,xfer.ctrl_sock, xfer.user->alias,*xfer.data_sock);
800
				else if(ERROR_VALUE==ECONNABORTED) 
801 802
					lprintf(LOG_WARNING,"%04d <%s> DATA Connection aborted by peer, sending on socket %d"
						,xfer.ctrl_sock, xfer.user->alias,*xfer.data_sock);
803
				else
804 805
					lprintf(LOG_WARNING,"%04d <%s> !DATA ERROR %d sending on data socket %d"
						,xfer.ctrl_sock, xfer.user->alias,ERROR_VALUE,*xfer.data_sock);
806
				/* Send NAK */
deuce's avatar
deuce committed
807
				sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"426 Error %d sending on DATA channel"
808
					,ERROR_VALUE);
809 810 811 812
				error=TRUE;
				break;
			}
			if(wr==0) {
813
				lprintf(LOG_WARNING,"%04d <%s> !DATA socket %d disconnected",xfer.ctrl_sock, xfer.user->alias, *xfer.data_sock);
deuce's avatar
deuce committed
814
				sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"426 DATA channel disconnected");
815 816 817
				error=TRUE;
				break;
			}
818 819
			lprintf(LOG_ERR,"%04d <%s> !DATA ERROR %d (%d) sending on socket %d"
				,xfer.ctrl_sock, xfer.user->alias, wr, ERROR_VALUE, *xfer.data_sock);
deuce's avatar
deuce committed
820
			sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"451 DATA send error");
821 822 823 824
			error=TRUE;
			break;
		}
		total+=wr;
825
		*xfer.lastactive=time(NULL);
826
		//YIELD();
827 828
	}

829
	if((i=ferror(fp))!=0) 
830 831
		lprintf(LOG_ERR,"%04d <%s> !DATA FILE ERROR %d (%d, %s)"
			,xfer.ctrl_sock, xfer.user->alias, i, errno, strerror(errno));
832

deuce's avatar
deuce committed
833
	ftp_close_socket(xfer.data_sock,xfer.data_sess,__LINE__);	/* Signal end of file */
834
	if(startup->options&FTP_OPT_DEBUG_DATA)
835
		lprintf(LOG_DEBUG,"%04d <%s> DATA socket closed",xfer.ctrl_sock, xfer.user->alias);
836 837
	
	if(!error) {
838
		dur=(long)(time(NULL)-start);
839
		cps=dur ? total/dur : total*2;
840
		lprintf(LOG_INFO,"%04d <%s> DATA Transfer successful: %"PRIdOFF" bytes sent in %lu seconds (%lu cps)"
841
			,xfer.ctrl_sock
842
			,xfer.user->alias
843
			,total,dur,cps);
deuce's avatar
deuce committed
844
		sockprintf(xfer.ctrl_sock,xfer.ctrl_sess,"226 Download complete (%lu cps).",cps);
845 846 847

		if(xfer.dir>=0) {
			memset(&f,0,sizeof(f));
848
#ifdef _WIN32
849
			GetShortPathName(xfer.filename,fname,sizeof(fname));
850
#else
851
			SAFECOPY(fname,xfer.filename);
852 853
#endif
			padfname(getfname(fname),f.name);
854 855 856 857 858
			f.dir=xfer.dir;
			f.size=total;
			if(getfileixb(&scfg,&f)==TRUE && getfiledat(&scfg,&f)==TRUE) {
				f.timesdled++;
				putfiledat(&scfg,&f);
859
				f.datedled=time32(NULL);
860 861
				putfileixb(&scfg,&f);

862
				lprintf(LOG_INFO,"%04d <%s> DATA downloaded: %s (%u times total)"
863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886
					,xfer.ctrl_sock
					,xfer.user->alias
					,xfer.filename
					,f.timesdled);
				/**************************/
				/* Update Uploader's Info */
				/**************************/
				uploader.number=matchuser(&scfg,f.uler,TRUE /*sysop_alias*/);
				if(uploader.number
					&& uploader.number!=xfer.user->number 
					&& getuserdat(&scfg,&uploader)==0
					&& uploader.firston<f.dateuled) {
					l=f.cdt;
					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;
						adjustuserrec(&scfg,uploader.number,U_MIN,10,mod);
						sprintf(tmp,"%lu minute",mod);
					} else {
						mod=(ulong)(l*(scfg.dir[f.dir]->dn_pct/100.0));
						adjustuserrec(&scfg,uploader.number,U_CDT,10,mod);
						ultoac(mod,tmp);
					}
887
					if(!(scfg.dir[f.dir]->misc&DIR_QUIET)) {
888 889
						addr_len = sizeof(addr);
						if(uploader.level>=SYSOP_LEVEL
deuce's avatar
deuce committed
890 891 892
							&& 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);
893 894
						else
							SAFECOPY(username,xfer.user->alias);
895
						/* Inform uploader of downloaded file */
896
						safe_snprintf(str,sizeof(str),text[DownloadUserMsg]
897 898
							,getfname(xfer.filename)
							,xfer.filepos ? "partially FTP-" : "FTP-"
899
							,username,tmp); 
900 901
						putsmsg(&scfg,uploader.number,str); 
					}
902
				}
903
			}
904
			if(!xfer.tmpfile && !xfer.delfile && !(scfg.dir[f.dir]->misc&DIR_NOSTAT))
905
				inc_sys_download_stats(&scfg, 1, total);
906 907 908
		}	

		if(xfer.credits) {
909
			user_downloaded(&scfg, xfer.user, 1, total);
rswindell's avatar
rswindell committed
910
			if(xfer.dir>=0 && !is_download_free(&scfg,xfer.dir,xfer.user,xfer.client))
911 912 913 914 915
				subtract_cdt(&scfg, xfer.user, xfer.credits);
		}
	}

	fclose(fp);
deuce's avatar
deuce committed
916
	if(ftp_set!=NULL && !terminate_server)
917
		*xfer.inprogress=FALSE;
rswindell's avatar
rswindell committed
918 919
	if(xfer.tmpfile) {
		if(!(startup->options&FTP_OPT_KEEP_TEMP_FILES))
920
			ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias);
rswindell's avatar
rswindell committed
921 922
	} 
	else if(xfer.delfile && !error)
923
		ftp_remove(xfer.ctrl_sock, __LINE__, xfer.filename, xfer.user->alias);
924

925
#if defined(SOCKET_DEBUG_SENDTHREAD)
rswindell's avatar
rswindell committed
926 927 928
			socket_debug[xfer.ctrl_sock]&=~SOCKET_DEBUG_SENDTHREAD;
#endif

929 930 931 932 933
	thread_down();
}

static void receive_thread(void* arg)
{
934
	char*		p;
935
	char		str[128];
936
	char		buf[8192];
937 938 939 940
	char		ext[F_EXBSIZE+1];
	char		desc[F_EXBSIZE+1];
	char		cmd[MAX_PATH*2];
	char		tmp[MAX_PATH+1];
941
	char		fname[MAX_PATH+1];
942
	int			i;
943 944
	int			rd;
	int			file;
945 946
	off_t		total=0;
	off_t		last_total=0;
947 948 949
	ulong		dur;
	ulong		cps;
	BOOL		error=FALSE;
950
	BOOL		filedat;
951 952 953 954 955 956
	FILE*		fp;
	file_t		f;
	xfer_t		xfer;
	time_t		now;
	time_t		start;
	time_t		last_report;