/* PIPServer - A deamon for finger protocol v2
 *
 * Copyright (C) 1999 Michael Baumer <baumi@vis.ethz.ch>
 *
 * 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.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

/* $Id: finger1.c,v 1.36 2001/07/06 05:58:38 baumi Exp $
 *
 * Handle the anwser if the incomming request was sent in the 
 * finger protocol version 1.
 *
 * This is not as fancy as in GNU Finger v1.x for example, but keep in mind
 * that most important info is accessible by a local user only, who probably 
 * has the new protocol anyway
 *
 * The pinciple is:
 * 1. Create login_info for all poeple online form utmp, gnufinger hostdata
 * or db. 
 * 2a. If the user is online print it else look the user up in the passwd
 * file or in the db.
 * 2b. (Long Output) Do special lookup for information to be printed.
 */

#include <config.h>

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif /* HAVE_STRINGS_H */
#include <stdlib.h>
#include <ctype.h>
#include <pwd.h>
#include <sys/types.h>

#include "in.fingerd.h"
#include "local_user.h"
#include "readutmp.h"
#include "file.h"
#include "finger.h"
#include "parser.h"
#include "mail.h"
#include "configfile.h"
#include "acl.h"

/* prefix for inter-daemon calls made by GNU Finger */
#define GNU_INFINGERD_FLAG  "FiNgEr"

void finger1_loginline(login_info *linfo);

char header_string[126];
int fromhostlen;

/* print the resulting info 
 * - in "long" format
 * - Offline information
 *
 * Note that the .plan (and similar) files are returned in another form
 * in version 2 of the finger protocol
 */

