Logo Search packages:      
Sourcecode: xcdroast version File versions  Download package

xcdrwrap.c

/*
      xcdrwrap.c
      hopefully secure wrapper to call cdrtools
      12.7.01 tn

      remove all references to glib functions to have no external
      libraries in use - on some platforms suid mode is forbidden then
      23.2.03 tn
*/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include "largefile.h"

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <pwd.h>
#include "xcdroast.h"

#undef DEBUG

/* dont allow any known exploit code to be passed through the cdrtools */
#define ANTI_CDRTOOLS_EXPLOIT


/* copy glib glist stuff we need into this code, so we dont need 
   to link with glib */

/* code ripped from glib 1.2.10 - glib.h and glist.c */

typedef struct _GList   GList;
typedef void* gpointer;
struct _GList
{
  gpointer data;
  GList *next;
  GList *prev;
};

static GList* g_list_last (GList *list)
{
  if (list)
    {
      while (list->next)
      list = list->next;
    }
  
  return list;
}

static GList* g_list_first (GList *list)
{
  if (list)
    {
      while (list->prev)
      list = list->prev;
    }
  
  return list;
}

static GList* g_list_append (GList *list, gpointer data)
{
  GList *new_list;
  GList *last;
  
  new_list = calloc(1, sizeof(GList));
  new_list->data = data;
  
  if (list)
    {
      last = g_list_last (list);
      /* g_assert (last != NULL); */
      last->next = new_list;
      new_list->prev = last;

      return list;
    }
  else
    return new_list;
}

/* end of glib code */


static char sharedir[MAXLINE];
static char prefixdir[MAXLINE];
static char rootconfig[MAXLINE];
static char username[MAXLINE];
static char hostname[MAXLINE];

static int root_users_access;
static int root_hosts_access;
static GList *root_users_lists;
static GList *root_hosts_lists;

static int load_rootconfig(char *cnf);
static void check_access(int verbose);
static void get_spawn_path(char *app, char *ret, int debugout);


/* code duplicated from tools.c and cleared from glib symbols */

static int isroot() {

#ifdef DEBUG
      printf("I am uid: %d\n", getuid());
#endif
        if (getuid() == 0) {
                return 1;
        } else {
                return 0;
        }
}

static int is_directory(char *path) {
struct stat buf;

        if (stat(path,&buf) != 0) {
                return 0;
        }

        if (S_ISDIR(buf.st_mode) == 1) {
                return 1;
        } else {
                return 0;
        }
}

static int is_file(char *path) {
struct stat buf;

        if (stat(path,&buf) != 0) {
                return 0;
        }
        return 1;
}

static char *strip_string(char *str) {
int i,j;
int c1;

        if ( str == NULL) return (NULL);

        /* count how many leading chars to be whitespace */
        for(i=0; i<strlen(str); i++) {
                if (str[i] != ' ' && str[i] != '\t' && str[i] != '\r') 
                        break;
        }

        /* count how many trailing chars to be whitespace */
        for(j=strlen(str)-1; j >= 0; j--) {
                if (str[j] != ' ' && str[j] != '\t' && str[j] != '\n' && str[j] != '\r')
                        break;
        }

        /* string contains only whitespace? */
        if (j<i) {
                str[0] = '\0';
                return(str);
        }

        /* now move the chars to the front */
        for(c1=i; c1 <= j; c1++) {
                str[c1-i] = str[c1]; 
        }
        str[j+1-i] = '\0';      

        return(str);
}

static char *escape_parse(char *str) {
char tmp[MAXLINE];
char c;
int i,j;

        if ( str == NULL) return (NULL);

      if (strlen(str) > MAXLINE) return (NULL);

        j = 0;
        for(i=0; i<strlen(str); i++) {
                c = str[i];
                if (c == '\\') {
                        i++;
                        switch(str[i]) {

                        case 'n':
                                c = '\n';
                                break;
                        
                        case 't':
                                c = '\t';
                                break;
        
                        case 'b':
                                c = '\b';
                                break;

                        default:
                                c = str[i];
                        }
                }       

                tmp[j]=c;
                j++;
        }

        tmp[j] = '\0';

        strcpy(str,tmp);
        return(str);
}

