示例#1
0
文件: fs_home.c 项目: h1z1/firejail
// check new private home directory (--private= option) - exit if it fails
void fs_check_private_dir(void) {
    invalid_filename(cfg.home_private);

    // Expand the home directory
    char *tmp = expand_home(cfg.home_private, cfg.homedir);
    cfg.home_private = realpath(tmp, NULL);
    free(tmp);

    if (!cfg.home_private
            || !is_dir(cfg.home_private)
            || is_link(cfg.home_private)
            || strstr(cfg.home_private, "..")) {
        fprintf(stderr, "Error: invalid private directory\n");
        exit(1);
    }

    // check home directory and chroot home directory have the same owner
    struct stat s2;
    int rv = stat(cfg.home_private, &s2);
    if (rv < 0) {
        fprintf(stderr, "Error: cannot find %s directory\n", cfg.home_private);
        exit(1);
    }

    struct stat s1;
    rv = stat(cfg.homedir, &s1);
    if (rv < 0) {
        fprintf(stderr, "Error: cannot find %s directory, full path name required\n", cfg.homedir);
        exit(1);
    }
    if (s1.st_uid != s2.st_uid) {
        printf("Error: --private directory should be owned by the current user\n");
        exit(1);
    }
}
示例#2
0
文件: fs_etc.c 项目: h1z1/firejail
// return 0 if file not found, 1 if found
static int check_dir_or_file(const char *name) {
	assert(name);
	invalid_filename(name);
	
	struct stat s;
	char *fname;
	if (asprintf(&fname, "/etc/%s", name) == -1)
		errExit("asprintf");
	if (arg_debug)
		printf("Checking %s\n", fname);		
	if (stat(fname, &s) == -1) {
		if (arg_debug)
			printf("Warning: file %s not found.\n", fname);
		return 0;
	}
	
	// dir or regular file
	if (S_ISDIR(s.st_mode) || S_ISREG(s.st_mode)) {
		free(fname);
		return 1;
	}

	if (!is_link(fname)) {
		free(fname);
		return 1;
	}
	
	fprintf(stderr, "Error: invalid file type, %s.\n", fname);
	exit(1);
}
示例#3
0
//***********************************************************************************
// --private-home
//***********************************************************************************
static char *check_dir_or_file(const char *name) {
	assert(name);

	// basic checks
	invalid_filename(name);
	if (arg_debug)
		printf("Private home: checking %s\n", name);

	// expand home directory
	char *fname = expand_home(name, cfg.homedir);
	assert(fname);

	// If it doesn't start with '/', it must be relative to homedir
	if (fname[0] != '/') {
		char* tmp;
		if (asprintf(&tmp, "%s/%s", cfg.homedir, fname) == -1)
			errExit("asprintf");
		free(fname);
		fname = tmp;
	}

	// we allow only files in user home directory or symbolic links to files or directories owned by the user
	struct stat s;
	if (lstat(fname, &s) == 0 && S_ISLNK(s.st_mode)) {
		if (stat(fname, &s) == 0) {	
			if (s.st_uid != getuid()) {
				fprintf(stderr, "Error: symbolic link %s to file or directory not owned by the user\n", fname);
				exit(1);
			}
			return fname;
		}
		else {
			fprintf(stderr, "Error: invalid file %s\n", name);
			exit(1);
		}
	}
	else {
		// check the file is in user home directory, a full home directory is not allowed
		char *rname = realpath(fname, NULL);
		if (!rname ||
		    strncmp(rname, cfg.homedir, strlen(cfg.homedir)) != 0 ||
		    strcmp(rname, cfg.homedir) == 0) {
			fprintf(stderr, "Error: invalid file %s\n", name);
			exit(1);
		}
		
		// only top files and directories in user home are allowed
		char *ptr = rname + strlen(cfg.homedir);
		assert(*ptr != '\0');
		ptr = strchr(++ptr, '/');
		if (ptr) {
			if (*ptr != '\0') {
				fprintf(stderr, "Error: only top files and directories in user home are allowed\n");
				exit(1);
			}
		}
		free(fname);
		return rname;
	}
}
示例#4
0
文件: fs_bin.c 项目: boite/firejail
// return 1 if found, 0 if not found
static char *check_dir_or_file(const char *name) {
	assert(name);
	invalid_filename(name);
	
	struct stat s;
	char *fname = NULL;
	
	int i = 0;
	while (paths[i]) {
		if (asprintf(&fname, "%s/%s", paths[i], name) == -1)
			errExit("asprintf");
		if (arg_debug)
			printf("Checking %s/%s\n", paths[i], name);		
		if (stat(fname, &s) == 0 && !S_ISDIR(s.st_mode)) // do not allow directories
			break; // file found
		
		free(fname);
		fname = NULL;
		i++;
	}

	if (!fname) {
//		fprintf(stderr, "Warning: file %s not found\n", name);
		return NULL;
	}
	
	free(fname);
	return paths[i];
}
示例#5
0
文件: midlevel.c 项目: 007/afpfs-ng
int ml_utime(struct afp_volume * vol, const char * path, 
	struct utimbuf * timebuf)
{

	int ret=0;
	unsigned int dirid;
	struct afp_file_info fp;
	char basename[AFP_MAX_PATH];
	char converted_path[AFP_MAX_PATH];
	int rc;

	if (volume_is_readonly(vol))
		return -EACCES;

	memset(&fp,0,sizeof(struct afp_file_info));

	fp.modification_date=timebuf->modtime;

	if (invalid_filename(vol->server,path)) 
		return -ENAMETOOLONG;

	if (convert_path_to_afp(vol->server->path_encoding,
		converted_path,(char *) path,AFP_MAX_PATH)) {
		return -EINVAL;
	}

	ret=appledouble_utime(vol,path,timebuf);
	if (ret<0) return ret;
	if (ret==1) return 0;

	get_dirid(vol,converted_path,basename,&dirid );

	if (is_dir(vol,dirid,basename)) {
		rc=afp_setdirparms(vol,
			dirid,basename, kFPModDateBit, &fp);
	} else {
		rc=afp_setfileparms(vol,
			dirid,basename, kFPModDateBit, &fp);
	}

	switch(rc) {
	case kFPNoErr:
		break;
	case kFPAccessDenied:
		return -EACCES;
	case kFPObjectNotFound:
		return -ENOENT;
	case kFPBitmapErr:
	case kFPMiscErr:
	case kFPObjectTypeErr:
	case kFPParamErr:
	default:
		break;

	}

	return -ret;
}
示例#6
0
void check_netfilter_file(const char *fname) {
    EUID_ASSERT();
    invalid_filename(fname);

    if (is_dir(fname) || is_link(fname) || strstr(fname, "..") || access(fname, R_OK )) {
        fprintf(stderr, "Error: invalid network filter file %s\n", fname);
        exit(1);
    }
}
示例#7
0
文件: midlevel.c 项目: 007/afpfs-ng
int ml_rmdir(struct afp_volume * vol, const char *path)
{
	int ret,rc;
	unsigned int dirid;
	char basename[AFP_MAX_PATH];
	char converted_path[AFP_MAX_PATH];

	if (invalid_filename(vol->server,path)) 
		return -ENAMETOOLONG;

	if (convert_path_to_afp(vol->server->path_encoding,
		converted_path,(char *) path,AFP_MAX_PATH))
		return -EINVAL;

	if (volume_is_readonly(vol))
		return -EACCES;
	
	ret=appledouble_rmdir(vol,path);
	if (ret<0) return ret;
	if (ret==1) return 0;

	get_dirid(vol, converted_path, basename, &dirid);

	if (!is_dir(vol,dirid,basename)) return -ENOTDIR;

	rc=afp_delete(vol,dirid,basename);

	switch(rc) {
	case kFPAccessDenied:
		ret=EACCES;
		break;
	case kFPObjectLocked:
		ret=EBUSY;
		break;
	case kFPObjectNotFound:
		ret=ENOENT;
		break;
	case kFPVolLocked:
		ret=EACCES;
		break;
	case kFPDirNotEmpty:
		ret=ENOTEMPTY;
		break;
	case kFPObjectTypeErr:
	case kFPMiscErr:
	case kFPParamErr:
	case -1:
		ret=EINVAL;
		break;
	default:
		remove_did_entry(vol,converted_path);
		ret=0;
	}
	return -ret;
}
示例#8
0
文件: midlevel.c 项目: 007/afpfs-ng
int ml_mkdir(struct afp_volume * vol, const char * path, mode_t mode) 
{
	int ret,rc;
	unsigned int result_did;
	char basename[AFP_MAX_PATH];
	char converted_path[AFP_MAX_PATH];
	unsigned int dirid;

	if (convert_path_to_afp(vol->server->path_encoding,
		converted_path,(char *) path,AFP_MAX_PATH))
		return -EINVAL;

	if (invalid_filename(vol->server,path)) 
		return -ENAMETOOLONG;

	if (volume_is_readonly(vol))
		return -EACCES;

	ret=appledouble_mkdir(vol,path,mode);
	if (ret<0) return ret;
	if (ret==1) return 0;

	get_dirid(vol,converted_path,basename,&dirid);

	rc = afp_createdir(vol,dirid, basename,&result_did);

	switch (rc) {
	case kFPAccessDenied:
		ret = EACCES;
		break;
	case kFPDiskFull:
		ret = ENOSPC;
		break;
	case kFPObjectNotFound:
		ret = ENOENT;
		break;
	case kFPObjectExists:
		ret = EEXIST;
		break;
	case kFPVolLocked:
		ret = EBUSY;
		break;
	case kFPFlatVol:
	case kFPMiscErr:
	case kFPParamErr:
	case -1:
		ret = EFAULT;
		break;
	default:
		ret =0;
	}

	return -ret;
}
示例#9
0
文件: midlevel.c 项目: 007/afpfs-ng
int ml_open(struct afp_volume * volume, const char *path, int flags, 
	struct afp_file_info **newfp)
{

/* FIXME:  doesn't handle create properly */

	struct afp_file_info * fp ;
	int ret;
	unsigned int dirid;
	char converted_path[AFP_MAX_PATH];

	if (convert_path_to_afp(volume->server->path_encoding,
		converted_path,(char *) path,AFP_MAX_PATH))
		return -EINVAL;

	if (invalid_filename(volume->server,converted_path))
		return -ENAMETOOLONG;

	if (volume_is_readonly(volume) && 
		(flags & (O_WRONLY|O_RDWR|O_TRUNC|O_APPEND|O_CREAT))) 
		return -EACCES;

	if ((fp=malloc(sizeof(*fp)))==NULL) {
		return -1;
	}
	*newfp=fp;

	memset(fp,0,sizeof(*fp));

	ret=appledouble_open(volume,path,flags,fp);
	if (ret<0) return ret;
	if (ret==1) goto out;
	

	if (get_dirid(volume,converted_path,fp->basename,&dirid)<0)
		return -ENOENT;

	fp->did=dirid;

	ret=ll_open(volume,converted_path,flags,fp);

