#!/usr/bin/perl -wT
# gsutil - GrandStream configuration dump/restore + reboot
# Copyright (C) 2006  Charles Howes <gsutil@ch.pkts.ca>
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.

$main::VERSION="3.0"; # 2006-07-27

use strict;
use Getopt::Std;
use HTML::Form;
use HTTP::Request;
use LWP::UserAgent;
use Socket;
use POSIX qw(strftime);
use Data::Dumper;

# Backup the command line:
my $cmdline=join(" ",@ARGV);

# Stuff used by getopts:
$Getopt::Std::STANDARD_HELP_VERSION=1;

# Get command line options:
my %opts;
my $r=getopts('bdehnop:r',\%opts);

# If the user is asking for help:
if (defined $opts{'h'}) {
  VERSION_MESSAGE(*STDOUT{IO});
  HELP_MESSAGE(*STDOUT{IO});
  exit 0;
}

# Version information, called by getopts if --version or --help given.
sub VERSION_MESSAGE {
  my $fh=$_[0];  # Passed a file handle.
  print $fh "Version $main::VERSION of GSutil, a GrandStream BudgeTone phone backup, restore\n";
  print $fh "and reboot utility.  Written by Charles Howes (gsutil\@ch.pkts.ca).\n";
  print $fh "http://www.pkts.ca/gsutil.shtml\n";
}

# Help information, called by getopts if --help given.
sub HELP_MESSAGE {
  my $fh=$_[0];  # Passed a file handle.
  print $fh "Usage: $0 -[b|d|e|h|r] [-n] [-o] [-p password] address..\n";
  print $fh "  -b : reboot\n";
  print $fh "  -d : dump to stdout\n";
  print $fh "  -e : show phone firmware versions\n";
  print $fh "  -r : restore from stdin\n";
  print $fh "  -n : don't wait for reboot to finish\n";
  print $fh "  -o : don't translate configuration values\n";
  print $fh "  -p : password (default: admin)\n";
  print $fh "  -h, --help : print this help\n";
  print $fh "  --version : print the version of gsutil\n";
  # getopts exits after calling HELP_MESSAGE()
}

if (!defined $opts{'b'} and !defined $opts{'d'} and !defined $opts{'e'} and !defined $opts{'r'}) {
  print STDERR "No command was given.\n\n";
  print STDERR "Choose one of -b, -d, -e, or -r to do something.\n";
  print STDERR "Try `$0 --help' for more information.\n";
  exit 0;
}

# Get the password
my $password=$opts{'p'};
if (!defined $password) {$password='admin'};  # Default password

# The addresses are on the command line:
if (!defined $ARGV[0]) {
  print STDERR "No addresses of phones were given on the command line.\n";
  HELP_MESSAGE(*STDOUT{IO});
  exit(3);
}

# Convert and error-check all hosts:
my @todo=map {parseaddress($_)} @ARGV;

# The configuration variables get translated into english with this table.
# The translations themselves are stored in the __DATA__ block at the end.
my %translate;
my %untranslate;
while (<DATA>) {
  chomp;
  s/#.*//; # remove comments
  s/\s+$//; # remove trailing space
  # if no translation is available, use the key:
  if (/^\s*(P\d+):\s*(.*)/) {
    $translate{$1} = $2;
    $untranslate{uc($2)} = $1;
  }
}

# Lines of data from stdin
my $linenumber=0;