static int parse_config_line(char *iline, char *id, char *value) {
char *p,*p2;
char line[1024];
char tmp[1024];

        strncpy(line,iline, MAXLINE); 
        strcpy(id,"");
        p = strtok(line,"=");
        if (p != NULL) {
                /* got id */
                strcpy(id,p);
                strip_string(id);
        } else {
                return 1;
        }

        strcpy(tmp,"");
        p = strtok(NULL,"");
        if (p != NULL) {
                /* string after = */
                strcpy(tmp,p);
                strip_string(tmp);
        } else { 
                return 1;
        }

        /* now strip quotes from string */
        p = tmp;
        if (*p == '\"') {
                p2 = p+1;
        } else {
                p2 = p;
        }
        if (p[strlen(p)-1] == '\"') {
                p[strlen(p)-1] = '\0';
        }
        strcpy(value,p2);

        /* now reconvert escape-chars */
        escape_parse(value);

        /* all ok */
        return 0;
}

/* done code from tools.c */


/* return the defined callpath for a helper-binary */

static char *cmdstring(char *cmd, char *ret) {
char tmp[MAXLINE];

      if (strncmp(cmd,"CDRECORD",MAXLINE) == 0) {
            strcpy(ret, CDRECORD);
            return ret;
      }
      if (strncmp(cmd,"CDDA2WAV",MAXLINE) == 0) {
            strcpy(ret, CDDA2WAV);
            return ret;
      }
      if (strncmp(cmd,"READCD",MAXLINE) == 0) {
            strcpy(ret, READCD);
            return ret;
      }
      if (strncmp(cmd,"MKISOFS",MAXLINE) == 0) {
            strcpy(ret, MKISOFS);
            return ret;
      }
      if (strncmp(cmd,"WRITETEST",MAXLINE) == 0) {
            strcpy(ret,"WRITETEST");
            return ret;
      }
      if (strncmp(cmd,"-V",MAXLINE) == 0) {
            printf("X-CD-Roast %s\n", XCDROAST_VERSION);
            printf("sharedir: %s\n", sharedir);
            printf("prefixdir: %s\n", prefixdir);

#if !(defined(__MACH__) && defined(__APPLE__)) && (USE_NONROOTMODE == 1)

            if (load_rootconfig(rootconfig)) {
                  printf("Warning: rootconfig unreadable\n");
            } else {
                  if (!isroot()) 
                        check_access(0);
            }
#endif
            /* show found paths of helper apps */
            get_spawn_path(CDRECORD, tmp, 1);
            printf("cdrecord found at: %s\n", tmp);
            get_spawn_path(CDDA2WAV, tmp, 1);
            printf("cdda2wav found at: %s\n", tmp);
            get_spawn_path(READCD, tmp, 1);
            printf("readcd found at: %s\n", tmp);
            get_spawn_path(MKISOFS, tmp, 1);
            printf("mkisofs found at: %s\n", tmp);

            exit(0);
      }
      return NULL;      
}


/* drop any root permissions */

static void drop_root() {

#ifdef  HAVE_SETREUID
        if (setreuid(-1, getuid()) < 0)
#else
#ifdef  HAVE_SETEUID
        if (seteuid(getuid()) < 0)
#else
        if (setuid(getuid()) < 0)
#endif
#endif
        {
                perror("Panic: Cannot set back effective uid.");
                exit(1);
        }
}


/* determine path for helper apps */

static void get_spawn_path(char *app, char *ret, int debugout) {
struct stat buf;

      /* when path is with a leading slash (absolute), do nothing */
      if (app[0] == '/') {
            strncpy(ret,app,MAXLINE);
            return;
      }

      /* otherwise its relative - add sharedir first */
      snprintf(ret,MAXLINE,"%s/%s", sharedir, app);

      /* now check if this file does exist */
      if (stat(ret,&buf) != 0) {
            /* it does not, so try the fallback */
            snprintf(ret,MAXLINE,"%s/%s", prefixdir, app);
      }

      /* additional check if we run with -V */
      if (debugout) {
            /* check the fallback too */
            if (stat(ret,&buf) != 0) {
                  strcpy(ret, "- not found -");
            }
            return;
      }

      /* paranoid check */
      if (ret[0] != '/') {
            printf("ERROR: Invalid relative spawnpath %s\nExiting...\n", ret);
            exit(1);
      }

      return;
}


/* print warning when wrong arguments */

static void usagequit() {

      printf("This wrapper should only be called by X-CD-Roast %s\n", 
            XCDROAST_VERSION);
      exit(1);
}


/* read root-user relevant stuff from config file */