	if (ret<0) goto error;


out:
	return 0;

error:
	free(fp);
	return ret;
}
示例#10
0
// return 1 if found, 0 if not found
static char *check_dir_or_file(const char *name) {
	assert(name);
	invalid_filename(name);
	
	struct stat s;
	char *fname = NULL;
	
	int i = 0;
	while (paths[i]) {
		if (asprintf(&fname, "%s/%s", paths[i], name) == -1)
			errExit("asprintf");
		if (arg_debug)
			printf("Checking %s/%s\n", paths[i], name);		
		if (stat(fname, &s) == 0 && !S_ISDIR(s.st_mode)) { // do not allow directories
			// check symlink to firejail executable in /usr/local/bin
			if (strcmp(paths[i], "/usr/local/bin") == 0 && is_link(fname)) {
				char *actual_path = realpath(fname, NULL);
				if (actual_path) {
					char *ptr = strstr(actual_path, "/firejail");
					if (ptr && strlen(ptr) == strlen("/firejail")) {
						if (arg_debug)
							printf("firejail exec symlink detected\n");
						free(actual_path);
						free(fname);
						fname = NULL;
						i++;
						continue;
					}
					free(actual_path);
				}
				
			}		
			break; // file found
		}
		
		free(fname);
		fname = NULL;
		i++;
	}

	if (!fname) {
		if (arg_debug)
			fprintf(stderr, "Warning: file %s not found\n", name);
		return NULL;
	}
	
	free(fname);
	return paths[i];
}
示例#11
0
static void check_dir_or_file(const char *name) {
	assert(name);
	struct stat s;
	
	invalid_filename(name);
	
	
	char *fname = expand_home(name, cfg.homedir);
	if (!fname) {
		fprintf(stderr, "Error: file %s not found.\n", name);
		exit(1);
	}
	if (fname[0] != '/') {
		// If it doesn't start with '/', it must be relative to homedir
		char* tmp;
		if (asprintf(&tmp, "%s/%s", cfg.homedir, fname) == -1)
			errExit("asprintf");
		free(fname);
		fname = tmp;
	}
	if (arg_debug)
		printf("Checking %s\n", fname);		
	if (stat(fname, &s) == -1) {
		fprintf(stderr, "Error: file %s not found.\n", fname);
		exit(1);
	}
	
	// check uid
	uid_t uid = getuid();
	gid_t gid = getgid();
	if (s.st_uid != uid || s.st_gid != gid) {
		fprintf(stderr, "Error: only files or directories created by the current user are allowed.\n");
		exit(1);
	}

	// dir or regular file
	if (S_ISDIR(s.st_mode) || S_ISREG(s.st_mode)) {
		free(fname);
		return;
	}

	if (!is_link(fname)) {
		free(fname);
		return;
	}
	
	fprintf(stderr, "Error: invalid file type, %s.\n", fname);
	exit(1);
}
示例#12
0
文件: cgroup.c 项目: maces/firejail
void set_cgroup(const char *path) {
	EUID_ASSERT();
	
	invalid_filename(path);
	
	// path starts with /sys/fs/cgroup
	if (strncmp(path, "/sys/fs/cgroup", 14) != 0)
		goto errout;
	
	// path ends in tasks
	char *ptr = strstr(path, "tasks");
	if (!ptr)
		goto errout;
	if (*(ptr + 5) != '\0')
		goto errout;
	
	// no .. traversal
	ptr = strstr(path, "..");
	if (ptr)
		goto errout;
	
	// tasks file exists
	struct stat s;
	if (stat(path, &s) == -1)
		goto errout;
	
	// task file belongs to the user running the sandbox
	if (s.st_uid != getuid() && s.st_gid != getgid())
		goto errout2;
		
	// add the task to cgroup
	/* coverity[toctou] */
	FILE *fp = fopen(path,	"a");
	if (!fp)
		goto errout;
	pid_t pid = getpid();
	int rv = fprintf(fp, "%d\n", pid);
	(void) rv;
	fclose(fp);
	return;

errout:		
	fprintf(stderr, "Error: invalid cgroup\n");
	exit(1);
errout2:		
	fprintf(stderr, "Error: you don't have permissions to use this control group\n");
	exit(1);
}
示例#13
0
文件: midlevel.c 项目: 007/afpfs-ng
int ml_truncate(struct afp_volume * vol, const char * path, off_t offset)
{
	int ret=0;
	char converted_path[AFP_MAX_PATH];
	struct afp_file_info *fp;
	int flags;

	if (convert_path_to_afp(vol->server->path_encoding,
		converted_path,(char *) path,AFP_MAX_PATH))
		return -EINVAL;

	/* The approach here is to get the forkid by calling ml_open()
	   (and not afp_openfork).  Note the fake afp_file_info used
	   just to grab this forkid. */

	if (invalid_filename(vol->server,converted_path)) 
		return -ENAMETOOLONG;

	if (volume_is_readonly(vol))
		return -EACCES;

	ret=appledouble_truncate(vol,path,offset);
	if (ret<0) return ret;
	if (ret==1) return 0;

	/* Here, we're going to use the untranslated path since it is
	   translated through the ml_open() */

	flags=O_WRONLY;
	if ((ml_open(vol,path,flags,&fp))) {
		return ret;
	};

	if ((ret=ll_zero_file(vol,fp->forkid,0)))
		goto out;

	afp_closefork(vol,fp->forkid);
	remove_opened_fork(vol, fp);
	free(fp);

out:
	return -ret;
}
示例#14
0
void fs_mkfile(const char *name) {
	EUID_ASSERT();
	
	// check file name
	invalid_filename(name);
	char *expanded = expand_home(name, cfg.homedir);
	if (strncmp(expanded, cfg.homedir, strlen(cfg.homedir)) != 0) {
		fprintf(stderr, "Error: only files in user home are supported by mkfile\n");
		exit(1);
	}

	struct stat s;
	if (stat(expanded, &s) == 0) {
		// file exists, do nothing
		goto doexit;
	}

	// create file
	pid_t child = fork();
	if (child < 0)
		errExit("fork");
	if (child == 0) {
		// drop privileges
		drop_privs(0);

		FILE *fp = fopen(expanded, "w");
		if (!fp)
			fprintf(stderr, "Warning: cannot create %s file\n", expanded);
		else {
			fclose(fp);
			int rv = chmod(expanded, 0600);
			(void) rv;
		}
		exit(0);
	}
	// wait for the child to finish
	waitpid(child, NULL, 0);

doexit:
	free(expanded);
}
示例#15
0
文件: midlevel.c 项目: 007/afpfs-ng
int ml_close(struct afp_volume * volume, const char * path, 
	struct afp_file_info * fp)
{

	int ret=0;
	char converted_path[AFP_MAX_PATH];

	if (convert_path_to_afp(volume->server->path_encoding,
		converted_path, (char *) path,AFP_MAX_PATH)) {
		return -EINVAL;
	}
 
	if (invalid_filename(volume->server,converted_path)) 
		return -ENAMETOOLONG;

	/* The logic here is that if we don't have an fp anymore, then the
	   fork must already be closed. */
	if (!fp) 
		return EBADF;

	if (fp->icon) {
		free(fp->icon);
	}
	if (fp->resource) {
		return appledouble_close(volume,fp);
	}

	switch(afp_closefork(volume,fp->forkid)) {
		case kFPNoErr:
			break;
		default:
		case kFPParamErr:
		case kFPMiscErr:
			ret=EIO;
			goto error;
	}
	remove_opened_fork(volume, fp);
		
error:
	return ret;
}
示例#16
0
void fs_mkdir(const char *name) {
	EUID_ASSERT();

	// check directory name
	invalid_filename(name, 0); // no globbing
	char *expanded = expand_macros(name);
	if (strncmp(expanded, cfg.homedir, strlen(cfg.homedir)) != 0 &&
	    strncmp(expanded, "/tmp", 4) != 0) {
		fprintf(stderr, "Error: only directories in user home or /tmp are supported by mkdir\n");
		exit(1);
	}

	struct stat s;
	if (stat(expanded, &s) == 0) {
		// file exists, do nothing
		goto doexit;
	}

	// create directory
	pid_t child = fork();
	if (child < 0)
		errExit("fork");
	if (child == 0) {
		// drop privileges
		drop_privs(0);

		// create directory
		mkdir_recursive(expanded);
#ifdef HAVE_GCOV
		__gcov_flush();
#endif
		_exit(0);
	}
	// wait for the child to finish
	waitpid(child, NULL, 0);

doexit:
	free(expanded);
}
示例#17
0
void check_netns(const char *nsname) {
	if (strchr(nsname, '/') || strstr(nsname, "..")) {
		fprintf(stderr, "Error: invalid netns name %s\n", nsname);
		exit(1);
	}
	invalid_filename(nsname, 0); // no globbing
	char *control_file = netns_control_file(nsname);

	EUID_ASSERT();

	struct stat st;
	if (lstat(control_file, &st)) {
		fprintf(stderr, "Error: invalid netns '%s' (%s: %s)\n",
			nsname, control_file, strerror(errno));
		exit(1);
	}
	if (!S_ISREG(st.st_mode)) {
		fprintf(stderr, "Error: invalid netns '%s' (%s: not a regular file)\n",
			nsname, control_file);
		exit(1);
	}
	free(control_file);
}
示例#18
0
void fs_mkdir(const char *name) {
	EUID_ASSERT();
	
	// check directory name
	invalid_filename(name);
	char *expanded = expand_home(name, cfg.homedir);
	if (strncmp(expanded, cfg.homedir, strlen(cfg.homedir)) != 0) {
		fprintf(stderr, "Error: only directories in user home are supported by mkdir\n");
		exit(1);
	}

	struct stat s;
	if (stat(expanded, &s) == 0) {
		// file exists, do nothing
		goto doexit;
	}

	// create directory
	pid_t child = fork();
	if (child < 0)
		errExit("fork");
	if (child == 0) {
		// drop privileges
		drop_privs(0);

		// create directory
		if (mkdir(expanded, 0700) == -1)
			fprintf(stderr, "Warning: cannot create %s directory\n", expanded);
		exit(0);
	}
	// wait for the child to finish
	waitpid(child, NULL, 0);

doexit:
	free(expanded);
}	
示例#19
0
void fs_mkfile(const char *name) {
	EUID_ASSERT();

	// check file name
	invalid_filename(name, 0); // no globbing
	char *expanded = expand_macros(name);
	if (strncmp(expanded, cfg.homedir, strlen(cfg.homedir)) != 0 &&
	    strncmp(expanded, "/tmp", 4) != 0) {
		fprintf(stderr, "Error: only files in user home or /tmp are supported by mkfile\n");
		exit(1);
	}

	struct stat s;
	if (stat(expanded, &s) == 0) {
		// file exists, do nothing
		goto doexit;
	}

	// create file
	touch_file_as_user(expanded, 0600);

doexit:
	free(expanded);
}
示例#20
0
// check profile line; if line == 0, this was generated from a command line option
// return 1 if the command is to be added to the linked list of profile commands
// return 0 if the command was already executed inside the function
int profile_check_line(char *ptr, int lineno, const char *fname) {
	EUID_ASSERT();

	// check ignore list
	int i;
	for (i = 0; i < MAX_PROFILE_IGNORE; i++) {
		if (cfg.profile_ignore[i] == NULL)
			break;

		if (strncmp(ptr, cfg.profile_ignore[i], strlen(cfg.profile_ignore[i])) == 0)
			return 0;	// ignore line
	}

	if (strncmp(ptr, "ignore ", 7) == 0) {
		char *str = strdup(ptr + 7);
		if (*str == '\0') {
			fprintf(stderr, "Error: invalid ignore option\n");
			exit(1);
		}
		// find an empty entry in profile_ignore array
		int j;
		for (j = 0; j < MAX_PROFILE_IGNORE; j++) {
			if (cfg.profile_ignore[j] == NULL)
				break;
		}
		if (j >= MAX_PROFILE_IGNORE) {
			fprintf(stderr, "Error: maximum %d --ignore options are permitted\n", MAX_PROFILE_IGNORE);
			exit(1);
		}
		// ... and configure it
		else
			cfg.profile_ignore[j] = str;

		return 0;
	}

	// mkdir
	if (strncmp(ptr, "mkdir ", 6) == 0) {
		fs_mkdir(ptr + 6);
		return 1;	// process mkdir again while applying blacklists
	}
	// mkfile
	if (strncmp(ptr, "mkfile ", 7) == 0) {
		fs_mkfile(ptr + 7);
		return 1;	// process mkfile again while applying blacklists
	}
	// sandbox name
	else if (strncmp(ptr, "name ", 5) == 0) {
		cfg.name = ptr + 5;
		if (strlen(cfg.name) == 0) {
			fprintf(stderr, "Error: invalid sandbox name\n");
			exit(1);
		}
		return 0;
	}
	else if (strcmp(ptr, "ipc-namespace") == 0) {
		arg_ipc = 1;
		return 0;
	}
	// seccomp, caps, private, user namespace
	else if (strcmp(ptr, "noroot") == 0) {
#if HAVE_USERNS
		if (checkcfg(CFG_USERNS))
			check_user_namespace();
		else
			warning_feature_disabled("noroot");
#endif

		return 0;
	}
	else if (strcmp(ptr, "nonewprivs") == 0) {
		arg_nonewprivs = 1;
		return 0;
	}
	else if (strcmp(ptr, "seccomp") == 0) {
#ifdef HAVE_SECCOMP
		if (checkcfg(CFG_SECCOMP))
			arg_seccomp = 1;
		else
			warning_feature_disabled("seccomp");
#endif
		return 0;
	}
	else if (strcmp(ptr, "caps") == 0) {
		arg_caps_default_filter = 1;
		return 0;
	}
	else if (strcmp(ptr, "caps.drop all") == 0) {
		arg_caps_drop_all = 1;
		return 0;
	}
	else if (strcmp(ptr, "shell none") == 0) {
		arg_shell_none = 1;
		return 0;
	}
	else if (strcmp(ptr, "tracelog") == 0) {
		arg_tracelog = 1;
		return 0;
	}
	else if (strcmp(ptr, "private") == 0) {
		arg_private = 1;
		return 0;
	}
	if (strncmp(ptr, "private-home ", 13) == 0) {
#ifdef HAVE_PRIVATE_HOME
		if (checkcfg(CFG_PRIVATE_HOME)) {
			if (cfg.home_private_keep) {
				if ( asprintf(&cfg.home_private_keep, "%s,%s", cfg.home_private_keep, ptr + 13) < 0 )
					errExit("asprintf");
			} else
				cfg.home_private_keep = ptr + 13;
			arg_private = 1;
		}
		else
			warning_feature_disabled("private-home");
#endif
		return 0;
	}
	else if (strcmp(ptr, "allusers") == 0) {
		arg_allusers = 1;
		return 0;
	}
	else if (strcmp(ptr, "private-dev") == 0) {
		arg_private_dev = 1;
		return 0;
	}
	else if (strcmp(ptr, "private-tmp") == 0) {
		arg_private_tmp = 1;
		return 0;
	}
	else if (strcmp(ptr, "nogroups") == 0) {
		arg_nogroups = 1;
		return 0;
	}
	else if (strcmp(ptr, "nosound") == 0) {
		arg_nosound = 1;
		return 0;
	}
	else if (strcmp(ptr, "novideo") == 0) {
		arg_novideo = 1;
		return 0;
	}
	else if (strcmp(ptr, "no3d") == 0) {
		arg_no3d = 1;
		return 0;
	}
	else if (strcmp(ptr, "allow-private-blacklist") == 0) {
		arg_allow_private_blacklist = 1;
		return 0;
	}
	else if (strcmp(ptr, "netfilter") == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK))
			arg_netfilter = 1;
		else
			warning_feature_disabled("networking");
