/*
 * Copyright 2003 Ned Ludd <solar@gentoo.org>
 * Copyright 1999-2003 Gentoo Technologies, Inc.
 * Distributed under the terms of the GNU General Public License v2
 * $Header: $
 *
 ********************************************************************
 * 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.
 ********************************************************************
 *
 * This program was written for the hcc suite by (solar|pappy)@g.o.
 * visit http://www.gentoo.org/proj/en/hardened/etdyn-ssp.xml for more
 * information on the Gentoo Hardened gcc suite
 * Also of interest is the pax site http://pageexec.virtualave.net/
 * but you should know about that already.
 *
 * fetch: http://dev.gentoo.org/~solar/elf/scanexec.c
 *
 * Normal Compile:
 * gcc -o scanexec sanexec.c
 *
 * Debug Compile when hardened-gcc is enabled and pax is in effect.
 * make LDFLAGS=-yet_exec scanexec ; chpax -perms $PWD/scanexec
 *
 *
 * Function listing:
 *	readelf(), check_elf_header(), usage(), scanexec(), main()
 ********************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <dlfcn.h>
#include <dirent.h>
#include <getopt.h>

#ifdef __linux__
#include <elf.h>
#include <asm/elf.h>
#else
#include <sys/elf_common.h>
#endif

static const char *rcsid = "$Id: $";

#ifndef ELF_CLASS
#error "UNABLE TO DETECT ELF_CLASS"
#endif

#if (ELF_CLASS == ELFCLASS32)
#define Elf_Ehdr        Elf32_Ehdr
#define Elf_Phdr        Elf32_Phdr
#define Elf_Shdr	Elf32_Shdr
#define Elf_Dyn		Elf32_Dyn
#endif

#if (ELF_CLASS == ELFCLASS64)
#define Elf_Ehdr        Elf64_Ehdr
#define Elf_Phdr        Elf64_Phdr
#define Elf_Shdr	Elf64_Shdr
#define Elf_Dyn		Elf64_Dyn
#endif

struct Elf_File {
   Elf_Ehdr *ehdr;
   Elf_Phdr *phdr;
   Elf_Shdr *shdr;
   Elf_Dyn *dyn;
   char *data;
   int len;
};

typedef struct Elf_File elfobj;

int display_pax_flags = 0;

#define IS_ELF_ET_EXEC(elf) ( \
	(elf->ehdr->e_type == ET_EXEC) && \
		(elf->ehdr->e_ident[EI_CLASS] == ELFCLASS32 || \
			elf->ehdr->e_ident[EI_CLASS] == ELFCLASS64) \
	)


/* PaX flags (to be read in elfhdr.e_flags) */
#define HF_PAX_PAGEEXEC		1	/* 0: Paging based non-exec pages */
#define HF_PAX_EMUTRAMP		2	/* 0: Emulate trampolines */
#define HF_PAX_MPROTECT		4	/* 0: Restrict mprotect() */
#define HF_PAX_RANDMMAP		8	/* 0: Randomize mmap() base */
#define HF_PAX_RANDEXEC		16	/* 1: Randomize ET_EXEC base */
#define HF_PAX_SEGMEXEC		32	/* 0: Segmentation based non-exec pages */

#define EI_PAX			14	/* Index in e_ident[] where to read flags */

#define PAX_FLAGS(elf) ((elf->ehdr->e_ident[EI_PAX + 1] << 8) + (elf->ehdr->e_ident[EI_PAX]))


#define PARSE_FLAGS "hvxp"
static struct option const long_options[] = {
   {"help", no_argument, 0, 'h'},
   {"version", no_argument, 0, 'v'},
   {"pax", no_argument, 0, 'x'},
   {"path", no_argument, 0, 'p'},
//   {"quiet", required_argument, 0, 'q'},
   {NULL, no_argument, NULL, 0}
};

/* Read an ELF into memory */
elfobj *readelf(char *filename)
{
   struct stat st;
   elfobj *elf;
   int fd;

   if (stat(filename, &st) == -1)
      return NULL;

   if ((fd = open(filename, O_RDONLY)) == -1)
      return NULL;

   if (st.st_size <= 0)
      goto close_fd_and_return;

   elf = NULL;

   (elf = (void *) malloc(sizeof(elfobj)));
   if (elf == NULL)
      goto close_fd_and_return;

   elf->len = st.st_size;
   elf->data = (char *) mmap(0, elf->len, PROT_READ /* | PROT_WRITE */ ,
			     MAP_PRIVATE /* | MAP_DENYWRITE */ , fd, 0);

   if (elf->data == (char *) MAP_FAILED) {
      free(elf);
      goto close_fd_and_return;
   }

   elf->ehdr = (void *) elf->data;
   elf->phdr = (void *) (elf->data + elf->ehdr->e_phoff);
   elf->shdr = (void *) (elf->data + elf->ehdr->e_shoff);

   /* elf->fd = fd; */
   /* do we want to keep the fd open? */
   close(fd);
   return elf;

 close_fd_and_return:
   close(fd);
   return NULL;
}

