/*
 * build with: gcc -std=c99 -D_GNU_SOURCE=1 -Wall -O2 -ffunction-sections -fdata-sections -fomit-frame-pointer ./suidcmd.c -o ./suidcmd
 * strip -s ./suidcmd
 */

#ifdef _HAVE_CONFIG
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>    /* memset(...), strstr(...) */
#include <sys/wait.h>
#include <libgen.h>    /* basename(...) */


static const char* cmds[] =
  { "/usr/sbin/ether-wake" };
static const size_t cmdsiz = sizeof(cmds)/sizeof(cmds[0]);

static int safe_exec(const char* cmdWithArgs)
{
  pid_t child;
  if ( (child = fork()) == 0 ) {
    size_t szCur = 0, szMax = 10;
    char** args = calloc(szMax, sizeof(char**));
    const char* cmd = NULL;

    const char* prv = cmdWithArgs;
    const char* cur = NULL;
    while ( (cur = strchr(prv, ' ')) ) {
      if (cmd == NULL)
        cmd = strndup(prv, cur-prv);

      args[szCur++] = strndup(prv, cur-prv);
      if (szCur >= szMax) {
        szMax *= 2;
        args = realloc(args, sizeof(char**)*szMax);
      }

      cur++;
      prv = cur;
    }
    if (cmd == NULL) {
      cmd = cmdWithArgs;
    } else {
      args[szCur++] = strndup(prv, cur-prv);
    }
    args[szCur] = NULL;
    execv(cmd, args);
    exit(-5);
  } else if (child != -1) {
    int retval = 0;
    waitpid(child, &retval, 0);
    return retval;
  }
  return -6;
}

static const char* getCmd(char* arg0)
{
  char* barg0 = strdup( basename(arg0) );
  size_t szArg0 = strlen(barg0);
  char* bcmd = NULL;
  const char* retval = NULL;

  if (barg0) {
    for (size_t i = 0; i < cmdsiz; ++i) {
      bcmd = strdup( basename((char*)cmds[i]) );
      if (bcmd) {
        if (strlen(bcmd) == szArg0 && strstr(arg0, bcmd)) {
          retval = cmds[i];
          break;
        }
        free(bcmd);
        bcmd = NULL;
      }
    }
  }

  if (bcmd)
    free(bcmd);
  free(barg0);
  return retval;
}

int main(int argc, char** argv)
{
  uid_t ruid, euid, suid;

  const char* runpath = getCmd(argv[0]);
  if (!runpath) {
    fprintf(stderr, "%s not runnable cmd\n", argv[0]);
    return 1;
  }

  if (getresuid(&ruid, &euid, &suid) != 0) {
    perror("getresuid()");
  } else {
    printf("%s: RUID:%u , EUID:%u , SUID:%u\n", argv[0], ruid, euid, suid);
  }

  if (setuid(0) != 0) {
    perror("setuid(0)");
  } else printf("%s: setuid(0)\n", argv[0]);

  char* cmd = NULL;
  if (asprintf(&cmd, "%s", runpath) <= 0) {
    fprintf(stderr, "%s: asprintf(\"%s\") error\n", argv[0], runpath);
    return 1;
  }

  char* prev_cmd = NULL;
  for (int i = 1; i < argc; ++i) {
    prev_cmd = cmd;
    if (asprintf(&cmd, "%s %s", prev_cmd, argv[i]) < 0) {
      fprintf(stderr, "%s: asprintf(\"%s\") error\n", argv[0], argv[i]);
      return 1;
    }
    free(prev_cmd);
  }

  int retval = -1;
  switch ( (retval = safe_exec(cmd)) ) {
    case -1: fprintf(stderr, "%s: could not create child process..\n", argv[0]); return 1;
    case 127: fprintf(stderr, "%s: could not execute shell (child process)..\n", argv[0]); return 1;
    default:
      printf("%s: child process returned with: %d\n", argv[0], retval);
  }
  free(cmd);
  return 0;
}