static int load_rootconfig(char *cnf) {
FILE *fd;
char line[MAXLINE];
char id[MAXLINE];
char value[MAXLINE];

      if ((fd = fopen(cnf,"r")) == NULL) { 
            /* error opening file */
            return 1;
      }

      for (;;) {
            if (fgets(line,MAXLINE,fd) == NULL)
                  break;

            /* skip empty or hashed lines */
            strip_string(line);
            if (*line == '#' || *line == '\0') 
                  continue;

                /* parse lines */
            if (parse_config_line(line,id,value) || !value) {
                  fprintf(stderr,"syntax error in config-file\n");
                  exit(1);
            }     

            if (strcmp("ROOT_USERS_ACCESS",id) == 0) {
                  root_users_access = atoi(value);
            }
            if (strcmp("ROOT_USERS_LISTS",id) == 0) {
                  root_users_lists = g_list_append(root_users_lists, strdup(value));      
            }
            if (strcmp("ROOT_HOSTS_ACCESS",id) == 0) {
                  root_hosts_access = atoi(value);
            }
            if (strcmp("ROOT_HOSTS_LISTS",id) == 0) {
                  root_hosts_lists = g_list_append(root_hosts_lists, strdup(value));      
            }

      }

      if (fclose(fd) != 0) {
            /* error closing file */
            return 1;
      }

      return 0;
}


/* do check if the current user and host is allowed to start xcdroast */
/* return 1 if so, 0 if denied */

static int checkuserhost(char *username, char *hostname) {
int userok, hostok;
int match;
GList *loop;

      match = 0;
      /* user first */
      if (root_users_access == 0) {
            userok = 1;
      } else 
      if (root_users_access == 1) {
            userok = 0;
      } else {
            loop = g_list_first(root_users_lists);
            while (loop) {
#ifdef DEBUG
                  printf("trying to match %s to %s\n", username,(char *)loop->data);
#endif
                  if (loop->data && strcmp(username,(char *)loop->data) == 0) {
                        /* found our login on the list */
                        match = 1;
                  }           
                  loop = loop->next;
            }
            if ((root_users_access == 2 && match) ||
                (root_users_access == 3 && !match)) {
                  userok = 1;
            } else  {
                  userok = 0;
            }
      }     

      match = 0;
      /* now check host */
      if (root_hosts_access == 0) {
            hostok = 1;
      } else 
      if (root_hosts_access == 1) {
            hostok = 0;
      } else {
            loop = g_list_first(root_hosts_lists);
            while (loop) {
#ifdef DEBUG
                  printf("trying to match %s to %s\n", hostname,(char *)loop->data);
#endif
                  if (loop->data && strcmp(hostname,(char *)loop->data) == 0) {
                        /* found our login on the list */
                        match = 1;
                  }           
                  loop = loop->next;
            }
            if ((root_hosts_access == 2 && match) ||
                (root_hosts_access == 3 && !match)) {
                  hostok = 1;
            } else  {
                  hostok = 0;
            }
      }

      /* only when both the host and the user are allowed, allow access */
      if (userok && hostok) {
            return 1;
      } else {
            return 0;
      }
}


/* try to write a file to the given directory 
   return 0 if ok, or 1 when failed */

static int test_write_perms(char *dir) {
int pidseed;
char tmp[MAXLINE];
int count;
FILE *fd;

      drop_root();

      if (!is_directory(dir))
            return 1;

      /* generate a truely unused unique filename */
      pidseed = (int) getpid();
      count = 0;

      while (1) {
            snprintf(tmp,MAXLINE,"%s/xcdtmp%02d.%d", dir, count, pidseed);
            if (!is_file(tmp))
                  break;
            count++;

            /* try max 100 times to find a name */
            if (count > 99) 
                  return 1;
      }

      /* tmp does now contain a full filename which is unused for sure */

      /* open that file for writing */
      fd = fopen(tmp,"w");

      if (fd == NULL) {
            /* we failed */
            return 1;
      }     

      if (fclose(fd) != 0) {
            /* error closing file */
            return 1;
      }
      
      /* test finished - remove file */
      unlink(tmp);
      
      return 0;
}


/* access denied */

static void check_access(int verbose) {

      if (!username || !hostname)
            return;

      /* check if the current user on current host may run xcdroast at all */
      if (!checkuserhost(username,hostname)) {
            printf("X-CD-Roast %s\n", "ACCESS DENIED");
            if (verbose) {
                  fprintf(stderr,"Unable to run wrapper - permission denied by admin.\n");
                  fprintf(stderr,"Aborting...\n");
            }
            exit(1);
      }
}

/* returns the current username */

static char *get_username() {
struct passwd *ent;

      ent = getpwuid(getuid());
      return ent->pw_name;
} 

