
function PrintHelp()
{
  PrintVersion
  echo
  ABecho 'teamviewer'                                 'Start TeamViewer user interface (if not running).'
  ABecho 'teamviewer --url URL'                       'Start a remote control connection with the given TeamViewer link'
  echo
  ABecho 'teamviewer help'                            'Print this help screen.'
  ABecho 'teamviewer version'                         'Print version information.'
  ABecho 'teamviewer info'                            'Print version, status, id.'
  ABecho 'teamviewer ziplog'                          'Create an archive with teamviewer logs and system information (useful when contacting support).'
  ABecho 'teamviewer ziplog plus'                     'Add more information (process list, environment...) - recommended if TeamViewer does not launch properly'
  echo
  ABecho 'teamviewer license [show|accept]'           'Interactively agree or show/agree to End User License Agreement.'
  isInstalledTV || return
  ABecho 'teamviewer setup'                           'Configure headless modes (non-gui/console)'
  echo
  ABecho 'teamviewer unassign'                        'Unassign a device i.e. make it unmanaged. Suitable for unattended deployment.'
  ABecho 'teamviewer assignment --id=<ID>'            'Assign a device and make it managed. Suitable for unattended deployment.'
  echo
  ABecho 'teamviewer passwd [PASSWD]'                 'Set a password (useful when installing remote (ssh).'
  echo
  ABecho 'teamviewer daemon status'                   'Show current status of the TeamViewer daemon.'
  ABecho 'teamviewer daemon start'                    'Start		TeamViewer daemon.'
  ABecho 'teamviewer daemon stop'                     'Stop		TeamViewer daemon.'
  ABecho 'teamviewer daemon restart'                  'Stop/Start	TeamViewer daemon.'
  ABecho 'teamviewer daemon disable'                  "Disable	TeamViewer daemon - don't start daemon on system startup."
  ABecho 'teamviewer daemon enable'                   'Enable		TeamViewer daemon - start daemon on system startup (default).'
  echo
  ABecho 'teamviewer extended-input enable'           'Enable simulation of extended input: pen tablet'
  ABecho 'teamviewer extended-input disable'          'Disable simulation of extended input'
  ABecho 'teamviewer extended-input status'           'Print current status of extended input simulation'
  PrintHelpRepo
  [ "$TV_PKGTYPE" == "TAR_IN" ] || return
  ABecho 'teamviewer uninstall [force]'  "Uninstall TAR package. Force: don't ask for confirmation."
  echo
}

function PrintVersion()
{
  ABecho "TeamViewer" "$TV_VERSION  ($TV_PKGTYPE)"
}

function PrintInfo()
{
  PrintVersion
  echo
  PrintTeamViewerID
  echo
  PrintDaemonStatus
}

function PrintDaemonStatus()
{
  local cmd="$(daemonCtl 'status')"
  local txt="n/a"

  if [ isInstalledTV ] ; then
    txt="$(eval "$cmd")"
    [ $? = 0 ] || txt='n/a (error)'
  fi

  ABecho "teamviewerd status" "$txt"
}

function PrintTeamViewerID()
{
  local config="$TV_BASE_DIR/config/global.conf"
  local txt='not found'
  local tvid

  [ -e "$config" ] && tvid=$( grep 'ClientID' "$config" | cut --delimiter='=' -f2 )
  [ -n "$tvid"   ] && txt="$tvid"

  ABecho "TeamViewer ID:" "$tvid"

  if [ -z "$tvid" ] && isInstalledTV; then
    echo "Try restarting the TeamViewer daemon (e.g. teamviewer --daemon restart)"
  fi
}

function SetPasswd()
{
  local pwd="$1"
  [ -n "$pwd" ] || die 'no password specified'

  installedTVorDie
  isSuperUser || die 'You need root permissions for this operation'

  Run_Daemon 'stop' > /dev/null

  "$TV_BIN_DIR/teamviewerd" --passwd "$pwd"
  case $? in
    0  ) echo 'ok'	;;
    11 ) echo 'password too short - use at least 8 characters [E11]'	;;
    # 12 ) reserved, was used in older versions
    13 ) echo 'password not accepted - illegal char detected [E13]'	;;
    14 ) echo 'passwort invalid - validation failed [E14]'	;;
    *  ) echo 'unknown response'	;;
   esac

  Run_Daemon 'start' > /dev/null || die 'failed to restart the daemon'
  echo
}

function ExportLicense()
{
  local license="$1"
  local path='/tmp/tv_global.conf'

  [ -n "$license" ] || die 'no license specified'

  isSuperUser || die 'You need root permissions for this operation'

  Run_Daemon 'stop' > /dev/null

  "$TV_BIN_DIR/teamviewerd" --export-license "$license" "$path"
  case $? in
    0  ) echo "ok - license exported to '$path'"		;;
    11 ) echo "destination '$path' not accessible"		;;
    *  ) echo 'unknown response'	;;
   esac

  Run_Daemon 'start' > /dev/null || die 'failed to restart the daemon'
  echo
}