#endif
		return 0;
	}
	else if (strncmp(ptr, "netfilter ", 10) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			arg_netfilter = 1;
			arg_netfilter_file = strdup(ptr + 10);
			if (!arg_netfilter_file)
				errExit("strdup");
			check_netfilter_file(arg_netfilter_file);
		}
		else
			warning_feature_disabled("networking");
#endif
		return 0;
	}
	else if (strncmp(ptr, "netfilter6 ", 11) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			arg_netfilter6 = 1;
			arg_netfilter6_file = strdup(ptr + 11);
			if (!arg_netfilter6_file)
				errExit("strdup");
			check_netfilter_file(arg_netfilter6_file);
		}
		else
			warning_feature_disabled("networking");
#endif
		return 0;
	}
	else if (strcmp(ptr, "net none") == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			arg_nonetwork  = 1;
			cfg.bridge0.configured = 0;
			cfg.bridge1.configured = 0;
			cfg.bridge2.configured = 0;
			cfg.bridge3.configured = 0;
			cfg.interface0.configured = 0;
			cfg.interface1.configured = 0;
			cfg.interface2.configured = 0;
			cfg.interface3.configured = 0;
		}
		else
			warning_feature_disabled("networking");
#endif
		return 0;
	}
	else if (strncmp(ptr, "net ", 4) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
#ifdef HAVE_NETWORK_RESTRICTED
			// compile time restricted networking
			if (getuid() != 0) {
				fprintf(stderr, "Error: only \"net none\" is allowed to non-root users\n");
				exit(1);
			}
#endif
			// run time restricted networking
			if (checkcfg(CFG_RESTRICTED_NETWORK) && getuid() != 0) {
				fprintf(stderr, "Error: only \"net none\" is allowed to non-root users\n");
				exit(1);
			}

			if (strcmp(ptr + 4, "lo") == 0) {
				fprintf(stderr, "Error: cannot attach to lo device\n");
				exit(1);
			}

			Bridge *br;
			if (cfg.bridge0.configured == 0)
				br = &cfg.bridge0;
			else if (cfg.bridge1.configured == 0)
				br = &cfg.bridge1;
			else if (cfg.bridge2.configured == 0)
				br = &cfg.bridge2;
			else if (cfg.bridge3.configured == 0)
				br = &cfg.bridge3;
			else {
				fprintf(stderr, "Error: maximum 4 network devices are allowed\n");
				exit(1);
			}
			net_configure_bridge(br, ptr + 4);
		}
		else
			warning_feature_disabled("networking");
#endif
		return 0;
	}

	else if (strncmp(ptr, "veth-name ", 10) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			Bridge *br = last_bridge_configured();
			if (br == NULL) {
				fprintf(stderr, "Error: no network device configured\n");
				exit(1);
			}

			br->veth_name = strdup(ptr + 10);
			if (br->veth_name == NULL)
				errExit("strdup");
			if (*br->veth_name == '\0') {
				fprintf(stderr, "Error: no veth-name configured\n");
				exit(1);
			}
		}
		else
			warning_feature_disabled("networking");
#endif
		return 0;
	}

	else if (strncmp(ptr, "iprange ", 8) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			Bridge *br = last_bridge_configured();
			if (br == NULL) {
				fprintf(stderr, "Error: no network device configured\n");
				exit(1);
			}
			if (br->iprange_start || br->iprange_end) {
				fprintf(stderr, "Error: cannot configure the IP range twice for the same interface\n");
				exit(1);
			}

			// parse option arguments
			char *firstip = ptr + 8;
			char *secondip = firstip;
			while (*secondip != '\0') {
				if (*secondip == ',')
					break;
				secondip++;
			}
			if (*secondip == '\0') {
				fprintf(stderr, "Error: invalid IP range\n");
				exit(1);
			}
			*secondip = '\0';
			secondip++;

			// check addresses
			if (atoip(firstip, &br->iprange_start) || atoip(secondip, &br->iprange_end) ||
			    br->iprange_start >= br->iprange_end) {
				fprintf(stderr, "Error: invalid IP range\n");
				exit(1);
			}
			if (in_netrange(br->iprange_start, br->ip, br->mask) || in_netrange(br->iprange_end, br->ip, br->mask)) {
				fprintf(stderr, "Error: IP range addresses not in network range\n");
				exit(1);
			}
		}
		else
			warning_feature_disabled("networking");
#endif
		return 0;
	}


	else if (strncmp(ptr, "mac ", 4) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			Bridge *br = last_bridge_configured();
			if (br == NULL) {
				fprintf(stderr, "Error: no network device configured\n");
				exit(1);
			}

			if (mac_not_zero(br->macsandbox)) {
				fprintf(stderr, "Error: cannot configure the MAC address twice for the same interface\n");
				exit(1);
			}

			// read the address
			if (atomac(ptr + 4, br->macsandbox)) {
				fprintf(stderr, "Error: invalid MAC address\n");
				exit(1);
			}
		}
		else
			warning_feature_disabled("networking");
#endif
		return 0;
	}

	else if (strncmp(ptr, "mtu ", 4) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			Bridge *br = last_bridge_configured();
			if (br == NULL) {
				fprintf(stderr, "Error: no network device configured\n");
				exit(1);
			}

			if (sscanf(ptr + 4, "%d", &br->mtu) != 1 || br->mtu < 576 || br->mtu > 9198) {
				fprintf(stderr, "Error: invalid mtu value\n");
				exit(1);
			}
		}
		else
			warning_feature_disabled("networking");
#endif
		return 0;
	}

	else if (strncmp(ptr, "ip ", 3) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			Bridge *br = last_bridge_configured();
			if (br == NULL) {
				fprintf(stderr, "Error: no network device configured\n");
				exit(1);
			}
			if (br->arg_ip_none || br->ipsandbox) {
				fprintf(stderr, "Error: cannot configure the IP address twice for the same interface\n");
				exit(1);
			}

			// configure this IP address for the last bridge defined
			if (strcmp(ptr + 3, "none") == 0)
				br->arg_ip_none = 1;
			else {
				if (atoip(ptr + 3, &br->ipsandbox)) {
					fprintf(stderr, "Error: invalid IP address\n");
					exit(1);
				}
			}
		}
		else
			warning_feature_disabled("networking");
#endif
		return 0;
	}

	else if (strncmp(ptr, "ip6 ", 4) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			Bridge *br = last_bridge_configured();
			if (br == NULL) {
				fprintf(stderr, "Error: no network device configured\n");
				exit(1);
			}
			if (br->arg_ip_none || br->ip6sandbox) {
				fprintf(stderr, "Error: cannot configure the IP address twice for the same interface\n");
				exit(1);
			}

			// configure this IP address for the last bridge defined
			// todo: verify ipv6 syntax
			br->ip6sandbox = ptr + 4;
//			if (atoip(argv[i] + 5, &br->ipsandbox)) {
//				fprintf(stderr, "Error: invalid IP address\n");
//				exit(1);
//			}

		}
		else
			warning_feature_disabled("networking");
#endif
		return 0;
	}

	else if (strncmp(ptr, "defaultgw ", 10) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			if (atoip(ptr + 10, &cfg.defaultgw)) {
				fprintf(stderr, "Error: invalid IP address\n");
				exit(1);
			}
		}
		else
			warning_feature_disabled("networking");