void finger1_show(int longflag, finger_userinfo *pwd, login_info *linfo)
{
  char *filename;
  login_info *current = linfo;
  Node *node = NULL;
  Node *cur_node;
  time_t access_time;

  /* Seach for matching online information */
  while (current) {
    
    /* Check login name */
    if (!strncmp(current->login_user, pwd->pw_name, UT_NAMESIZE-1)) {
      break;
    }
    /* Check Real name */
    current = current->next;
  }
  
  if (!longflag) {
    if (!current)
      fprintf(stdout, "%-8s %-20s\tOffline \t< . . . >\r\n", 
	      pwd->pw_name, 
	      pwd->pw_realname);
    else
      finger1_loginline(current);
  }
  else {

    /* Long output requested */

    /* Login name and real name */
    fprintf(stdout, "Login Name: %-8s\t\t\tIn real life: %s\r\n",
	    pwd->pw_name,
	    pwd->pw_realname);
    
    /* Try to open .fingerconf */
    filename = findUserFile(pwd, "fingerconf");
    if (filename && (node = readFingerconf(filename))) {
      
      /* Output everything */
      cur_node = node->child;
      while (cur_node) {
	if (!strcmp(cur_node->id, "userinfo")) {
	  cur_node = cur_node->child;
	  while(cur_node) {
	    /* Handle specials */
	    if (strcmp(cur_node->id, "first") && 
		strcmp(cur_node->id, "last")) {
	      /* Make it simple */

	      /* this is probably a hack */
	      cur_node->id[0] = toupper(cur_node->id[0]);

	      if (!*cur_node->value)
		printf("*%s*\r\n", cur_node->id);
	      else {
		if (index(cur_node->value, '\n'))
		  printf("%-10s:\r\n%s\r\n", cur_node->id, cur_node->value);
		else
		  printf("%-10s: %s\r\n", cur_node->id, cur_node->value);
	      }
	    }
	    if (cur_node->child) 
	      cur_node = cur_node->child;
	    else {
	      while (!cur_node->next) {
		if (!cur_node->parent)
		  break;
		cur_node = cur_node->parent;
	      }
	      if (!strcmp(cur_node->id, "userinfo"))
		cur_node = NULL;
	      else
		cur_node = cur_node->next;
	    }
	  }
	} else
	  cur_node = cur_node->next;
      }

      /* free parse tree */
      FreeParseTree(node);
    }
    else {

      /* No .fingerconf, use GECOS fields instead */

      /* Office and Office phone */
      if (pwd->office) {
	if (pwd->office_phone)
	  fprintf(stdout, "Office: %s, %s\r\n", 
		  pwd->office, pwd->office_phone);
	else 
	  fprintf(stdout, "Office: %s\r\n", pwd->office);
      }
      else 
      if (pwd->office_phone)
	fprintf(stdout, "Office phone: %s\r\n", pwd->office_phone);
      
      /* Home phone */
      if (pwd->home_phone)
	fprintf(stdout, "Home phone: %s\r\n", pwd->home_phone);
    }

    /* Print login information */
    if (current) {
      fprintf(stdout, header_string);
      finger1_loginline(current);
    } 
    else {
      fprintf(stdout, "%s (%s) is not currently logged in\r\n",
	      pwd->pw_realname,
	      pwd->pw_name);
    }
  
    if (config.acl->options & ACL_MAIL) {
      
      /* Print users mail status
       * Missing: - Mail forwarding
       *          - security
       */
      switch (checkmail(pwd, &access_time)) {
      case NO_MAIL:
	fprintf(stdout, "No mail.\r\n");
	break;
      case UNREAD_MAIL:
	fprintf(stdout, "Unread mail since %.24s.\r\n", ctime(&access_time));
	break;
      case NEW_MAIL:
	fprintf(stdout, "New mail since %.24s.\r\n", ctime(&access_time));
	break;
      case READ_MAIL:
	fprintf(stdout, "No new mail.\r\n");
	break;
      }
    }

    /* Print if the user has a .key  or even a .pgpkey file 
     * The difference is that .key is generic and .pgpkey is clear.
     */
    if (findUserFile(pwd, "key"))
      fprintf(stdout, 
	      "Public key available: Use \"finger %s.key\" to get it.\r\n",
	      pwd->pw_name);
    if (findUserFile(pwd, "pgpkey"))
      fprintf(stdout, 
	      "PGP public key available: Use \"finger %s.pgpkey\" "
	      "to get it.\r\n",
	      pwd->pw_name);
    
    /* printing of the users .project file is not done yet.
     * It will only be included if someone really wants it.
     * Since the project file is only a single line this should
     * _really_ go in a more general rc-file.
     */
    
    /* print .plan file 
     *
     * Note: GNU Finger 1.x allows the plan file to be parsed by cpp
     * for replacing some strings (e.g. Date, Time).
     * I don't think that is absolutely necessary and is non-standard.
     */
  
    if ((filename = findUserFile(pwd, "plan"))) {
      /* send the file */
      fprintf (stdout, "Plan:\r\n");
      print_file(filename, 0);
    }
    else  
      if (!node)
	/* Only say that if we hav no fingerconf */
	fprintf(stdout, "No plan.\r\n");
  
    /* exec .fingerc: Not implemented */

    /* make an empty line */
    fprintf(stdout, "\n");
  }
}

/* Handle target on user */
void target1(finger_userinfo *pwd, char *target) 
{
  char *filename;
  int i = 0;

  while(config.validfile[i] && strcmp(config.validfile[i], target))
    i++;

  if (config.validfile[i]) {
     
    filename = findUserFile(pwd, config.validfile[i]);
    
    if (filename)
      /* send the file */
      print_file(filename, 0);
  }
}

/* answer to a finger request
 *
 * 1. match online login, if found print output and exit
 * 2. match login, if found print output and exit
 * 3. match remote logins, if found print output and exit
 * 4. match local users and their realnames
 * 5. match remote users and their realnames
 *
 * That way we are able to provide on/offline information for matching
 * Users and are able to search for non-matching users.
 * The problem is that we can't provide a useful search that way, if the login 
 * name exists. Abbreviations must be used in that case.
 */