function StripPersonalInformation()
{
  expectVariables ziplog_dir cfg_dir

  local -r config_dir="$ziplog_dir/$cfg_dir"
  local -r strip_global=(
    '[bin  ] Certificate'
    '[bin  ] CertificateKey'
    '[bin  ] MultiPwdMgmtPwdData'
    '[bin  ] PermanentPassword'
    '[bin  ] PK'
    '[bin  ] SK'
    '[bin  ] SRPPasswordMachineIdentifier'
    '[strng] OwningManagerAccountName'
    '[strng] OwningManagerCompanyName'
    )
  local -r strip_client=(
    '[bin  ] BuddyLoginTokenAES'
    '[bin  ] BuddyLoginTokenSecretAES'
    )
  local config=

  ( # subshell: preserve pwd
    cd "$config_dir"

    # global.conf
    config='global.conf'
    for s in "${strip_global[@]}"; do
      StripItem "$config" "$s"
    done

    # client.conf
    for config in client_*.conf ; do
      [ -e "$config" ] || continue
      for s in "${strip_client[@]}"; do
        StripItem "$config" "$s"
      done
    done
  )
}

function StripItem()
{
  local file="$1"
  local pattern="$2 ="
  local sedpattern="$(escapeBrackets "$pattern")"

  grep -q "$sedpattern" "$file" || return

  sed -i -e "/$sedpattern/d" "$file"
  echo "# $pattern (stripped)" >> "$file"
}

function escapeBrackets()
{
  local pattern="${1/[/\\[}"
  pattern="${pattern/]/\\]}"
  echo "$pattern"
}

function InfoArch()
{
  local ai64=' '; hasX86_64Support && ai64='X'
  local ai32=' '; hasX86_32Support && ai32='X'
  local aarm=' '; hasArmhfSupport  && aarm='X'

  HeadEcho "DistArch:" "$(uname -m)	( Loader:  [$ai64] x86_64  [$ai32] x86_32  [$aarm] armhf )"
}

function InfoDistro()				# log information about the Linux distribution
{
  local files=$(cd /etc; ls *-release *-version *_version 2> /dev/null)
  local rfile
  local fhead

  echo     "Distribution:"

  cmdExists lsb_release && fhead=$(lsb_release -idrc)		# first, try lsb_release
  if [ -n "$fhead" ]; then
    IndentEcho "$fhead" '      '
  else
    HeadEcho "  Files" "$(echo "$files" | tr '\n' ' ')"		# try various files

    for rfile in $files ; do
      echo "    $rfile:"
      fhead=$(head -n 10 "/etc/$rfile")
      IndentEcho "$fhead" '      '
    done
  fi
}

function InformationHeader()
{
  echo -e "\n\n$@\n======================================\n"
}

