pastebin - collaborative debugging tool
rovema.kpaste.net RSS


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