
#include "captive.h"


/* This is the CGI program that processes client logins and futzes with the iptables
   rules.  It does the following, in this order:

	Read client data from web form (username & password).  Check for proper format,
	illegal characters, string length, etc.

	Consult database (/var/smoothwall/mods/captive/captivedb).  If an invalid
	user/pass pair, or if the account is disabled, prints an error.  Otherwise,
	proceeds with authentication process:

		If there is another user already logged in at this IP:
		- adjust that other user's database entry to show he's logged off
		- leave the iptables rules in place since our new user is at the
		  same computer

		then

		If the user is not logged in:
		- add an entry for this user in the "currently logged in" database
		- exec the iptables commands to grant red-side access to the user's
		  MAC/IP (if these rules don't already exist from the scenario
		  above)

		If the user is already logged in (same MAC & IP as before):
		- change this client's entry in the database to reflect his new
		  re-login time
		- no iptables work is necessary

		If the user is already logged in (different MAC or IP than before):
		- change this client's entry in the "currently logged in" database
		  to reflect his new re-login time and new MAC and/or IP
		- exec the iptables commands to delete the rules for the old MAC/IP
		- exec the iptables commands to grant red-side access for tis MAC/IP

	Print out the "Login Successful" message.
*/




int main( int argc, char *argv[] ) {
	GDBM_FILE dbf;
	datum key, nextkey;
	datum content;

	char buffer[1000];

	char login_username[USERNAME_LEN],
	     login_password[PASSWORD_LEN];
	long login_dur_in_seconds;

	char ip[IP_LEN], oldip[IP_LEN];
	char mac[MAC_LEN], oldmac[MAC_LEN];

	int retval;
	int done;
	time_t tp;


	time( &tp );

	printf( "Content-type: text/html\r\n\r\n" );

	/* Read the form's POST data into a buffer.  */
	fgets( buffer, 1000, stdin );

	/* Get the provided username & password.  */
	if( parse_post_data(buffer,login_username,login_password) == 0 ) {
		/* Bad POST data or non-alphanumeric u/p pair */
		printfile( "/var/smoothwall/mods/captive/error_bad_user_or_pass.html" );
		exit(0);
	}

	/*  Get his IP address from the environment variable.  */

	strncpy( ip, getenv("REMOTE_ADDR"), IP_LEN );
	if( ip[0] == '\0' ) {
		/* don't know the client's IP address for some reason */
                post_to_log( EVENT_LOGIN_FAIL_NO_IP, login_username, "?", "?", (long)tp );
		printf( "<html>Error - unable to get client's IP address from REMOTE_ADDR env variable.</html>\n" );
		exit(0);
	}

	/* Get the MAC address most recently associated with this IP from the
	   SW's dhcp lease file.  Finding, or not finding, the user's correct
	   MAC address here is key in preventing valid users with static IP
	   addresses from logging in. */
	retval = get_mac_from_dhcp_lease( ip, mac );
	if( retval != 1 ) {
		/* the client didn't get an IP address from the DHCP server */
                post_to_log( EVENT_LOGIN_FAIL_NO_MAC_IN_LEASE_FILE, login_username, ip, "?", (long)tp );
		printf( "<html>Error %d<br>\nEither the authentication server has made an error, the DHCP log has been reset, or you are using a static IP address.  You must obtain an IP address by DHCP.<br><br>Be sure your computer is set to get its IP address automatically.  Typing \"ipconfig /renew\" at a command prompt (Start ... Run ... \"cmd\") may help.</html>\n", retval );
		exit(0);
	}

        /* Check to see if this is a valid u/p pair, and retrieve
           this user's alloted login time. */
        retval = valid_up(login_username,login_password,&login_dur_in_seconds);

        if( retval == 0 ) {
                /* u/p pair not in authorized list */
                post_to_log( EVENT_LOGIN_FAIL_BAD_USERPASS, login_username, ip, mac, (long)tp );
                printfile( "/var/smoothwall/mods/captive/error_bad_user_or_pass.html" );
                exit(0);
        } else if( retval == 2 ) {
                /* u/p pair in authorized list, but account marked as disabled */
                post_to_log( EVENT_LOGIN_FAIL_ACCOUNT_DISABLED, login_username, ip, mac, (long)tp );
                printfile( "/var/smoothwall/mods/captive/error_acct_disabled.html" );
                exit(0);
        } else if( retval == -1 ) {
                /* db error */
                post_to_log( EVENT_DATABASE_ERROR, login_username, ip, mac, (long)tp );
                printfile( "/var/smoothwall/mods/captive/error_system.html" );
                exit(0);
        }




	/* There are a few possibilities WRT the existing state of things ...

		Someone at this IP is already logged on
			1 same user, same MAC
			2 same user, different MAC
			3 different user, same MAC
			4 different user, different MAC

		No one is currently logged on at this IP
			5 this user is already logged on at a different IP (MAC may/may not be the same)
			6 this user is not already logged on

	   We will search the database for any user who's currently logged on at
	   this IP address.  If we get a hit, we'll handle cases 1-4.  If not, we'll
	   handle caess 5-6.
	*/


	dbf = gdbm_open( "/var/smoothwall/mods/captive/captivedb", 512, GDBM_WRCREAT, S_IRWXU, NULL );
	if( dbf == NULL ) { printf( "<html>Error opening captivedb</html>\n" ); exit(0); }

	/* Is there another user already logged in at this IP & MAC?  If so, we
	   need to log that user off, and log the new user on.  Messing with
	   iptables rules is not necessary.
	*/

	key = gdbm_firstkey( dbf );
	if( key.dptr != NULL ) key.dptr[key.dsize] = '\0';   /* Add null term, just to be safe. */

	done = 0;

	while( key.dptr ) {
		content = gdbm_fetch( dbf, key );
		if( strncmp(((userinfo *)content.dptr)->ip,ip,IP_LEN) == 0 ) {

			/* Either a) this user is already logged in, or b) another
			   user is logged in at this IP. */
/*
printf( "Comparing old:|%s|%d|%d with new:|%s|%d<br><br>\n", key.dptr, strlen(key.dptr), key.dsize, login_username, strlen(login_username) );
*/
			if( strncmp( key.dptr, login_username, USERNAME_LEN) == 0 ) {

/*
printf( "Handling cases 1-2:  Same user, logging in from same IP<br><br>\n" );
*/
				/* Cases 1 and 2
				   It's the same user.  Change login_expires_at time.
				*/
				((userinfo *)content.dptr)->login_expires_at = (long)tp + ((userinfo *)content.dptr)->login_dur_in_seconds;

				/* If the MAC is different, the iptables rules must change. */
				strncpy( oldmac, ((userinfo *)content.dptr)->mac, MAC_LEN );
				if( strncmp(mac,oldmac,MAC_LEN) != 0 ) {
/*
printf( "12 Different MAC - changing IPtables rules.<br><br>\n" );
printf( "oldmac = |%s| &nbsp; mac = |%s|<br>\n", oldmac, mac );
*/
					if( setuid(0) != 0 ) { printf( "<html>Error 5 (a)</html>\n" ); exit(0); }
					iptables_rule( IPTABLES_DEL, ip, oldmac );
					iptables_rule( IPTABLES_INS, ip, mac );
				}
/*
else printf( "12 Same MAC - no need to change IPtables rules.<br><br>\n" );
*/

				/* Save the new login_expires_at to the database. */
				gdbm_store( dbf, key, content, GDBM_REPLACE );
				free( key.dptr );
				free( content.dptr );
				done = 1;

                		post_to_log( EVENT_LOGIN_SUCCESS, login_username, ip, mac, (long)tp );
				nmap_portscan( login_username, ip );

			} else {
				/* Cases 3 and 4
				   It's a different user.  Log the old guy off ...
				*/
/*
printf( "Handling cases 3-4:  Different user, logging in from same IP<br><br>\n" );
*/

				strncpy( oldmac, ((userinfo *)content.dptr)->mac, MAC_LEN );  /* save this info, we'll need it */
                		post_to_log( EVENT_LOGOFF_FORCED, key.dptr, ip, oldmac, (long)tp );

				((userinfo *)content.dptr)->logged_in_now = 0;
				((userinfo *)content.dptr)->ip[0] = '\0';
				((userinfo *)content.dptr)->mac[0] = '\0';
				((userinfo *)content.dptr)->login_expires_at = 0;
				gdbm_store( dbf, key, content, GDBM_REPLACE );
				free( key.dptr );
				free( content.dptr );

				/* ... and log the new guy on.  */
				key.dptr = login_username;
				key.dsize = strlen( login_username );
				content = gdbm_fetch( dbf, key );

				/* But first, check to see if the new guy is already logged
				   on somewhere else.  If so, log him off there before logging
				   him on here.  All we need to do is whack the iptables rules
				   that gave him access over there.  We're going to overwrite
				   his userinfo data pertaining to that previous login anyway ...
				*/
				if( ((userinfo *)content.dptr)->logged_in_now ) {
                                        if( setuid(0) != 0 ) { printf( "<html>Error 5 (a)</html>\n" ); exit(0); }
                                        iptables_rule( IPTABLES_DEL, ((userinfo *)content.dptr)->ip, ((userinfo *)content.dptr)->mac );
                			post_to_log( EVENT_LOGOFF_FORCED, login_username, ((userinfo *)content.dptr)->ip, ((userinfo *)content.dptr)->mac, (long)tp );
				}


                		post_to_log( EVENT_LOGIN_SUCCESS, login_username, ip, mac, (long)tp );
				nmap_portscan( login_username, ip );

				((userinfo *)content.dptr)->logged_in_now = 1;
				strncpy( ((userinfo *)content.dptr)->ip, ip, IP_LEN );
				strncpy( ((userinfo *)content.dptr)->mac, mac, MAC_LEN );
				((userinfo *)content.dptr)->login_expires_at = (long)tp + ((userinfo *)content.dptr)->login_dur_in_seconds;
				gdbm_store( dbf, key, content, GDBM_REPLACE );
				free( content.dptr );
				/* No need to free key.dptr as it's pointing at a static array. */

				/* If the MAC is different, the iptables rules must change. */
				if( strncmp(mac,oldmac,MAC_LEN) != 0 ) {
/*
printf( "34 Different MAC - changing IPtables rules.<br><br>\n" );
*/
					if( setuid(0) != 0 ) { printf( "<html>Error 5 (a)</html>\n" ); exit(0); }
					iptables_rule( IPTABLES_DEL, ip, oldmac );
					iptables_rule( IPTABLES_INS, ip, mac );
				}
/*
else printf( "34 Same MAC - no need to change IPtables rules.<br><br>\n" );
*/
				done = 1;

			}

			break;
		}
		free( content.dptr );
	
		nextkey = gdbm_nextkey ( dbf, key );
		free( key.dptr );
		key = nextkey;
		if( key.dptr != NULL ) key.dptr[key.dsize] = '\0';   /* Add null term, just to be safe. */
	}


	if ( !done ) {
		/* Then we need to handle either case 5 or 6 */
/*
printf( "Handling cases 5-6:  No one logged in from same IP<br><br>\n" );
*/

		key.dptr = login_username;
		key.dsize = strlen( login_username );
		content = gdbm_fetch( dbf, key );

		if( ((userinfo *)content.dptr)->logged_in_now == 1 ) {
/*
printf( "Case 5, user already logged on, but at a different IP.<br><br>\n" );
*/
			/* Case 5
			   This user is already logged in, but at a different IP.  We
			   need to delete his old iptables rules, add some new ones,
			   and update his database entry with new login_expires_at time.
			*/

			strncpy( oldip, ((userinfo *)content.dptr)->ip, IP_LEN );
			strncpy( oldmac, ((userinfo *)content.dptr)->mac, MAC_LEN );
			
			if( setuid(0) != 0 ) { printf( "<html>Error 5 (a)</html>\n" ); exit(0); }
			iptables_rule( IPTABLES_DEL, oldip, oldmac );
			iptables_rule( IPTABLES_INS, ip, mac );

               		post_to_log( EVENT_LOGOFF_FORCED, login_username, oldip, oldmac, (long)tp );
               		post_to_log( EVENT_LOGIN_SUCCESS, login_username, ip, mac, (long)tp );
			nmap_portscan( login_username, ip );

			strncpy( ((userinfo *)content.dptr)->ip, ip, IP_LEN );
			strncpy( ((userinfo *)content.dptr)->mac, mac, MAC_LEN );
			((userinfo *)content.dptr)->login_expires_at = (long)tp + ((userinfo *)content.dptr)->login_dur_in_seconds;
			gdbm_store( dbf, key, content, GDBM_REPLACE );
			free( content.dptr );

		} else {
/*
printf( "Case 6, user not logged on, no one else logged on at that IP either.<br><br>\n" );
*/
			/* Case 6
			   This user is not logged in, and no one else is listed
			   as logged in at his current IP address.
			*/

			if( setuid(0) != 0 ) { printf( "<html>Error 5 (a)</html>\n" ); exit(0); }
			iptables_rule( IPTABLES_INS, ip, mac );

			((userinfo *)content.dptr)->logged_in_now = 1;
			strncpy( ((userinfo *)content.dptr)->ip, ip, IP_LEN );
			strncpy( ((userinfo *)content.dptr)->mac, mac, MAC_LEN );
/*
printf( "The actual MAC address stored is |%s|<br>\n", ((userinfo *)content.dptr)->mac );
*/
			((userinfo *)content.dptr)->login_expires_at = (long)tp + ((userinfo *)content.dptr)->login_dur_in_seconds;
			gdbm_store( dbf, key, content, GDBM_REPLACE );
			free( content.dptr );

               		post_to_log( EVENT_LOGIN_SUCCESS, login_username, ip, mac, (long)tp );
			nmap_portscan( login_username, ip );

		}

	}

	printfile( "/var/smoothwall/mods/captive/successful_login.html" );

	return 0;
}

