#!/usr/local/bin/ruby
# $Id: tmlctl,v 1.12 2004/01/22 05:47:58 tommy Exp $
#
# Copyright (C) 2003 TOMITA Masahiro
# tommy@tmtm.org
#

require "getopts"
require "net/smtp"

$script_dir = File::dirname $0
require $script_dir+"/tml.rb"
require $script_dir+"/mail.rb"

conf = $script_dir+"/tml.conf"
load conf if File::exist? conf

class TmlCtlError < StandardError
end

unless getopts("f:", "domain:") then
  $stderr.puts "invalid usage"
  exit 1
end

load $OPT_f if $OPT_f
$domain = $OPT_domain if $OPT_domain

mlname = ARGV.shift
if mlname == nil then
  $stderr.puts "invalid usage"
  exit 1
end

if not $domain then
  $stderr.puts "$domain parameter required"
  exit 1
end

# command => :id
$commands = {
  "confirm"	=> :confirm,
  "admin"	=> :admin,
  "subscribe"	=> :subscribe,
  "unsubscribe"	=> :unsubscribe,
  "bye"		=> :unsubscribe,
  "members"	=> :members,
  "summary"	=> :summary,
  "index"	=> :summary,
  "get"		=> :get,
  "end"		=> :end,
  "exit"	=> :end,
}

def parse(cmd, args, confirmed=false)
  unless $commands.key? cmd then
    $ml.log "#{cmd}: unknown command"
    return
  end
  c = $commands[cmd.downcase]
  ac = $ml.get_attr(:allowed_command)
  if c != :end and ac != "all" and not ac.split(/\s*,\s*/).include? cmd then
    raise TmlCtlError, "#{cmd}: not allowed"
  end
  unless ($ml.all_members+$ml.admins).include? $from then
    ac = $ml.get_attr(:anonymous_command)
    if c != :end and ac != "all" and not ac.split(/\s*,\s*/).include? cmd then
      raise TmlCtlError, "#{cmd}: not allowed"
    end
  end
  unless confirmed then
    cc = $ml.get_attr(:need_confirm_command)
    if cc.split(/\s*,\s*/).include? cmd then
      $need_confirm_command << cmd+" "+args.join(" ")
      return
    end
  end

  case c
  when :confirm
    confirm args
  when :admin
    admin args
  when :subscribe
    subscribe
  when :unsubscribe
    unsubscribe
  when :members
    members
  when :summary
    summary args
  when :index
    index args
  when :get
    get args
  when :end
    throw :exit
  end
end

def confirm(args)
  begin
    cmds = File::open($ml.tmpfile("confirm-#{$from}")){|f| f.read}.split(/\n/)
  rescue Errno::ENOENT
    raise TmlCtlError, "not found your address"
  end
  key = cmds.shift
  if args[0] != key then
    raise TmlCtlError, "confirmation key not matched"
  end
  error = false
  cmds.each do |line|
    cmd, *args = line.split
    begin
      parse cmd, args, true
    rescue
      puts $!.to_s
      error = true
    end
  end
  File::unlink($ml.tmpfile("confirm-#{$from}"))
  if error then
    exit 100
  end
end

def admin(args)
  unless $ml.admins.include? $from then
    raise TmlCtlError, "You are not administrator"
  end
  unless $ml.get_attr(:remote_admin) then
    raise TmlCtlError, "not allowed remote administrator command"
  end
  cmd = args.shift
  ac = $ml.get_attr(:admin_command)
  if ac != "all" and not ac.split(/\s*,\s*/).include? cmd then
    raise TmlCtlError, "#{cmd}: not allowed"
  end
  result = nil
  case cmd.downcase.intern
  when :add
    $ml.add *args
  when :bye
    $ml.bye *args
  when :members
    result = $ml.members.join("\n")
  when :allmembers
    result = $ml.all_members.join("\n")
  when :children
    result = $ml.children.map{|c| c.name}.join("\n")
  when :parents
    result = $ml.parents.map{|c| c.name}.join("\n")
  when :admins
    result = $ml.admins.join("\n")
  when :addadmin
    $ml.add_admin args[0]
  when :byeadmin
    $ml.bye_admin args[0]
  when :setattr
    args.each do |av|
      attr, value = av.split(/=/, 2)
      if attr == "admin_command" then
        raise TmlCtlError, "#{attr}: attribute not allowed to set"
      end
      $ml.set_attr attr, value
    end
  when :getattr
    if args.empty? then
      hash = $ml.get_attr
      result = hash.keys.sort{|a,b|a.to_s<=>b.to_s}.map{|a| "#{a}=#{hash[a]}"}.join("\n")
    else
      result = args.map{|a| $ml.get_attr a}.join("\n")
    end
  when :setcounter
    $ml.counter = args[0]
  when :getcounter
    result = $ml.counter.to_s
  else
    raise TmlCtlError, "#{cmd}: unknown admin command"
  end
  $ml.send_msg "#{$ml.name}-ctl@#{$domain}", $from, "admin", {:command=>"#{cmd} #{args.join(" ")}", :result=>result+"\n"} if result
