diff options
Diffstat (limited to 'src/samizdat-ssh-command')
-rwxr-xr-x | src/samizdat-ssh-command | 324 |
1 files changed, 0 insertions, 324 deletions
diff --git a/src/samizdat-ssh-command b/src/samizdat-ssh-command deleted file mode 100755 index a03b554..0000000 --- a/src/samizdat-ssh-command +++ /dev/null | |||
@@ -1,324 +0,0 @@ | |||
1 | #!/bin/dash | ||
2 | default_command="$HOME/samizdat-default-command" | ||
3 | if ! [ "$default_command" ] || ! [ -e "$default_command" ] | ||
4 | then | ||
5 | default_command=password_authentication | ||
6 | fi | ||
7 | |||
8 | authorize() | ||
9 | { | ||
10 | local authtype authdata forced_command comment authorized_keys_line | ||
11 | [ "$SSH_USER_AUTH" -a -f "$SSH_USER_AUTH" ] || return | ||
12 | read authtype authdata < "$SSH_USER_AUTH" || return | ||
13 | [ "$authtype" = publickey ] || return | ||
14 | |||
15 | forced_command="command=\"${0} authorize-full-access\",no-port-forwarding" | ||
16 | comment="samizdat: password-authenticated from ${SSH_CONNECTION%% *}" | ||
17 | |||
18 | authorized_keys_line="$forced_command $authdata $comment" | ||
19 | sentinel='Samizdat - YES WE CAN' | ||
20 | |||
21 | su - "$USER" -c 'mkdir -p "$HOME"/.ssh; touch "$HOME"/.ssh/authorized_keys' && | ||
22 | add_before_sentinel "$sentinel" \ | ||
23 | "$authorized_keys_line" \ | ||
24 | "$HOME"/.ssh/authorized_keys | ||
25 | } | ||
26 | |||
27 | add_before_sentinel() | ||
28 | { | ||
29 | local sentinel="$1" add_me="$2" target="$3" | ||
30 | sed -i.samizdat~ \ | ||
31 | -e "/$sentinel/i $add_me" \ | ||
32 | "$target" | ||
33 | } | ||
34 | |||
35 | password_authentication() | ||
36 | { | ||
37 | [ "$USER" ] || { echo 'Error: no $USER' >&2; exit 1; } | ||
38 | [ "$SSH_CLIENT_FINGERPRINT" ] || { echo 'Error: no $SSH_CLIENT_FINGERPRINT' >&2; exit 1; } | ||
39 | |||
40 | tty=$(tty) && [ "$tty" != 'not a tty' ] || tty= | ||
41 | |||
42 | if [ "$SSH_ORIGINAL_COMMAND" ]; then | ||
43 | msg='You are not authorized to execute the command: %s\n' | ||
44 | if ! [ "$tty" ]; then | ||
45 | msg="$msg"'To authorize your public key via password, reconnect without a command, or with a terminal attached.\n' | ||
46 | msg="$msg"'To attach a terminal, use the "-t" option to ssh.\n' | ||
47 | else | ||
48 | msg="$msg"'To authorize your public key and execute the command, enter your password.\n' | ||
49 | fi | ||
50 | printf "\n$msg\n" "$SSH_ORIGINAL_COMMAND" >&2 | ||
51 | if ! [ "$tty" ]; then | ||
52 | exit 1 | ||
53 | fi | ||
54 | else | ||
55 | msg='You are not authorized to log in.\n' | ||
56 | msg="$msg"'To authorize your public key and log in, enter your password.\n' | ||
57 | printf "\n$msg\n" >&2 | ||
58 | fi | ||
59 | authorize || exit $? | ||
60 | # TODO: blacklist after too many authentication failures | ||
61 | if [ "$SSH_ORIGINAL_COMMAND" ]; then | ||
62 | exec "$SSH_ORIGINAL_COMMAND" | ||
63 | else | ||
64 | exec $(getent passwd "$USER"|cut -d: -f7) -i | ||
65 | fi | ||
66 | exit $? # exec failed | ||
67 | } | ||
68 | |||
69 | die() { echo "Error: $*" >&2; exit 1; } | ||
70 | |||
71 | dequote() | ||
72 | { | ||
73 | # Sorry about the slashes. The perl would be: s{ \\(.) | '([^']+)' }{ $1$2 }gx | ||
74 | git_dir=$(echo -n "$git_dir" | sed -e "s/\\\\\\(.\\)\\|'\\([^']\\+\\)'/\\1\\2/g") | ||
75 | } | ||
76 | |||
77 | homedir_expand() | ||
78 | { | ||
79 | git_dir=$(homedir_expand_arg "$git_dir") && | ||
80 | [ "$git_dir" ] || die "Could not expand home directory. HOME=$HOME, USER=$USER, id=$(id)" | ||
81 | } | ||
82 | |||
83 | homedir_expand_arg() | ||
84 | { | ||
85 | [ "$HOME" ] || die '$HOME is not set.' | ||
86 | case "$1" in | ||
87 | \~) echo "$HOME";; | ||
88 | \~/*) echo "${HOME}${1#\~}";; | ||
89 | \~*) | ||
90 | local u | ||
91 | u=${1#\~} | ||
92 | u=${u%%/*} | ||
93 | u=$(getent passwd "$u"|cut -f6 -d:) && [ "$u" ] || return 1 | ||
94 | echo "$u/${1#*/}";; | ||
95 | /*) echo "$1";; | ||
96 | *) echo "$HOME/$1";; | ||
97 | esac | ||
98 | } | ||
99 | |||
100 | initialize_git() | ||
101 | { | ||
102 | local git_dir="$1" anonymous="$2" | ||
103 | if ! [ -e "$git_dir" ]; then | ||
104 | mkdir -p "$(dirname "$git_dir")" | ||
105 | git init --bare "$git_dir" >&2 | ||
106 | if [ "$anonymous" ]; then | ||
107 | git --git-dir "$git_dir" config samizdat.allow-anonymous-access true | ||
108 | git --git-dir "$git_dir" config samizdat.anonymous-ssh-owner "$anonymous" | ||
109 | fi | ||
110 | fi | ||
111 | } | ||
112 | |||
113 | is_gitdir() { git rev-parse --resolve-git-dir "$1" >/dev/null 2>&1; } | ||
114 | |||
115 | deny() { echo 'Error: permission denied.' >&2; exit 1; } | ||
116 | |||
117 | valid_new_public_repo() | ||
118 | { | ||
119 | local git_dir="$1" | ||
120 | [ ! -e "$git_dir" ] || return | ||
121 | [ "$HOME" -a -d "$HOME"/public_git ] || return | ||
122 | local dirname="$(dirname "$git_dir")" | ||
123 | |||
124 | case "$git_dir" in | ||
125 | *.git) ;; | ||
126 | *) | ||
127 | echo 'Error: public repos must be named *.git' >&2 | ||
128 | return 1 ;; | ||
129 | esac | ||
130 | |||
131 | case "$dirname" in | ||
132 | $HOME/public_git) return 0 ;; | ||
133 | $HOME/public_git/*) | ||
134 | # Ensure that no parent directory is named *.git | ||
135 | # Also enforce a maximum depth of 4. | ||
136 | # Valid: public_git/a/b/c/d.git | ||
137 | # Invalid: public_git/a/b/c/d/e.git | ||
138 | local n relative="${git_dir#$HOME/public_git/}" | ||
139 | for n in 1 2 3 4; do | ||
140 | local topmost="${relative%%/*}" | ||
141 | case "$topmost" in | ||
142 | "$relative") return 0;; | ||
143 | *.git) return 1;; | ||
144 | esac | ||
145 | relative=${relative#$topmost/} | ||
146 | done | ||
147 | echo 'Error: directories nest too deep' >&2 | ||
148 | return 1 | ||
149 | ;; | ||
150 | *) return 1 ;; | ||
151 | esac | ||
152 | } | ||
153 | |||
154 | check_if_ssh_user_owns_repository() | ||
155 | { | ||
156 | git --git-dir "$git_dir" config --get-all samizdat.anonymous-ssh-owner | grep -xqF "$SSH_CLIENT_FINGERPRINT" | ||
157 | } | ||
158 | ssh_user_owns_repository() | ||
159 | { | ||
160 | if [ -z "$SSH_USER_OWNS_REPOSITORY" ]; then | ||
161 | check_if_ssh_user_owns_repository | ||
162 | SSH_USER_OWNS_REPOSITORY=$? | ||
163 | fi | ||
164 | return $SSH_USER_OWNS_REPOSITORY | ||
165 | } | ||
166 | |||
167 | is_public_repository() | ||
168 | { | ||
169 | case "$git_dir" in | ||
170 | */../*) false;; | ||
171 | "$HOME"/public_git/*) true;; | ||
172 | *) false;; | ||
173 | esac | ||
174 | } | ||
175 | |||
176 | authorized() | ||
177 | { | ||
178 | # TODO: check SSH_CLIENT_FINGERPRINT against a blacklist | ||
179 | ssh_user_owns_repository && return | ||
180 | is_public_repository && return | ||
181 | test "$(git --git-dir "$1" config --bool --get samizdat.allow-anonymous-access)" = true 2>/dev/null && return 0 | ||
182 | # TODO: check SSH_CLIENT_FINGERPRINT against a whitelist | ||
183 | } | ||
184 | |||
185 | maybe_initialize_heads() | ||
186 | { | ||
187 | [ "$GIT_NAMESPACE" ] || die 'Programmer error' | ||
188 | heads=$git_dir/refs/namespaces/$GIT_NAMESPACE/refs/heads | ||
189 | mkdir -p "$heads" | ||
190 | found_file=$(find "$heads" -type f -print -quit) | ||
191 | [ "$found_file" ] && return | ||
192 | [ -e "$git_dir/refs/heads/master" ] && cp "$git_dir/refs/heads/master" "$heads" | ||
193 | # TODO: copy actual file 'HEAD' and whatever it references | ||
194 | } | ||
195 | |||
196 | |||
197 | if [ "$1" = "authorize-full-access" ]; then | ||
198 | case "$SSH_ORIGINAL_COMMAND" in | ||
199 | git-receive-pack\ *) | ||
200 | git_cmd=git-receive-pack | ||
201 | git_dir="${SSH_ORIGINAL_COMMAND#git-receive-pack }" | ||
202 | dequote | ||
203 | homedir_expand | ||
204 | initialize_git "$git_dir" | ||
205 | exec "$git_cmd" "$git_dir" | ||
206 | ;; | ||
207 | "") | ||
208 | shell=$(getent passwd $USER|cut -d: -f7) | ||
209 | argv0=-${shell##*/} | ||
210 | if which chpst >/dev/null 2>&1; then | ||
211 | exec chpst -b "$argv0" "$shell" | ||
212 | else | ||
213 | exec "$shell" | ||
214 | fi | ||
215 | ;; | ||
216 | *) | ||
217 | exec /bin/sh -c "$SSH_ORIGINAL_COMMAND" | ||
218 | ;; | ||
219 | esac | ||
220 | exit | ||
221 | fi | ||
222 | |||
223 | eval "$(samizdat-ssh-uid)" || die eval | ||
224 | |||
225 | if [ $# -gt 0 ] | ||
226 | then | ||
227 | exec "$@" | ||
228 | exit | ||
229 | fi | ||
230 | |||
231 | # TODO: call password_authentication on all authorization failures | ||
232 | |||
233 | #echo "SSH_ORIGINAL_COMMAND=$SSH_ORIGINAL_COMMAND" >&2 | ||
234 | case "$SSH_ORIGINAL_COMMAND" in | ||
235 | git-upload-pack\ *|git-receive-pack\ *) | ||
236 | # set three variables | ||
237 | # 1. git_cmd | ||
238 | # 2. git_dir | ||
239 | # 3. git_ns (optional) | ||
240 | |||
241 | git_cmd=${SSH_ORIGINAL_COMMAND%%\ *} | ||
242 | git_dir=${SSH_ORIGINAL_COMMAND#*\ } | ||
243 | |||
244 | dequote | ||
245 | homedir_expand | ||
246 | |||
247 | case "$git_dir" in | ||
248 | $HOME/git_namespace/*/public_git/*) | ||
249 | git_ns_subdir=${git_dir#$HOME/git_namespace/} | ||
250 | git_ns=${git_ns_subdir%%/*} | ||
251 | git_dir=$HOME/${git_ns_subdir#*/} | ||
252 | ;; | ||
253 | esac | ||
254 | |||
255 | ;; | ||
256 | rsync\ --server\ --sender\ -de.LsfxC\ .\ public_git/|rsync\ --server\ --sender\ -de.LsfxC\ .\ public_git/|rsync\ --server\ --sender\ -de.Lsf\ .\ public_git/) | ||
257 | #echo "$SSH_ORIGINAL_COMMAND" >&2 | ||
258 | [ -d "$HOME"/public_git ] || { password_authentication; exit 1; } | ||
259 | exec $SSH_ORIGINAL_COMMAND | ||
260 | #exec rrsync -ro "$HOME"/public_git | ||
261 | exit 1 | ||
262 | ;; | ||
263 | rsync\ --server\ --sender\ *) | ||
264 | #echo "$SSH_ORIGINAL_COMMAND" >&2 | ||
265 | [ -d "$HOME"/public_rsync ] || { password_authentication; exit 1; } | ||
266 | exec rrsync -ro "$HOME"/public_rsync | ||
267 | exit 1 | ||
268 | ;; | ||
269 | rsync\ --server\ *) | ||
270 | [ -d "$HOME"/incoming_rsync -a "${SSH_CLIENT_FINGERPRINT}" ] || { password_authentication; exit 1; } | ||
271 | destdir=$HOME/incoming_rsync/$SSH_CLIENT_FINGERPRINT/ | ||
272 | mkdir -p "$destdir" && exec rrsync "$destdir" | ||
273 | exit 1 | ||
274 | ;; | ||
275 | *) | ||
276 | #password_authentication | ||
277 | $default_command | ||
278 | exit | ||
279 | ;; | ||
280 | esac | ||
281 | |||
282 | if [ "$git_cmd" = 'git-upload-pack' ]; then | ||
283 | case "$git_dir" in | ||
284 | $HOME/public_git/*|public_git/*) | ||
285 | is_gitdir "$git_dir" || git_dir="$git_dir/.git" | ||
286 | if ! is_gitdir "$git_dir"; then | ||
287 | # git rev-parse --resolve-git-dir "${git_dir%/.git}" # show git's error message | ||
288 | deny | ||
289 | fi | ||
290 | if [ "$git_ns" -a -e "$git_dir/refs/namespaces/$git_ns" ]; then | ||
291 | export GIT_NAMESPACE="$git_ns" | ||
292 | # maybe_initialize_heads | ||
293 | fi | ||
294 | exec "$git_cmd" "$git_dir" | ||
295 | ;; | ||
296 | esac | ||
297 | |||
298 | elif [ "$git_cmd" = 'git-receive-pack' ]; then | ||
299 | |||
300 | if [ ! -d "$git_dir" ]; then | ||
301 | if valid_new_public_repo "$git_dir"; then | ||
302 | initialize_git "$git_dir" "$SSH_CLIENT_FINGERPRINT" | ||
303 | else | ||
304 | deny | ||
305 | fi | ||
306 | fi | ||
307 | |||
308 | fi | ||
309 | |||
310 | if authorized "$git_dir"; then | ||
311 | if [ "$git_cmd" = 'git-receive-pack' ]; then | ||
312 | if ! ssh_user_owns_repository | ||
313 | then | ||
314 | export GIT_NAMESPACE="$SSH_CLIENT_FINGERPRINT" | ||
315 | maybe_initialize_heads | ||
316 | printf '%s:%s\n' 'd@cryptonomic.net' "git_namespace/$GIT_NAMESPACE/${git_dir#${HOME}/}" >&2 | ||
317 | fi | ||
318 | fi | ||
319 | exec "$git_cmd" "$git_dir" | ||
320 | else | ||
321 | $default_command | ||
322 | exit | ||
323 | # echo 'Error: git access is unauthorized' >&2; exit 1 # unreached | ||
324 | fi | ||