/*
 * Configurable Finger Daemon
 * Version 1.0.3 [LINUX/BSDI]
 *
 * by Ken Hollis (khollis@bitgate.chatlink.com)
 *
 * This program is released under the GNU Public License.  Please read the
 * man pages on cfingerd for more details.
 */

#include "cfingerd.h"

CONFIG prog_config;
BOOL local_finger, trusted, rejected;
char *local_host = NULL;

int main(int argc, char **argv[])
{
    int psize, fake = 0;
    BOOL illegal = FALSE, found_real = FALSE;
    pid_t process_id;
    struct hostent *host_ent;
    struct sockaddr_in socket_addr;
    char *system_name = NULL, *username = NULL, *extra_text = NULL, 
	*realuser = NULL, *progid = NULL, *stupid = NULL, *procname = NULL;
    char buffer[128];

    /* Set up the syslogger output, get process id, etc. */
    process_id = getpid();
    progid = (char *) malloc(18);
    sprintf(progid, "cfingerd[%d]", process_id);

    /* And open the logger... */
    openlog(progid, LOG_NDELAY, LOG_DAEMON);

    /* This is for the actual user's name (without the "."s) */
    realuser = (char *) malloc(16);

    /* Initialize the daemon, and get our local hostname */
    initialize_daemon();
    strmcpy(&local_host, get_localhost());

    /* Read the cfingerd.conf (or whatever) file. */
    read_configuration();

#ifdef	DEBUG
    show_configuration();
#endif

    /* Get socket peer's name */
    psize = sizeof(socket_addr);
    if (getpeername(0, (struct sockaddr *) &socket_addr, &psize)) {
#ifdef	SYSLOG
	syslog(LOG_ERR, "Fatal - getpeername: %s (Possibly run from command line)", strerror(errno));
#endif
	printf("\nYou cannot run the Configurable Finger Daemon by itself on a command\n");
	printf("line.  It must be called from within inetd.  Please RTFM.\n\n");
	closelog();
	exit(PROGRAM_SYSLOG);
    }

    /* Get host entry */
    host_ent = (struct hostent *) gethostbyaddr((char *) &socket_addr.sin_addr,
	       sizeof(socket_addr.sin_addr), AF_INET);

    /* Reserve space for the process name -- 80 chars for good measure */
    procname = (char *) malloc(80);

    /* Check for actual hostname entry (if the entry exists or not) */
    if (host_ent == NULL) {
	system_name = "(N/A)";
#ifdef	SYSLOG
	syslog(LOG_WARNING, "%s", prog_config.p_strings[D_IP_NO_MATCH]);
#endif
 	if (prog_config.config_bits1 & SHOW_IP_MATCH) {
	    display_file(prog_config.rejected_file);
	    closelog();
	    exit(PROGRAM_OKAY);
	}

	sprintf(procname, "CFINGERD: ??? active");
    } else {
	system_name = (char *) host_ent->h_name;
	sprintf(procname, "CFINGERD: %s active", system_name);
    }

    /* Change the name in the process listing for PS (and /proc on Linux) */
    strcpy((char *) argv[0], procname);

    /* And free the process name entry */
    free(procname);

    /* Get the system name that is fingering us */
    local_finger = ((!strcmp(local_host, system_name)) || 
		!strcmp("localhost", system_name)) ? TRUE : FALSE;

    /* Check to see if this system is in the trusted or rejected list */
    trusted = is_trusted(system_name);
    rejected = is_rejected(system_name);

    /* Run the program at the LOWEST possible priority.  10 is the LOWEST */
    if (nice(10) == -1) {
#ifdef	SYSLOG
	syslog(LOG_ERR, "%s %s", prog_config.p_strings[D_NICE_FATAL],
	       strerror(errno));
#endif
	closelog();
	exit(PROGRAM_SYSLOG);
    }

    /* Reset the buffer, and set the timeout period */
    buffer[0] = 0;
    alarm(TIMEOUT);

    /* Make sure we have information waiting */
    if (!fgets(buffer, sizeof(buffer), stdin)) {
#ifdef	SYSLOG
	syslog(LOG_ERR, "%s (%s): %s [%s]",
		inet_ntoa(socket_addr.sin_addr),
		prog_config.p_strings[D_STDIN_EMPTY],
		system_name, strerror(errno));
#endif
	closelog();
	exit(PROGRAM_SYSLOG);
    }

    /* Copy the buffer, and strip the username of \r and \n */
    strmcpy(&username, buffer);
    username[strlen(username)-2]='\0';

    /* Check for ANY illegal characters in the user's username -- this
       will close all connections if any illegal character is found. */
    check_illegal(username);

    /* Check to see if the user actually exists (if the user has a "." in the
       user's name, it still checks.  :) */
    found_real = check_for_user(username);

    /* Check for any "."'s in the name, and if any exist, make a back-up of
       the actual user's name that is fingered. */
    if ((strcmp(".", username)) && (!found_real)) {
	char *backup = NULL;

	strmcpy(&backup, username);
	realuser = strsep(&backup, ".");

	free(backup);
    } else {
	strmcpy(&realuser, username);
    }

    /* Keep a backup of the stupid user if someone does a "/W" or "-L"...
       (Psst...  "/W" requests whois info, and "-L" is a long userlist) */
    strmcpy(&stupid, realuser);

    /* If they're requesting whois information, search fwhois for the
       user's information (if you actually run a server...) */
    if (!strncmp(stupid, "/W", 2)) {
	char *command = NULL, *user_tmp = NULL;

	command = (char *) malloc(80);
	user_tmp = (char *) malloc(80);

	sscanf(stupid, "/W%[^\r\n]\r\n", user_tmp);
	sprintf(command, "%s %s@%s", WHOIS, user_tmp, local_host);
	system(command);
	free(command);
	free(user_tmp);
    }

    /* Sorry, MicroSloth Windoze doesn't have precidence here.  Besides, it's
       non finger-RFC compliant. */
    if (!strncmp(stupid, "-L", 2)) {
	printf("Sorry, the long format display of user information is not\n");
	printf("supported in this version of the finger daemon server.\n");
	exit(PROGRAM_SYSLOG);
    }

    /* Check to see if this user is fake or not */
    fake = is_fake(realuser);

    /* Patch by Martin Radford, 04/12/95 */
    extra_text = (trusted) ? prog_config.p_strings[D_TRUST_HOST] : "";
    extra_text = (rejected) ? prog_config.p_strings[D_REJECT_HOST] : extra_text;

#ifdef	SYSLOG
    if (strcmp(realuser, "version"))
	if ((!fake) && (strncmp("/W", realuser, 2))) {
	    strmcpy(&realuser, username);

	    /* Show syslog info if this is NOT a WHOIS request */
	    syslog(LOG_NOTICE, "%s [%s%s] : %s", 
	   (local_finger) ? "127.0.0.1" : inet_ntoa(socket_addr.sin_addr),
	   (local_finger) ? "localhost" : system_name, extra_text,
	   ((!strcmp("root", realuser)) ? prog_config.p_strings[D_ROOT_FINGER] : 
	   ((!strcmp("services", realuser)) ? prog_config.p_strings[D_SVC_FINGER] :
	   ((!strcmp("", realuser)) ? prog_config.p_strings[D_ULIST_FINGER] : realuser))));
	} else if (!strncmp("/W", realuser, 2)) {
	    char *whoisuser = NULL;

	    whoisuser = (char *) malloc(80);

	    /* Cut off the "/W" header */
	    sscanf(realuser, "/W%[^\r\n]\r\n", whoisuser);

	    /* Since this is a whois request, display that. */
	    syslog(LOG_NOTICE, "%s [%s%s] : %s \"%s\"", 
	   (local_finger) ? "127.0.0.1" : inet_ntoa(socket_addr.sin_addr),
	   (local_finger) ? "localhost" : system_name, extra_text,
	   prog_config.p_strings[D_WHOIS_USER],
	   whoisuser);
	    free(whoisuser);
	} else if (fake) {
	    /* If this is a fake (script) user, display so! */
	    syslog(LOG_NOTICE, "%s [%s%s] : %s %s\"%s\"", 
	   (local_finger) ? "127.0.0.1" : inet_ntoa(socket_addr.sin_addr),
	   (local_finger) ? "localhost" : system_name, extra_text,
	   prog_config.p_strings[D_FAKE_USER],
	   (is_searchable(realuser)) ? "*" : "", realuser);
	}
#endif

    /* If this was a WHOIS query, exit the program here. */
    if ((!strncmp("/W", realuser, 2)))
	exit(PROGRAM_OKAY);

    /* Change the root working directory to / */
    if (chroot("/")) {
#ifdef 	SYSLOG
	syslog(LOG_ERR, "chroot: %s", strerror(errno));
#endif

#ifdef	DEBUG
	syslog(LOG_NOTICE, "Cause: inetd not hung up or finger is not root-run!");
#endif
	printf("Cannot change to root permissions!\n\n");
	printf("There are two probable causes of error, and they are:\n");
	printf("\no\tinetd.conf does not run finger as ROOT (ie. RTFM)\n");
	printf("o\tYou did not hang up the inetd process.\n\n");
	printf("There are *NO OTHER CAUSES* of this error!\n\n");
	exit(PROGRAM_SYSLOG);
    }

    /* Got a fast system?  Slow it down here! */
    /* Patched 04/12/95 by Ken Hollis */
    if (GRACE_PERIOD > 0)
	sleep(GRACE_PERIOD);

    if (rejected) {
	/* If this is a rejected host, display the rejected file */
	display_file(prog_config.rejected_file);
    } else if (!fake) {
	if (strlen(realuser) > 0) {
	    /* Take a WILD guess for what this is here for...  :) */
	    if (!strncmp(realuser, "version", 7)) {
		show_version_information();
	    }

	    /* Show the services list */
	    if (!strncmp(realuser, "services", 8)) {
		show_services();
	    }

	    /* Check to see if the username entered is illegal */
	    if (search_illegal(realuser)) {
		illegal = TRUE;
#ifdef	DEBUG
		printf("Illegal username entered: %s\n", realuser);
#endif
#ifdef	SYSLOG
		syslog(LOG_WARNING, "Illegal username: %s", realuser);
#endif

		if ((((local_finger) || (trusted)) &&
		   prog_config.local_config_bits2 & SHOW_NOUSER) ||
		   (((!local_finger) && (!trusted)) &&
		   prog_config.config_bits2 & SHOW_NOUSER)) {
		    /* If you want a file displayed for a non-existent user,
			this is called. */
		    display_file(prog_config.no_user_banner_file);
		} else {
		    /* Otherwise, it displays this text by default. */
		    printf("Sorry, you have entered a username using an illegal character.\n");
		    printf("The username, %s, does not exist on this system.\n",
				realuser);
		}
		closelog();

		exit(PROGRAM_OKAY);
	    }
	}

	/* Is the requested user a non-illegal username?  If so, go here! */
	if ((strncmp(realuser, "version", 7)) && 
	    (strncmp(realuser, "services", 8)) && !illegal) {
	    if ((strlen(realuser) == 0) &&
		(prog_config.config_bits2 & SHOW_NN_BANNER)) {
		if ((((local_finger) || (trusted)) &&
		   prog_config.local_config_bits2 & SHOW_SYSTEMLIST) ||
		   (((!local_finger) && (!trusted)) &&
		   prog_config.config_bits2 & SHOW_SYSTEMLIST)) {
		    /* Become "nobody" to not pose any problems. */
		    become_nobody();

		    if ((((local_finger) || (trusted)) &&
			prog_config.local_config_bits1 & SHOW_TOP) ||
			(((!local_finger) && (!trusted)) &&
			prog_config.config_bits1 & SHOW_TOP))
			display_file(prog_config.top_display_file);

		    /* Flush output at the top before the finger does! */
		    fflush(stdout);

		    /* If the user fingers "@", display who's online. */
		    system(prog_config.finger_program);

		    if ((((local_finger) || (trusted)) &&
			prog_config.local_config_bits1 & SHOW_BOTTOM) ||
			(((!local_finger) && (!trusted)) &&
			prog_config.config_bits1 & SHOW_BOTTOM))
			display_file(prog_config.bottom_display_file);
#ifdef	DEBUG
#ifdef	SYSLOG
		    syslog(LOG_NOTICE, "Spawned %s",
			prog_config.finger_program);
#endif
#endif
		} else {
		    /* Else display that no user was requested. */
		    display_file(prog_config.no_name_banner_file);
		}
	    }

	    /* If we're not local, and someone's fingering us, do this... */
	    if (!no_finger(realuser)) {
		if ((strlen(realuser) > 0) || 
		   ((strlen(realuser) == 0) &&
		   (!(prog_config.config_bits2 & SHOW_NN_BANNER))))
		    if ((((local_finger) || (trusted)) &&
			prog_config.local_config_bits1 & SHOW_TOP) ||
			(((!local_finger) && (!trusted)) &&
			prog_config.config_bits1 & SHOW_TOP)) {

			/* Display the top file if configured to do so */
			display_file(prog_config.top_display_file);
		    }

		/* Display the main part of the finger */
		if ((strlen(realuser) > 0) || 
		   ((strlen(realuser) == 0) &&
		   (!(prog_config.config_bits2 & SHOW_NN_BANNER))))
		    if (!search_illegal(realuser))
			main_finger_part((char *) realuser);

		if ((strlen(realuser) > 0) || 
		   ((strlen(realuser) == 0) &&
		   (!(prog_config.config_bits2 & SHOW_NN_BANNER))))
		    if ((((local_finger) || (trusted)) &&
			prog_config.local_config_bits1 & SHOW_BOTTOM) ||
			(((!local_finger) && (!trusted)) &&
			prog_config.config_bits1 & SHOW_BOTTOM)) {
			/* And display the bottom file if configured. */
			display_file(prog_config.bottom_display_file);
		    }
	    }
	}
    }

    /* If we're a fake user... */
    if (fake) {
	/* Change the permissions to the NOBODY user ID */
	become_nobody();

	/* And call the script under that ID.  (And you CAN'T do /etc/passwd
	   in this mode! */
	call_userscript(username, realuser);
    }

    /* Close the syslogger */
    closelog();

    /* And GET OUTTA HERE! */
    exit(PROGRAM_OKAY);
}