end

def subscribe()
  if $ml.have? $from then
    raise TmlCtlError, "#{$from}: already exist"
  end
  unless $ml.check_subscribe $from then
    raise TmlCtlError, "#{$from}: not permitted to subscribe to #{$ml.name}"
  end
  $ml.add $from
  $ml.send_msg $ml.get_attr(:sender), $from, "welcome"
end

def unsubscribe()
  $ml.bye $from
  $ml.send_msg $ml.get_attr(:sender), $from, "unsubscribe"
end

def members()
  msg = $ml.children.map{|c| "[#{c.name}]"}.join("\n") + $ml.members.join("\n")
  $ml.send_msg $ml.get_attr(:sender), $from, "memberlist", {:list=>msg}
end

def summary(args)
  if args == nil or args.empty? then
    msg = $ml.summary_all
  else
    msg = ""
    args.each do |arg|
      if arg.include? "-" then
        s, e = arg.split(/-/,2).map{|i| i.to_i}
        msg << $ml.summary(s, e)
      elsif arg =~ /^last:/i then
        msg << $ml.summary_last(arg.split(/:/,2)[1].to_i)
      else
        msg << $ml.summary(arg.to_i)
      end
    end
  end
  $ml.send_msg $ml.get_attr(:sender), $from, "summary", {:summary=>msg}
end

def get(args)
  return if args.empty?
  msg = []
  args.each do |arg|
    if arg.include? "-" then
      s, e = arg.split(/-/,2).map{|i| i.to_i}
      msg.concat $ml.get(s, e)
#    elsif arg =~ /^last:/i then
#      msg << $ml.get_last(arg.split(/:/,2)[1].to_i)
    else
      msg.concat $ml.get(arg.to_i)
    end
  end
  return if msg.empty?
  attach = $ml.archive msg
  $ml.send_msg $ml.get_attr(:sender), $from, "archive", {:msg=>attach}
end

$ml = TML::new mlname
unless $ml.get_attr(:remote_command) then
  puts "remote command not allowed"
  exit 100
end

header = ""
body = nil
$stdin.each do |line|
  line.sub!(/\r\n/, "\n")
  if body then
    body << line
  elsif line == "\n" then
    body = ""
  else
    header << line
  end
end
if body == nil then
  body = ""
end

$from = get_from header
$ml.log "from #{$from}"
$need_confirm_command = []
errcnt = 0
catch :exit do
  body.each do |line|
    break if line =~ /^-- /
    if line =~ /^[a-z0-9_-]*>/ then
      line = $'
    end
    next if line =~ /^\s*$/
    cmd, *args = line.strip.split
    next unless cmd =~ /^[a-z]+$/i
    begin
      parse cmd, args
    rescue TmlCtlError, TML::Error
      puts $!.to_s
      $ml.log $!.to_s
      errcnt += 1
      break if errcnt > 5
    rescue
      $ml.log $!.to_s+"\n\t"+$@.join("\n\t")
      puts "tmlctl failed"
      exit 1
    end
  end
end

unless $need_confirm_command.empty? then
  key = Array.new(16).map{(rand(26)+?A).chr}.join
  commands = $need_confirm_command.join("\n")
  File::open($ml.tmpfile("confirm-#{$from}"), "w") do |f|
    f.puts key
    f.puts commands
  end
  $ml.send_msg "#{$ml.name}-ctl@#{$domain}", $from, "confirm", {:key=>key, :commands=>commands}
end

exit 100 if errcnt > 0
