#compdef openvpn

dump_state()
{
    {
        echo "--- $words"
        echo $CURRENT
        echo "QIPREFIX: $QIPREFIX"
        echo "$IPREFIX-$PREFIX . ${SUFFIX}-${ISUFFIX}"
        echo "QISUFFIX: $QISUFFIX"

        echo "compstate:\tcontext $compstate[context]"
        echo "\tparameter: $compstate[parameter]"
    } >> $1
}

# -C
# mmc: to use "-> action", -C option is needed.

# fixme: local
_openvpn() {
    # A function calling _arguments with at least one action
    # containing a '->STRING' must therefore declare appropriate
    # local parameters:
    local context state state_descr line
    typeset -A opt_args

    if  [[ -f ${logfile::=${TMPDIR-/tmp}/state} ]]; then dump_state $logfile;else touch $logfile ;fi
    local -a keytypes
    keytypes=(
        'secret:Standard OpenVPN shared secret keys'
        'tls-crypt:Alias for secret'
        'tls-auth:Alias for secret'

        'auth-token:Key used for --auth-gen-token-key:'

        'tls-crypt-v2-server:TLS Crypt v2 Server key'

        'tls-crypt-v2-client:TLS Crypt v2 Client key'
    )
    # $keytypes

# fileroot
    local fileroot
    if (( words[(b:CURRENT-1:I)--cd] ))
    then
        fileroot=${words[$words[(I)--cd] + 1]}
        if [[ $fileroot[1] != '/' ]]; then
            fileroot=$PWD/$fileroot
        fi

    else
        : # fileroot=$PWD
    fi
    echo "found --cd directory $fileroot" >> $logfile
    local -a SSL_Library_information
    SSL_Library_information=(
        --show-ciphers
        --show-engines
        --show-digests
        --show-tls
        --show-curves
    )
    generating_options=(
        '--genkey+[require keytype]:keytype:->keytypes'
    )

    # todo: For these I need
    # P2MP client or server mode
    local -a tls_options

# TLS options

    if (( words[(b:CURRENT-1:I)--tls-(client|server)] ));
    then
        #

        echo "adding tls options" >> $logfile
        # case ${words[$words[(I)--format=*]]#--format=} in

        tls_options=(
            # OpenVPN initiates a TLS session over the `control' channel and uses it to exchange `cipher'
            # and `HMAC' keys to protect the data channel. TLS mode uses a robust reliability layer over the
            # UDP connection for all control channel communication, while the data channel, over which
            # encrypted tunnel data passes, is forwarded without any mediation. The result is the best of
            # both worlds: a fast data channel that forwards over UDP with only the overhead of encrypt,
            # decrypt, and HMAC functions, and a control channel that provides all of the security
            # features of TLS, including certificate-based authentication and `Diffie-Hellman' forward
            # secrecy.

            "--askpass[Get certificate password from console or \[file\] before we daemonize]::certificate-file:_files -W $fileroot"
            # btw. openssl -nodes means avoid pass-phrase!

            # {pem,
            "(--pkcs12)--ca[Certificate authority, root certificate]:pem file:_files -g '*.crt' -W $fileroot"
            "--capath[dir containing CAs and CRLs]:directory:_files -/ -W $fileroot"
            # notice certs_by_serial
            "--crl-verify[check Certificate Revocation List(CRL)]:pem file:_files -g '*.pem' -W $fileroot ::directory:_files -/"

            # after --tls-server
            "(--ecdh-curve)--dh[Diffie Hellman parameters in .pem]:file:_files -g '*.pem' -W $fileroot"

            # todo: list them by --show-curves
            "(--dh)--ecdh-curve[name]:curve:->curves"
            '--tls-groups[list of groups curves :-separated]:curves:'
            # X25519:secp256r1:X448:secp521r1:secp384r1
            '--hand-window[seconds limit/60]:number:'


            # I want these options only if --tls-server is present.
            # can I do it? ->action
            # so how does it relate to --ecdh-curve ?
            "(--ca --cert --key)--pkcs12:file:_files -W $fileroot"
            # Specify a PKCS #12 file containing local private key, local certificate, and root CA certificate. This option can be used instead of --ca, --cert, and --key.
            "(--pkcs12)--key:file:_files -g '*.key' -W $fileroot"

            # the certificate has embedded information. Here we match that info -- key usage and extended ku.
            '--remote-cert-eku[require extended key usage]:oid:'
            '--remote-cert-ku[bitmask encoded in hex]:keyUsage:'
            '--remote-cert-tls[require key usage, RFC3280 TLS rules]:type:(server client)'

            # DoS '--tls-auth'
            "--tls-auth[sign every TLS control channel packet with HMAC: file \[direction\]]: :_files -W $fileroot ::number:(0 1)"
            "(--tls-crypt-v2)--tls-crypt[Encrypt and authenticate all control channel packets]:keyfile:_files -g '*.key' -W $fileroot"
            # on tls-server one can use both! todo!
            "(--tls-crypt)--tls-crypt-v2[]:keyfile:_files -W $fileroot: ::option:(force-cookie allow-noncookie)"

            # scripts:
            '--tls-crypt-v2-verify[]:command:_files'

            # WARNING: No server certificate verification method has been enabled
            '--tls-cert-profile[allowed cryptographic algorithms for certificates]:profile:(insecure legacy preferred suiteb)'
            '(--tls-ciphersuites)--tls-cipher[list of allowable TLS ciphers :-separated]:cipher:->ciphers'
            '(--tls-cipher)--tls-ciphersuites[list of allowable TLS ciphers :-separated (tls 1.3)]:cipher:->ciphers'

            "--cert[Local peer signed certificate pem file]:file:_files -g '*.crt' -W $fileroot"
            "--extra-certs[file with concatenated certs]:file:_files -g '*.crt' -W $fileroot"

            # mmc: what?
            "--tls-exit[exit on TLS negotiation failure]"
            '--tls-timeout[Packet retransmit timeout on TLS control channel/2]:seconds:'
            #
            # Data channel packets (which carry encrypted tunnel data) are never acknowledged,
            # sequenced, or retransmitted by OpenVPN because the higher level network protocols
            # running on top of the tunnel such as TCP expect this role to be left to them.
            # mmc: if space :(1.0 1.1 1.2) ::  -> error! : command not found: (
            '--tls-version-min[version \[or-highest\]]:option:(1.0 1.1 1.2) ::option:(or-highest)'
            "--tls-export-cert[Store the certificates the clients use]:directory:_files -/ -W $fileroot"

            # deprecated:
            # --verify-hash
            # --peer-fingerprint
            '--verify-x509-name[X.509 name must be equal to name]:name: ::type:'
            '--x509-track'
            '--x509-username-field'
        )
    fi

# LOG
    local -a log_options
    log_options=(
        "--echo[params into log]"
        "--errors-to-stderr"
        "--log[including scripts, file truncated]:file:_files"
        "--log-append:file:_files"
        '--machine-readable-output'
        "--mute[at most n consecutive messages in the same category]:seconds:"
        '--mute-replay-warnings[common on Wifi]'
        '--suppress-timestamps'
        '--syslog[without daemon]:name:'
        '--verb[level: 0 fatal 1-4 normal  5 RW 6(NAT)-11 debug]:number:'
    )

# PROTOCOL
    local -a protocol_options
    protocol_options=(
        # Options in this section affect features available in the OpenVPN wire protocol.
        # Many of these options also define the encryption options of the data channel in
        # the OpenVPN wire protocol.  These options must be configured in a compatible way
        # between both the local and remote side.

        '--allow-compression[potentially dangerous]:mode:(asym yes no)'
        '--compress[deprecated]:alg:'

        '--cipher[ignored for TLS; deprecated]:alg:->ciphers'
        # --show-ciphers

        '--auth[Authenticate data & control channel packets using digest algorithm/SHA1]:alg:->digests'
        # AEAD cipher mod -> ignored! but not for tls-auth
        # encrypt-then-mac

        # In `static-key' encryption mode, the HMAC key is `included' in the key file
        # generated by --genkey.
        # In `TLS' mode, the HMAC key is dynamically generated and shared between peers
        # via the TLS control channel.
        # If OpenVPN receives a packet with a bad HMAC it will `drop' the packet. HMAC
        # usually adds 16 or 20 bytes per packet. Set alg=none to disable authentication.
        '--data-ciphers[Restrict the allowed ciphers, colon-separated]:cipher:->ciphers'
        '--data-ciphers-fallback[to connect with OLD versions]:cipher:->ciphers'
        '--tran-window[old key can live this many seconds after a new a key renegotiated/3600]:seconds:'

        '--key-directions[for --tls-auth and --secret]'
        '--secret[deprecated file direction]:file:_files ::direction:'
        )

# DATA CHANNEL RENEG.
    data_channel_renegotiation=(
        # When running OpenVPN in client/server mode, the data channel will use a separate
        # ephemeral encryption key which is rotated at regular intervals.
        '--reneg-bytes[after N bytes]:number:'
        '--reneg-pkts[after N packets]:number:'
        '--reneg-sec[seconds/3600]:seconds:'
    )

# CLIENT
    local -a client_options
    if (( words[(b:CURRENT-1:I)--(tls-client|client)] ))
    then
    echo "adding client options" >> $logfile

    ## This is if I'm client:
    # The client options are used when connecting to an OpenVPN server configured to use --server,
    # --server-bridge, or --mode server in its configuration.
    client_options=(
        '--allow-pull-fqdn[Allow client to pull DNS names from server for --ifconfig, --route, and --route-gateway]'
        #
        '--allow-recursive-routing[not drop incoming tun packets with same destination as host]'
        #
        '--auth-token[token for --client-connect?]:token:'
        '--auth-token-user[base64username]:username:'
        '--auth-user-pass[Authenticate with server using username/password \[file\]]::file:_file'
        '--auth-retry[type]:type:(none nointeract interact)'

        '--client-nat[pushable client option sets up stateless 1-1 NAT rule on packet addresses]:direction:(snat dnat) :netmask:'
        '--dns' #:(search-domains server resolve-domains dnssec transport sni)

        '--connect-retry[seconds -- between connection attempt, doubled/5]:seconds: ::maximum:'
        '--connect-retry-max[max/300s]:seconds:'
        '--connect-timeout[see --server-poll-timeout]:number seconds:'
        '--server-poll-timeout[N seconds before trying the next server/120]:seconds:'

        '--explicit-exit-notify[send peer exit notification if tunnel is restarted or OpenVPN process is exited]::number attempts:'
        '--inactive[exit if no data traffic on TUN for seconds \[bytes\]]:seconds: ::bytes:'
        '--proto-force[use only profiles of protocol: tcp udp]:protocol:(tcp udp)'

        '--pull[accept options pushed by the server, must be used to connect to  multi-client server]'
        # todo: only after pull?
        '--pull-filter[(accept|ignore|reject) text]:operation:(accept ignore reject):options:'
        '--push-peer-info'
        # always:
        # IV_VER=
        # IV_PLAT=[linux|solaris|openbsd|mac|netbsd|freebsd|win]
        # IV_GUI_VER
        # with `push-peer':
        # unique and persistent ID of the client.
        # IV_SSL
        # IV_HWADDR
        # UV_ generic Env Variables?

        '(--float)--remote[host \[port \[proto\]\]]:host:_host::port: ::proto: '
        # note: Since UDP is connectionless, connection failure is defined by the --ping and --ping-restart options.

        '--remote-random'
        '--remote-random-hostname[to prevent DNS caching]'
        # 0
        '--resolv-retry[n or infinite]:seconds:(infinite)'
        '--single-session[with --ping-exit or --inactive]'
        '--static-challenge[text echo]:text: :echo:(0 1)'

        '--http-proxy[host port authfile]'
        --http-proxy-option
        '--socks-proxy'
    )
    fi

# SERVER
    local -a server_options
    if (( words[(b:CURRENT-1:I)--(tls-server|server)] ))
    then
    echo "adding server options" >> $logfile

    server_options=(
        # fixme: :: means optional!
        # '--mode::(p2p server)'


        '--auth-gen-token[lifetime external-auth]::'
        '--auth-gen-token-secret[secret for the HMAC]:file:_files'

        '--client-config-dir:directory:file -/'
        # it is effectively a router.
        '--client-to-client[internally route client-to-client traffic rather than pushing]'
        '--connect-freq:n per sec:'

        '--ifconfig-pool:start-ip end-ip mask:'
        '--ifconfig-pool-persist[file seconds/600]:file:_files ::seconds:'
        '--ifconfig-push[local remote-netmask]:netmask:'
        '--client-connect:script'
        --multihome
        '--iroute[network \[netmask\]]:network: ::netmask:'
        '--max-clients[n]:number:'
        # ddos
        '--max-routes-per-client[n/256]::number:'
        --opt-verify
        #
        '--port-share[host port \[dir\]]:host: :port: ::dir:_files -/'
        '--push[option: route route-gateway route-delay redirect-gateway ip-win32 dhcp-option dns inactive ping ping-exit ping-restart setenv auth-token persist-key persist-tun echo comp-lzo  socket-flags  snd-buf rcvbuf]:option-name:(route route-gateway route-delay redirect-gateway ip-win32 dhcp-option dns inactive ping ping-exit ping-restart setenv auth-token persist-key persist-tun echo comp-lzo socket-flags snd-buf rcvbuf)'
        # (--route --route-gateway --route-delay --redirect-gateway --ip-win32 --dhcp-option,--dns --inactive --ping --ping-exit --ping-restart --setenv --auth-token --persist-key --persist-tun --echo --comp-lzo  --socket-flags  --snd-buf --rcvbuf)
        --push-remove
        --push-reset

        '--server[network netmask]:network: :netmask:'
        --server-bridge
        # gateway netmask pool-start-IP pool-end-IP
        # ethernet bridging configurations

        # default option
        '--verify-client-cert[mode]:mode:(none optional require)'
    )
    fi

# LINK/network
    link_options=(
        # also --proto tcp-client
        "(--http-proxy --socks-proxy)--bind[ipv6only?]:keyword:(ipv6only)"
        '(--remote)--float[Allow remote peer to change its IP address and/or port number]'
        "--fragment[max \[mtu\] ...adds 4 bytes of overhead per datagram, allows fragmentation]: "
        '--link-mtu[deprecated]'

        '--keepalive[period wait -- see ping ping-restart]:seconds: :seconds:'
        '--ping[Ping remote over the control channel if no packets (for firewalls --ping-exit)]:seconds:'
        '--ping-restart[]:seconds:'
        '--ping-exit[exit if no packet received in seconds]:seconds:'
        # '--inactive'

        '--local[host]:host:'
        "--lport[port number]:port: "
        --mark
        # --mode
        --nobind

        '--proto::(udp tcp-client tcp-server)'
        '--port[both local and remote]:port:'
        --rport:port:
        '--replay-window[n t]:arg:'
        '--replay-persist:file:_files'
        --tcp-nodelay
    )

# VPN
    vpn_options=(
        '--bind-dev[Set device to bind the server socket to]:device:_files'
        # eno1?

        --block-ipv6

        '--dev:tun/tap:(tun0 tun1 tun2 tun3 tun4 tap ovpn)'

        # these go along
        '--dev-node:full path:_files -g "/dev/*"'
        '--dev-type::(tun tap)'

        #
        '--dhcp-option[Set additional network parameters: type \[parm\]]:options:'

        '--ifconfig[ip (tun -> remote ip) (tap -> subnet)]:ip: :remoteIP: '

        '--ifconfig-noexec'
        '--ifconfig-nowarn'
        "--persist-tun[don't reopen accross sigusr1/--ping-restart]"
        '--redirect-gateway:flags:(local autolocal def1 bypass-dhcp bypass-dns block-local ipv6 ipv4)'
        '--routet[network/IP optional: netmask gateway metric]:args:'
        '--topology:mode:(net30 p2p subnet)'
        '--tun-mtu[maximum datagram size in bytes/1500]:n:'
        '--tun-mtu-extra:n:'
    )
# SCRIPTING
    local -a scripting_integration_options
    scripting_integration_options=(
        '--up[after socket bind and TUN/TAP open]:cmd:'
        '--tls-verify[untrusted client]:cmd:'
        '--ipchange[authenticated, or ip changed]:cmd:'
        '--client-connect[immediately after client authentication]:cmd:'

        '--route-up[after connection authentication,see --route-delay]'
        '--route-pre-down[]'
        '--client-disconnect[client shutdown]:cmd:'
        '--down[socket & tun close]:cmd:'
        '--learn-address'
        '--auth-user-pass-verify[cmd method]:args:'

        {--setenv,--setenv-safe}':name value:'
    )


    managemenent_options=(
        '--management[socket-name unix pw-file, IP port pw-file]'
        --management-client
        --management-client-auth
        --management-forget-disconnect
        --management-query-passwords
        --management-signal
        --management-up-down
    )

# GENERIC
    local -a generic_options
    generic_options_passwords=(
        "--auth-nocache[Don't cache username/passwords in virtual memory]"
    )
    generic_options=(
        --help
        # should be absolute path!
        "--cd:directory:_files -/"
        "--chroot:directory:_files -/"


        #should be used: (--persist-key --persist-tun)
        "--setcon[Apply SELinux context after initialization, needs /proc]:SEL context:"
        --group:group:_groups
        --user:user:_users # fixme!
        # what's the point ^^ of this `second:'
        '--script-security[0 -no hooks 1-only builtin 2-call 3-passwords passed]:level:(0 1 2 3)'


        # OPENVPN_PLUGIN_CLIENT_CONNECT
        # OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY
        "--tmp-dir[writes files for hooks]:directory:_files -/"
        # for hooks: --client-connect

        "--config:file:_files"
        # mmc: Note that configuration files can be nested to a reasonable depth.

        # I need an argument, but no completion
        "(--auth-user-pass --auth-nocache)--daemon[syslog (unless --log)]:progname:"

        '--engine[use --show-engine]:ssl engine:'
        # parse and extract [name]
        #
        ## ssl
        '--providers[ssl external provider]:ssl-provider:'

        '--use-prediction-resistance[DANGEROUS: depletes RNG]'
        '--fast-io[avoid poll/epoll/select call prior to write]'

        #--iproute:command:_cmd
        # OPENVPN_PLUGIN_TLS_FINAL

        '--mlock[Disable  paging  by calling the POSIX mlockall function]'
        '--nice[level]:number:'

        # signals:
        "--persist-key[don't re-read files across SIGUSR1 or --ping-restart]"
        '--remap-usr1[SIGUSR1 remapped to restart without persisting state or exit]:signal:(SIGHUP SIGTERM)'

        '(--dev --remote)--test-crypto[self-test, without peers]'

        "--writepid:file:_files"
        "--status[write client list: file \[seconds\]]:file:_files ::seconds:"
        # todo: only if that is there!
        "--status-version[1 traditional,comma-sep' 2 more 3 tabs]:version:(1 2 3)"
    )

# sudo openvpn --tls-server --ca /home/michal/vpn-crypto/ca.crt --data-ciphers AES-256-CBC   --dev tun1 --verb 0 --lport 1194 --ifconfig 10.9.8.12 10.9.8.11
    # `needs' --dh
    # still:
    # Cipher negotiation is disabled since neither P2MP client nor server mode is enabled
    # ca_file can only be specified in TLS-mode, i.e. where --tls-server or --tls-client is also specified.

    # so $tls_options only if ?
    _arguments -C -S : \
               $generic_options \
               $generic_options_passwords \
               $log_options \
               $protocol_options \
               $data_channel_renegotiation \
               \
               '--mode[set OpenVPN major mode]:mode:(p2p server)' \
               $server_options \
               '(--tls-client --tls-server)--client[=pull tls-client]' \
               $client_options \
               \
               $SSL_Library_information \
               $generating_options \
               '(--tls-server)--tls-client[act as such during TLS handshake]' \
               '(--tls-client)--tls-server[provide dh during the TLS handshak]' \
               $tls_options \
               $link_options \
               $vpn_options \
               $scripting_integration_options \
               $managemenent_options \
               --plugin:args: \
               --show-gateway:args: \
               --show-groups \
               --version
    # + ?
    #                     ^^^^^^ help for the option in the option-list
    # keytype ?
    #

    if [[ -n $state ]]
    then
        # mmc: this is an array!
        # and $state_descr contains the MESSAGE.
        case $state in
            keytypes)
                # -t tag  'name' array
                _describe  "keytype" keytypes
                ;;
            curves)
                curves=($(openvpn --show-curves |tail -n +5))
                compadd \
                        -X "available %B%Ucurves" \
                        -l \
                        -a \
                        -- curves
                ;;
            digests)
                local digests=($(openvpn --show-digests|tail -n +8| cut --field 1 --delimiter ' '))
                compadd \
                        -X "available %B%Udigests" \
                        -l \
                        -a \
                        -- digests
                ;;
            ciphers)

                local input_line
                local cipher
                local description
                local -a ciphers
                local -a description

                openvpn --show-ciphers | egrep '^([-A-Z0-9])+ ' |
                    while read input_line
                    do
                        # set +x
                        # local cipher
                        # local description
                        cipher="${input_line%% *}"
                        description="${input_line#* \(}"
                        # drop the final ) as well!
                        # -X "$description"
                        ciphers+=($cipher)
                        descriptions+=("$cipher ${description%)}")
                    done

                # -J ciphers-group
                compadd \
                        -X "available %B%Ucipher" \
                        -d descriptions \
                        -l \
                        -a \
                        -- ciphers

                ;;
            *)
        esac
    fi
}

_openvpn $@




# --genkey tls-crypt-v2-server [keyfile] [base64 metadata]:
# --genkey tls-crypt-v2-client [keyfile] [base64 metadata]:

# "--management: ip port [pass]"


# _values -s , 'description' \
#                   '*foo[bar]' \
#                   '(two)*one[number]:first count:' \
#                   'two[another number]::second count:(1 2 3)'
