/* SAMHAIN file system integrity testing                                   */
/* Copyright (C) 2008 Rainer Wichmann                                      */
/*                                                                         */
/*  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., 675 Mass Ave, Cambridge, MA 02139, USA.              */

#include "config_xor.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

#ifdef HAVE_DIRENT_H
#include <dirent.h>
#define NAMLEN(dirent) sl_strlen((dirent)->d_name)
#else
#define dirent direct
#define NAMLEN(dirent) (dirent)->d_namlen
#ifdef HAVE_SYS_NDIR_H
#include <sys/ndir.h>
#endif
#ifdef HAVE_SYS_DIR_H
#include <sys/dir.h>
#endif
#ifdef HAVE_NDIR_H
#include <ndir.h>
#endif
#endif
#define NEED_ADD_DIRENT

#if defined(SH_USE_PORTCHECK) && (defined (SH_WITH_CLIENT) || defined (SH_STANDALONE))

#if defined(__linux__)
 
#include "samhain.h"
#include "sh_error_min.h"
#include "sh_utils.h"
#include "sh_pthread.h"

#define FIL__  _("sh_port2proc.c")

static  size_t  sh_minpid = 0x0001;
static  size_t  sh_maxpid = 0x8000;

#ifndef HAVE_LSTAT
#define lstat(x,y) stat(x,y)
#endif /* HAVE_LSTAT */

#if defined(S_IFLNK) && !defined(S_ISLNK)
#define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK)
#else
#if !defined(S_ISLNK)
#define S_ISLNK(mode) (0)
#endif
#endif

struct sock_store {
  unsigned long sock;
  size_t        pid;
  char *        path;
  char *        user;
  struct sock_store * next;
};

/* /proc: 
 *        linux:     /proc/pid/exe
 *        freebsd:   /proc/pid/file
 *        solaris10: /proc/pid/path/a.out
 */
static void get_user_and_path (struct sock_store * add)
{
  extern char *  sh_unix_getUIDname (int level, uid_t uid, char * out, size_t len);

  char        path[128];
  char *      buf;
  struct stat sbuf;
  int         len;
  char *      tmp;

  sl_snprintf (path, sizeof(path), "/proc/%ld/exe", (unsigned long) add->pid);

  if (0 == retry_lstat(FIL__, __LINE__, path, &sbuf) && S_ISLNK(sbuf.st_mode))
    {
      goto linkread;
    }

  sl_snprintf (path, sizeof(path), "/proc/%ld/file", (unsigned long) add->pid);

  if (0 == retry_lstat(FIL__, __LINE__, path, &sbuf) && S_ISLNK(sbuf.st_mode))
    {
      goto linkread;
    }

  sl_snprintf (path, sizeof(path), "/proc/%ld/path/a.out", (unsigned long) add->pid);

  if (0 == retry_lstat(FIL__, __LINE__, path, &sbuf) && S_ISLNK(sbuf.st_mode))
    {
      goto linkread;
    }

  return;

 linkread:

  buf = SH_ALLOC(PATH_MAX);
  len = readlink(path, buf, PATH_MAX);   /* flawfinder: ignore */
  len = (len >= PATH_MAX) ? (PATH_MAX-1) : len;

  if (len > 0)
    { 
      buf[len] = '\0';
      add->path = buf;
    }
  else
    {
      SH_FREE(buf);
    }

  add->user = SH_ALLOC(USER_MAX);
  tmp  = sh_unix_getUIDname (SH_ERR_ALL, sbuf.st_uid, add->user, USER_MAX);

  if (!tmp)
    sl_snprintf (add->user, USER_MAX, "%ld", (unsigned long) sbuf.st_uid);

  return;
}

#if defined(__linux__)
#define PROC_PID_MAX _("/proc/sys/kernel/pid_max")

static int proc_max_pid (size_t * procpid)
{
  char * ret;
  unsigned long  pid;
  FILE * fd;
  char   str[128];
  char * ptr;

  SL_ENTER(_("proc_max_pid"));
    
  if (0 == access(PROC_PID_MAX, R_OK)) /* flawfinder: ignore */
    {
      if (NULL != (fd = fopen(PROC_PID_MAX, "r")))
        {
          str[0] = '\0';
          ret = fgets(str, 128, fd);
          if (ret && *str != '\0')
            {
              pid = strtoul(str, &ptr, 0);
              if (*ptr == '\0' || *ptr == '\n')
                {
                  fclose(fd);
                  *procpid = (size_t) pid;
                  SL_RETURN(0, _("proc_max_pid"));
                }
            }
          fclose(fd);
        }
    }
  SL_RETURN((-1), _("proc_max_pid"));
}
#else
static int proc_max_pid(size_t * procpid)
{
  *procpid = sh_maxpid;
  return 0;
}
#endif

static struct sock_store * socklist = NULL;

