/* * helper functions for scponly */ #include /* io */ #include /* for str* */ #include /* for stat, getpwuid */ #include /* for stat */ #include /* for exit, access, getpwuid, execve, getopt */ #ifdef HAVE_GETOPT_H #include /* for getopt */ #endif #include /* for debugging */ #include /* to get username for config parsing */ #include /* time */ #include /* basename */ #include /* realloc */ #include #include "scponly.h" #include "config.h" #ifdef HAVE_GLOB #include /* for glob() */ #else #ifdef HAVE_WORDEXP #include /* for wordexp() */ #endif #endif #define MAX(x,y) ( ( x > y ) ? x : y ) #define MIN(x,y) ( ( x < y ) ? x : y ) extern int debuglevel; extern char username[MAX_USERNAME]; extern char homedir[FILENAME_MAX]; extern cmd_t commands[]; extern cmd_arg_t dangerous_args[]; extern char * allowed_env_vars[]; extern char * safeenv[MAX_ENV]; #ifdef HAVE_GETOPT extern char *optarg; extern int optind; #ifdef HAVE_OPTRESET extern int optreset; #endif #endif #ifdef UNIX_COMPAT char* solaris_needs_strsep(char** str, char* delims) { char* tmpstr; if (*str==NULL) { return NULL; } tmpstr=*str; while (**str!='\0') { if (strchr(delims,**str)!=NULL) { **str='\0'; (*str)++; return tmpstr; } (*str)++; } *str=NULL; return tmpstr; } #endif void discard_vector(char **av) { char **tmpptr=av; while (*tmpptr!=NULL) free(*tmpptr++); free(av); } char *flatten_vector(char **av) { char **tmpptr=av; char *temp=NULL; char *crptr=NULL; char *outbuf=NULL; int len=0,newlen=0; while (*tmpptr!=NULL) { if (NULL != (crptr=strchr(*tmpptr, '\n'))) { *crptr='\0'; } if (outbuf!=NULL) { len = strlen(outbuf); newlen=len + strlen(*tmpptr)+1; } else { len = 0; newlen=strlen(*tmpptr); } if (NULL == (temp = realloc (outbuf, newlen + 1))) { perror("realloc"); if (outbuf) free(outbuf); exit(EXIT_FAILURE); } outbuf=temp; temp=NULL; if (len) { outbuf[len]=' '; strcpy(&outbuf[len+1],*tmpptr); } else strcpy(outbuf,*tmpptr); *tmpptr++; } return (outbuf); } /* * since some programs support invoking other programs for their encryption * (dropins to replace ssh), we must refuse to support these arguments * * RETURN: 1 means reject this command, 0 means it is safe. */ int check_dangerous_args(char **av) { cmd_arg_t *cmdarg=dangerous_args; char **tmpptr=av; int ch; int ac=0; char **av2 = NULL; /* * first count the arguments in the vector */ tmpptr=av; while (*tmpptr!=NULL) { *tmpptr++; ac++; } #ifdef PROG_RSYNC if (exact_match(PROG_RSYNC, av[0])) { /* * these are the long opts (beginning "--") which we * allow for rsync */ char *permitted_long_opts[] = { "--server", "--sender", "--delete", NULL /* last element must be NULL */ }; /* * make a copy of the args excluding any permitted long * options */ int i, j; av2 = malloc(ac * sizeof *av2); av2[0] = av[0]; for (i = 1, j = 1; i < ac; ++i) { if (0 == strncmp(av[i], "--", 2)) { char **p; /* * test against permitted opts */ for (p = permitted_long_opts; *p; ++p) { if (exact_match(av[i], *p)) break; } if (*p) { /* * permitted; skip this one */ continue; } else { /* * no match */ syslog(LOG_ERR, "option %s is not permitted for use with %s (%s)", av[i], cmdarg->name, logstamp()); return 1; } } av2[j++] = av[i]; } av2[j] = NULL; ac = j; av = av2; } #endif /* PROG_RSYNC */ while (cmdarg != NULL) { if (cmdarg->name == NULL) return 0; if (exact_match(cmdarg->name,av[0])) { /* * the command matches one of our dangerous commands * check the rest of the vector for dangerous command * line arguments * * if the "getoptflag" is set for this command, use getopt * to determine if the flag is present, otherwise * to a string match on each element of the argument vector */ if (1 == cmdarg->getoptflag) { /* * now use getopt to look for our problem option */ #ifdef HAVE_GETOPT #ifdef HAVE_OPTRESET /* * if we have optreset, use it to reset the global variables */ optreset=1; optind=1; #else /* * otherwise, try a glibc-style reset of the global getopt vars */ optind=0; #endif /* * tell getopt to only be strict if the 'opts' is well defined */ opterr=cmdarg->strict; while ((ch = getopt(ac, av, cmdarg->opts)) != -1) if (ch == cmdarg->badarg[0]) { syslog(LOG_ERR, "option %s is not permitted for use with %s (arg was %s)(%s))", cmdarg->badarg, cmdarg->name, optarg, logstamp()); return 1; } #elif syslog(LOG_ERR, "a getopt() argument check could not be performed for %s, recompile scponly without support for %s or rebuild scponly with getopt", av[0], av[0]); #endif } else { tmpptr=av; *tmpptr++; while (*tmpptr!=NULL) { if(strbeg(*tmpptr, cmdarg->badarg)) { syslog(LOG_ERR, "%s is not permitted for use with %s (%s))", cmdarg->badarg, cmdarg->name, logstamp()); return 1; } *tmpptr++; } } } cmdarg++; } return 0; } int valid_arg_vector(char **av) { cmd_t *cmd=commands; while (cmd != NULL) { if (cmd->name == NULL) return 0; if (exact_match(cmd->name,av[0])) { if ((cmd->argflag == 0) && (av[1]!=NULL)) { return 0; } return 1; } cmd++; } return 0; } char *substitute_known_path(char *request) { cmd_t *cmd=commands; char *stripped_req=strdup(basename(request)); while (cmd != NULL) { if (cmd->name == NULL) break; if (exact_match(basename(cmd->name),stripped_req)) { free(stripped_req); /* discard old pathname */ return (strdup(cmd->name)); } cmd++; } return (stripped_req); } char **build_arg_vector(char *request) { /* * i strdup vector elements so i know they are * mine to free later. */ char **ap, *argv[MAX_ARGC], *inputstring, *tmpstring, *freeme; char **ap2,**av=(char **)malloc(MAX_ARGC * (sizeof(char *))); ap=argv; freeme=inputstring=strdup(request); /* make a local copy */ while (ap < &argv[(MAX_ARGC-1)]) { if (inputstring && (*inputstring=='"')) { if (NULL != (tmpstring=strchr((inputstring+1),'"'))) { *tmpstring++='\0'; *ap=(inputstring+1); #ifdef UNIX_COMPAT if (solaris_needs_strsep(&tmpstring, WHITE) == NULL) #else if (strsep(&tmpstring, WHITE) == NULL) #endif break; inputstring=tmpstring; if (**ap != '\0') ap++; continue; } } #ifdef UNIX_COMPAT if ((*ap = solaris_needs_strsep(&inputstring, WHITE)) == NULL) #else if ((*ap = strsep(&inputstring, WHITE)) == NULL) #endif break; if (**ap != '\0') ap++; } *ap = NULL; ap=argv; ap2=av; while (*ap != NULL) { *ap2=strdup(*ap); ap2++; ap++; } *ap2 = NULL; free(freeme); return (av); } char **expand_wildcards(char **av_old) #ifdef HAVE_GLOB { char **av_new=(char **)malloc(MAX_ARGC * (sizeof(char *))); glob_t g; int c_old,c_new,c; /* argument counters */ #ifdef UNIX_COMPAT int flags = GLOB_NOCHECK; #else int flags = GLOB_NOCHECK | GLOB_TILDE; #endif g.gl_offs = c_new = c_old = 0; while(av_old[c_old] != NULL ) { if (0 == glob(av_old[c_old++],flags,NULL,&g)) { c=0; while((g.gl_pathv[c] != NULL) && (c_new < (MAX_ARGC-1))) av_new[c_new++]=strdup(g.gl_pathv[c++]); globfree(&g); } } av_new[c_new]=NULL; discard_vector(av_old); return av_new; } #else #ifdef HAVE_WORDEXP { return NULL; } #endif #endif int cntchr(char *buf, char x) { int count=0; while (*buf!=0) if (*buf++==x) count++; return count; } char *logstamp () { /* Time and pid are handled for us by syslog(3). */ static char ret_buf[255]; static const char bad_ip[10] = "no ip?!"; char *ipstring = NULL; ipstring = (char *)getenv("SSH_CLIENT"); if (!ipstring) ipstring = (char *)getenv("SSH2_CLIENT"); if (!ipstring) ipstring = (char *)bad_ip; snprintf(ret_buf, sizeof(ret_buf)-1, "username: %s(%d), IP/port: %s", username, getuid(), ipstring); return ret_buf; } /* * if big ends with small, return big without * small in a new buf, else NULL */ inline char *strend (char *big, char *small) { int blen,slen; slen=strlen(small); blen=strlen(big); if ((blen==0) || (slen==0) || (blen < slen)) { return NULL; } if (0 == strcmp(&big[(blen-slen)],small)) { char *tempbuf=NULL; tempbuf=(char *)malloc(blen-slen+1); if (tempbuf==NULL) { perror("malloc"); exit(EXIT_FAILURE); } bzero(tempbuf,(blen-slen+1)); strncpy(tempbuf, big, blen-slen); return tempbuf; } return NULL; } /* * if big starts with small, return the char after * the last char in small from big. ahem. */ inline char *strbeg (char *big, char *small) { if (strlen(big) <= strlen(small)) return NULL; if (0==strncmp(big,small,strlen(small))) return (big+strlen(small)); return NULL; } /* * if any chars in string dont appear in ALLOWABLE * then fail (return 0) */ int valid_chars(char *string) { int count; if ((count=strspn(string,ALLOWABLE))==strlen(string)) return 1; else { fprintf (stderr, "invalid characters in scp command!\n"); fprintf (stderr, "here:%s\n",string+count); fprintf (stderr, "try using a wildcard to match this file/directory\n"); return 0; } } /* * retrieves username and home directory from passwd database */ int get_uservar(void) { struct passwd *userinfo; if (NULL==(userinfo=getpwuid(getuid()))) { syslog (LOG_WARNING, "no knowledge of uid %d [%s]", getuid(), logstamp()); if (debuglevel) { fprintf (stderr, "no knowledge of uid %d\n", getuid()); perror ("getpwuid"); } return 0; } if (debuglevel) syslog(LOG_DEBUG, "retrieved home directory of \"%s\" for user \"%s\"", userinfo->pw_dir, userinfo->pw_name); strncpy(username,userinfo->pw_name,MAX_USERNAME); strncpy(homedir,userinfo->pw_dir,FILENAME_MAX); return 1; } int mysetenv(const char *name, const char *value) { /* from: http://www.onlamp.com/pub/a/onlamp/excerpt/PUIS3_chap16/index3.html */ static char count = 0; char buff[255]; if (count == 0) /* in case nothing ever gets put in here... */ safeenv[0] = NULL; if (count == MAX_ENV) return 0; if (!name || !value) return 0; if (snprintf(buff, sizeof(buff), "%s=%s", name, value) < 0) return 0; if (safeenv[count] = strdup(buff)) { safeenv[++count] = NULL; return 1; } return 0; } void filter_allowed_env_vars() { int slen = 0; char *p_env, *p_str; char **p_valid = allowed_env_vars; /* check each allowed variable */ while (NULL != *p_valid) { p_env = (char*)getenv(*p_valid); if (NULL != p_env) { if (debuglevel) syslog(LOG_DEBUG, "Found \"%s\" and setting it to \"%s\"", *p_valid, p_env); if (!mysetenv(*p_valid, p_env)) syslog(LOG_ERR, "Unable to set environment var \"%s\" to \"%s\"", *p_valid, p_env); } else if (debuglevel) { syslog(LOG_DEBUG, "Unable to find \"%s\" in the environment", *p_valid); } p_valid++; } }