/* check the elf header */
int check_elf_header(Elf_Ehdr const *const ehdr)
{
   if (!ehdr || strncmp((void *) ehdr, ELFMAG, SELFMAG) != 0 ||
       (ehdr->e_ident[EI_CLASS] != ELFCLASS32
	&& ehdr->e_ident[EI_CLASS] != ELFCLASS64)
       || ehdr->e_ident[EI_VERSION] != EV_CURRENT) {
      return 1;
   }
   return 0;
}

char *pax_short_flags(unsigned long flags)
{
   static char buffer[7];

   snprintf(buffer, sizeof(buffer), "%c%c%c%c%c%c",
	    flags & HF_PAX_PAGEEXEC ? 'p' : 'P',
	    flags & HF_PAX_EMUTRAMP ? 'E' : 'e',
	    flags & HF_PAX_MPROTECT ? 'm' : 'M',
	    flags & HF_PAX_RANDMMAP ? 'r' : 'R',
	    flags & HF_PAX_RANDEXEC ? 'X' : 'x',
	    flags & HF_PAX_SEGMEXEC ? 's' : 'S');
   return buffer;
}

/* scan a directory for ET_EXEC files and print when we find one */
void scanexec(const char *path)
{
   elfobj *elf = NULL;
   register DIR *dir;
   register struct dirent *dentry;

   if (chdir(path) == 0) {
      if ((dir = opendir(path))) {
	 while ((dentry = readdir(dir))) {
	    /* verify this is real ELF ET_EXEC. */
	    if ((elf = readelf(dentry->d_name)) != NULL) {
	       if (!check_elf_header(elf->ehdr))
		  if (IS_ELF_ET_EXEC(elf))
		     printf("%s%s%s/%s\n",
			    ((display_pax_flags) ?
			     pax_short_flags(PAX_FLAGS(elf)) : ""),
			    ((display_pax_flags) ? " " : "")
			    , path, dentry->d_name);

	       if (elf != NULL) {
		  munmap(elf->data, elf->len);
		  free(elf);
		  elf = NULL;
	       }
	    }
	 }
	 closedir(dir);
      }
   }
}


/* display usage and exit */
int usage(char **argv)
{
   printf("Usage: %s dir1 dir2 dirN...\n",
	  (*argv != NULL) ? argv[0] : __FILE__ "\b\b");
   exit(EXIT_FAILURE);
}


void showopt(int c, char *data)
{
   int i;
   for (i = 0; long_options[i].name; i++)
      if (long_options[i].val == c)
	 printf("  -%c, --%s\t: %s\n", c, long_options[i].name, data);
}

/* parse command line arguments and preform needed actions */
void parseargs(int argc, char **argv)
{
   int flag;
   char *p, *path;
   opterr = 0;

   while ((flag = (int) getopt_long(argc, argv, PARSE_FLAGS,
				    long_options, NULL)) != EOF) {
      switch (flag) {
	 case 'h':
	    showopt('p', "Scan all directories in PATH environment.");
	    showopt('x', "Display PaX flags when scanning.");
	    showopt('h', "Print this help and exit.");
	    showopt('v', "Print version and exit.");
	    exit(EXIT_SUCCESS);
	 case 'v':
	    printf("%s compiled %s\n", __FILE__, __DATE__);
	    printf
		("%s written for Gentoo Linux <solar@gentoo.org>\n\t%s\n",
		 (*argv != NULL) ? argv[0] : __FILE__ "\b\b", rcsid);
	    exit(EXIT_SUCCESS);
	 case 'x':
	    display_pax_flags = 1;
	    break;
	 case 'p':
	    if ((path = strdup(getenv("PATH"))) == NULL) {
	       perror("strdup");
	       exit(EXIT_FAILURE);
	    }
	    /* split string into dirs */
	    while ((p = strrchr(path, ':')) != NULL) {
	       scanexec(p + 1);
	       *p = 0;
	    }
	    if (path != NULL)
	       free(path);
	    break;
	 case '?':
	 default:
	    break;
      }
   }
   while (optind < argc)
      scanexec(argv[optind++]);
}

int main(int argc, char **argv)
{
   if (argc < 2)
      usage(argv);

   parseargs(argc, argv);

   return 0;
}