# For each host on the command line:
foreach my $address (@todo) {

  # Set up the user agent:
  my $ua=LWP::UserAgent->new(timeout=>3);
  my $url="http://$address/";

  # Get the login page:
  my $req_login=HTTP::Request->new(GET=>$url);
  my $resp_login=$ua->request($req_login);
  if ($resp_login->code != 200) { show_error($address,$resp_login); next;}
  my $form_login=HTML::Form->parse($resp_login->content,$url);
  if (!defined $form_login) {
    warn("$address didn't respond with a form; is it a GS phone?\n");
    next;
  }

  # See if this is the right form
  if (grep(/^P2$/,$form_login->param)==0) {
    warn("$address The form didn't have the expected 'P2' (password) field in it!\n".
       "    Debug output:\n".Dumper($form_login));
    next;
  }

  # Stuff in the password
  $form_login->value('P2' => $password);

  # Log in and get main page
  my $resp_main = $ua->request($form_login->click);
  if ($resp_main->code != 200 or $resp_main->content =~ m/Your Login Password is not recognized/) { show_error($address,$resp_main); next;}
  my $form_main=HTML::Form->parse($resp_main->content,$form_login->action);

  # Find the cookie:
  my $cookie=$resp_main->headers->header("set-cookie");
  #1.0.5.x:'set-cookie' => 'SessionId=3bf1ca965d26edb9"; Version=1; Path=/'
  #1.0.6.x:'set-cookie' => 'SessionId="965c22e1bd86fece"; Version=1; Path=/'
  if (!defined $cookie) {
    warn("$address No cookie was sent, but the password was accepted?!  This may be a new firmware.\n");
    next;
  }
  if ($cookie!~m/SessionId="?([^"]*)"/) {
    warn("$address The cookie didn't contain 'SessionId'!  This may be a new firmware.\n".
       "  It said: $cookie\n");
    next;
  }
  my $sessionid=$1;

  # Get everything:
  my $domore=0;
  my $resp_index_htm=$ua->request(HTTP::Request->new(GET=>$url."index.htm",HTTP::Headers->new(Cookie=>"SessionID=\"$sessionid\"")));
  my $resp_config2_htm=$ua->request(HTTP::Request->new(GET=>$url."config2.htm",HTTP::Headers->new(Cookie=>"SessionID=\"$sessionid\"")));

  # Get extended phone configuration if your firmware supports it:
  my $resp_config_a1_htm=$ua->request(HTTP::Request->new(GET=>$url."config_a1.htm",HTTP::Headers->new(Cookie=>"SessionID=\"$sessionid\"")));
  if ($resp_config_a1_htm->code==200) {$domore=1;}  # Do a2-a4 only if a1 was found.
  my $resp_config_a2_htm=$domore?$ua->request(HTTP::Request->new(GET=>$url."config_a2.htm",HTTP::Headers->new(Cookie=>"SessionID=\"$sessionid\""))):undef;
  my $resp_config_a3_htm=$domore?$ua->request(HTTP::Request->new(GET=>$url."config_a3.htm",HTTP::Headers->new(Cookie=>"SessionID=\"$sessionid\""))):undef;
  my $resp_config_a4_htm=$domore?$ua->request(HTTP::Request->new(GET=>$url."config_a4.htm",HTTP::Headers->new(Cookie=>"SessionID=\"$sessionid\""))):undef;

  # Do everything:
  if (defined $opts{'e'}) { show_version($address,$resp_main,$resp_index_htm); }
  if (defined $opts{'d'}) { dump_data($address,$resp_main,$resp_index_htm,$resp_config2_htm,$resp_config_a1_htm,$resp_config_a2_htm,$resp_config_a3_htm,$resp_config_a4_htm); }
  if (defined $opts{'r'}) { restore_data($address,$sessionid,$resp_main,$resp_index_htm,$resp_config2_htm,$resp_config_a1_htm,$resp_config_a2_htm,$resp_config_a3_htm,$resp_config_a4_htm); }
  if (defined $opts{'b'}) { reboot($address,$url,$sessionid,$opts{'n'}); }
}

# Show the phone's hardware model and version:
sub show_version {
  my ($address,$resp_main,$resp_index_htm)=@_;
  my $body=$resp_main->content.$resp_index_htm->content;
  my $model;
  my $version;

  if ($body=~m/<b>Product Model:\s*<\/b><\/td>\s*<td>([^<]*)/s) {
    $model=$1;
    $model=~s/(&nbsp;|\s+)/ /gs;
    $model=~s/(^\s+)|(\s+$)//gs;
  }
  if ($body=~m/<b>Software Version:\s*<\/b><\/td>\s*<td>([^<]*)/s) {
    $version=$1;
    $version=~s/(&nbsp;|\s+)/ /gs;  # delete spaces and &nbsp;
    $version=~s/(^\s+)|(\s+$)//gs;  # delete leading/trailing spaces
    $version=~s/\s+/ /gs;           # delete spaces some more
    $version=~s/--/:/gs;          # turn -- into :
  }
  my $head="# $address";
  if (defined $model) {print $head." Model ".$model;$head="";}
  if (defined $version) {print $head." ".$version;$head="";}
  if ($head eq "") {print "\n";}
}