void finger_reply(int longflag, char *username, login_info *linfo)
{
  finger_userinfo *pwd;
  char *target;
     
  /* special targets */
  target = index(username, '.');
  if (target) {
    *target='\0';
    target++;
    if (!*target)
      target = NULL;
  }
  else
    if (!longflag) 
      fprintf(stdout, header_string);

  /* search for local users by login name */
  pwd = pwd_search_login(username, 1);
  if (pwd) {

    if (!target)
      finger1_show(longflag, pwd, linfo);
    else
      target1(pwd, target);

    return;
  }
  
  /* search for users on other hosts by login name*/
  /*  db_search(username); */
  
  /* search for local users by real name */
  
  /* rewind passwd file */
  setpwent();
  while ((pwd = pwd_search_realname(username)))
    if (!target)
      finger1_show(longflag, pwd, linfo); 
    else
      target1(pwd, target);
}

/* Print one line for a login
 */
void finger1_loginline(login_info *linfo)
{
  char timestr[20];
  char idlestr[20];
  char *idx;

  if (time(0) - linfo->login_time > 30758400) 
	/* more than a year ago (approx.) */
	strftime(timestr, 20, "%d %b %y", localtime(&linfo->login_time));
    else
      if (time(0) - linfo->login_time > 604800)
	/* more than a week ago */
	strftime(timestr, 20, "%a %d %b", localtime(&linfo->login_time));
      else
	strftime(timestr, 20, "%a %H:%M", localtime(&linfo->login_time));
  linfo->hostname[9]='\0';
  if ((idx = index(linfo->hostname, '.')))
    *idx = '\0';
  if (!(config.acl->options & ACL_IDLETIME))
    fprintf(stdout, "%-8s %-20s\t%-6s  %-10s %-10s", 
	    linfo->login_user, 
	    linfo->realname,
	    linfo->status,
	    timestr,
	    linfo->hostname);
  else {
    if (linfo->idle_time < 3600)
      sprintf(idlestr, "%d:%02dm", (linfo->idle_time / 60),
	       (linfo->idle_time % 60));
    else if (linfo->idle_time < 24*3600)
      sprintf(idlestr, "%d:%02dh", (linfo->idle_time / 3600),
	       ((linfo->idle_time % 3600) / 60));
    else 
      sprintf(idlestr, "%ddays", (linfo->idle_time / (24*3600)));

    fprintf(stdout, "%-8s %-20s\t%-6s %6s %-10s %-10s", 
	    linfo->login_user, 
	    linfo->realname,
	    linfo->status,
	    idlestr,
	    timestr,
	    linfo->hostname);
  }
  if (config.acl->options & ACL_FROMHOST) {
    linfo->fromhost[fromhostlen] = '\0';
    printf("%s\r\n", linfo->fromhost);
  }
  else
    printf("\r\n");
}


/* Handle one line of finger protocol v1 input
 * (called recursively for each user)
 *
 * - Read utmp/gnufinger hostdata/db for online users
 *
 * NOTE: we do not provide tty information:
 *       - it is not necessary (even if you want to use talk)
 *       - it is senseless in a distributed environment
 */

