#! /usr/bin/perl -w # # Written by Oron Peled # Copyright (C) 2007, Xorcom # This program is free software; you can redistribute and/or # modify it under the same terms as Perl itself. # # $Id$ # use strict; use File::Basename; BEGIN { my $dir = dirname($0); unshift(@INC, "$dir", "$dir/perl_modules"); } use Dahdi; use Dahdi::Xpp; use Dahdi::Config::GenconfDefaults; my $genconf_defaults; my %default_context = ( FXO => 'from-pstn', FXS => 'from-internal', IN => 'astbank-input', OUT => 'astbank-output', BRI_TE => 'from-pstn', BRI_NT => 'from-internal', E1_TE => 'from-pstn', T1_TE => 'from-pstn', J1_TE => 'from-pstn', E1_NT => 'from-internal', T1_NT => 'from-internal', J1_NT => 'from-internal', ); my %default_group = ( FXO => 0, FXS => "5", IN => '', OUT => '', BRI_TE => 0, BRI_NT => 6, E1_TE => 0, T1_TE => 0, J1_TE => 0, E1_NT => 6, T1_NT => 6, J1_NT => 6, ); my $fxs_default_start = 'ls'; my %default_dahdi_signalling = ( FXO => 'fxsks', FXS => "fxo{fxs_default_start}", IN => "fxo{fxs_default_start}", OUT => "fxo{fxs_default_start}", ); my %default_chan_dahdi_signalling = ( FXO => 'fxs_ks', FXS => "fxo_{fxs_default_start}", IN => "fxo_{fxs_default_start}", OUT => "fxo_{fxs_default_start}", ); my $file = ''; my $base_exten = 4000; my $fxs_immediate = 'no'; my $lc_country = 'us'; my $loadzone = $lc_country; my $defaultzone = $lc_country; my $bri_sig_style = 'bri_ptmp'; my $brint_overlap = 'no'; my $pri_termtype = 'SPAN/* TE'; my $pri_connection_type = 'PRI'; # PRI or R2 my $r2_idle_bits = '1101'; my $echo_can = 'mg2'; my $bri_hardhdlc= 'no'; my %dahdi_default_vars = ( GENCONF_FILE => \$file, base_exten => \$base_exten, fxs_immediate => \$fxs_immediate, fxs_default_start => \$fxs_default_start, lc_country => [ \$loadzone, \$defaultzone, ], context_lines => \$default_context{FXO}, context_phones => \$default_context{FXS}, context_input => \$default_context{IN}, context_output => \$default_context{OUT}, group_phones => [ \$default_group{FXS}, \$default_group{IN}, \$default_group{OUT}, ], group_lines => \$default_group{FXO}, bri_sig_style => \$bri_sig_style, brint_overlap => \$brint_overlap, pri_termtype => \$pri_termtype, pri_connection_type => \$pri_connection_type, r2_idle_bits => \$r2_idle_bits, echo_can => \$echo_can, bri_hardhdlc => \$bri_hardhdlc, ); sub map_dahdi_defaults { my %defaults = @_; foreach my $name (keys %defaults) { my $val = $defaults{$name}; my $ref = $dahdi_default_vars{$name}; my $type = ref $ref; my @vars = (); # Some broken shells (msh) export even variables # That where not defined. Work around that. next unless defined $val && $val ne ''; if($type eq 'SCALAR') { @vars = ($ref); } elsif($type eq 'ARRAY') { @vars = @$ref; } else { die "$0: Don't know how to map '$name' (type=$type)\n"; } foreach my $v (@vars) { $$v = $val; #printf STDERR "%-20s %s\n", $v, $val; } } } my $dahdiconf_file; my $chan_dahdi_channels_file; my $users_file; my $chan_dahdi_conf_file; my $unicall_channels_file; my %files = ( dahdi => { file => \$dahdiconf_file, func => \&gen_dahdiconf }, chan_dahdi => { file => \$chan_dahdi_channels_file, func => \&gen_chan_dahdi_channelsconf }, users => { file => \$users_file, func => \&gen_usersconf }, unicall => { file => \$unicall_channels_file, func => \&gen_unicall_channels }, chan_dahdi_full => { file => \$chan_dahdi_conf_file, func => \&gen_chan_dahdi_conf }, ); my @default_files = ("dahdi", "chan_dahdi"); my @spans = Dahdi::spans(); sub bchan_range($) { my $span = shift || die; my $first_chan = ($span->chans())[0]; my $first_num = $first_chan->num(); my $range_start = $first_num; my @range; my $prev = undef; die unless $span->is_digital(); foreach my $c (@{$span->bchan_list()}) { my $curr = $c + $first_num; if(!defined($prev)) { $prev = $curr; } elsif($curr != $prev + 1) { push(@range, sprintf("%d-%d", $range_start, $prev)); $range_start = $curr; } $prev = $curr; } if($prev >= $first_num) { push(@range, sprintf("%d-%d", $range_start, $prev)); } return join(',', @range); } sub gen_dahdi_signalling($) { my $chan = shift || die; my $type = $chan->type; my $num = $chan->num; die "channel $num type $type is not an analog channel\n" if $chan->span->is_digital(); if($type eq 'EMPTY') { printf "# channel %d, %s, no module.\n", $num, $chan->fqn; return; } my $sig = $default_dahdi_signalling{$type} || die "unknown default dahdi signalling for chan $num type $type"; if ($type eq 'IN') { printf "# astbanktype: input\n"; } elsif ($type eq 'OUT') { printf "# astbanktype: output\n"; } printf "$sig=$num\n"; print_echo_can($num); } sub print_echo_can($) { my $chans = shift; # channel or range of channels. return if ($echo_can eq 'none'); print "echocanceller=$echo_can,$chans\n"; } my $bri_te_last_timing = 1; sub gen_dahdi_digital($) { my $span = shift || die; my $num = $span->num() || die; die "Span #$num is analog" unless $span->is_digital(); my $termtype = $span->termtype() || die "$0: Span #$num -- unkown termtype [NT/TE]\n"; my $timing; my $lbo = 0; my $framing = $span->framing() || die "$0: No framing information for span #$num\n"; my $coding = $span->coding() || die "$0: No coding information for span #$num\n"; my $span_crc4 = $span->crc4(); $span_crc4 = (defined $span_crc4) ? ",$span_crc4" : ''; my $span_yellow = $span->yellow(); $span_yellow = (defined $span_yellow) ? ",$span_yellow" : ''; # "MFC/R2 does not normally use CRC4" # FIXME: a finer way to override: if ($pri_connection_type eq 'R2') { $span_crc4 = ''; $framing = 'cas'; } my $dchan_type = 'dchan'; if ($span->is_bri() && ($bri_hardhdlc eq 'yes')) { $dchan_type = 'hardhdlc'; } $timing = ($termtype eq 'NT') ? 0 : $bri_te_last_timing++; printf "span=%d,%d,%d,%s,%s%s%s\n", $num, $timing, $lbo, $framing, $coding, $span_crc4, $span_yellow; printf "# termtype: %s\n", lc($termtype); if ($pri_connection_type eq 'PRI') { printf "bchan=%s\n", bchan_range($span); my $dchan = $span->dchan(); printf "$dchan_type=%d\n", $dchan->num(); } elsif ($pri_connection_type eq 'R2' ) { my $idle_bits = $r2_idle_bits; printf "cas=%s:$idle_bits\n", bchan_range($span); printf "dchan=%d\n", $span->dchan()->num(); } print_echo_can(bchan_range($span)); } sub gen_unicall_channels($) { my $file = shift || die; die "Only for R2" unless $pri_connection_type eq 'R2'; rename "$file", "$file.bak" or $! == 2 # ENOENT (No dependency on Errno.pm) or die "Failed to backup old config: $!\n"; open(F, ">$file") || die "$0: Failed to open $file: $!\n"; my $old = select F; printf "; Autogenerated by %s on %s -- do not hand edit\n", $0, scalar(localtime); print "; This file should be #included in unicall.conf\n\n"; foreach my $span (@spans) { next unless $span->is_digital(); printf "; Span %d: %s %s\n", $span->num, $span->name, $span->description; my $idle_bits = $r2_idle_bits; printf "protocolend=%s\n", ($span->termtype() eq 'TE') ? 'cpe' : 'co'; printf "channel=%s\n", bchan_range($span); print "\n"; } close F; select $old; } sub gen_dahdiconf($) { my $file = shift || die; rename "$file", "$file.bak" or $! == 2 # ENOENT (No dependency on Errno.pm) or die "Failed to backup old config: $!\n"; open(F, ">$file") || die "$0: Failed to open $file: $!\n"; my $old = select F; printf "# Autogenerated by %s on %s -- do not hand edit\n", $0, scalar(localtime); print <<"HEAD"; # Dahdi Configuration File # # This file is parsed by the Dahdi Configurator, dahdi_cfg # HEAD foreach my $span (@spans) { printf "# Span %d: %s %s\n", $span->num, $span->name, $span->description; if($span->is_digital()) { gen_dahdi_digital($span); } else { foreach my $chan ($span->chans()) { if(1 || !defined $chan->type) { my $type = $chan->probe_type; my $num = $chan->num; die "Failed probing type for channel $num" unless defined $type; $chan->type($type); } gen_dahdi_signalling($chan); } } print "\n"; } print <<"TAIL"; # Global data loadzone = $loadzone defaultzone = $defaultzone TAIL close F; select $old; } my %DefaultConfigs = ( context => 'default', group => '63', # FIXME: should not be needed. overlapdial => 'no', busydetect => 'no', rxgain => 0, txgain => 0, ); sub reset_chan_dahdi_values { foreach my $arg (@_) { if (exists $DefaultConfigs{$arg}) { print "$arg = $DefaultConfigs{$arg}\n"; } else { print "$arg =\n"; } } } sub gen_chan_dahdi_digital($) { my $span = shift || die; my $num = $span->num() || die; die "Span #$num is analog" unless $span->is_digital(); if($span->is_pri && $pri_connection_type eq 'R2') { printf "; Skipped: $pri_connection_type config generated into $unicall_channels_file\n\n"; return; } my $type = $span->type() || die "$0: Span #$num -- unkown type\n"; my $termtype = $span->termtype() || die "$0: Span #$num -- unkown termtype [NT/TE]\n"; my $group = $default_group{"$type"}; my $context = $default_context{"$type"}; my @to_reset = qw/context group/; die "$0: missing default group (termtype=$termtype)\n" unless defined($group); die "$0: missing default context\n" unless $context; my $sig = $span->signalling || die "missing signalling info for span #$num type $type"; grep($bri_sig_style eq $_, 'bri', 'bri_ptmp', 'pri') or die "unknown signalling style for BRI"; if($span->is_bri() and $bri_sig_style eq 'bri_ptmp') { $sig .= '_ptmp'; } if ($span->is_bri() && $termtype eq 'NT' && $brint_overlap eq 'yes') { print "overlapdial = yes\n"; push(@to_reset, qw/overlapdial/); } $group .= "," . (10 + $num); # Invent unique group per span printf "group=$group\n"; printf "context=$context\n"; printf "switchtype = %s\n", $span->switchtype; printf "signalling = %s\n", $sig; printf "channel => %s\n", bchan_range($span); reset_chan_dahdi_values(@to_reset); } sub gen_chan_dahdi_channel($) { my $chan = shift || die; my $type = $chan->type; my $num = $chan->num; die "channel $num type $type is not an analog channel\n" if $chan->span->is_digital(); my $exten = $base_exten + $num; my $sig = $default_chan_dahdi_signalling{$type}; my $context = $default_context{$type}; my $group = $default_group{$type}; my $callerid; my $immediate; return if $type eq 'EMPTY'; die "missing default_chan_dahdi_signalling for chan #$num type $type" unless $sig; $callerid = ($type eq 'FXO') ? 'asreceived' : sprintf "\"Channel %d\" <%04d>", $num, $exten; if($type eq 'IN') { $immediate = 'yes'; } # FIXME: $immediage should not be set for 'OUT' channels, but meanwhile # it's better to be compatible with genzaptelconf $immediate = 'yes' if $fxs_immediate eq 'yes' and $sig =~ /^fxo_/; my $signalling = $chan->signalling; $signalling = " " . $signalling if $signalling; my $info = $chan->info; $info = " " . $info if $info; printf ";;; line=\"%d %s%s%s\"\n", $num, $chan->fqn, $signalling, $info; printf "signalling=$sig\n"; printf "callerid=$callerid\n"; printf "mailbox=%04d\n", $exten unless $type eq 'FXO'; if(defined $group) { printf "group=$group\n"; } printf "context=$context\n"; printf "immediate=$immediate\n" if defined $immediate; printf "channel => %d\n", $num; # Reset following values to default printf "callerid=\n"; printf "mailbox=\n" unless $type eq 'FXO'; if(defined $group) { printf "group=\n"; } printf "context=default\n"; printf "immediate=no\n" if defined $immediate; print "\n"; } sub gen_chan_dahdi_channelsconf($) { my $file = shift || die; rename "$file", "$file.bak" or $! == 2 # ENOENT (No dependency on Errno.pm) or die "Failed to backup old config: $!\n"; open(F, ">$file") || die "$0: Failed to open $file: $!\n"; my $old = select F; printf "; Autogenerated by %s on %s -- do not hand edit\n", $0, scalar(localtime); print <<"HEAD"; ; Dahdi Channels Configurations (chan_dahdi.conf) ; ; This is not intended to be a complete chan_dahdi.conf. Rather, it is intended ; to be #include-d by /etc/asterisk/chan_dahdi.conf that will include the global settings ; HEAD foreach my $span (@spans) { printf "; Span %d: %s %s\n", $span->num, $span->name, $span->description; if($span->is_digital()) { gen_chan_dahdi_digital($span); } else { foreach my $chan ($span->chans()) { gen_chan_dahdi_channel($chan); } } print "\n"; } close F; select $old; } sub gen_users_channel($) { my $chan = shift || die; my $type = $chan->type; my $num = $chan->num; die "channel $num type $type is not an analog channel\n" if $chan->span->is_digital(); my $exten = $base_exten + $num; my $sig = $default_chan_dahdi_signalling{$type}; my $full_name = "$type $num"; die "missing default_chan_dahdi_signalling for chan #$num type $type" unless $sig; print << "EOF"; [$exten] callwaiting = yes context = numberplan-custom-1 fullname = $full_name cid_number = $exten hasagent = no hasdirectory = no hasiax = no hasmanager = no hassip = no hasvoicemail = yes host = dynamic mailbox = $exten threewaycalling = yes vmsecret = 1234 secret = 1234 signalling = $sig dahdichan = $num registeriax = no registersip = no canreinvite = no nat = no dtmfmode = rfc2833 disallow = all allow = all EOF } # generate users.conf . The specific users.conf is strictly oriented # towards using with the asterisk-gui . # # This code could have generated a much simpler and smaller # configuration file, had there been minimal level of support for # configuration templates in the asterisk configuration rewriting. Right # now Asterisk's configuration rewriting simply freaks out in the face # of templates: http://bugs.digium.com/11442 . sub gen_usersconf($) { my $file = shift || die; rename "$file", "$file.bak" or $! == 2 # ENOENT (No dependency on Errno.pm) or die "Failed to backup old config: $!\n"; open(F, ">$file") || die "$0: Failed to open $file: $!\n"; my $old = select F; print <<"HEAD"; ;! ;! Automatically generated configuration file ;! Filename: @{[basename($file)]} ($file) ;! Generator: $0 ;! Creation Date: @{[scalar(localtime)]} ;! [general] ; ; Full name of a user ; fullname = New User ; ; Starting point of allocation of extensions ; userbase = @{[$base_exten+1]} ; ; Create voicemail mailbox and use use macro-stdexten ; hasvoicemail = yes ; ; Set voicemail mailbox @{[$base_exten+1]} password to 1234 ; vmsecret = 1234 ; ; Create SIP Peer ; hassip = no ; ; Create IAX friend ; hasiax = no ; ; Create Agent friend ; hasagent = no ; ; Create H.323 friend ; ;hash323 = yes ; ; Create manager entry ; hasmanager = no ; ; Remaining options are not specific to users.conf entries but are general. ; callwaiting = yes threewaycalling = yes callwaitingcallerid = yes transfer = yes canpark = yes cancallforward = yes callreturn = yes callgroup = 1 pickupgroup = 1 localextenlength = @{[length($base_exten)]} HEAD foreach my $span (@spans) { next unless grep { $_ eq $span->type} ( 'FXS', 'IN', 'OUT' ); printf "; Span %d: %s %s\n", $span->num, $span->name, $span->description; foreach my $chan ($span->chans()) { gen_users_channel($chan); } print "\n"; } close F; select $old; } sub gen_chan_dahdi_conf($) { my $file = shift || die; open(F, ">>$file") || die "$0: Failed to open $file: $!\n"; my $old = select F; foreach my $span (@spans) { next unless $span->type eq 'FXO'; my $current_sig = ""; for my $chan ($span->chans()) { my $chan_num = $chan->num; if ($default_chan_dahdi_signalling{$chan->type} ne $current_sig) { $current_sig = $default_chan_dahdi_signalling{$chan->type}; print "\nsignalling = $current_sig"; print "\nchannel => $chan_num"; } else { print ",$chan_num"; } } print "\n"; } close F; select $old; } sub set_defaults { # Source default files my $default_file = $ENV{GENCONF_PARAMETERS} || "/etc/dahdi/genconf_parameters"; $genconf_defaults = Dahdi::Config::GenconfDefaults->new($default_file); #$genconf_defaults->dump; map_dahdi_defaults(%$genconf_defaults); foreach my $span (@spans) { if($span->is_pri) { $span->pri_set_fromconfig($genconf_defaults); } } # Fixups foreach my $val (values %default_dahdi_signalling, values %default_chan_dahdi_signalling) { $val =~ s/{fxs_default_start}/$fxs_default_start/g; } $dahdiconf_file = $ENV{DAHDI_CONF_FILE} || "/etc/dahdi/system.conf"; $chan_dahdi_channels_file = $ENV{CHAN_DAHDI_CHANNELS_FILE} || "/etc/asterisk/dahdi-channels.conf"; $users_file = $ENV{USERS_FILE} || "/etc/asterisk/users.conf"; $unicall_channels_file = $ENV{UNICALL_CHANNELS_FILE} || "/etc/asterisk/unicall-channels.conf"; $chan_dahdi_conf_file = $ENV{CHAN_DAHDI_CONF_FILE} || "/etc/asterisk/chan_dahdi.conf"; } sub parse_args { push(@ARGV, 'unicall') if $pri_connection_type eq 'R2'; for my $file (@ARGV) { die "$0: Unknown file '$file'" unless defined $files{$file}; push @default_files, $file; } } sub generate_files { for my $file (@default_files) { &{$files{$file}->{func}}(${$files{$file}->{file}}); } } set_defaults; parse_args; generate_files; __END__ =head1 NAME dahdi_genconf - Generate configuration for dahdi channels. =head1 SYNOPSIS dahdi_genconf [FILES...] =head1 DESCRIPTION This script generate configuration files for Dahdi hardware. Currently it can generate three files: dahdi, chan_dahdi, users and chan_dahdi_full (see below). Without arguments, it generates only dahdi and chan_dahdi. =over 4 =item dahdi - /etc/dahdi/system.conf Configuration for dahdi_cfg(1). It's location may be overriden by the environment variable DAHDI_CONF_FILE. =item chan_dahdi - /etc/asterisk/dahdi-channels.conf Configuration for asterisk(1). It should be included in the main /etc/asterisk/chan_dahdi.conf. It's location may be overriden by the environment variable CHAN_DAHDI_CHANNELS_FILE. =item users - /etc/asterisk/users.conf Configuration for asterisk(1) and AsteriskGUI. It's location may be overriden by the environment variable USERS_FILE. =item chan_dahdi_full - /etc/asterisk/chan_dahdi.conf Configuration for asterisk(1) and AsteriskGUI. It's location may be overriden by the environment variable CHAN_DAHDI_CONF_FILE. =back