# Reboot the phone:
sub reboot {
  my ($address,$url,$sessionid,$opts_n)=@_;
  my $ua2=LWP::UserAgent->new(timeout=>1);
  my $resp_rs_htm=$ua2->request(HTTP::Request->new(GET=>$url."rs.htm",HTTP::Headers->new(Cookie=>"SessionID=\"$sessionid\"")));
  if (!defined $opts_n) {
    my $req_login=HTTP::Request->new(GET=>$url);
    my $resp_login;
    my $now=time();
    my $up=1;
    for (my $n=0;$n<30;$n++) { # Should be done in 21 seconds or so.
      $resp_login=$ua2->request($req_login);
      if ($resp_login->code==200 and $up==1) {sleep(1);next;}
      if ($resp_login->code==500) {$up=0;next;}
      last;  # It's up again after being down (200 -> 500 -> 200)
      # Note, if pppoe is configured, it could go down again for a bit.
    }
  }
}

# Dump the phone configuration to stdout:
sub dump_data {
  my ($address,@responses)=@_;
  my $stamp=strftime("%Y-%m-%d %H:%M:%S",localtime());
  print "#-- start of phone $address ($stamp)\n"; # Mark the start
  foreach (@responses) { if (defined $_) {dump_form($address,HTML::Form->parse($_));} }
  print "#-- end of phone $address ($stamp)\n";
}

