/*
 * CFINGERD
 * Utilities
 *
 * 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 1, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include "cfingerd.h"
#include "proto.h"
#include "version.h"
#ifdef	BSD
#include "getutent.h"
#endif
#include "privs.h"

#include <regex.h>

/*
 * CLEAR_CFINGERD_VARS
 *
 * This clears out memory for the program configuration variables.
 */
void clear_cfingerd_vars(void)
{
    int i;

    prog_config.top_display_file = NULL;
    prog_config.bottom_display_file = NULL;
    prog_config.plan_file = NULL;
    prog_config.project_file = NULL;
    prog_config.pgpkey_file = NULL;
    prog_config.xface_file = NULL;
    prog_config.userlog_file = NULL;
    prog_config.mailbox_file = NULL;
    prog_config.no_name_banner_file = NULL;
    prog_config.no_user_banner_file = NULL;
    prog_config.no_finger_file = NULL;
    prog_config.identd_banner_file = NULL;
    prog_config.rejected_file = NULL;
    prog_config.syslog_file = NULL;
    prog_config.finger_program = NULL;
    prog_config.whois_program = NULL;

    for (i = 0; i < 80; i++) {
	prog_config.rejected[i] = NULL;
	prog_config.trusted[i] = NULL;
	prog_config.forward[i] = NULL;
	prog_config.p_strings[i] = NULL;
	prog_config.finger_sites[i] = NULL;
    }

    for (i = 0; i < 24; i++)
	prog_config.siglist[i] = NULL;

    memset (prog_config.stime_format, 0, sizeof (prog_config.stime_format));
    memset (prog_config.ltime_format, 0, sizeof (prog_config.ltime_format));

    for (i = 0; i < 40; i++) {
	prog_config.fusers[i].user = NULL;
	prog_config.fusers[i].script = NULL;
	prog_config.fusers[i].description = NULL;
	prog_config.fusers[i].searchable = FALSE;
    }

    for (i = 0; i < 20; i++)
	prog_config.services.header[i] = NULL;

    prog_config.services.display_string = NULL;
    prog_config.services.name_pos = 0;
    prog_config.services.service_pos = 0;
    prog_config.services.search_pos = 0;

    prog_config.config_bits1 = 0;
    prog_config.config_bits2 = 0;
    prog_config.config_bits3 = 0;
    prog_config.local_config_bits1 = 0;
    prog_config.local_config_bits2 = 0;
    prog_config.local_config_bits3 = 0;
    prog_config.override_bits1 = 0;
    prog_config.override_bits2 = 0;
}

/*
 * START_CFINGERD
 *
 * This simply clears out memory, and changes the program ID name for
 * syslog.
 */
void start_cfingerd(void)
{
    int pid = getpid();
    char *proc;

    proc = (char *) malloc(20);

    sprintf(proc, "cfingerd[%d]", pid);
    openlog(proc, LOG_NDELAY, LOG_DAEMON);

    clear_cfingerd_vars();
}

/*
 * SET_TIME_FORMAT
 *
 * Sets two format strings for time formatting, according to clock24.
 */
void set_time_format()
{
    if (((local_finger) && (prog_config.local_config_bits3 & SHOW_CLOCK24)) ||
	((!local_finger) && (prog_config.config_bits3 & SHOW_CLOCK24))) {
	strncpy (prog_config.stime_format, "%H:%M (%Z)", sizeof (prog_config.stime_format));
	strncpy (prog_config.ltime_format, "%a %b %d %H:%M (%Z)", sizeof (prog_config.ltime_format));
    } else {
	strncpy (prog_config.stime_format, "%I:%M %p (%Z)", sizeof (prog_config.stime_format));
	strncpy (prog_config.ltime_format, "%a %b %d %I:%M %p (%Z)", sizeof (prog_config.ltime_format));
    }
}

/*
 * CHECK_STATS
 *
 * This program checks statistics on the program to make sure the settings
 * on the program are correct.
 */
void check_stats()
{
    if (!emulated) {
	if (isatty(0)) {
	    syslog(LOG_ERR, "cfingerd run from command line/daemon");
	    CF_ERROR(E_INETD);
	}
    }
}

/*
 * PARSE_LINE
 *
 * This routine simply parses a string given to the routine, and displays
 * information accordingly.
 */