void finger1(char *input)
{
  char *username;
  static int longflag = FALSE;
  static login_info *linfo = NULL;

  /* skip trailing white space */
  while (*input && isspace(*input))
    input++;

  /* Read online information once */
  if (!linfo)
    linfo = read_utmp(1);

  /* empty line */
  if (!*input) {

    /* only do that if we are allowed to */
    if (config.acl->options & ACL_USERLIST) {

      /* Print a nice title */
      fprintf(stdout, header_string);
      
      /* Output the online information */
      while(linfo) {
	finger1_loginline(linfo);
	linfo = linfo->next;
      }
    }
    else {
      fprintf(stdout, "Sorry, the request-type 'finger @host' is disabled.\r\n"
	      "Try finger .help for a list of available commands.\r\n");
    }
    return;
  }

  if ((input[0]=='.') || (input[0]=='/') || (input[0]=='-')) {
    if (toupper(input[1])=='W') {
      /* somebody asked for the "longflag" format */
      
      /* skip trailing white space */
      input += 2;
      while (*input && isspace(*input))
	input++;

      longflag = TRUE;
    }
#if 0
    /* not used at the moment */
    else if (!strncmp(&input[1], GNU_INFINGERD_FLAG, 
		      strlen(GNU_INFINGERD_FLAG))) { 

      /* go to next token */
      input += strlen(GNU_INFINGERD_FLAG) + 1;
      while (*input && isspace(*input))
	input++;
    }
#endif
    else if (!strncmp(&input[1], "all", 3)) {
      
      finger1("");
      
      /* go to next token */
      input += 4;
      while (*input && isspace(*input))
	input++;
    }
    else if (!strncmp(&input[1], "help", 4)) {
      
      /* Print some basic usage information 
       * This is the default text. It will only be printed if
       * not overwritten.
       */
      
      if (!config.help) {
	fprintf(stdout, "\t\tThis is the finger service\r\n\r\n");
      
	fprintf(stdout, "Normally you just want to do a\r\n\r\n"
		"\t\"finger username\"\tfor a short information\r\n"
		"or"
		"\t\"finger -l username\"\tfor a more detailed information"
		"\r\n\r\n"
		"If the user has a public key or a PGP public key you can use"
		"\r\n\r\n"
		"\tfinger username.key \r\nor\tfinger username.pgpkey\t"
		"to get it.\r\n\r\n");

	fprintf(stdout, "This PFinger server "
		"accepts several special user names.\r\n\r\n"
		"\t.version\tReturn server version\r\n"
		"%s"
		"\t.help\t\tThis message\r\n\r\n",
		(config.siteinfo) ? 
		"\t.site\t\tList site information\r\n" : "");
      }
      else {
	/* todo: make correct CR/LF line endings */
	fprintf(stdout, "%s\r\n", config.help);
      }
	
      /* go to next token */
      input += 5;
      while (*input && isspace(*input))
	input++;
    }
    else if (!strncmp(&input[1], "version", 7)) {
      
      /* Print out version */

      fprintf(stdout, "PFinger version %s\r\n", VERSION);
      
      /* go to next token */
      input += 8;
      while (*input && isspace(*input))
	input++;
    }    
    else if ((config.siteinfo) && (!strncmp(&input[1], "site", 4))) {
      
      /* todo: make correct CR/LF line endings */
      fprintf(stdout, "%s\r\n", config.siteinfo + 1);
      
      /* go to next token */
      input += 5;
      while (*input && isspace(*input))
	input++;
    }
    else {
      /* unknown option - skip */
      
      /* skip trailing option */
      input++;
      while (*input && !isspace(*input))
	input++;
      
      /* skip trailing white space */
      while (*input && isspace(*input))
	input++;
    }
  }
  else {
    /* string should be "plain" username */
    
    username = input;
    
    /* search for end of username */
    while(*input && !isspace(*input)) {
      input++;
    }
    
    /* we have reached the end of line */
    if (username == input)
      return;
    
    if (*input) {
      *input = '\0';
      finger_reply(longflag, username, linfo);
      
      /* make shure we do not call ourself with an empty line */
      input++;
      while (*input && isspace(*input))
	input++;
    }
    else
      finger_reply(longflag, username, linfo);
  }
  if (*input)
    finger1(input);
}

/* init_finger1
 *
 * Create the various format strings
 */
void init_finger1()
{
  strcpy (header_string, "Login\t Name\t\t\tStatus ");
  if (config.acl->options & ACL_IDLETIME)
    strcat(header_string, "Idle  ");
  strcat(header_string, " Login time Host");
  if (config.acl->options & ACL_FROMHOST)
    strcat(header_string, "      From\r\n");
  else
    strcat(header_string, "\r\n");

  fromhostlen = 68-strlen(header_string);
}



syntax highlighted by Code2HTML, v. 0.9.1