/* finger.c
 * Nicholas Ingolia
 * ingolia@mit.edu
 */

#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

#include "errors.h"
#include "finger.h"
#include "sendz.h"

#if 0
typedef unsigned long in_addr_t;
#endif

enum {
  errmsglen = 1024,
  buflen = 1024
};

static int finger_sockaddr_from_hostname(const char *hostname, 
					 struct sockaddr_in *saddr);
char **finger_users_from_reply(const char *buf);
char **finger_users_empty_reply(void);
void finger_users_report(const char *hostname, char **users);

char **finger_users(const char *hostname)
{
  struct servent    *fingersvc;
  struct sockaddr_in saddr;
  int                s, bufread;
  char               errmsg[errmsglen], buf[buflen];
  char             **users;

  if (finger_sockaddr_from_hostname(hostname, &saddr) < 0) {
    return finger_users_empty_reply();
  }

  fingersvc = getservbyname("finger", "tcp");
  if (fingersvc == NULL) {
    error("finger: Unknown service \"tcp/finger\"\n");
    return finger_users_empty_reply();
  }

  saddr.sin_port = fingersvc->s_port;

  s = socket(saddr.sin_family, SOCK_STREAM, 0);
  if (s < 0) {
    snprintf(errmsg, errmsglen, "finger %s: socket(): %s (%d)\n", 
	     hostname, strerror(errno), errno);
    sendz_send_zephyr_debugging(errmsg);
    error(errmsg);
    return finger_users_empty_reply();
  }

  if (connect(s, (struct sockaddr *) &saddr, sizeof(saddr)) < 0) {
    snprintf(errmsg, errmsglen, "finger %s: connect(): %s (%d)\n", 
	     hostname, strerror(errno), errno);
    sendz_send_zephyr_debugging(errmsg);
    error(errmsg);
    close(s);
    return finger_users_empty_reply();
  }

  if (write(s, "\r\n", 2) < 2) {
    snprintf(errmsg, errmsglen, "finger %s: write(): %s (%d)\n", 
	     hostname, strerror(errno), errno);
    sendz_send_zephyr_debugging(errmsg);
    error(errmsg);
    close(s);
    return finger_users_empty_reply();
  }

  if ((bufread = read(s, buf, buflen - 1)) < 1) {
    snprintf(errmsg, errmsglen, "finger %s: read(): %s (%d)\n", 
	     hostname, strerror(errno), errno);
    sendz_send_zephyr_debugging(errmsg);
    error(errmsg);
    close(s);
    return finger_users_empty_reply();
  }
  buf[bufread] = '\0';

  close(s);

  users = finger_users_from_reply(buf);

  finger_users_report(hostname, users);

  return users;
}

static int finger_sockaddr_from_hostname(const char *hostname, 
					 struct sockaddr_in *saddr)
{
  in_addr_t       addr;
  struct hostent *host;
  char            errmsg[errmsglen];

  addr = inet_addr(hostname);
  if (addr == (in_addr_t) -1) {
    host = gethostbyname(hostname);
    if (host == NULL) {
      snprintf(errmsg, errmsglen, "finger: Unknown host \"%s\"\n", hostname);
      sendz_send_zephyr_debugging(errmsg);
      error(errmsg);
      return 0;
    }
    
    saddr->sin_family = host->h_addrtype;
    memcpy(&(saddr->sin_addr), host->h_addr, host->h_length);
  } else {
    saddr->sin_family = AF_INET;
    memcpy(&(saddr->sin_addr), &addr, sizeof(in_addr_t));
  }

  return 1;
}

#define NO_ONE "No one logged on"
#define SOMEONE "Login"
#define MAX_USER 10

char **finger_users_from_reply(const char *buf)
{
  char       *user, **users = calloc(MAX_USER, sizeof(char *));
  const char *c, *d;
  int         userno, i, duplicate;

  if (!strncmp(buf, NO_ONE, strlen(NO_ONE))) {
    return users;
  } else if (strncmp(buf, SOMEONE, strlen(SOMEONE))) {
    sendz_send_zephyr_debugging("finger: Received anomolous finger reply \'%s\'\n", buf);
    return users;
  }

  for (c = buf; *c != '\0' && *c != '\n'; c++)
    ;

  if (*c == '\n') {
    c++;
  }

  userno = 0;

  while (*c != '\0' && userno < MAX_USER) {
    for (d = c; !isspace(*d); d++)
      ;

    user = calloc(1 + d - c, sizeof(char));
    strncpy(user, c, d - c);

    for (duplicate = 0, i = 0; i < userno; i++) {
      if (!strcmp(user, users[i])) {
	duplicate++;
      }
    }

    if (duplicate) {
      free(user); 
    } else {
      users[userno] = user;
      userno++;
    }

    for (; *c != '\n' && *c != '\0'; c++)
      ;

    if (*c == '\n') {
      c++;
    }
  }

  return users;
}

char **finger_users_empty_reply(void)
{
  char **users = malloc(sizeof(char *));

  users[0] = NULL;

  return users;
}

void finger_users_report(const char *hostname, char **users)
{
  int   i, listlen;
  char *list;

  listlen = 1;

  listlen += strlen(hostname) + 2;


  for (i = 0; users[i] != NULL; i++) {
    listlen += strlen(users[i]) + 2; 
  }

  list = calloc(listlen, sizeof(char));

  strcat(list, hostname);
  strcat(list, ": ");

  for (i = 0; users[i] != NULL; i++) {
    if (i > 0) {
      strcat(list, ", ");
    }

    strcat(list, users[i]);
  }

  sendz_send_zephyr_debugging(list);

  free(list);
}