int main(int argc, char **argv) {
int i, stat;
char **arglist;
char callpath[MAXLINE];
char tmp[MAXLINE];
char *p, *p1;
int seen_device_spec;

      root_users_access = 0;
      root_hosts_access = 0;
      root_users_lists = NULL;
      root_hosts_lists = NULL;
      seen_device_spec = 0;

#ifdef PRE_LIBDIR 
        /* use prefix as sharedir as it came from the makefile-option */
        strncpy(sharedir, PRE_LIBDIR, MAXLINE);
#else
        /* otherwise install our default prefix */
        strncpy(sharedir, LIBDIR, MAXLINE);
#endif

#ifdef CDRTOOLS_PREFIX
        /* use prefix as it came from the makefile-option */
        strncpy(prefixdir, CDRTOOLS_PREFIX, MAXLINE);
#else
# ifdef PREFIX
        /* use prefix as it came from the makefile-option */
        strncpy(prefixdir, PRE_PREFIX, MAXLINE);
# else
        /* otherwise install our default prefix */
        strncpy(prefixdir, PREFIX, MAXLINE);
# endif
#endif  

        snprintf(rootconfig, MAXLINE, "%s/%s", SYSCONFDIR, ROOTCONFIG);

      if (argc < 2) 
            usagequit();


      /* get username and host */
      if (gethostname(hostname,MAXLINE) != 0) {
            strncpy(hostname,"not_available",MAXLINE);
      }
      p1 = get_username();
      if (p1 != NULL) {
            strncpy(username,p1, MAXLINE);
      } else {
            strncpy(username,"not_available",MAXLINE);
      }

#ifdef DEBUG
      printf("User/Host: %s@%s\n", username, hostname);
#endif

      /* check for a known first argument */
      if (cmdstring(argv[1],tmp) == NULL) 
            usagequit();

      
      /* now read rootconfig file - just the user and host allowlists */

#if !(defined(__MACH__) && defined(__APPLE__)) && (USE_NONROOTMODE == 1)

      if (!isroot()) {
            if (load_rootconfig(rootconfig)) {
                  fprintf(stderr,"Error reading %s - Aborting...\n", rootconfig);
                  exit(1);
            }
            /* check access and quit if not allowed */
            check_access(1);
      }
#endif

      /* note: Its NOT possible to change the path of the binaries
               via the commandline (-l switch), because this would
             be a security risk. You have to use the compiled-in
             paths */


      /* we should check if a directory is writeable? */
      if (strcmp(tmp,"WRITETEST") == 0) {
            if (argc != 3) 
                  usagequit();
            stat = test_write_perms(argv[2]);

            if (stat == 0) {
                  /* ok, dir is writeable */
                  return 0;
            } else {
                  return 1;
            }
      }
      
      /* make path absolute */
      get_spawn_path(tmp, callpath, 0);

      /* build new command line */
      arglist = calloc(argc+1, sizeof(char *));
      for (i = 1; i < argc; i++) {
            arglist[i-1] = strdup(argv[i]);
      }

      /* now remove path from first argument */
      strcpy(tmp,callpath);
      p = rindex(tmp,'/');
      if (p != NULL) {
            if (arglist[0]) {
                  free(arglist[0]);
            }
            arglist[0] = strdup(p+1);
      }     

#ifdef ANTI_CDRTOOLS_EXPLOIT
      i = 0;
      while(arglist[i]) {

            /* this argument is a device-spec */
            if (strstr(arglist[i], "dev=") ||
                strstr(arglist[i], "-D") ||
                strstr(arglist[i], "--dev")) {
                  seen_device_spec = i;
            }

            /* this arg or the previous argument a device spec? */
            if (i == seen_device_spec || i == seen_device_spec + 1) {

                  /* ok, here is no %x or %n allowed */
                  /* these tags show up in exploit code */
                  if (strstr(arglist[i], "%x") ||
                      strstr(arglist[i], "%n")) {
                        printf("Illegal input detected. Go away.\n");
                        exit(1);
                  }           
            }
            i++;
      }
#endif

#ifdef DEBUG
      printf(":%s:\n",callpath);
      i = 0;
      while(arglist[i]) {
            printf(":%s:\n",arglist[i]);
            i++;
      }
      exit(1);
#endif

      /* now we recreated the original command line */ 
      if (execv(callpath,arglist) < 0) {
            fprintf(stderr,"execv error while calling %s (%s)\n", 
                        callpath, strerror(errno));
      }

      return 1;
}


Generated by  Doxygen 1.6.0   Back to index