static void del_sock_all()
{
  struct sock_store * del = socklist;

  while (del)
    {
      socklist = del->next;
      if (del->path)
	SH_FREE(del->path);
      if (del->user)
	SH_FREE(del->user);
      SH_FREE(del);
      del = socklist;
    }
  socklist = NULL;
  return;
}

static void add_sock(unsigned long sock, size_t pid)
{
  struct sock_store * add = SH_ALLOC(sizeof(struct sock_store));

  add->sock = sock;
  add->pid  = pid;
  add->path = NULL;
  add->user = NULL;
  SH_MUTEX_LOCK(mutex_thread_nolog);
  get_user_and_path(add);
  SH_MUTEX_UNLOCK(mutex_thread_nolog);
  add->next = socklist;
  socklist  = add;
  return;
}

static void check_and_add_sock(char * fbuf, size_t pid)
{
  if (0 == strncmp(_("socket:["), fbuf, 8))
    {
      char * end;
      unsigned long sock;
      size_t len = strlen(fbuf);
      if (fbuf[len-1] == ']')
	fbuf[len-1] = '\0';
      sock = strtoul(&fbuf[8], &end, 0);
      if (*end == '\0' && fbuf[8] != '\0')
	{
	  add_sock(sock, pid);
	}
    }
}

static void fetch_socks(size_t pid)
{
  char path[128];
  DIR * dir;
  sl_snprintf(path, sizeof(path), _("/proc/%lu/fd"), (unsigned long) pid);

  dir = opendir(path);
  if (dir)
    {
      struct dirent *entry;
      while (NULL != (entry = readdir(dir)))
	{
	  char fpath[384];
	  char fbuf[64];
	  int  ret;
	  /* /proc/PID/fd/N-> socket:[15713] */
	  sl_snprintf(fpath, sizeof(fpath), _("%s/%s"), path, entry->d_name);
	  ret = readlink(fpath, fbuf, sizeof(fbuf)-1);   /* flawfinder: ignore */
	  if (ret > 0)
	    {
	      fbuf[ret] = '\0';
	      check_and_add_sock(fbuf, pid);
	    }
	}
      closedir(dir);
    }
}

int sh_port2proc_prepare()
{
  size_t i;
  
  if (0 != proc_max_pid(&sh_maxpid))
    {
      SH_MUTEX_LOCK(mutex_thread_nolog);
      sh_error_handle(SH_ERR_ALL, FIL__, __LINE__, 0, MSG_E_SUBGEN, 
                      _("Failed to detect max_pid"), 
                      _("sh_port2proc"));
      SH_MUTEX_UNLOCK(mutex_thread_nolog);
      SL_RETURN ((-1), _("sh_port2proc"));
    }

  /* Delete old socket list and re-create it
   */
  del_sock_all();

  for (i = sh_minpid; i < sh_maxpid; ++i)
    {
      fetch_socks(i);
    }

  return 0;
}

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

char * sh_port2proc_query(int proto, struct in_addr * saddr, int sport,
			  char * user, size_t userlen)
{
  FILE * fd;

  if (proto == IPPROTO_TCP)
    fd = fopen("/proc/net/tcp", "r");
  else
    fd = fopen("/proc/net/udp", "r");

  if (fd)
    {
      int n, iface, port, inode;
      char line[512];

      while (NULL != fgets(line, sizeof(line), fd))
	{
	
	  if (4 == sscanf(line, 
			  "%d: %X:%X %*X:%*X %*X %*X:%*X %*X:%*X %*X %*d %*d %d %*s",
			  &n, &iface, &port, &inode))
	    {
	      struct in_addr haddr;
	      haddr.s_addr = (unsigned long)iface;

	      if (haddr.s_addr == saddr->s_addr && port == sport)
		{
		  struct sock_store * new = socklist;

		  while (new)
		    {
		      if ((unsigned int)inode == new->sock)
			{
			  fclose(fd);
			  if (new->path)
			    {
			      if (new->user)
				sl_strlcpy(user, new->user, userlen);
			      else
				sl_strlcpy(user, "-", userlen);
			      return sh_util_strdup(new->path);
			    }
			  goto err_out;
			}
		      new = new->next;
		    }
		}
	    }
	}
      fclose(fd);
    }
 err_out:
  sl_strlcpy(user, "0", userlen);
  return sh_util_strdup("-");
}

#else /* !defined(__linux__) */

char * sh_port2proc_query(int proto, struct in_addr * saddr, int sport,
			  char * user, size_t userlen)
{
  (void) proto;
  (void) saddr;
  (void) sport;

  sl_strlcpy(user, "-", userlen);
  return sh_util_strdup("-");
}

int sh_port2proc_prepare()
{
  return 0;
}

#endif

#endif /* defined(SH_USE_PORTCHECK) */