function CollectALSAInformation()
{
  expectVariables sysinfo_dir
  local -r alsaFile="$sysinfo_dir"/audio-alsa

  cmdExists alsa-info    && alsa-info    --stdout --no-upload &> $alsaFile && return
  cmdExists alsa-info.sh && alsa-info.sh --stdout --no-upload &> $alsaFile && return

  ( # fallback
    InformationHeader "ALSA system information"

    InformationHeader "ALSA dmesg"
    dmesg | grep -i alsa

    InformationHeader "Sound Devices"
    find /dev/snd | sort

    if cmdExists aplay ; then
      InformationHeader "Playback information"
      echo -e "Devices:\n\n $(aplay -l)\n\n"
      echo -e "Streams:\n\n $(aplay -L)\n\n"
    fi

    if cmdExists arecord ; then
      InformationHeader "Capture information"
      echo -e "Devices:\n\n $(arecord -l)\n\n"
      echo -e "Streams:\n\n $(arecord -L)\n\n"
    fi

    if cmdExists amixer ; then
      InformationHeader "Mixer information"
      for control in /dev/snd/controlC* ; do
        card=${control#/dev/snd/controlC}
        echo -e "\nhw:$card:\n======"
        amixer -c $card scontents
      done
    fi
  ) &> $alsaFile
}

function ImpersonatePulseOwner()
{
  expectVariables owner
  owner="$(stat -c %U "$XDG_RUNTIME_DIR" 2> /dev/null)"
  isSuperUser && [ -n "$owner" ] && [ "$owner" != "root" ]
}

function PrintPulseAudioInformation()
{
  InformationHeader pactl info
  pactl info
  InformationHeader pactl list short
  pactl list short
  InformationHeader pactl list
  pactl list
}

function CollectPulseAudioInformation()
{
  expectVariables sysinfo_dir
  local -r pulseFile="$sysinfo_dir"/audio-pulseaudio
  local owner=

  cmdExists pactl || return

  (
    echo "Runtime Dir: $XDG_RUNTIME_DIR"
    if ImpersonatePulseOwner; then
      echo -e "impersonating $owner\n"
      su "$owner" "$0" pulseinfo
    else
      "$0" pulseinfo
    fi
  ) &> $pulseFile
}

function runInfoCmd()
{
  cmdExists $1 || { echo -e "$1 not installed\n"; return 1; }
  echo "$@"
  "$@"
  echo
}

function CollectSystemInformation()
{
  expectVariables ziplog_dir sysinfo_dir sysinfoPlus_dir plus

  local -r sysinfo_dir="$ziplog_dir/$sysinfo_dir"

  mkdir "$sysinfo_dir" || die "failed to create $sysinfo_dir"

  ( cd "$sysinfo_dir"   # subshell: preserve pwd
    PATH=$PATH:/sbin:/usr/sbin # e.g. alsa-info

    (
      InfoDistro     # distribution information
      echo
      InfoArch       # supported architecture information (32/64bit)
      echo
      echo -e "machine-id: $(cat /etc/machine-id)\n"
      echo -e "Kernel:\n$(cmdExists uname && uname -a)\n"    # Kernel version
      echo -e "Modules:\n$(cmdExists lsmod && lsmod)"        # Kernel modules
    ) &> 'sysinfo'

    (
      runInfoCmd cat /proc/cpuinfo
      runInfoCmd cat /proc/sys/kernel/shmmax
      runInfoCmd cat /proc/meminfo
    ) &> 'cpu-mem-info'

    # network interfaces
    runInfoCmd ip address show &>> 'network'
    runInfoCmd ifconfig -a     &>> 'network'

    # Devices: PCI, USB
    runInfoCmd lspci &>> 'devices'
    runInfoCmd lsusb &>> 'devices'

    # Audio: ALSA, PulseAudio
    CollectALSAInformation
    CollectPulseAudioInformation

    # dbus names
    runInfoCmd dbus-send --system --print-reply --dest=org.freedesktop.DBus / org.freedesktop.DBus.ListNames &> 'dbus-system-service-list'

    # copy X logs
    local -r logs=(/var/log/X*.log*)
    for file in "${logs[@]}" ; do
      [ -f "$file" ] && cp -p "$file" .
    done

    ListProcesses ps &> processlist-tv_X
    echo -e "\nX processes\n$(ps aux | grep X)"  &>> processlist-tv_X

    for bin in ls -lah "$TV_BIN_DIR"/*; do
      [ -x "$bin" ] && [ -f "$bin" ] && runInfoCmd ldd "$bin" &>> tv-ldd
    done
  )

  [ -z "$plus" ] && return    # extended system info: teamviewer ziplog plus
  local -r plus_path="$ziplog_dir/$sysinfoPlus_dir"
  mkdir "$plus_path" || die "failed to create $sysinfoPlus_dir"

  ( cd "$plus_path"   # subshell: preserve pwd
    cat /proc/self/environ | tr '\0' '\n' | sort > ziplog-environment
    PATH=$PATH:/sbin:/usr/sbin

    runInfoCmd ps aux --sort=args &> processlist
  )
}

function CreateZipLogTmpDir()
{
  mktemp -d -p /tmp tv_ziplog_XXXXXX || die "Error creating temporary dir in /tmp"
}

function CollectSystemCrashFiles()
{
  isInstalledTV || return

  local -r dst=$ziplog_dir/$varCrash
  mkdir -p $dst

  for f in /var/crash/_opt_teamviewer_tv_bin_*.crash; do
    [ -e $f ] && cp -Lp $f $dst
  done
}

function CollectLogFiles()
{
  expectVariables ziplog_dir log_dir cfg_dir

  cp -Lrp "$TV_BASE_DIR/$cfg_dir" $ziplog_dir # global.conf
  cp -Lrp "$TV_BASE_DIR/$log_dir" $ziplog_dir # global log, plus linked user logs

  (isInstalledTV && isSuperUser) || return

  local -r dst_dir="$ziplog_dir/$log_dir"
  for item in "$dst_dir"/* ; do
    [ -d "$item" ] && return # user logs already copied
  done

  local -r logpath="$TV_USER_LOCAL_SUBPATH/$log_dir/"
  for item in /home/*/"$logpath"; do
    [ -e "$item" ] || continue
    local homedir="${item%/$logpath}"
    local userdir="$dst_dir/found${homedir//'/'/'_'}"
    mkdir "$userdir"
    cp -a "$item"/* "$userdir"
  done
}

function CollectUserConfig()
{
  expectVariables ziplog_dir log_dir cfg_dir
  isInstalledTV || return

  local -r logs="$TV_BASE_DIR/$log_dir"

  for file in "$logs"/* ; do
    [ -h "$file" ] || continue

    local name="$(basename "$file")"
    local logdest="$(readlink "$file")"
    local cfgfile="${logdest/%"$TV_USER_LOCAL_SUBPATH/$log_dir/"/"$TV_USER_CONFIG_SUBPATH/client.conf"}"
    local dstname="client_$name.conf"	        # see StripPersonalInformation

    [ "$logdest" != "$cfgfile" ] || continue    # substitution failed
    [ -e "$cfgfile"            ] || continue    # no user config file found

    cp "$cfgfile" "$ziplog_dir/$cfg_dir/$dstname"
  done
}

function CollectRemoteManagementFiles()
{
  expectVariables ziplog_dir tvrm_dir
  [ -z "$TV_REMOTE_MANAGEMENT_DIR" ] && return 0
  [ ! -d "$TV_REMOTE_MANAGEMENT_DIR" ] && return 0

  local -r tvrm_logs="$TV_REMOTE_MANAGEMENT_DIR/logfiles"
  local -r tvrm_config="$TV_REMOTE_MANAGEMENT_DIR/config"
  local -r tvrm_ziplog="$ziplog_dir/$tvrm_dir"

  mkdir -p "$tvrm_ziplog"

  cp -Lrp "$tvrm_logs" $tvrm_ziplog
  cp -Lrp "$tvrm_config" $tvrm_ziplog
}

function SelectZipper()
{
  expectVariables cmd archive plus

  local user=$SUDO_USER
  [ -n "$user" ] || user=$USER
  isSuperUser && [ "$user" != root ] && user="su_$user"

  local plusName=
  [ -n "$plus" ] && plusName='_plus'

  archive="/tmp/tvlog_$(hostname)_$(date +%F)_${user}$plusName"

  if cmdExists zip; then
    archive+='.zip'
    cmd='zip -r9'
  elif cmdExists tar && cmdExists gzip; then
    archive+='.tar.gz'
    cmd='tar -zchf'
  fi

  if [ -z "$cmd" ]; then
    Yecho 'Please install either\n* zip   or\n* tar and gzip to allow creation of a compressed archive'
    die 'Could not create a compressed archive of log files - no suitable compressor found'
  fi
}

function CreateZipLog()
{
  local -r ziplog_dir="$(CreateZipLogTmpDir)"
  local -r cfg_dir='config'
  local -r log_dir='logfiles'
  local -r sysinfo_dir='sysinfo'
  local -r varCrash='varCrash'
  local -r tvrm_dir="tvRemoteMgmt"

  local -r sysinfoPlus_dir="$sysinfo_dir"plus
  local plus=
  [ "$1" == 'plus' ] && plus='plus'

  echo -e 'Creating a zip archive from TeamViewer log files, config files, and some system information...\n'

  CollectLogFiles # and global.conf
  CollectUserConfig
  StripPersonalInformation

  CollectSystemCrashFiles
  CollectSystemInformation
  CollectRemoteManagementFiles

  local cmd=
  local archive=
  SelectZipper

  ( # subshell: preserve pwd
    cd "${ziplog_dir}"

    rm -f $archive  # remove existing archive

    cmd_args=("$cfg_dir" "$log_dir" "$sysinfo_dir" "$varCrash")
    [ -d "$tvrm_dir" ]        && cmd_args+=("$tvrm_dir")
    [ -d "$sysinfoPlus_dir" ] && cmd_args+=("$sysinfoPlus_dir")

    $cmd $archive ${cmd_args[@]} || die "Done. An error ($?) occurred when creating archive $archive"

    rm -fR $ziplog_dir		# delete temporary data
    chmod 666 $archive		# allow every user to read and delete

    Gecho "\n** Success**\n"
    echo -e "Archive written to $archive\n\n"
  ) || return

  isInstalledTV && (isSuperUser || Yecho "Warning:\n\tSome important information is missing, as it could not be accessed.\n\tPlease run ziplog as root, or\n\tsudo teamviewer ziplog $plus")

  [ -n "$plus" ] && Yecho "NOTE:\n\t'ziplog plus' adds a process list and environment information to the archive.\n\tIn some cases this might leak sensitve information like passwords\n\tYou might want to check the $sysinfoPlus_dir directory in the archive."
}

function ListProcesses()
{
  local -r action="$1$2"
  local -r tmpList="$(find "$TV_BIN_DIR" -maxdepth 1 -type f -executable -printf '%f,')"
  local -r cmdList="${tmpList%,}"

  case "$action" in
    ( ps      ) ps u -C $cmdList | grep -v " $$ "    ;;
    ( psfuzzy ) ps aux | grep -E '[Tt]eam[Vv]iewer'  ;;
    ( pstree  ) pstree -pula $(pgrep teamviewerd)    ;;
  esac
}
