/[gentoo-src]/keychain/keychain.bash
Gentoo

Contents of /keychain/keychain.bash

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.3 - (show annotations) (download)
Fri May 14 21:22:40 2004 UTC (16 years, 4 months ago) by agriffis
Branch: MAIN
CVS Tags: HEAD
Changes since 1.2: +1 -1 lines
FILE REMOVED
New version 2.3.0 which rewrites the locking code to avoid procmail :-)

1 #!/bin/sh
2 # Copyright 1999-2003 Gentoo Technologies, Inc.
3 # Distributed under the terms of the GNU General Public License v2
4 # Author: Daniel Robbins <drobbins@gentoo.org>
5 # Previous Maintainer: Seth Chandler <sethbc@gentoo.org>
6 # Current Maintainer: Aron Griffis <agriffis@gentoo.org>
7 # $Header: /home/cvsroot/gentoo-src/keychain/keychain.bash,v 1.2 2004/05/03 22:09:44 agriffis Exp $
8
9 version=2.2.2
10
11 PATH="/usr/bin:/bin:/sbin:/usr/sbin:/usr/ucb:${PATH}"
12
13 maintainer="agriffis@gentoo.org"
14 zero="`basename $0`"
15 mesglog=''
16 myaction=''
17 ignoreopt=false
18 noaskopt=false
19 noguiopt=false
20 nolockopt=false
21 openssh=unknown
22 quickopt=false
23 quietopt=false
24 clearopt=false
25 timeout=''
26 attempts=3
27 myavail=''
28 mykeys=''
29 keydir="${HOME}/.keychain"
30
31 BLUE=""
32 CYAN=""
33 GREEN=""
34 OFF=""
35 RED=""
36
37 # pidf holds the specific name of the keychain .ssh-agent-myhostname file.
38 # We use the new hostname extension for NFS compatibility. cshpidf is the
39 # .ssh-agent file with csh-compatible syntax. lockf is the lockfile, used
40 # to serialize the execution of multiple ssh-agent processes started
41 # simultaneously (only works if lockfile from the procmail package is
42 # available.
43 hostname=`uname -n 2>/dev/null || echo unknown`
44 pidf="${keydir}/${hostname}-sh"
45 cshpidf="${keydir}/${hostname}-csh"
46 lockf="${keydir}/${hostname}-lock"
47
48 # synopsis: qprint "message"
49 qprint() {
50 $quietopt || echo "$*" >&2
51 }
52
53 # synopsis: mesg "message"
54 # Prettily print something to stderr, honors quietopt
55 mesg() {
56 qprint " ${GREEN}*${OFF} $*"
57 }
58
59 # synopsis: warn "message"
60 # Prettily print a warning to stderr
61 warn() {
62 echo " ${RED}* Warning${OFF}: $*" >&2
63 }
64
65 # synopsis: error "message"
66 # Prettily print an error
67 error() {
68 echo " ${RED}* Error${OFF}: $*" >&2
69 }
70
71 # synopsis: die "message"
72 # Prettily print an error, then abort
73 die() {
74 [ -n "$1" ] && error "$*"
75 qprint
76 exit 1
77 }
78
79 # synopsis: versinfo
80 # Display the version information
81 versinfo() {
82 qprint
83 qprint "${GREEN}KeyChain ${version}; ${BLUE}http://www.gentoo.org/projects/keychain${OFF}"
84 qprint "Copyright 2002-2004 Gentoo Technologies, Inc.; Distributed under the GPL"
85 qprint
86 }
87
88 # synopsis: helpinfo
89 # Display the help information. There's no really good way to use qprint for
90 # this...
91 helpinfo() {
92 cat >&2 <<EOHELP
93 INSERT_POD_OUTPUT_HERE
94 EOHELP
95 }
96
97 # synopsis: testssh
98 # Figure out which ssh is in use, set the global boolean $openssh
99 testssh() {
100 # Query local host for SSH application, presently supporting only
101 # OpenSSH (see http://www.openssh.org) when openssh="yes" and
102 # SSH2 (see http://www.ssh.com) when openssh="no".
103 case "`ssh -V 2>&1`" in
104 *OpenSSH*) openssh=true ;;
105 *) openssh=false ;;
106 esac
107 }
108
109 # synopsis: getuser
110 # Set the global string $me
111 getuser() {
112 # whoami gives euid, which might be different from USER or LOGNAME
113 me=`whoami` || die "Who are you? whoami doesn't know..."
114 }
115
116 # synopsis: verifykeydir
117 # Make sure the key dir is set up correctly. Exits on error.
118 verifykeydir() {
119 # Create keydir if it doesn't exist already
120 if [ -f ${keydir} ]; then
121 die "${keydir} is a file (it should be a directory)"
122 # Solaris 9 doesn't have -e; using -d....
123 elif [ ! -d ${keydir} ]; then
124 mkdir ${keydir} || die "can't create ${keydir}"
125 chmod 0700 ${keydir} || die "can't chmod ${keydir}"
126 fi
127 }
128
129 # synopsis: takelock
130 # Attempts to get the lockfile $lockf. If locking isn't available, just returns.
131 # If locking is available but can't get the lock, exits with error.
132 takelock() {
133 # Honor --nolock
134 if $nolockopt; then
135 lockf=''
136 return 0
137 fi
138
139 # lockfile is part of procmail
140 # TODO: implement with mkdir to avoid the dependency
141 lockfile -1 -r 30 -l 35 -s 2 "$lockf" 2>/dev/null
142 case $? in
143 0)
144 return 0
145 ;;
146 73)
147 error "couldn't get lock"
148 return 1
149 ;;
150 *)
151 warn "locking unavailable (install procmail or use --nolock)"
152 return 0
153 ;;
154 esac
155 }
156
157 # synopsis: droplock
158 # Drops the lock if we're holding it.
159 droplock() {
160 [ -n "$lockf" ] && rm -f "$lockf"
161 }
162
163 # synopsis: findpids
164 # Returns a space-separated list of ssh-agent pids
165 findpids() {
166 unset fp_psout
167
168 # OS X requires special handling. It returns a false positive with
169 # "ps -u $me" but is running bash so we can check for it via OSTYPE
170 case "$OSTYPE" in darwin*) fp_psout=`ps x 2>/dev/null` ;; esac
171
172 # SysV syntax will work on Cygwin, Linux, HP-UX and Tru64
173 # (among others)
174 [ -z "$fp_psout" ] && fp_psout=`ps -u $me 2>/dev/null`
175
176 # BSD syntax for others
177 [ -z "$fp_psout" ] && fp_psout=`ps x 2>/dev/null`
178
179 # Return the list of pids; ignore case for Cygwin
180 # and check only 8 characters since Solaris truncates at that length
181 if [ -n "$fp_psout" ]; then
182 echo "$fp_psout" | \
183 awk 'BEGIN{IGNORECASE=1} /[s]sh-agen/{print $1}' | xargs
184 return 0
185 fi
186
187 # If neither worked, we're stuck
188 error "Unable to use \"ps\" to scan for ssh-agent processes"
189 error "Please report to $maintainer"
190 return 1
191 }
192
193 # synopsis: stopagent
194 # --stop tells keychain to kill the existing ssh-agent(s)
195 stopagent() {
196 sa_mypids=`findpids`
197 [ $? = 0 ] || die
198
199 kill $sa_mypids >/dev/null 2>&1
200
201 if [ -n "$sa_mypids" ]; then
202 mesg "All $me's ssh-agent(s) ($sa_mypids) are now stopped."
203 else
204 mesg "No ssh-agent(s) found running."
205 fi
206 qprint
207
208 rm -f "${pidf}" "${cshpidf}" 2>/dev/null
209 }
210
211 # synopsis: quickload
212 # Load agent variables (either from $pidf or environment) and copy
213 # implementation-specific environment variables into generic global strings
214 quickload() {
215 [ -f "$pidf" ] && . "$pidf"
216 # Copy implementation-specific environment variables into generic local
217 # variables.
218 if [ -n "$SSH_AUTH_SOCK" ]; then
219 ssh_auth_sock=$SSH_AUTH_SOCK
220 ssh_agent_pid=$SSH_AGENT_PID
221 ssh_auth_sock_name=SSH_AUTH_SOCK
222 ssh_agent_pid_name=SSH_AGENT_PID
223 elif [ -n "$SSH2_AUTH_SOCK" ]; then
224 ssh_auth_sock=$SSH2_AUTH_SOCK
225 ssh_agent_pid=$SSH2_AGENT_PID
226 ssh_auth_sock_name=SSH2_AUTH_SOCK
227 ssh_agent_pid_name=SSH2_AGENT_PID
228 else
229 unset ssh_auth_sock ssh_agent_pid ssh_auth_sock_name ssh_agent_pid_name
230 return 1
231 fi
232 return 0
233 }
234
235 # synopsis: loadagent
236 # Load agent variables from $pidf
237 loadagent() {
238 unset SSH_AUTH_SOCK SSH_AGENT_PID SSH2_AUTH_SOCK SSH2_AGENT_PID
239 quickload
240 return $?
241 }
242
243 # synopsis: startagent
244 # Starts the ssh-agent if it isn't already running.
245 # Requires $ssh_agent_pid
246 startagent() {
247 sa_mypids=`findpids`
248 [ $? = 0 ] || die
249
250 # Check for an existing agent
251 [ -n "$ssh_agent_pid" ] || ssh_agent_pid=none
252 case " $sa_mypids " in
253 *" $ssh_agent_pid "*)
254 mesg "Found existing ssh-agent at PID $ssh_agent_pid"
255 return 0
256 ;;
257 esac
258
259 kill $sa_mypids >/dev/null 2>&1 && \
260 mesg "All previously running ssh-agent(s) have been stopped."
261
262 # Init the bourne-formatted pidfile
263 mesg "Initializing ${pidf} file..."
264 :> "$pidf" && chmod 0600 "$pidf"
265 if [ $? != 0 ]; then
266 rm -f "$pidf" "$cshpidf" 2>/dev/null
267 error "can't create ${pidf}"
268 return 1
269 fi
270
271 # Init the csh-formatted pidfile
272 mesg "Initializing ${cshpidf} file..."
273 :> "$cshpidf" && chmod 0600 "$cshpidf"
274 if [ $? != 0 ]; then
275 rm -f "$pidf" "$cshpidf" 2>/dev/null
276 error "can't create ${cshpidf}"
277 return 1
278 fi
279
280 # Start the agent.
281 # Some versions of ssh-agent don't understand -s, which means to generate
282 # Bourne shell syntax. So set SHELL instead
283 mesg "Starting ssh-agent"
284 SHELL=/bin/sh sshout=`ssh-agent`
285 if [ $? != 0 ]; then
286 rm -f "$pidf" "$cshpidf" 2>/dev/null
287 error "Failed to start ssh-agent"
288 return 1
289 fi
290
291 # Add content to pidfiles
292 echo "$sshout" | grep -v 'Agent pid' >"$pidf"
293 loadagent
294 echo "setenv $ssh_auth_sock_name $ssh_auth_sock" >"$cshpidf"
295 echo "setenv $ssh_agent_pid_name $ssh_agent_pid" >>"$cshpidf"
296 }
297
298 # synopsis: ssh_l
299 # Return space-separated list of known fingerprints
300 ssh_l() {
301 sl_mylist=`ssh-add -l 2>/dev/null`
302 sl_retval=$?
303
304 if $openssh; then
305 # Error codes:
306 # 0 success
307 # 1 no identities (not an error)
308 # 2 can't connect to auth agent
309 case $sl_retval in
310 0)
311 # Output of ssh-add -l:
312 # 1024 7c:c3:e2:7e:fb:05:43:f1:8e:e6:91:0d:02:a0:f0:9f .ssh/id_dsa (DSA)
313 # Return a space-separated list of fingerprints
314 echo "$sl_mylist" | cut -f2 -d' ' | xargs
315 return 0
316 ;;
317 1)
318 return 0
319 ;;
320 *)
321 return $sl_retval
322 ;;
323 esac
324 else
325 # Error codes:
326 # 0 success
327 # 1 can't connect to auth agent
328 # 2 bad passphrase
329 # 3 bad identity file
330 # 4 the agent does not have the requested identity
331 # 5 unspecified error
332 if [ $sl_retval = 0 ]; then
333 # Output of ssh-add -l:
334 # The authorization agent has one key:
335 # id_dsa_2048_a: 2048-bit dsa, agriffis@alpha.zk3.dec.com, Fri Jul 25 2003 10:53:49 -0400
336 # Since we don't have a fingerprint, just get the filenames *shrug*
337 echo "$sl_mylist" | awk 'NR>1{sub(":.*", ""); print}' | xargs
338 fi
339 return $sl_retval
340 fi
341 }
342
343 # synopsis: ssh_f filename
344 # Return finger print for a keyfile
345 # Requires $openssh
346 ssh_f() {
347 sf_filename="$1"
348 if $openssh; then
349 if [ ! -f "$sf_filename.pub" ]; then
350 warn "$sf_filename.pub missing; can't tell if $sf_filename is loaded"
351 return 1
352 fi
353 sf_fing=`ssh-keygen -l -f "$sf_filename.pub"` || return 1
354 echo "$sf_fing" | cut -f2 -d' '
355 else
356 # can't get fingerprint for ssh2 so use filename *shrug*
357 basename "$sf_filename"
358 fi
359 return 0
360 }
361
362 # synopsis: listmissing
363 # Uses $mykeys and $myavail
364 # Returns a space-separated list of keys found to be missing.
365 listmissing() {
366 lm_missing=''
367
368 for lm_k in $mykeys; do
369 # Search for the keyfile
370 if [ -f "$lm_k" ]; then
371 lm_kfile="$lm_k"
372 elif [ -f "$HOME/.ssh/$lm_k" ]; then
373 lm_kfile="$HOME/.ssh/$lm_k"
374 elif [ -f "$HOME/.ssh2/$lm_k" ]; then
375 lm_kfile="$HOME/.ssh2/$lm_k"
376 else
377 $ignoreopt || warn "can't find $lm_k; skipping"
378 continue
379 fi
380
381 # Fingerprint current user-specified key
382 finger=`ssh_f "$lm_kfile"` || continue
383
384 # Check if it needs to be added
385 case " $myavail " in
386 *" $finger "*)
387 # already know about this key
388 mesg "Key: ${BLUE}$lm_k${OFF}"
389 ;;
390 *)
391 # need to add this key
392 lm_missing="$lm_missing $lm_kfile"
393 ;;
394 esac
395 done
396 echo "$lm_missing"
397 }
398
399 # synopsis: set_action
400 # Sets $myaction or dies if $myaction is already set
401 setaction() {
402 if [ -n "$myaction" ]; then
403 die "you can't specify --$myaction and $1 at the same time"
404 else
405 myaction="$1"
406 fi
407 }
408
409 #
410 # MAIN PROGRAM
411 #
412
413 # parse the command-line
414 while [ -n "$1" ]; do
415 case "$1" in
416 --help|-h)
417 setaction help
418 ;;
419 --stop|-k)
420 setaction stop
421 ;;
422 --version|-V)
423 setaction version
424 ;;
425 --attempts)
426 shift
427 if [ "$1" -gt 0 ] 2>/dev/null; then
428 attempts=$1
429 else
430 die "--attempts requires a numeric argument greater than zero"
431 fi
432 ;;
433 --dir)
434 shift
435 case "$1" in
436 */.*) keydir="$1" ;;
437 '') die "--dir requires an argument" >&2 ;;
438 *) keydir="$1/.keychain" ;; # be backward-compatible
439 esac
440 ;;
441 --clear)
442 clearopt=true
443 ;;
444 --ignore-missing)
445 ignoreopt=true
446 ;;
447 --noask)
448 noaskopt=true
449 ;;
450 --nogui)
451 noguiopt=true
452 ;;
453 --nolock)
454 nolockopt=true
455 ;;
456 --quick|-Q)
457 quickopt=true
458 ;;
459 --quiet|-q)
460 quietopt=true
461 ;;
462 --nocolor)
463 unset BLUE CYAN GREEN OFF RED
464 ;;
465 --timeout)
466 shift
467 if [ "$1" -gt 0 ] 2>/dev/null; then
468 timeout=$1
469 else
470 die "--timeout requires a numeric argument greater than zero"
471 fi
472 ;;
473 --)
474 shift
475 [ -n "$1" ] && mykeys="${mykeys} $*"
476 break
477 ;;
478 -*)
479 echo "$zero: unknown option $1" >&2
480 exit 1
481 ;;
482 *)
483 mykeys="${mykeys} $1"
484 ;;
485 esac
486 shift
487 done
488
489 # Don't use color if there's no terminal on stdout
490 if [ -n "$OFF" ]; then
491 tty <&1 >/dev/null 2>&1 || unset BLUE CYAN GREEN OFF RED
492 fi
493
494 # versinfo uses qprint, which honors --quiet
495 versinfo
496 [ "$myaction" = version ] && exit 0
497 [ "$myaction" = help ] && { helpinfo; exit 0; }
498
499 # Disallow ^C until we've had a chance to --clear.
500 # Don't use signal names because they don't work on Cygwin.
501 trap '' 2
502 trap 'droplock' 0 1 15 # drop the lock on exit
503
504 verifykeydir # sets up $keydir
505 testssh # sets $openssh
506 getuser # sets $me
507
508 # --stop: kill the existing ssh-agent(s) and quit
509 if [ "$myaction" = stop ]; then
510 takelock || die
511 stopagent
512 exit 0 # stopagent is always successful
513 fi
514
515 # Note regarding locking: if we're trying to be quick, then don't take the lock.
516 # It will be taken later if we discover we can't be quick. On the other hand,
517 # if we're not trying to be quick, then take the lock now to avoid a race
518 # condition.
519 $quickopt || takelock || die # take lock to manipulate keys/pids/files
520 loadagent # sets ssh_auth_sock, ssh_agent_pid, etc
521 myavail=`ssh_l` # try to use existing agent
522 # 0 = found keys, 1 = no keys, 2 = no agent
523 if [ $? = 0 -o \( $? = 1 -a -z "$mykeys" \) ]; then
524 mesg "Found running ssh-agent ($ssh_agent_pid)"
525 $quickopt && exit 0
526 else
527 $quickopt && { takelock || die; }
528 startagent || die # start ssh-agent
529 fi
530
531 # --timeout translates almost directly to ssh-add -t, but commercial ssh uses
532 # minutes and OpenSSH uses seconds
533 if [ -n "$timeout" ]; then
534 $openssh && timeout=`expr $timeout \* 60`
535 timeout="-t $timeout"
536 fi
537
538 # --clear: remove all keys from the agent
539 if $clearopt; then
540 sshout=`ssh-add -D 2>&1`
541 if [ $? = 0 ]; then
542 mesg "$sshout"
543 touch "$pidf" # reset for --timeout
544 else
545 warn "$sshout"
546 fi
547 fi
548 trap 'droplock' 2 # done clearing, safe to ctrl-c
549
550 # --noask: "don't ask for keys", so we're all done
551 $noaskopt && { qprint; exit 0; }
552
553 myavail=`ssh_l` # update myavail now that we're locked
554 mykeys=`listmissing` # cache list of missing keys
555
556 # Attempt to add the keys
557 while [ -n "$mykeys" ]; do
558 mesg "Adding ${BLUE}`echo $mykeys | wc -w`${OFF} key(s)..."
559
560 # For some reason commercial ssh spits out multiple success messages per
561 # key. Use uniq to filter it down to a single message.
562 if $noguiopt || [ -z "$SSH_ASKPASS" ]; then
563 sshout=`ssh-add $timeout $mykeys 2>&1 | uniq`
564 else
565 sshout=`ssh-add $timeout $mykeys 2>&1 </dev/null | uniq`
566 fi
567 retval=$?
568 [ -n "$sshout" ] && echo "$sshout" | while read line; do mesg "$line"; done
569 [ $retval = 0 ] && break
570
571 if [ $attempts = 1 ]; then
572 die "Problem adding; giving up"
573 else
574 warn "Problem adding; trying again"
575 fi
576
577 # Update the list of missing keys
578 myavail=`ssh_l`
579 [ $? = 0 ] || die "problem running ssh-add -l"
580 mykeys=`listmissing`
581
582 # Decrement the countdown
583 attempts=`expr $attempts - 1`
584 done
585
586 qprint # trailing newline
587
588 # vim:sw=4 expandtab tw=80

  ViewVC Help
Powered by ViewVC 1.1.20