#!/usr/bin/expect

package require cmdline

proc usage {} {
  puts stderr {
Usage:
  puavo-rest-request urlpath(s)
                     [--authoritative]
                     [--debug]
                     [--post]
                     [--send-device-credentials]
                     [--send-device-school-dn]
                     [--send-hostname]
                     [--user username (password from standard input)
                       | --user-bootserver
                       | --user-etc
                       | --user-krb]
                     [--writable]
                     [-- [arbitrary curl arguments]]
}
}

proc get_puavo_domain {} {
  set f [open /etc/puavo/domain]
  if {[catch { set puavo_domain [read $f] }]} {
    close $f
    error "could not read puavo domain from /etc/puavo/domain"
  }
  close $f
  string trim $puavo_domain
}

proc make_request {curl_args {password ""}} {
  global debug_mode

  # use curl instead of puavo-rest-client for efficiency reasons

  set exec_curl_args [list --cacert /etc/puavo-conf/rootca.pem \
                           --fail                              \
                           --show-error                        \
                           --silent                            \
                           {*}$curl_args]

  # no timeouts here, it is the caller's responsibility to decide on that

  if {$debug_mode} { puts stderr "Running curl $exec_curl_args" }

  if {$password eq ""} {
    try {
      set fh [open "|[list curl {*}$exec_curl_args]"]
      fconfigure $fh -translation binary
      set curl_output [read $fh]
      close $fh
    } on error {errmsg} {
      error "error executing curl:\n  ${errmsg}"
    } finally {
      catch { close $fh }
    }

    fconfigure stdout -translation binary
    puts -nonewline stdout $curl_output
    flush stdout
    return
  }

  if {[catch {
         set timeout -1

         log_user 0
         spawn -noecho curl {*}$exec_curl_args
         expect -re {Enter host password for user '.*':}
         send "$password\n"
         expect "\n"

         log_user 1
         expect eof

         lassign [wait] pid spawn_id ok curl_status
         if {$ok != 0} { error {operating system error} }
         if {$curl_status != 0} {
           error "curl returned error code $curl_status:\n$expect_out(buffer)"
         }
       } result]} {
    error "error executing curl with password:\n${result}"
  }
}

set auth_mode  ""
set debug_mode false

proc set_auth_mode {new_authmode} {
  global auth_mode
  if {$auth_mode ne ""} {
    usage
    exit 1
  }
  set auth_mode $new_authmode
}

set prr_tmpdir [exec mktemp -d /tmp/puavo-rest-request.XXXXXX]

proc cleanup {{exitcode 1}} {
  global prr_tmpdir
  catch { exec rm -rf $prr_tmpdir }
  exit $exitcode
}

trap cleanup {SIGHUP SIGINT SIGQUIT SIGPIPE SIGTERM}

if {[catch {
  set curl_args          [list]
  set puavo_resolve_args [list]
  set user_username      ""
  set user_password      ""

  set urlpaths [list]

  for {set i 0} {true} {incr i} {
    set next_arg [lindex $argv $i]
    switch -glob -- $next_arg {
      --* -
      ""  { break }
      default { lappend urlpaths $next_arg }
    }
  }

  if {[llength $urlpaths] == 0} {
    usage
    error "no urlpath(s) set"
  }

  set argv [lrange $argv $i end]

  set options {
    authoritative
    debug
    post
    send-device-credentials
    send-device-school-dn
    send-hostname
    user.arg
    user-bootserver
    user-etc
    user-krb
    writable
  }

  if {[catch { array set params [::cmdline::getoptions argv $options] }]} {
    usage
    error {could not parse command line parameters}
  }

  set puavo_domain [get_puavo_domain]
  lappend curl_args --header "Host: ${puavo_domain}"

  if $params(authoritative) {
    lappend curl_args --form-string authoritative=true
  }

  if $params(debug) {
    set debug_mode true
  }

  if $params(post) {
    lappend curl_args -X POST
  }

  if $params(send-device-credentials) {
    set r [catch { exec tr -d {\n} < /etc/puavo/ldap/dn > "${prr_tmpdir}/dn" }]
    if {$r} {
      error {Could not read device dn from /etc/puavo/ldap/dn}
    }
    set r [catch { exec tr -d {\n} < /etc/puavo/ldap/password \
                                   > "${prr_tmpdir}/password" }]
    if {$r} {
      error {Could not read device password from /etc/puavo/ldap/password}
    }

    lappend curl_args --form "device_dn=<${prr_tmpdir}/dn" \
                      --form "device_password=<${prr_tmpdir}/password"
  }

  if $params(send-device-school-dn) {
    set r [catch { exec jq -j .school_dn /etc/puavo/device.json \
                     > "${prr_tmpdir}/school_dn" }]
    if {$r} {
      error {Could not read device school dn from /etc/puavo/device.json}
    }

    lappend curl_args --form "school_dn=<${prr_tmpdir}/school_dn"
  }

  if $params(send-hostname) {
    set r [catch { exec tr -d '\n' < /etc/puavo/hostname \
                                   > "${prr_tmpdir}/hostname" }]
    if {$r} {
      error {Could not read puavo hostname from /etc/puavo/hostname}
    }

    lappend curl_args --form "hostname=<${prr_tmpdir}/hostname"
  }

  if $params(user-bootserver) {
    set_auth_mode bootserver
  }

  if $params(user-etc) {
    set_auth_mode etc
  }

  if $params(user-krb) {
    set_auth_mode krb
  }

  if {$params(user) ne ""} {
    set_auth_mode user
    set user_username $params(user)
    if {$user_username eq ""} {
      usage
      exit 1
    }
    set user_password [string trimright [read stdin]]
  }

  if $params(writable) {
    lappend puavo_resolve_args --writable
  }

  set r [
    catch { set api_server [exec /usr/sbin/puavo-resolve-api-server \
                                 {*}$puavo_resolve_args] }
  ]
  if {$r} { error {Could not resolve Puavo API server} }

  set urls [lmap _ $urlpaths { format "%s%s" $api_server $_ }]

  switch -- $auth_mode {
    {} {
      lappend curl_args {*}$urls {*}$argv
      make_request $curl_args
    }

    bootserver {
      lappend curl_args --header {Authorization: Bootserver} {*}$urls {*}$argv
      make_request $curl_args
    }

    etc {
      set r [catch { set device_dn [exec cat /etc/puavo/ldap/dn] }]
      if {$r} {
        error {Could not determine device dn for this host}
      }

      set r [catch { set device_password [exec cat /etc/puavo/ldap/password] }]
      if {$r} {
        error {Could not determine device password for this host}
      }

      lappend curl_args --user $device_dn {*}$urls {*}$argv

      make_request $curl_args $device_password
    }

    krb {
      lappend curl_args --delegation always \
                        --negotiate         \
                        --user :            \
                        {*}$urls            \
                        {*}$argv
      make_request $curl_args
    }

    user {
      lappend curl_args --user $user_username {*}$urls {*}$argv
      make_request $curl_args $user_password
    }

    default {
      error {internal error, could not decide on operation mode}
    }
  }
} errmsg]} {
  puts stderr $errmsg
  cleanup
}

cleanup 0