#define DISPLEN	160
void parse_line(uid_t uid, gid_t gid, char *line)
{
    int pos = 0, newpos = 0, done = FALSE, center_flag = FALSE, exec_line = FALSE;
    char command[80], *displine;
    char *buf;

    displine = (char *) malloc(DISPLEN);
    memset(displine, 0, DISPLEN);

    while(pos < strlen(line)) {
	if (line[pos] != '$') {
	    if (!exec_line) {
		if (center_flag) {
		    char allocation[2];

		    memset(allocation, 0, 2);
		    allocation[0] = line[pos];
		    displine = strncat(displine, (char *) allocation, DISPLEN);
		} else
		    printf("%c", line[pos]);
	    }

	    pos++;
	} else {
	    done = FALSE;
	    newpos = 0;

	    memset(command, 0, 80);

	    pos++;

	    while((line[pos] != ' ') && (!done)) {
		command[newpos] = line[pos];

		if (line[pos] == ' ')
		    done = TRUE;

		if (pos >= strlen(line))
		    done = TRUE;

		newpos++;
		pos++;

		if (command[0] == '$') {
		    if (center_flag)
			displine = strncat(displine, "$", DISPLEN);
		    else
			printf("$");

		    done = TRUE;
		}

		if (strlen(command) >= 4) {
		    if ((!strncasecmp(command, "time", 4)) &&
			(strlen(command) == 4)) {
			time_t tim = time(NULL);
			char bleah[80];

			strftime(bleah, 80, prog_config.stime_format,
				localtime(&tim));

			if (center_flag)
			    displine = strncat(displine, (char *) bleah, DISPLEN);
			else
			    printf((char *) bleah);

			done = TRUE;
		    }

		    if ((!strncasecmp(command, "date", 4)) &&
			(strlen(command) == 4)) {
			time_t tim = time(NULL);
			char bleah[80];

			strftime(bleah, 80, "%a %b %d",
				localtime(&tim));

			if (center_flag)
			    displine = strncat(displine, (char *) bleah, DISPLEN);
			else
			    printf((char *) bleah);

			done = TRUE;
		    }

		    if ((!strncasecmp(command, "ident", 5)) &&
			(strlen(command) == 5)) {
			if (center_flag)
			    displine = strncat(displine, ident_user, DISPLEN);
			else
			    printf("%s", ident_user);

			done = TRUE;
		    }

		    if ((!strncasecmp(command, "center", 6)) &&
			(strlen(command) == 6)) {
			center_flag = TRUE;
			done = TRUE;
		    }

		    if ((!strncasecmp(command, "compile_datetime", 16)) &&
			(strlen(command) == 16)) {
			if (center_flag)
			    displine = strncat(displine, (char *) COMPILE_DT, DISPLEN);
			else
			    printf("%s", (char *) COMPILE_DT);

			done = TRUE;
		    }

		    if ((!strncasecmp(command, "version", 7)) &&
			(strlen(command) == 7)) {
			if (center_flag)
			    displine = strncat(displine, (char *) VERSION, DISPLEN);
			else
			    printf("%s", (char *) VERSION);

			done = TRUE;
		    }

		    if ((!strncasecmp(command, "exec", 4)) &&
			(strlen(command) == 4) &&
			(prog_config.config_bits3 & SHOW_EXEC)) {
			char *cp;

			cp=line;
			cp+=6; 	/* forward "$exec " */
			
			exec_line = TRUE;

			if ((buf = safe_exec (uid, gid, cp)) != NULL) {
			    printf ("%s", buf);
			    fflush(stdout);
			    free (buf);
			}
			done = TRUE;
		    }
		}
	    }
	}
    }

    if (center_flag) {
	int center_dist = 40 - (strlen(displine) / 2) - 1;
	int x;

	if (center_dist > 0)
	    for(x = 0; x < center_dist; x++)
		printf(" ");

	printf(displine);

	free(displine);
    }
}

/*
 * DISPLAY_FILE
 *
 * This displays the file specified.
 */
void display_file(uid_t uid, gid_t gid, FILE *file_ent)
{
    char line[128];
    int bsize = sizeof(line);

    if (file_ent) {
	while(fgets(line, bsize, file_ent)) {
	    if (prog_config.config_bits3 & SHOW_PARSING)
		parse_line(uid, gid, line);
	    else
		fputs(line, stdout);
	}

	fflush(stdout);
	fclose(file_ent);
    }
}

/* 
 * SHOW_TOP
 *
 * This shows the top (or header) file.
 */
void show_top(void)
{
    BOOL can_show = FALSE;

    if (local_finger) {
	if (prog_config.local_config_bits1 & SHOW_TOP)
	    can_show = TRUE;
    } else {
	if (prog_config.config_bits1 & SHOW_TOP)
	    can_show = TRUE;
    }

    if (can_show)
	display_file(NOBODY_UID, NOBODY_GID, top_display);
}