#endif
		return 0;
	}

	if (strcmp(ptr, "apparmor") == 0) {
#ifdef HAVE_APPARMOR
		arg_apparmor = 1;
#endif
		return 0;
	}

	if (strncmp(ptr, "protocol ", 9) == 0) {
#ifdef HAVE_SECCOMP
		if (checkcfg(CFG_SECCOMP)) {
			if (cfg.protocol) {
				fwarning("a protocol list is present, the new list \"%s\" will not be installed\n", ptr + 9);
				return 0;
			}

			// store list
			cfg.protocol = strdup(ptr + 9);
			if (!cfg.protocol)
				errExit("strdup");
		}
		else
			warning_feature_disabled("seccomp");
#endif
		return 0;
	}

	if (strncmp(ptr, "env ", 4) == 0) {
		env_store(ptr + 4, SETENV);
		return 0;
	}
	if (strncmp(ptr, "rmenv ", 6) == 0) {
		env_store(ptr + 6, RMENV);
		return 0;
	}

	// seccomp drop list on top of default list
	if (strncmp(ptr, "seccomp ", 8) == 0) {
#ifdef HAVE_SECCOMP
		if (checkcfg(CFG_SECCOMP)) {
			arg_seccomp = 1;
			cfg.seccomp_list = seccomp_check_list(ptr + 8);
		}
		else if (!arg_quiet)
			warning_feature_disabled("seccomp");
#endif

		return 0;
	}

	// seccomp drop list without default list
	if (strncmp(ptr, "seccomp.drop ", 13) == 0) {
#ifdef HAVE_SECCOMP
		if (checkcfg(CFG_SECCOMP)) {
			arg_seccomp = 1;
			cfg.seccomp_list_drop = seccomp_check_list(ptr + 13);
		}
		else
			warning_feature_disabled("seccomp");
#endif
		return 0;
	}

	// seccomp keep list
	if (strncmp(ptr, "seccomp.keep ", 13) == 0) {
#ifdef HAVE_SECCOMP
		if (checkcfg(CFG_SECCOMP)) {
			arg_seccomp = 1;
			cfg.seccomp_list_keep= seccomp_check_list(ptr + 13);
		}
		else
			warning_feature_disabled("seccomp");
#endif
		return 0;
	}

	// caps drop list
	if (strncmp(ptr, "caps.drop ", 10) == 0) {
		arg_caps_drop = 1;
		arg_caps_list = strdup(ptr + 10);
		if (!arg_caps_list)
			errExit("strdup");
		// verify caps list and exit if problems
		caps_check_list(arg_caps_list, NULL);
		return 0;
	}

	// caps keep list
	if (strncmp(ptr, "caps.keep ", 10) == 0) {
		arg_caps_keep = 1;
		arg_caps_list = strdup(ptr + 10);
		if (!arg_caps_list)
			errExit("strdup");
		// verify caps list and exit if problems
		caps_check_list(arg_caps_list, NULL);
		return 0;
	}

	// hostname
	if (strncmp(ptr, "hostname ", 9) == 0) {
		cfg.hostname = ptr + 9;
		return 0;
	}

	// hosts-file
	if (strncmp(ptr, "hosts-file ", 11) == 0) {
		cfg.hosts_file = fs_check_hosts_file(ptr + 11);
		return 0;
	}

	// dns
	if (strncmp(ptr, "dns ", 4) == 0) {
		uint32_t dns;
		if (atoip(ptr + 4, &dns)) {
			fprintf(stderr, "Error: invalid DNS server IP address\n");
			return 1;
		}

		if (cfg.dns1 == 0)
			cfg.dns1 = dns;
		else if (cfg.dns2 == 0)
			cfg.dns2 = dns;
		else if (cfg.dns3 == 0)
			cfg.dns3 = dns;
		else {
			fprintf(stderr, "Error: up to 3 DNS servers can be specified\n");
			return 1;
		}
		return 0;
	}

	// cpu affinity
	if (strncmp(ptr, "cpu ", 4) == 0) {
		read_cpu_list(ptr + 4);
		return 0;
	}

	// nice value
	if (strncmp(ptr, "nice ", 4) == 0) {
		cfg.nice = atoi(ptr + 5);
		if (getuid() != 0 &&cfg.nice < 0)
			cfg.nice = 0;
		arg_nice = 1;
		return 0;
	}

	// cgroup
	if (strncmp(ptr, "cgroup ", 7) == 0) {
		set_cgroup(ptr + 7);
		return 0;
	}

	// writable-etc
	if (strcmp(ptr, "writable-etc") == 0) {
		if (cfg.etc_private_keep) {
			fprintf(stderr, "Error: private-etc and writable-etc are mutually exclusive\n");
			exit(1);
		}
		arg_writable_etc = 1;
		return 0;
	}

	if (strcmp(ptr, "machine-id") == 0) {
		arg_machineid = 1;
		return 0;
	}
	// writable-var
	if (strcmp(ptr, "writable-var") == 0) {
		arg_writable_var = 1;
		return 0;
	}
	if (strcmp(ptr, "writable-var-log") == 0) {
		arg_writable_var_log = 1;
		return 0;
	}

	// private directory
	if (strncmp(ptr, "private ", 8) == 0) {
		cfg.home_private = ptr + 8;
		fs_check_private_dir();
		arg_private = 1;
		return 0;
	}

	if (strcmp(ptr, "x11 none") == 0) {
		arg_x11_block = 1;
		return 0;
	}

	if (strcmp(ptr, "x11 xephyr") == 0) {
#ifdef HAVE_X11
		if (checkcfg(CFG_X11)) {
			char *x11env = getenv("FIREJAIL_X11");
			if (x11env && strcmp(x11env, "yes") == 0) {
				return 0;
			}
			else {
				// start x11
				x11_start_xephyr(cfg.original_argc, cfg.original_argv);
				exit(0);
			}
		}
		else
			warning_feature_disabled("x11");
#endif
		return 0;
	}

	if (strcmp(ptr, "x11 xorg") == 0) {
#ifdef HAVE_X11
		if (checkcfg(CFG_X11))
			arg_x11_xorg = 1;
		else
			warning_feature_disabled("x11");
#endif
		return 0;
	}

	if (strcmp(ptr, "x11 xpra") == 0) {
#ifdef HAVE_X11
		if (checkcfg(CFG_X11)) {
			char *x11env = getenv("FIREJAIL_X11");
			if (x11env && strcmp(x11env, "yes") == 0) {
				return 0;
			}
			else {
				// start x11
				x11_start_xpra(cfg.original_argc, cfg.original_argv);
				exit(0);
			}
		}
		else
			warning_feature_disabled("x11");
#endif
		return 0;
	}

	if (strcmp(ptr, "x11 xvfb") == 0) {
#ifdef HAVE_X11
		if (checkcfg(CFG_X11)) {
			char *x11env = getenv("FIREJAIL_X11");
			if (x11env && strcmp(x11env, "yes") == 0) {
				return 0;
			}
			else {
				// start x11
				x11_start_xvfb(cfg.original_argc, cfg.original_argv);
				exit(0);
			}
		}
		else
			warning_feature_disabled("x11");
#endif
		return 0;
	}

	if (strcmp(ptr, "x11") == 0) {
#ifdef HAVE_X11
		if (checkcfg(CFG_X11)) {
			char *x11env = getenv("FIREJAIL_X11");
			if (x11env && strcmp(x11env, "yes") == 0) {
				return 0;
			}
			else {
				// start x11
				x11_start(cfg.original_argc, cfg.original_argv);
				exit(0);
			}
		}
		else
			warning_feature_disabled("x11");
#endif
		return 0;
	}

	// private /etc list of files and directories
	if (strncmp(ptr, "private-etc ", 12) == 0) {
		if (arg_writable_etc) {
			fprintf(stderr, "Error: --private-etc and --writable-etc are mutually exclusive\n");
			exit(1);
		}
		if (cfg.etc_private_keep) {
			if ( asprintf(&cfg.etc_private_keep, "%s,%s", cfg.etc_private_keep, ptr + 12) < 0 )
				errExit("asprintf");
		} else {
			cfg.etc_private_keep = ptr + 12;
		}
		arg_private_etc = 1;

		return 0;
	}

	// private /opt list of files and directories
	if (strncmp(ptr, "private-opt ", 12) == 0) {
		if (cfg.opt_private_keep) {
			if ( asprintf(&cfg.opt_private_keep, "%s,%s", cfg.opt_private_keep, ptr + 12) < 0 )
				errExit("asprintf");
		} else {
			cfg.opt_private_keep = ptr + 12;
		}
		arg_private_opt = 1;

		return 0;
	}

	// private /srv list of files and directories
	if (strncmp(ptr, "private-srv ", 12) == 0) {
		if (cfg.srv_private_keep) {
			if ( asprintf(&cfg.srv_private_keep, "%s,%s", cfg.srv_private_keep, ptr + 12) < 0 )
				errExit("asprintf");
		} else {
			cfg.srv_private_keep = ptr + 12;
		}
		arg_private_srv = 1;

		return 0;
	}

	// private /bin list of files
	if (strncmp(ptr, "private-bin ", 12) == 0) {
		if (cfg.bin_private_keep) {
			if ( asprintf(&cfg.bin_private_keep, "%s,%s", cfg.bin_private_keep, ptr + 12) < 0 )
				errExit("asprintf");
		} else {
			cfg.bin_private_keep = ptr + 12;
		}
		arg_private_bin = 1;
		return 0;
	}


#ifdef HAVE_OVERLAYFS
	if (strncmp(ptr, "overlay-named ", 14) == 0) {
		if (checkcfg(CFG_OVERLAYFS)) {
			if (cfg.chrootdir) {
				fprintf(stderr, "Error: --overlay and --chroot options are mutually exclusive\n");
				exit(1);
			}
			struct stat s;
			if (stat("/proc/sys/kernel/grsecurity", &s) == 0) {
				fprintf(stderr, "Error: --overlay option is not available on Grsecurity systems\n");
				exit(1);
			}
			arg_overlay = 1;
			arg_overlay_keep = 1;
			arg_overlay_reuse = 1;

			char *subdirname = ptr + 14;
			if (subdirname == '\0') {
				fprintf(stderr, "Error: invalid overlay option\n");
				exit(1);
			}

			// check name
			invalid_filename(subdirname);
			if (strstr(subdirname, "..") || strstr(subdirname, "/")) {
				fprintf(stderr, "Error: invalid overlay name\n");
				exit(1);
			}
			cfg.overlay_dir = fs_check_overlay_dir(subdirname, arg_overlay_reuse);
		}

		return 0;
	} else if (strcmp(ptr, "overlay-tmpfs") == 0) {
		if (checkcfg(CFG_OVERLAYFS)) {
			if (cfg.chrootdir) {
				fprintf(stderr, "Error: --overlay and --chroot options are mutually exclusive\n");
				exit(1);
			}
			struct stat s;
			if (stat("/proc/sys/kernel/grsecurity", &s) == 0) {
				fprintf(stderr, "Error: --overlay option is not available on Grsecurity systems\n");
				exit(1);
			}
			arg_overlay = 1;

			return 0;
		}
	} else if (strcmp(ptr, "overlay") == 0) {
		if (checkcfg(CFG_OVERLAYFS)) {
			if (cfg.chrootdir) {
				fprintf(stderr, "Error: --overlay and --chroot options are mutually exclusive\n");
				exit(1);
			}
			struct stat s;
			if (stat("/proc/sys/kernel/grsecurity", &s) == 0) {
				fprintf(stderr, "Error: --overlay option is not available on Grsecurity systems\n");
				exit(1);
			}
			arg_overlay = 1;
			arg_overlay_keep = 1;

			char *subdirname;
			if (asprintf(&subdirname, "%d", getpid()) == -1)
				errExit("asprintf");
			cfg.overlay_dir = fs_check_overlay_dir(subdirname, arg_overlay_reuse);

			free(subdirname);

			return 0;
		}
	}
