FrauBSD / secure_thumb

GELI encrypted images and thumb drives for FreeBSD
https://FrauBSD.org/secure_thumb
BSD 2-Clause "Simplified" License
8 stars 0 forks source link

Function issues #3

Closed freebsdfrau closed 4 years ago

freebsdfrau commented 4 years ago

https://github.com/FrauBSD/secure_thumb/blob/cd2f39d536ac1bdc257b2f766b22cc1c29d1437f/etc/ssh.csh#L74

When the dialog_menutag alias is invoked, it spawns a new /bin/sh child that knows nothing about the "quiet" alias:

https://github.com/FrauBSD/secure_thumb/blob/cd2f39d536ac1bdc257b2f766b22cc1c29d1437f/etc/ssh.csh#L80

Which was declared in the [t]csh namespace, and thus, invisible to /bin/sh.

Let's see if we can make a [t]csh-native dialog_menutag with this:

quietly unalias dialog_menutag
alias dialog_menutag ' \
        set tmpfile="$DIALOG_TMPDIR/dialog.menu.$$" \
        [ -e "$tmpfile" ] && cat "$tmpfile" \
        quietly rm -f "$tmpfile" \
;:'

This should work as a native CSH "function", but we'll see how it fairs (it may be taking us in the wrong direction if we can't fully realize all the use-cases inside similarly native requirements

freebsdfrau commented 4 years ago

I decided that functionality was more important than native CSH code.

So here's a working fprintf/eprintf:

quietly unalias fprintf
alias fprintf "/bin/sh -c '"'                \\
        fd=$1                                \\
        [ $# -gt 1 ] || return ${FAILURE:-1} \\
        shift 1                              \\
        printf "$@" >&$fd                    \\
'"' -- /bin/sh"

quietly unalias eprintf
alias eprintf fprintf 2
freebsdfrau commented 4 years ago

I tried to make a native-csh version of dialog_menutag2help but something strange is happening.

alias dialog_menutag2help '\\
       set _argv = (\!*) \
       set _argc = $#_argv \
       set _tag = $_argv[1] \
       set _i = 2 \
       while ( $_i <= $_argc ) \
               set _tagi = $_argv[$_i] \
               @ _i += 2 \
               set _help = $_argv[$_i] \
               if ( $_tag == $_tagi ) then \
                       echo "$_help" \
                       break \
               endif \
               @ _i++ \
       end \
       unset _argv _argc _tag _i _tagi _help \
;:'

While it does work, it messes up the command history for the session that it is run.

It turns out that invoking "while" in csh causes each and every line that follows to be appended to the command-history, leading up-to and including the final "end" statement to terminate the while loop.

The net-effect is that someone running the alias may be surprised (as I was) when, following the completion of the alias, pressing up-arrow does not result in retrieving the command that was executed, but rather elements from the while loop (you have to keep pressing up-arrow several times to get back to the alias which is stored in the session command history just before the while loop invocation.

This is a very strange thing for a shell to do, but I take it that it is entirely because an alias is not a protected namespace nor it is even considered to run in a separate aspect of the user's session.

This may force our hand into going non-native (in other words, fork to /bin/sh) for every alias that needs 1) the ability to set an exit status (something aliases cannot do when you parameterize them -- by using !* to place the arguments some place other than at the end), 2) interpret the meaning of arguments-passed before acting on them.

freebsdfrau commented 4 years ago

I found a way to achieve what we need:

alias dialog_menutag2help 'echo '\'' \\
        set argc = $#argv \\
        set tag = $argv[1] \\
        set i = 2 \\
        while ( $i <= $argc ) \\
                set tagi = $argv[$i] \\
                @ i += 2 \\
                set help = $argv[$i] \\
                if ( $tag == $tagi ) then \\
                        echo "$help" \\
                        break \\
                endif \\
                @ i++ \\
        end \\
'\'' | $shell /dev/stdin'
kfv commented 4 years ago

On the subject of command history corruption, yes I had the same issue weeks ago when I was trying to use switch cases in an alias. I also noticed I had to run a script twice so it could get correct values for variables inside the switch case. I thought it must be my mistake but now that I see you've had the same issue with a while statement I can see why many believe it's the best to avoid csh - but I still love it :-))

freebsdfrau commented 4 years ago