/*
 * CHECK_EXIST
 *
 * Check whether a username exists.
 */
BOOL check_exist(char *username)
{
    struct passwd *pwent;

    pwent = getpwnam(username);
    return((pwent != NULL) ? TRUE : FALSE);
}

/*
 * CHECK_EXIST_ALIAS
 *
 * Check whether a Qmail user alias exists.
 */
BOOL check_exist_alias(char *username)
{
    struct passwd *pwent;
    char acctname[100];

    strncpy(acctname, username, 100);
    acctname[sizeof(acctname) - 1] = '\0';
    strtok(acctname, "-");

    pwent = getpwnam(acctname);
    if(pwent)
    {
        char qmfile[1024];
        snprintf(qmfile, 1024, "%s/.qmail-%s", pwent->pw_dir
                , &acctname[strlen(acctname) + 1]);
        return exist(qmfile);
    }
    return FALSE;
}

/*
 * SHOW_NOTEXIST
 *
 * Show the nouser banner if the user doesn't exist.
 */
void show_notexist(void)
{
    BOOL can_show = FALSE;

    if (local_finger) {
	if (prog_config.local_config_bits2 & SHOW_NOUSER)
	    can_show = TRUE;
    } else {
	if (prog_config.config_bits2 & SHOW_NOUSER)
	    can_show = TRUE;
    }

    if (can_show)
	display_file(NOBODY_UID, NOBODY_GID, nouser_display);
}

/* 
 * SHOW_BOTTOM
 *
 * Show the bottom (or footer) file.
 */
void show_bottom(void)
{
    BOOL can_show = FALSE;

    if (local_finger) {
	if (prog_config.local_config_bits1 & SHOW_BOTTOM)
	    can_show = TRUE;
    } else {
	if (prog_config.config_bits1 & SHOW_BOTTOM)
	    can_show = TRUE;
    }

    if (can_show)
	display_file(NOBODY_UID, NOBODY_GID, bottom_display);
}

/*
 * INETTOS
 *
 * Change INET address string to the "dot" ordered pair.
 */
char *inettos(long addr)
{
    char *ret;
    int pair1, pair2, pair3, pair4;

    ret = (char *) malloc(20);

    pair1 = (addr & 0x000000FF);
    pair2 = (addr & 0x0000FF00) >> 8;
    pair3 = (addr & 0x00FF0000) >> 16;
    pair4 = (addr & 0xFF000000) >> 24;

    sprintf(ret, "%d.%d.%d.%d", pair1, pair2, pair3, pair4);
    return(ret);
}

/*
 * GET_LOCALHOST
 *
 * Get the name of the local host computer.
 */
char *get_localhost(void)
{
    char *ret;
    char hostname[80], domname[80];

    gethostname((char *) hostname, (size_t) 80);
    getdomainname((char *) domname, (size_t) 80);

    ret = (char *) malloc(strlen((char *) hostname) +
			  strlen((char *) domname) + 2);

    snprintf(ret, sizeof(ret), "%s.%s", (char *) hostname, (char *) domname);
    return(ret);
}

/*
 * CHECK_UNKNOWN
 *
 * This routine checks if the ident username is unknown, and processes the
 * data as it should normally.
 */
void check_unknown(char *host)
{
    if ((!(prog_config.config_bits2 & SHOW_NOBODY1413)) &&
	!strncmp(ident_user,"unknown@", 8)) {
	syslog(LOG_NOTICE, "Finger from %s %s",
	    host, prog_config.p_strings[D_REJECT_HOST]);
	if (prog_config.config_bits2 & SHOW_REJECTED)
	    display_file(NOBODY_UID, NOBODY_GID, rejected_display);
	log(LOG_IDENT, "Unknown not allowed from ", host);

	exit(PROGRAM_OKAY);
    }
}

/*
 * CHECK_TRUSTED
 *
 * This routine returns a TRUE or FALSE, depending on if the passed host is
 * in the trusted host array.
 *
 * Additional: This module now checks against the ident username.  If it's
 * unknown, and unknown ident fingers are NOT allowed, it rejects the
 * connection.
 */
BOOL check_trusted(char *host)
{
    int i;

    check_unknown(host);

    for (i = 0; i < trusted_host_num; i++)
	if ((wildmat(host, prog_config.trusted[i])) ||
	    (wildmat(ip_address, prog_config.trusted[i]))) {
	    syslog(LOG_NOTICE, "Finger from %s %s",
		host, prog_config.p_strings[D_TRUST_HOST]);
	    return(TRUE);
	}

    return(FALSE);
}