#endif

	// filesystem bind
	if (strncmp(ptr, "bind ", 5) == 0) {
#ifdef HAVE_BIND
		if (checkcfg(CFG_BIND)) {
			if (getuid() != 0) {
				fprintf(stderr, "Error: --bind option is available only if running as root\n");
				exit(1);
			}

			// extract two directories
			char *dname1 = ptr + 5;
			char *dname2 = split_comma(dname1); // this inserts a '0 to separate the two dierctories
			if (dname2 == NULL) {
				fprintf(stderr, "Error: missing second directory for bind\n");
				exit(1);
			}

			// check directories
			invalid_filename(dname1);
			invalid_filename(dname2);
			if (strstr(dname1, "..") || strstr(dname2, "..")) {
				fprintf(stderr, "Error: invalid file name.\n");
				exit(1);
			}
			if (is_link(dname1) || is_link(dname2)) {
				fprintf(stderr, "Symbolic links are not allowed for bind command\n");
				exit(1);
			}

			// insert comma back
			*(dname2 - 1) = ',';
			return 1;
		}
		else
			warning_feature_disabled("bind");
#endif
		return 0;
	}

	// rlimit
	if (strncmp(ptr, "rlimit", 6) == 0) {
		if (strncmp(ptr, "rlimit-nofile ", 14) == 0) {
			check_unsigned(ptr + 14, "Error: invalid rlimit in profile file: ");
			sscanf(ptr + 14, "%llu", &cfg.rlimit_nofile);
			arg_rlimit_nofile = 1;
		}
		else if (strncmp(ptr, "rlimit-nproc ", 13) == 0) {
			check_unsigned(ptr + 13, "Error: invalid rlimit in profile file: ");
			sscanf(ptr + 13, "%llu", &cfg.rlimit_nproc);
			arg_rlimit_nproc = 1;
		}
		else if (strncmp(ptr, "rlimit-fsize ", 13) == 0) {
			check_unsigned(ptr + 13, "Error: invalid rlimit in profile file: ");
			sscanf(ptr + 13, "%llu", &cfg.rlimit_fsize);
			arg_rlimit_fsize = 1;
		}
		else if (strncmp(ptr, "rlimit-sigpending ", 18) == 0) {
			check_unsigned(ptr + 18, "Error: invalid rlimit in profile file: ");
			sscanf(ptr + 18, "%llu", &cfg.rlimit_sigpending);
			arg_rlimit_sigpending = 1;
		}
		else {
			fprintf(stderr, "Invalid rlimit option on line %d\n", lineno);
			exit(1);
		}

		return 0;
	}

	if (strncmp(ptr, "join-or-start ", 14) == 0) {
		// try to join by name only
		pid_t pid;
		if (!name2pid(ptr + 14, &pid)) {
			if (!cfg.shell && !arg_shell_none)
				cfg.shell = guess_shell();

			// find first non-option arg
			int i;
			for (i = 1; i < cfg.original_argc && strncmp(cfg.original_argv[i], "--", 2) != 0; i++);

			join(pid, cfg.original_argc,cfg.original_argv, i + 1);
			exit(0);
		}

		// set sandbox name and start normally
		cfg.name = ptr + 14;
		if (strlen(cfg.name) == 0) {
			fprintf(stderr, "Error: invalid sandbox name\n");
			exit(1);
		}
		return 0;
	}

	// rest of filesystem
	if (strncmp(ptr, "blacklist ", 10) == 0)
		ptr += 10;
	else if (strncmp(ptr, "blacklist-nolog ", 16) == 0)
		ptr += 16;
	else if (strncmp(ptr, "noblacklist ", 12) == 0)
		ptr += 12;
	else if (strncmp(ptr, "whitelist ", 10) == 0) {
#ifdef HAVE_WHITELIST
		if (checkcfg(CFG_WHITELIST)) {
			arg_whitelist = 1;
			ptr += 10;
		}
		else
			return 0;
#else
		return 0;
#endif
	}
	else if (strncmp(ptr, "nowhitelist ", 12) == 0)
		ptr += 12;
	else if (strncmp(ptr, "read-only ", 10) == 0)
		ptr += 10;
	else if (strncmp(ptr, "read-write ", 11) == 0)
		ptr += 11;
	else if (strncmp(ptr, "noexec ", 7) == 0)
		ptr += 7;
	else if (strncmp(ptr, "tmpfs ", 6) == 0) {
		if (getuid() != 0) {
			fprintf(stderr, "Error: tmpfs available only when running the sandbox as root\n");
			exit(1);
		}
		ptr += 6;
	}
	else {
		if (lineno == 0)
			fprintf(stderr, "Error: \"%s\" as a command line option is invalid\n", ptr);
		else if (fname != NULL)
			fprintf(stderr, "Error: line %d in %s is invalid\n", lineno, fname);
		else
			fprintf(stderr, "Error: line %d in the custom profile is invalid\n", lineno);
		exit(1);
	}

	// some characters just don't belong in filenames
	invalid_filename(ptr);
	if (strstr(ptr, "..")) {
		if (lineno == 0)
			fprintf(stderr, "Error: \"%s\" is an invalid filename\n", ptr);
		else if (fname != NULL)
			fprintf(stderr, "Error: line %d in %s is invalid\n", lineno, fname);
		else
			fprintf(stderr, "Error: line %d in the custom profile is invalid\n", lineno);
		exit(1);
	}
	return 1;
}
示例#21
0
void profile_read(const char *fname) {
	EUID_ASSERT();

	// exit program if maximum include level was reached
	if (include_level > MAX_INCLUDE_LEVEL) {
		fprintf(stderr, "Error: maximum profile include level was reached\n");
		exit(1);
	}

	// check file
	invalid_filename(fname);
	if (strlen(fname) == 0 || is_dir(fname)) {
		fprintf(stderr, "Error: invalid profile file\n");
		exit(1);
	}
	if (access(fname, R_OK)) {
		// if the file ends in ".local", do not exit
		const char *base = gnu_basename(fname);
		char *ptr = strstr(base, ".local");
		if (ptr && strlen(ptr) == 6)
			return;

		fprintf(stderr, "Error: cannot access profile file\n");
		exit(1);
	}

	// allow debuggers
	if (arg_allow_debuggers) {
		char *tmp = strrchr(fname, '/');
		if (tmp && *(tmp + 1) != '\0') {
			tmp++;
			if (strcmp(tmp, "disable-devel.inc") == 0)
				return;
		}
	}

	// open profile file:
	FILE *fp = fopen(fname, "r");
	if (fp == NULL) {
		fprintf(stderr, "Error: cannot open profile file %s\n", fname);
		exit(1);
	}

	int msg_printed = 0;

	// read the file line by line
	char buf[MAX_READ + 1];
	int lineno = 0;
	while (fgets(buf, MAX_READ, fp)) {
		++lineno;
		// remove empty space - ptr in allocated memory
		char *ptr = line_remove_spaces(buf);
		if (ptr == NULL)
			continue;

		// comments
		if (*ptr == '#' || *ptr == '\0') {
			free(ptr);
			continue;
		}

		// process quiet
		if (strcmp(ptr, "quiet") == 0) {
			arg_quiet = 1;
			free(ptr);
			continue;
		}
		if (!msg_printed) {
			if (!arg_quiet)
				fprintf(stderr, "Reading profile %s\n", fname);
			msg_printed = 1;
		}

		// process include
		if (strncmp(ptr, "include ", 8) == 0) {
			include_level++;

			// extract profile filename and new skip params
			char *newprofile = ptr + 8; // profile name

			// expand ${HOME}/ in front of the new profile file
			char *newprofile2 = expand_home(newprofile, cfg.homedir);

			// recursivity
			profile_read((newprofile2)? newprofile2:newprofile);
			include_level--;
			if (newprofile2)
				free(newprofile2);
			free(ptr);
			continue;
		}

		// verify syntax, exit in case of error
		if (profile_check_line(ptr, lineno, fname))
			profile_add(ptr);
// we cannot free ptr here, data is extracted from ptr and linked as a pointer in cfg structure
//		else {
//			free(ptr);
//		}
#ifdef HAVE_GCOV
		__gcov_flush();
#endif
	}
	fclose(fp);
}
示例#22
0
// check profile line; if line == 0, this was generated from a command line option
// return 1 if the command is to be added to the linked list of profile commands
// return 0 if the command was already executed inside the function
int profile_check_line(char *ptr, int lineno, const char *fname) {
	EUID_ASSERT();
	
	// check ignore list
	int i;
	for (i = 0; i < MAX_PROFILE_IGNORE; i++) {
		if (cfg.profile_ignore[i] == NULL)
			break;
		
		if (strncmp(ptr, cfg.profile_ignore[i], strlen(cfg.profile_ignore[i])) == 0)
			return 0;	// ignore line
	}
	
	if (strncmp(ptr, "ignore ", 7) == 0) {
		char *str = strdup(ptr + 7);
		if (*str == '\0') {
			fprintf(stderr, "Error: invalid ignore option\n");
			exit(1);
		}
		// find an empty entry in profile_ignore array
		int j;
		for (j = 0; j < MAX_PROFILE_IGNORE; j++) {
			if (cfg.profile_ignore[j] == NULL) 
				break;
		}
		if (j >= MAX_PROFILE_IGNORE) {
			fprintf(stderr, "Error: maximum %d --ignore options are permitted\n", MAX_PROFILE_IGNORE);
			exit(1);
		}
		// ... and configure it
		else 
			cfg.profile_ignore[j] = str;

		return 0;
	}

	// mkdir 
	if (strncmp(ptr, "mkdir ", 6) == 0) {
		fs_mkdir(ptr + 6);
		return 0;
	}
	// sandbox name
	else if (strncmp(ptr, "name ", 5) == 0) {
		cfg.name = ptr + 5;
		if (strlen(cfg.name) == 0) {
			fprintf(stderr, "Error: invalid sandbox name\n");
			exit(1);
		}
		return 0;
	}
	else if (strcmp(ptr, "ipc-namespace") == 0) {
		arg_ipc = 1;
		return 0;
	}
	// seccomp, caps, private, user namespace
	else if (strcmp(ptr, "noroot") == 0) {
#if HAVE_USERNS
		if (checkcfg(CFG_USERNS))
			check_user_namespace();
		else
			fprintf(stderr, "Warning: user namespace feature is disabled in Firejail configuration file\n");
#endif

		return 0;
	}
	else if (strcmp(ptr, "nonewprivs") == 0) {
		arg_nonewprivs = 1;
		return 0;
	}
	else if (strcmp(ptr, "seccomp") == 0) {
#ifdef HAVE_SECCOMP
		if (checkcfg(CFG_SECCOMP))
			arg_seccomp = 1;
		else
			fprintf(stderr, "Warning: user seccomp feature is disabled in Firejail configuration file\n");
#endif
		return 0;
	}
	else if (strcmp(ptr, "caps") == 0) {
		arg_caps_default_filter = 1;
		return 0;
	}
	else if (strcmp(ptr, "caps.drop all") == 0) {
		arg_caps_drop_all = 1;
		return 0;
	}
	else if (strcmp(ptr, "shell none") == 0) {
		arg_shell_none = 1;
		return 0;
	}	
	else if (strcmp(ptr, "tracelog") == 0) {
		arg_tracelog = 1;
		return 0;
	}
	else if (strcmp(ptr, "private") == 0) {
		arg_private = 1;
		return 0;
	}
	else if (strcmp(ptr, "private-dev") == 0) {
		arg_private_dev = 1;
		return 0;
	}
	else if (strcmp(ptr, "private-tmp") == 0) {
		arg_private_tmp = 1;
		return 0;
	}
	else if (strcmp(ptr, "nogroups") == 0) {
		arg_nogroups = 1;
		return 0;
	}
	else if (strcmp(ptr, "nosound") == 0) {
		arg_nosound = 1;
		arg_private_dev = 1;
		return 0;
	}
	else if (strcmp(ptr, "netfilter") == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK))
			arg_netfilter = 1;
		else
			fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n");
#endif
		return 0;
	}
	else if (strncmp(ptr, "netfilter ", 10) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			arg_netfilter = 1;
			arg_netfilter_file = strdup(ptr + 10);
			if (!arg_netfilter_file)
				errExit("strdup");
			check_netfilter_file(arg_netfilter_file);
		}
		else
			fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n");
#endif
		return 0;
	}
	else if (strncmp(ptr, "netfilter6 ", 11) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			arg_netfilter6 = 1;
			arg_netfilter6_file = strdup(ptr + 11);
			if (!arg_netfilter6_file)
				errExit("strdup");
			check_netfilter_file(arg_netfilter6_file);
		}
		else
			fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n");
#endif
		return 0;
	}
	else if (strcmp(ptr, "net none") == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			arg_nonetwork  = 1;
			cfg.bridge0.configured = 0;
			cfg.bridge1.configured = 0;
			cfg.bridge2.configured = 0;
			cfg.bridge3.configured = 0;
			cfg.interface0.configured = 0;
			cfg.interface1.configured = 0;
			cfg.interface2.configured = 0;
			cfg.interface3.configured = 0;
		}
		else
			fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n");
#endif
		return 0;
	}
	else if (strncmp(ptr, "net ", 4) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
#ifdef HAVE_NETWORK_RESTRICTED
			// compile time restricted networking
			if (getuid() != 0) {
				fprintf(stderr, "Error: only \"net none\" is allowed to non-root users\n");
				exit(1);
			}
#endif
			// run time restricted networking
			if (checkcfg(CFG_RESTRICTED_NETWORK) && getuid() != 0) {
				fprintf(stderr, "Error: only \"net none\" is allowed to non-root users\n");
				exit(1);
			}
			
			if (strcmp(ptr + 4, "lo") == 0) {
				fprintf(stderr, "Error: cannot attach to lo device\n");
				exit(1);
			}

			Bridge *br;
			if (cfg.bridge0.configured == 0)
				br = &cfg.bridge0;
			else if (cfg.bridge1.configured == 0)
				br = &cfg.bridge1;
			else if (cfg.bridge2.configured == 0)
				br = &cfg.bridge2;
			else if (cfg.bridge3.configured == 0)
				br = &cfg.bridge3;
			else {
				fprintf(stderr, "Error: maximum 4 network devices are allowed\n");
				exit(1);
			}
			net_configure_bridge(br, ptr + 4);
		}
		else
			fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n");
#endif
		return 0;
	}
	
	else if (strncmp(ptr, "iprange ", 8) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			Bridge *br = last_bridge_configured();
			if (br == NULL) {
				fprintf(stderr, "Error: no network device configured\n");
				exit(1);
			}
			if (br->iprange_start || br->iprange_end) {
				fprintf(stderr, "Error: cannot configure the IP range twice for the same interface\n");
				exit(1);
			}

			// parse option arguments
			char *firstip = ptr + 8;
			char *secondip = firstip;
			while (*secondip != '\0') {
				if (*secondip == ',')
					break;
				secondip++;
			}
			if (*secondip == '\0') {
				fprintf(stderr, "Error: invalid IP range\n");
				exit(1);
			}
			*secondip = '\0';
			secondip++;
			
			// check addresses
			if (atoip(firstip, &br->iprange_start) || atoip(secondip, &br->iprange_end) ||
			    br->iprange_start >= br->iprange_end) {
				fprintf(stderr, "Error: invalid IP range\n");
				exit(1);
			}
			if (in_netrange(br->iprange_start, br->ip, br->mask) || in_netrange(br->iprange_end, br->ip, br->mask)) {
				fprintf(stderr, "Error: IP range addresses not in network range\n");
				exit(1);
			}
		}
		else
			fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n");
#endif
		return 0;
	}


// from here
	else if (strncmp(ptr, "mac ", 4) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			Bridge *br = last_bridge_configured();
			if (br == NULL) {
				fprintf(stderr, "Error: no network device configured\n");
				exit(1);
			}
			
			if (mac_not_zero(br->macsandbox)) {
				fprintf(stderr, "Error: cannot configure the MAC address twice for the same interface\n");
				exit(1);
			}

			// read the address
			if (atomac(ptr + 4, br->macsandbox)) {
				fprintf(stderr, "Error: invalid MAC address\n");
				exit(1);
			}
		}
		else
			fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n");
