pastebin - collaborative debugging tool
rovema.kpaste.net RSS


sshnfs - ssh with nfs forwarding
Posted by Anonymous on Mon 27th Feb 2023 13:58
raw | new post
view followups (newest first): sshnfs - ssh with nfs forwarding by Anonymous
modification of post by Anonymous (view diff)

  1. #!/usr/bin/ksh93
  2. #
  3. # sshnfs - remote login client with NFSv4 forwarding
  4. #
  5. # Example usage:
  6. # $ ksh sshnfs.ksh -o NFSURL=nfs://localhost/export/home/rmainz root@10.49.20.207 #
  7. # $ ksh sshnfs.ksh -o NFSURL=nfs://derfwpc5131/export/home/rmainz -o NFSJumphost=rmainz@derfwpc5131,roland.mainz@derfwnb8353 -J rmainz@derfwpc5131,roland.mainz@derfwnb8353 root@10.49.20.207
  8. # $ ksh sshnfs.ksh -o NFSURL=nfs://derfwpc5131/export/home/rmainz target@fe80::d6f5:27ff:fe2b:8588%enp2s0
  9. #
  10. # Written by Roland Mainz <roland.mainz@nrubsig.org>
  11. #
  12.  
  13. #    
  14. # simple netstat -n parser
  15. #    
  16. function netstat_list_connections
  17. {
  18.         set -o nounset
  19.         nameref data=$1
  20.        
  21.         compound out=( typeset stdout stderr ; integer res )
  22.        
  23.         out.stderr="${ { out.stdout="${ LC_ALL='POSIX' PATH='/usr/bin:/bin' netstat -a -n ; (( out.res=$? )) ; }" ; } 2>&1 ; }"
  24.         if (( out.res != 0 )) || [[ ${out.stderr} != '' ]] ; then
  25.                 return 1
  26.         fi
  27.  
  28.         typeset -a data.connections
  29.         typeset l
  30.         integer dci=0 # data.connections array index
  31.  
  32.         while read l ; do
  33.                 leftover="${l/~(Elrx)
  34.                 (?: # non-capturing group
  35.                         #
  36.                         # regex group for tcp,udp
  37.                         #
  38.                         (tcp|tcp6|udp|udp6|raw6)        # Proto
  39.                         [[:space:]]+
  40.                         ([[:digit:]]+)                  # Recv-Q
  41.                         [[:space:]]+
  42.                         ([[:digit:]]+)                  # Send-Q
  43.                         [[:space:]]+
  44.                         ([^[:space:]]+)                 # Local Address
  45.                         [[:space:]]+
  46.                         ([^[:space:]]+)                 # Foreign Address
  47.                         (?:
  48.                                 |
  49.                                 [[:space:]]+
  50.                                 ([^[:space:]]*?)        # State (Optional)
  51.                         )
  52.                 |
  53.                         #
  54.                         # regex for unix
  55.                         #
  56.                         (unix)                          # Proto
  57.                         [[:space:]]+
  58.                         ([[:digit:]]+)                  # RefCnt
  59.                         [[:space:]]+
  60.                         (\[.+?\])                       # Flags
  61.                         [[:space:]]+
  62.                         ([^[:space:]]+)                 # Type
  63.                         [[:space:]]+
  64.                         ([^[:space:]]*?)                # State (optional)
  65.                         [[:space:]]+
  66.                         ([[:digit:]]+)                  # I-Node
  67.                         (?:
  68.                                 |
  69.                                 [[:space:]]+
  70.                                 ([^[:space:]]+)         # Path (optional)
  71.                         )
  72.                 )
  73.                         /X}"
  74.        
  75.                 # If the regex above did not match then .sh.match
  76.                 # remains untouched, so we might see data from the
  77.                 # previous round.
  78.                 # So we check the "leftover" var whether it just
  79.                 # contains the dummy value of "X" to indicate a
  80.                 # successful regex match
  81.                 if [[ "$leftover" == 'X' ]] ; then
  82.                         #print -v .sh.match
  83.                        
  84.                         if [[ "${.sh.match[1]-}" != '' ]] ; then
  85.                                 nameref dcn=data.connections[$dci]
  86.  
  87.                                 typeset dcn.proto="${.sh.match[1]}"
  88.                                 typeset dcn.recv_q="${.sh.match[2]}"
  89.                                 typeset dcn.send_q="${.sh.match[3]}"
  90.                                 typeset dcn.local_address="${.sh.match[4]}"
  91.                                 typeset dcn.foreign_address="${.sh.match[5]}"
  92.                                 typeset dcn.state="${.sh.match[6]}"
  93.                                 ((dci++))
  94.                         elif [[ "${.sh.match[7]-}" != '' ]] ; then
  95.                                 nameref dcn=data.connections[$dci]
  96.  
  97.                                 typeset dcn.proto="${.sh.match[7]}"
  98.                                 typeset dcn.refcnt="${.sh.match[8]}"
  99.                                 typeset dcn.flags="${.sh.match[9]}"
  100.                                 typeset dcn.type="${.sh.match[10]}"
  101.                                 [[ "${.sh.match[11]}" != '' ]] && typeset dcn.state="${.sh.match[11]}"
  102.                                 typeset dcn.inode="${.sh.match[12]}"
  103.                                 [[ "${.sh.match[13]}" != '' ]] && typeset dcn.path="${.sh.match[13]}"
  104.                                 ((dci++))
  105.                         fi
  106.                 else
  107.                         true
  108.                         #printf $"leftover=%q\n" "${leftover}"
  109.                 fi
  110.         done <<<"${out.stdout}"
  111.        
  112.         return 0
  113. }        
  114.  
  115. function netstat_list_active_local_tcp_connections
  116. {
  117.         set -o nounset
  118.         nameref ar=$1
  119.         compound c
  120.         integer port
  121.         integer i
  122.  
  123.         netstat_list_connections c || return 1
  124.         #print -v c
  125.        
  126.         [[ -v ar ]] || integer -a ar
  127.  
  128.         for i in "${!c.connections[@]}" ; do
  129.                 nameref n=c.connections[$i]
  130.                
  131.                 # look for only for TCP connections which match
  132.                 # 127.0.*.* or IPv6 ::1 for localhost
  133.                 # 0.0.0.0 or IPv6 :: for all addresses (e.g. servers)
  134.                 if [[ "${n.proto}" == ~(El)tcp && \
  135.                         "${n.local_address}" == ~(Elr)((127\.0\..+|::1)|(::|0\.0\.0\.0|)):[[:digit:]]+ ]] ; then
  136.  
  137.                         port="${n.local_address##*:}"
  138.                         #printf $"port = %d\n" port
  139.  
  140.                         (( ar[port]=1 ))
  141.                 fi
  142.         done
  143.  
  144.         return 0
  145. }
  146.  
  147. function netstat_find_next_free_local_tcp_port
  148. {
  149.         set -o nounset
  150.         compound c=( integer -a ar )
  151.         nameref ret_free_port=$1
  152.         integer start_port
  153.         integer end_port
  154.         integer i
  155.  
  156.         netstat_list_active_local_tcp_connections c.ar || return 1
  157.  
  158.         #print -v c
  159.  
  160.         (( start_port=$2 ))
  161.         if (( $# > 2 )) ; then
  162.                 (( end_port=$3 ))
  163.         else
  164.                 (( end_port=65535 ))
  165.         fi
  166.  
  167.         for ((i=start_port ; i < end_port ; i++ )) ; do
  168.                 if [[ ! -v c.ar[i] ]] ; then
  169.                         (( ret_free_port=i ))
  170.                         return 0
  171.                 fi
  172.         done
  173.        
  174.         return 1
  175. }
  176.  
  177. #
  178. # parse url
  179. #
  180. # returns:
  181. # data.protocol
  182. # data.host
  183. # data.port (optional)
  184. # data.path
  185. #
  186. function parse_url
  187. {
  188.         typeset url="$2"
  189.         typeset leftover
  190.         nameref data="$1"
  191.        
  192.         # ~(E) is POSIX extended regular expression matching (instead of
  193.         # shell pattern)
  194.         leftover="${url//~(Elr)(.+?):\/\/(.+?)(?:|:([[:digit:]]+))(?:\/(.*?))?/X}"
  195.        
  196.         # All parsed data should be captured via eregex in .sh.match - if
  197.         # there is anything left (except the 'X') then the input string did not
  198.         # properly match the eregex
  199.         [[ "$leftover" == 'X' ]] || { print -u2 -f $"%s: Parser error\n" "$0" ; return 1 ; }
  200.  
  201.         data.protocol="${.sh.match[1]}"
  202.         data.host="${.sh.match[2]}"
  203.         # bug: should be [[ -v .sh.match[3] }}, but ksh93u has bugs
  204.         [[ "${.sh.match[3]}" != '' ]] && integer data.port="${.sh.match[3]}"
  205.         data.path="${.sh.match[4]}"
  206.  
  207.         return 0
  208. }
  209.  
  210. function parse_nfs_url
  211. {
  212.         typeset url="$2"
  213.         nameref data="$1"
  214.        
  215.         parse_url data "$url" || return 1
  216.        
  217.         [[ "${data.protocol}" == 'nfs' ]]       || { print -u2 -f $"%s: Not a NFS url\n" "$0" ; return 1 ; }
  218.         [[ "${data.host}" != '' ]]              || { print -u2 -f $"%s: NFS hostname missing\n" "$0" ; return 1 ; }
  219.         [[ "${data.path}" != '' ]]              || { print -u2 -f $"%s: NFS path missing\n" "$0" ; return 1 ; }
  220.        
  221.         if [[ ! -v data.port ]] ; then
  222.                 # use # default NFSv4 TCP port number (see
  223.                 # $ getent services nfs #)
  224.                 integer data.port=2049
  225.         fi
  226.  
  227.         return 0
  228. }
  229.  
  230.  
  231. function main
  232. {
  233.         set -o nounset
  234.         typeset mydebug=false # fixme: should be "bool" for ksh93v
  235.         integer i
  236.         integer retval
  237.         compound c=(
  238.                 # port on THIS machine
  239.                 integer local_forward_port
  240.  
  241.                 # TCP port on destination machine where we forward the
  242.                 # NFS port from the server
  243.                 integer destination_nfs_port=33049
  244.  
  245.                 typeset ssh_control_socket_name="/tmp/sshnfs_ssh-control-socket_logname${LOGNAME}_ppid${PPID}_pid$$"
  246.         )
  247.  
  248.         #
  249.         # find free local forwarding port...
  250.         #
  251.         (( i=34049 ))
  252.         netstat_find_next_free_local_tcp_port c.local_forward_port $i
  253.  
  254.         #
  255.         # ... and adjust c.destination_nfs_port by the same offset
  256.         # we do that so that multiple sshweb.ksh logins to the same
  257.         # machine do try to use the same ports on that machine
  258.         #
  259.         (( c.destination_nfs_port += c.local_forward_port-i ))
  260.         ${mydebug} && printf $"debug: c.local_forward_port=%d, c.destination_nfs_port=%d\n" \
  261.                 c.local_forward_port \
  262.                 c.destination_nfs_port
  263.  
  264.         typeset c.args=( "$@" )
  265.  
  266.         for ((i=0 ; i < ${#c.args[@]} ; i++)) ; do
  267.                 if [[ "${c.args[i]}" == '-o' ]] ; then
  268.                         case "${c.args[i+1]-}" in
  269.                                 'NFSURL='*)
  270.                                         unset c.nfs_server
  271.                                         compound c.nfs_server
  272.                                         typeset c.url="${c.args[i+1]#NFSURL=}"
  273.                                         parse_nfs_url c.nfs_server "${c.url}" || return 1
  274.                                         unset c.args[$i] c.args[$((i+1))]
  275.                                         ((i++))
  276.                                         ;;
  277.                                 'NFSJumphost='*)
  278.                                         [[ ! -v c.ssh_jumphost_args ]] && typeset -a c.ssh_jumphost_args
  279.                                         c.ssh_jumphost_args+=( "-J" "${c.args[i+1]#NFSJumphost=}" )
  280.                                         unset c.args[$i] c.args[$((i+1))]
  281.                                         ((i++))
  282.                                         ;;
  283.                         esac
  284.                 fi
  285.         done
  286.  
  287.         if [[ -v c.nfs_server ]] ; then
  288.                 # Forward NFS port from server to local machine
  289.                 # Notes:
  290.                 # - We use $ ssh -M ... # here as a way to terminate the port
  291.                 # forwarding process later using "-O exit" without the need
  292.                 # for a pid
  293.                 print -u2 -f $"# Please enter the login data for NFS server (%s):\n" \
  294.                         "root@${c.nfs_server.host}"
  295.                 ssh \
  296.                         -L "${c.local_forward_port}:localhost:${c.nfs_server.port}" \
  297.                         -M -S "${c.ssh_control_socket_name}" \
  298.                         -N \
  299.                         -f -o 'ExitOnForwardFailure=yes' \
  300.                         "${c.ssh_jumphost_args[@]}" \
  301.                         "root@${c.nfs_server.host}"
  302.                 if (( $? != 0 )) ; then
  303.                         print -u2 -f $"%s: NFS forwarding ssh failed with error code %d\n" "$0" $?
  304.                         return 1
  305.                 fi
  306.  
  307.                 # debug
  308.                 ${mydebug} && ssh -S "${c.ssh_control_socket_name}" -O 'check' "root@${c.nfs_server.host}"
  309.  
  310.                 print -u2 -f $"# Use this to mount the directory:\n"
  311.                 print -u2 -f $"# $ mkdir /mnt_nfs\n"
  312.                 print -u2 -f $"# $ mount -vvv -t nfs -o vers=4,port=%d localhost:/%s /mnt_nfs\n" \
  313.                         c.destination_nfs_port \
  314.                         "${c.nfs_server.path}"
  315.  
  316.                 # add NFS forwarding options to main ssh argument list
  317.                 c.args=(
  318.                         '-R' "${c.destination_nfs_port}:localhost:${c.local_forward_port}"
  319.                         '-o' 'ExitOnForwardFailure=yes'
  320.                         "${c.args[@]}"
  321.                 )
  322.         fi
  323.  
  324.         # debug: print application data (compound c)
  325.         ${mydebug} && print -v c
  326.  
  327.         print -u2 -f $"# ssh login data for destination machine:\n"
  328.         ssh "${c.args[@]}" ; (( retval=$? ))
  329.  
  330.         if [[ -v c.nfs_server ]] ; then
  331.                 ssh -S "${c.ssh_control_socket_name}" -O 'exit' "root@${c.nfs_server.host}"
  332.         fi
  333.  
  334.         wait
  335.  
  336.         return $retval
  337. }
  338.  
  339. main "$@"
  340. exit $?
  341.  
  342. # EOF.

Submit a correction or amendment below (click here to make a fresh posting)
After submitting an amendment, you'll be able to view the differences between the old and new posts easily.

Syntax highlighting:

To highlight particular lines, prefix each line with {%HIGHLIGHT}




All content is user-submitted.
The administrators of this site (kpaste.net) are not responsible for their content.
Abuse reports should be emailed to us at