├── etc ├── jail-shell │ ├── cmd_config │ ├── cmdd_config │ └── jail-config │ │ ├── jail.cfg.sample │ │ └── jail-bin-symbolic-link.cfg.sample ├── default │ └── jail-shell └── init.d │ └── jail-shell ├── docs └── Architecture.png ├── .gitignore ├── .travis.yml ├── lib └── systemd │ └── system │ └── jail-shell.service ├── pam_jail_shell ├── jail-shell.conf ├── Makefile ├── jail_init.c └── pam_jail_shell.c ├── Makefile ├── jail-cmd ├── Makefile ├── jail-common.c ├── jail-cmd.h ├── jail-cmd.c └── jail-cmdd.c ├── misc └── rootfs │ └── etc │ └── profile ├── bin ├── jail-shell-post ├── jail-shell └── jail-shell-setup ├── README_zh-CN.md ├── install ├── README.md └── LICENSE.txt /etc/jail-shell/cmd_config: -------------------------------------------------------------------------------- 1 | 2 | # what port. 3 | port 21104 4 | -------------------------------------------------------------------------------- /docs/Architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pymumu/jail-shell/HEAD/docs/Architecture.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | jail-cmd/*.o 3 | jail-cmd/jail-cmd 4 | jail-cmd/jail-cmdd 5 | pam_jail_shell/*.o 6 | pam_jail_shell/*.so 7 | pam_jail_shell/jail-init 8 | -------------------------------------------------------------------------------- /etc/jail-shell/cmdd_config: -------------------------------------------------------------------------------- 1 | 2 | # what port. 3 | port 21104 4 | 5 | # audit 6 | audit true 7 | 8 | # audit args 9 | audit-args false 10 | -------------------------------------------------------------------------------- /etc/default/jail-shell: -------------------------------------------------------------------------------- 1 | # Default settings for jail shell server. This file is sourced by /bin/sh from 2 | # /etc/init.d/jail-shell. 3 | 4 | # Options to pass to jail-cmdd 5 | JAIL_CMDD_OPTS= 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | compiler: 4 | - clang 5 | - gcc 6 | 7 | os: 8 | - linux 9 | 10 | before_install: 11 | - sudo apt-get install -y libxml2-dev 12 | 13 | script: make 14 | -------------------------------------------------------------------------------- /lib/systemd/system/jail-shell.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Jail Shell Server 3 | After=network.target 4 | 5 | [Service] 6 | PIDFile=/var/run/jail-cmdd.pid 7 | EnvironmentFile=-/etc/default/jail-shell 8 | ExecStart=/usr/sbin/jail-cmdd $JAIL_CMDD_OPTS 9 | KillMode=process 10 | Restart=on-failure 11 | 12 | [Install] 13 | WantedBy=multi-user.target 14 | Alias=jail-shell.service 15 | -------------------------------------------------------------------------------- /pam_jail_shell/jail-shell.conf: -------------------------------------------------------------------------------- 1 | # /etc/jail-shell/jail-shell.conf 2 | # 3 | # 4 | # jail home path 5 | JAIL_HOME /var/local/jail-shell/jails 6 | 7 | # chroot settings. 8 | # basic format: 9 | # 10 | # user jail-name-user mnt,ipc,net,pid,uts|none 11 | # @group jail-name-group mnt,ipc,net,pid,uts 12 | # 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | JAIL_CMD_DIR=jail-cmd 4 | 5 | PAM_JAIL_SHELL_DIR=pam_jail_shell 6 | PAM_JAIL_SHELL_BIN=$(PAM_JAIL_SHELL_DIR)/pam_jail_shell.so 7 | 8 | 9 | .PHONY: all JAIL_CMD PAM_JAIL_SHELL 10 | 11 | all: JAIL_CMD PAM_JAIL_SHELL 12 | 13 | JAIL_CMD: 14 | $(MAKE) -C $(JAIL_CMD_DIR) $(MAKEFLAGS) all 15 | 16 | PAM_JAIL_SHELL: 17 | $(MAKE) -C $(PAM_JAIL_SHELL_DIR) $(MAKEFLAGS) all 18 | 19 | install: all 20 | @chmod +x install 21 | @./install -i 22 | 23 | uninstall: 24 | @chmod +x install 25 | @./install -u 26 | 27 | clean: 28 | $(MAKE) -C $(JAIL_CMD_DIR) clean 29 | $(MAKE) -C $(PAM_JAIL_SHELL_DIR) clean 30 | -------------------------------------------------------------------------------- /pam_jail_shell/Makefile: -------------------------------------------------------------------------------- 1 | 2 | PAM_JAIL_SHELL_OBJS=pam_jail_shell.o 3 | PAM_JAIL_SHELL=pam_jail_shell.so 4 | 5 | PAM_JAIL_INIT_OBJS=jail_init.o 6 | PAM_JAIL_INIT=jail-init 7 | 8 | CFLAGS +=-g -O2 -fPIC -Wall -Wall -Wstrict-prototypes -fno-omit-frame-pointer -Wstrict-aliasing 9 | CXXFLAGS +=-Wall -fno-omit-frame-pointer -Wstrict-aliasing 10 | 11 | .PHONY: all 12 | 13 | all: $(PAM_JAIL_SHELL) $(PAM_JAIL_INIT) 14 | 15 | $(PAM_JAIL_SHELL) : $(PAM_JAIL_SHELL_OBJS) 16 | $(CC) $(CFLAGS) $^ -shared -o $@ 17 | 18 | $(PAM_JAIL_INIT): $(PAM_JAIL_INIT_OBJS) 19 | $(CC) $(CFLAGS) $^ -o $@ 20 | 21 | clean: 22 | $(RM) $(PAM_JAIL_SHELL) $(PAM_JAIL_SHELL_OBJS) $(PAM_JAIL_INIT) $(PAM_JAIL_INIT_OBJS) 23 | 24 | 25 | -------------------------------------------------------------------------------- /jail-cmd/Makefile: -------------------------------------------------------------------------------- 1 | 2 | JAIL_COMMON_OBJS=jail-common.o 3 | JAIL_CMD_OBJS=jail-cmd.o $(JAIL_COMMON_OBJS) 4 | JAIL_CMDD_OBJS=jail-cmdd.o $(JAIL_COMMON_OBJS) 5 | JAIL_CMD=jail-cmd 6 | JAIL_CMDD=jail-cmdd 7 | JAIL_CMDD_LIB = -lutil 8 | 9 | CFLAGS +=-g -O2 -Werror -Wall -Wstrict-prototypes -fno-omit-frame-pointer -Wstrict-aliasing 10 | CXXFLAGS +=-Wall -fno-omit-frame-pointer -Wstrict-aliasing 11 | 12 | .PHONY: all 13 | 14 | all: $(JAIL_CMD) $(JAIL_CMDD) 15 | 16 | $(JAIL_CMD) : $(JAIL_CMD_OBJS) 17 | $(CC) $(CFLAGS) $^ -o $@ 18 | 19 | $(JAIL_CMDD) : $(JAIL_CMDD_OBJS) 20 | $(CC) $(CFLAGS) $^ -o $@ $(JAIL_CMDD_LIB) 21 | 22 | 23 | clean: 24 | $(RM) $(JAIL_CMD) $(JAIL_CMD_OBJS) $(JAIL_CMDD) $(JAIL_CMDD_OBJS) 25 | 26 | 27 | -------------------------------------------------------------------------------- /misc/rootfs/etc/profile: -------------------------------------------------------------------------------- 1 | # /etc/profile 2 | 3 | alias ls="ls --color=auto" 4 | alias grep="grep --color=auto" 5 | alias ll="ls -l" 6 | alias cp="cp -i" 7 | alias rm="rm -i" 8 | alias l="ls -al" 9 | 10 | PATH="/bin:/usr/bin:/sbin:/usr/sbin" 11 | if [ "${PS1-}" ]; then 12 | if [ "${BASH-}" ]; then 13 | PS1="\u@\h:\w " 14 | else 15 | if [ "`id -u`" -eq 0 ]; then 16 | PS1="# " 17 | else 18 | PS1="$ " 19 | fi 20 | fi 21 | fi 22 | ulimit -c 0 23 | 24 | #USER=`id -un` 25 | #LOGNAME=$USER 26 | 27 | HISTSIZE=1000 28 | EDITOR=e3 29 | VISUAL=e3 30 | TERM=xterm 31 | 32 | if [ ! -d "$HOME" ]; then 33 | HOME=/home 34 | fi 35 | 36 | PAGER=more 37 | if [ -z "$INPUTRC" -a ! -f "$HOME/.inputrc" ]; then 38 | INPUTRC=/etc/inputrc 39 | fi 40 | 41 | export PATH PS1 USER LOGNAME HISTSIZE EDITOR VISUAL PAGER INPUTRC 42 | 43 | for i in /etc/profile.d/*.sh ; do 44 | if [ -x $i ]; then 45 | . $i 46 | fi 47 | done 48 | 49 | unset i 50 | -------------------------------------------------------------------------------- /etc/init.d/jail-shell: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ### BEGIN INIT INFO 4 | # Provides: jail-shell 5 | # Required-Start: $network 6 | # Required-Stop: $network 7 | # Default-Start: 2 3 4 5 8 | # Default-Stop: 9 | # Short-Description: Start jail-shell server 10 | ### END INIT INFO 11 | 12 | PATH=/sbin:/bin:/usr/sbin:/usr/bin 13 | 14 | JAIL_CMDD=/usr/sbin/jail-cmdd 15 | PIDFILE=/var/run/jail-cmdd.pid 16 | 17 | test -x $JAIL_CMDD || exit 5 18 | 19 | case $1 in 20 | start) 21 | $JAIL_CMDD $JAIL_SHELL_OPTS 22 | while true; do 23 | if [ -e "$PIDFILE" ]; then 24 | break; 25 | fi 26 | sleep .5 27 | done 28 | PID="`cat $PIDFILE 2>/dev/null`" 29 | if [ -z "$PID" ]; then 30 | echo "start jail-shell server failed." 31 | exit 1 32 | fi 33 | if [ ! -e "/proc/$PID" ]; then 34 | echo "start jail-shell server failed." 35 | exit 1 36 | fi 37 | echo "start jail-shell server success." 38 | ;; 39 | stop) 40 | if [ ! -f "$PIDFILE" ]; then 41 | echo "jail-shell server is stopped." 42 | exit 0 43 | fi 44 | PID="`cat $PIDFILE 2>/dev/null`" 45 | if [ ! -e "/proc/$PID" ] || [ -z "$PID" ]; then 46 | echo "jail-shell server is stopped" 47 | exit 0 48 | fi 49 | 50 | kill -TERM $PID 51 | if [ $? -ne 0 ]; then 52 | echo "Stop jail-shell server failed." 53 | exit 1; 54 | fi 55 | rm -f $PIDFILE 56 | echo "Stop jail-shell server success." 57 | ;; 58 | restart) 59 | $0 stop && sleep 1 && $0 start 60 | ;; 61 | status) 62 | PID="`cat $PIDFILE 2>/dev/null`" 63 | if [ ! -e "/proc/$PID" ] || [ -z "$PID" ]; then 64 | echo "jail-shell server is not running." 65 | exit 1 66 | fi 67 | echo "jail-shell server is running." 68 | status=$? 69 | ;; 70 | *) 71 | echo "Usage: $0 {start|stop|restart|status}" 72 | exit 2 73 | ;; 74 | esac 75 | 76 | exit $status 77 | 78 | -------------------------------------------------------------------------------- /jail-cmd/jail-common.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Ruilin Peng (Nick) 3 | */ 4 | 5 | #include "jail-cmd.h" 6 | 7 | void set_sock_opt(int sock) 8 | { 9 | int on = 1; 10 | setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)); 11 | 12 | } 13 | 14 | int normalize_path(char *path) 15 | { 16 | char *temp = NULL; 17 | char *begin = path; 18 | char *nextadd = path; 19 | char *nextcmp = path; 20 | 21 | /* skip space */ 22 | while (*nextcmp == '\x20') { 23 | nextcmp++; 24 | } 25 | 26 | /* if not start with /, return 0 */ 27 | if (*nextcmp != '/' || nextcmp[0] == '\0') { 28 | return 0; 29 | } 30 | 31 | 32 | *nextadd = *nextcmp; 33 | nextadd++; 34 | nextcmp++; 35 | 36 | while (*nextcmp) { 37 | if (*nextcmp == '.') { 38 | if (nextcmp[1] == '/' || nextcmp[1] == '\0') { 39 | nextcmp += 2; 40 | continue; 41 | } else if (nextcmp[1] == '.' && (nextcmp[2] == '/' || nextcmp[2] == '\0')) { 42 | temp = nextadd - 1; 43 | if (temp == begin) { 44 | nextcmp += 3; 45 | continue; 46 | } 47 | 48 | while (*(--temp) != '/') { 49 | } 50 | 51 | nextadd = temp + 1; 52 | nextcmp += 3; 53 | continue; 54 | } 55 | } else if (*nextcmp == '/') { 56 | nextcmp++; 57 | continue; 58 | } 59 | 60 | while (*nextcmp && (*nextadd++ = *nextcmp++) != '/') { 61 | } 62 | } 63 | 64 | temp = nextadd; 65 | if (*(temp - 1) == '/') { 66 | temp--; 67 | } 68 | 69 | while (*temp != 0) { 70 | *temp++ = 0; 71 | } 72 | 73 | return nextadd - begin; 74 | } 75 | 76 | int load_config(char *config_file, config_callback call_back) 77 | { 78 | char buff[MAX_LINE_LEN]; 79 | char filed1[MAX_LINE_LEN]; 80 | char filed2[MAX_LINE_LEN]; 81 | int filedNum = 0; 82 | 83 | FILE *fp = NULL; 84 | 85 | fp = fopen(config_file, "r"); 86 | if (fp == NULL) { 87 | fprintf(stderr, "open %s failed, %s\n", config_file, strerror(errno)); 88 | goto errout; 89 | } 90 | 91 | while (fgets(buff, MAX_LINE_LEN, fp)) { 92 | filedNum = sscanf(buff, "%1024s %1024s", filed1, filed2); 93 | if (filedNum < 0) { 94 | continue; 95 | } 96 | 97 | /* comment line */ 98 | if (filed1[0] == '#') { 99 | continue; 100 | } 101 | 102 | if (filedNum != 2) { 103 | continue; 104 | } 105 | 106 | if (call_back(filed1, filed2) != 0) { 107 | goto errout; 108 | } 109 | } 110 | 111 | fclose(fp); 112 | return 0; 113 | errout: 114 | if (fp) { 115 | fclose(fp); 116 | } 117 | return -1; 118 | } 119 | 120 | -------------------------------------------------------------------------------- /jail-cmd/jail-cmd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Ruilin Peng (Nick) 3 | */ 4 | 5 | #ifndef _JAIL_CMD_ 6 | #define _JAIL_CMD_ 7 | 8 | #define _GNU_SOURCE 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #define TMP_BUFF_LEN_32 32 35 | #define TMP_BUFF_LEN_128 128 36 | #define SOCKET_BUFF_LEN (1024 * 32) 37 | #define MAX_ARGS_COUNT 1024 38 | 39 | #define DEFAULT_PORT 21104 40 | 41 | #define MSG_MAGIC 0x615461446C49614A /* JaIlDaTa */ 42 | 43 | #define max(x, y) ({ \ 44 | typeof(x) _max1 = (x); \ 45 | typeof(y) _max2 = (y); \ 46 | (void) (&_max1 == &_max2); \ 47 | _max1 > _max2 ? _max1 : _max2; }) 48 | 49 | #define JAIL_KEY "JSID" 50 | #define JAIL_JSID_FILE "/var/local/jail-shell/jsid/jsid-%s" 51 | #define COMMAND_LIST_FILE "command.list" 52 | #define MAX_LINE_LEN 1024 53 | 54 | #define CONF_PORT "port" 55 | #define CONF_AUDIT "audit" 56 | #define CONF_AUDIT_ARGS "audit-args" 57 | 58 | #define CONF_TRUE "true" 59 | #define CONF_FALSE "false" 60 | 61 | typedef enum CMD_MSG_TYPE { 62 | CMD_MSG_CMD = 1, 63 | CMD_MSG_DATA_IN = 2, 64 | CMD_MSG_DATA_OUT = 3, 65 | CMD_MSG_DATA_ERR = 4, 66 | CMD_MSG_DATA_EXIT = 5, 67 | CMD_MSG_EXIT_CODE = 6, 68 | CMD_MSG_WINSIZE = 7, 69 | CMD_MSG_BUTT = 255 70 | }CMD_MSG_TYPE; 71 | 72 | typedef enum CMD_RETURN { 73 | CMD_RETURN_OK = 0, 74 | CMD_RETURN_EXIT = 1, 75 | CMD_RETURN_ERR = 2, 76 | CMD_RETURN_CONT = 3, 77 | CMD_RETURN_BUTT =255 78 | }CMD_RETURN; 79 | 80 | struct jail_cmd_head { 81 | unsigned long long magic; 82 | unsigned int type; 83 | unsigned int data_len; 84 | unsigned char data[0]; 85 | }; 86 | 87 | struct jail_cmd_cmd { 88 | uid_t uid; 89 | uid_t gid; 90 | int isatty; 91 | char jsid[TMP_BUFF_LEN_32]; 92 | char term[TMP_BUFF_LEN_32]; 93 | struct winsize ws; 94 | int argc; 95 | char argvs[0]; 96 | }; 97 | 98 | struct jail_cmd_data { 99 | unsigned char data[0]; 100 | }; 101 | 102 | struct jail_cmd_exit { 103 | int exit_code; 104 | }; 105 | 106 | struct jail_cmd_winsize { 107 | struct winsize ws; 108 | }; 109 | 110 | struct sock_data { 111 | int total_len; 112 | int curr_offset; 113 | char data[SOCKET_BUFF_LEN]; 114 | }; 115 | 116 | extern void set_sock_opt(int sock); 117 | 118 | extern int normalize_path(char *path); 119 | 120 | typedef int (*config_callback)(char *param, char *value); 121 | 122 | extern int load_config(char *config_file, config_callback call_back); 123 | 124 | #endif 125 | 126 | -------------------------------------------------------------------------------- /bin/jail-shell-post: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # this script is called by pam_jail_shell.so when user login. 3 | # shell ARGS: 4 | # $1: login user name 5 | # $2: jail root path 6 | # 7 | 8 | USER="$1" 9 | ROOT_DIR="$2" 10 | 11 | JAIL_VAR_DIR="/var/local/jail-shell" 12 | JAIL_WRITE_MNT_DIR="$JAIL_WRITE_MNT_DIR/mnt" 13 | ROOT_DIR_RW="$JAIL_WRITE_MNT_DIR/mnt_write.$USER" 14 | 15 | NEED_MODIFY=0 16 | NEED_ADD_USER=0 17 | 18 | RW_MOUNTED=0; 19 | 20 | umount_rw() 21 | { 22 | if [ $RW_MOUNTED -eq 0 ]; then 23 | return 24 | fi 25 | 26 | rmdir $ROOT_DIR_RW 27 | #rmdir $JAIL_WRITE_MNT_DIR 28 | umount $ROOT_DIR_RW 29 | } 30 | 31 | 32 | mount_rw() 33 | { 34 | touch $ROOT_DIR/rw_test >/dev/null 2>&1 35 | if [ $? -eq 0 ]; then 36 | rm -f $ROOT_DIR/rw_test 37 | ROOT_DIR_RW="$ROOT_DIR" 38 | RW_MOUNTED=0 39 | return 0 40 | fi 41 | 42 | RW_MOUNTED=1 43 | 44 | if [ ! -d $JAIL_WRITE_MNT_DIR ]; then 45 | mkdir -m 0750 $JAIL_WRITE_MNT_DIR 46 | if [ $? -ne 0 ]; then 47 | return 1 48 | fi 49 | fi 50 | 51 | if [ ! -d $ROOT_DIR_RW ]; then 52 | mkdir -m 0750 $ROOT_DIR_RW 53 | if [ $? -ne 0 ]; then 54 | return 1 55 | fi 56 | fi 57 | 58 | mount --bind $ROOT_DIR $ROOT_DIR_RW 59 | if [ $? -ne 0 ]; then 60 | return 1 61 | fi 62 | 63 | mount -o remount,rw,bind $ROOT_DIR_RW 64 | if [ $? -ne 0 ]; then 65 | umount_rw 66 | return 1 67 | fi 68 | 69 | return 0 70 | } 71 | 72 | 73 | add_user_passwd() 74 | { 75 | passwd_file="$ROOT_DIR_RW/etc/passwd" 76 | 77 | if [ -z "$USER" ] || [ ! -d "$ROOT_DIR_RW" ]; then 78 | return 1 79 | fi 80 | 81 | if [ ! -f "$passwd_file" ]; then 82 | return 1 83 | fi 84 | 85 | PASS_INFO="`getent passwd $USER`" 86 | if [ -z "$PASS_INFO" ]; then 87 | return 1 88 | fi 89 | USER_ID="`echo "$PASS_INFO" | awk -F ':' '{print $3}'`" 90 | GROUP_ID="`echo "$PASS_INFO" | awk -F ':' '{print $4}'`" 91 | HOME_DIR="`echo "$PASS_INFO" | awk -F ':' '{print $6}'`" 92 | SH_NAME="`echo "$PASS_INFO" | awk -F ':' '{print $7}'`" 93 | 94 | if [ -z "$USER_ID" ] || [ -z "$GROUP_ID" ]; then 95 | return 1 96 | fi 97 | 98 | if [ ! -d "$ROOT_DIR_RW/$HOME_DIR" ]; then 99 | mkdir $ROOT_DIR_RW/$HOME_DIR 100 | chown $USER_ID:$GROUP_ID $ROOT_DIR_RW/$HOME_DIR 101 | fi 102 | 103 | grep "^$USER:" $passwd_file >/dev/null 2>&1 104 | if [ $? -eq 0 ]; then 105 | return 0 106 | fi 107 | 108 | echo "$USER:x:$USER_ID:$GROUP_ID::$HOME_DIR:$SH_NAME" >> $passwd_file 109 | if [ $? -ne 0 ]; then 110 | return 1 111 | fi 112 | 113 | return 0 114 | } 115 | 116 | check_user_passwd() 117 | { 118 | passwd_file="$ROOT_DIR/etc/passwd" 119 | grep "^$USER:" $passwd_file >/dev/null 2>&1 120 | if [ $? -eq 0 ]; then 121 | return 0 122 | fi 123 | 124 | NEED_ADD_USER=1 125 | NEED_MODIFY=1 126 | 127 | return 0 128 | } 129 | 130 | check_post() 131 | { 132 | check_user_passwd 133 | if [ $? -ne 0 ]; then 134 | return 1 135 | fi 136 | 137 | return 0 138 | } 139 | 140 | do_user_post() 141 | { 142 | if [ $NEED_ADD_USER -eq 1 ]; then 143 | add_user_passwd 144 | if [ $? -ne 0 ]; then 145 | return 1 146 | fi 147 | fi 148 | 149 | return 0 150 | } 151 | 152 | main() 153 | { 154 | ret=0 155 | check_post 156 | if [ $? -ne 0 ]; then 157 | return 1 158 | fi 159 | 160 | if [ $NEED_MODIFY -eq 0 ]; then 161 | return 0 162 | fi 163 | 164 | mount_rw 165 | if [ $? -ne 0 ]; then 166 | return 1 167 | fi 168 | 169 | do_user_post 170 | ret=$? 171 | 172 | umount_rw 173 | 174 | } 175 | 176 | main $@ 177 | exit $? 178 | -------------------------------------------------------------------------------- /pam_jail_shell/jail_init.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Ruilin Peng (Nick) 3 | */ 4 | 5 | #define _GNU_SOURCE 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #define NS_PID_FILE_PATH "/var/run/jail-shell-ns-%s.pid" 24 | 25 | #define TMP_BUFF_LEN_32 32 26 | #define MAX_LINE_LEN 4096 27 | 28 | int only_one_process(void) 29 | { 30 | DIR *dir; 31 | struct dirent *dent; 32 | int proc_num = 0; 33 | char comm_file[PATH_MAX]; 34 | int is_one_process = 1; 35 | 36 | dir = opendir("/proc"); 37 | if (dir == NULL) { 38 | return -1; 39 | } 40 | 41 | while((dent = readdir(dir)) != NULL) { 42 | if (dent->d_type != DT_DIR) { 43 | continue; 44 | } 45 | 46 | snprintf(comm_file, PATH_MAX, "/proc/%s/comm", dent->d_name); 47 | if (access(comm_file, F_OK) != 0) { 48 | continue; 49 | } 50 | 51 | proc_num++; 52 | if (proc_num > 1) { 53 | is_one_process = 0; 54 | break; 55 | } 56 | } 57 | 58 | closedir(dir); 59 | 60 | return is_one_process; 61 | } 62 | 63 | void set_process_name(const char *user) 64 | { 65 | int fd = 0; 66 | int cmd_len = 0; 67 | char buff[4096]; 68 | fd = open("/proc/self/cmdline", O_RDONLY); 69 | if (fd < 0) { 70 | return; 71 | } 72 | 73 | /* get length of cmdline */ 74 | cmd_len = read(fd, buff, 4096); 75 | if (cmd_len < 0) { 76 | close(fd); 77 | return; 78 | } 79 | close(fd); 80 | 81 | /* zero cmdline */ 82 | memset(program_invocation_name, 0, cmd_len); 83 | 84 | /* set cmdline to init [user] */ 85 | snprintf(program_invocation_name, TMP_BUFF_LEN_32, "init [%s]", user); 86 | } 87 | 88 | int loop(char *user) 89 | { 90 | int wstat; 91 | int sleep_cnt = 0; 92 | int last_proccess = 0; 93 | int pid_wait; 94 | 95 | set_process_name(user); 96 | 97 | /* wait until all process exit, except jail_init process. */ 98 | while ((pid_wait = waitpid(-1, &wstat, 0)) > 0 || last_proccess == 0) { 99 | if (pid_wait > 0) { 100 | continue; 101 | } 102 | 103 | sleep_cnt++; 104 | sleep(1); 105 | 106 | if (sleep_cnt % 30 == 0) { 107 | sleep_cnt = 0; 108 | last_proccess = only_one_process(); 109 | } 110 | } 111 | 112 | return 0; 113 | } 114 | 115 | void help(void) 116 | { 117 | char *help = "" 118 | "Usage: jail-init [OPTION]...\n" 119 | "jail init process.\n" 120 | " -u user name.\n" 121 | " -l lock file fd.\n" 122 | " -h help message.\n" 123 | "\n" 124 | ; 125 | printf("%s", help); 126 | } 127 | 128 | int main(int argc, char *argv[]) 129 | { 130 | int opt; 131 | char user[MAX_LINE_LEN]={0}; 132 | int lock_fd = -1; 133 | int flags; 134 | 135 | while ((opt = getopt(argc, argv, "u:l:h")) != -1) { 136 | switch (opt) { 137 | case 'u': 138 | strncpy(user, optarg, MAX_LINE_LEN - 1); 139 | break; 140 | case 'l': 141 | lock_fd = atoi(optarg); 142 | break; 143 | case 'h': 144 | help(); 145 | return 1; 146 | } 147 | } 148 | 149 | if (user[0] == 0) { 150 | help(); 151 | return 1; 152 | } 153 | 154 | if (lock_fd > 0) { 155 | flags = fcntl(lock_fd, F_GETFD); 156 | if (flags < 0) { 157 | fprintf(stderr, "Could not get flags for PID file, fd is %d\n", lock_fd); 158 | return 1; 159 | } 160 | 161 | flags |= FD_CLOEXEC; 162 | if (fcntl(lock_fd, F_SETFD, flags) == -1) { 163 | fprintf(stderr, "Could not set flags for PID file, fd is %d\n", lock_fd); 164 | return 1; 165 | } 166 | } 167 | 168 | return loop(user); 169 | } 170 | -------------------------------------------------------------------------------- /etc/jail-shell/jail-config/jail.cfg.sample: -------------------------------------------------------------------------------- 1 | # config description. 2 | # This configuration file is used to generate a jail automatically. 3 | # The configuration supports the following commands: 4 | # dir 5 | # DESC: create a directory into jail 6 | # COMMAND: dir PATH MODE OWNER 7 | # EXAMPLE: dir /bin/ 0755 root:root 8 | # file: 9 | # DESC: copy a file into jail 10 | # COMMAND: file SRC DEST MODE OWNER 11 | # EXAMPLE: file /etc/nsswitch.conf /etc/nsswitch.conf 0644 root:root 12 | # hlink: 13 | # DESC: create a hardlink file into jail 14 | # COMMAND: file SRC DEST MODE OWNER 15 | # EXAMPLE: file /etc/nsswitch.conf /etc/nsswitch.conf 0644 root:root 16 | # slink: 17 | # DESC: create a symbolic link into jail 18 | # COMMAND: slink TARGET LINKNAME 19 | # EXAMPLE: slink /bin/bash /bin/sh 20 | # clink: 21 | # DESC: Try to create hardlinks instead of copying the files. If linking fails it falls back to copying 22 | # COMMAND: clink TARGET LINKNAME 23 | # EXAMPLE: clink /etc/localtime /etc/localtime 24 | # node: 25 | # DESC: create device file. 26 | # COMMAND: node PATH TYPE MAJON MINOR MODE OWNER 27 | # EXAMPLE: node /dev/null c 1 3 666 root:root 28 | # NOTE: security tips 29 | # should avoid adding block device files. 30 | # bind: 31 | # DESC: bind a directory to jail 32 | # COMMAND: bind [SRC] DEST OPTION 33 | # OPTION: rw,ro,dev,nodev,exec,noexec, refer to (man mount) for the parameter description 34 | # %u in path '[SRC] DEST' will be replaced as user name 35 | # EXAMPLE: bind / ro,nodev,nosuid 36 | # bind /opt/ /opt/ ro,nodev,noexec 37 | # bind /opt/upload /opt/upload rw,nodev,noexec,nosuid 38 | # bind /opt/%u /opt/upload ro,nodev,noexec,nosuid 39 | # cmd: 40 | # DESC: executes commands within the system which outside jail. 41 | # COMMAND: cmd SRC DEST RUN_AS_USER 42 | # RUN_AS_USER: User who executes system commands, -:- means user in jail 43 | # EXAMPLE: cmd /usr/bin/passwd /usr/bin/passwd -:- 44 | # cmd /some/root/command /some/root/command root:root 45 | # cmd /some/user/command /some/user/command user:user 46 | # NOTE: security tips 47 | # This channel may lead users to escape jail, should avoid adding command which can be shell-inject, 48 | # For example, read the commands entered by the user 49 | # 50 | # ################################################################### 51 | # JAIL SAMPLE 52 | # ################################################################### 53 | 54 | # List of basic directories 55 | dir /bin/ 0755 root:root 56 | dir /dev/ 0755 root:root 57 | dir /etc/ 0755 root:root 58 | dir /sbin/ 0755 root:root 59 | dir /root/ 0700 root:root 60 | dir /home/ 0755 root:root 61 | dir /lib/ 0755 root:root 62 | dir /proc/ 0755 root:root 63 | dir /usr/bin 0755 root:root 64 | dir /usr/sbin 0755 root:root 65 | dir /etc/security 0755 root:root 66 | dir /usr/lib/terminfo 0755 root:root 67 | 68 | # basic configration files 69 | clink /etc/ld.so.conf /etc/ld.so.conf 70 | clink /etc/security/limits.conf /etc/security/limits.conf 71 | clink /etc/nsswitch.conf /etc/nsswitch.conf 72 | clink /etc/inputrc /etc/inputrc 73 | clink /etc/localtime /etc/localtime 74 | file /usr/local/jail-shell/misc/rootfs/etc/profile /etc/profile 0755 root:root 75 | 76 | # basic device files 77 | clink /dev/null /dev/null 78 | clink /dev/zero /dev/zero 79 | clink /dev/ptmx /dev/ptmx 80 | clink /dev/urandom /dev/urandom 81 | clink /dev/tty /dev/tty 82 | clink /dev/tty1 /dev/tty1 83 | clink /dev/tty2 /dev/tty2 84 | clink /dev/tty3 /dev/tty3 85 | slink /proc/self/fd/2 /dev/stderr 86 | slink /proc/self/fd/0 /dev/stdin 87 | slink /proc/self/fd/1 /dev/stdout 88 | 89 | # basic command list 90 | clink /bin/sh /bin/sh 91 | clink /bin/cat /bin/cat 92 | clink /bin/ls /bin/ls 93 | clink /bin/ps /bin/ps 94 | clink /bin/bash /bin/bash 95 | clink /bin/grep /bin/grep 96 | clink /bin/rm /bin/rm 97 | clink /bin/cp /bin/cp 98 | clink /bin/touch /bin/touch 99 | clink /bin/mv /bin/mv 100 | clink /bin/hostname /bin/hostname 101 | clink /bin/sed /bin/sed 102 | clink /bin/true /bin/true 103 | clink /bin/false /bin/false 104 | clink /bin/mkdir /bin/mkdir 105 | clink /bin/rmdir /bin/rmdir 106 | clink /bin/dd /bin/dd 107 | clink /bin/uname /bin/uname 108 | clink /bin/date /bin/date 109 | clink /bin/kill /bin/kill 110 | clink /bin/tar /bin/tar 111 | clink /bin/gzip /bin/gzip 112 | clink /usr/bin/[ /usr/bin/[ 113 | clink /usr/bin/tail /usr/bin/tail 114 | clink /usr/bin/less /usr/bin/less 115 | clink /usr/bin/awk /usr/bin/awk 116 | clink /usr/bin/free /usr/bin/free 117 | clink /usr/bin/head /usr/bin/head 118 | clink /usr/bin/id /usr/bin/id 119 | clink /usr/bin/tee /usr/bin/tee 120 | clink /usr/bin/test /usr/bin/test 121 | clink /usr/bin/watch /usr/bin/watch 122 | clink /usr/bin/which /usr/bin/which 123 | clink /usr/bin/xargs /usr/bin/xargs 124 | clink /usr/bin/find /usr/bin/find 125 | 126 | # Base directory binding configuration 127 | # Set directory read-only, and prohibit device files 128 | bind / ro,nodev,nosuid 129 | bind /dev ro,dev,noexec,nosuid 130 | bind /usr/lib/terminfo /usr/lib/terminfo ro,nodev,nosuid 131 | 132 | # export a writable upload directory. 133 | # dir /upload 0755 root:root 134 | # bind /opt/upload /upload rw,nodev,noexec,nosuid 135 | 136 | # system command list 137 | # this used for user to change password. 138 | cmd /usr/bin/passwd /usr/bin/passwd -:- 139 | -------------------------------------------------------------------------------- /etc/jail-shell/jail-config/jail-bin-symbolic-link.cfg.sample: -------------------------------------------------------------------------------- 1 | # config description. 2 | # this config file is for linux distribution like redhat 7 which moves the /bin, /sbin, /lib and /lib64 directories into /usr 3 | # The configuration supports the following commands: 4 | # dir 5 | # DESC: create a directory into jail 6 | # COMMAND: dir PATH MODE OWNER 7 | # EXAMPLE: dir /bin/ 0755 root:root 8 | # file: 9 | # DESC: copy a file into jail 10 | # COMMAND: file SRC DEST MODE OWNER 11 | # EXAMPLE: file /etc/nsswitch.conf /etc/nsswitch.conf 0644 root:root 12 | # hlink: 13 | # DESC: create a hardlink file into jail 14 | # COMMAND: file SRC DEST MODE OWNER 15 | # EXAMPLE: file /etc/nsswitch.conf /etc/nsswitch.conf 0644 root:root 16 | # slink: 17 | # DESC: create a symbolic link into jail 18 | # COMMAND: slink TARGET LINKNAME 19 | # EXAMPLE: slink /bin/bash /bin/sh 20 | # clink: 21 | # DESC: Try to create hardlinks instead of copying the files. If linking fails it falls back to copying 22 | # COMMAND: clink TARGET LINKNAME 23 | # EXAMPLE: clink /etc/localtime /etc/localtime 24 | # node: 25 | # DESC: create device file. 26 | # COMMAND: node PATH TYPE MAJON MINOR MODE OWNER 27 | # EXAMPLE: node /dev/null c 1 3 666 root:root 28 | # NOTE: security tips 29 | # should avoid adding block device files. 30 | # bind: 31 | # DESC: bind a directory to jail 32 | # COMMAND: bind [SRC] DEST OPTION 33 | # OPTION: rw,ro,dev,nodev,exec,noexec, refer to (man mount) for the parameter description 34 | # %u in path '[SRC] DEST' will be replaced as user name 35 | # EXAMPLE: bind / ro,nodev,nosuid 36 | # bind /opt/ /opt/ ro,nodev,noexec 37 | # bind /opt/upload /opt/upload rw,nodev,noexec,nosuid 38 | # bind /opt/%u /opt/upload ro,nodev,noexec,nosuid 39 | # cmd: 40 | # DESC: executes commands within the system which outside jail. 41 | # COMMAND: cmd SRC DEST RUN_AS_USER 42 | # RUN_AS_USER: User who executes system commands, -:- means user in jail 43 | # EXAMPLE: cmd /usr/bin/passwd /usr/bin/passwd -:- 44 | # cmd /some/root/command /some/root/command root:root 45 | # cmd /some/user/command /some/user/command user:user 46 | # NOTE: security tips 47 | # This channel may lead users to escape jail, should avoid adding command which can be shell-inject, 48 | # For example, read the commands entered by the user 49 | # 50 | # ################################################################### 51 | # JAIL SAMPLE 52 | # ################################################################### 53 | 54 | # List of basic directories 55 | dir /dev/ 0755 root:root 56 | dir /etc/ 0755 root:root 57 | dir /root/ 0700 root:root 58 | dir /home/ 0755 root:root 59 | dir /proc/ 0755 root:root 60 | dir /usr/bin 0755 root:root 61 | dir /usr/sbin 0755 root:root 62 | dir /usr/lib/ 0755 root:root 63 | dir /usr/lib64/ 0755 root:root 64 | dir /etc/security 0755 root:root 65 | dir /usr/lib/terminfo 0755 root:root 66 | slink /usr/bin /bin 67 | slink /usr/sbin /sbin 68 | slink /usr/lib /lib 69 | slink /usr/lib64 /lib64 70 | 71 | # basic configration files 72 | clink /etc/ld.so.conf /etc/ld.so.conf 73 | clink /etc/security/limits.conf /etc/security/limits.conf 74 | clink /etc/nsswitch.conf /etc/nsswitch.conf 75 | clink /etc/inputrc /etc/inputrc 76 | clink /etc/localtime /etc/localtime 77 | file /usr/local/jail-shell/misc/rootfs/etc/profile /etc/profile 0755 root:root 78 | 79 | # basic device files 80 | clink /dev/null /dev/null 81 | clink /dev/zero /dev/zero 82 | clink /dev/ptmx /dev/ptmx 83 | clink /dev/urandom /dev/urandom 84 | clink /dev/tty /dev/tty 85 | clink /dev/tty1 /dev/tty1 86 | clink /dev/tty2 /dev/tty2 87 | clink /dev/tty3 /dev/tty3 88 | slink /proc/self/fd/2 /dev/stderr 89 | slink /proc/self/fd/0 /dev/stdin 90 | slink /proc/self/fd/1 /dev/stdout 91 | 92 | # basic command list 93 | clink /bin/sh /bin/sh 94 | clink /bin/cat /bin/cat 95 | clink /bin/ls /bin/ls 96 | clink /bin/ps /bin/ps 97 | clink /bin/bash /bin/bash 98 | clink /bin/grep /bin/grep 99 | clink /bin/rm /bin/rm 100 | clink /bin/cp /bin/cp 101 | clink /bin/touch /bin/touch 102 | clink /bin/mv /bin/mv 103 | clink /bin/hostname /bin/hostname 104 | clink /bin/sed /bin/sed 105 | clink /bin/true /bin/true 106 | clink /bin/false /bin/false 107 | clink /bin/mkdir /bin/mkdir 108 | clink /bin/rmdir /bin/rmdir 109 | clink /bin/dd /bin/dd 110 | clink /bin/uname /bin/uname 111 | clink /bin/date /bin/date 112 | clink /bin/kill /bin/kill 113 | clink /bin/tar /bin/tar 114 | clink /bin/gzip /bin/gzip 115 | clink /usr/bin/[ /usr/bin/[ 116 | clink /usr/bin/tail /usr/bin/tail 117 | clink /usr/bin/less /usr/bin/less 118 | clink /usr/bin/awk /usr/bin/awk 119 | clink /usr/bin/free /usr/bin/free 120 | clink /usr/bin/head /usr/bin/head 121 | clink /usr/bin/id /usr/bin/id 122 | clink /usr/bin/tee /usr/bin/tee 123 | clink /usr/bin/test /usr/bin/test 124 | clink /usr/bin/watch /usr/bin/watch 125 | clink /usr/bin/which /usr/bin/which 126 | clink /usr/bin/xargs /usr/bin/xargs 127 | clink /usr/bin/find /usr/bin/find 128 | 129 | # Base directory binding configuration 130 | # Set directory read-only, and prohibit device files 131 | bind / ro,nodev,nosuid 132 | bind /dev ro,dev,noexec,nosuid 133 | bind /usr/lib/terminfo /usr/lib/terminfo ro,nodev,nosuid 134 | 135 | # system command list 136 | # this used for user to change password. 137 | cmd /usr/bin/passwd /usr/bin/passwd -:- 138 | -------------------------------------------------------------------------------- /README_zh-CN.md: -------------------------------------------------------------------------------- 1 | jail-shell安全受限shell 2 | ============== 3 | [![Build Status](https://travis-ci.org/pymumu/jail-shell.svg?branch=master)](https://travis-ci.org/pymumu/jail-shell) 4 | 5 | jail-shell安全受限shell是一个Linux环境下的安全工具,主要使用chroot, namespace技术,限制用户执行特定的命令,和访问特定的目录。 6 | 7 | 可将用户通过ssh, scp, sftp,telnet, 终端等方式登录限制到安全的运行环境中。 8 | 9 | 可用于webhost的ssh受限访问;企业管理员的权限分级管理;一体化产品的权限控制。 10 | 11 | 功能列表 12 | ============== 13 | - **易于使用** 14 | 15 | 通过配置文件,自动生成chroot运行环境,提供jail-shell管理命令方便添加、查看、删除受限用户,以及安装、删除chroot运行环境。 16 | 17 | - **chroot技术限制用户访问范围** 18 | 19 | 采用Linux chroot技术,限制用户的目录访问范围,避免用户访问受限信息,防止用户对系统做破坏。 20 | 21 | - **目录只读保护** 22 | 23 | 对chroot运行环境进行只读保护,避免需要保护的目录被修改、文件被破坏;避免用户创建设备文件,访问系统受限文件,并避免执行外部可执行文件。 24 | 25 | - **namespace限制用户可见范围** 26 | 27 | 采用Linux namespace技术,限制用户pid,mount目录的可见范围,避免信息泄漏。 28 | 29 | - **系统命令通道** 30 | 31 | 提供系统命令代理通道,允许用户在chroot环境中执行真实系统的受限命令,在提供必要功能的情况下,保护系统。 32 | 33 | - **自动处理chroot环境命令依赖** 34 | 35 | 只需要提供命令列表,即可自动将命令依赖的动态库复制到chroot环境,避免手工复制动态库的繁杂工作。 36 | 37 | - **capabilities限制** 38 | 39 | 丢弃关键的capabilities权限,避免系统,chroot运行环境被rootkit破解。 40 | 41 | - **多Linux操作系统支持** 42 | 43 | 支持redhat, sles, debian及其衍生的操作系统。 44 | 45 | 架构说明 46 | ============== 47 | ![Architecture](docs/Architecture.png) 48 | 安全受限shell包含3部分,pam插件,jail-cmd命令代理,jail-shell命令工具。 49 | 50 | - **pam_jail_shell插件**: 51 | 52 | 主要控制登录的用户,根据配置列表,将登录的用户采用chroot, namespace技术,限制在特定的受限目录中。 53 | 54 | - **jail-cmd命令代理**: 55 | 56 | 主要将命令转发到真实的系统中,如用户密码修改,或其他用户相关的业务命令的执行,并对命令做注入检测,防止注入。 57 | 58 | - **jail-shell工具**: 59 | 60 | 主要提供管理安全受限shell的功能,让用户更加易于使用,包括用户的添加、删除,shell的配置,安装,删除。 61 | 62 | **说明** 63 | 1. 用户通过ssh, 终端,telnet等shell管理工具登录到系统后,pam_jail_shell插件根据配置列表,将登录用户的访问范围限制在指定的chroot环境中。 64 | 2. 管理员通过jail-shell命令,管理受限用户名单列表,以及管理chroot环境的命令列表,并配置目录的访问范围。 65 | 3. jail-cmd代理用户执行的系统命令,辅助实现必要的业务功能。 66 | 67 | 68 | 编译安装 69 | ============== 70 | **编译** 71 | ``` 72 | git clone https://github.com/pymumu/jail-shell.git 73 | cd jail-shell 74 | make 75 | ``` 76 | 77 | **安装** 78 | ``` 79 | sudo make install 80 | ``` 81 | 82 | **卸载** 83 | ``` 84 | sudo /usr/local/jail-shell/install -u 85 | ``` 86 | 87 | 使用 88 | ============== 89 | 安装完成后,可使用jail-shell命令管理安全受限shell,通过`jail-shell -h`查看命令帮助 90 | 在使用上,步骤如下: 91 | 1. 使用`useradd username`命令添加用户到系统中。 92 | 2. 使用`jail-shell jail`创建安全受限shell配置,并创建受限运行环境。 93 | 3. 使用`jail-shell user`将用户添加到受限系统中。 94 | 95 | 使用举例 96 | ------------- 97 | 如下以将test用户,添加到安全受限shell为例。 98 | 1. 添加test用户,并设置密码 99 | ```shell 100 | sudo useradd test -s /bin/bash 101 | sudo passwd test 102 | ``` 103 | 104 | 2.创建安全受限shell运行环境 105 | ```shell 106 | sudo jail-shell jail -e test-jail 107 | ``` 108 | 执行上述命令后,将使用模板创建新的安全shell配置,并使用vi打开编译,可使用`vi`命令`:w!`保存后退出。 109 | 使用命令`jail-shell jail -l`可以查看已经配置的安全受限shell列表。 110 | 111 | 3.安装安全受限shell运行环境 112 | ```shell 113 | sudo jail-shell jail -i test-jail 114 | ``` 115 | 116 | 4.将test用户加入到安全受限shell中。 117 | ```shell 118 | sudo jail-shell user -a test -j test-jail 119 | ``` 120 | 121 | 5.使用ssh连接到安全受限shell中进行测试验证 122 | ```shell 123 | ssh test@127.0.0.1 124 | ``` 125 | ![Example](https://github.com/pymumu/backup/raw/master/image/example.gif) 126 | 127 | 配置文件格式说明 128 | ------------- 129 | 配置文件在`/etc/jail-shell/jail-config/`目录中, 后缀为 `.cfg`,此配置文件用于生成制定的jail,将需要的文件,命令复制到jail中。 130 | 配置支持如下命令: 131 | - **dir: 创建目录** 132 | * 参数: 133 | `dir PATH MODE OWNER` 134 | * 例子: 135 | `dir /bin/ 0755 root:root` 136 | 137 | - **file: 复制文件** 138 | * 参数: 139 | `file SRC DEST MODE OWNER` 140 | * 例子: 141 | `file /etc/nsswitch.conf /etc/nsswitch.conf 0644 root:root` 142 | 143 | - **hlink: 创建硬链接文件** 144 | * 参数: 145 | `hlink SRC DEST ` 146 | * 例子: 147 | `hlink /etc/localtime /etc/localtime` 148 | 149 | - **slink: 创建符号连接** 150 | * 参数: 151 | `slink TARGET LINKNAME` 152 | * 例子: 153 | `slink /bin/bash /bin/sh` 154 | 155 | - **clink: 先硬连接创建文件,若失败则复制文件** 156 | * 参数: 157 | `clink SRC DEST ` 158 | * 例子: 159 | `clink /etc/localtime /etc/localtime` 160 | 161 | - **node: 创建设备文件** 162 | * 参数: 163 | `node PATH TYPE MAJON MINOR MODE OWNER` 164 | * 例子: 165 | `node /dev/null c 1 3 666 root:root` 166 | * 安全说明: 167 | 应该避免添加块设备文件。 168 | 169 | - **bind: 绑定映射目录** 170 | * 参数: 171 | `bind [SRC] DEST OPTION` 172 | OPTION: rw,ro,dev,nodev,exec,noexec 173 | 路径'[SRC] DEST'中的%u将会替换为用户名 174 | 查阅`man mount`获取参数说明 175 | * 例子: 176 | `bind / ro,nodev,nosuid` 177 | `bind /opt/ /opt/ ro,nodev,noexec` 178 | `bind /opt/upload /opt/upload rw,nodev,noexec,nosuid` 179 | `bind /opt/%u /opt/upload ro,nodev,noexec,nosuid` 180 | 181 | - **cmd: 执行系统内命令** 182 | * 参数: 183 | `cmd SRC DEST RUN_AS_USER` 184 | RUN_AS_USER: 指定执行命令的用户,-:-表示chroot环境中的用户。 185 | * 例子: 186 | `cmd /usr/bin/passwd /usr/bin/passwd -:- ` 187 | `cmd /some/root/command /some/root/command root:root` 188 | `cmd /some/user/command /some/user/command user:user ` 189 | * 安全说明: 190 | 此通道可能导致用户从chroot环境中逃狱,所以,添加的命令必须避免被注入。 191 | 192 | 安全注意事项 193 | ------------- 194 | 使用安全受限shell时,应采取最小安全授权原则。在保证使用功能的前提下,减少用户权限。 195 | 1. bind安全注意事项 196 | * 对于除/dev目录外,建议增加`nodev`参数,/dev目录设置`ro,noexec`(只读,禁止可执行文件)权限 197 | * 对于chroot运行环境目录,建议设置`ro,nodev,nosuid`(只读,禁止设备文件,禁止suid文件)权限。 198 | * 对于可写目录,建议设置`nodev,noexec,nosuid`(禁止设备文件,禁止可执行文件,禁止suid文件)权限。 199 | 200 | 2. 避免的命令 201 | * 应该避免:`gdb, mount, strace`等调试命令。 202 | 203 | 204 | 文件目录说明: 205 | ============== 206 | |目录 |说明 | 207 | |------------------------------- |-----------------------------------------------------------------| 208 | |`/etc/jail-shell/` |配置文件目录 | 209 | |`/etc/jail-shell/jail-shell.conf` |受限用户配置列表文件 | 210 | |`/etc/jail-shell/jail-config/` |安全受限shell配置文件所在目录,后缀为.cfg文件即被识别为配置文件。| 211 | |`/var/local/jail-shell/` |受限安全shell数据目录 | 212 | |`/var/local/jail-shell/jails` |受限安全shell chroot运行环境目录 | 213 | |`/usr/local/jail-shell` |jail-shell程序目录 | 214 | 215 | 调试chroot环境 216 | ============== 217 | 当向chroot环境中复制命令后,复制后的命令如果执行失败,则需要调试,找出缺少的文件,并添加到chroot环境中。 218 | 219 | 调试需要的工具:`strace`, 调试方法如下 220 | 将strace命令复制到chroot环境中,然后使用strace执行需要调试的命令,找出执行命令缺少的文件。对应的调试命令如下 221 | ```shell 222 | strace -F -eopen command 223 | ``` 224 | -eopen表示跟踪进程打开的文件列表, 必要时,可以不使用此参数。 225 | 226 | 执行上述命令后,排查打开失败的文件列表是否在chroot环境中。 227 | ```shell 228 | open("/etc/ld.so.preload", O_RDONLY) = -1 ENOENT (No such file or directory) 229 | ``` 230 | 如上表示:/etc/ld-so.preload文件读取时不存在,可能需要将上述文件添加到chroot环境中。这时可使用`clink`,`file`命令将缺失文件添加到chroot环境中。 231 | 232 | License 233 | ============== 234 | jail-shell安全受限shell采用GPL-V2 License。 235 | 236 | 类似工具 237 | ============== 238 | [jailkit https://olivier.sessink.nl/jailkit/](https://olivier.sessink.nl/jailkit/) 239 | [rshell https://en.wikipedia.org/wiki/Restricted_shell](https://en.wikipedia.org/wiki/Restricted_shell) 240 | [firejail https://github.com/netblue30/firejail](https://github.com/netblue30/firejail) 241 | 242 | -------------------------------------------------------------------------------- /bin/jail-shell: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (C) 2017 Ruilin Peng (Nick) 4 | # 5 | 6 | umask 027 7 | JAIL_SETUP_SCRIPT="/usr/local/jail-shell/bin/jail-shell-setup" 8 | JAIL_CONFIG_FILE="/etc/jail-shell/jail-shell.conf" 9 | CONF_PATH="/etc/jail-shell/jail-config" 10 | 11 | showhelp() 12 | { 13 | echo "Usage: jail-shell [user | jail | config ] [OPTION]" 14 | echo "Options:" 15 | echo " user [option]" 16 | echo " -l | --list list jail users." 17 | echo " -a | --add [user | group] add a user or group to jail-shell, start with '@' means group." 18 | echo " -d | --del [user | group] del a user or group from jail-shell. start with '@' means group." 19 | echo " -n | --namespace [namespace] user namespace: mnt,pid,ipc,net,uts." 20 | echo " -j | --jail [jail name ] jail shell name." 21 | echo "" 22 | echo " jail [option]" 23 | echo " -l | --list list jail names." 24 | echo " -i | --install [jail name | all] install a jail shell." 25 | echo " -r | --remove [jail name] remove a jail shell." 26 | echo " -c | --clean [jail name | all] clean up a jail shell." 27 | echo " -e | --edit [jail name] create and or edit jail shell configuration." 28 | echo " -d | --delete [jail name] delete jail configuration." 29 | echo "" 30 | echo "example:" 31 | echo " user operation:" 32 | echo " add user: jail-shell user -a user -j jail -n mnt,pid" 33 | echo " add group: jail-shell user -a @group -j jail -n mnt,pid" 34 | echo " del user: jail-shell -d user" 35 | echo " jail operation:" 36 | echo " install jail: jail-shell jail -i jail" 37 | echo " remove jail: jail-shell jail -r jail" 38 | } 39 | 40 | is_user_exist() 41 | { 42 | echo "$1" | grep "@" > /dev/null 2>&1 43 | if [ $? -ne 0 ]; then 44 | id -u $1 >/dev/null 2>&1 45 | return $? 46 | fi 47 | 48 | GROUP="`echo $1 | sed 's/@//g'`" 49 | getent group $GROUP >/dev/null 2>&1 50 | if [ $? -ne 0 ]; then 51 | return 1 52 | fi 53 | 54 | return 0 55 | } 56 | 57 | is_user_added() 58 | { 59 | user="$1" 60 | 61 | grep "^ *$user[[:space:]]" $JAIL_CONFIG_FILE >/dev/null 2>&1 62 | return $? 63 | } 64 | 65 | is_namespace_valid() 66 | { 67 | local namespace=$1 68 | local ret=0 69 | OIFS=$IFS 70 | IFS="," 71 | for f in $namespace 72 | do 73 | case "$f" in 74 | mnt|ipc|net|pid|uts|none) 75 | continue 76 | ;; 77 | * ) 78 | ret=1 79 | break 80 | ;; 81 | esac 82 | 83 | done 84 | IFS=$OIFS 85 | 86 | return $ret 87 | } 88 | 89 | is_jail_exist() 90 | { 91 | local jail=$1 92 | JAIL_CFG="$CONF_PATH/${jail}.cfg" 93 | if [ ! -e $JAIL_CFG ]; then 94 | return 1 95 | fi 96 | 97 | return 0 98 | } 99 | 100 | user() 101 | { 102 | local act="none" 103 | local jail="" 104 | local namespace="mnt,pid" 105 | local user="" 106 | OPTS=`getopt -o la:d:n:j: --long list,add:del:namespace:jail: \ 107 | -n "" -- "$@"` 108 | 109 | if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi 110 | 111 | # Note the quotes around `$TEMP': they are essential! 112 | eval set -- "$OPTS" 113 | 114 | while true; do 115 | case "$1" in 116 | -l | --list) 117 | act="LIST" 118 | shift ;; 119 | -a | --add ) 120 | act="ADD" 121 | user="$2" 122 | shift 2;; 123 | -d | --del) 124 | act="DEL" 125 | user="$2" 126 | shift 2;; 127 | -n | --namespace ) 128 | namespace="$2" 129 | shift 2;; 130 | -j | --jail) 131 | jail="$2" 132 | shift 2;; 133 | -- ) shift; break ;; 134 | * ) break ;; 135 | esac 136 | done 137 | 138 | if [ "$act" = "LIST" ]; then 139 | printf "%-24s %-24s %-24s\n" "[User/Group]" "[Jail]" "[Namespace]" 140 | while read USER JAIL NAMESPACE 141 | do 142 | case "$USER" in 143 | ""|\#*) 144 | continue 145 | ;; 146 | esac 147 | 148 | if [ -z "$NAMESPACE" ]; then 149 | continue 150 | fi 151 | printf "%-24s %-24s %-24s\n" "$USER" "$JAIL" "$NAMESPACE" 152 | done < $JAIL_CONFIG_FILE 153 | elif [ "$act" = "ADD" ]; then 154 | is_user_exist $user 155 | if [ $? -ne 0 ]; then 156 | echo "user or group $user doesn't exists." 157 | echo "please add user or group with 'useradd' or 'groupadd' to system first." 158 | return 1 159 | fi 160 | 161 | is_user_added $user 162 | if [ $? -eq 0 ]; then 163 | echo "user or group $user exists." 164 | return 1 165 | fi 166 | 167 | is_namespace_valid $namespace 168 | if [ $? -ne 0 ]; then 169 | echo "namespace is invalid." 170 | return 1 171 | fi 172 | 173 | if [ -z "$jail" ]; then 174 | echo "please input jail name" 175 | return 1 176 | fi 177 | 178 | is_jail_exist $jail 179 | if [ $? -ne 0 ]; then 180 | echo "jail $jail is not exist." 181 | return 1 182 | fi 183 | 184 | printf "%-24s %-24s %-24s\n" "$user" "$jail" "$namespace" >> $JAIL_CONFIG_FILE 185 | if [ $? -ne 0 ]; then 186 | echo "add config failed." 187 | return 1 188 | fi 189 | return 0 190 | elif [ "$act" = "DEL" ]; then 191 | is_user_exist $user 192 | if [ $? -ne 0 ]; then 193 | echo "user $user doesn't exist." 194 | return 1 195 | fi 196 | 197 | sed -i "/^ *$user[[:space:]]/d" $JAIL_CONFIG_FILE 198 | if [ $? -ne 0 ]; then 199 | echo "delete user failed." 200 | return 1 201 | fi 202 | 203 | return 0 204 | 205 | else 206 | showhelp 207 | return 1 208 | fi 209 | } 210 | 211 | jail() 212 | { 213 | local act="none" 214 | local jail="" 215 | OPTS=`getopt -o li:r:c:e:d: --long list,install:,remove:,clean:edit:,delete: \ 216 | -n "" -- "$@"` 217 | 218 | if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi 219 | 220 | # Note the quotes around `$TEMP': they are essential! 221 | eval set -- "$OPTS" 222 | 223 | while true; do 224 | case "$1" in 225 | -l | --list ) 226 | $JAIL_SETUP_SCRIPT --list 227 | return $? 228 | shift ;; 229 | -i | --install) 230 | $JAIL_SETUP_SCRIPT --install $2 231 | return $? 232 | shift 2;; 233 | -r | --remove) 234 | $JAIL_SETUP_SCRIPT --remove $2 235 | return $? 236 | shift 2;; 237 | -c | --clean) 238 | $JAIL_SETUP_SCRIPT --clean $2 239 | return $? 240 | shift 2;; 241 | -e | --edit) 242 | JAIL="$2" 243 | JAIL_CFG="$CONF_PATH/${JAIL}.cfg" 244 | JAIL_SAMPLE="" 245 | if [ ! -f "$JAIL_CFG" ]; then 246 | if [ -h "/bin" ]; then 247 | JAIL_SAMPLE="$CONF_PATH/jail-bin-symbolic-link.cfg.sample" 248 | else 249 | JAIL_SAMPLE="$CONF_PATH/jail.cfg.sample" 250 | fi 251 | 252 | cp $JAIL_SAMPLE $JAIL_CFG -a 253 | if [ $? -ne 0 ]; then 254 | echo "copy sample at $CONF_PATH failed." 255 | return 1 256 | fi 257 | 258 | 259 | fi 260 | 261 | vi $JAIL_CFG 262 | 263 | # File is not edited. 264 | if [ ! -z "$JAIL_SAMPLE" ]; then 265 | if [ "`stat -c %Y $JAIL_SAMPLE`" = "`stat -c %Y $JAIL_CFG`" ]; then 266 | rm $JAIL_CFG 267 | echo "Jail not saved, you can run vi command :w! to save." 268 | return 1 269 | fi 270 | fi 271 | 272 | echo "please run 'jail-shell jail -i $JAIL' to install jail." 273 | 274 | return 0 275 | shift 2;; 276 | -d | --delete) 277 | JAIL="$2" 278 | JAIL_CFG="$CONF_PATH/${JAIL}.cfg" 279 | is_jail_exist $JAIL 280 | if [ $? -ne 0 ]; then 281 | echo "jail $JAIL doesn't exist." 282 | return 1 283 | fi 284 | 285 | $JAIL_SETUP_SCRIPT --remove $2 >/dev/null 2>&1 286 | 287 | rm -f $JAIL_CFG 288 | if [ $? -ne 0 ]; then 289 | echo "delete jail configuration failed." 290 | return 1 291 | fi 292 | 293 | echo "delete $JAIL success." 294 | 295 | return 0 296 | shift 2;; 297 | -- ) shift; break ;; 298 | * ) break ;; 299 | esac 300 | done 301 | 302 | showhelp 303 | 304 | return $? 305 | } 306 | 307 | config() 308 | { 309 | OPTS=`getopt -o h --long help \ 310 | -n "" -- "$@"` 311 | 312 | if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi 313 | 314 | # Note the quotes around `$TEMP': they are essential! 315 | eval set -- "$OPTS" 316 | 317 | while true; do 318 | case "$1" in 319 | -h | --help ) 320 | showhelp 321 | return 0 322 | shift ;; 323 | -- ) shift; break ;; 324 | * ) break ;; 325 | esac 326 | done 327 | } 328 | 329 | main() 330 | { 331 | local mode="$1" 332 | 333 | if [ $# -lt 1 ]; then 334 | showhelp 335 | return $? 336 | fi 337 | 338 | shift 1 339 | 340 | case "$mode" in 341 | user) 342 | user $@ 343 | return $? 344 | ;; 345 | jail) 346 | jail $@ 347 | return $? 348 | ;; 349 | config) 350 | config $@ 351 | return $? 352 | ;; 353 | help) 354 | showhelp 355 | return $? 356 | ;; 357 | *) 358 | showhelp 359 | return $? 360 | ;; 361 | esac 362 | } 363 | 364 | main $@ 365 | exit $? 366 | -------------------------------------------------------------------------------- /install: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (C) 2017 Ruilin Peng (Nick) 4 | # 5 | 6 | INST_DIR=$(cd $(dirname $0);pwd) 7 | 8 | showhelp() 9 | { 10 | echo "Usage: install [OPTION]" 11 | echo "Options:" 12 | echo " -i install jail-shell." 13 | echo " -u uninstall jail-shell." 14 | echo " --prefix [dir] prefix directory." 15 | echo " -h show this message." 16 | } 17 | 18 | start_service() 19 | { 20 | if [ $ISSYSTEMD -ne 0 ]; then 21 | chkconfig jail-shell on 22 | service jail-shell start 23 | return $? 24 | fi 25 | 26 | systemctl daemon-reload 27 | systemctl enable jail-shell 28 | systemctl start jail-shell 29 | } 30 | 31 | stop_service() 32 | { 33 | if [ $ISSYSTEMD -ne 0 ]; then 34 | service jail-shell stop 35 | chkconfig jail-shell off 36 | return 0 37 | fi 38 | 39 | systemctl stop jail-shell 40 | systemctl disable jail-shell 41 | 42 | return 0 43 | } 44 | 45 | clean_service() 46 | { 47 | if [ $ISSYSTEMD -ne 0 ]; then 48 | return 0 49 | fi 50 | systemctl daemon-reload 51 | } 52 | 53 | add_system_depdence_libs() 54 | { 55 | SAMPLE_FILE=$1 56 | LIBC_PATH="`ldd /bin/sh | grep libc.so | awk '{print $3}'`" 57 | LIB_PATH="`dirname $LIBC_PATH`" 58 | LIBNSS_COMP="`ldconfig -p | grep "libnss_compat.so\." | grep $LIB_PATH | awk -F"=>" '{print $2}'`" 59 | LIBNSS_FILES="`ldconfig -p | grep "libnss_files.so\." | grep $LIB_PATH | awk -F"=>" '{print $2}'`" 60 | LIBNSS_DNS="`ldconfig -p | grep "libnss_dns.so\." | grep $LIB_PATH | awk -F"=>" '{print $2}'`" 61 | LIBDIR="`dirname $LIBNSS_DNS`" 62 | 63 | echo "" >> $SAMPLE_FILE 64 | echo "# Basic library list" >> $SAMPLE_FILE 65 | if [ "`dirname $LIBDIR`" != "/" ]; then 66 | echo "dir `dirname $LIBDIR` 0755 root:root" >> $SAMPLE_FILE 67 | fi 68 | if [ -z "$(echo $LIB_PATH | grep /usr)" ]; then 69 | echo "dir /usr${LIB_PATH} 0755 root:root" >> $SAMPLE_FILE 70 | fi 71 | echo "dir `dirname $LIBNSS_DNS` 0755 root:root" >> $SAMPLE_FILE 72 | echo "clink $LIBNSS_COMP $LIBNSS_COMP" >> $SAMPLE_FILE 73 | echo "clink $LIBNSS_FILES $LIBNSS_FILES" >> $SAMPLE_FILE 74 | echo "clink $LIBNSS_DNS $LIBNSS_DNS" >> $SAMPLE_FILE 75 | } 76 | 77 | get_systemd_path() 78 | { 79 | service="`systemctl --no-legend| grep .service | head -n 1 | awk '{print $1}'`" 80 | SERVICE_PATH="`systemctl show $service | grep FragmentPath | awk -F'=' '{print $2}'`" 81 | dirname $SERVICE_PATH 82 | } 83 | 84 | install_files() 85 | { 86 | install -v -d $JAIL_SHELL_HOME_DIR $JAIL_SHELL_HOME_DIR/jail-cmd/ $JAIL_SHELL_CONF_DIR/jail-config \ 87 | $JAIL_SHELL_CONF_DIR $JAIL_SHELL_HOME_DIR/misc $JAIL_SHELL_HOME_DIR/bin 88 | if [ $? -ne 0 ]; then 89 | return 1 90 | fi 91 | 92 | install -v -m 0755 -t $JAIL_SHELL_HOME_DIR/jail-cmd/ jail-cmd/jail-cmd jail-cmd/jail-cmdd 93 | if [ $? -ne 0 ]; then 94 | return 1 95 | fi 96 | 97 | ln -v -f -s /usr/local/jail-shell/jail-cmd/jail-cmdd $PREFIX/usr/sbin/jail-cmdd 98 | if [ $? -ne 0 ]; then 99 | return 1 100 | fi 101 | 102 | install -v -m 0755 -t $JAIL_SHELL_HOME_DIR/bin bin/jail-shell bin/jail-shell-setup bin/jail-shell-post pam_jail_shell/jail-init 103 | if [ $? -ne 0 ]; then 104 | return 1 105 | fi 106 | 107 | ln -v -f -s /usr/local/jail-shell/bin/jail-shell $PREFIX/usr/sbin/jail-shell 108 | if [ $? -ne 0 ]; then 109 | return 1 110 | fi 111 | 112 | install -v -m 0755 -t $SECURITY_DIR pam_jail_shell/pam_jail_shell.so 113 | if [ $? -ne 0 ]; then 114 | return 1 115 | fi 116 | #for selinux, change permission to lib_t 117 | chcon --type lib_t $SECURITY_DIR/pam_jail_shell.so >/dev/null 2>&1 118 | 119 | if [ ! -f "$PREFIX$JAIL_SHELL_CONF_DIR/jail-shell.conf" ]; then 120 | install -v -m 0600 -t $PREFIX$JAIL_SHELL_CONF_DIR pam_jail_shell/jail-shell.conf 121 | if [ $? -ne 0 ]; then 122 | return 1 123 | fi 124 | fi 125 | 126 | ln -v -f -s $JAIL_SHELL_CONF_DIR/jail-shell.conf /etc/security/jail-shell.conf 127 | if [ $? -ne 0 ]; then 128 | return 1 129 | fi 130 | 131 | install -v -m 0755 -t $JAIL_SHELL_INIT_DIR etc/init.d/jail-shell 132 | if [ $? -ne 0 ]; then 133 | return 1 134 | fi 135 | 136 | if [ $ISSYSTEMD -eq 0 ]; then 137 | SYSTEM_UNIT_PATH="`get_systemd_path`" 138 | if [ -z "$SYSTEM_UNIT_PATH" ]; then 139 | return 1 140 | fi 141 | install -v -m 0644 -t $PREFIX$SYSTEM_UNIT_PATH lib/systemd/system/jail-shell.service 142 | if [ $? -ne 0 ]; then 143 | return 1 144 | fi 145 | fi 146 | 147 | install -v -m 0644 -t $PREFIX/etc/default etc/default/jail-shell 148 | if [ $? -ne 0 ]; then 149 | return 1 150 | fi 151 | 152 | install -v -t $JAIL_SHELL_CONF_DIR etc/jail-shell/cmd_config etc/jail-shell/cmdd_config -m 644 153 | if [ $? -ne 0 ]; then 154 | return 1 155 | fi 156 | 157 | install -v -t $JAIL_SHELL_CONF_DIR/jail-config etc/jail-shell/jail-config/*.sample -m 640 158 | if [ $? -ne 0 ]; then 159 | return 1 160 | fi 161 | 162 | install -v -t $JAIL_SHELL_HOME_DIR install -m 755 163 | if [ $? -ne 0 ]; then 164 | return 1 165 | fi 166 | 167 | add_system_depdence_libs $JAIL_SHELL_CONF_DIR/jail-config/jail.cfg.sample 168 | add_system_depdence_libs $JAIL_SHELL_CONF_DIR/jail-config/jail-bin-symbolic-link.cfg.sample 169 | 170 | cp misc/* $JAIL_SHELL_HOME_DIR/misc/ -avf 171 | if [ $? -ne 0 ]; then 172 | return 1 173 | fi 174 | 175 | chmod 0644 $JAIL_SHELL_HOME_DIR/misc/ -R 176 | if [ $? -ne 0 ]; then 177 | return 1 178 | fi 179 | 180 | add_pam_config 181 | if [ $? -ne 0 ]; then 182 | return 1 183 | fi 184 | 185 | return 0 186 | } 187 | 188 | remove_pam_config() 189 | { 190 | sed -i '/pam_jail_shell.so/d' $PREFIX/etc/pam.d/common-session 2>/dev/null 191 | sed -i '/pam_jail_shell.so/d' $PREFIX/etc/pam.d/login 2>/dev/null 192 | sed -i '/pam_jail_shell.so/d' $PREFIX/etc/pam.d/sshd 2>/dev/null 193 | sed -i '/pam_jail_shell.so/d' $PREFIX/etc/pam.d/su 2>/dev/null 194 | } 195 | 196 | 197 | add_pam_config() 198 | { 199 | remove_pam_config 200 | if [ -e "$PREFIX/etc/pam.d/common-session" ]; then 201 | echo "session required pam_jail_shell.so" >> $PREFIX/etc/pam.d/common-session 2>/dev/null 202 | if [ $? -eq 0 ]; then 203 | return 0 204 | fi 205 | return 1 206 | fi 207 | 208 | echo "session required pam_jail_shell.so" >> $PREFIX/etc/pam.d/login 2>/dev/null 209 | if [ $? -ne 0 ]; then 210 | return 1 211 | fi 212 | 213 | echo "session required pam_jail_shell.so" >> $PREFIX/etc/pam.d/sshd 2>/dev/null 214 | if [ $? -ne 0 ]; then 215 | return 1 216 | fi 217 | 218 | echo "session required pam_jail_shell.so" >> $PREFIX/etc/pam.d/su 2>/dev/null 219 | if [ $? -ne 0 ]; then 220 | return 1 221 | fi 222 | 223 | return $? 224 | } 225 | 226 | 227 | uninstall_jail_shell() 228 | { 229 | if [ -z "$PREFIX" ]; then 230 | remove_pam_config 231 | stop_service 232 | fi 233 | rm -fr $JAIL_SHELL_HOME_DIR 234 | rm -fr $JAIL_SHELL_CONF_DIR/jail-shell.conf 235 | rm -fr $JAIL_SHELL_CONF_DIR/cmd_config 236 | rm -fr $JAIL_SHELL_CONF_DIR/cmdd_config 237 | rm -fr $JAIL_SHELL_CONF_DIR/jail-config/*.sample 238 | rmdir $JAIL_SHELL_CONF_DIR/jail-config 2>/dev/null 239 | rmdir $JAIL_SHELL_CONF_DIR 2>/dev/null 240 | rm -fr $JAIL_SHELL_INIT_DIR/jail-shell 241 | rm -fr $PREFIX/usr/sbin/jail-cmdd 242 | rm -fr $PREFIX/usr/sbin/jail-shell 243 | rm -fr $SECURITY_DIR/pam_jail_shell.so 244 | rm -fr $PREFIX/etc/security/jail-shell.conf 245 | rm -fr $PREFIX/etc/default/jail-shell 246 | 247 | if [ $ISSYSTEMD -eq 0 ]; then 248 | SYSTEM_UNIT_PATH="`get_systemd_path`" 249 | if [ ! -z "$SYSTEM_UNIT_PATH" ]; then 250 | rm -f $PREFIX/$SYSTEM_UNIT_PATH/jail-shell.service 251 | fi 252 | fi 253 | 254 | if [ -z "$PREFIX" ]; then 255 | clean_service 256 | fi 257 | 258 | printf "\033[31mjail home /var/local/jail-shell is not deleted, please check and delete manually.\033[0m\n" 259 | } 260 | 261 | 262 | install_jail_shell() 263 | { 264 | local ret 265 | 266 | install_files 267 | ret=$? 268 | if [ $ret -ne 0 ]; then 269 | uninstall_jail_shell 270 | return $ret 271 | fi 272 | 273 | if [ -z "$PREFIX" ]; then 274 | start_service 275 | fi 276 | 277 | return 0 278 | } 279 | 280 | 281 | 282 | init_dir() 283 | { 284 | JAIL_SHELL_HOME_DIR=$PREFIX/usr/local/jail-shell 285 | JAIL_SHELL_CONF_DIR=$PREFIX/etc/jail-shell 286 | JAIL_SHELL_INIT_DIR=$PREFIX/etc/init.d 287 | LIB_DIR=$PREFIX"`ldd /bin/sh | grep libc | awk '{print $3}' | xargs dirname`" 288 | SECURITY_DIR=$LIB_DIR/security 289 | which systemctl >/dev/null 2>&1 290 | ISSYSTEMD="$?" 291 | 292 | cd $INST_DIR 293 | } 294 | 295 | main() 296 | { 297 | ACTION="" 298 | 299 | OPTS=`getopt -o iuh --long help,prefix: \ 300 | -n "" -- "$@"` 301 | 302 | if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi 303 | 304 | # Note the quotes around `$TEMP': they are essential! 305 | eval set -- "$OPTS" 306 | 307 | while true; do 308 | case "$1" in 309 | --prefix) 310 | PREFIX="$2" 311 | shift 2;; 312 | -h | --help ) 313 | showhelp 314 | return 0 315 | shift ;; 316 | -i ) 317 | ACTION="INSTALL" 318 | shift ;; 319 | -u ) 320 | ACTION="UNINSTALL" 321 | shift ;; 322 | -- ) shift; break ;; 323 | * ) break ;; 324 | esac 325 | done 326 | 327 | init_dir 328 | 329 | if [ -z "$ACTION" ]; then 330 | showhelp 331 | return 0 332 | elif [ "$ACTION" = "INSTALL" ]; then 333 | install_jail_shell 334 | return $? 335 | elif [ "$ACTION" = "UNINSTALL" ]; then 336 | uninstall_jail_shell 337 | return 0 338 | fi 339 | 340 | } 341 | 342 | main $@ 343 | exit $? 344 | 345 | 346 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Jail-Shell 2 | ============== 3 | [![Build Status](https://travis-ci.org/pymumu/jail-shell.svg?branch=master)](https://travis-ci.org/pymumu/jail-shell) 4 | 5 | [中文REAMDME请看这里](README_zh-CN.md) 6 | 7 | Jail-shell is a Linux security tool mainly using chroot, namespaces technologies, limiting users to perform specific commands, and access specific directories. 8 | 9 | Users can login through SSH, SCP, SFTP, Telnet, terminals, etc. and be restricted to a secure operating enviroment. 10 | 11 | Jail-shell can be used for webhost ssh access control, enterprise Administrator's privilege hierarchy management. 12 | 13 | Features 14 | ============== 15 | - **Easy to use** 16 | 17 | Through the configuration file jail-shell automatically generates the chroot running environment. Through jail-shell management commands it's very easy to add, list, delete, restrict users, and easy to install, delete chroot running environment. 18 | 19 | - **Chroot technology limits user access** 20 | 21 | The Linux chroot technology is used to restrict the user's directory access, to avoid users accessing restricted directories and to prevent users from destroying the system. 22 | 23 | - **Directory read-only protection** 24 | 25 | The chroot running enviroment is readonly, this prevents users from deleting protected directories and files, creating device files, and accessing restricted files. 26 | 27 | - **Namespace limit user Visible range** 28 | 29 | Use Linux namespace technology, limit the visible range of user PID, Mount directories, and avoid information leakage. 30 | 31 | - **System command channel** 32 | 33 | Provides a system command-and-proxy channel that allows users to execute a real system's restricted command in a chroot environment, protecting the system in the event that it provides the necessary functionality. 34 | 35 | - **Automatic processing of chroot Environment command library dependencies** 36 | 37 | Only a list of commands is required to automatically copy the dynamic library that the command relies on to the chroot environment, avoiding the cumbersome work of copying the dynamic library manually. 38 | 39 | - **Capabilities Restrictions** 40 | 41 | Discard critical capabilities privileges to avoid the system, and the chroot running environment, being cracked by rootkit. 42 | 43 | - **Multi-Linux operating system support** 44 | 45 | Supports Redhat, SLEs, Debian and their derivative operating systems. 46 | 47 | Architecture 48 | ============== 49 | ![Architecture](docs/Architecture.png) 50 | Jail-shell contains 3 parts, Pam Plugins, jail-cmd command agents, Jail-shell command tools. 51 | 52 | - **pam_jail_shell Plugins** 53 | 54 | Mainly control the login of users. according to the configuration list, use chroot and namespace technology to restrict the login users to a specific restricted directory. 55 | 56 | - **jail-cmd command-and-proxy** 57 | 58 | It forwards specific command to the real system, such as `passwd`, or other user-related business commands, and it also prevents command injection. 59 | 60 | - **jail-shell commandline tool** 61 | 62 | Mainly provides the ability to manage the restricted security shell, making it easier for administrators to use, including user's add, delete, shell's configuration, installation, deletion, etc. 63 | 64 | **Instructions** 65 | 1. According to the configuration, pam_jail_shell limits users to the specified chroot enviroment. 66 | 2. Administrators use jail-shell command to manage the list of restricted users, manage the list of commands for the chroot enviroment, and manage the access range of directories. 67 | 3. Jail-cmd proxies specific command, to help implement the necessary business functions. 68 | 69 | 70 | Compile and install 71 | ============== 72 | **Compile** 73 | ``` 74 | git clone https://github.com/pymumu/jail-shell.git 75 | cd jail-shell 76 | make 77 | ``` 78 | 79 | **Install** 80 | ``` 81 | sudo make install 82 | ``` 83 | 84 | **Uninstall** 85 | ``` 86 | sudo /usr/local/jail-shell/install -u 87 | ``` 88 | 89 | Usage 90 | ============== 91 | After installation, you can use `jail-shell` command to manage jails, `jail-shell -h` for help. 92 | In use, the steps are as follows: 93 | 1. Use `useradd username` command to add user to the system. 94 | 2. Use `jail-shell jail` command to create a chroot enviroment. 95 | 3. Use `jail-shell user` command to add user to the jails. 96 | 97 | 98 | Example 99 | ------------- 100 | The following is an example of adding user `test` to a jail named `test-jail`. 101 | 1. add user `test`,and set password 102 | ```shell 103 | sudo useradd test -s /bin/bash 104 | sudo passwd test 105 | ``` 106 | 107 | 2. create chroot enviroment 108 | ```shell 109 | sudo jail-shell jail -e test-jail 110 | ``` 111 | After executing the above command, a new jail configuration will be created from the template, and it is opened by `vi`, you can edit it, after that, remember to save the configuration with vi command `:w!`. 112 | 113 | 3. install chroot enviroment 114 | ```shell 115 | sudo jail-shell jail -i test-jail 116 | ``` 117 | 118 | 4. add user `test` to jail `test-jail` 119 | ```shell 120 | sudo jail-shell user -a test -j test-jail 121 | ``` 122 | 123 | 5. connect and test whether `test` is jailed. 124 | ```shell 125 | ssh test@127.0.0.1 126 | ``` 127 | ![Example](https://github.com/pymumu/backup/raw/master/image/example.gif) 128 | 129 | Jail Config file format description 130 | ------------- 131 | The jail config file is located at `/etc/jail-shell/jail-config/`, and file suffix is `.cfg` 132 | The configuration supports the following commands: 133 | - **dir** 134 | * DESC: 135 | create a directory into jail 136 | * COMMAND: 137 | `dir PATH MODE OWNER` 138 | * EXAMPLE: 139 | `dir /bin/ 0755 root:root` 140 | 141 | - **file:** 142 | * DESC: 143 | copy a file into jail 144 | * COMMAND: 145 | `file SRC DEST MODE OWNER` 146 | * EXAMPLE: 147 | `file /etc/nsswitch.conf /etc/nsswitch.conf 0644 root:root` 148 | 149 | - **hlink:** 150 | * DESC: 151 | create a hardlink file into jail 152 | * COMMAND: 153 | `file SRC DEST MODE OWNER` 154 | * EXAMPLE: 155 | `file /etc/nsswitch.conf /etc/nsswitch.conf 0644 root:root` 156 | 157 | - **slink:** 158 | * DESC: 159 | create a symbolic link into jail 160 | * COMMAND: 161 | `slink TARGET LINKNAME` 162 | * EXAMPLE: 163 | `slink /bin/bash /bin/sh` 164 | 165 | - **clink:** 166 | * DESC: 167 | Try to create hardlinks instead of copying the files. If linking fails it falls back to copying 168 | * COMMAND: 169 | `clink TARGET LINKNAME` 170 | * EXAMPLE: 171 | `clink /etc/localtime /etc/localtime` 172 | 173 | - **node:** 174 | * DESC: 175 | create device file. 176 | * COMMAND: 177 | `node PATH TYPE MAJON MINOR MODE OWNER` 178 | * EXAMPLE: 179 | `node /dev/null c 1 3 666 root:root` 180 | * NOTE: security tips 181 | should avoid adding block device files. 182 | 183 | - **bind:** 184 | * DESC: 185 | bind a directory to jail 186 | * COMMAND: 187 | `bind [SRC] DEST OPTION` 188 | * OPTION: rw,ro,dev,nodev,exec,noexec, refer to (man mount) for the parameter description 189 | %u in path '[SRC] DEST' will be replaced as user name 190 | * EXAMPLE: 191 | `bind / ro,nodev,nosuid` 192 | `bind /opt/ /opt/ ro,nodev,noexec` 193 | `bind /opt/upload /opt/upload rw,nodev,noexec,nosuid` 194 | `bind /opt/%u /opt/upload ro,nodev,noexec,nosuid` 195 | 196 | - **cmd:** 197 | * DESC: 198 | executes commands within the system which outside jail. 199 | * COMMAND: 200 | `cmd SRC DEST RUN_AS_USER` 201 | * RUN_AS_USER: User who executes system commands, -:- means user in jail 202 | * EXAMPLE: 203 | `cmd /usr/bin/passwd /usr/bin/passwd -:- ` 204 | `cmd /some/root/command /some/root/command root:root` 205 | `cmd /some/user/command /some/user/command user:user ` 206 | * NOTE: security tips 207 | This channel may lead users to escape jail, should avoid adding command which can be shell-inject, 208 | For example, read the commands entered by the user 209 | 210 | Security Tips 211 | ============== 212 | When using jail-shell, the minimum security authorization principle should be adopted. In the premise of ensuring the use of functions, reduce user rights. 213 | 1. `bind` tips 214 | * Except `/dev` directory, it is recommended to add `nodev` parameters, /dev directory must set to `ro, noexec` (read-only, disable executable) permissions. 215 | * For the chroot environment directory, it is recommended to set `ro, nodev, nosuid` (read only, prohibit device files, and prohibit suid files) permissions. 216 | * For writable bind directories, it is recommended to set `nodev, noexec, nosuid` (disable device files, disable executable files, disable suid files) permissions. 217 | 218 | 2. avoid commands 219 | * avoid: debug commands such as `gdb, mount, strace`, etc.. 220 | 221 | 222 | File Directory Description 223 | ============== 224 | | directory |description | 225 | |-------------------------------------|-----------------------------------------------------------------| 226 | | `/etc/jail-shell/` | Configure file Directory | 227 | | `/etc/jail-shell/jail-shell. conf` | Restricted User Configuration list file | 228 | | `/etc/jail-shell/jail-config/` | The directory where the jail shell configuration file is located, and the suffix. cfg file is recognized as a jail configuration file. | 229 | | `/var/local/jail-shell/` | Jail-shell Data Directory | 230 | | `/var/local/jail-shell/jails` | Jail-shell chroot Environment Directory | 231 | | `/usr/local/jail-shell` | Jail-shell program Directory | 232 | 233 | Debugging the chroot environment 234 | ============== 235 | When you copy a command to the chroot environment, if the copy command fails, you need to debug to find the missing dependent files, and add them to the chroot environment. 236 | Copy the `strace` command into the chroot environment, and then use `strace` to execute the commands that need to be debugged to find the missing dependent files. 237 | The following debugging commands are as follows 238 | ```shell 239 | strace -F -eopen command 240 | ``` 241 | -eopen represents a list of files that the trace process opens. 242 | After executing the above command, troubleshoot to find the open file list. 243 | ```shell 244 | open ("/etc/ld.so.preload", "O_RDONLY") = -1 ENOENT (No, such, file, or, directory) 245 | ``` 246 | As indicated above, the `/etc/ld-so.preload` file does not exist when reading, and may need to add the above files to the chroot environment. At this point, you can use the `clink`, `file` command to add missing files to the chroot environment. 247 | 248 | License 249 | ============== 250 | Jail-shell using GPL-V2 License. 251 | 252 | Donate 253 | ============== 254 | [![Support via PayPal](https://cdn.rawgit.com/twolfson/paypal-github-button/1.0.0/dist/button.svg)](https://paypal.me/PengNick/) 255 | 256 | Similar tools 257 | ============== 258 | [jailkit https://olivier.sessink.nl/jailkit/](https://olivier.sessink.nl/jailkit/) 259 | [rshell https://en.wikipedia.org/wiki/Restricted_shell](https://en.wikipedia.org/wiki/Restricted_shell) 260 | [firejail https://github.com/netblue30/firejail](https://github.com/netblue30/firejail) 261 | -------------------------------------------------------------------------------- /jail-cmd/jail-cmd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Ruilin Peng (Nick) 3 | */ 4 | 5 | #include "jail-cmd.h" 6 | 7 | #define JAIL_CMD "jail-cmd" 8 | #define JAIL_CMD_CONF_FILE "/etc/jail-shell/cmd_config" 9 | 10 | int window_size_changed = 0; 11 | int is_term_mode_change = 0; 12 | struct termios tmios; 13 | int is_atty = 0; 14 | 15 | struct cmd_context { 16 | int sock; 17 | int maxfd; 18 | int isatty; 19 | 20 | fd_set rfds; 21 | fd_set wfds; 22 | 23 | int is_stdin_eof; 24 | 25 | struct sock_data send_data; 26 | struct sock_data recv_data; 27 | 28 | int prog_exit; 29 | }; 30 | 31 | struct cmd_config { 32 | int port; 33 | }; 34 | 35 | struct cmd_config config = { 36 | .port = DEFAULT_PORT, 37 | }; 38 | 39 | void help(void) 40 | { 41 | char *help = "" 42 | "Usage: jail-cmd [command] [args]\n" 43 | "run jail command.\n" 44 | "press CTRL+] to exit.\n" 45 | ; 46 | printf("%s", help); 47 | } 48 | 49 | int set_term(void) 50 | { 51 | struct termios tm; 52 | 53 | tcgetattr(STDIN_FILENO, &tmios); 54 | memcpy(&tm, &tmios, sizeof(tm)); 55 | cfmakeraw(&tm); 56 | tcsetattr(STDIN_FILENO, TCSANOW, &tm); 57 | 58 | is_term_mode_change = 1; 59 | return 0; 60 | 61 | } 62 | 63 | void reset_term(void) 64 | { 65 | if (is_term_mode_change == 0) { 66 | return ; 67 | } 68 | 69 | tcsetattr(STDIN_FILENO, TCSANOW, &tmios); 70 | } 71 | 72 | /* send user information, term information, args to peer server. */ 73 | int cmd_init(struct cmd_context *context, int argc, char *argv[]) 74 | { 75 | struct jail_cmd_head *cmd_head = (struct jail_cmd_head *)context->send_data.data; 76 | struct jail_cmd_cmd *cmd = NULL; 77 | int arg_len = 0; 78 | int i = 0; 79 | char *jsidkey = NULL; 80 | 81 | cmd_head->magic = MSG_MAGIC; 82 | cmd_head->type = CMD_MSG_CMD; 83 | cmd_head->data_len = sizeof(*cmd); 84 | cmd = (struct jail_cmd_cmd *)(cmd_head->data); 85 | 86 | cmd->uid = geteuid(); 87 | cmd->gid = getegid(); 88 | cmd->isatty = is_atty; 89 | 90 | jsidkey = getenv(JAIL_KEY); 91 | if (jsidkey) { 92 | strncpy(cmd->jsid, getenv(JAIL_KEY), sizeof(cmd->jsid) - 1); 93 | } else { 94 | memset(cmd->jsid, 0, sizeof(cmd->jsid)); 95 | } 96 | 97 | if (cmd->isatty) { 98 | /* send term name to server */ 99 | if (getenv("TERM") != NULL) { 100 | snprintf(cmd->term, TMP_BUFF_LEN_32, "%s", getenv("TERM")); 101 | } else { 102 | snprintf(cmd->term, TMP_BUFF_LEN_32, "xterm"); 103 | } 104 | 105 | /* send term win size to server */ 106 | if (ioctl(STDIN_FILENO, TIOCGWINSZ, &cmd->ws) != 0) { 107 | fprintf(stderr, "get console win size failed, %s\r\n", strerror(errno)); 108 | return -1; 109 | } 110 | } 111 | 112 | /* send args to server 113 | * data format: arg0\0arg1\0arg2\0...argn\0\0" 114 | */ 115 | 116 | cmd->argc = argc; 117 | if (argc > MAX_ARGS_COUNT) { 118 | fprintf(stderr, "too many args list.\r\n"); 119 | return -1; 120 | } 121 | 122 | for (i = 0; i < argc; i++) { 123 | strncpy(cmd->argvs + arg_len, argv[i], sizeof(context->send_data.data) - sizeof(*cmd_head) - sizeof(*cmd) - arg_len); 124 | arg_len += strlen(argv[i]) + 1; 125 | } 126 | cmd_head->data_len = arg_len + sizeof(*cmd); 127 | 128 | context->send_data.total_len = sizeof(*cmd_head) + cmd_head->data_len; 129 | context->send_data.curr_offset = 0; 130 | 131 | return 0; 132 | } 133 | 134 | CMD_RETURN read_stdin(struct cmd_context *context) 135 | { 136 | int len; 137 | int need_size; 138 | int free_buff_size; 139 | 140 | struct jail_cmd_head *cmd_head; 141 | struct jail_cmd_data *cmd_data; 142 | 143 | free_buff_size = sizeof(context->send_data.data) - context->send_data.total_len; 144 | /* if free space is not enougth, then block reading from stdin */ 145 | need_size = sizeof(struct jail_cmd_head) + sizeof(struct jail_cmd_data) + 16; 146 | if ((free_buff_size - need_size) < 0) { 147 | FD_CLR(STDIN_FILENO, &context->rfds); 148 | return CMD_RETURN_OK; 149 | } 150 | 151 | cmd_head = (struct jail_cmd_head *)(context->send_data.data + context->send_data.total_len); 152 | cmd_data = (struct jail_cmd_data *)cmd_head->data; 153 | cmd_head->magic = MSG_MAGIC; 154 | cmd_head->type = CMD_MSG_DATA_IN; 155 | len = read(STDIN_FILENO, cmd_data->data, free_buff_size - sizeof(struct jail_cmd_head) - sizeof(struct jail_cmd_data)); 156 | if (len < 0) { 157 | FD_CLR(STDIN_FILENO, &context->rfds); 158 | fprintf(stderr, "read mirror failed, %s\r\n", strerror(errno)); 159 | return CMD_RETURN_ERR; 160 | } else if (len == 0) { 161 | /* end of stdin, stop read stdin, and wake up sock , to send remain data to server.*/ 162 | FD_CLR(STDIN_FILENO, &context->rfds); 163 | context->is_stdin_eof = 1; 164 | FD_SET(context->sock, &context->wfds); 165 | return CMD_RETURN_OK; 166 | } 167 | 168 | cmd_head->data_len = len + sizeof(*cmd_data);; 169 | context->send_data.total_len += sizeof(*cmd_head) + cmd_head->data_len; 170 | 171 | if (context->isatty && len == 1 && cmd_data->data[0] == '\35') { 172 | /* if user press CTRL + ], then force exit. */ 173 | FD_CLR(STDIN_FILENO, &context->rfds); 174 | context->is_stdin_eof = 1; 175 | cmd_head->magic = MSG_MAGIC; 176 | cmd_head->type = CMD_MSG_DATA_EXIT; 177 | } 178 | 179 | /* have read data from stdin, wake up sock, and start send. */ 180 | FD_SET(context->sock, &context->wfds); 181 | 182 | return CMD_RETURN_OK; 183 | } 184 | 185 | CMD_RETURN process_cmd(struct cmd_context *context, struct jail_cmd_head *cmd_head) 186 | { 187 | switch (cmd_head->type) { 188 | case CMD_MSG_DATA_OUT: { 189 | /* Write out data to stdout */ 190 | struct jail_cmd_data *cmd_data = (struct jail_cmd_data *)cmd_head->data; 191 | if (write(STDOUT_FILENO, cmd_data->data, cmd_head->data_len) < 0) { 192 | fprintf(stderr, "write data to stdout failed.\r\n"); 193 | } 194 | break; } 195 | case CMD_MSG_DATA_ERR: { 196 | /* Write err data to stderr */ 197 | struct jail_cmd_data *cmd_data = (struct jail_cmd_data *)cmd_head->data; 198 | if (write(STDERR_FILENO, cmd_data->data, cmd_head->data_len) < 0) { 199 | fprintf(stderr, "write data to stderr failed.\r\n"); 200 | } 201 | break; } 202 | case CMD_MSG_EXIT_CODE: { 203 | /* get exit code from peer child process */ 204 | struct jail_cmd_exit *cmd_exit = (struct jail_cmd_exit *)cmd_head->data; 205 | context->prog_exit = cmd_exit->exit_code; 206 | return CMD_RETURN_EXIT; 207 | break; } 208 | default: 209 | fprintf(stderr, "data type error.\r\n"); 210 | return CMD_RETURN_ERR; 211 | } 212 | 213 | return CMD_RETURN_OK; 214 | } 215 | 216 | CMD_RETURN send_sock(struct cmd_context *context) 217 | { 218 | int len; 219 | 220 | /* send data to server */ 221 | len = send(context->sock, context->send_data.data + context->send_data.curr_offset, context->send_data.total_len - context->send_data.curr_offset, MSG_NOSIGNAL | MSG_DONTWAIT); 222 | if (len < 0) { 223 | fprintf(stderr, "socket send failed, %s\r\n", strerror(errno)); 224 | return CMD_RETURN_ERR; 225 | } 226 | context->send_data.curr_offset += len; 227 | 228 | if (context->send_data.curr_offset == context->send_data.total_len) { 229 | /* if all data has been sent, stop send event, and reset buffer length info */ 230 | FD_CLR(context->sock, &context->wfds); 231 | context->send_data.total_len = 0; 232 | context->send_data.curr_offset = 0; 233 | } else if (context->send_data.curr_offset < context->send_data.total_len){ 234 | /* exists more data, move data to the beggining of the buffer */ 235 | memmove(context->send_data.data, context->send_data.data + context->send_data.curr_offset, context->send_data.total_len - context->send_data.curr_offset); 236 | context->send_data.total_len = context->send_data.total_len - context->send_data.curr_offset; 237 | context->send_data.curr_offset = 0; 238 | } else { 239 | fprintf(stderr, "BUG: internal error, data length mismach\r\n"); 240 | return CMD_RETURN_ERR; 241 | } 242 | 243 | if (context->is_stdin_eof == 0) { 244 | /* Have enough free buff now, wake up stdin, and read. */ 245 | FD_SET(STDIN_FILENO, &context->rfds); 246 | } else if (context->is_stdin_eof == 1 && context->send_data.total_len == 0) { 247 | /* if stdin is closed and all data has been sent, then shutdown sock, to notify peer server exit. */ 248 | shutdown(context->sock, SHUT_WR); 249 | } 250 | 251 | return CMD_RETURN_OK; 252 | } 253 | 254 | CMD_RETURN recv_sock(struct cmd_context *context) 255 | { 256 | int len; 257 | struct jail_cmd_head *cmd_head; 258 | CMD_RETURN retval; 259 | 260 | /* recv data from peer server */ 261 | len = recv(context->sock, context->recv_data.data + context->recv_data.total_len, sizeof(context->recv_data.data) - context->recv_data.total_len, MSG_DONTWAIT); 262 | if (len < 0) { 263 | fprintf(stderr, "recv from socket failed, %s\r\n", strerror(errno)); 264 | return CMD_RETURN_ERR; 265 | } else if (len == 0) { 266 | /* if peer server closed, then exit. */ 267 | FD_CLR(context->sock, &context->rfds); 268 | return CMD_RETURN_EXIT; 269 | } 270 | 271 | context->recv_data.total_len += len; 272 | 273 | /* process data which received from server. */ 274 | while (1) { 275 | /* if data is partial, continue recv */ 276 | if (context->recv_data.total_len - context->recv_data.curr_offset < sizeof(struct jail_cmd_head)) { 277 | break; 278 | } 279 | 280 | cmd_head = (struct jail_cmd_head *)(context->recv_data.data + context->recv_data.curr_offset); 281 | if (cmd_head->magic != MSG_MAGIC || cmd_head->data_len > sizeof(context->recv_data.data) - sizeof(struct jail_cmd_head)) { 282 | /* if recevied error data, exit. */ 283 | fprintf(stderr, "Data invalid\r\n"); 284 | return CMD_RETURN_ERR; 285 | } 286 | 287 | /* if data is partial, continue recv */ 288 | if (context->recv_data.total_len - context->recv_data.curr_offset < sizeof(struct jail_cmd_head) + cmd_head->data_len) { 289 | break; 290 | } 291 | 292 | retval = process_cmd(context, cmd_head); 293 | if (retval != CMD_RETURN_OK) { 294 | return retval; 295 | } 296 | 297 | context->recv_data.curr_offset += sizeof(struct jail_cmd_head) + cmd_head->data_len; 298 | } 299 | 300 | if (context->recv_data.total_len == context->recv_data.curr_offset) { 301 | /* if all data has been proceed, reset buffer length info */ 302 | context->recv_data.curr_offset = 0; 303 | context->recv_data.total_len = 0; 304 | } else if (context->recv_data.total_len > context->recv_data.curr_offset) { 305 | /* exists more data, move data to the beggining of the buffer */ 306 | memmove(context->recv_data.data, context->recv_data.data + context->recv_data.curr_offset, context->recv_data.total_len - context->recv_data.curr_offset); 307 | context->recv_data.total_len -= context->recv_data.curr_offset; 308 | context->recv_data.curr_offset = 0; 309 | } else { 310 | fprintf(stderr, "BUG: internal error, data length mismach\r\n"); 311 | return CMD_RETURN_ERR; 312 | } 313 | 314 | return CMD_RETURN_OK; 315 | } 316 | 317 | int set_win_size(struct cmd_context *context) 318 | { 319 | struct jail_cmd_head *cmd_head; 320 | struct jail_cmd_winsize *cmd_winsize; 321 | 322 | if (window_size_changed == 0) { 323 | return 0; 324 | } 325 | 326 | if (sizeof(context->send_data.data) - context->send_data.total_len < sizeof(struct jail_cmd_head)) { 327 | return 0; 328 | } 329 | 330 | /* if terminal win size changed, read winsize and send to peer server */ 331 | cmd_head = (struct jail_cmd_head *)(context->send_data.data + context->send_data.total_len); 332 | cmd_winsize = (struct jail_cmd_winsize *)cmd_head->data; 333 | cmd_head->magic = MSG_MAGIC; 334 | cmd_head->type = CMD_MSG_WINSIZE; 335 | if (ioctl(STDIN_FILENO, TIOCGWINSZ, &cmd_winsize->ws) != 0) { 336 | fprintf(stderr, "get console win size failed, %s\r\n", strerror(errno)); 337 | return -1; 338 | } 339 | 340 | cmd_head->data_len = sizeof(*cmd_winsize); 341 | context->send_data.total_len += sizeof(*cmd_head) + cmd_head->data_len; 342 | 343 | window_size_changed = 0; 344 | /* set sock write event, to send win size info. */ 345 | FD_SET(context->sock, &context->wfds); 346 | 347 | return 0; 348 | } 349 | 350 | int cmd_loop(struct cmd_context *context) 351 | { 352 | fd_set rfds_set; 353 | fd_set wfds_set; 354 | 355 | CMD_RETURN retval; 356 | int select_ret = 0; 357 | 358 | FD_ZERO(&context->rfds); 359 | FD_ZERO(&context->wfds); 360 | 361 | FD_SET(STDIN_FILENO, &context->rfds); 362 | FD_SET(context->sock, &context->rfds); 363 | /* this event will send CMD_MSG_CMD message initialized by cmd_init*/ 364 | FD_SET(context->sock, &context->wfds); 365 | 366 | while (1) { 367 | 368 | if (set_win_size(context) != 0) { 369 | goto errout; 370 | } 371 | 372 | rfds_set = context->rfds; 373 | wfds_set = context->wfds; 374 | 375 | select_ret = select(context->sock + 1, &rfds_set, &wfds_set, NULL, NULL); 376 | if (select_ret < 0) { 377 | if (errno == EINTR) { 378 | continue; 379 | } 380 | fprintf(stderr, "select fd failed, %s\r\n", strerror(errno)); 381 | goto errout; 382 | } else if (select_ret == 0) { 383 | continue; 384 | } 385 | 386 | if (FD_ISSET(context->sock, &rfds_set)) { 387 | /* recv message from peer server */ 388 | retval = recv_sock(context); 389 | if (retval == CMD_RETURN_EXIT) { 390 | goto out; 391 | } else if (retval == CMD_RETURN_ERR) { 392 | goto errout; 393 | } 394 | } 395 | 396 | if (FD_ISSET(context->sock, &wfds_set)) { 397 | /* send message to peer server */ 398 | retval = send_sock(context); 399 | if (retval == CMD_RETURN_EXIT) { 400 | goto out; 401 | } else if (retval == CMD_RETURN_ERR) { 402 | goto errout; 403 | } 404 | } 405 | 406 | if (FD_ISSET(STDIN_FILENO, &rfds_set)) { 407 | /* read data from stdin */ 408 | retval = read_stdin(context); 409 | if (retval == CMD_RETURN_EXIT) { 410 | goto out; 411 | } else if (retval == CMD_RETURN_ERR) { 412 | goto errout; 413 | } 414 | } 415 | } 416 | 417 | out: 418 | return context->prog_exit; 419 | 420 | errout: 421 | return -1; 422 | } 423 | 424 | int run_cmd(int argc, char * argv[], int port) 425 | { 426 | int client = -1; 427 | struct cmd_context *context; 428 | struct sockaddr_in server_addr; 429 | int retval = 1; 430 | 431 | context = malloc(sizeof(*context)); 432 | if (context == NULL) { 433 | fprintf(stderr, "malloc cmd context failed, %s\n", strerror(errno)); 434 | goto errout; 435 | } 436 | memset(context, 0, sizeof(*context)); 437 | 438 | client = socket(PF_INET, SOCK_STREAM, 0); 439 | if (client < 0) { 440 | fprintf(stderr, "create socke failed, %s\n", strerror(errno)); 441 | goto errout; 442 | } 443 | 444 | bzero((char *) &server_addr, sizeof(server_addr)); 445 | server_addr.sin_family = AF_INET; 446 | server_addr.sin_port = htons(port); 447 | server_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 448 | if (connect(client, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { 449 | fprintf(stderr, "connect to cmdd server failed, %s\n", strerror(errno)); 450 | goto errout; 451 | } 452 | 453 | if (cmd_init(context, argc, argv) != 0) { 454 | fprintf(stderr, "init cmd failed.\n"); 455 | goto errout; 456 | } 457 | 458 | set_sock_opt(client); 459 | 460 | context->sock = client; 461 | 462 | /* whether current shell is interactive */ 463 | context->isatty = is_atty; 464 | 465 | if (context->isatty) { 466 | /* if current shell is interactive, then make this shell in raw mode. */ 467 | set_term(); 468 | } 469 | 470 | retval = cmd_loop(context); 471 | 472 | errout: 473 | if (client > 0) { 474 | close(client); 475 | } 476 | 477 | if (context) { 478 | free(context); 479 | } 480 | 481 | return retval; 482 | } 483 | 484 | void onexit(void) 485 | { 486 | if (is_atty) { 487 | reset_term(); 488 | } 489 | } 490 | 491 | void signal_handler(int sig) 492 | { 493 | switch(sig) { 494 | case SIGWINCH: 495 | if (is_atty) { 496 | window_size_changed = 1; 497 | } 498 | return; 499 | break; 500 | } 501 | 502 | onexit(); 503 | _exit(1); 504 | } 505 | 506 | int load_cmd_config(char *param, char *value) 507 | { 508 | if (strncmp(param, CONF_PORT, sizeof(CONF_PORT)) == 0) { 509 | int port = atoi(value); 510 | if (port <= 0) { 511 | fprintf(stderr, "port is invalid: %s\n", value); 512 | return -1; 513 | } 514 | 515 | config.port = port; 516 | } 517 | 518 | return 0; 519 | } 520 | 521 | int main(int argc, char *argv[]) 522 | { 523 | /* if command is not executed from symbol link, the arg[1] is the command will being executed */ 524 | if (strncmp(basename(argv[0]), JAIL_CMD, PATH_MAX) == 0) { 525 | argc -= 1; 526 | argv++; 527 | 528 | if (argc < 1) { 529 | help(); 530 | return 1; 531 | } 532 | } 533 | 534 | is_atty = (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) ? 1 : 0; 535 | 536 | atexit(onexit); 537 | 538 | if (access(JAIL_CMD_CONF_FILE, R_OK) == 0) { 539 | if (load_config(JAIL_CMD_CONF_FILE, load_cmd_config) != 0 ) { 540 | fprintf(stderr, "load configuration failed.\n"); 541 | return 1; 542 | } 543 | } 544 | 545 | signal(SIGWINCH, signal_handler); 546 | signal(SIGTERM, signal_handler); 547 | signal(SIGQUIT, signal_handler); 548 | 549 | return run_cmd(argc, argv, config.port); 550 | } 551 | 552 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | jail-shell 294 | Copyright (c) 2018 Nick Peng 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /bin/jail-shell-setup: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (C) 2017 Ruilin Peng (Nick) 4 | # 5 | 6 | JAIL_DIR= 7 | JAIL_ROOT_DIR= 8 | CONF_PATH=/etc/jail-shell/jail-config 9 | CMD_CONF=/etc/jail-shell/cmd_config 10 | JAIL_SHELL_HOME=/usr/local/jail-shell 11 | JAIL_VAR_DIR=/var/local/jail-shell 12 | COMMAND_PATH=$JAIL_VAR_DIR/command 13 | JAIL_COMMAND_PATH= 14 | JAIL_COMMAND_LIST_FILE="command.list" 15 | JAIL_CMD=/usr/bin/jail-cmd 16 | ELF_FILE_LIST="`mktemp`" 17 | JAIL_SHELL_CONF=/etc/jail-shell/jail-shell.conf 18 | 19 | showhelp() 20 | { 21 | echo "Usage: jail-shell-setup [OPTION]" 22 | echo "Options:" 23 | echo " --list list jail names." 24 | echo " --install [name | all] install jail-shell from config file." 25 | echo " --remove [name] remove a jail-shell." 26 | echo " --clean [name | all] cleanup a jail-shell." 27 | echo " --user [name] mount user, this paramter must before --mount." 28 | echo " --mount [name] mount directory to a jail-shell." 29 | echo " -h, --help show this help message." 30 | echo "" 31 | echo " tool for creating chroot enviroment" 32 | echo " --chroot create a chroot enviroment." 33 | echo " --cfg [cfg] configruation file." 34 | echo " --root [root path] root path." 35 | } 36 | 37 | create_dir() 38 | { 39 | local CREATE_DIR="$1" 40 | local MODE="$2" 41 | local OWNER="$3" 42 | 43 | if [ $# -ne 3 ]; then 44 | return 1 45 | fi 46 | 47 | CHMOD_DIR="$CREATE_DIR" 48 | CHECK_DIR="`dirname $CREATE_DIR`" 49 | while [ ! -d "$CHECK_DIR" ] 50 | do 51 | CHMOD_DIR="$CHECK_DIR" 52 | CHECK_DIR="`dirname $CHECK_DIR`" 53 | done 54 | 55 | mkdir -p $CREATE_DIR 56 | if [ $? -ne 0 ]; then 57 | return 1 58 | fi 59 | chmod $MODE $CHMOD_DIR -R 60 | chown $OWNER $CHMOD_DIR -R 61 | if [ $? -ne 0 ]; then 62 | return 1 63 | fi 64 | 65 | return 0 66 | } 67 | 68 | get_root_dir() 69 | { 70 | # get JAIL_HOME directory path from jail-shell.conf file. 71 | JAIL_DIR="`grep "^ *JAIL_HOME " $JAIL_SHELL_CONF 2>/dev/null | awk '{print $2}' 2>/dev/null`" 72 | 73 | if [ -z "$JAIL_DIR" ]; then 74 | return 1 75 | fi 76 | 77 | if [ ! -d "$JAIL_VAR_DIR" ]; then 78 | create_dir $JAIL_VAR_DIR 0755 root:root 79 | if [ $? -ne 0 ]; then 80 | echo "create $JAIL_VAR_DIR failed" 81 | return 1 82 | fi 83 | chcon --type tmp_t $JAIL_VAR_DIR >/dev/null 2>&1 84 | fi 85 | 86 | if [ ! -d "$JAIL_DIR" ]; then 87 | create_dir $JAIL_DIR 0755 root:root 88 | if [ $? -ne 0 ]; then 89 | echo "create $JAIL_DIR failed" 90 | return 1 91 | fi 92 | chcon --type tmp_t $JAIL_DIR >/dev/null 2>&1 93 | fi 94 | 95 | JAIL_DIR="`readlink -e $JAIL_DIR`" 96 | if [ ! -d "$JAIL_DIR" ]; then 97 | return 1 98 | fi 99 | 100 | if [ "$JAIL_DIR" = "/" ]; then 101 | return 1 102 | fi 103 | 104 | return 0; 105 | } 106 | 107 | link_cp() 108 | { 109 | local src=$1 110 | local target=$2 111 | local link=$3 # 1 for link and copy, 0 for copy only 112 | 113 | if [ -z "$link" ]; then 114 | link=1 115 | fi 116 | 117 | if [ -d "$target" ]; then 118 | target="$target/`basename $src`" 119 | fi 120 | 121 | target_dir="`dirname $target | sed 's#/\{2,\}#/#g'`" 122 | if [ ! -d "$target_dir" ]; then 123 | echo "Directory $target_dir doesn't exist" 124 | return 1 125 | fi 126 | 127 | link_target_dir="`readlink -e $target_dir`" 128 | check_dir="$(cd $target_dir; pwd)" 129 | if [ "$check_dir" != "$link_target_dir" ]; then 130 | target="$JAIL_ROOT_DIR/$link_target_dir/`basename $target`" 131 | fi 132 | 133 | if [ $link -eq 1 ]; then 134 | ln -f $src $target 2>/dev/null 135 | if [ $? -eq 0 ]; then 136 | return 0 137 | fi 138 | fi 139 | 140 | cp -a $src $target 141 | } 142 | 143 | cp_lib() 144 | { 145 | local LIBS_FILE="${ELF_FILE_LIST}.LIBS" 146 | local LIBS_ALL_FILE="${ELF_FILE_LIST}.LIBS_UNIQ" 147 | > $LIBS_FILE 148 | while read FILE 149 | do 150 | ldd $FILE >> $LIBS_FILE 2>/dev/null 151 | done < $ELF_FILE_LIST 152 | 153 | NOT_FOUND="`cat $LIBS_FILE | grep "not found" | awk '{print $1}'`" 154 | if [ ! -z "$NOT_FOUND" ]; then 155 | echo "The following libraries are not found, please make sure all libraries exist and in the library search path" 156 | echo "$NOT_FOUND" 157 | return 1 158 | fi 159 | sort $LIBS_FILE | grep -v "^ldd:" |grep -v "not a dynamic" | grep -v "linux-vdso.so" | awk '{if(NF == 2){print $1}else{print $3}}' | uniq > $LIBS_ALL_FILE 160 | 161 | while read FILE 162 | do 163 | DIR="`dirname $FILE`" 164 | LIB_FILE="$FILE" 165 | if [ -h "$FILE" ]; then 166 | if [ ! -d "$JAIL_ROOT_DIR/$DIR" ]; then 167 | create_dir $JAIL_ROOT_DIR/$DIR 0755 root:root 168 | if [ $? -ne 0 ]; then 169 | echo "create directory failed." 170 | return 1 171 | fi 172 | fi 173 | link_cp $FILE $JAIL_ROOT_DIR/$DIR 174 | if [ $? -ne 0 ]; then 175 | rm -f $LIBS_ALL_FILE 176 | rm -f $LIBS_FILE 177 | return 1 178 | fi 179 | LIB_FILE="`readlink -e $FILE`" 180 | fi 181 | DIR="`dirname $LIB_FILE`" 182 | if [ ! -d "$JAIL_ROOT_DIR/$DIR" ]; then 183 | create_dir $JAIL_ROOT_DIR/$DIR 0755 root:root 184 | if [ $? -ne 0 ]; then 185 | echo "create directory failed." 186 | return 1 187 | fi 188 | fi 189 | link_cp $LIB_FILE $JAIL_ROOT_DIR/$DIR 190 | if [ $? -ne 0 ]; then 191 | rm -f $LIBS_ALL_FILE 192 | rm -f $LIBS_FILE 193 | return 1 194 | fi 195 | done < $LIBS_ALL_FILE 196 | 197 | rm -f $LIBS_ALL_FILE 198 | rm -f $LIBS_FILE 199 | } 200 | 201 | add_elf_file() 202 | { 203 | echo $1 >> $ELF_FILE_LIST 204 | } 205 | 206 | dir() 207 | { 208 | local userinfo="$3" 209 | if [ $# -ne 3 ]; then 210 | echo "arg number is invalid" 211 | return 1 212 | fi 213 | user=${userinfo%%:*} 214 | group=${userinfo##*:} 215 | install -o $user -g $group -m $2 -d $JAIL_ROOT_DIR/$1 216 | } 217 | 218 | file() 219 | { 220 | local userinfo="$4" 221 | if [ $# -ne 4 ]; then 222 | echo "arg number is invalid" 223 | return 1 224 | fi 225 | 226 | if [ -h "$1" ]; then 227 | echo "source file is symbolic link" 228 | return 1 229 | fi 230 | 231 | user=${userinfo%%:*} 232 | group=${userinfo##*:} 233 | link_cp $1 $JAIL_ROOT_DIR/$2 0 234 | if [ $? -ne 0 ]; then 235 | return 1 236 | fi 237 | chmod $3 $JAIL_ROOT_DIR/$2 238 | chown $user:$group $JAIL_ROOT_DIR/$2 239 | 240 | add_elf_file "$1" 241 | } 242 | 243 | clink() 244 | { 245 | if [ $# -ne 2 ]; then 246 | echo "arg number is invalid" 247 | return 1 248 | fi 249 | 250 | if [ -h "$1" ]; then 251 | LINKED_FILE="`readlink -e $1`" 252 | link_cp $1 $JAIL_ROOT_DIR/$1 253 | if [ $? -ne 0 ]; then 254 | return 1 255 | fi 256 | clink $LINKED_FILE $LINKED_FILE 257 | return $? 258 | fi 259 | 260 | TARGET_DIR="`dirname $JAIL_ROOT_DIR/$2`" 261 | if [ ! -d "$TARGET_DIR" ]; then 262 | create_dir $TARGET_DIR 0755 root:root 263 | if [ $? -ne 0 ]; then 264 | echo "create directory $TARGET_DIR failed." 265 | return 1 266 | fi 267 | fi 268 | link_cp $1 $JAIL_ROOT_DIR/$2 269 | if [ $? -ne 0 ]; then 270 | return 1 271 | fi 272 | 273 | add_elf_file "$1" 274 | } 275 | 276 | hlink() 277 | { 278 | if [ $# -ne 2 ]; then 279 | echo "arg number is invalid" 280 | return 1 281 | fi 282 | 283 | ln -f $1 $JAIL_ROOT_DIR/$2 284 | if [ $? -ne 0 ]; then 285 | return 1 286 | fi 287 | 288 | add_elf_file "$1" 289 | } 290 | 291 | slink() 292 | { 293 | if [ $# -ne 2 ]; then 294 | return 1 295 | fi 296 | 297 | ln -f -s $1 $JAIL_ROOT_DIR/$2 298 | } 299 | 300 | node() 301 | { 302 | if [ $# -ne 6 ]; then 303 | return 1 304 | fi 305 | 306 | mknod $JAIL_ROOT_DIR/$1 $2 $3 $4 -m $5 307 | chown $6 $JAIL_ROOT_DIR/$1 308 | } 309 | 310 | cmd() 311 | { 312 | local userinfo="$3" 313 | if [ $# -ne 3 ]; then 314 | echo "arg number is invalid" 315 | return 1 316 | fi 317 | 318 | user=${userinfo%%:*} 319 | group=${userinfo##*:} 320 | 321 | if [ -z "$user" ] || [ -z "$group" ]; then 322 | echo "user or group is invalid." 323 | return 1 324 | fi 325 | 326 | ln -f -s $JAIL_CMD $JAIL_ROOT_DIR/$2 327 | if [ $? -ne 0 ]; then 328 | return 1 329 | fi 330 | 331 | LINK_NAME="`basename $2`" 332 | ln -f -s $1 $JAIL_COMMAND_PATH/$LINK_NAME 333 | if [ $? -ne 0 ]; then 334 | return 1 335 | fi 336 | 337 | echo "$LINK_NAME $user $group" >> $JAIL_COMMAND_PATH/$JAIL_COMMAND_LIST_FILE 338 | } 339 | 340 | bind() 341 | { 342 | local src 343 | local dest 344 | local opts 345 | if [ $# -eq 3 ]; then 346 | src="$1" 347 | dest="$JAIL_ROOT_DIR/$2" 348 | opts="$3" 349 | elif [ $# -eq 2 ]; then 350 | src="$JAIL_ROOT_DIR/$1" 351 | dest="$JAIL_ROOT_DIR/$1" 352 | opts="$2" 353 | else 354 | echo "arg number is invalid" 355 | return 1 356 | fi 357 | 358 | if [ ! -z "$USER_NAME" ]; then 359 | #if user name is not empty and %src %dest include %u, replace %u to user name 360 | src="`echo $src | sed "s/%u/${USER_NAME}/g"`" 361 | dest="`echo $dest | sed "s/%u/${USER_NAME}/g"`" 362 | fi 363 | 364 | if [ ! -e "$src" ] || [ ! -e "$dest" ]; then 365 | echo "directory $src or $dest doesn't exist" 366 | return 1 367 | fi 368 | 369 | mount --bind $src $dest 370 | if [ $? -ne 0 ]; then 371 | return 1 372 | fi 373 | 374 | mount -o remount,$opts,bind $dest 375 | if [ $? -ne 0 ]; then 376 | umount $dest 377 | return 1 378 | fi 379 | } 380 | 381 | add_basic_files() 382 | { 383 | if [ ! -f "$JAIL_ROOT_DIR/etc/passwd" ]; then 384 | echo "root:x:0:0:root:/root:/bin/bash" > $JAIL_ROOT_DIR/etc/passwd 385 | # TODO 386 | # for selinux 387 | chcon --type etc_t $JAIL_ROOT_DIR/etc/passwd >/dev/null 2>&1 388 | 389 | chmod 0644 $JAIL_ROOT_DIR/etc/passwd 390 | chown root:root $JAIL_ROOT_DIR/etc/passwd 391 | fi 392 | 393 | if [ ! -f "$JAIL_ROOT_DIR/etc/group" ]; then 394 | echo "root:x:0:" > $JAIL_ROOT_DIR/etc/group 395 | # TODO 396 | # for selinux 397 | chcon --type etc_t $JAIL_ROOT_DIR/etc/group >/dev/null 2>&1 398 | 399 | chmod 0644 $JAIL_ROOT_DIR/etc/group 400 | chown root:root $JAIL_ROOT_DIR/etc/group 401 | fi 402 | } 403 | 404 | setup_basic_files() 405 | { 406 | if [ $cp_cmd -eq 1 ]; then 407 | # if jail-cmd is needed, copy it into jail 408 | clink $JAIL_SHELL_HOME/jail-cmd/jail-cmd $JAIL_CMD 409 | if [ $? -ne 0 ]; then 410 | echo "copy jail-cmd failed." 411 | return 1 412 | fi 413 | clink $CMD_CONF $CMD_CONF 414 | fi 415 | 416 | # copy init to jail 417 | if [ $CREATE_CHROOT -eq 0 ]; then 418 | clink $JAIL_SHELL_HOME/bin/jail-init /usr/bin/init 419 | if [ $? -ne 0 ]; then 420 | echo "copy jail-init failed." 421 | return 1 422 | fi 423 | fi 424 | 425 | cp_lib 426 | if [ $? -ne 0 ]; then 427 | echo "copy lib failed." 428 | return 1 429 | fi 430 | 431 | add_basic_files 432 | if [ $? -ne 0 ]; then 433 | echo "add basic files failed." 434 | return 1 435 | fi 436 | } 437 | 438 | process_cfg() 439 | { 440 | local jail_name=$1 441 | local jail_cfg=$2 442 | local act="$3" 443 | local cp_cmd=0 444 | 445 | if [ -z "$jail_name" ]; then 446 | return 1 447 | fi 448 | 449 | if [ ! -f "$jail_cfg" ]; then 450 | echo "jail config $jail_cfg is not exist." 451 | return 1 452 | fi 453 | 454 | JAIL_COMMAND_PATH=$COMMAND_PATH/$jail_name 455 | 456 | if [ ! -d "$JAIL_COMMAND_PATH" ]; then 457 | create_dir $JAIL_COMMAND_PATH 0755 root:root 458 | if [ $? -ne 0 ]; then 459 | echo "create jail dir $JAIL_COMMAND_PATH failed." 460 | return 1 461 | fi 462 | fi 463 | 464 | JAIL_ROOT_DIR="$JAIL_DIR/$jail_name" 465 | 466 | if [ "$act" = "setup" ]; then 467 | rm -f $JAIL_COMMAND_PATH/$JAIL_COMMAND_LIST_FILE 468 | fi 469 | 470 | LINE_NO=0 471 | while read CMD ARGS; 472 | do 473 | LINE_NO=$((LINE_NO+1)) 474 | case "$CMD" in 475 | ""|\#*) 476 | continue 477 | ;; 478 | bind ) 479 | # this is called by pam_jail_shell when user login 480 | if [ "$act" != "mount" ]; then 481 | continue; 482 | fi 483 | ;; 484 | dir | file | hlink | slink | clink | node) 485 | # this is called when install jail 486 | if [ "$act" != "setup" ]; then 487 | continue; 488 | fi 489 | ;; 490 | cmd) 491 | # this is called when install jail 492 | if [ "$act" != "setup" ]; then 493 | continue; 494 | fi 495 | cp_cmd=1 496 | ;; 497 | *) 498 | echo "unknown command at line $LINE_NO: $CMD $ARGS" 499 | return 1 500 | ;; 501 | esac 502 | 503 | $CMD $ARGS 504 | if [ $? -ne 0 ]; then 505 | echo "command failed at line $LINE_NO: $CMD $ARGS" 506 | return 1 507 | fi 508 | done < $jail_cfg 509 | 510 | if [ "$act" = "setup" ]; then 511 | setup_basic_files 512 | if [ $? -ne 0 ]; then 513 | return 1 514 | fi 515 | fi 516 | 517 | return 0 518 | } 519 | 520 | setup_jail() 521 | { 522 | local jail_name=$1 523 | local jail_cfg=$2 524 | 525 | process_cfg $jail_name $jail_cfg "setup" 526 | if [ $? -ne 0 ]; then 527 | return 1; 528 | fi 529 | } 530 | 531 | create_chroot() 532 | { 533 | local jail_root=$1 534 | local jail_cfg=$2 535 | local jail_name="" 536 | 537 | CREATE_CHROOT=1 538 | 539 | if [ -z "$jail_root" ]; then 540 | echo "Please input root path." 541 | return 1 542 | fi 543 | 544 | if [ ! -d "`dirname $jail_root`" ]; then 545 | echo "directory `dirname $jail_root` doesn't exist." 546 | return 1 547 | fi 548 | 549 | JAIL_DIR="$(cd $(dirname $jail_root); pwd)" 550 | jail_name="`basename $jail_root`" 551 | 552 | if [ -z "$JAIL_DIR" ] || [ ! -d "$JAIL_DIR" ]; then 553 | echo "Directory doesn't exist $JAIL_DIR" 554 | return 1 555 | fi 556 | 557 | if [ -z "$jail_name" ]; then 558 | echo "Get directory failed, $1" 559 | return 1 560 | fi 561 | 562 | if [ ! -f "$jail_cfg" ]; then 563 | echo "chroot config file $jail_cfg doesn't exist." 564 | return 1 565 | fi 566 | 567 | process_cfg $jail_name $jail_cfg "setup" 568 | if [ $? -ne 0 ]; then 569 | return 1; 570 | fi 571 | } 572 | 573 | clean_jail() 574 | { 575 | JAIL_NAME="$1" 576 | 577 | JAIL_PATH="$JAIL_DIR/$JAIL_NAME" 578 | 579 | JAIL_CFG="$CONF_PATH/${JAIL_NAME}.cfg" 580 | if [ ! -e $JAIL_CFG ]; then 581 | echo "jail $JAIL_NAME doesn't exist." 582 | return 1 583 | fi 584 | 585 | if [ ! -d "$JAIL_PATH" ]; then 586 | echo "jail $JAIL_NAME is not installed" 587 | return 1 588 | fi 589 | 590 | # kill all process in jail, and umount directories. 591 | which fuser >/dev/null 2>&1 592 | if [ $? -ne 0 ]; then 593 | printf "\033[31mneed fuser command to kill process, please install.\033[0m\n" 594 | echo "clean may not success." 595 | fi 596 | fuser -k $JAIL_PATH >/dev/null 2>&1 597 | 598 | cat /proc/mounts | grep "/$JAIL_NAME[/| ]" | tac | awk '{print $2}' | uniq | xargs -i umount {} 599 | if [ $? -ne 0 ]; then 600 | echo "umount directories failed." 601 | return 1 602 | fi 603 | 604 | return 0 605 | } 606 | 607 | clean_jails() 608 | { 609 | local ret=0 610 | JAIL_NAME="$1" 611 | if [ "$JAIL_NAME" = "all" ]; then 612 | for JAIL_CFG in `ls $CONF_PATH/*.cfg 2>/dev/null` 613 | do 614 | JAIL_NAME="`basename $JAIL_CFG .cfg`" 615 | JAIL_PATH="$JAIL_DIR/$JAIL_NAME" 616 | if [ ! -d "$JAIL_PATH" ]; then 617 | continue 618 | fi 619 | clean_jail "$JAIL_NAME" "$JAIL_CFG" 620 | if [ $? -ne 0 ]; then 621 | echo "setup jail $JAIL_NAME failed." 622 | ret=1 623 | fi 624 | done 625 | 626 | return $ret 627 | fi 628 | 629 | JAIL_CFG="$CONF_PATH/${JAIL_NAME}.cfg" 630 | if [ ! -e $JAIL_CFG ]; then 631 | echo "jail $JAIL_NAME doesn't exist." 632 | return 1 633 | fi 634 | 635 | clean_jail "$JAIL_NAME" "$JAIL_CFG" 636 | if [ $? -ne 0 ]; then 637 | echo "clean jail $JAIL_NAME failed." 638 | ret=1 639 | fi 640 | 641 | return $ret 642 | } 643 | 644 | remove_jail() 645 | { 646 | local ret=0 647 | JAIL_NAME="$1" 648 | 649 | JAIL_CFG="$CONF_PATH/${JAIL_NAME}.cfg" 650 | if [ ! -e $JAIL_CFG ]; then 651 | echo "jail $JAIL_NAME doesn't exist." 652 | return 1 653 | fi 654 | 655 | JAIL_PATH="$JAIL_DIR/$JAIL_NAME" 656 | if [ ! -d "$JAIL_PATH" ]; then 657 | echo "jail $JAIL_NAME is not installed." 658 | return 1 659 | fi 660 | 661 | clean_jail $JAIL_NAME 662 | 663 | rm -fr $JAIL_PATH 664 | 665 | return $? 666 | } 667 | 668 | install_jails() 669 | { 670 | local ret=0 671 | JAIL_NAME="$1" 672 | if [ "$JAIL_NAME" = "all" ]; then 673 | for JAIL_CFG in `ls $CONF_PATH/*.cfg 2>/dev/null` 674 | do 675 | JAIL_NAME="`basename $JAIL_CFG .cfg`" 676 | setup_jail "$JAIL_NAME" "$JAIL_CFG" 677 | if [ $? -ne 0 ]; then 678 | echo "setup jail $JAIL_NAME failed." 679 | ret=1 680 | fi 681 | done 682 | 683 | return $ret 684 | fi 685 | 686 | JAIL_CFG="$CONF_PATH/${JAIL_NAME}.cfg" 687 | if [ ! -e $JAIL_CFG ]; then 688 | echo "jail $JAIL_NAME doesn't exist." 689 | return 1 690 | fi 691 | 692 | setup_jail "$JAIL_NAME" "$JAIL_CFG" 693 | if [ $? -ne 0 ]; then 694 | echo "setup jail $JAIL_NAME failed." 695 | ret=1 696 | fi 697 | 698 | return $ret 699 | } 700 | 701 | mount_jail() 702 | { 703 | local ret=0 704 | JAIL_NAME="$1" 705 | USER_NAME="$2" 706 | 707 | JAIL_CFG="$CONF_PATH/${JAIL_NAME}.cfg" 708 | if [ ! -e $JAIL_CFG ]; then 709 | echo "jail $JAIL_NAME doesn't exist." 710 | return 1 711 | fi 712 | 713 | JAIL_ROOT_DIR="$JAIL_DIR/$jail_name" 714 | if [ ! -d "$JAIL_ROOT_DIR" ]; then 715 | echo "jail $JAIL_NAME is not installed" 716 | return 1 717 | fi 718 | 719 | process_cfg $JAIL_NAME $JAIL_CFG "mount" 720 | if [ $? -ne 0 ]; then 721 | return 1 722 | fi 723 | 724 | return 0 725 | 726 | } 727 | 728 | list_jail() 729 | { 730 | if [ -d "$JAIL_DIR/$JAIL_NAME" ]; then 731 | printf "%-24s%-24s%-24s\n" "$JAIL_NAME" "Installed" "$JAIL_DIR/$JAIL_NAME" 732 | else 733 | printf "%-24s%-24s%-24s\n" "$JAIL_NAME" "Not Installed" "N/A" 734 | fi 735 | } 736 | 737 | list_jails() 738 | { 739 | printf "%-24s%-24s%-24s\n" "[Jail]" "[Install]" "[Root Path]" 740 | 741 | for JAIL_CFG in `ls $CONF_PATH/*.cfg 2>/dev/null` 742 | do 743 | JAIL_NAME="`basename $JAIL_CFG .cfg`" 744 | list_jail 745 | done 746 | } 747 | 748 | main() 749 | { 750 | local USER="" 751 | local ACTION="" 752 | local JAIL_NAME="" 753 | 754 | OPTS=`getopt -o h --long help,install:,remove:,clean:,user:,mount:,list,chroot,cfg:,root: \ 755 | -n "" -- "$@"` 756 | 757 | if [ $# -eq 0 ]; then 758 | showhelp 759 | return 0 760 | fi 761 | 762 | if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi 763 | 764 | # Note the quotes around `$OPTS': they are essential! 765 | eval set -- "$OPTS" 766 | 767 | while true; do 768 | case "$1" in 769 | -h | --help ) 770 | showhelp 771 | return $! 772 | shift ;; 773 | --install ) 774 | JAIL_NAME="$2" 775 | ACTION="INSTALL" 776 | shift 2;; 777 | --remove ) 778 | JAIL_NAME="$2" 779 | ACTION="REMOVE" 780 | shift 2;; 781 | --clean ) 782 | JAIL_NAME="$2" 783 | ACTION="CLEAN" 784 | shift 2;; 785 | --user ) 786 | USER="$2" 787 | shift 2;; 788 | --mount ) 789 | JAIL_NAME="$2" 790 | ACTION="MOUNT" 791 | shift 2;; 792 | --list ) 793 | ACTION="LIST" 794 | shift 1;; 795 | --chroot ) 796 | ACTION="CHROOT"; 797 | shift ;; 798 | --cfg ) 799 | CHROOT_CFG_FILE="$2" 800 | shift 2;; 801 | --root ) 802 | CHROOT_ROOT_PATH="$2" 803 | shift 2;; 804 | -- ) shift; break ;; 805 | * ) break ;; 806 | esac 807 | done 808 | 809 | if [ "$ACTION" = "CHROOT" ]; then 810 | create_chroot "$CHROOT_ROOT_PATH" "$CHROOT_CFG_FILE" 811 | return $? 812 | fi 813 | 814 | CREATE_CHROOT=0 815 | 816 | get_root_dir 817 | if [ $? -ne 0 ]; then 818 | echo "Please config JAIL_HOME path at file '$JAIL_SHELL_CONF'" 819 | return 1 820 | fi 821 | 822 | if [ "$ACTION" = "INSTALL" ]; then 823 | install_jails $JAIL_NAME 824 | elif [ "$ACTION" = "REMOVE" ]; then 825 | remove_jail $JAIL_NAME 826 | elif [ "$ACTION" = "CLEAN" ]; then 827 | clean_jails $JAIL_NAME 828 | elif [ "$ACTION" = "MOUNT" ]; then 829 | mount_jail $JAIL_NAME $USER 830 | elif [ "$ACTION" = "LIST" ]; then 831 | list_jails 832 | else 833 | showhelp 834 | fi 835 | } 836 | 837 | main $@ 838 | ret=$? 839 | rm -fr $ELF_FILE_LIST 840 | exit $ret 841 | -------------------------------------------------------------------------------- /pam_jail_shell/pam_jail_shell.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Ruilin Peng (Nick) 3 | */ 4 | 5 | #define _GNU_SOURCE 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #define PAM_SM_SESSION 29 | #include 30 | #include 31 | #include 32 | 33 | #define MAX_GROUP_NUM 64 34 | 35 | #ifndef PAM_EXTERN 36 | #define PAM_EXTERN 37 | #endif 38 | 39 | #define JAIL_CONF_PATH "/etc/jail-shell/jail-shell.conf" 40 | #define NS_PID_FILE_PATH "/var/run/jail-shell-ns-%s.pid" 41 | #define JAIL_VAR_DIR "/var/local/jail-shell" 42 | #define JAIL_VAR_JSID_DIR "/var/local/jail-shell/jsid" 43 | #define JAIL_JSID_FILE "/var/local/jail-shell/jsid/jsid-%s" 44 | #define MOUNT_SCRIPT_PATH "/usr/local/jail-shell/bin/jail-shell-setup" 45 | #define LOGIN_POST_SCRIPT "/usr/local/jail-shell/bin/jail-shell-post" 46 | 47 | #define TMP_BUFF_LEN_32 32 48 | #define MAX_LINE_LEN 4096 49 | #define MAX_USER_INFO_LEN 4096 50 | #define MAX_FIELD_LEN 1024 51 | #define JAIL_HOME_CONFIG "JAIL_HOME" 52 | #define JAIL_KEY "JSID" 53 | 54 | extern char *program_invocation_name; 55 | 56 | struct user_jail_struct { 57 | char name[MAX_FIELD_LEN]; 58 | char jail[MAX_FIELD_LEN]; 59 | int namespace_flag; 60 | }; 61 | 62 | struct user_jail_struct user_jail[MAX_USER_INFO_LEN]; 63 | int user_jail_number; 64 | char jail_home[MAX_LINE_LEN]; 65 | 66 | struct cap_drop_struct { 67 | char *capname; 68 | unsigned int cap; 69 | int isdrop; 70 | }; 71 | 72 | int setns(int fd, int nstype) __attribute__((weak)); 73 | 74 | /* for cap details, please read (man capabilities) */ 75 | struct cap_drop_struct cap_drop[] = 76 | { 77 | {"CAP_AUDIT_CONTROL", CAP_AUDIT_CONTROL, 1}, 78 | #ifdef CAP_AUDIT_READ 79 | {"CAP_AUDIT_READ", CAP_AUDIT_READ, 1}, 80 | #endif 81 | {"CAP_AUDIT_WRITE", CAP_AUDIT_WRITE, 1}, 82 | #ifdef CAP_BLOCK_SUSPEND 83 | {"CAP_BLOCK_SUSPEND", CAP_BLOCK_SUSPEND, 1}, 84 | #endif 85 | {"CAP_CHOWN", CAP_CHOWN, 0}, 86 | {"CAP_DAC_OVERRIDE", CAP_DAC_OVERRIDE, 1}, 87 | {"CAP_DAC_READ_SEARCH", CAP_DAC_READ_SEARCH, 1}, 88 | {"CAP_FOWNER", CAP_FOWNER, 1}, 89 | {"CAP_FSETID", CAP_FSETID, 1}, 90 | {"CAP_IPC_LOCK", CAP_IPC_LOCK, 0}, 91 | {"CAP_IPC_OWNER", CAP_IPC_OWNER, 1}, 92 | {"CAP_KILL", CAP_KILL, 1}, 93 | {"CAP_LEASE", CAP_LEASE, 0}, 94 | {"CAP_LINUX_IMMUTABLE", CAP_LINUX_IMMUTABLE, 0}, 95 | {"CAP_MAC_ADMIN", CAP_MAC_ADMIN, 0}, 96 | {"CAP_MAC_OVERRIDE", CAP_MAC_OVERRIDE, 0}, 97 | {"CAP_MKNOD", CAP_MKNOD, 1}, 98 | {"CAP_NET_ADMIN", CAP_NET_ADMIN, 1}, 99 | {"CAP_NET_BIND_SERVICE", CAP_NET_BIND_SERVICE, 1}, 100 | {"CAP_NET_BROADCAST", CAP_NET_BROADCAST, 0}, 101 | {"CAP_NET_RAW", CAP_NET_RAW, 1}, 102 | {"CAP_SETGID", CAP_SETGID, 0}, 103 | {"CAP_SETFCAP", CAP_SETFCAP, 1}, 104 | {"CAP_SETPCAP", CAP_SETPCAP, 1}, 105 | {"CAP_SETUID", CAP_SETUID, 1}, 106 | {"CAP_SYS_ADMIN", CAP_SYS_ADMIN, 1}, 107 | {"CAP_SYS_BOOT", CAP_SYS_BOOT, 1}, 108 | {"CAP_SYS_CHROOT", CAP_SYS_CHROOT, 1}, 109 | {"CAP_SYS_MODULE", CAP_SYS_MODULE, 1}, 110 | {"CAP_SYS_NICE", CAP_SYS_NICE, 1}, 111 | {"CAP_SYS_PACCT", CAP_SYS_PACCT, 0}, 112 | {"CAP_SYS_PTRACE", CAP_SYS_PTRACE, 1}, 113 | {"CAP_SYS_RAWIO", CAP_SYS_RAWIO, 1}, 114 | {"CAP_SYS_RESOURCE", CAP_SYS_RESOURCE, 1}, 115 | {"CAP_SYS_TIME", CAP_SYS_TIME, 1}, 116 | {"CAP_SYS_TTY_CONFIG", CAP_SYS_TTY_CONFIG, 0}, 117 | #ifdef CAP_SYSLOG 118 | {"CAP_SYSLOG", CAP_SYSLOG, 0}, 119 | #endif 120 | #ifdef CAP_WAKE_ALARM 121 | {"CAP_WAKE_ALARM", CAP_WAKE_ALARM, 0}, 122 | #endif 123 | }; 124 | 125 | enum { 126 | FLAG_NEWIPC = 0, 127 | FLAG_NEWNET, 128 | FLAG_NEWNS, 129 | FLAG_NEWPID, 130 | FLAG_NEWUSER, 131 | FLAG_NEWUTS, 132 | FLAG_NONE, 133 | }; 134 | 135 | char *const namespace_opt[] = { 136 | [FLAG_NEWIPC] = "ipc", 137 | [FLAG_NEWNET] = "net", 138 | [FLAG_NEWNS] = "mnt", 139 | [FLAG_NEWPID] = "pid", 140 | /* [FLAG_NEWUSER] = "user", */ 141 | [FLAG_NEWUTS] = "uts", 142 | [FLAG_NONE] = "none", 143 | NULL 144 | }; 145 | 146 | int cap_drop_size = sizeof(cap_drop) / sizeof(struct cap_drop_struct); 147 | 148 | pam_handle_t *jail_shell_pamh = NULL; 149 | 150 | void pam_log(int priority, const char *format, ...) 151 | { 152 | va_list args; 153 | 154 | va_start(args, format); 155 | if (jail_shell_pamh == NULL) { 156 | vprintf(format, args); 157 | } else { 158 | pam_vsyslog(jail_shell_pamh, priority, format, args); 159 | } 160 | va_end(args); 161 | } 162 | 163 | int drop_cap(void) 164 | { 165 | int i; 166 | 167 | for (i = 0; i < cap_drop_size; i++) { 168 | if (cap_drop[i].isdrop == 0) { 169 | continue; 170 | } 171 | 172 | if (prctl(PR_CAPBSET_DROP, cap_drop[i].cap, 0, 0) < 0) { 173 | //pam_log("Drop %s failed, errno %s\n", cap_drop[i].capname, strerror(errno)); 174 | continue; 175 | } 176 | 177 | } 178 | return 0; 179 | } 180 | 181 | unsigned long get_rnd_number(void) 182 | { 183 | int rnd_fd = -1; 184 | unsigned long rnd_num = 0; 185 | 186 | rnd_fd = open("/dev/urandom", O_RDONLY); 187 | if (rnd_fd < 0) { 188 | pam_log(LOG_ERR, "open /dev/urandom file failed, %s", strerror(errno)); 189 | goto errout; 190 | } 191 | 192 | if (read(rnd_fd, &rnd_num, sizeof(rnd_num)) != sizeof(rnd_num)) { 193 | pam_log(LOG_ERR, "read /dev/urandom file failed, %s", strerror(errno)); 194 | goto errout; 195 | } 196 | 197 | close(rnd_fd); 198 | 199 | return rnd_num; 200 | errout: 201 | if (rnd_fd > 0) { 202 | close(rnd_fd); 203 | } 204 | return -1; 205 | } 206 | 207 | int get_namespace_flag(char *namespace, int max_len) 208 | { 209 | #ifdef __NR_setns 210 | int flag = 0; 211 | char *value; 212 | char optarg[MAX_FIELD_LEN]; 213 | char *subopts = optarg; 214 | 215 | strncpy(optarg, namespace, max_len); 216 | 217 | while (*subopts != '\0') { 218 | switch (getsubopt(&subopts, namespace_opt, &value)) { 219 | case FLAG_NEWIPC: 220 | flag |= CLONE_NEWIPC; 221 | break; 222 | case FLAG_NEWNET: 223 | flag |= CLONE_NEWNET; 224 | break; 225 | case FLAG_NEWNS: 226 | flag |= CLONE_NEWNS; 227 | break; 228 | case FLAG_NEWPID: 229 | flag |= CLONE_NEWPID; 230 | break; 231 | case FLAG_NEWUSER: 232 | flag |= CLONE_NEWUSER; 233 | break; 234 | case FLAG_NEWUTS: 235 | flag |= CLONE_NEWUTS; 236 | break; 237 | case FLAG_NONE: 238 | return 0; 239 | default: 240 | pam_log(LOG_ERR, "namespace option is invalid. %s", namespace); 241 | return -1; 242 | break; 243 | } 244 | } 245 | 246 | return flag; 247 | #else 248 | return 0; 249 | #endif 250 | } 251 | 252 | int load_config(void) 253 | { 254 | FILE *fp; 255 | int ret = 0; 256 | char line[MAX_LINE_LEN]; 257 | char filed1[MAX_FIELD_LEN]; 258 | char filed2[MAX_FIELD_LEN]; 259 | char filed3[MAX_FIELD_LEN]; 260 | int filedNum = 0; 261 | int flag = -1; 262 | 263 | fp = fopen(JAIL_CONF_PATH, "r"); 264 | if (fp == NULL) { 265 | pam_log(LOG_ERR, "open %s file failed, %s", JAIL_CONF_PATH, strerror(errno)); 266 | return -1; 267 | } 268 | 269 | while (fgets(line, MAX_LINE_LEN, fp)) { 270 | filedNum = sscanf(line, "%1024s %1024s %1024s", filed1, filed2, filed3); 271 | if (filedNum < 0) { 272 | continue; 273 | } 274 | 275 | if (filedNum == 2) { 276 | if (strncmp(filed1, JAIL_HOME_CONFIG, sizeof(filed1)) != 0) { 277 | continue; 278 | } 279 | strncpy(jail_home, filed2, MAX_FIELD_LEN); 280 | } else if (filedNum == 3) { 281 | struct user_jail_struct *info; 282 | if (filed1[0] == '#') { 283 | continue; 284 | } 285 | info = &user_jail[user_jail_number]; 286 | strncpy(info->name, filed1, MAX_FIELD_LEN); 287 | strncpy(info->jail, filed2, MAX_FIELD_LEN); 288 | flag = get_namespace_flag(filed3, MAX_FIELD_LEN); 289 | if (flag == -1) { 290 | ret = 1; 291 | break; 292 | } 293 | info->namespace_flag = flag; 294 | user_jail_number++; 295 | } 296 | } 297 | 298 | fclose(fp); 299 | return ret; 300 | } 301 | 302 | struct user_jail_struct *get_user_jail(pam_handle_t *pamh) 303 | { 304 | struct passwd *pwd; 305 | struct group *gr; 306 | const char *user; 307 | int ret; 308 | gid_t groups[MAX_GROUP_NUM]; 309 | int ngroups = MAX_GROUP_NUM; 310 | int i; 311 | int j; 312 | struct user_jail_struct *info; 313 | 314 | ret = pam_get_user(pamh, &user, NULL); 315 | if (ret != PAM_SUCCESS) { 316 | return NULL; 317 | } 318 | 319 | for (i = 0; i < user_jail_number; i++) { 320 | info = &user_jail[i]; 321 | if (strncmp(user, info->name, MAX_FIELD_LEN) != 0) { 322 | continue; 323 | } 324 | 325 | return info; 326 | } 327 | 328 | pwd = getpwnam(user); 329 | if (pwd == NULL) { 330 | pam_log(LOG_ERR, "get user passwd failed, user %s, %s", user, strerror(errno)); 331 | return NULL; 332 | } 333 | 334 | if (getgrouplist(user, pwd->pw_gid, groups, &ngroups) < 0) { 335 | pam_log(LOG_ERR, "get group list for user %s failed, %s", user, strerror(errno)); 336 | return NULL; 337 | } 338 | 339 | for (i = 0; i < ngroups; i++) { 340 | gr = getgrgid(groups[i]); 341 | if (gr == NULL) { 342 | continue; 343 | } 344 | 345 | for (j = 0; j < user_jail_number; j++) { 346 | info = &user_jail[j]; 347 | if (info->name[0] != '@') { 348 | continue; 349 | } 350 | 351 | if (strncmp(gr->gr_name, info->name + 1, MAX_FIELD_LEN) != 0) { 352 | continue; 353 | } 354 | 355 | return info; 356 | } 357 | } 358 | 359 | return NULL; 360 | } 361 | 362 | int do_chroot(const char *path) 363 | { 364 | if (chroot(path) < 0) { 365 | pam_log(LOG_ERR, "chroot %s failed, %s", path, strerror(errno)); 366 | return -1; 367 | } 368 | 369 | if (chdir("/") != 0) { 370 | pam_log(LOG_WARNING, "chdir / failed, %s", path, strerror(errno)); 371 | } 372 | 373 | return 0; 374 | } 375 | 376 | int try_lock_pid(const char *pid_file, int no_close_exec) 377 | { 378 | int fd; 379 | int flags; 380 | 381 | /* create pid file, and lock this file */ 382 | fd = open(pid_file, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); 383 | if (fd == -1) { 384 | pam_log(LOG_ERR, "create pid file %s failed, %s", pid_file, strerror(errno)); 385 | return -1; 386 | } 387 | 388 | flags = fcntl(fd, F_GETFD); 389 | if (flags < 0) { 390 | pam_log(LOG_ERR, "get flags for pid file %s failed, %s", pid_file, strerror(errno)); 391 | goto errout; 392 | } 393 | 394 | if (no_close_exec == 0) { 395 | flags |= FD_CLOEXEC; 396 | if (fcntl(fd, F_SETFD, flags) == -1) { 397 | pam_log(LOG_ERR, "set flags for pid file %s failed, %s", pid_file, strerror(errno)); 398 | goto errout; 399 | } 400 | } 401 | 402 | if (lockf(fd, F_TLOCK, 0) < 0) { 403 | goto errout; 404 | } 405 | 406 | return fd; 407 | errout: 408 | if (fd > 0) { 409 | close(fd); 410 | } 411 | return -1; 412 | } 413 | 414 | int mount_from_cfg(struct user_jail_struct *info, const char *user) 415 | { 416 | char mount_cmd[PATH_MAX]; 417 | int ret; 418 | 419 | /* do bind directory for user */ 420 | snprintf(mount_cmd, PATH_MAX, "%s --user %s --mount %s", MOUNT_SCRIPT_PATH, user, info->jail); 421 | 422 | ret = system(mount_cmd); 423 | if (ret != 0) { 424 | pam_log(LOG_ERR, "run %s failed, ret = %d", mount_cmd, ret); 425 | return -1; 426 | } 427 | 428 | return 0; 429 | } 430 | 431 | int do_mount(struct user_jail_struct *info, const char *user, const char *root_path) 432 | { 433 | char proc_path[PATH_MAX * 2]; 434 | char pts_path[PATH_MAX]; 435 | char check_file[PATH_MAX * 2]; 436 | char mount_cmd[PATH_MAX * 4]; 437 | struct stat buf; 438 | uid_t ruid; 439 | uid_t euid; 440 | uid_t suid; 441 | int ret = 0; 442 | 443 | snprintf(proc_path, sizeof(proc_path) - 1 , "%s/proc", root_path); 444 | snprintf(pts_path, sizeof(pts_path) - 1 , "%s/dev/pts", root_path); 445 | snprintf(check_file, sizeof(check_file) - 1, "%s/ptmx", pts_path); 446 | /* if jail is ready mounted, return */ 447 | if (lstat(check_file, &buf) == 0) { 448 | return 0; 449 | } 450 | 451 | mkdir(proc_path, 0555); 452 | mkdir(pts_path, 0755); 453 | 454 | if (getresuid(&ruid, &euid, &suid) != 0) { 455 | pam_log(LOG_ERR, "get resuid failed, %s", strerror(errno)); 456 | return -1; 457 | } 458 | 459 | if (setresuid(0, 0, 0) != 0) { 460 | pam_log(LOG_ERR, "set resuid failed, %s", strerror(errno)); 461 | return -1; 462 | } 463 | #if 0 464 | mount("none", "/", NULL, MS_REC|MS_PRIVATE, NULL); 465 | mount("none", "/proc", NULL, MS_REC|MS_PRIVATE, NULL); 466 | #endif 467 | 468 | /* For selinux call shell command to mount directory */ 469 | /* mount API may fail, when selinux is enabled. */ 470 | ret = system("mount --make-rprivate /"); 471 | if (ret != 0) { 472 | pam_log(LOG_ERR, "mount --make-rprivate / failed, ret = %d", mount_cmd, ret); 473 | goto errout; 474 | } 475 | 476 | ret = system("mount --make-rprivate /proc"); 477 | if (ret != 0) { 478 | pam_log(LOG_ERR, "mount --make-rprivate /proc failed, ret = %d", mount_cmd, ret); 479 | goto errout; 480 | } 481 | 482 | if (mount_from_cfg(info, user) != 0) { 483 | goto errout; 484 | } 485 | 486 | snprintf(mount_cmd, sizeof(mount_cmd) - 1, "mount -t proc proc %s -o nosuid,noexec,nodev,ro", proc_path); 487 | ret = system(mount_cmd); 488 | if (ret != 0) { 489 | pam_log(LOG_ERR, "run %s failed, ret = %d", mount_cmd, ret); 490 | goto errout; 491 | } 492 | 493 | snprintf(mount_cmd, sizeof(mount_cmd) - 1, "mount -t devpts devpts %s -o nosuid,noexec", pts_path); 494 | ret = system(mount_cmd); 495 | if (ret != 0) { 496 | pam_log(LOG_ERR, "run %s failed, ret = %d", mount_cmd, ret); 497 | goto errout; 498 | } 499 | #if 0 500 | /* mount proc for jail */ 501 | if (mount("proc", proc_path, "proc", MS_RDONLY | MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) < 0) { 502 | goto errout; 503 | } 504 | 505 | if (mount("devpts", pts_path, "devpts", MS_NOSUID|MS_NOEXEC, NULL) < 0) { 506 | goto errout; 507 | } 508 | #endif 509 | if (setresuid(ruid, euid, suid) != 0) { 510 | pam_log(LOG_ERR, "set suid failed"); 511 | goto errout; 512 | } 513 | 514 | return 0; 515 | errout: 516 | if (setresuid(ruid, euid, suid) != 0) { 517 | 518 | } 519 | return -1; 520 | } 521 | 522 | void jail_init(struct user_jail_struct *info, char *user, char *pid_file, char *chroot_path) 523 | { 524 | int i = 0; 525 | int fd; 526 | char *argv[] = {"/usr/bin/init", "-u", user, "-l", 0, 0}; 527 | char str_fd[TMP_BUFF_LEN_32]; 528 | 529 | /* remount proc directory */ 530 | if (do_mount(info, user, chroot_path) != 0) { 531 | goto out; 532 | } 533 | 534 | /* start a init process for new namespace */ 535 | fd = open("/dev/null", O_RDWR); 536 | close(0); 537 | close(1); 538 | close(2); 539 | if (fd > 0 ) { 540 | dup2(fd, 0); 541 | dup2(fd, 1); 542 | dup2(fd, 2); 543 | close(fd); 544 | } 545 | 546 | for (i = 3; i < 1024; i++) { 547 | close(i); 548 | } 549 | 550 | /* create pid file and lock */ 551 | fd = try_lock_pid(pid_file, 1); 552 | if (fd < 0) { 553 | goto out; 554 | } 555 | 556 | if (do_chroot(chroot_path) < 0) { 557 | goto out; 558 | } 559 | 560 | if (drop_cap() != 0) { 561 | goto out; 562 | } 563 | 564 | snprintf(str_fd, TMP_BUFF_LEN_32, "%d", fd); 565 | argv[4] = str_fd; 566 | 567 | execv(argv[0], argv); 568 | pam_log(LOG_ERR, "execve %s failed, %s", argv[0], strerror(errno)); 569 | out: 570 | _exit(0); 571 | } 572 | 573 | int create_jail_ns(struct user_jail_struct *info, char *user, char *pid_file, char *chroot_path) 574 | { 575 | int pid; 576 | int fd = -1; 577 | char buff[TMP_BUFF_LEN_32]; 578 | 579 | if (setns) { 580 | if (info->namespace_flag == 0) { 581 | return do_mount(info, user, chroot_path); 582 | } 583 | if (unshare(info->namespace_flag) != 0) { 584 | pam_log(LOG_ERR, "unshare %d failed, user %s, %s", info->namespace_flag, user, strerror(errno)); 585 | return -1; 586 | } 587 | } else { 588 | /* NOT support */ 589 | return do_mount(info, user, chroot_path); 590 | } 591 | 592 | pid = fork(); 593 | if (pid < 0) { 594 | pam_log(LOG_ERR, "fork jail-init failed, %s", strerror(errno)); 595 | return -1; 596 | } else if (pid == 0) { 597 | /* start user's init process */ 598 | jail_init(info, user, pid_file, chroot_path); 599 | } 600 | 601 | fd = open(pid_file, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); 602 | if (fd < 0) { 603 | pam_log(LOG_ERR, "open pid file %s failed, %s", pid_file, strerror(errno)); 604 | goto errout; 605 | } 606 | 607 | if (ftruncate(fd, 0) != 0) { 608 | pam_log(LOG_WARNING, "truncate file %s failed, %s", pid_file, strerror(errno)); 609 | } 610 | 611 | /* write init pid to pid file */ 612 | snprintf(buff, TMP_BUFF_LEN_32, "%d\n", pid); 613 | if (write(fd, buff, strnlen(buff, TMP_BUFF_LEN_32)) < 0) { 614 | pam_log(LOG_ERR, "write pid to file failed, %s", strerror(errno)); 615 | goto errout; 616 | } 617 | 618 | close(fd); 619 | return 0; 620 | 621 | errout: 622 | if (pid > 0) { 623 | /* kill namespace init process */ 624 | kill(pid, SIGKILL); 625 | } 626 | 627 | if (fd > 0) { 628 | close(fd); 629 | } 630 | return -1; 631 | } 632 | 633 | int enter_ns(int pid, char *ns_name, int flag) 634 | { 635 | #ifdef __NR_setns 636 | char ns_file[MAX_LINE_LEN]; 637 | int fd = 0; 638 | snprintf(ns_file, MAX_LINE_LEN, "/proc/%d/ns/%s", pid, ns_name); 639 | 640 | fd = open(ns_file, O_RDONLY); 641 | if (fd < 0) { 642 | pam_log(LOG_ERR, "open ns file %s failed, %s", ns_file, strerror(errno)); 643 | return -1; 644 | } 645 | 646 | if (setns(fd, flag) != 0) { 647 | pam_log(LOG_ERR, "set ns %s failed, %s", ns_file, strerror(errno)); 648 | close(fd); 649 | return -1; 650 | } 651 | 652 | close(fd); 653 | 654 | return 0; 655 | #else 656 | return -1; 657 | #endif 658 | } 659 | 660 | int enter_jail_ns(struct user_jail_struct *info, const char *user, char *pid_file, const char *chroot_path) 661 | { 662 | char buff[TMP_BUFF_LEN_32]; 663 | int fd = -1; 664 | int ret = 1; 665 | int ns_pid = 0; 666 | 667 | /* read pid from pid file */ 668 | fd = open(pid_file, O_RDONLY); 669 | if (fd < 0) { 670 | if (errno == ENOENT) { 671 | return 0; 672 | } 673 | pam_log(LOG_ERR, "open pid file %s failed, %s", pid_file, strerror(errno)); 674 | return -1; 675 | } 676 | 677 | /* read jail init process pid */ 678 | if (read(fd, buff, sizeof(buff)) < 0) { 679 | ret = -1; 680 | pam_log(LOG_ERR, "read pid file %s failed, %s", pid_file, strerror(errno)); 681 | goto out; 682 | } 683 | 684 | ns_pid = atoi(buff); 685 | if (ns_pid <= 0) { 686 | ret = -1; 687 | pam_log(LOG_ERR, "get pid %s from %s failed.", buff, pid_file); 688 | goto out; 689 | } 690 | 691 | #ifdef __NR_setns 692 | /* enter ipc namespace */ 693 | if (enter_ns(ns_pid, "ipc", CLONE_NEWIPC) != 0) { 694 | ret = 1; 695 | goto out; 696 | } 697 | 698 | /* enter net namespace */ 699 | if (enter_ns(ns_pid, "net", CLONE_NEWNET) != 0) { 700 | ret = 1; 701 | goto out; 702 | } 703 | 704 | /* enter mnt namespace */ 705 | if (enter_ns(ns_pid, "mnt", CLONE_NEWNS) != 0) { 706 | ret = 1; 707 | goto out; 708 | } 709 | 710 | /* enter pid namespace */ 711 | if (enter_ns(ns_pid, "pid", CLONE_NEWPID) != 0) { 712 | ret = 1; 713 | goto out; 714 | } 715 | 716 | /* enter user namespace */ 717 | /* TODO 718 | * currently return EINVAL 719 | if (enter_ns(ns_pid, "user", CLONE_NEWUSER) != 0) { 720 | ret = 1; 721 | goto out; 722 | } 723 | */ 724 | 725 | /* enter uts namespace */ 726 | if (enter_ns(ns_pid, "uts", CLONE_NEWUTS) != 0) { 727 | ret = 1; 728 | goto out; 729 | } 730 | 731 | ret = 0; 732 | #else 733 | ret = -1; 734 | #endif 735 | out: 736 | if (fd > 0) { 737 | close(fd); 738 | } 739 | return ret; 740 | } 741 | 742 | int set_jsid_env(pam_handle_t *pamh, struct user_jail_struct *info, const char *user) 743 | { 744 | unsigned long rnd_num = -1; 745 | char jsid_env[PATH_MAX * 2]; 746 | char buff[MAX_LINE_LEN]; 747 | char jsid_file_path[PATH_MAX]; 748 | char pid_file[MAX_LINE_LEN]; 749 | int fd = -1; 750 | int jsid_fd = -1; 751 | int create_jid = 0; 752 | int ret; 753 | 754 | mkdir(JAIL_VAR_DIR, 0755); 755 | mkdir(JAIL_VAR_JSID_DIR, 0700); 756 | 757 | snprintf(pid_file, MAX_LINE_LEN, NS_PID_FILE_PATH, user); 758 | snprintf(jsid_file_path, PATH_MAX, JAIL_JSID_FILE, user); 759 | 760 | fd = try_lock_pid(pid_file, 0); 761 | jsid_fd = open(jsid_file_path, O_RDONLY); 762 | if (jsid_fd < 0) { 763 | /* if jsid file doesn't exist, just create jsid file. */ 764 | create_jid = 1; 765 | } else { 766 | if (fd > 0 && lseek(fd, 0, SEEK_END) > 0) { 767 | /* if jsid file exists, and init process is not running, create a new JSID */ 768 | create_jid = 1; 769 | close(jsid_fd); 770 | jsid_fd = -1; 771 | } else { 772 | /* if jsid file exist, and pid file is empty, just read JSID in jsid file 773 | * or user's init process is running, just read JSID in jsid file*/ 774 | create_jid = 0; 775 | } 776 | } 777 | 778 | if (create_jid) { 779 | jsid_fd = open(jsid_file_path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); 780 | if (jsid_fd < 0) { 781 | ret = -1; 782 | pam_log(LOG_ERR, "open jsid file %s failed, %s", jsid_file_path, strerror(errno)); 783 | goto out; 784 | } 785 | 786 | rnd_num = get_rnd_number(); 787 | if (rnd_num == -1) { 788 | ret = -1; 789 | goto out; 790 | } 791 | 792 | if (ftruncate(jsid_fd, 0) != 0) { 793 | pam_log(LOG_WARNING, "truncate file %s failed, %s", jsid_file_path, strerror(errno)); 794 | } 795 | 796 | /* write JSID number */ 797 | snprintf(buff, MAX_LINE_LEN, "%lu\n", rnd_num); 798 | if (write(jsid_fd, buff, strnlen(buff, MAX_LINE_LEN)) < 0) { 799 | ret = -1; 800 | pam_log(LOG_ERR, "write jsid failed, file %s, %s", jsid_file_path, strerror(errno)); 801 | goto out; 802 | } 803 | 804 | /* write user name */ 805 | snprintf(buff, MAX_LINE_LEN, "%s\n", user); 806 | if (write(jsid_fd, buff, strnlen(buff, MAX_LINE_LEN)) < 0) { 807 | ret = -1; 808 | pam_log(LOG_ERR, "write user name failed, file %s, %s", jsid_file_path, strerror(errno)); 809 | goto out; 810 | } 811 | 812 | /* write jail name */ 813 | snprintf(buff, MAX_LINE_LEN, "%s\n", info->jail); 814 | if (write(jsid_fd, buff, strnlen(buff, MAX_LINE_LEN)) < 0) { 815 | ret = -1; 816 | pam_log(LOG_ERR, "write jail name failed, file %s, %s", jsid_file_path, strerror(errno)); 817 | goto out; 818 | } 819 | 820 | snprintf(jsid_env, TMP_BUFF_LEN_32, "%s=%lu", JAIL_KEY, rnd_num); 821 | } else { 822 | char *line_end; 823 | int len = read(jsid_fd, buff, MAX_LINE_LEN - 1); 824 | if ( len < 0) { 825 | ret = -1; 826 | pam_log(LOG_ERR, "read jsid failed, file %s, %s", jsid_file_path, strerror(errno)); 827 | goto out; 828 | } 829 | buff[len] = 0; 830 | 831 | line_end = index(buff, '\n'); 832 | if (line_end) { 833 | buff[line_end - buff] = 0; 834 | } 835 | 836 | snprintf(jsid_env, sizeof(jsid_env) - 1, "%s=%s", JAIL_KEY, buff); 837 | } 838 | 839 | /* set JSID enviroment to shell */ 840 | if (pam_putenv(pamh, jsid_env) != PAM_SUCCESS) { 841 | pam_log(LOG_ERR, "put env failed, user %s, %s", user, strerror(errno)); 842 | return PAM_SERVICE_ERR; 843 | } 844 | 845 | ret = 0; 846 | out: 847 | if (jsid_fd> 0) { 848 | close(jsid_fd); 849 | } 850 | 851 | if (fd > 0) { 852 | close(fd); 853 | } 854 | 855 | return ret; 856 | } 857 | 858 | int wait_proc_mounted(const char *root_path) 859 | { 860 | char proc_uname_path[PATH_MAX]; 861 | int count = 0; 862 | 863 | snprintf(proc_uname_path, PATH_MAX, "%s/proc/uptime", root_path); 864 | 865 | /* wait for directory mount */ 866 | while (access(proc_uname_path, F_OK) < 0) { 867 | usleep(100000); 868 | count++; 869 | /* wait for 5 seconds */ 870 | if (count > 10 * 5) { 871 | pam_log(LOG_ERR, "wait for %s mount time out.", proc_uname_path); 872 | return -1; 873 | } 874 | } 875 | 876 | return 0; 877 | } 878 | 879 | int unshare_pid(struct user_jail_struct *info, char *user, char *chroot_path) 880 | { 881 | char pid_file[MAX_LINE_LEN]; 882 | int fd; 883 | 884 | snprintf(pid_file, MAX_LINE_LEN, NS_PID_FILE_PATH, user); 885 | fd = try_lock_pid(pid_file, 0); 886 | if ( fd > 0) { 887 | close(fd); 888 | /* if no user's namespace init process running 889 | * create new namepace, and start a init process for user. 890 | */ 891 | if (create_jail_ns(info, user, pid_file, chroot_path) != 0) { 892 | return -1; 893 | } 894 | } else { 895 | /* 896 | * if user's namepsace init process running 897 | * just enter the namepsace of init process 898 | */ 899 | if (enter_jail_ns(info, user, pid_file, chroot_path) != 0) { 900 | return -1; 901 | } 902 | } 903 | 904 | /* from mount success */ 905 | if (wait_proc_mounted(chroot_path) != 0) { 906 | return -1; 907 | } 908 | 909 | return 0; 910 | } 911 | 912 | int run_jail_post_script(const char *user, struct user_jail_struct *info) 913 | { 914 | int ret; 915 | char post_cmd[PATH_MAX * 2]; 916 | uid_t ruid; 917 | uid_t euid; 918 | uid_t suid; 919 | 920 | if (access(LOGIN_POST_SCRIPT, X_OK) != 0) { 921 | return 0; 922 | } 923 | 924 | if (getresuid(&ruid, &euid, &suid) != 0) { 925 | pam_log(LOG_ERR, "get resuid for %s failed, %s", user, strerror(errno)); 926 | return -1; 927 | } 928 | 929 | if (setresuid(0, 0, 0) != 0) { 930 | pam_log(LOG_WARNING, "set resuid for %s failed, %s", user, strerror(errno)); 931 | } 932 | 933 | /* LOGIN_POST_SCRIPT %user% %jail_root_path%*/ 934 | snprintf(post_cmd, sizeof(post_cmd) - 1, "%s %s %s/%s", LOGIN_POST_SCRIPT, user, jail_home, info->jail); 935 | ret = system(post_cmd); 936 | 937 | if (setresuid(ruid, euid, suid) != 0) { 938 | pam_log(LOG_WARNING, "set resuid for %s failed, %s", user, strerror(errno)); 939 | } 940 | 941 | if (ret != 0) { 942 | pam_log(LOG_ERR, "run %s failed, ret %d", post_cmd, ret); 943 | return -1; 944 | } 945 | 946 | return 0; 947 | 948 | } 949 | 950 | int start_jail(pam_handle_t *pamh, int flags, int argc, const char *argv[]) 951 | { 952 | struct user_jail_struct *info; 953 | char jail_path[MAX_LINE_LEN * 2]; 954 | char user[MAX_LINE_LEN]; 955 | const char *user_pam; 956 | 957 | /* get username from pam */ 958 | if (pam_get_user(pamh, &user_pam, NULL) != PAM_SUCCESS) { 959 | pam_log(LOG_ERR, "pam get user failed."); 960 | return PAM_USER_UNKNOWN; 961 | } 962 | strncpy(user, user_pam, MAX_LINE_LEN - 1); 963 | 964 | /* load configuration from jail-shell.conf */ 965 | if (load_config()) { 966 | return PAM_SUCCESS; 967 | } 968 | 969 | /* get jail info from user name */ 970 | info = get_user_jail(pamh); 971 | if (info == NULL) { 972 | return PAM_SUCCESS; 973 | } 974 | 975 | /* set JSID enviroment with a random number 976 | * if JSID exists, read from existrs and set. 977 | */ 978 | if (set_jsid_env(pamh, info, user) != 0) { 979 | pam_log(LOG_INFO, "set env failed, user %s.", user); 980 | return PAM_SERVICE_ERR; 981 | } 982 | 983 | /* 984 | * run jail-shell-post script, in order to add user entry in /etc/passwd 985 | */ 986 | if (run_jail_post_script(user, info) != 0) { 987 | pam_log(LOG_ERR, "run jail post script failed, user %s.", user); 988 | return PAM_SERVICE_ERR; 989 | } 990 | 991 | snprintf(jail_path, sizeof(jail_path) - 1, "%s/%s", jail_home, info->jail); 992 | if (unshare_pid(info, user, jail_path) != 0) { 993 | pam_log(LOG_ERR, "unshared namespace failed, user %s.", user); 994 | return PAM_SERVICE_ERR; 995 | } 996 | 997 | /* chroot to jail directory */ 998 | if (do_chroot(jail_path) < 0) { 999 | pam_log(LOG_ERR, "chroot failed. user %s", user); 1000 | return PAM_SERVICE_ERR; 1001 | } 1002 | 1003 | /* Drop all unnesseary cap */ 1004 | if (drop_cap() != 0) { 1005 | pam_log(LOG_ERR, "drop cap failed, user %s.", user); 1006 | return PAM_SERVICE_ERR; 1007 | } 1008 | 1009 | return PAM_SUCCESS; 1010 | } 1011 | 1012 | PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char *argv[]) 1013 | { 1014 | return PAM_SUCCESS; 1015 | } 1016 | 1017 | PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char *argv[]) 1018 | { 1019 | 1020 | return PAM_SUCCESS; 1021 | } 1022 | 1023 | PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char *argv[]) 1024 | { 1025 | return PAM_SUCCESS; 1026 | } 1027 | 1028 | PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char *argv[]) 1029 | { 1030 | jail_shell_pamh = pamh; 1031 | if (start_jail(pamh, flags, argc, argv) != PAM_SUCCESS) { 1032 | return PAM_USER_UNKNOWN; 1033 | } 1034 | 1035 | return PAM_SUCCESS; 1036 | } 1037 | 1038 | PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char *argv[]) 1039 | { 1040 | return PAM_SUCCESS; 1041 | } 1042 | 1043 | PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char *argv[]) 1044 | { 1045 | return PAM_SERVICE_ERR; 1046 | } 1047 | 1048 | #ifdef PAM_MODULE_ENTRY 1049 | PAM_MODULE_ENTRY("pam_jail_shell"); 1050 | #endif 1051 | -------------------------------------------------------------------------------- /jail-cmd/jail-cmdd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Ruilin Peng (Nick) 3 | */ 4 | 5 | #include "jail-cmd.h" 6 | #include 7 | #include 8 | #include 9 | 10 | #define PID_FILE_PATH "/var/run/jail-cmdd.pid" 11 | #define COMMAND_ROOT_PATH "/var/local/jail-shell/command" 12 | #define MAX_GROUP_NUM 64 13 | #define JAIL_CMDD_CONF_FILE "/etc/jail-shell/cmdd_config" 14 | 15 | 16 | struct cmdd_context { 17 | int sock; 18 | int mirror; 19 | int mirror_err; 20 | int maxfd; 21 | 22 | fd_set rfds; 23 | fd_set wfds; 24 | 25 | int isatty; 26 | 27 | int is_sock_eof; 28 | int is_mirror_eof; 29 | int is_mirror_err_eof; 30 | 31 | int child_pid; 32 | 33 | struct sock_data send_data; 34 | struct sock_data recv_data; 35 | struct sock_data mirror_data; 36 | }; 37 | 38 | 39 | struct cmdd_config { 40 | int port; 41 | int audit; 42 | int audit_args; 43 | }; 44 | 45 | int pid_file_fd; 46 | 47 | struct cmdd_config config = { 48 | .port = DEFAULT_PORT, 49 | .audit = 0, 50 | .audit_args = 0, 51 | }; 52 | 53 | void help(void) 54 | { 55 | char *help = "" 56 | "Usage: jail-cmdd [OPTION]...\n" 57 | "Start jail cmd proxy server.\n" 58 | " -f run forground.\n" 59 | " -h show this help message.\n" 60 | "\n" 61 | ; 62 | printf("%s", help); 63 | } 64 | 65 | int create_pid_file(const char *pid_file) 66 | { 67 | int fd; 68 | int flags; 69 | char buff[TMP_BUFF_LEN_32]; 70 | 71 | /* create pid file, and lock this file */ 72 | fd = open(pid_file, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); 73 | if (fd == -1) { 74 | fprintf(stderr, "create pid file failed, %s", strerror(errno)); 75 | return -1; 76 | } 77 | 78 | flags = fcntl(fd, F_GETFD); 79 | if (flags < 0) { 80 | fprintf(stderr, "Could not get flags for PID file %s", pid_file); 81 | goto errout; 82 | } 83 | 84 | flags |= FD_CLOEXEC; 85 | if (fcntl(fd, F_SETFD, flags) == -1) { 86 | fprintf(stderr, "Could not set flags for PID file %s", pid_file); 87 | goto errout; 88 | } 89 | 90 | if (lockf(fd, F_TLOCK, 0) < 0) { 91 | fprintf(stderr, "Server is already running.\n"); 92 | goto errout; 93 | } 94 | 95 | snprintf(buff, TMP_BUFF_LEN_32, "%d\n", getpid()); 96 | 97 | if (write(fd, buff, strnlen(buff, TMP_BUFF_LEN_32)) < 0) { 98 | fprintf(stderr, "write pid to file failed, %s.\n", strerror(errno)); 99 | goto errout; 100 | } 101 | 102 | return fd; 103 | errout: 104 | if (fd > 0) { 105 | close(fd); 106 | } 107 | return -1; 108 | } 109 | 110 | /* fork process and create socketpair to stdin, stdout, stderr for data writing and reading */ 111 | int forksocket(int *mirror, int *mirror_err) 112 | { 113 | int fd[2]; 114 | int fd_err[2]; 115 | int pid; 116 | 117 | static const int parentsocket = 0; 118 | static const int childsocket = 1; 119 | 120 | /* create socketpair for stdin, stdout, stderr */ 121 | if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fd) != 0) { 122 | return -1; 123 | } 124 | 125 | if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fd_err) != 0) { 126 | return -1; 127 | } 128 | 129 | pid = fork(); 130 | if (pid == 0) { 131 | close(fd[parentsocket]); 132 | close(fd_err[parentsocket]); 133 | 134 | /* close original std fd */ 135 | close(0); 136 | close(1); 137 | close(2); 138 | 139 | /* duplicate socket to std fd */ 140 | dup2(fd[childsocket], 0); 141 | dup2(fd[childsocket], 1); 142 | dup2(fd_err[childsocket], 2); 143 | 144 | *mirror = fd[childsocket]; 145 | *mirror_err = fd_err[childsocket]; 146 | 147 | return pid; 148 | } else if (pid > 0) { 149 | close(fd[childsocket]); 150 | close(fd_err[childsocket]); 151 | *mirror = fd[parentsocket]; 152 | *mirror_err = fd_err[parentsocket]; 153 | } 154 | 155 | return pid; 156 | } 157 | 158 | int set_uid_gid(int uid, int gid) 159 | { 160 | struct passwd *pwd; 161 | 162 | pwd = getpwuid(uid); 163 | if (pwd == NULL) { 164 | fprintf(stderr, "User is invalid.\n"); 165 | goto errout; 166 | } 167 | 168 | setenv("LOGNAME", pwd->pw_name, 1); 169 | setenv("USER", pwd->pw_name, 1); 170 | setenv("USERNAME", pwd->pw_name, 1); 171 | setenv("HOME", pwd->pw_dir, 1); 172 | setenv("SHELL", pwd->pw_shell, 1); 173 | 174 | if (setresgid(gid, gid, gid) < 0) { 175 | goto errout; 176 | } 177 | 178 | if (initgroups(pwd->pw_name, gid) < 0) { 179 | goto errout; 180 | } 181 | 182 | if (setresuid(uid, uid, uid) < 0) { 183 | goto errout; 184 | } 185 | 186 | return 0; 187 | errout: 188 | return -1; 189 | 190 | } 191 | 192 | int get_command_user_group(char *jail_name, char *command, char *user, int user_maxlen, char *group, int group_maxlen) 193 | { 194 | char command_file_path[PATH_MAX]; 195 | char buff[MAX_LINE_LEN]; 196 | char filed1[MAX_LINE_LEN]; 197 | char filed2[MAX_LINE_LEN]; 198 | char filed3[MAX_LINE_LEN]; 199 | int filedNum = 0; 200 | 201 | FILE *fp = NULL; 202 | snprintf(command_file_path, PATH_MAX, "%s/%s/%s", COMMAND_ROOT_PATH, jail_name, COMMAND_LIST_FILE); 203 | 204 | fp = fopen(command_file_path, "r"); 205 | if (fp == NULL) { 206 | fprintf(stderr, "open %s failed, %s\n", command_file_path, strerror(errno)); 207 | goto errout; 208 | } 209 | 210 | while (fgets(buff, MAX_LINE_LEN, fp)) { 211 | filedNum = sscanf(buff, "%1024s %1024s %1024s", filed1, filed2, filed3); 212 | if (filedNum < 0) { 213 | continue; 214 | } 215 | 216 | /* comment line */ 217 | if (filed1[0] == '#') { 218 | continue; 219 | } 220 | 221 | if (filedNum != 3) { 222 | continue; 223 | } 224 | 225 | /* found command */ 226 | if (strncmp(basename(command), filed1, MAX_LINE_LEN) != 0) { 227 | continue; 228 | } 229 | 230 | /* copy user and group */ 231 | strncpy(user, filed2, user_maxlen); 232 | strncpy(group, filed3, group_maxlen); 233 | break; 234 | } 235 | 236 | fclose(fp); 237 | return 0; 238 | errout: 239 | if (fp) { 240 | fclose(fp); 241 | } 242 | return -1; 243 | } 244 | 245 | int injection_check(int argc, char *argv[]) 246 | { 247 | char *inject_char[] = {";", "|", "`", "$(", "&&", "||", ">", "<"}; 248 | int char_count = sizeof(inject_char) / sizeof(char*); 249 | int i = 0; 250 | int j = 0; 251 | for (i = 0; i < argc; i++) { 252 | for (j = 0; j < char_count; j++) { 253 | if (strstr(argv[i], inject_char[j])) { 254 | return -1; 255 | } 256 | } 257 | } 258 | 259 | return 0; 260 | } 261 | 262 | int change_uid_gid_by_command(struct jail_cmd_cmd *cmd_cmd, char *jail_name, char *command) 263 | { 264 | char username[TMP_BUFF_LEN_128]={0}; 265 | char groupname[TMP_BUFF_LEN_128]={0}; 266 | int uid = cmd_cmd->uid; 267 | int gid = cmd_cmd->gid; 268 | 269 | if (get_command_user_group(jail_name, command, username, sizeof(username), groupname, sizeof(groupname)) == 0) { 270 | /* if user or group is '-' then use uid and gid from jail-cmd */ 271 | if (strncmp(username, "-", sizeof(username)) == 0 || strncmp(groupname, "-", sizeof(groupname)) == 0) { 272 | /* if uid and gid is 0, return no permission */ 273 | if (uid == 0 || gid == 0) { 274 | errno = EPERM; 275 | return -1; 276 | } 277 | } else { 278 | struct passwd *pwd ; 279 | struct group *grp; 280 | gid_t groups[MAX_GROUP_NUM]; 281 | int ngroups = MAX_GROUP_NUM; 282 | int i = 0; 283 | 284 | pwd = getpwnam(username); 285 | if (pwd == NULL) { 286 | errno = EINVAL; 287 | return -1; 288 | } 289 | 290 | if (getgrouplist(username, pwd->pw_gid, groups, &ngroups) < 0) { 291 | return -1; 292 | } 293 | 294 | grp = getgrnam(groupname); 295 | if (grp == NULL) { 296 | errno = EINVAL; 297 | return -1; 298 | } 299 | 300 | /* check if gid is in user's group list. */ 301 | for (i = 0; i < ngroups; i++) { 302 | if (grp->gr_gid == groups[i]) { 303 | break; 304 | } 305 | } 306 | 307 | /* if gid is not in user's group list, return invalid. */ 308 | if (i >= ngroups) { 309 | errno = EINVAL; 310 | return -1; 311 | } 312 | 313 | uid = pwd->pw_uid; 314 | gid = grp->gr_gid; 315 | } 316 | } 317 | 318 | /* start change process uid, gid */ 319 | if (set_uid_gid(uid, gid) != 0) { 320 | return -1; 321 | } 322 | 323 | return 0; 324 | } 325 | 326 | void run_process(struct jail_cmd_cmd *cmd_cmd, char *jail_name) 327 | { 328 | char cmd_name[PATH_MAX]; 329 | char cmd_path[PATH_MAX * 2]; 330 | char prog[PATH_MAX]; 331 | int len = 0; 332 | int i = 0; 333 | int argc = cmd_cmd->argc; 334 | char *argv[argc + 1]; 335 | 336 | /* get args to standard parameter: argc, argv[] */ 337 | for (i = 0; i < cmd_cmd->argc; i++) { 338 | argv[i] = cmd_cmd->argvs + len; 339 | len += strlen(cmd_cmd->argvs + len) + 1; 340 | } 341 | argv[i] = 0; 342 | 343 | /* check shell inject characters */ 344 | if (injection_check(argc, argv) != 0) { 345 | errno = EINVAL; 346 | goto errout; 347 | } 348 | 349 | snprintf(cmd_name, sizeof(cmd_name) - 1, "/%s", argv[0]); 350 | if (normalize_path(cmd_name) <= 0) { 351 | goto errout; 352 | } 353 | 354 | snprintf(cmd_path, sizeof(cmd_path) - 1, "%s/%s%s", COMMAND_ROOT_PATH, jail_name, cmd_name); 355 | 356 | len = readlink(cmd_path, prog, sizeof(prog)); 357 | if (len < 0) { 358 | goto errout; 359 | } 360 | prog[len] = 0; 361 | 362 | /* change user id */ 363 | if (change_uid_gid_by_command(cmd_cmd, jail_name, cmd_name) != 0) { 364 | goto errout; 365 | } 366 | 367 | if (chdir("/tmp") < 0) { 368 | goto errout; 369 | } 370 | 371 | execv(prog, argv); 372 | 373 | errout: 374 | fprintf(stderr, "-sh: %s: %s\n", argv[0], strerror(errno)); 375 | } 376 | 377 | void audit_log(struct jail_cmd_cmd *cmd_cmd) 378 | { 379 | if (config.audit == 0) { 380 | return ; 381 | } 382 | 383 | openlog("jail-shell", LOG_PID, 0); 384 | 385 | if (config.audit_args) { 386 | int i = 0; 387 | int len = 0; 388 | char argvs[MAX_LINE_LEN]; 389 | char *argv = NULL; 390 | int argvs_len = 0; 391 | 392 | for (i = 0; i < cmd_cmd->argc; i++) { 393 | argv = cmd_cmd->argvs + len; 394 | len += strlen(cmd_cmd->argvs + len) + 1; 395 | strncpy(argvs + argvs_len, argv, sizeof(argvs) - argvs_len - 1); 396 | argvs_len = len; 397 | argvs[argvs_len - 1] = ' '; 398 | if (argvs_len >= sizeof(argvs)) { 399 | argvs_len = sizeof(argvs); 400 | break; 401 | } 402 | } 403 | 404 | argvs[argvs_len - 1] = 0; 405 | syslog(LOG_NOTICE, "UID:%d GID:%d CMD:%s", cmd_cmd->uid, cmd_cmd->gid, argvs); 406 | } else { 407 | syslog(LOG_NOTICE, "UID:%d GID:%d CMD:%s", cmd_cmd->uid, cmd_cmd->gid, cmd_cmd->argvs); 408 | } 409 | closelog(); 410 | 411 | } 412 | 413 | int get_jail_name(struct jail_cmd_cmd *cmd_cmd, char *out, int out_max_len) 414 | { 415 | struct passwd *pwd; 416 | char jsid_file_path[PATH_MAX]; 417 | char buff[MAX_LINE_LEN]; 418 | FILE *fp = NULL; 419 | int len; 420 | 421 | pwd = getpwuid(cmd_cmd->uid); 422 | if (pwd == NULL) { 423 | fprintf(stderr, "User is invalid.\n"); 424 | goto errout; 425 | } 426 | 427 | snprintf(jsid_file_path, PATH_MAX, JAIL_JSID_FILE, pwd->pw_name); 428 | fp = fopen(jsid_file_path, "r"); 429 | if (fp == NULL) { 430 | fprintf(stderr, "open %s failed, %s\n", jsid_file_path, strerror(errno)); 431 | goto errout; 432 | } 433 | 434 | /* read JSID */ 435 | if (fgets(buff, sizeof(buff) - 1, fp) == NULL) { 436 | fprintf(stderr, "read gsid failed, %s\n", strerror(errno)); 437 | goto errout; 438 | } 439 | len = strnlen(buff, sizeof(buff) - 1); 440 | if (buff[len - 1] == '\n') { 441 | buff[len - 1] = '\0'; 442 | } 443 | 444 | /* check JSID */ 445 | if (strncmp(buff, cmd_cmd->jsid, TMP_BUFF_LEN_32) != 0) { 446 | fprintf(stderr, "gsid not match, %s:%s\n", buff, cmd_cmd->jsid); 447 | goto errout; 448 | } 449 | 450 | /* read user name */ 451 | if (fgets(buff, sizeof(buff) - 1, fp) == NULL) { 452 | fprintf(stderr, "read gsid failed, %s\n", strerror(errno)); 453 | goto errout; 454 | } 455 | len = strnlen(buff, sizeof(buff) - 1); 456 | if (buff[len - 1] == '\n') { 457 | buff[len - 1] = '\0'; 458 | } 459 | 460 | /* check user name */ 461 | if (strncmp(buff, pwd->pw_name, MAX_LINE_LEN) != 0) { 462 | fprintf(stderr, "user name not match, %s:%s\n", buff, pwd->pw_name); 463 | goto errout; 464 | } 465 | 466 | /* read jail name */ 467 | if (fgets(out, out_max_len - 1, fp) == NULL) { 468 | fprintf(stderr, "read gsid failed, %s\n", strerror(errno)); 469 | goto errout; 470 | } 471 | len = strnlen(out, out_max_len - 1); 472 | if (out[len - 1] == '\n') { 473 | out[len - 1] = '\0'; 474 | } 475 | 476 | if (strlen(out) <= 0) { 477 | fprintf(stderr, "jail name is invalid, %s", out); 478 | goto errout; 479 | } 480 | 481 | fclose(fp); 482 | return 0; 483 | errout: 484 | if (fp) { 485 | fclose(fp); 486 | } 487 | return -1; 488 | } 489 | 490 | int start_process(struct jail_cmd_cmd *cmd_cmd, int *mirror, int *mirror_err) 491 | { 492 | int pid = -1; 493 | char jail_name[MAX_LINE_LEN]={0}; 494 | 495 | audit_log(cmd_cmd); 496 | 497 | if (get_jail_name(cmd_cmd, jail_name, sizeof(jail_name)) != 0) { 498 | return -1; 499 | } 500 | 501 | if (cmd_cmd->isatty) { 502 | /* if command comes from interactive shell, start a pty term and fork process */ 503 | pid = forkpty(mirror, NULL, NULL, &cmd_cmd->ws); 504 | } else { 505 | /* if command comes from non-interactive shell, make socketpair and fork process */ 506 | pid = forksocket(mirror, mirror_err); 507 | } 508 | 509 | if (pid < 0) { 510 | /* fork failed */ 511 | return -1; 512 | } else if (pid == 0) { 513 | close(*mirror); 514 | if (*mirror_err > 0) { 515 | close(*mirror_err); 516 | } 517 | setenv("TERM", cmd_cmd->term, 1); 518 | 519 | run_process(cmd_cmd, jail_name); 520 | _exit(1); 521 | } 522 | 523 | return pid; 524 | } 525 | 526 | int check_args(struct cmdd_context *context, struct jail_cmd_head *cmd_head, struct jail_cmd_cmd *cmd_cmd) 527 | { 528 | int arg_len; 529 | int argc = cmd_cmd->argc; 530 | int arg_count = 0; 531 | int i; 532 | 533 | if (argc > MAX_ARGS_COUNT) { 534 | fprintf(stderr, "too many args\n"); 535 | return -1; 536 | } 537 | 538 | if (cmd_head->data_len > sizeof(context->recv_data.data) - sizeof(*cmd_head)) { 539 | fprintf(stderr, "cmd length is invalid.\n"); 540 | return -1; 541 | } 542 | 543 | /* check arg number is valid. */ 544 | arg_len = cmd_head->data_len - sizeof(*cmd_cmd); 545 | for (i = 0; i < arg_len; i++) { 546 | if (cmd_cmd->argvs[i] == 0) { 547 | arg_count++; 548 | } 549 | } 550 | 551 | if (argc != arg_count) { 552 | fprintf(stderr, "arg number is invalid.\n"); 553 | return -1; 554 | } 555 | 556 | return 0; 557 | } 558 | 559 | CMD_RETURN process_cmd(struct cmdd_context *context, struct jail_cmd_head *cmd_head) 560 | { 561 | switch (cmd_head->type) { 562 | case CMD_MSG_CMD: { 563 | /* init cmd message */ 564 | struct jail_cmd_cmd *cmd_cmd = (struct jail_cmd_cmd *)cmd_head->data; 565 | 566 | if (check_args(context, cmd_head, cmd_cmd) != 0) { 567 | FD_CLR(context->sock, &context->rfds); 568 | return CMD_RETURN_ERR; 569 | } 570 | 571 | context->child_pid = start_process(cmd_cmd, &context->mirror, &context->mirror_err); 572 | if (context->child_pid < 0) { 573 | FD_CLR(context->sock, &context->rfds); 574 | return CMD_RETURN_ERR; 575 | } 576 | 577 | context->isatty = cmd_cmd->isatty; 578 | 579 | FD_SET(context->mirror, &context->rfds); 580 | if (context->mirror_err > 0) { 581 | FD_SET(context->mirror_err, &context->rfds); 582 | } 583 | 584 | context->maxfd = max(context->maxfd, context->mirror); 585 | context->maxfd = max(context->maxfd, context->mirror_err); 586 | break; } 587 | case CMD_MSG_DATA_IN: { 588 | /* input message */ 589 | struct jail_cmd_data *cmd_data = (struct jail_cmd_data *)cmd_head->data; 590 | if (context->mirror < 0) { 591 | break; 592 | } 593 | 594 | /* if mirror_data is full, stop write to mirror, and stop read data from client socket */ 595 | int mirror_free = sizeof(context->mirror_data.data) - context->mirror_data.total_len; 596 | if (mirror_free < cmd_head->data_len) { 597 | FD_CLR(context->sock, &context->rfds); 598 | return CMD_RETURN_CONT; 599 | } 600 | 601 | /* copy read data to mirror_data, and start mirror write event. */ 602 | memcpy(context->mirror_data.data + context->mirror_data.total_len, cmd_data->data, cmd_head->data_len); 603 | context->mirror_data.total_len += cmd_head->data_len; 604 | FD_SET(context->mirror, &context->wfds); 605 | break; } 606 | case CMD_MSG_DATA_EXIT: { 607 | /* exit message */ 608 | FD_CLR(context->mirror, &context->rfds); 609 | close(context->mirror); 610 | context->mirror = -1; 611 | return CMD_RETURN_EXIT; 612 | break; } 613 | case CMD_MSG_WINSIZE: { 614 | /* win size change message */ 615 | struct jail_cmd_winsize *cmd_winsize = (struct jail_cmd_winsize *)cmd_head->data; 616 | ioctl(context->mirror, TIOCSWINSZ, &cmd_winsize->ws); 617 | break; } 618 | default: 619 | fprintf(stderr, "data type error.\r\n"); 620 | return CMD_RETURN_EXIT; 621 | } 622 | 623 | return CMD_RETURN_OK; 624 | } 625 | 626 | CMD_RETURN send_sock(struct cmdd_context *context) 627 | { 628 | int len; 629 | 630 | /* send data to client */ 631 | len = send(context->sock, 632 | context->send_data.data + context->send_data.curr_offset, 633 | context->send_data.total_len - context->send_data.curr_offset, MSG_NOSIGNAL | MSG_DONTWAIT); 634 | if (len < 0) { 635 | fprintf(stderr, "socket send failed, %s\n", strerror(errno)); 636 | return CMD_RETURN_ERR; 637 | } 638 | 639 | context->send_data.curr_offset += len; 640 | 641 | if (context->send_data.curr_offset == context->send_data.total_len) { 642 | /* if all data has been sent, stop send event, and reset buffer length info */ 643 | FD_CLR(context->sock, &context->wfds); 644 | context->send_data.total_len = 0; 645 | context->send_data.curr_offset = 0; 646 | } else if (context->send_data.curr_offset < context->send_data.total_len) { 647 | /* exists more data, move data to the beggining of the buffer */ 648 | memmove(context->send_data.data, context->send_data.data + context->send_data.curr_offset, 649 | context->send_data.total_len - context->send_data.curr_offset); 650 | context->send_data.total_len = context->send_data.total_len - context->send_data.curr_offset; 651 | context->send_data.curr_offset = 0; 652 | } else { 653 | fprintf(stderr, "BUG: internal error, data length mismach\n"); 654 | return CMD_RETURN_ERR; 655 | } 656 | 657 | /* Have enough free buff now, wake up mirror stdout, stderr event, and read. */ 658 | if (context->is_mirror_eof == 0) { 659 | FD_SET(context->mirror, &context->rfds); 660 | } 661 | 662 | if (context->is_mirror_err_eof == 0) { 663 | FD_SET(context->mirror_err, &context->rfds); 664 | } 665 | 666 | return CMD_RETURN_OK; 667 | } 668 | 669 | CMD_RETURN process_msg(struct cmdd_context *context) 670 | { 671 | struct jail_cmd_head *cmd_head; 672 | CMD_RETURN retval; 673 | 674 | /* process data which received from client. */ 675 | while (1) { 676 | /* if data is partial, continue recv */ 677 | if (context->recv_data.total_len - context->recv_data.curr_offset < sizeof(struct jail_cmd_head)) { 678 | break; 679 | } 680 | 681 | cmd_head = (struct jail_cmd_head *)(context->recv_data.data + context->recv_data.curr_offset); 682 | if (cmd_head->magic != MSG_MAGIC || cmd_head->data_len > sizeof(context->recv_data.data) - sizeof(struct jail_cmd_head)) { 683 | /* if recevied error data, exit. */ 684 | fprintf(stderr, "Data invalid\n"); 685 | return CMD_RETURN_ERR; 686 | } 687 | 688 | /* if data is partial, continue recv */ 689 | if (context->recv_data.total_len - context->recv_data.curr_offset < sizeof(struct jail_cmd_head) + cmd_head->data_len) { 690 | break; 691 | } 692 | 693 | retval = process_cmd(context, cmd_head); 694 | if (retval != CMD_RETURN_OK) { 695 | return retval; 696 | } 697 | 698 | context->recv_data.curr_offset += sizeof(struct jail_cmd_head) + cmd_head->data_len; 699 | } 700 | 701 | if (context->recv_data.total_len == context->recv_data.curr_offset) { 702 | /* if all data has been proceed, reset buffer length info */ 703 | context->recv_data.curr_offset = 0; 704 | context->recv_data.total_len = 0; 705 | } else if (context->recv_data.total_len > context->recv_data.curr_offset) { 706 | /* exists more data, move data to the beggining of the buffer */ 707 | memmove(context->recv_data.data, context->recv_data.data + context->recv_data.curr_offset, context->recv_data.total_len - context->recv_data.curr_offset); 708 | context->recv_data.total_len -= context->recv_data.curr_offset; 709 | context->recv_data.curr_offset = 0; 710 | } else { 711 | fprintf(stderr, "BUG: internal error, data length mismach\r\n"); 712 | return CMD_RETURN_ERR; 713 | } 714 | 715 | return CMD_RETURN_OK; 716 | } 717 | 718 | CMD_RETURN recv_sock(struct cmdd_context *context) 719 | { 720 | int len; 721 | 722 | /* recv data from client */ 723 | len = recv(context->sock, context->recv_data.data + context->recv_data.total_len, sizeof(context->recv_data.data) - context->recv_data.total_len, MSG_DONTWAIT); 724 | if (len < 0) { 725 | fprintf(stderr, "recv from socket failed, %s\n", strerror(errno)); 726 | return CMD_RETURN_ERR; 727 | } else if (len == 0) { 728 | /* if peer server closed, then stop recv event. */ 729 | FD_CLR(context->sock, &context->rfds); 730 | shutdown(context->sock, SHUT_RD); 731 | context->is_sock_eof = 1; 732 | /* wake up mirror write event, write remain data to stdin */ 733 | if (context->mirror <= 0 || context->is_mirror_eof == 1) { 734 | return CMD_RETURN_EXIT; 735 | } 736 | FD_SET(context->mirror, &context->wfds); 737 | return CMD_RETURN_OK; 738 | } 739 | 740 | context->recv_data.total_len += len; 741 | 742 | return process_msg(context); 743 | } 744 | 745 | CMD_RETURN read_mirror_err(struct cmdd_context *context) 746 | { 747 | int len; 748 | int need_size; 749 | int free_buff_size; 750 | 751 | struct jail_cmd_head *cmd_head; 752 | struct jail_cmd_data *cmd_data; 753 | 754 | free_buff_size = sizeof(context->send_data.data) - context->send_data.total_len; 755 | /* if free space is not enougth, then block reading from stderr */ 756 | need_size = sizeof(struct jail_cmd_head) + sizeof(struct jail_cmd_data) + 16; 757 | if ((free_buff_size - need_size) < 0) { 758 | FD_CLR(context->mirror_err, &context->rfds); 759 | return CMD_RETURN_OK; 760 | } 761 | 762 | cmd_head = (struct jail_cmd_head *)(context->send_data.data + context->send_data.total_len); 763 | cmd_data = (struct jail_cmd_data *)cmd_head->data; 764 | cmd_head->magic = MSG_MAGIC; 765 | cmd_head->type = CMD_MSG_DATA_ERR; 766 | len = read(context->mirror_err, cmd_data->data, free_buff_size - sizeof(struct jail_cmd_head) - sizeof(struct jail_cmd_data)); 767 | if (len < 0) { 768 | fprintf(stderr, "read mirror_err failed, %s\n", strerror(errno)); 769 | FD_CLR(context->mirror_err, &context->rfds); 770 | context->is_mirror_err_eof = 1; 771 | return CMD_RETURN_OK; 772 | } else if (len == 0 ) { 773 | FD_CLR(context->mirror_err, &context->rfds); 774 | context->is_mirror_err_eof = 1; 775 | return CMD_RETURN_OK; 776 | } 777 | 778 | cmd_head->data_len = len + sizeof(*cmd_data); 779 | context->send_data.total_len += sizeof(*cmd_head) + cmd_head->data_len; 780 | 781 | /* have read data from stderr, wake up sock, and start send. */ 782 | FD_SET(context->sock, &context->wfds); 783 | 784 | return CMD_RETURN_OK; 785 | } 786 | 787 | CMD_RETURN write_mirror(struct cmdd_context *context) 788 | { 789 | int len; 790 | CMD_RETURN retval; 791 | 792 | /* write mirror data to mirror as stdin */ 793 | len = write(context->mirror, context->mirror_data.data + context->mirror_data.curr_offset, context->mirror_data.total_len - context->mirror_data.curr_offset); 794 | if (len < 0) { 795 | FD_CLR(context->mirror, &context->wfds); 796 | if (errno == EPIPE) { 797 | /* child process may exit, return ok to ensure all 798 | * child out data has been sent to client 799 | */ 800 | return CMD_RETURN_OK; 801 | 802 | } 803 | 804 | fprintf(stderr, "write mirror failed, %s\n", strerror(errno)); 805 | return CMD_RETURN_ERR; 806 | } 807 | 808 | context->mirror_data.curr_offset += len; 809 | 810 | if (context->mirror_data.total_len == context->mirror_data.curr_offset) { 811 | /* if all data has been written to mirror as stin, stop write event, and reset buffer length info */ 812 | FD_CLR(context->mirror, &context->wfds); 813 | context->mirror_data.total_len = 0; 814 | context->mirror_data.curr_offset = 0; 815 | } else if (context->mirror_data.total_len > context->mirror_data.curr_offset) { 816 | /* exists more data, move data to the beggining of the buffer */ 817 | memmove(context->mirror_data.data, context->mirror_data.data + context->mirror_data.curr_offset, context->mirror_data.total_len - context->mirror_data.curr_offset); 818 | context->mirror_data.total_len -= context->mirror_data.curr_offset; 819 | context->mirror_data.curr_offset = 0; 820 | } else { 821 | fprintf(stderr, "BUG: internal error, data length mismach."); 822 | return CMD_RETURN_ERR; 823 | } 824 | 825 | if (context->is_sock_eof == 0 || context->recv_data.total_len > context->recv_data.curr_offset) { 826 | /* Have enough free buff and recv buffer has data, wake up sock for reading. */ 827 | FD_SET(context->sock, &context->rfds); 828 | } 829 | 830 | /* process CMD_MSG_DATA_IN message */ 831 | retval = process_msg(context); 832 | 833 | if (context->is_sock_eof == 1 && context->mirror_data.total_len == 0 && context->recv_data.total_len == 0) { 834 | /* if sock recv is closed and all data has been sent, then shutdown mirror stdin, notify child process exit. */ 835 | if (context->isatty) { 836 | /* interactive shell, just close fd */ 837 | close(context->mirror); 838 | context->mirror = -1; 839 | if (context->mirror_err > 0) { 840 | close(context->mirror_err); 841 | context->mirror_err = -1; 842 | } 843 | 844 | return CMD_RETURN_EXIT; 845 | } else { 846 | /* socketpair, do shutdown write */ 847 | shutdown(context->mirror, SHUT_WR); 848 | if (context->mirror_err > 0) { 849 | shutdown(context->mirror_err, SHUT_WR); 850 | } 851 | } 852 | } 853 | 854 | return retval; 855 | 856 | } 857 | 858 | CMD_RETURN read_mirror(struct cmdd_context *context) 859 | { 860 | struct jail_cmd_head *cmd_head; 861 | struct jail_cmd_data *cmd_data; 862 | 863 | int len; 864 | int free_buff_size; 865 | int need_size; 866 | 867 | free_buff_size = sizeof(context->send_data.data) - context->send_data.total_len; 868 | /* if free space is not enougth, then block reading from stdout */ 869 | need_size = sizeof(struct jail_cmd_head) + sizeof(struct jail_cmd_data) + 16; 870 | if ((free_buff_size - need_size) < 0) { 871 | FD_CLR(context->mirror, &context->rfds); 872 | return CMD_RETURN_OK; 873 | } 874 | 875 | cmd_head = (struct jail_cmd_head *)(context->send_data.data + context->send_data.total_len); 876 | cmd_data = (struct jail_cmd_data *)cmd_head->data; 877 | cmd_head->magic = MSG_MAGIC; 878 | cmd_head->type = CMD_MSG_DATA_OUT; 879 | len = read(context->mirror, cmd_data->data, free_buff_size - sizeof(struct jail_cmd_head) - sizeof(struct jail_cmd_data)); 880 | if (len < 0) { 881 | CMD_RETURN retval; 882 | /* if child process exits normally, return exit coode to peer client. */ 883 | if (errno == EIO || errno == ECONNRESET) { 884 | retval = CMD_RETURN_EXIT; 885 | } else { 886 | fprintf(stderr, "read mirror failed, %s\n", strerror(errno)); 887 | retval = CMD_RETURN_ERR; 888 | } 889 | FD_CLR(context->mirror, &context->rfds); 890 | context->is_mirror_eof = 1; 891 | return retval; 892 | } else if (len == 0 ) { 893 | /* end of mirror stdout, stop read stdout.*/ 894 | FD_CLR(context->mirror, &context->rfds); 895 | context->is_mirror_eof = 1; 896 | return CMD_RETURN_EXIT; 897 | } 898 | 899 | cmd_head->data_len = len + sizeof(*cmd_data); 900 | context->send_data.total_len += sizeof(*cmd_head) + cmd_head->data_len; 901 | 902 | /* have read data from mirror as stdout, wake up sock, and start send to client. */ 903 | FD_SET(context->sock, &context->wfds); 904 | 905 | return CMD_RETURN_OK; 906 | } 907 | 908 | void send_exit_code(struct cmdd_context *context) 909 | { 910 | int status = 0x100; 911 | 912 | /* send last remain data to client with block io */ 913 | if (context->send_data.total_len > 0) { 914 | send(context->sock, context->send_data.data + context->send_data.curr_offset, context->send_data.total_len - context->send_data.curr_offset, MSG_NOSIGNAL); 915 | context->send_data.total_len = 0; 916 | context->send_data.curr_offset = 0; 917 | } 918 | 919 | /* send child process exit code to client. */ 920 | struct jail_cmd_head *cmd_head = (struct jail_cmd_head *)(context->send_data.data + context->send_data.total_len); 921 | struct jail_cmd_exit *cmd_exit = (struct jail_cmd_exit *)cmd_head->data; 922 | cmd_head->magic = MSG_MAGIC; 923 | cmd_head->type = CMD_MSG_EXIT_CODE; 924 | cmd_head->data_len = sizeof(*cmd_exit); 925 | context->send_data.total_len += sizeof(*cmd_head) + cmd_head->data_len; 926 | 927 | /* get child process exit code. */ 928 | if (context->child_pid > 0) { 929 | if (waitpid(context->child_pid, &status, 0) < 0) { 930 | fprintf(stderr, "wait pid failed.\n"); 931 | } 932 | } 933 | 934 | cmd_exit->exit_code = WEXITSTATUS(status); 935 | 936 | send(context->sock, context->send_data.data + context->send_data.curr_offset, context->send_data.total_len - context->send_data.curr_offset, MSG_NOSIGNAL); 937 | } 938 | 939 | void server_loop(struct cmdd_context *context) 940 | { 941 | fd_set rfds_set; 942 | fd_set wfds_set; 943 | 944 | CMD_RETURN retval; 945 | int select_ret = 0; 946 | 947 | context->mirror = -1; 948 | context->mirror_err = -1; 949 | FD_ZERO(&context->rfds); 950 | FD_ZERO(&context->wfds); 951 | 952 | FD_SET(context->sock, &context->rfds); 953 | context->maxfd = max(context->sock, context->maxfd); 954 | 955 | while (1) { 956 | rfds_set = context->rfds; 957 | wfds_set = context->wfds; 958 | 959 | select_ret = select(context->maxfd + 1, &rfds_set, &wfds_set, NULL, NULL); 960 | if (select_ret < 0) { 961 | if (errno == EINTR) { 962 | continue; 963 | } 964 | fprintf(stderr, "select fd failed, %s\r\n", strerror(errno)); 965 | goto out; 966 | } else if (select_ret == 0) { 967 | continue; 968 | } 969 | 970 | if (FD_ISSET(context->sock, &rfds_set)) { 971 | /* recv message from client */ 972 | retval = recv_sock(context); 973 | if (retval == CMD_RETURN_EXIT) { 974 | goto out; 975 | } else if (retval == CMD_RETURN_ERR) { 976 | goto errout; 977 | } 978 | } 979 | 980 | if (FD_ISSET(context->sock, &wfds_set)) { 981 | /* send message to client */ 982 | retval = send_sock(context); 983 | if (retval == CMD_RETURN_EXIT) { 984 | goto out; 985 | } else if (retval == CMD_RETURN_ERR) { 986 | goto errout; 987 | } 988 | } 989 | 990 | if (FD_ISSET(context->mirror, &wfds_set)) { 991 | /* write data to child's stdin */ 992 | retval = write_mirror(context); 993 | if (retval == CMD_RETURN_EXIT) { 994 | goto out; 995 | } else if (retval == CMD_RETURN_ERR) { 996 | goto errout; 997 | } 998 | } 999 | 1000 | if (context->mirror_err > 0 && FD_ISSET(context->mirror_err, &rfds_set)) { 1001 | /* read data from child's stderr */ 1002 | retval = read_mirror_err(context); 1003 | if (retval == CMD_RETURN_EXIT) { 1004 | goto out; 1005 | } else if (retval == CMD_RETURN_ERR) { 1006 | goto errout; 1007 | } 1008 | } 1009 | 1010 | if (FD_ISSET(context->mirror, &rfds_set)) { 1011 | /* read data from child's stdout */ 1012 | retval = read_mirror(context); 1013 | if (retval == CMD_RETURN_EXIT) { 1014 | goto out; 1015 | } else if (retval == CMD_RETURN_ERR) { 1016 | goto errout; 1017 | } 1018 | } 1019 | 1020 | } 1021 | 1022 | out: 1023 | send_exit_code(context); 1024 | errout: 1025 | 1026 | if (context->mirror > 0) { 1027 | close(context->mirror); 1028 | } 1029 | 1030 | if (context->mirror_err > 0) { 1031 | close(context->mirror_err); 1032 | } 1033 | 1034 | return; 1035 | 1036 | } 1037 | 1038 | void serve(int sock) 1039 | { 1040 | struct cmdd_context *context; 1041 | 1042 | context = malloc(sizeof(*context)); 1043 | if (context == NULL) { 1044 | fprintf(stderr, "malloc context failed, %s\n", strerror(errno)); 1045 | goto errout; 1046 | } 1047 | memset(context, 0, sizeof(*context)); 1048 | 1049 | set_sock_opt(sock); 1050 | 1051 | context->sock = sock; 1052 | 1053 | /* restore SIGCHLD handle to default*/ 1054 | signal(SIGCHLD, SIG_DFL); 1055 | server_loop(context); 1056 | 1057 | errout: 1058 | if (context) { 1059 | free(context); 1060 | } 1061 | } 1062 | 1063 | int run_server(int port) 1064 | { 1065 | int server; 1066 | int sock; 1067 | socklen_t clilen; 1068 | struct sockaddr_in server_addr; 1069 | struct sockaddr_in client_addr; 1070 | int pid; 1071 | int on = 1; 1072 | 1073 | server = socket(PF_INET, SOCK_STREAM, 0); 1074 | if (server < 0) { 1075 | fprintf(stderr, "create socket failed, %s\n", strerror(errno)); 1076 | return -1; 1077 | } 1078 | 1079 | if (setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0) { 1080 | fprintf(stderr, "setsockopt socket opt SO_REUSEADDR failed, %s", strerror(errno)); 1081 | goto errout; 1082 | } 1083 | 1084 | if (setsockopt(server, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) != 0) { 1085 | fprintf(stderr, "setsockopt socket opt SO_KEEPALIVE failed, %s", strerror(errno)); 1086 | goto errout; 1087 | } 1088 | 1089 | if (fcntl(server, F_SETFD, fcntl(server, F_GETFD) | FD_CLOEXEC) != 0) { 1090 | fprintf(stderr, "setsockopt socket opt FD_CLOEXEC failed, %s", strerror(errno)); 1091 | goto errout; 1092 | } 1093 | 1094 | bzero((char *) &server_addr, sizeof(server_addr)); 1095 | server_addr.sin_family = AF_INET; 1096 | server_addr.sin_port = htons(port); 1097 | server_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 1098 | if (bind(server, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { 1099 | fprintf(stderr, "bind port %d failed, %s\n", port, strerror(errno)); 1100 | goto errout; 1101 | } 1102 | 1103 | if (listen(server, 10) < 0) { 1104 | fprintf(stderr, "listen failed, %s\n", strerror(errno)); 1105 | goto errout; 1106 | } 1107 | 1108 | while (1) { 1109 | clilen = sizeof(client_addr); 1110 | sock = accept(server, (struct sockaddr *)&client_addr, &clilen); 1111 | if (sock < 0) { 1112 | fprintf(stderr, "accept connection failed, %s\n", strerror(errno)); 1113 | continue; 1114 | } 1115 | 1116 | fcntl(sock, F_SETFD, fcntl(sock, F_GETFD) | FD_CLOEXEC); 1117 | 1118 | pid = fork(); 1119 | if (pid == 0) { 1120 | close(server); 1121 | close(pid_file_fd); 1122 | serve(sock); 1123 | 1124 | /* wait peer recv all data */ 1125 | shutdown(sock, SHUT_WR); 1126 | char buf[4096]; 1127 | while(recv(sock, buf, sizeof(buf), 0) > 0) { 1128 | } 1129 | shutdown(sock, SHUT_RD); 1130 | close(sock); 1131 | _exit(0); 1132 | } else if (pid > 0) { 1133 | close(sock); 1134 | } else { 1135 | fprintf(stderr, "fork failed, err %s\n", strerror(errno)); 1136 | } 1137 | } 1138 | 1139 | close(server); 1140 | return 0; 1141 | 1142 | errout: 1143 | if (server > 0) { 1144 | close(server); 1145 | } 1146 | return -1; 1147 | } 1148 | 1149 | void onexit(void) 1150 | { 1151 | 1152 | unlink(PID_FILE_PATH); 1153 | } 1154 | 1155 | void signal_handler(int sig) 1156 | { 1157 | switch(sig) { 1158 | case SIGTERM: 1159 | case SIGINT: 1160 | case SIGABRT: 1161 | case SIGQUIT: 1162 | onexit(); 1163 | _exit(1); 1164 | break; 1165 | } 1166 | } 1167 | 1168 | int load_cmdd_config(char *param, char *value) 1169 | { 1170 | if (strncmp(param, CONF_PORT, sizeof(CONF_PORT)) == 0) { 1171 | int port = atoi(value); 1172 | if (port <= 0) { 1173 | fprintf(stderr, "port is invalid: %s\n", value); 1174 | return -1; 1175 | } 1176 | config.port = port; 1177 | } else if (strncmp(param, CONF_AUDIT, sizeof(CONF_AUDIT)) == 0) { 1178 | if (strncmp(value, CONF_TRUE, sizeof(CONF_TRUE)) == 0) { 1179 | config.audit = 1; 1180 | } 1181 | } else if (strncmp(param, CONF_AUDIT_ARGS, sizeof(CONF_AUDIT_ARGS)) == 0) { 1182 | if (strncmp(value, CONF_TRUE, sizeof(CONF_TRUE)) == 0) { 1183 | config.audit_args = 1; 1184 | } 1185 | } 1186 | 1187 | return 0; 1188 | } 1189 | 1190 | int main(int argc, char *argv[]) 1191 | { 1192 | int opt; 1193 | int is_forground = 0; 1194 | 1195 | while ((opt = getopt(argc, argv, "fh")) != -1) { 1196 | switch (opt) { 1197 | case 'f': 1198 | is_forground = 1; 1199 | break; 1200 | case 'h': 1201 | help(); 1202 | return 1; 1203 | } 1204 | } 1205 | if (is_forground == 0) { 1206 | if (daemon(0, 0) < 0) { 1207 | fprintf(stderr, "run daemon process failed, %s\n", strerror(errno)); 1208 | return 1; 1209 | } 1210 | } 1211 | pid_file_fd = create_pid_file(PID_FILE_PATH); 1212 | if (pid_file_fd < 0) { 1213 | return 1; 1214 | } 1215 | 1216 | atexit(onexit); 1217 | 1218 | if (access(JAIL_CMDD_CONF_FILE, R_OK) == 0) { 1219 | if (load_config(JAIL_CMDD_CONF_FILE, load_cmdd_config) != 0 ) { 1220 | fprintf(stderr, "load configuration failed.\n"); 1221 | return 1; 1222 | } 1223 | } 1224 | 1225 | /* ignore SIGCHLD, child will be recycled automatically */ 1226 | signal(SIGCHLD, SIG_IGN); 1227 | signal(SIGPIPE, SIG_IGN); 1228 | signal(SIGTERM, signal_handler); 1229 | signal(SIGINT, signal_handler); 1230 | signal(SIGQUIT, signal_handler); 1231 | signal(SIGABRT, signal_handler); 1232 | 1233 | if (run_server(config.port) != 0) { 1234 | return 1; 1235 | } 1236 | 1237 | return 0; 1238 | } 1239 | --------------------------------------------------------------------------------