/*
 * CHECK_REJECTED
 *
 * This routine simply checks whether or not the listed host is part of the
 * list of rejected hosts.  If it's matched, it displays the rejected list
 * file and quits completely.
 */
void check_rejected(char *host)
{
    int i;

    check_unknown(host);

    for (i = 0; i < rejected_host_num; i++)
	if ((wildmat(host, prog_config.rejected[i])) ||
	    (wildmat(ip_address, prog_config.rejected[i]))) {
	    syslog(LOG_NOTICE, "Finger from %s %s",
		host, prog_config.p_strings[D_REJECT_HOST]);
	    if (prog_config.config_bits2 & SHOW_REJECTED)
		display_file(NOBODY_UID, NOBODY_GID, rejected_display);

	    log(LOG_REJECTED, "Rejected host finger detected to ", host);

	    exit(PROGRAM_OKAY);
	}
}

/*
 * SAFE_EXEC
 *
 * This is a safe version of the "system", "exec" or "popen" command.  This
 * simply takes on the UID/GID of the NOBODY (or apparent NOBODY UID/GID),
 * and runs the specified program as that user.  If it ever fails, it will
 * display in syslog the reason WHY it failed, along with a semi-detailed
 * message of the failure.
 *
 *
 * After an exec() system call, the saved uid is lost. Therefore, a child
 * program cannot use it to regain suid root permissions.
 *
 * W. Richard Stevens, Advanced Programming in the Unix Environment, page
 * 213: "The saved set-user-ID is copied from the effective user ID by exec."
 *
 */
char *safe_exec (uid_t uid, gid_t gid, char *cmd)
{
    FILE *file;
    char line[600];
    int fd[2];
    int pid;
    char *result = NULL;
    unsigned long size = 0;

    if (pipe (fd) != 0) {
	syslog(LOG_ERR, "pipe() failed, %s.", strerror(errno));
	return NULL;
    }

    if ((pid = fork()) < 0) {
	syslog(LOG_ERR, "fork() failed, %s.", strerror(errno));
	return NULL;
    }

    if (pid > 0) {	/* father */
	close (fd[1]);	/* for writing */

	if ((result = (char *) malloc (1)) == NULL)
	    return NULL;
	*result = '\0';
	memset (line, 0, 600);
	while ((read (fd[0], line, sizeof (line)-1)) > 0) {
	    if ((size += strlen (line)) < MAX_POPEN_BUF) {
		syslog (LOG_INFO, "Reallocating %d bytes", size);
		if ((result = (char *) realloc (result, size+1)) == NULL) {
		    return NULL;
		}
		strcat (result, line);
		memset (line, 0, 600);
	    } else {	/* buffer limit reached */
		close (fd[0]);
		return result;
	    }
	}
    } else {		/* child */
	close (fd[0]);	/* for reading */
	DROP_ALL_PRIVS(uid, gid);
	file = popen(cmd, "r");

	if (file) {
	    while (!feof (file)) {
		memset (line, 0, 600);
		fgets (line, 600, file);
		write (fd[1], line, strlen (line));
	    }
	} else {
	    close (fd[0]);
	    close (fd[1]);
	    syslog(LOG_ERR, "safe_exec failed on %s: %s", cmd, strerror(errno));
	}
	close (fd[1]);
	exit (0);
    }

    if (!strlen (result)) {
	free (result);
	result = NULL;
    }
    return result;
}

/*
 * OPEN_FILE
 *
 * Quick way to open files and check for errors at the same time.
 */
FILE *open_file(char *filename)
{
    if (check_illegal (filename, "CFINGERD"))
	return (fopen (filename, "r"));
    return NULL;
}

/*
 * OPEN_INITIAL_FILES
 *
 * This safely opens all files that are specified at startup.  This allows
 * the files to be read, after they have been opened with root access.
 */
void open_initial_files(void)
{
    top_display = open_file (prog_config.top_display_file);
    bottom_display = open_file (prog_config.bottom_display_file);
    noname_display = open_file (prog_config.no_name_banner_file);
    nouser_display = open_file (prog_config.no_user_banner_file);
    rejected_display = open_file (prog_config.rejected_file);
}

/*
 * Local variables:
 *  c-indent-level: 4
 *  c-basic-offset: 4
 *  tab-width: 8
 * End:
 */