# Restre the phone configuration from stdin:
sub restore_data {
  my ($address,$sessionid,@responses)=@_;
  my %val;

  # Error checking:
  my %formlines;
  my %filled;

  # Read stdinput for the phone's data:
  while(<STDIN>) {
    chomp;
    $linenumber++;

    if (m/^#-- end of phone/) {last;} # End of this phone's configuration
    if (m/^#/) {next;}  # Skip comment lines
    if (m/^$/) {next;}  # Skip blank lines
    s/\s+#.*//;  # Comment deletion (won't cut in the middle of a word)

    if (m/^([^=]+)=(.*)$/) {   # name = value...
      my $ls=$1; # ls=left side
      my $rs=$2; # rs=right side
      # Trim whitespace, uppercase ls:
      $ls=~s/^\s*(.*?)\s*$/\U$1/;
      $rs=~s/^\s*(.*?)\s*$/$1/;
      # Quote removal from rs:
      $rs=~s/^'(.*)'$/$1/;
      $rs=~s/^"(.*)"$/$1/;
      # Stick it into %val:
      if (defined($translate{$ls})) { $val{$ls}=$rs;next;} # P999 = ...
      if (defined($untranslate{$ls})) { $val{$untranslate{$ls}}=$rs;next;} # name = ...
      warn("$address Unknown input variable '$_', from stdin line $linenumber.\n   Add it to the translation table at the end of the program.\n");
    }
    warn("$address Unrecognized input line '$_', from stdin line $linenumber\n");
  }

  # Fill in the form(s):
  my $form;
  my $ua2=LWP::UserAgent->new(timeout=>3);
  foreach my $response (@responses) {
    if (!defined $response) {next;} # No response here (a2-a4?)
    $form=HTML::Form->parse($response);
    if (!defined $form) {next;} # No form here (a1?)
    foreach my $n ($form->param) {
      $formlines{$n}=1;  # These fields were present on the phone
      if (defined $val{$n}) {
	$form->param($n,$val{$n});
	$filled{$n}=1;  # These fields were filled in from stdin
      }
    }

    # Make the request and add a cookie:
    my $request=$form->make_request;
    $request->header("Cookie" => "SessionId=\"$sessionid\"");

    # Send it:
    my $response=$ua2->request($request);
  }

  # Check for missed data:
  foreach (sort keys %val) {
    if (!defined $formlines{$_}) {
      warn("$address The config file had $_ ($translate{$_}), but the phone did not.\n");
    }
  }
}

# Print out the fields and their values:
sub dump_form {
    my ($address,$form) = @_;
    if (!defined $form) {return;}  # Deal with undefined forms
    foreach my $n ($form->param) {
	if ($n =~ m/^(cancel|reboot|gnkey|update|P2|P196)$/) {next;}
	if (defined $translate{$n} && !defined $opts{'o'}) {
	    print $translate{$n}," = ",$form->param($n),"\n";
	} else {
	    if (!defined $opts{'o'}) {
		warn("$address The parameter $n(=".($form->param($n)).") was not found in the translate table.\n  Use -o to skip translation, or manually add it to the translate table\n");
	    }
	    if (defined $translate{$n}) { print "# $translate{$n}:\n"; }
	    print "$n = ",$form->param($n),"\n";
	    print "\n";
	}
    }
}

# Convert addresses given on command line, and check them.
sub parseaddress {
  my $add=$_[0];
  if ($add !~ m/^([a-z0-9.-]{1,80})$/) { # Valid characters?
    warn("$add isn't a valid hostname\n");
    return;
  }
  my $address=$1;
  if ($address=~m/[a-z]/) {  # If it has a letter, it's a hostname
    if (!defined gethostbyname($address)) {
      warn("$address unknown host\n");
      return;
    }
  }
  return $address;
}

# Decode the error message
sub show_error {
  my ($address,$response)=@_;
  if (!defined $response) {
    die("$0: show_error called with not enough arguments");
  }

  # Page not found:
  if ($response->code == 403) {
    warn("$address page not found, is this a GS phone?\n");
    return;
  }

  # Bad password:
  if ($response->content =~ m/Your Login Password is not recognized/) {
    $cmdline =~ s/\s+-p\s*\S+//;  # Cut out old password
    warn("$address bad password.\n".
        "  Try:  $0 -p your-secret-password $cmdline\n");
    return;
  }

  # Error:
  if ($response->code == 500) {
    # Trying to connect to a firewalled, misconfigured or dead phone:
    if ($response->content =~ m/Can't connect to \S+ \(connect: timeout\)/) {
      warn("$address was unreachable (either rebooting, firewalled, misconfigured, unplugged, or dead)\n");
      return;
    }
    # The arp table knows how to get there, but the phone is not answering:
    if ($response->content =~ m/Can't connect to \S+ \(connect: No route to host\)/) {
      warn("$address was unreachable (either rebooting or recently unplugged)\n");
      return;
    }

    # The phone is tired of being queried over and over:
    if ($response->content =~ m/read timeout/) {
      warn("$address gave a read timeout after connecting; try again.\n");
      return;
    }

  }

  # Some other fatal error:
  warn("$address gave an unexpected ".($response->code)." error.\n".
	"  Here is a full dump of the response:\n".
	Dumper($response)."\n".
	"  Send this to the author, gsutil\@ch.pkts.ca\n".
	"  so that it can be added to the program.\n"
      );
  return;
}

# This is the translate table:
__DATA__
P2: admin_password
P3: name
P8: static_ipaddress_true
P9: static_ipaddress_1
P10: static_ipaddress_2
P11: static_ipaddress_3
P12: static_ipaddress_4
P13: static_subnet_1
P14: static_subnet_2
P15: static_subnet_3
P16: static_subnet_4
P17: static_router_1
P18: static_router_2
P19: static_router_3
P20: static_router_4
P21: static_dns1_1
P22: static_dns1_2
P23: static_dns1_3
P24: static_dns1_4
P25: static_dns2_1
P26: static_dns2_2
P27: static_dns2_3
P28: static_dns2_4
P29: early_dial
P30: ntp_server
P31: sip_registration
P32: register_expiration
P33: voicemail_userid
P34: authenticate_password
P35: sip_userid
P36: authenticate_id
P37: voice_frames_per_tx
P38: layer3_QoS
P39: local_rtp_port
P40: local_sip_port
P41: tftp_server_1
P42: tftp_server_2
P43: tftp_server_3
P44: tftp_server_4
P46: vocoder_7
P47: sip_server
P48: outbound_proxy
P49: G723_rate
P50: silence_suppression
P51: layer2_QoS_vlan_tag
P52: nat_traversal_true
P57: vocoder_1
P58: vocoder_2
P59: vocoder_3
P60: vocoder_4
P61: vocoder_5
P62: vocoder_6
P63: userid_is_phone_number
P64: time_zone
P65: send_anonymous
P66: dial_plan_prefix
P71: offhook_autodial
P72: use_hash_as_dial_key
P73: send_dtmf_mode
P74: send_flash_event
P75: daylight_savings_time
P76: nat_traversal_stun_server
P78: use_random_port
P79: dtmf_payload_type
P81: unregister_on_reboot
P82: PPPoE_id
P83: PPPoE_password
P84: keepalive_interval
P85: no_key_entry_timeout
P86: FXS_impedance
P87: layer2_QoS_priority_value
P88: lock_keypad_update
P90: auto_answer
P91: disable_call_waiting
P92: dhcp_dns_server_1
P93: dhcp_dns_server_2
P94: dhcp_dns_server_3
P95: dhcp_dns_server_4
P96: iLBC_payload_type
P97: iLBC_frame_size
P98: vocoder_8
P99: subscribe_for_mwi
P101: use_nat_ip
P102: date_display_format
P103: use_dns_srv
P104: default_ring_tone
P105: custom_ring_tone_1_caller_id
P106: custom_ring_tone_2_caller_id
P107: custom_ring_tone_3_caller_id
P109: allow_out_call_without_registration # 0 or 1
P110: lan_subnet_mask
P111: lan_dhcp_base_ip
P112: dhcp_ip_lease_time
P113: dmz_ip
P115: cloned_wan_mac_addr_1
P116: cloned_wan_mac_addr_2
P117: cloned_wan_mac_addr_3
P118: cloned_wan_mac_addr_4
P119: cloned_wan_mac_addr_5
P120: cloned_wan_mac_addr_6
P122: time_display_format  # 0 = 12 hour; 1 = 24 hour
P123: display_clock_instead_of_date # 0 or 1
P143: dhcp_option_2_timezone_override
P144: dhcp_option_42_timeserver_override
P145: dhcp_option_66_bootserver
P146: dhcp_option_12_hostname # ppoe_hostname
P147: dhcp_option_15_domain # ppoe_domain_name
P148: dhcp_option_60_vendor_class_id # ppoe_vendor_class
P150: port_fw_port_num_1
P151: port_fw_port_num_2
P152: port_fw_port_num_3
P153: port_fw_port_num_4
P154: port_fw_port_num_5
P155: port_fw_port_num_6
P156: port_fw_port_num_7
P157: port_fw_port_num_8
P158: port_fw_lan_ip_1
P159: port_fw_lan_ip_2
P160: port_fw_lan_ip_3
P161: port_fw_lan_ip_4
P162: port_fw_lan_ip_5
P163: port_fw_lan_ip_6
P164: port_fw_lan_ip_7
P165: port_fw_lan_ip_9
P166: port_fw_lan_port_1
P167: port_fw_lan_port_2
P168: port_fw_lan_port_3
P169: port_fw_lan_port_4
P170: port_fw_lan_port_5
P171: port_fw_lan_port_6
P172: port_fw_lan_port_7
P173: port_fw_lan_port_8
P174: port_fw_lan_proto_1
P175: port_fw_lan_proto_2
P176: port_fw_lan_proto_3
P177: port_fw_lan_proto_4
P178: port_fw_lan_proto_5
P179: port_fw_lan_proto_6
P180: port_fw_lan_proto_7
P181: port_fw_lan_proto_8
P182: disable_missed_calls_1
P184: use_quick_ip_call_mode
P189: replay_to_icmp_in_wan	# 0 = no
				# 1 = yes
P190: wan_side_http_access	# 0 = no
				# 1 = yes
P191: enable_call_features
P192: http_upgrade_url
P193: automatic_http_upgrade_days
P194: automatic_http_upgrade
P196: end_user_password
P197: proxy_require
P198: special_feature_1 # 100 = Standard
                        # 101 = Nortel MCS
                        # 102 = Broadsoft
                        # 103 = Howdy
                        # 104 = Sonus
                        # 105 = 3COM
P200: caller_id_scheme
P205: polarity_reversal
P206: onhook_voltage
P207: syslog_server
P208: syslog_level
P209: sip_t1_timeout	# 50 = 0_5_sek
			# 100 = 1_sek
			# 200 = 2_sek
P212: firmware_upgrade
P213: firmware_upgrade_tftp_1
P214: firmware_upgrade_tftp_2
P215: firmware_upgrade_tftp_3
P216: firmware_upgrade_tftp_4
P228: fax_mode
P231: device_mode	# 0 = switch
			# 1 = nat_router
P232: firmware_file_prefix
P233: firmware_file_postfix
P234: config_file_prefix
P235: config_file_postfix
P237: config_server_path
P238: always_check_for_new_firmware
P239: register_expiration_delay # in seconds, default 1800
P240: auth_config_file	# 0 = No
			# 1 = Yes
P241: allow_conf_sip_account_in_basic_settings	# 0 or 1
P242: firmware_key	# text (in Hexadecimal Representation)
P243: allow_from_sip_proxy_only # 0 or 1
P244: override_mtu_size	# 0 or 1
P245: onhook_threshold	# 0 = Hookflash OFF
			# 2 = 200 ms
			# 4 = 400 ms
			# 6 = 600 ms
			# 8 = 800 ms (default)
			# 10 = 1000 ms
			# 12 = 1200 ms
P250: sip_t2_interval
P260: session_expiration_1 # in seconds, default 180
P261: min_session_expiration_1 # seconds, minimum 90
P262: caller_request_timer_1 # 0 or 1
P263: callee_request_timer_1 # 0 or 1
P264: force_timer_1 # 0 or 1
P265: force_invite_1 # 0 or 1
P266: uac_specify_refresher_1 # 0=omit, 1=UAC, 2=UAS
P267: uas_specify_refresher_1 # 1=UAC, 2=UAS
P268: anonymous_method	# 0 = Use From Header (default)
			# 1 = Use Privacy Header
P270: account_name_1
P271: account_active_1 # 0 or 1
P272: enable_100rel_1 # 0 or 1
P298: allow_auto_answer_by_call_info
P299: turn_off_speaker_on_remote_disconnect
P301: speed_dial_1_account # 0, 1, 2, 3
P302: speed_dial_1_name
P303: speed_dial_1_userid
P304: speed_dial_2_account
P305: speed_dial_2_name
P306: speed_dial_2_userid
P307: speed_dial_3_account
P308: speed_dial_3_name
P309: speed_dial_3_userid
P310: speed_dial_4_account
P311: speed_dial_4_name
P312: speed_dial_4_userid
P313: speed_dial_5_account
P314: speed_dial_5_name
P315: speed_dial_5_userid
P316: speed_dial_6_account
P317: speed_dial_6_name
P318: speed_dial_6_userid
P319: speed_dial_7_account
P320: speed_dial_7_name
P321: speed_dial_7_userid
P322: lcd_backlight_always_on # 0 or 1
P323: multi_purpose_key_1	# 0 = speed_dial
				# 1 = asterisk_blf
P324: multi_purpose_key_2	# 0 = speed_dial
				# 1 = asterisk_blf
P325: multi_purpose_key_3	# 0 = speed_dial
				# 1 = asterisk_blf
P326: multi_purpose_key_4	# 0 = speed_dial
				# 1 = asterisk_blf
P327: multi_purpose_key_5	# 0 = speed_dial
				# 1 = asterisk_blf
P328: multi_purpose_key_6	# 0 = speed_dial
				# 1 = asterisk_blf
P329: multi_purpose_key_7	# 0 = speed_dial
				# 1 = asterisk_blf
P401: account_active_2 # 0 or 1
P402: sip_server_2
P403: outbound_proxy_2
P404: sip_userid_2
P405: authenticate_id_2
P406: authenticate_password_2
P407: name_2
P408: use_dns_srv_2
P409: userid_is_phone_number_2
P410: sip_registration_2
P411: unregister_on_reboot_2
P412: register_expiration_2
P413: local_sip_port_2
P414: nat_traversal_true_2
P415: subscribe_for_mwi_2
P416: send_dtmf_2 # 0=in-audio, 1=RTP (RFC2833), 2=SIP INFO
P417: account_name_2
P418: proxy_require_2
P419: dial_plan_prefix_2
P420: enable_call_features_2
P421: send_anonymous_2
P422: early_dial_2
P423: account_ring_tone_2 # 0, 1, 2, 3
P424: special_feature_2 # 100 = Standard
                        # 101 = Nortel MCS
                        # 102 = Broadsoft
                        # 103 = Howdy
                        # 104 = Sonus
                        # 105 = 3COM
P425: auto_answer_2
P426: voicemail_userid_2
P427: min_session_expiration_2
P428: caller_request_timer_2
P429: callee_request_timer_2
P430: force_timer_2
P431: force_invite_2
P432: uac_specify_refresher_2
P433: uas_specify_refresher_2
P434: session_expiration_2
P435: enable_100rel_2
P438: allow_auto_answer_by_caller_info
P439: turn_off_speaker_on_remote_disconnect
P440: sip_t1_imeout
P441: sip_t2_interval
P442: disable_missed_calls_2
P451: vocoder_1_2
P452: vocoder_2_2
P453: vocoder_3_2
P454: vocoder_4_2
P455: vocoder_5_2
P456: vocoder_6_2
P457: vocoder_7_2
P458: vocoder_8_2
P501: account_active_3 # 0 or 1
P502: sip_server_3
P503: outbound_proxy_3
P504: sip_userid_3
P505: authenticate_id_3
P506: authenticate_password_3
P507: name_3
P508: use_dns_srv_3
P509: userid_is_phone_number_3
P510: sip_registration_3
P511: unregister_on_reboot_3
P512: register_expiration_3
P513: local_sip_port_3
P514: nat_traversal_true_3
P515: subscribe_for_mwi_3
P516: send_dtmf_3 # 0=in-audio, 1=RTP (RFC2833), 2=SIP INFO
P517: account_name_3
P518: proxy_require_3
P519: dial_plan_prefix_3
P520: enable_call_features_3
P521: send_anonymous_3
P522: early_dial_3
P523: account_ring_tone_3 # 0, 1, 2, 3
P524: special_feature_3 # 100 = Standard
                        # 101 = Nortel MCS
                        # 102 = Broadsoft
                        # 103 = Howdy
                        # 104 = Sonus
                        # 105 = 3COM
P525: auto_answer_3
P526: voicemail_userid_3
P527: min_session_expiration_3
P528: caller_request_timer_3
P529: callee_request_timer_3
P530: force_timer_3
P531: force_invite_3
P532: uac_specify_refresher_3
P533: uas_specify_refresher_3
P534: session_expiration_3
P535: enable_100rel_3
P538: allow_auto_answer_by_caller_info
P539: turn_off_speaker_on_remote_disconnect
P540: sip_t1_imeout
P541: sip_t2_interval
P542: disable_missed_calls_3
P551: vocoder_1_3
P552: vocoder_2_3
P553: vocoder_3_3
P554: vocoder_4_3
P555: vocoder_5_3
P556: vocoder_6_3
P557: vocoder_7_3
P558: vocoder_8_3
P601: account_active_4 # 0 or 1
P602: sip_server_4
P603: outbound_proxy_4
P604: sip_userid_4
P605: authenticate_id_4
P606: authenticate_password_4
P607: name_4
P608: use_dns_srv_4
P609: userid_is_phone_number_4
P610: sip_registration_4
P611: unregister_on_reboot_4
P612: register_expiration_4
P613: local_sip_port_4
P614: nat_traversal_true_4
P615: subscribe_for_mwi_4
P616: send_dtmf_4 # 0=in-audio, 1=RTP (RFC2833), 2=SIP INFO
P617: account_name_4
P618: proxy_require_4
P619: dial_plan_prefix_4
P620: enable_call_features_4
P621: send_anonymous_4
P622: early_dial_4
P623: account_ring_tone_4 # 0, 1, 2, 3
P624: special_feature_4 # 100 = Standard
                        # 101 = Nortel MCS
                        # 102 = Broadsoft
                        # 103 = Howdy
                        # 104 = Sonus
                        # 105 = 3COM
P625: auto_answer_4
P626: voicemail_userid_4
P627: min_session_expiration_4
P628: caller_request_timer_4
P629: callee_request_timer_4
P630: force_timer_4
P631: force_invite_4
P632: uac_specify_refresher_4
P633: uas_specify_refresher_4
P634: session_expiration_4
P635: enable_100rel_4
P638: allow_auto_answer_by_caller_info
P639: turn_off_speaker_on_remote_disconnect
P640: sip_t1_imeout
P641: sip_t2_interval
P642: disable_missed_calls_4
P651: vocoder_1_4
P652: vocoder_2_4
P653: vocoder_3_4
P654: vocoder_4_4
P655: vocoder_5_4
P656: vocoder_6_4
P657: vocoder_7_4
P658: vocoder_8_4
# End of the file
