/*
 * CNS/fingerd - 2002
 * cns@minithins.net
 *
 *
 * Using: -i to run in inetd, standalone mode by default
 *
 * files:
 * ~/.nofinger to disable finger
 * ~/.noutmp to show you're not logged in
 * 
 * ~/.project - .project file
 * ~/.plan - .plan file
 * ~/.pgpkey - pgp key
 * ~/.email - emails 
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <pwd.h>
#include <signal.h>
#include <utmp.h>
#include <unistd.h>
#include <fcntl.h>
#include <syslog.h>
#include <sys/stat.h>

#define DATASIZE 1024

int sfd;

void fin (int signal)
{
  close (sfd);
  closelog ();
  syslog(LOG_INFO, "End of fingerd by signal %i.\n", signal);
  exit (EXIT_SUCCESS);
}

int allow_finger (char *name)
{
  int lenght_file, fd;
  struct stat stat_buf;
  char *bigbuffer;

  if (stat("/etc/finger", &stat_buf))
    return 1;
  if ((fd = open("/etc/finger", O_RDONLY)) <= 0)
    return 0;

  lenght_file = stat_buf.st_size;
  if (!(bigbuffer = (char*)malloc(lenght_file * sizeof(char))))
    goto end; 

  if (read (fd, bigbuffer, lenght_file) != lenght_file)
    goto end2;

  if (strstr(bigbuffer, name) != NULL)
    return 1;

end2:
  if (bigbuffer)
    free (bigbuffer);

end:
  close (fd);
  return 0;
}

void dump_file (int sock, char *name, char *file)
{
  char buffdata[DATASIZE];
  int ffd, i;

  sprintf (buffdata, "%s/%s", name, file);
  ffd = open (buffdata, O_RDONLY);
  if (ffd != -1)
  {
    sprintf (buffdata, "%s file:\n", file);
    write (sock, buffdata, strlen(buffdata));
    while ((i = read (ffd, buffdata, DATASIZE)) > 0)
      write (sock, buffdata, i);

    sprintf (buffdata, "\n");
    write (sock, buffdata, strlen(buffdata));

    close (ffd);
  } else {
    sprintf (buffdata, "No %s file.\n", file);
    write (sock, buffdata, strlen(buffdata));
  }

  return ;
}

int gere_data (int sock, char *buffer, int len)
{
  struct passwd *ent;
  char buffdata[DATASIZE];
  int ffd, i;
  struct utmp temp;

  sprintf (buffdata, "\n  CNS/minithins - fingerd - 20020520\n");
  write (sock, buffdata, strlen(buffdata));
  sprintf (buffdata, "  Informations by email: cns@minithins.net\n\n\n");
  write (sock, buffdata, strlen(buffdata));

  if (strlen (buffer) == 0)
  {
    sprintf (buffdata, "Use finger <login>@minithins.net\n");
    write (sock, buffdata, strlen(buffdata));
    return (EXIT_FAILURE);
  }

  if (len < 0 || len > 16 || !buffer 
    || !(ent = getpwnam (buffer)) || !allow_finger(buffer))
  {
    sprintf (buffdata, "User %s doesn't exists.\n", buffer);
    write (sock, buffdata, strlen(buffdata));
    return (EXIT_FAILURE);
  }

  setuid (ent->pw_uid); setgid (ent->pw_gid);
  seteuid (ent->pw_uid); seteuid (ent->pw_gid);
  setreuid (ent->pw_uid, ent->pw_uid); setregid (ent->pw_gid, ent->pw_gid);

  sprintf (buffdata, "%s/.nofinger", ent->pw_dir);
  if (access (buffdata, F_OK) == 0)
  {
    sprintf (buffdata, "User %s doesn't exists.\n", buffer);
    write (sock, buffdata, strlen(buffdata));
    return (EXIT_SUCCESS);
  }

  sprintf (buffdata, "User: %-16s   Name/Mail: %-40s\n\n", 
    ent->pw_name, ent->pw_gecos);
  write (sock, buffdata, strlen(buffdata));

  sprintf (buffdata, "%s/.noutmp", ent->pw_dir);

  ffd = open ("/var/run/utmp", O_RDONLY);
  if (ffd != -1)
  {
    i = 0;
    if (!(access (buffdata, F_OK) == 0))
    {
      while (read (ffd, &temp, sizeof(struct utmp)) == sizeof(struct utmp))
      {
        if (!strncmp(temp.ut_user, ent->pw_name, 16) 
          && temp.ut_type == USER_PROCESS)
        {
          sprintf (buffdata, "- %s %5i (%-6s) logged in at %s", 
            temp.ut_user, temp.ut_pid, temp.ut_line, 
          ctime((const time_t *)&temp.ut_tv));
          write (sock, buffdata, strlen(buffdata));
          i++;
        }
      }
    }
    if (!i)
    {
      sprintf (buffdata, "User is not logged in.\n");
      write (sock, buffdata, strlen(buffdata));
    }
    sprintf (buffdata, "\n");
    write (sock, buffdata, strlen(buffdata));
    close (ffd);
  }

  dump_file (sock, ent->pw_dir, ".project");
  dump_file (sock, ent->pw_dir, ".plan");
  dump_file (sock, ent->pw_dir, ".pgpkey");
  dump_file (sock, ent->pw_dir, ".email");

  return (EXIT_SUCCESS); 
}

int main (int argc, char **argv)
{
  int cfs, i, j, arg;
  struct sockaddr_in list;
  struct sockaddr_in tmp;
  int mode_inetd = 0;
  int len = sizeof(struct sockaddr_in);
  char main_buffer[32];
  struct sigaction myend;

  myend.sa_handler = (void*)fin;
  myend.sa_flags = 0 ;
  sigaction(SIGINT, &myend, NULL);

  openlog("cns/fingerd", LOG_PID, LOG_DAEMON);
  syslog(LOG_INFO, "Start of fingerd.");

  while ((arg = getopt(argc, argv, "i")) != EOF)
  {
   switch (arg)
   {
     case 'i':
       mode_inetd = 1;
     break;
   }
  }

  bzero (main_buffer, 32);

  if (mode_inetd)
  {
    getpeername (0, (struct sockaddr*)&tmp, &len);

    i = read (0, main_buffer, 16);

    for (j = 0; j < i; j ++)
      if (main_buffer[j] == '\n' || main_buffer[j] == '\r'
           || !isgraph(main_buffer[j]))
      {
        main_buffer [j] = 0;
        break;
      }

    syslog (LOG_NOTICE, "Request [%s] from %s.",
      main_buffer, inet_ntoa (tmp.sin_addr));
    gere_data (1, main_buffer, i); 
  }
  else
  {
    sfd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);

    list.sin_family = AF_INET;
    list.sin_port = htons (79);
    list.sin_addr.s_addr = INADDR_ANY;

    if (bind (sfd, (struct sockaddr*)&list, sizeof(struct sockaddr_in)))
      perror("bind"), exit (1);

    if (listen (sfd, 64))
      perror ("listen"), exit (1);

    while (1)
    {
      if ((cfs = accept (sfd, (struct sockaddr*)&list, &len)) != -1)
      {
        i = read (cfs, main_buffer, 16);
        getpeername (cfs, (struct sockaddr*)&tmp, &len);

        for (j = 0; j < i; j ++)
          if (main_buffer[j] == '\n' || main_buffer[j] == '\r' 
               || !isgraph(main_buffer[j]))
          {
            main_buffer [j] = 0;
            break;
          }

        syslog (LOG_NOTICE, "Request [%s] from %s.", 
          main_buffer, inet_ntoa (tmp.sin_addr));
        gere_data (cfs, main_buffer, i);
        close (cfs);
      }
    }
  }

  syslog(LOG_INFO, "End of fingerd.\n");
  closelog ();
  close (sfd);
  return 0;
}