#endif
		return 0;
	}

	else if (strncmp(ptr, "mtu ", 4) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			Bridge *br = last_bridge_configured();
			if (br == NULL) {
				fprintf(stderr, "Error: no network device configured\n");
				exit(1);
			}
			
			if (sscanf(ptr + 4, "%d", &br->mtu) != 1 || br->mtu < 576 || br->mtu > 9198) {
				fprintf(stderr, "Error: invalid mtu value\n");
				exit(1);
			}
		}
		else
			fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n");
#endif
		return 0;
	}

	else if (strncmp(ptr, "ip ", 3) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			Bridge *br = last_bridge_configured();
			if (br == NULL) {
				fprintf(stderr, "Error: no network device configured\n");
				exit(1);
			}
			if (br->arg_ip_none || br->ipsandbox) {
				fprintf(stderr, "Error: cannot configure the IP address twice for the same interface\n");
				exit(1);
			}

			// configure this IP address for the last bridge defined
			if (strcmp(ptr + 3, "none") == 0)
				br->arg_ip_none = 1;
			else {
				if (atoip(ptr + 3, &br->ipsandbox)) {
					fprintf(stderr, "Error: invalid IP address\n");
					exit(1);
				}
			}
		}
		else
			fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n");
#endif
		return 0;
	}

	else if (strncmp(ptr, "ip6 ", 4) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			Bridge *br = last_bridge_configured();
			if (br == NULL) {
				fprintf(stderr, "Error: no network device configured\n");
				exit(1);
			}
			if (br->arg_ip_none || br->ip6sandbox) {
				fprintf(stderr, "Error: cannot configure the IP address twice for the same interface\n");
				exit(1);
			}

			// configure this IP address for the last bridge defined
			// todo: verify ipv6 syntax
			br->ip6sandbox = ptr + 4;
//			if (atoip(argv[i] + 5, &br->ipsandbox)) {
//				fprintf(stderr, "Error: invalid IP address\n");
//				exit(1);
//			}
			
		}
		else
			fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n");
#endif
		return 0;
	}

	else if (strncmp(ptr, "defaultgw ", 10) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			if (atoip(ptr + 10, &cfg.defaultgw)) {
				fprintf(stderr, "Error: invalid IP address\n");
				exit(1);
			}
		}
		else
			fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n");