To properly create function-like aliases that don't corrupt the history and remain using CSH, I had to do five things:

  1. Put the code to execute into a single-quoted string (prevents premature expansion of variables resulting in "Unknown variable" errors when the parser does a pre-pass interpolation during compilation of the alias

  2. Use double-escaped newline continuation characters at the end of each line within the singly-quoted code

  3. Use compound strings (immediately-adjacent quoting mechanisms transitioning from singly-quoted to bareword (to express a literal single-quote), back to single-quotes

  4. echo the singly-quoted code into the current interpreter ($shell) to prevent history mangling when you use things like loops (for, while) or conditionals (if, switch, etc).

  5. Allow the arguments to the alias to permeate to the interpreter to populate $argv for the singly-quoted code

freebsdfrau commented 4 years ago

Holy crap! I did it! It actually can be done. Hey, it's not pretty, but it's workable. Check this out ...

alias colorize 'cat > /tmp/colorize.$$; setenv __ $$; echo '\'' \\
        set ti = "31;1" \\
        set te = "39;22" \\
 \\
        set argc = $#argv \\
        set i = 1 \\
        while ($i <= $argc) \\
                set argv[$i] = "$argv[$i]:as/'\'\\\'\''/__$$__/" \\
                set argv[$i] = "$argv[$i]:as/__$$__/'\'\\\'\\\\\\\'\\\'\''/" \\
                set argv[$i] = "'\'\\\'\''$argv[$i]'\'\\\'\''" \\
                @ i++ \\
        end \\
        eval set argv = \($argv:q\) \\
        while (1) \\
                switch ($1:q) \\
                case -c: \\
                        set ti = $2:q \\
                        shift \\
                        shift \\
                        breaksw \\
                case -e: \\
                        set te = $2:q \\
                        shift \\
                        shift \\
                        breaksw \\
                case --: \\
                        shift \\
                        break \\
                default: \\
                        break \\
                endsw \\
        end \\
        awk -v ti="$ti" -v te="$te" -v pattern="$1" '\'\\\'\'' \\\
                gsub(pattern, "\033[" ti "m&\033[" te "m")||1 \\\
        '\'\\\'\'' /tmp/colorize.$__ \\
        rm -f /tmp/colorize.$__ \\
'\'' | $shell /dev/stdin'
#'\'' | cat;:' # Use this line to see what $shell will execute

For debugging:

When run in this debugging mode, we can see the effects of first-pass interpolation, or in other words, what is actually sent to the $shell interpreter (/bin/csh):

    set ti = "31;1" 
    set te = "39;22" 

    set argc = $#argv 
    set i = 1 
    while ($i <= $argc) 
        set argv[$i] = "$argv[$i]:as/'/__$$__/" 
        set argv[$i] = "$argv[$i]:as/__$$__/'\''/" 
        set argv[$i] = "'$argv[$i]'" 
        @ i++ 
    end 
    eval set argv = \($argv:q\) 
    while (1) 
        switch ($1:q) 
        case -c: 
            set ti = $2:q 
            shift 
            shift 
            breaksw 
        case -e: 
            set te = $2:q 
            shift 
            shift 
            breaksw 
        case --: 
            shift 
            break 
        default: 
            break 
        endsw 
    end 
    awk -v ti="$ti" -v te="$te" -v pattern="$1" ' \
        gsub(pattern, "\033[" ti "m&\033[" te "m")||1 \
    ' /tmp/colorize.$__ 
    rm -f /tmp/colorize.$__ 

Oh my, is it glorious. The fact that it exhibits bullet-proof argument processing despite forking to getopt is a testament to CSH's support for compound strings which we create prior to the evaluation of getopt. If csh/tcsh did not support both eval and compound strings, I could not fully support immutable options. For example:

colorize -c '31;1"abc   xyz' 123

Two very difficult things to support when you have to pass arguments outside of the shell namespace, doubly-so when using eval. The goal here would be to, in such conditions:

The above alias achieves preservation of such complex strings passed-in as arguments and transforms them into compound strings (they themselves potentially once being compound strings before we got them) before we pass them into eval for getopt processing.

So what do these funny arguments look like when they are converted? Let's. See. Those compound strings:

'31;1"abc   xyz' becomes '31;1"abc   xyz'

In the case of the double-quote embedded in the string, there is no change (simply quoting the value with single-quotes is enough to preserve the value as-is).

In the case of the single-quote embedded in the string, the single-quote becomes a series of 4 characters: single-quote, backslash, single-quote, single-quote

This has the net effect of terminating the previous single-quote prematurely, causing a transition from singly-quoted text to bare (not-quoted) text, wherein we (without space, immediately adjoining) lay down an escaped single-quote (backslash-single-quote sequence of two characters in the middle of the 4 that we lay down for each single-quote we replace in the string value), and then finally another single-quote to resume the string in singly-quoted fashion.

So, thusly explained, the compound-string in this case is the combination of immediately-adjacent strings:

'31;1' (singly-quoted) \' (bare; not-quoted) 'abc xyz'

When interpolated by eval, the argument becomes what the value was prior to our munging.

Last but not least, the way in which we de-interpolate (aka "escape") before the eval to getopt is by way of:

$varname:as/find/replace/

Syntax which allows you to find/replace globally all instances of find with replace in $varname

freebsdfrau commented 4 years ago

I spent some time on unified declarations for disparate namespaces.

I think this is working quite well ...

setenv alias_getvar 'eval eval "set \!:2 = "\\\"\\\$"\!:1:q"\\\";:'
quietly unalias getvar
alias getvar $alias_getvar:q

unset alias_shell_escape
setenv alias_shell_escape ' \
    set __var = $__argv[2] \
    set __value = $__argv[1]:q \
    set __value = "$__value:as/'\''/__$$__/" \
    set __value = "$__value:as/__$$__/'\'\\\'\''/" \
    eval set $__var = \"'\\\''\$__value'\\\''\" \
'
quietly unalias shell_escape
alias shell_escape "set __argv = (\!*); "$alias_shell_escape:q

unset alias_colorize
setenv alias_colorize 'cat > /tmp/colorize.$$; setenv __ $$; echo '\''      \\
    alias shell_escape "set __argv = (\\\!*);"$alias_shell_escape:q     \\
    set ti = "31;1"                                                     \\
    set te = "39;22"                                                    \\
    set argc = $#argv                                                   \\
    set i = 1                                                           \\
    while ($i <= $argc)                                                 \\
        shell_escape "$argv[$i]" tmp                                \\
        set argv[$i] = "$tmp"                                       \\
        @ i++                                                       \\
    end                                                                 \\
    eval set argv = \($argv:q\)                                         \\
    while (1)                                                           \\
        switch ($1:q)                                               \\
        case -c:                                                    \\
            set ti = $2:q                                       \\
            shift                                               \\
            shift                                               \\
            breaksw                                             \\
        case -e:                                                    \\
            set te = $2:q                                       \\
            shift                                               \\
            shift                                               \\
            breaksw                                             \\
        case --:                                                    \\
            shift                                               \\
            break                                               \\
        default:                                                    \\
            break                                               \\
        endsw                                                       \\
    end                                                                 \\
    awk -v ti="$ti" -v te="$te" -v pattern="$1" '\'\\\'\''             \\\
        gsub(pattern, "\033[" ti "m&\033[" te "m")||1              \\\
    '\'\\\'\'' /tmp/colorize.$__                                        \\
    rm -f /tmp/colorize.$__                                             \\
'\'' | $shell /dev/stdin'
#'\'' | cat;:' # Use this line to see what $shell will execute
quietly unalias colorize
alias colorize $alias_colorize:q

The getvar alias has some problems being called from another alias, but works well in a non-alias context and in the forked context (piping into a tcsh sub-shell via alias). We'll keep this guy around to make it handy to perform variable indirection (example: set a = b; set b = c; getvar $a c; results in c = c because $a resolves to b and getvar puts the value of $b into c).

The shell_escape alias is going to be very important. It allows me to simplify the process of escaping so that we can properly handle arguments in certain cases without mangling them.

The colorize alias uses the shell_escape alias (despite the fact that colorize operates in a segregated namespace). This is starting to feel very much like we have functions at our disposal and can build upon that to tackle the bigger jobs like ssh-agent-dup(), loadkeys(), etc. -- at least one of which that needs porting, relies upon colorize to highlight keys that have been successfully loaded (I believe that is loadkeys()).

freebsdfrau commented 4 years ago

Make sure you're sitting down for this one ...

unset alias_function
set alias_function = ':                                                      \
    set __name = $argv_function[1]                                       \
    set __body = $argv_function[2]:q                                     \
    set __argv = argv_$__name                                            \
    unalias $__name                                                      \
    eval alias $__name "'\''set $__argv = (\\\!*);'\''"\$__body:q        \
    setenv alias_$__name $__body:q                                       \
;:'
quietly unalias function
alias function "set argv_function = (\!*); "$alias_function:q

function sfunction ' \
    set __name = $argv_sfunction[1]                                      \
    set __body = $argv_sfunction[2]:q                                    \
    set __argv = argv_$__name                                            \
    set __alias = alias_$__name                                          \
    set __interp = "$shell /dev/stdin"                                   \
    setenv $__alias $__body:q                                            \
    unalias $__name                                                      \
    eval alias $__name "'\''echo "\$"${__alias}:q | $__interp'\''"       \
'

function pfunction ' \
    set __name = $argv_pfunction[1]                                      \
    set __pre = $argv_pfunction[2]:q                                     \
    set __body = $argv_pfunction[3]:q                                    \
    set __argv = argv_$__name                                            \
    set __alias = alias_$__name                                          \
    set __interp = "$shell /dev/stdin"                                   \
    setenv $__alias $__body:q                                            \
    unalias $__name                                                      \
    alias $__name "$__pre; echo "\$"${__alias}:q | $__interp"            \
'

function shell_escape '                                                      \
    set __var = $argv_shell_escape[2]                                    \
    set __value = $argv_shell_escape[1]:q                                \
    set __value = "$__value:as/'\''/__$$__/"                             \
    set __value = "$__value:as/__$$__/'\'\\\'\''/"                       \
    eval set $__var = \"'\\\''\$__value'\\\''\"                          \
'

function getfunc '                                                           \
    set __fname = $argv_getfunc[1]                                       \
    set __fbody = $argv_getfunc[2]:q                                     \
    set __fargv = argv_$__fname                                          \
    eval alias $__fname "'\''set $__fargv = (\\\!*);'\''"\$__fbody:q     \
'

function getvar \
    'eval eval "set $argv_getvar[2] = "\\\"\\\$"$argv_getvar[1]"\\\";:'

sfunction getvar_test ' \
    alias getfunc "set argv_getfunc = (\\!*);"$alias_getfunc:q           \
    getfunc getvar $alias_getvar:q                                       \
    unset a b c                                                          \
    set a = "abc'\''123   xyz"                                           \
    set b = a                                                            \
    getvar $b c                                                          \
    echo "a=[$a]"                                                        \
    echo "b=[$b]"                                                        \
    echo "c=[$c]"                                                        \
'

pfunction colorize 'cat > /tmp/colorize.$$; setenv __ $$' '                  \
    alias getfunc "set argv_getfunc = (\\!*);"$alias_getfunc:q           \
    getfunc shell_escape $alias_shell_escape:q                           \
    set ti = "31;1"                                                      \
    set te = "39;22"                                                     \
    set argc = $#argv                                                    \
    set i = 1                                                            \
    while ($i <= $argc)                                                  \
        shell_escape "$argv[$i]" tmp                                 \
        set argv[$i] = "$tmp"                                        \
        @ i++                                                        \
    end                                                                  \
    eval set argv = \($argv:q\)                                          \
    while (1)                                                            \
        switch ($1:q)                                                \
        case -c:                                                     \
            set ti = $2:q                                        \
            shift                                                \
            shift                                                \
            breaksw                                              \
        case -e:                                                     \
            set te = $2:q                                        \
            shift                                                \
            shift                                                \
            breaksw                                              \
        case --:                                                     \
            shift                                                \
            break                                                \
        default:                                                     \
            break                                                \
        endsw                                                        \
    end                                                                  \
    awk -v ti="$ti" -v te="$te" -v pattern="$1" '\''                    \\
        gsub(pattern, "\033[" ti "m&\033[" te "m")||1               \\
    '\'' /tmp/colorize.$__                                               \
    rm -f /tmp/colorize.$__                                              \
'
freebsdfrau commented 4 years ago

I had almost entirely ported the openkey function when I hit an impassable brick wall.

The fact that neither an sfunction nor pfunction (as I have defined them above) is capable of accessing stdin (because the code is running as part of a tcsh which had consumed the script itself off of stdin) meant that it was not possible to do things like prompt for user input, etc.

I had a long hard think, going back to the original problem at the top that this issue is dedicated too ... if we are going to use aliases pointed at /bin/sh how do we "import" functions defined in the CSH namespace into the forked /bin/sh (assuming they are defined as /bin/sh routines).

I re-used some of the logic in the *function aliases wherein I stuff the code into the environment and then pull it back out at the time the function is called.

However, I had another problem that I wanted to solve too ...

I did not want to actually export the code into the environment (using setenv instead of set; wherein the undesired results of using setenv are that every forked executable can see the exported code and that the exported data counts toward your ulimit data size -- export too much and you won't be able to fork programs).

So with this dump, I am actually quite happy with the progress and the security.

Now, when defining aliases that are based off of /bin/sh routines (using a new alias called "shfunction"), the shell code is stuffed into the current namespace, but you can optionally define any number of co-routines that are to be shoved into the environment using an adhoc-export for the one-time invocation. The shell code picks up the adhoc variables and evaluates their code to define the function in its own namespace and when the program exits, the environment is left clean. This way, only the programs that we want to get the code via the environment receive said code.

So, behold, ... we are now just 4 functions away from completion.

# -*- tab-width:  4 -*- ;; Emacs
# vi: set tabstop=8     :: Vi/ViM
############################################################ IDENT(1)
#
# $Title: csh(1) semi-subroutine file $
# $Copyright: 2015-2019 Devin Teske. All rights reserved. $
# $FrauBSD: secure_thumb/etc/ssh.csh 2019-09-12 00:27:36 +0430 kfvahedi $
#
############################################################ INFORMATION
#
# Add to .cshrc:
#       source ~/etc/ssh.csh
#
############################################################ GLOBALS

#
# Global exit status variables
#
setenv SUCCESS 0
setenv FAILURE 1

#
# Are we running interactively?
#
set interactive = 0
if ( $?prompt ) set interactive = 1

#
# OS Specifics
# NB: Requires uname(1) -- from base system
#
if ( ! $?UNAME_s ) setenv UNAME_s `uname -s`

#
# For dialog(1) and Xdialog(1) menus -- mainly cvspicker in FUNCTIONS below
#
set DIALOG_MENU_TAGS = "123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"

#
# Default directory to store dialog(1) and Xdialog(1) temporary files
#
if ( ! $?DIALOG_TMPDIR ) set DIALOG_TMPDIR = "/tmp"

############################################################ ALIASES

unalias quietly >& /dev/null
alias quietly '\!* >& /dev/null'

quietly unalias have
alias have 'which \!* >& /dev/null'

quietly unalias eval2
alias eval2 'echo \!*; eval \!*'

# ssh-agent [ssh-agent options]
#
# Override ``ssh-agent'' to call an alias (you can always call the real binary
# by executing /usr/bin/ssh-agent) that launches a background ssh-agent that
# times-out in 30 minutes.
#
# Will evaluate the output of /usr/bin/ssh-agent (the real ssh-agent) called
# with either a known-secure set of arguments (if none are provided) or the
# unmodified arguments to this alias.
#
# Purpose is to prevent memorizing something like ``eval "`ssh-agent ...`"''
# but instead simply ``ssh-agent [...]''.
#
# This allows you to, for example:
#
#   ssh-agent
#   : do some ssh-add
#   : do some commits
#   ssh-agent -k
#   : or instead of ``ssh-agent -k'' just wait 30m for it to die
#
# NB: Requires ssh-agent -- from base system
#
quietly unalias ssh-agent
alias ssh-agent 'eval `ssh-agent -c -t 1800 \!*`'

# function $name $code
#
# Define a ``function'' that runs in the current namespace.
#
# NB: Evaluated context (using `eval') may not merge namespace immediately.
# NB: Commands considered unsafe by the shell may not work in this context.
# NB: Using builtins such as if, while, switch, and others will invoke an adhoc
# parser that appends to the history.
#
set alias_function = '                                                       \
    set __name = $argv_function[1]                                       \
    set __body = $argv_function[2]:q                                     \
    set __var  = "$__name:as/-/_/"                                       \
    set __argv = argv_$__var                                             \
    set __alias = alias_$__var                                           \
    set __body = "set ALIASNAME = $__name; "$__body:q                    \
    eval alias $__name "'\''set $__argv = (\\\!*);'\''"\$__body:q        \
    unset $__alias                                                       \
    set $__alias = $__body:q                                             \
'
quietly unalias function
alias function "set argv_function = (\!*); "$alias_function:q

############################################################ FUNCTIONS

# shfunction $name $code
#
# Define a ``function'' that runs under /bin/sh.
# NB: There must be a literal newline at the end.
# NB: No alias is created if one already exists.
#
quietly unalias shfunction
function shfunction '                                                        \
    set __name = $argv_shfunction[1]                                     \
    set __argc = $#argv_shfunction                                       \
    @ __argc--                                                           \
    set __penv = ($argv_shfunction[2-$__argc]:q)                         \
    @ __argc++                                                           \
    set __body = $argv_shfunction[$__argc]:q                             \
    set __var = "$__name:as/-/_/"                                        \
    set __alias = shalias_$__var                                         \
    set __func = shfunc_$__var                                           \
    set __interp = "env $__penv:q /bin/sh -c "\"\$"${__alias}:q"\"       \
    set __body = "$__name(){ local FUNCNAME=$__name; $__body:q }"        \
    set $__func = $__body:q                                              \
    set $__alias = $__body:q\;\ $__name\ \"\$@\"                         \
    have $__name || eval alias $__name "'\''$__interp -- /bin/sh'\''"    \
'

# quietly $cmd ...
#
# Execute /bin/sh $cmd while sending stdout and stderr to /dev/null.
#
shfunction quietly '                                                         \
    "$@" > /dev/null 2>&1                                                \
'

# have name
#
# Silently test for name as an available command, builtin, or other executable.
#
shfunction have '                                                            \
    type "$@" > /dev/null 2>&1                                           \
'

# eval2 $cmd ...
#
# Print $cmd on stdout before executing it. 
#
shfunction eval2 '                                                           \
    echo "$*"; eval "$@"                                                 \
'

# fprintf $fd $fmt [ $opts ... ]
#
# Like printf, except allows you to print to a specific file-descriptor. Useful
# for printing to stderr (fd=2) or some other known file-descriptor.
#
quietly unalias fprintf
shfunction fprintf '                                                         \
    fd=$1                                                                \
    [ $# -gt 1 ] || return ${FAILURE:-1}                                 \
    shift 1                                                              \
    printf "$@" >&$fd                                                    \
'

# eprintf $fmt [ $opts ... ]
#
# Like printf, except send output to stderr (fd=2).
#
quietly unalias eprintf
shfunction eprintf \
    '__fprintf=$shfunc_fprintf:q' \
'                                                                            \
    eval "$__fprintf"                                                    \
    fprintf 2 "$@"                                                       \
'

# ssh-agent-dup [-aqn]
#
# Connect to an open/active ssh-agent session available to the currently
# authenticated user. If more than one ssh-agent is available and the `-n' flag
# is not given, provide a menu list of open/active sessions available. Allows
# the user to quickly duplicate access to an ssh-agent launched in another
# interactive session on the same machine or for switching between agents.
#
# This allows you to, for example:
#
#   (in shell session A)
#   ssh-agent
#   (in shell session B)
#   ssh-agent-dup
#   (now both sessions A and B can use the same agent)
#
# No menu is presented if only a single agent session is available (the open
# session is duplicated for the active shell session). If more than one agent
# is available, a menu is presented. The menu choice becomes the active agent.
#
# If `-a' is present, list all readable agent sockets, not just those owned by
# the currently logged-in user.
#
# If `-q' is present, do not list agent nor keys.
#
# If `-n' is present, run non-interactively (good for scripts; pedantic).
#
# NB: Requires dialog_menutag() dialog_menutag2help() eval2() have()
#     -- from this file
# NB: Requires $DIALOG_TMPDIR $DIALOG_MENU_TAGS -- from this file
# NB: Requires awk(1) cat(1) grep(1) id(1) ls(1) ps(1) ssh-add(1) stat(1)
#     -- from base system
#
quietly unalias ssh-agent-dup
shfunction ssh-agent-dup '                                                   \
    : XXX TODO XXX                                                       \
'

# openkey [-hv]
#
# Mounts my F.o thumb
#
# NB: Requires eprintf() eval2() have() -- from this file
# NB: Requires awk(1) df(1) id(1) mount(8) -- from base system
#
quietly unalias openkey
shfunction openkey \
    '__fprintf=$shfunc_fprintf:q' \
    '__eprintf=$shfunc_eprintf:q' \
    '__eval2=$shfunc_eval2:q' \
    '__have=$shfunc_have:q' \
'                                                                            \
    eval "$__fprintf"                                                    \
    eval "$__eprintf"                                                    \
    eval "$__eval2"                                                      \
    eval "$__have"                                                       \
    [ "$UNAME_s" = "FreeBSD" ] ||                                        \
        { echo "$FUNCNAME: FreeBSD only!" >&2; return 1; }           \
    local OPTIND=1 OPTARG flag verbose= sudo=                            \
    while getopts hv flag; do                                            \
        case "$flag" in                                              \
        v) verbose=1 ;;                                              \
        *) local optfmt="\t%-4s %s\n"                                \
           eprintf "Usage: $FUNCNAME [-hv]\n"                        \
           eprintf "OPTIONS:\n"                                      \
           eprintf "$optfmt" "-h" \                                  \
                   "Print this text to stderr and return."           \
           eprintf "$optfmt" "-v" \                                  \
                   "Print verbose debugging information."            \
           return ${FAILURE:-1}                                      \
        esac                                                         \
    done                                                                 \
    shift $(( $OPTIND - 1 ))                                             \
    if [ "$( id -u )" != "0" ]; then                                     \
        if have sr; then                                             \
            sudo=sr                                              \
        elif have sudo; then                                         \
            sudo=sudo                                            \
        fi || {                                                      \
            eprintf "$FUNCNAME: not enough privileges\n"         \
            return ${FAILURE:-1}                                 \
        }                                                            \
    fi                                                                   \
    df -l /mnt | awk '\''                                               \\
        $NF == "/mnt" { exit found++ } END { exit \!found }         \\
    '\'' || ${verbose:+eval2} $sudo mount /mnt || return                 \
    local nfail=3                                                        \
    while [ $nfail -gt 0 ]; do                                           \
        /mnt/mount.sh -d${verbose:+v} && break                       \
        nfail=$(( $nfail - 1 ))                                      \
    done                                                                 \
    [ "$verbose" ] && df -hT /mnt/* | ( awk '\''                        \\
        NR == 1 { print > "/dev/stderr"; next } 1                   \\
    '\'' | sort -u ) 2>&1                                                \
    return ${SUCCESS:-0}                                                 \
'

# closekey [-ehv]
#
# Unmounts my F.o thumb
#
# NB: Requires eprintf() have() -- from this file
# NB: Requires awk(1) camcontrol(8) df(1) id(1) umount(8) -- from base system
#
quietly unalias closekey
shfunction closekey '                                                        \
    : XXX TODO XXX                                                       \
'

# loadkeys [OPTIONS] [key ...]
#
# Load my SSH private keys from my F.o thumb. The `key' argument is to the
# SSH private keyfile's suffix; in example, "sf" for "id_rsa.sf" or "f.o" for
# "id_rsa.f.o" or "gh" for "id_rsa.gh".
#
# For example, to load the Sourceforge.net key, F.o key, and Github key:
#   loadkeys sf f.o gh
#
# OPTIONS:
#   -c           Close USB media after loading keys.
#   -e           Close and eject USB media after loading keys.
#   -h           Print this text to stderr and return.
#   -k           Kill running ssh-agent(1) and launch new one.
#   -n           Start a new ssh-agent, ignoring current one.
#   -t timeout   Timeout. Only used if starting ssh-agent(1).
#   -v           Print verbose debugging information.
#
# NB: Requires closekey() colorize() eprintf() openkey() ssh-agent() quietly()
#     ssh-agent-dup() -- from this file
# NB: Requires awk(1) kill(1) ps(1) ssh-add(1) -- from base system
#
quietly unalias loadkeys
shfunction loadkeys '                                                        \
    : XXX TODO XXX                                                       \
'

# unloadkeys [OPTIONS] [key ...]
#
# Unload my SSH private keys from my F.o thumb. The `key' argument is to the
# SSH private keyfile's suffix; in example, "sf" for "id_rsa.sf" or "f.o" for
# "id_rsa.f.o" or "gh" for "id_rsa.gh".
#
# For example, to unload the Sourceforge.net key, F.o key, and Github key:
#   unloadkeys sf f.o gh
#
# OPTIONS:
#   -a           Unload all keys.
#   -c           Close USB media after unloading keys.
#   -e           Close and eject USB media after unloading keys.
#   -h           Print this text to stderr and return.
#   -v           Print verbose debugging information.
#
# NB: Requires closekey() colorize() eprintf() openkey() quietly()
#     -- from this file
# NB: Requires awk(1) ps(1) ssh-add(1) -- from base system
#
quietly unalias unloadkeys
shfunction unloadkeys '                                                      \
    : XXX TODO XXX                                                       \
'

# dialog_menutag
#
# Obtain the menutag chosen by the user from the most recently displayed
# dialog(1) menu and clean up any temporary files.
#
# NB: Requires quietly() -- from this file
# NB: Requires $DIALOG_TMPDIR -- from this file
# NB: Requires rm(1) -- from base system
#
quietly unalias dialog_menutag
shfunction dialog_menutag \
    '__quietly=$shfunc_quietly:q' \
'                                                                            \
    eval "$__quietly"                                                    \
    local tmpfile="$DIALOG_TMPDIR/dialog.menu.$$"                        \
                                                                             \
    [ -f "$tmpfile" ] || return ${FAILURE:-1}                            \
                                                                             \
    cat "$tmpfile" 2> /dev/null                                          \
    quietly rm -f "$tmpfile"                                             \
                                                                             \
    return ${SUCCESS:-0}                                                 \
'

# dialog_menutag2help $tag_chosen $tag1 $item1 $help1 \
#                                 $tag2 $item2 $help2
#
# To use the `--menu' option of dialog(1) with the `--item-help' option, you
# must pass an ordered list of tag/item/help triplets on the command-line. When
# the user selects a menu option the tag for that item is printed to stderr.
#
# This function allows you to dereference the tag chosen by the user back into
# the help associated with said tag (item is discarded/ignored).
#
# Pass the tag chosen by the user as the first argument, followed by the
# ordered list of tag/item/help triplets (HINT: use the same tag/item/help list
# as was passed to dialog(1) for consistency).
#
# If the tag cannot be found, NULL is returned.
#
quietly unalias dialog_menutag2help
shfunction dialog_menutag2help '                                             \
    local tag="$1" tagn help                                             \
    shift 1 # tag                                                        \
                                                                             \
    while [ $# -gt 0 ]; do                                               \
        tagn="$1"                                                    \
        help="$3"                                                    \
        shift 3 # tagn/item/help                                     \
                                                                             \
        if [ "$tag" = "$tagn" ]; then                                \
            echo "$help"                                         \
            return ${SUCCESS:-0}                                 \
        fi                                                           \
    done                                                                 \
    return ${FAILURE:-1}                                                 \
'

# colorize [-c ANSI] [-e ANSI] pattern
#
# Colorize text matching pattern with ANSI sequence (default is `31;1' for red-
# bold). Non-matching lines are printed as-is.
#
# NB: Requires awk(1) -- from base system
#
quietly unalias colorize
shfunction colorize '                                                        \
    local OPTIND=1 OPTARG flag                                           \
    local ti=                                                            \
    local te=                                                            \
    while getopts c:e: flag; do                                          \
        case "$flag" in                                              \
        c) ti="$OPTARG" ;;                                           \
        e) te="$OPTARG" ;;                                           \
        esac                                                         \
    done                                                                 \
    shift $(( $OPTIND - 1 ))                                             \
                                                                             \
    awk -v ti="${ti:-31;1}" -v te="${te:-39;22}" -v pattern="$1" '\''    \
        gsub(pattern, "\033[" ti "m&\033[" te "m")||1                \
    '\'' # END-QUOTE                                                     \
'

############################################################ MISCELLANEOUS

#
# Override the default password prompt for sudo(8). This helps differentiate
# the sudo(8) password prompt from others such as su(1), ssh(1), and login(1).
#
setenv SUDO_PROMPT '[sudo] Password:'

#
# Quietly attach to running ssh-agent(1) unless agent already given
#
if ( ! $?SSH_AUTH_SOCK ) then
    if ( "$interactive" ) then
        ssh-agent-dup
    else
        quietly ssh-agent-dup -n || :
    endif
endif

################################################################################
# END
################################################################################
freebsdfrau commented 4 years ago

All we should have to do now is the following functions:

The ssh-agent-dup function will be the most challenging as it has to hoist a new value for SSH_AUTH_SOCK and SSH_AGENT_PID up from its /bin/sh namespace back to the CSH parent namespace. Normally, a child cannot set variables in a parent namespace, but we're going to use an evaluated expression to cull the stdout from /bin/sh which will eschew a "setenv" command that, when evaluated by the culling-parent, will have the desired effect of changing the value in the current environment (exported so that the likes of ssh, ssh-add, and friends can pick up on them).

closekey will be the easiest as it does not have to export any variables up to the parent.

Both loadkeys and unloadkeys will just use an inherited SSH_AUTH_SOCK to speak to the agent, and so those too will be easy, though I think loadkeys does some work with SSH_AGENT_PID.

freebsdfrau commented 4 years ago

Awesome!