int process_ssh_request(char *request) { char **av, **tmp_av, **tenv; char *flat_request,*tmpstring, *tmprequest; char bad_winscp3str[] = "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server exec sftp-server"; int retval; int reqlen=strlen(request); char **env = NULL; debug(LOG_DEBUG, "processing request: \"%s\"\n", request); tmprequest=strdup(request); #ifdef WINSCP_COMPAT bad_winscp3str[57]=10; bad_winscp3str[127]=10; if(strcmp(request,bad_winscp3str)==0) { /* * switch out the command to use, winscp wont know the difference */ free(tmprequest); tmprequest=strdup(PROG_SFTP_SERVER); syslog(LOG_DEBUG, "winscp3 compat correcting to: \"[%s]\"\n", PROG_SFTP_SERVER); } #endif #ifdef GFTP_COMPAT /* * gFTP compatibility hack */ if (NULL != (tmpstring=strbeg(request, "echo -n xsftp ; "))) { free(tmprequest); tmprequest=strdup(tmpstring); printf("xsftp"); fflush(stdout); } #endif #ifdef RESTRICTIVE_FILENAMES /* * we flat out reject special chars */ if (!valid_chars(tmprequest)) { debug(LOG_DEBUG, "rejected because of invalid chars (%s)", logstamp()); free(tmprequest); return(-1); } #endif #ifdef WINSCP_COMPAT if (strbeg(tmprequest,PROG_CD)) { char *destdir=(char *)malloc(reqlen); if (destdir == NULL) { perror("malloc"); exit(EXIT_FAILURE); } /* * well, now that scponly is a persistent shell * i have to maintain a $PWD. damn. * we're going to INSIST upon a double quote * encapsulated new directory to change to. */ if ((tmprequest[(reqlen-1)]=='"') && (tmprequest[3]=='"')) { bzero(destdir,reqlen); strncpy(destdir,&tmprequest[4],reqlen-5); debug(LOG_INFO, "chdir: %s (%s)", tmprequest, logstamp()); retval=chdir(destdir); free(destdir); free(tmprequest); return(retval); } syslog(LOG_ERR, "bogus chdir request: %s (%s)", tmprequest, logstamp()); free(tmprequest); return(-1); } #endif /* * convert request string to an arg_vector */ av = build_arg_vector(tmprequest); /* * clean any path info from request and substitute our known pathnames */ av[0] = substitute_known_path(av[0]); /* * we only process wildcards for scp commands */ #ifdef ENABLE_WILDCARDS #ifdef ENABLE_SCP2 if (exact_match(av[0],PROG_SCP)) av = expand_wildcards(av); #endif #endif /* * check for a compile time chdir configuration */ #ifdef ENABLE_DEFAULT_CHDIR if (exact_match(av[0],PROG_SFTP_SERVER)) { syslog(LOG_INFO, "changing initial directory to %s", DEFAULT_CHDIR); chdir(DEFAULT_CHDIR); } #endif flat_request = flatten_vector(av); /* * Use a temp arg vector since getopt will permute the command line arguments * for anything that it does not know about. If all rsync options are well * defined this isn't necessary. */ tmp_av = build_arg_vector(flat_request); if(check_dangerous_args(tmp_av)) { syslog(LOG_ERR, "requested command (%s) tried to use disallowed argument (%s))", flat_request, logstamp()); exit(EXIT_FAILURE); } discard_vector(tmp_av); if (valid_arg_vector(av)) { /* * Unison needs the HOME environment variable be set to the directory * where the .unison directory resides. */ #ifdef USE_SAFE_ENVIRONMENT safeenv[0] = NULL; filter_allowed_env_vars(); tenv = safeenv; if (debuglevel) { while (NULL != *tenv) { syslog(LOG_DEBUG, "Environment contains \"%s\"", *tenv++); } } env = safeenv; #endif #ifdef UNISON_COMPAT /* the HOME environment variable should have been set above, but I need to make sure * that it's value as read from the environment is replaced with the actual value * as it exists within the chroot, which is what the applications will expect to see. */ if (replace_env_entry("HOME",homedir) && (((strlen(homedir) + 6 ) > FILENAME_MAX) || !mysetenv("HOME",homedir))) { syslog(LOG_ERR, "could not set HOME environment variable (%s)", logstamp()); exit(EXIT_FAILURE); } debug(LOG_DEBUG, "set non-chrooted HOME environment variable to %s (%s)", homedir, logstamp()); #endif syslog(LOG_INFO, "running: %s (%s)", flat_request, logstamp()); #ifdef WINSCP_COMPAT if (winscp_mode) { int status=0; if (fork() == 0) retval=execve(av[0],av,env); else { wait(&status); fflush(stdout); fflush(stderr); discard_vector(av); #ifdef USE_SAFE_ENVIRONMENT discard_child_vectors(safeenv); #endif free(flat_request); free(tmprequest); return(WEXITSTATUS(status)); } } else #endif { debug(LOG_DEBUG, "about to exec \"%s\" (%s)", av[0], logstamp()); retval=execve(av[0],av,env); } syslog(LOG_ERR, "failed: %s with error %s(%u) (%s)", flat_request, strerror(errno), errno, logstamp()); free(flat_request); discard_vector(av); #ifdef USE_SAFE_ENVIRONMENT discard_child_vectors(safeenv); #endif #ifdef WINSCP_COMPAT if (winscp_mode) { free(tmprequest); return(-1); } else #endif exit(errno); } /* * reaching this point in the code means the request isnt one of * our accepted commands */ if (debuglevel) { if (exact_match(flat_request,tmprequest)) syslog (LOG_ERR, "denied request: %s [%s]", tmprequest, logstamp()); else syslog (LOG_ERR, "denied request: %s (resolved to: %s) [%s]", tmprequest, flat_request, logstamp()); } free(flat_request); #ifdef WINSCP_COMPAT if (winscp_mode) { printf ("command not permitted by scponly\n"); free(tmprequest); return(-1); } else #endif exit(EXIT_FAILURE); }
static readstat_error_t recursive_discard(rdata_sexptype_header_t sexptype_header, rdata_ctx_t *ctx) { uint32_t length; rdata_sexptype_info_t info; rdata_sexptype_info_t prot, tag; readstat_error_t error = 0; int i; switch (sexptype_header.type) { case RDATA_SEXPTYPE_SYMBOL: if ((error = read_sexptype_header(&info, ctx)) != READSTAT_OK) goto cleanup; if ((error = recursive_discard(info.header, ctx)) != READSTAT_OK) goto cleanup; break; case RDATA_PSEUDO_SXP_PERSIST: case RDATA_PSEUDO_SXP_NAMESPACE: case RDATA_PSEUDO_SXP_PACKAGE: if ((error = read_sexptype_header(&info, ctx)) != READSTAT_OK) goto cleanup; if ((error = recursive_discard(info.header, ctx)) != READSTAT_OK) goto cleanup; break; case RDATA_SEXPTYPE_BUILTIN_FUNCTION: case RDATA_SEXPTYPE_SPECIAL_FUNCTION: error = discard_character_string(0, ctx); break; case RDATA_SEXPTYPE_PAIRLIST: error = discard_pairlist(sexptype_header, ctx); break; case RDATA_SEXPTYPE_CHARACTER_STRING: error = discard_character_string(1, ctx); break; case RDATA_SEXPTYPE_RAW_VECTOR: error = discard_vector(sexptype_header, 1, ctx); break; case RDATA_SEXPTYPE_LOGICAL_VECTOR: error = discard_vector(sexptype_header, 4, ctx); break; case RDATA_SEXPTYPE_INTEGER_VECTOR: error = discard_vector(sexptype_header, 4, ctx); break; case RDATA_SEXPTYPE_REAL_VECTOR: error = discard_vector(sexptype_header, 8, ctx); break; case RDATA_SEXPTYPE_COMPLEX_VECTOR: error = discard_vector(sexptype_header, 16, ctx); break; case RDATA_SEXPTYPE_CHARACTER_VECTOR: if (read_st(ctx, &length, sizeof(length)) != sizeof(length)) { return READSTAT_ERROR_READ; } if (ctx->machine_needs_byteswap) length = byteswap4(length); for (i=0; i<length; i++) { error = read_sexptype_header(&info, ctx); if (error != READSTAT_OK) goto cleanup; if (info.header.type != RDATA_SEXPTYPE_CHARACTER_STRING) { error = READSTAT_ERROR_PARSE; goto cleanup; } error = discard_character_string(0, ctx); if (error != READSTAT_OK) goto cleanup; } break; case RDATA_SEXPTYPE_GENERIC_VECTOR: case RDATA_SEXPTYPE_EXPRESSION_VECTOR: if (read_st(ctx, &length, sizeof(length)) != sizeof(length)) { return READSTAT_ERROR_READ; } if (ctx->machine_needs_byteswap) length = byteswap4(length); for (i=0; i<length; i++) { if ((error = read_sexptype_header(&info, ctx)) != READSTAT_OK) goto cleanup; if ((error = recursive_discard(info.header, ctx)) != READSTAT_OK) goto cleanup; } if (sexptype_header.attributes) { if ((error = read_attributes(NULL, ctx)) != READSTAT_OK) goto cleanup; } break; case RDATA_SEXPTYPE_DOT_DOT_DOT: case RDATA_SEXPTYPE_PROMISE: case RDATA_SEXPTYPE_LANGUAGE_OBJECT: case RDATA_SEXPTYPE_CLOSURE: if (sexptype_header.attributes) { if ((error = read_sexptype_header(&info, ctx)) != READSTAT_OK) goto cleanup; if ((error = recursive_discard(info.header, ctx)) != READSTAT_OK) goto cleanup; } if (sexptype_header.tag) { if ((error = read_sexptype_header(&info, ctx)) != READSTAT_OK) goto cleanup; if ((error = recursive_discard(info.header, ctx)) != READSTAT_OK) goto cleanup; } /* CAR */ if ((error = read_sexptype_header(&info, ctx)) != READSTAT_OK) goto cleanup; if ((error = recursive_discard(info.header, ctx)) != READSTAT_OK) goto cleanup; /* CDR */ if ((error = read_sexptype_header(&info, ctx)) != READSTAT_OK) goto cleanup; if ((error = recursive_discard(info.header, ctx)) != READSTAT_OK) goto cleanup; break; case RDATA_SEXPTYPE_EXTERNAL_POINTER: read_sexptype_header(&prot, ctx); recursive_discard(prot.header, ctx); read_sexptype_header(&tag, ctx); recursive_discard(tag.header, ctx); break; case RDATA_SEXPTYPE_ENVIRONMENT: /* locked */ if (lseek_st(ctx, sizeof(uint32_t)) == -1) { return READSTAT_ERROR_READ; } rdata_sexptype_info_t enclosure, frame, hash_table, attributes; read_sexptype_header(&enclosure, ctx); recursive_discard(enclosure.header, ctx); read_sexptype_header(&frame, ctx); recursive_discard(frame.header, ctx); read_sexptype_header(&hash_table, ctx); recursive_discard(hash_table.header, ctx); read_sexptype_header(&attributes, ctx); recursive_discard(attributes.header, ctx); /* if (sexptype_header.attributes) { if (lseek(ctx->fd, sizeof(uint32_t), SEEK_CUR) == -1) { return READSTAT_ERROR_READ; } } */ break; case RDATA_PSEUDO_SXP_REF: case RDATA_PSEUDO_SXP_NIL: case RDATA_PSEUDO_SXP_GLOBAL_ENVIRONMENT: case RDATA_PSEUDO_SXP_UNBOUND_VALUE: case RDATA_PSEUDO_SXP_MISSING_ARGUMENT: case RDATA_PSEUDO_SXP_BASE_NAMESPACE: case RDATA_PSEUDO_SXP_EMPTY_ENVIRONMENT: case RDATA_PSEUDO_SXP_BASE_ENVIRONMENT: break; default: return READSTAT_ERROR_READ; } cleanup: return error; }
int main (int argc, char **argv) { FILE *debugfile; int logopts = LOG_PID|LOG_NDELAY; int chars_read = 0; #ifdef CHROOT_CHECKDIR struct stat homedirstat; #endif /* * set debuglevel. any nonzero number will result in debugging info to log */ if (NULL!=(debugfile=fopen(DEBUGFILE,"r"))) { chars_read = fscanf(debugfile,"%d",&debuglevel); if (chars_read < 1) debuglevel = 0; fclose(debugfile); } #ifndef UNIX_COMPAT if (debuglevel > 1) /* debuglevel 1 will still log to syslog */ logopts |= LOG_PERROR; #endif #ifdef UNIX_COMPAT openlog(PACKAGE_NAME, logopts, LOG_AUTH); #elif IRIX_COMPAT openlog(PACKAGE_NAME, logopts, LOG_AUTH); #else if (debuglevel > 1) /* debuglevel 1 will still log to syslog */ logopts |= LOG_PERROR; openlog(PACKAGE_NAME, logopts, LOG_AUTHPRIV); #endif if (debuglevel > 0) debug = syslog; else debug = noop_syslog; #ifdef HAVE_GETOPT_H scponly_getopt_long = getopt_long; #else debug(LOG_INFO, "using netbsd's bundled getopt_long"); scponly_getopt_long = netbsd_getopt_long; #endif #ifdef CHROOTED_NAME /* * is this a chroot'ed scponly installation? */ #ifdef WINSCP_COMPAT if ((argc==3 && (0==strncmp(argv[0],CHROOTED_NAME,FILENAME_MAX)) ) || ( argc==1 && (0==strncmp(&argv[0][1],CHROOTED_NAME,FILENAME_MAX )))) #else if (0==strncmp(argv[0],CHROOTED_NAME,FILENAME_MAX)) #endif { debug(LOG_INFO, "chrooted binary in place, will chroot()"); chrooted=1; } #endif /* CHROOTED_NAME */ if (debuglevel) { int i; syslog(LOG_DEBUG, "%d arguments in total.", argc); for (i=0;i<argc;i++) syslog(LOG_DEBUG, "\targ %u is %s", i, argv[i]); } #ifdef UNIX_COMPAT debug(LOG_DEBUG, "opened log at LOG_AUTH, opts 0x%08x", logopts); #else debug(LOG_DEBUG, "opened log at LOG_AUTHPRIV, opts 0x%08x", logopts); #endif if (getuid()==0) { syslog(LOG_ERR, "root login denied [%s]", logstamp()); exit(EXIT_FAILURE); } #ifdef WINSCP_COMPAT if ((argc!=3) && (argc!=1)) #else if (argc!=3) #endif { debug(LOG_ERR, "incorrect number of args"); exit(EXIT_FAILURE); } if (!get_uservar()) { syslog(LOG_ERR, "%s is misconfigured. contact sysadmin.", argv[0]); exit (EXIT_FAILURE); } #ifdef CHROOTED_NAME if (chrooted) { char **av = NULL; char *tmprequest = NULL; char *root_dir = chrootdir; char chdir_path[FILENAME_MAX]; strcpy(chrootdir, homedir); strcpy(chdir_path, "/"); while((root_dir = strchr(root_dir, '/')) != NULL) { if (strncmp(root_dir, "//", 2) == 0) { snprintf(chdir_path, FILENAME_MAX, "%s", root_dir + 1); /* make sure HOME will be set to something correct if used*/ debug(LOG_DEBUG, "Setting homedir to %s", chdir_path); strcpy(homedir, chdir_path); *root_dir = '\0'; break; } root_dir++; } #ifdef CHROOT_CHECKDIR bzero(&homedirstat, sizeof(struct stat)); if (-1 == stat(chrootdir, &homedirstat)) { syslog (LOG_ERR, "couldnt stat chroot dir: %s with errno %u", chrootdir, errno); exit(EXIT_FAILURE); } if (0 == (homedirstat.st_mode | S_IFDIR)) { syslog (LOG_ERR, "chroot dir is not a directory: %s", chrootdir); exit(EXIT_FAILURE); } if (homedirstat.st_uid != 0) { syslog (LOG_ERR, "chroot dir not owned by root: %s", chrootdir); exit(EXIT_FAILURE); } if (0 != (homedirstat.st_mode & S_IWOTH)) { syslog (LOG_ERR, "chroot dir writable by other: %s", chrootdir); exit(EXIT_FAILURE); } if (0 != (homedirstat.st_mode & S_IWGRP)) { syslog (LOG_ERR, "chroot dir writable by group: %s", chrootdir); exit(EXIT_FAILURE); } #endif /* already within CHROOTED_NAME block */ #if defined(PASSWD_COMPAT) || defined(QUOTA_COMPAT) /* * perhaps we need to refactor so we don't have to exit right * in the middle of the code, but we can't chroot and expect to be * able to change the password and have it be of any use unless * there is some additional process that scponly is unaware of * happening on the back end. */ tmprequest = strdup(argv[2]); av = build_arg_vector(tmprequest); free(tmprequest); if ( #ifdef PASSWD_COMPAT (exact_match(av[0],"passwd")) || (exact_match(av[0],PROG_PASSWD)) #else 0 #endif #ifdef QUOTA_COMPAT || (exact_match(av[0],"quota")) || (exact_match(av[0],PROG_QUOTA)) #endif ) { int status = process_pre_chroot_request(av); discard_vector(av); if (status) { syslog(LOG_ERR, "process_pre_chroot_request(%s) failed with code %i [%s]", argv[2],WEXITSTATUS(status),logstamp()); exit(EXIT_FAILURE); } debug(LOG_DEBUG, "scponly completed"); exit(EXIT_SUCCESS); } else { discard_vector(av); } #endif /* passwd or quota */ debug(LOG_DEBUG, "chrooting to dir: \"%s\"", chrootdir); if (-1==(chroot(chrootdir))) { debug(LOG_ERR, "chroot: %m"); syslog(LOG_ERR, "couldn't chroot to %s [%s]", chrootdir, logstamp()); exit(EXIT_FAILURE); } debug(LOG_DEBUG, "chdiring to dir: \"%s\"", chdir_path); if (-1==(chdir(chdir_path))) { debug(LOG_ERR, "chdir: %m"); syslog (LOG_ERR, "couldn't chdir to %s [%s]", chdir_path, logstamp()); exit(EXIT_FAILURE); } } #endif /* CHROOTED_NAME */ debug(LOG_DEBUG, "setting uid to %u", getuid()); if (-1==(seteuid(getuid()))) { syslog(LOG_ERR, "couldn't revert to my real uid. seteuid: %m"); exit(EXIT_FAILURE); } #ifdef WINSCP_COMPAT if (argc==1) { debug(LOG_DEBUG, "entering WinSCP compatibility mode [%s]",logstamp()); if (-1==process_winscp_requests()) { syslog(LOG_ERR, "failed WinSCP compatibility mode [%s]", logstamp()); exit(EXIT_FAILURE); } } #else if (0) {} /* placeholder */ #endif else if (-1==process_ssh_request(argv[2])) { syslog(LOG_ERR, "bad request: %s [%s]", argv[2], logstamp()); exit(EXIT_FAILURE); } debug(LOG_DEBUG, "scponly completed"); exit(EXIT_SUCCESS); }