#endif
		return 0;
	}

	if (strncmp(ptr, "protocol ", 9) == 0) {
#ifdef HAVE_SECCOMP
		if (checkcfg(CFG_SECCOMP))
			protocol_store(ptr + 9);
		else
			fprintf(stderr, "Warning: user seccomp feature is disabled in Firejail configuration file\n");
#endif
		return 0;
	}
	
	if (strncmp(ptr, "env ", 4) == 0) {
		env_store(ptr + 4);
		return 0;
	}
	
	// seccomp drop list on top of default list
	if (strncmp(ptr, "seccomp ", 8) == 0) {
#ifdef HAVE_SECCOMP
		if (checkcfg(CFG_SECCOMP)) {
			arg_seccomp = 1;
			cfg.seccomp_list = strdup(ptr + 8);
			if (!cfg.seccomp_list)
				errExit("strdup");
		}
		else
			fprintf(stderr, "Warning: user seccomp feature is disabled in Firejail configuration file\n");
#endif

		return 0;
	}
	
	// seccomp drop list without default list
	if (strncmp(ptr, "seccomp.drop ", 13) == 0) {
#ifdef HAVE_SECCOMP
		if (checkcfg(CFG_SECCOMP)) {
			arg_seccomp = 1;
			cfg.seccomp_list_drop = strdup(ptr + 13);
			if (!cfg.seccomp_list_drop)
				errExit("strdup");
		}
		else
			fprintf(stderr, "Warning: user seccomp feature is disabled in Firejail configuration file\n");
#endif		
		return 0;
	}

	// seccomp keep list
	if (strncmp(ptr, "seccomp.keep ", 13) == 0) {
#ifdef HAVE_SECCOMP
		if (checkcfg(CFG_SECCOMP)) {
			arg_seccomp = 1;
			cfg.seccomp_list_keep= strdup(ptr + 13);
			if (!cfg.seccomp_list_keep)
				errExit("strdup");
		}
		else
			fprintf(stderr, "Warning: user seccomp feature is disabled in Firejail configuration file\n");
#endif		
		return 0;
	}
	
	// caps drop list
	if (strncmp(ptr, "caps.drop ", 10) == 0) {
		arg_caps_drop = 1;
		arg_caps_list = strdup(ptr + 10);
		if (!arg_caps_list)
			errExit("strdup");
		// verify seccomp list and exit if problems
		if (caps_check_list(arg_caps_list, NULL))
			exit(1);
		return 0;
	}
	
	// caps keep list
	if (strncmp(ptr, "caps.keep ", 10) == 0) {
		arg_caps_keep = 1;
		arg_caps_list = strdup(ptr + 10);
		if (!arg_caps_list)
			errExit("strdup");
		// verify seccomp list and exit if problems
		if (caps_check_list(arg_caps_list, NULL))
			exit(1);
		return 0;
	}

	// hostname
	if (strncmp(ptr, "hostname ", 9) == 0) {
		cfg.hostname = ptr + 9;
		return 0;
	}
	
	// dns
	if (strncmp(ptr, "dns ", 4) == 0) {
		uint32_t dns;
		if (atoip(ptr + 4, &dns)) {
			fprintf(stderr, "Error: invalid DNS server IP address\n");
			return 1;
		}
		
		if (cfg.dns1 == 0)
			cfg.dns1 = dns;
		else if (cfg.dns2 == 0)
			cfg.dns2 = dns;
		else if (cfg.dns3 == 0)
			cfg.dns3 = dns;
		else {
			fprintf(stderr, "Error: up to 3 DNS servers can be specified\n");
			return 1;
		}
		return 0;
	}
	
	// cpu affinity
	if (strncmp(ptr, "cpu ", 4) == 0) {
		read_cpu_list(ptr + 4);
		return 0;
	}
	
	// nice value
	if (strncmp(ptr, "nice ", 4) == 0) {
		cfg.nice = atoi(ptr + 5);
		if (getuid() != 0 &&cfg.nice < 0)
			cfg.nice = 0;
		arg_nice = 1;
		return 0;
	}

	// cgroup
	if (strncmp(ptr, "cgroup ", 7) == 0) {
		set_cgroup(ptr + 7);
		return 0;
	}
	
	// writable-etc
	if (strcmp(ptr, "writable-etc") == 0) {
		if (cfg.etc_private_keep) {
			fprintf(stderr, "Error: private-etc and writable-etc are mutually exclusive\n");
			exit(1);
		}
		arg_writable_etc = 1;
		return 0;
	}
	
	// writable-var
	if (strcmp(ptr, "writable-var") == 0) {
		arg_writable_var = 1;
		return 0;
	}
	
	// private directory
	if (strncmp(ptr, "private ", 8) == 0) {
		cfg.home_private = ptr + 8;
		fs_check_private_dir();
		arg_private = 1;
		return 0;
	}

	// private /etc list of files and directories
	if (strncmp(ptr, "private-etc ", 12) == 0) {
		if (arg_writable_etc) {
			fprintf(stderr, "Error: --private-etc and --writable-etc are mutually exclusive\n");
			exit(1);
		}
		cfg.etc_private_keep = ptr + 12;
		fs_check_etc_list();
		arg_private_etc = 1;
		
		return 0;
	}

	// private /bin list of files
	if (strncmp(ptr, "private-bin ", 12) == 0) {
		cfg.bin_private_keep = ptr + 12;
		arg_private_bin = 1;
		fs_check_bin_list();
		return 0;
	}

	// filesystem bind
	if (strncmp(ptr, "bind ", 5) == 0) {
#ifdef HAVE_BIND		
		if (checkcfg(CFG_BIND)) {
			if (getuid() != 0) {
				fprintf(stderr, "Error: --bind option is available only if running as root\n");
				exit(1);
			}
	
			// extract two directories
			char *dname1 = ptr + 5;
			char *dname2 = split_comma(dname1); // this inserts a '0 to separate the two dierctories
			if (dname2 == NULL) {
				fprintf(stderr, "Error: missing second directory for bind\n");
				exit(1);
			}
			
			// check directories
			invalid_filename(dname1);
			invalid_filename(dname2);
			if (strstr(dname1, "..") || strstr(dname2, "..")) {
				fprintf(stderr, "Error: invalid file name.\n");
				exit(1);
			}
			if (is_link(dname1) || is_link(dname2)) {
				fprintf(stderr, "Symbolic links are not allowed for bind command\n");
				exit(1);
			}
			
			// insert comma back
			*(dname2 - 1) = ',';
			return 1;
		}
		else {
			fprintf(stderr, "Warning: bind feature is disabled in Firejail configuration file\n");
			return 0;			
		}
#else
		return 0;
#endif		
	}

	// rlimit
	if (strncmp(ptr, "rlimit", 6) == 0) {
		if (strncmp(ptr, "rlimit-nofile ", 14) == 0) {
			ptr += 14;
			if (not_unsigned(ptr)) {
				fprintf(stderr, "Invalid rlimit option on line %d\n", lineno);
				exit(1);
			}
			sscanf(ptr, "%u", &cfg.rlimit_nofile);
			arg_rlimit_nofile = 1;
		}
		else if (strncmp(ptr, "rlimit-nproc ", 13) == 0) {
			ptr += 13;
			if (not_unsigned(ptr)) {
				fprintf(stderr, "Invalid rlimit option on line %d\n", lineno);
				exit(1);
			}
			sscanf(ptr, "%u", &cfg.rlimit_nproc);
			arg_rlimit_nproc = 1;
		}
		else if (strncmp(ptr, "rlimit-fsize ", 13) == 0) {
			ptr += 13;
			if (not_unsigned(ptr)) {
				fprintf(stderr, "Invalid rlimit option on line %d\n", lineno);
				exit(1);
			}
			sscanf(ptr, "%u", &cfg.rlimit_fsize);
			arg_rlimit_fsize = 1;
		}
		else if (strncmp(ptr, "rlimit-sigpending ", 18) == 0) {
			ptr += 18;
			if (not_unsigned(ptr)) {
				fprintf(stderr, "Invalid rlimit option on line %d\n", lineno);
				exit(1);
			}
			sscanf(ptr, "%u", &cfg.rlimit_sigpending);
			arg_rlimit_sigpending = 1;
		}
		else {
			fprintf(stderr, "Invalid rlimit option on line %d\n", lineno);
			exit(1);
		}
		
		return 0;		
	}

	// read-write
	if (strncmp(ptr, "read-write ", 11) == 0) {
		if (getuid() != 0) {
			fprintf(stderr, "Error: read-write command is available only for root user\n");
			exit(1);
		}
		fs_rdwr_add(ptr + 11);
		return 0;
	}

	// rest of filesystem
	if (strncmp(ptr, "blacklist ", 10) == 0)
		ptr += 10;
	else if (strncmp(ptr, "blacklist-nolog ", 16) == 0)
		ptr += 16;
	else if (strncmp(ptr, "noblacklist ", 12) == 0)
		ptr += 12;
	else if (strncmp(ptr, "whitelist ", 10) == 0) {
#ifdef HAVE_WHITELIST		
		if (checkcfg(CFG_WHITELIST)) {
			arg_whitelist = 1;
			ptr += 10;
		}
		else
			return 0;
#else		
		return 0;
#endif
	}
	else if (strncmp(ptr, "read-only ", 10) == 0)
		ptr += 10;
	else if (strncmp(ptr, "tmpfs ", 6) == 0) {
		if (getuid() != 0) {
			fprintf(stderr, "Error: tmpfs available only when running the sandbox as root\n");
			exit(1);
		}
		ptr += 6;
	}
	else {
		if (lineno == 0)
			fprintf(stderr, "Error: \"%s\" as a command line option is invalid\n", ptr);
		else if (fname != NULL)
			fprintf(stderr, "Error: line %d in %s is invalid\n", lineno, fname);
		else
			fprintf(stderr, "Error: line %d in the custom profile is invalid\n", lineno);
		exit(1);
	}

	// some characters just don't belong in filenames
	invalid_filename(ptr);
	if (strstr(ptr, "..")) {
		if (lineno == 0)
			fprintf(stderr, "Error: \"%s\" is an invalid filename\n", ptr);
		else if (fname != NULL)
			fprintf(stderr, "Error: line %d in %s is invalid\n", lineno, fname);
		else
			fprintf(stderr, "Error: line %d in the custom profile is invalid\n", lineno);
		exit(1);
	}
	return 1;
}
示例#23
0
// check profile line; if line == 0, this was generated from a command line option
// return 1 if the command is to be added to the linked list of profile commands
// return 0 if the command was already executed inside the function
int profile_check_line(char *ptr, int lineno) {
	// check ignore list
	int i;
	for (i = 0; i < MAX_PROFILE_IGNORE; i++) {
		if (cfg.profile_ignore[i] == NULL)
			break;
		
		if (strncmp(ptr, cfg.profile_ignore[i], strlen(cfg.profile_ignore[i])) == 0)
			return 0;	// ignore line
	}
	
	if (strncmp(ptr, "ignore ", 7) == 0) {
		char *str = strdup(ptr + 7);
		if (*str == '\0') {
			fprintf(stderr, "Error: invalid ignore option\n");
			exit(1);
		}
		// find an empty entry in profile_ignore array
		int j;
		for (j = 0; j < MAX_PROFILE_IGNORE; j++) {
			if (cfg.profile_ignore[j] == NULL) 
				break;
		}
		if (j >= MAX_PROFILE_IGNORE) {
			fprintf(stderr, "Error: maximum %d --ignore options are permitted\n", MAX_PROFILE_IGNORE);
			exit(1);
		}
		// ... and configure it
		cfg.profile_ignore[j] = str;
		return 0;
	}

	// seccomp, caps, private, user namespace
	if (strcmp(ptr, "noroot") == 0) {
		check_user_namespace();
		return 0;
	}
	else if (strcmp(ptr, "seccomp") == 0) {
		arg_seccomp = 1;
		return 0;
	}
	else if (strcmp(ptr, "caps") == 0) {
		arg_caps_default_filter = 1;
		return 0;
	}
	else if (strcmp(ptr, "caps.drop all") == 0) {
		arg_caps_drop_all = 1;
		return 0;
	}
	else if (strcmp(ptr, "shell none") == 0) {
		arg_shell_none = 1;
		return 0;
	}	
	else if (strcmp(ptr, "private") == 0) {
		arg_private = 1;
		return 0;
	}
	else if (strcmp(ptr, "private-dev") == 0) {
		arg_private_dev = 1;
		return 0;
	}
	else if (strcmp(ptr, "nogroups") == 0) {
		arg_nogroups = 1;
		return 0;
	}
	else if (strcmp(ptr, "netfilter") == 0) {
		arg_netfilter = 1;
		return 0;
	}
	else if (strncmp(ptr, "netfilter ", 10) == 0) {
		arg_netfilter = 1;
		arg_netfilter_file = strdup(ptr + 10);
		if (!arg_netfilter_file)
			errExit("strdup");
		check_netfilter_file(arg_netfilter_file);
		return 0;
	}
	else if (strcmp(ptr, "net none") == 0) {
		arg_nonetwork  = 1;
		cfg.bridge0.configured = 0;
		cfg.bridge1.configured = 0;
		cfg.bridge2.configured = 0;
		cfg.bridge3.configured = 0;
		return 0;
	}
	
	if (strncmp(ptr, "protocol ", 9) == 0) {
		protocol_store(ptr + 9);
		return 0;
	}
	
	if (strncmp(ptr, "env ", 4) == 0) {
		env_store(ptr + 4);
		return 0;
	}
	
	// seccomp drop list on top of default list
	if (strncmp(ptr, "seccomp ", 8) == 0) {
		arg_seccomp = 1;
#ifdef HAVE_SECCOMP
		cfg.seccomp_list = strdup(ptr + 8);
		if (!cfg.seccomp_list)
			errExit("strdup");
#endif
		return 0;
	}
	
	// seccomp drop list without default list
	if (strncmp(ptr, "seccomp.drop ", 13) == 0) {
		arg_seccomp = 1;
#ifdef HAVE_SECCOMP
		cfg.seccomp_list_drop = strdup(ptr + 13);
		if (!cfg.seccomp_list_drop)
			errExit("strdup");
#endif
		return 0;
	}

	// seccomp keep list
	if (strncmp(ptr, "seccomp.keep ", 13) == 0) {
		arg_seccomp = 1;
#ifdef HAVE_SECCOMP
		cfg.seccomp_list_keep= strdup(ptr + 13);
		if (!cfg.seccomp_list_keep)
			errExit("strdup");
#endif
		return 0;
	}
	
	// caps drop list
	if (strncmp(ptr, "caps.drop ", 10) == 0) {
		arg_caps_drop = 1;
		arg_caps_list = strdup(ptr + 10);
		if (!arg_caps_list)
			errExit("strdup");
		// verify seccomp list and exit if problems
		if (caps_check_list(arg_caps_list, NULL))
			exit(1);
		return 0;
	}
	
	// caps keep list
	if (strncmp(ptr, "caps.keep ", 10) == 0) {
		arg_caps_keep = 1;
		arg_caps_list = strdup(ptr + 10);
		if (!arg_caps_list)
			errExit("strdup");
		// verify seccomp list and exit if problems
		if (caps_check_list(arg_caps_list, NULL))
			exit(1);
		return 0;
	}
	
	// dns
	if (strncmp(ptr, "dns ", 4) == 0) {
		uint32_t dns;
		if (atoip(ptr + 4, &dns)) {
			fprintf(stderr, "Error: invalid DNS server IP address\n");
			return 1;
		}
		
		if (cfg.dns1 == 0)
			cfg.dns1 = dns;
		else if (cfg.dns2 == 0)
			cfg.dns2 = dns;
		else if (cfg.dns3 == 0)
			cfg.dns3 = dns;
		else {
			fprintf(stderr, "Error: up to 3 DNS servers can be specified\n");
			return 1;
		}
		return 0;
	}
	
	// cpu affinity
	if (strncmp(ptr, "cpu ", 4) == 0) {
		read_cpu_list(ptr + 4);
		return 0;
	}
	
	// cgroup
	if (strncmp(ptr, "cgroup ", 7) == 0) {
		set_cgroup(ptr + 7);
		return 0;
	}
	
	// private directory
	if (strncmp(ptr, "private ", 8) == 0) {
		cfg.home_private = ptr + 8;
		fs_check_private_dir();
		arg_private = 1;
		return 0;
	}

	// private home list of files and directories
	if ((strncmp(ptr, "private.keep ", 13) == 0)
	 || (strncmp(ptr, "private-home ", 13) == 0)) {
		cfg.home_private_keep = ptr + 13;
		fs_check_home_list();
		arg_private = 1;
		return 0;
	}
	
	// private /etc list of files and directories
	if (strncmp(ptr, "private-etc ", 12) == 0) {
		cfg.etc_private_keep = ptr + 12;
		fs_check_etc_list();
		arg_private_etc = 1;
		return 0;
	}

	// private /bin list of files
	if (strncmp(ptr, "private-bin ", 12) == 0) {
		cfg.bin_private_keep = ptr + 12;
		fs_check_bin_list();
		arg_private_bin = 1;
		return 0;
	}

	// filesystem bind
	if (strncmp(ptr, "bind ", 5) == 0) {
		if (getuid() != 0) {
			fprintf(stderr, "Error: --bind option is available only if running as root\n");
			exit(1);
		}

		// extract two directories
		char *dname1 = ptr + 5;
		char *dname2 = split_comma(dname1); // this inserts a '0 to separate the two dierctories
		if (dname2 == NULL) {
			fprintf(stderr, "Error: mising second directory for bind\n");
			exit(1);
		}
		
		// check directories
		invalid_filename(dname1);
		invalid_filename(dname2);
		if (strstr(dname1, "..") || strstr(dname2, "..")) {
			fprintf(stderr, "Error: invalid file name.\n");
			exit(1);
		}
		
		// insert comma back
		*(dname2 - 1) = ',';
		return 1;
	}

	// rlimit
	if (strncmp(ptr, "rlimit", 6) == 0) {
		if (strncmp(ptr, "rlimit-nofile ", 14) == 0) {
			ptr += 14;
			if (not_unsigned(ptr)) {
				fprintf(stderr, "Invalid rlimit option on line %d\n", lineno);
				exit(1);
			}
			sscanf(ptr, "%u", &cfg.rlimit_nofile);
			arg_rlimit_nofile = 1;
		}
		else if (strncmp(ptr, "rlimit-nproc ", 13) == 0) {
			ptr += 13;
			if (not_unsigned(ptr)) {
				fprintf(stderr, "Invalid rlimit option on line %d\n", lineno);
				exit(1);
			}
			sscanf(ptr, "%u", &cfg.rlimit_nproc);
			arg_rlimit_nproc = 1;
		}
		else if (strncmp(ptr, "rlimit-fsize ", 13) == 0) {
			ptr += 13;
			if (not_unsigned(ptr)) {
				fprintf(stderr, "Invalid rlimit option on line %d\n", lineno);
				exit(1);
			}
			sscanf(ptr, "%u", &cfg.rlimit_fsize);
			arg_rlimit_fsize = 1;
		}
		else if (strncmp(ptr, "rlimit-sigpending ", 18) == 0) {
			ptr += 18;
			if (not_unsigned(ptr)) {
				fprintf(stderr, "Invalid rlimit option on line %d\n", lineno);
				exit(1);
			}
			sscanf(ptr, "%u", &cfg.rlimit_sigpending);
			arg_rlimit_sigpending = 1;
		}
		else {
			fprintf(stderr, "Invalid rlimit option on line %d\n", lineno);
			exit(1);
		}
		
		return 0;		
	}

	// rest of filesystem
	if (strncmp(ptr, "blacklist ", 10) == 0)
		ptr += 10;
	else if (strncmp(ptr, "noblacklist ", 12) == 0)
		ptr += 12;
	else if (strncmp(ptr, "whitelist ", 10) == 0) {
		arg_whitelist = 1;
		ptr += 10;
	}
	else if (strncmp(ptr, "read-only ", 10) == 0)
		ptr += 10;
	else if (strncmp(ptr, "tmpfs ", 6) == 0)
		ptr += 6;
	else {
		if (lineno == 0)
			fprintf(stderr, "Error: \"%s\" as a command line option is invalid\n", ptr);
		else
			fprintf(stderr, "Error: line %d in the custom profile is invalid\n", lineno);
		exit(1);
	}

	// some characters just don't belong in filenames
	invalid_filename(ptr);
	if (strstr(ptr, "..")) {
		if (lineno == 0)
			fprintf(stderr, "Error: \"%s\" is an invalid filename\n", ptr);
		else
			fprintf(stderr, "Error: line %d in the custom profile is invalid\n", lineno);
		exit(1);
	}
	return 1;
}
示例#24
0
文件: midlevel.c 项目: 007/afpfs-ng
int ml_creat(struct afp_volume * volume, const char *path, mode_t mode)
{
	int ret=0;
	char basename[AFP_MAX_PATH];
	unsigned int dirid;
	struct afp_file_info fp;
	int rc;
	char converted_path[AFP_MAX_PATH];

	if (convert_path_to_afp(volume->server->path_encoding,
		converted_path,(char *) path,AFP_MAX_PATH))
		return -EINVAL;

	if (volume_is_readonly(volume))
		return -EACCES;

	ret=appledouble_creat(volume,path,mode);
	if (ret<0) return ret;
	if (ret==1) return 0;
 
	if (invalid_filename(volume->server,converted_path)) 
		return -ENAMETOOLONG;

	get_dirid(volume, converted_path, basename, &dirid);

	rc=afp_createfile(volume,kFPSoftCreate, dirid,basename);
	switch(rc) {
	case kFPAccessDenied:
		ret=EACCES;
		break;
	case kFPDiskFull:
		ret=ENOSPC;
		break;
	case kFPObjectExists:
		ret=EEXIST;
		break;
	case kFPObjectNotFound:
		ret=ENOENT;
		break;
	case kFPFileBusy:
	case kFPVolLocked:
		ret=EBUSY;
		break;
	case kFPNoErr:
		ret=0;
		break;
	default:
	case kFPParamErr:
	case kFPMiscErr:
		ret=EIO;
	}

	if (ret) return -ret;

	/* If we don't support unixprivs, just exit */
	if (~ volume->extra_flags & VOLUME_EXTRA_FLAGS_VOL_SUPPORTS_UNIX) 
		return 0;

	/* Figure out the privs of the file we just created */
	if ((ret=get_unixprivs(volume,
		dirid,basename, &fp)))
		return rc;

	if (ret) return -ret;

	if (fp.unixprivs.permissions==mode)
		return 0;


	fp.unixprivs.ua_permissions=0;
	fp.unixprivs.permissions=mode;
	fp.isdir=0;  /* Anything you make with mknod is a file */
	/* note that we're not monkeying with the ownership here */
	
	rc=set_unixprivs(volume, dirid, basename, &fp);

	switch(rc) {
	case kFPAccessDenied:
		ret=EPERM;
		goto error;
	case kFPObjectNotFound:
		ret=ENOENT;
		goto error;
	case 0:
		ret=0;
		break;
	case kFPBitmapErr:
	case kFPMiscErr:
	case kFPObjectTypeErr:
	case kFPParamErr:
	default:
		ret=EIO;
		goto error;
	}

error:
	return -ret;
}
示例#25
0
文件: midlevel.c 项目: 007/afpfs-ng
int ml_chown(struct afp_volume * vol, const char * path, 
	uid_t uid, gid_t gid) 
{
	int ret;
	struct afp_file_info fp;
	int rc;
	unsigned int dirid;
	char basename[AFP_MAX_PATH];
	char converted_path[AFP_MAX_PATH];

	if (convert_path_to_afp(vol->server->path_encoding,
		converted_path,(char *) path,AFP_MAX_PATH))
		return -EINVAL;

	if (invalid_filename(vol->server,converted_path)) 
		return -ENAMETOOLONG;

	if (volume_is_readonly(vol))
		return -EACCES;

	ret=appledouble_chown(vol,path,uid,gid);
	if (ret<0) return ret;
	if (ret==1) return 0;

	/* There's no way to do this in AFP < 3.0 */
	if (~ vol->extra_flags & VOLUME_EXTRA_FLAGS_VOL_SUPPORTS_UNIX) {

		if (vol->extra_flags & VOLUME_EXTRA_FLAGS_IGNORE_UNIXPRIVS) {
			struct stat stbuf;
			/* See if the file exists */
			ret=ll_getattr(vol,path,&stbuf,0);
			return ret;
		}

		return -ENOSYS;
	};

	get_dirid(vol,converted_path,basename,&dirid );

	if ((rc=get_unixprivs(vol,
		dirid,basename, &fp)))
		return rc;

#if 0
FIXME
	set_uidgid(volume,&fp,uid,gid);
THIS IS the wrong set of returns to check...
#endif
	rc=set_unixprivs(vol, dirid, basename, &fp);

	switch(rc) {
	case -ENOSYS:
		return -ENOSYS;
	case kFPNoErr:
		break;
	case kFPAccessDenied:
		return -EACCES;
	case kFPObjectNotFound:
		return -ENOENT;
	case kFPBitmapErr:
	case kFPMiscErr:
	case kFPObjectTypeErr:
	case kFPParamErr:
	default:
		break;

	}

	return 0;
}
示例#26
0
文件: midlevel.c 项目: 007/afpfs-ng
int ml_chmod(struct afp_volume * vol, const char * path, mode_t mode) 
{
/*
chmod has an interesting story to it.  

It is known to work with Darwin 10.3.9 (AFP 3.1), 10.4.2 and 10.5.x (AFP 3.2).

chmod will not work properly in the following situations:

- AFP 2.2, this needs some more verification but I don't see how it is possible

- netatalk 2.0.3 and probably earlier:

  . netatalk will only enable it at all if you have "options=upriv" 
    set for that volume.

  . netatalk will never be able to chmod the execute bit and some others on 
    files; this is hard coded in unix.c's setfilemode() in 2.0.3.  It's like
    it has 2.2 behaviour even though it is trying to speak 3.1.

  . The only bits allowed are
        S_IRUSR |S_IWUSR | S_IRGRP | S_IWGRP |S_IROTH | S_IWOTH;
    There's probably a reason for this, I don't know what it is.

  . afpfs-ng's behaviour's the same as the Darwin client.

The right way to see if a volume supports chmod is to check the attributes
found with getvolparm or volopen, then to test chmod the first time.

*/

	int ret=0,rc;
	struct afp_file_info fp;
	unsigned int dirid;
	char basename[AFP_MAX_PATH];
	char converted_path[AFP_MAX_PATH];
	uid_t uid; gid_t gid;

	if (invalid_filename(vol->server,path)) 
		return -ENAMETOOLONG;

	if (volume_is_readonly(vol))
		return -EACCES;


	/* There's no way to do this in AFP < 3.0 */
	if (~ vol->extra_flags & VOLUME_EXTRA_FLAGS_VOL_SUPPORTS_UNIX) {

		if (vol->extra_flags & VOLUME_EXTRA_FLAGS_IGNORE_UNIXPRIVS) {
			struct stat stbuf;
			/* See if the file exists */
			ret=ll_getattr(vol,path,&stbuf,0);
			return ret;
		}

		return -ENOSYS;
	};

	if (convert_path_to_afp(vol->server->path_encoding,
		converted_path,(char *) path,AFP_MAX_PATH)) {
		return -EINVAL;
	}

	ret=appledouble_chmod(vol,path,mode);
	if (ret<0) return ret;
	if (ret==1) return 0;

	get_dirid(vol,converted_path,basename,&dirid );

	if ((rc=get_unixprivs(vol,
		dirid,basename, &fp))) 
		return rc;

	mode&=(~S_IFDIR);

	/* Don't bother updating it if it's already the same */
	if ((fp.unixprivs.permissions&(~S_IFDIR))==mode)
		return 0;

	/* Check to make sure that we can; some servers (at least netatalk)
	   don't report an error when you try to setfileparm when you don't
	   own the file.  */

	/* Try to guess if the operation is possible */

	uid=fp.unixprivs.uid;
	gid=fp.unixprivs.gid;
	if (translate_uidgid_to_client(vol, &uid,&gid))
		return -EIO;

	if ((gid!=getgid()) && (uid!=geteuid())) {
		return -EPERM;
	}
	
	fp.unixprivs.permissions=mode;

	rc=set_unixprivs(vol, dirid,basename, &fp);
	if (rc==-ENOSYS) {
		return -ENOSYS;
	}



	return -ret;
}
示例#27
0
文件: appimage.c 项目: maces/firejail
void appimage_set(const char *appimage) {
	assert(appimage);
	assert(devloop == NULL);	// don't call this twice!
	EUID_ASSERT();
	
#ifdef LOOP_CTL_GET_FREE	// test for older kernels; this definition is found in /usr/include/linux/loop.h
	// check appimage file
	invalid_filename(appimage);
	if (access(appimage, R_OK) == -1) {
		fprintf(stderr, "Error: cannot access AppImage file\n");
		exit(1);
	}

	// get appimage type and ELF size
	// a value of 0 means we are dealing with a type1 appimage
	long unsigned int size = appimage2_size(appimage);
	if (arg_debug)
		printf("AppImage ELF size %lu\n", size);

	// open appimage file
	/* coverity[toctou] */
	int ffd = open(appimage, O_RDONLY|O_CLOEXEC);
	if (ffd == -1) {
		fprintf(stderr, "Error: cannot open AppImage file\n");
		exit(1);
	}

	// find or allocate a free loop device to use
	EUID_ROOT();
	int cfd = open("/dev/loop-control", O_RDWR);
	if (cfd == -1) {
		fprintf(stderr, "Error: /dev/loop-control interface is not supported by your kernel\n");
		exit(1);
	}
	int devnr = ioctl(cfd, LOOP_CTL_GET_FREE);
	if (devnr == -1) {
		fprintf(stderr, "Error: cannot allocate a new loopback device\n");
		exit(1);
	}
	close(cfd);
	if (asprintf(&devloop, "/dev/loop%d", devnr) == -1)
		errExit("asprintf");
		
	int lfd = open(devloop, O_RDONLY);
	if (lfd == -1) {
		fprintf(stderr, "Error: cannot open %s\n", devloop);
		exit(1);
	}
	if (ioctl(lfd, LOOP_SET_FD, ffd) == -1) {
		fprintf(stderr, "Error: cannot configure the loopback device\n");
		exit(1);
	}
	
	if (size) {
		struct loop_info64 info;
		memset(&info, 0, sizeof(struct loop_info64));
		info.lo_offset = size;
		if (ioctl(lfd,  LOOP_SET_STATUS64, &info) == -1)
			errExit("configure appimage offset");
	}
	
	close(lfd);
	close(ffd);
	EUID_USER();

	// creates appimage mount point perms 0700
	if (asprintf(&mntdir, "%s/.appimage-%u",  RUN_FIREJAIL_APPIMAGE_DIR, getpid()) == -1)
		errExit("asprintf");
	EUID_ROOT();
	mkdir_attr(mntdir, 0700, getuid(), getgid());
	EUID_USER();
	
	// mount
	char *mode;
	if (asprintf(&mode, "mode=700,uid=%d,gid=%d", getuid(), getgid()) == -1)
		errExit("asprintf");
	EUID_ROOT();
	
	if (size == 0) {
		if (mount(devloop, mntdir, "iso9660",MS_MGC_VAL|MS_RDONLY,  mode) < 0)
			errExit("mounting appimage");
	}
	else {
		if (mount(devloop, mntdir, "squashfs",MS_MGC_VAL|MS_RDONLY,  mode) < 0)
			errExit("mounting appimage");
	}

	if (arg_debug)
		printf("appimage mounted on %s\n", mntdir);
	EUID_USER();

	// set environment
	if (setenv("APPIMAGE", appimage, 1) < 0)
		errExit("setenv");
	if (mntdir && setenv("APPDIR", mntdir, 1) < 0)
		errExit("setenv");

	// build new command line
	if (asprintf(&cfg.command_line, "%s/AppRun", mntdir) == -1)
		errExit("asprintf");
	
	free(mode);
#ifdef HAVE_GCOV
	__gcov_flush();
#endif
#else
	fprintf(stderr, "Error: /dev/loop-control interface is not supported by your kernel\n");
	exit(1);
#endif
}
示例#28
0
void check_output(int argc, char **argv) {
	int i;
	char *outfile = NULL;
//	drop_privs(0);

	int found = 0;
	for (i = 1; i < argc; i++) {
		if (strncmp(argv[i], "--output=", 9) == 0) {
			found = 1;
			invalid_filename(argv[i] + 9);
			outfile = argv[i] + 9;

			// do not accept directories, links, and files with ".."
			if (strstr(outfile, "..") || is_link(outfile) || is_dir(outfile)) {
				fprintf(stderr, "Error: invalid output file. Links, directories and files with \"..\" are not allowed.\n");
				exit(1);
			}
			
			struct stat s;
			if (stat(outfile, &s) == 0) {
				// check permissions
				if (s.st_uid != getuid() || s.st_gid != getgid()) {
					fprintf(stderr, "Error: the output file needs to be owned by the current user.\n");
					exit(1);
				}
				
				// check hard links
				if (s.st_nlink != 1) {
					fprintf(stderr, "Error: no hard links allowed.\n");
					exit(1);
				}
			}

			// drop privileges and try to open the file for writing
			drop_privs(0);
			/* coverity[toctou] */
			FILE *fp = fopen(outfile, "a");
			if (!fp) {
				fprintf(stderr, "Error: cannot open output file %s\n", outfile);
				exit(1);
			}
			fclose(fp);
			break;
		}
	}
	if (!found)
		return;


	// build the new command line
	int len = 0;
	for (i = 0; i < argc; i++) {
		len += strlen(argv[i]) + 1; // + ' '
	}
	len += 50 + strlen(outfile); // tee command
	
	char *cmd = malloc(len + 1); // + '\0'
	if (!cmd)
		errExit("malloc");
	
	char *ptr = cmd;
	for (i = 0; i < argc; i++) {
		if (strncmp(argv[i], "--output=", 9) == 0)
			continue;
		ptr += sprintf(ptr, "%s ", argv[i]);
	}
	sprintf(ptr, "| %s/firejail/ftee %s", LIBDIR, outfile);

	// run command
	char *a[4];
	a[0] = "/bin/bash";
	a[1] = "-c";
	a[2] = cmd;
	a[3] = NULL;

	execvp(a[0], a); 

	perror("execvp");
	exit(1);
}