#compdef ipsec strongswan

# Completion for the ipsec script (aka strongswan on some systems) provided by
# FreeS/WAN, Openswan, Libreswan, and strongSwan. See also strongSwan's swanctl.
#
# As with swanctl, elevated privileges are usually required to complete SA names
# and the like; consider setting the gain-privileges style as follows:
# zstyle ':completion:*:(ipsec|strongswan)/*' gain-privileges yes
#
# @todo We don't complete pool names or virtual IPs

# Complete connection (IKE SA) names and optionally SA/instance names
# --instances => also complete SA/instance names
(( $+functions[_ipsec_connections] )) ||
_ipsec_connections() {
  local -a instances tmp ipsec_conns ipsec_insts

  zparseopts -D -E -a instances - -instances

  tmp=( ${(f)${"$(
    _call_program -p ipsec-status ${_ipsec_cmd:-$words[1]} statusall
  )"#*$'\n'[[:space:]]#[Cc]onnections:[[:space:]]#$'\n'}} )
  tmp=( ${(@M)tmp:#[[:space:]]#[^[:space:]]##:[[:space:]]##?*} )
  tmp=( ${(@)${(@)tmp##[[:space:]]##}%%:*} )

  ipsec_conns=( ${(@)tmp%%['[{']<->['}]']} )
  ipsec_insts=( ${(@M)tmp:#*['[{']<->['}]']} )

  (( $#ipsec_conns )) || {
    _message -e connections 'connection name'
    return
  }

  tmp=( 'connections:connection name:compadd -a - ipsec_conns' )
  (( $#instances && $#ipsec_insts )) &&
  tmp+=( 'instances:connection SA/instance name:compadd -a - ipsec_insts' )

  _alternative $tmp
}

# Complete arguments to /usr/lib/ipsec/starter. This is rarely invoked directly,
# and there is almost no documentation on it, but the start/restart commands
# pass options to it
(( $+functions[_ipsec_starter] )) ||
_ipsec_starter() {
  _arguments : \
    '(--nofork)--attach-gdb[start daemon under gdb (implies --nofork)]' \
    '--auto-update[specify select time-out]:select time-out (seconds)' \
    '--conf[specify path to ipsec.conf]:ipsec.conf file:_files' \
    '--daemon[specify daemon name]:daemon name' \
    '--nofork[do not fork daemon]' \
    + '(d)' \
    '--debug[set log level 2]' \
    '--debug-more[set log level 3]' \
    '--debug-all[set log level 4]' \
    '--nolog[set log level 0]'
}

_ipsec() {
  local ret=1 variant _ipsec_cmd=$words[1]
  local -a context line state state_descr tmp
  local -A opt_args

  _pick_variant -r variant \
    freeswan='(#i)frees/#wan' \
    libreswan='(#i)libreswan' \
    openswan='(#i)openswan' \
    strongswan='(#i)strongswan' \
    unix \
  --version

  # Provide only basic completion for non-strongSwan implementations
  if [[ $variant == unix ]]; then
    _default
    return
  elif [[ $variant == (free|libre|open)* ]]; then
    tmp=( ${(f)"$( _call_program ipsec-help $words[1] --help )"} )
    tmp=( ${(@M)tmp:#($'  '|$'\t')*} )
    tmp=( ${(@)tmp:#*[':/<>()[]']*} )
    tmp=( ${(f)${(F)tmp//[[:space:]]##/$'\n'}} )

    _arguments -S -A '-*' : \
      '(: * -)--help[display help information]' \
      '(: * -)--version[display version information]' \
      "(-)1:command:(${(j< >)${(@q-)tmp}})" \
      '(-)2: :_default'
    return
  fi

  _arguments -A '-*' \
    '(: * -)--confdir[display path to configuration directory (IPSEC_CONFDIR)]' \
    '(: * -)--copyright[display copyright information]' \
    '(: * -)--directory[display path to libexec/utility directory (IPSEC_DIR)]' \
    '(: * -)--help[display help information]' \
    '(: * -)--piddir[display path to PID directory (IPSEC_PIDDIR)]' \
    '(: * -)--version[display version information]' \
    '(: * -)--versioncode[display brief version information]' \
    '1:command:((
      down\:"terminate IPsec connection/SA"
      down-srcip\:"terminate IKE SAs by client virtual IP"
      leases\:"display IP address/pool status"
      listaacerts\:"display X.509 authorization authority certificates"
      listacerts\:"display X.509 attribute certificates"
      listalgs\:"display loaded algorithms"
      listall\:"execute all list commands"
      listcacerts\:"display X.509 certificate authority certificates"
      listcainfos\:"display certificate authority information"
      listcerts\:"display X.509/OpenPGP certificates"
      listcounters\:"display IKE counter information"
      listcrls\:"display certificate revocation lists"
      listgroups\:"display groups for user authorization profiles"
      listocsp\:"display OCSP revocation information"
      listocspcerts\:"display X.509 OCSP signer certificates"
      listplugins\:"display loaded plug-in features"
      listpubkeys\:"display RSA public keys"
      purgecerts\:"purge cached certificates"
      purgecrl\:"purge cached certificate revocation lists"
      purgeike\:"purge IKE SAs without a quick mode or CHILD_SA"
      purgeocsp\:"purge cached OCSP information"
      reload\:"reload entire configuration (send SIGUSR1)"
      rereadacerts\:"re-read attribute certificates"
      rereadaacerts\:"flush and re-read authorization authority certificates"
      rereadall\:"execute all re-read commands"
      rereadcacerts\:"flush and re-read certificate authority certificates"
      rereadcrls\:"re-read certificate revocation lists"
      rereadocspcerts\:"re-read OCSP certificates"
      rereadsecrets\:"flush and re-read secrets"
      resetcounters\:"reset IKE counter information"
      restart\:"equivalent to stop + start"
      route\:"insert kernel IPsec policy for connection"
      start\:"start IKE daemon"
      status\:"display concise connection status"
      statusall\:"display detailed connection status"
      stop\:"terminate all IPsec connections and stop IKE daemon"
      stroke\:"issue stroke command"
      unroute\:"remove kernel IPsec policy for connection"
      up\:"bring up IPsec connection"
      update\:"reload changes in configuration (send SIGHUP)"
    ))' \
    '*:: :->next' \
  && ret=0

  [[ $state == next ]] &&
  case $words[1] in
    down)
      _arguments : '1: :_ipsec_connections --instances' && ret=0
      ;;
    listcounters|resetcounters|route|status|statusall|unroute|up)
      _arguments : '1: :_ipsec_connections' && ret=0
      ;;
    down-srcip)
      _arguments : \
        '1:virtual IP address (start)' \
        '2::virtual IP address (end)' \
      && ret=0
      ;;
    leases)
      _arguments : '1:pool name' '2::virtual IP address' && ret=0
      ;;
    list*~list(counters|plugins))
      _arguments : '--utc[use UTC for time fields]' && ret=0
      ;;
    start|restart)
      _ipsec_starter && ret=0
      ;;
    stroke)
      _arguments -s -S -A '-*' \
        '(: * -)'{-h,--help}'[display help information]' \
        '(-d --daemon)'{-d+,--daemon=}'[specify daemon name]:daemon name' \
        '1: :_guard "^-*" "stroke command"' \
        '*:stroke command argument:_default' \
      && ret=0
      ;;
  esac

  return ret
}

_ipsec "$@"
