Wednesday, October 21, 2009

FreePBX, iaxmodem, HylaFAX & DDIs (DIDs)

asterisk-logoIn a move to eventually reduce the number of incoming telephony lines at our office, I have hylafax-logo been exploring the various fax options that can be easily integrated with an Asterisk PBX. Without dissecting all the detail, the route we have decided to implement as a test combines Asterisk (with the FreePBX interface) and HylaFAX with iaxmodem as the magic glue.

We have lots of DDIs (otherwise known as DIDs) broken into a number of ranges that come in to the business either by ISDN30 (E1) or ISDN2 (BRI). The DDI ranges associated with our IDSN2 circuits are exclusively used for fax and are connected to our current Zetafax server. This is great but we also have legacy DDIs that are used for fax within the ranges associated with our ISDN30 circuit which is connected to our Asterisk PBX. This was fine when we had a analogue Zetafax server, but since it went to ISDN2 so we could have proper fax to desktop we have been handling the faxes coming into our ISDN30 by bouncing them straight back out on a second ISDN30 channel to a specified DDI on the ISDN2 circuit. The small cost of the local call between the ISDN30 and ISDN2 circuits isn’t so much of a problem as having to effectively provide three ISDN channels to receive one fax!

Long term if this all works out we will more than likely ditch the Zetafax server and use HylaFAX for all our faxing requirements, both inbound and outbound. But lets get onto the the real topic, how to glue these bits together.

If you are still reading then you must already know that iaxmodem is a software virtual modem that connects to a VOIP PBX (typically Asterisk) using the IAX2 protocol, so that needs no further qualification. You must also know that HylaFAX is UNIX fax server software. What you maybe scratching your head about is how to get the two to work together using DDIs. The assumes you have got iaxmodem and HylaFAX installed, and you have an Asterisk server using the FreePBX management interface. There are lots of HOW-TOs and other information detailing the configuration, but what they all consistently ommited or got plainly wrong is how to implement DDI routing of inbound faxes.

There are five main steps:
  1. Configure your IAX2 extensions & virtual modems in FreePBX and /etc/iaxmodem/.
  2. Create a custom dialplan context in /etc/asterisk/extensions_custom.conf.
  3. Create a ‘Custom Destination’ in FreePBX.
  4. Create ‘Inbound Routes’ for the DDIs you want to use for fax.
  5. Edit /var/spool/hylafax/etc/FaxDispatch.
Step 1 is fairly straightforward. Here you only need to configure as many extensions as concurrent fax calls you want to handle, not an extension per DDI. I’ve seen references on the internet where people must have configured an extension (and iaxmodem) per DDI as they had quantities in the multi-hundreds! We went for 4 for testing purposes.

Step 2 is where things tend to unravel. Below is the normal recommendation of how to configure a custom context in /etc/asterisk/extensions_custom.conf:

[custom-fax-iaxmodem]
exten => s,1,Dial(IAX2/610/${EXTEN})
exten => s,n,Dial(IAX2/611/${EXTEN})
exten => s,n,Dial(IAX2/612/${EXTEN})
exten => s,n,Dial(IAX2/613/${EXTEN})
exten => s,n,Busy
exten => s,n,Hangup

Well it doesn’t work as the dialled digits that get passed with every fax are simply ‘s’. If you check the main Asterisk wiki you’ll see that the ‘s’ extension is normally used when the is no known called number, exactly the reverse of our situation where we want to specifically route on the called number.

What you need is this:

[custom-fax-iaxmodem]
exten => _X.,1,Dial(IAX2/610/${EXTEN})
exten => _X.,n,Dial(IAX2/611/${EXTEN})
exten => _X.,n,Dial(IAX2/612/${EXTEN})
exten => _X.,n,Dial(IAX2/613/${EXTEN})
exten => _X.,n,Busy
exten => _X.,n,Hangup

Here we accept any called number (we choose these with our inbound routes) and pass it one of our four iaxmodems. This passes the dialled digits correctly as we are not resetting the ${EXTEN} variable by using the ‘s’ extension.

Step 3 creates the custom destination to use in conjunction with inbound routes. Goto:
Tools –> Custom Destinations
The ‘Custom Destination’ statement should be:

custom-fax-iaxmodem,${EXTEN},1

With a description of something like ‘HylaFAX-IAXModem Pool’.

Step 4 is easy. Just create a new inbound route for each inbound DDI you want to use for fax and set the destination to be the custom destination you configured in step 3.

Finally Step 5 is edit/create /var/spool/hylafax/etc/FaxDispatch to configure the routing for your faxes by dialled DDI.

# Defaults
FROMADDR=hylafax@example.com;
FILETYPE=tif;

# DDI routing:
case "$CALLID4" in
788498) SENDTO=fred.smith@example.com;;
788497) SENDTO=harry.bloggs@example.com;;

# everything else goes to default case:
*) SENDTO=general-fax@example.com;;
esac

Done!

Tuesday, August 25, 2009

Windows 7 doesn’t map my drives at login

windows7
Windows 7 is out and my work laptop got upgraded to the RTM version of Windows 7 Enterprise last week. The whole process was very smooth, the only software that failed to run this time round was Crystal Reports v7.5 (this even has a 16bit option in the installer it is that old!). That was easily solved by installing it alongside Access 97 in the XP mode virtual machine.

Only two problems remained; one being that I could no longer access Samba shares on a FreeNAS box we use as a dumping ground, the other that the network drives mapped by our KiXtart login script were not showing in explorer.

The strange thing was that the drives were visible in dialog boxes in some applications, so the Group Policy was being processed and it wasn’t an incompatibility with KiXtart. It was clearly something going on at the desktop level in Windows.

The heavy handed approach would have been to permanently disable UAC as I found that everything worked as normal with this turned off. However UAC is a useful security feature that is a lot less obstructive in Windows 7 compared to Vista which I wanted to keep on.

After a bit of research I discovered that this is a ‘feature’ of the interaction between UAC and login scripts during the login process if you have local administrator rights to the PC. It is documented in Microsoft Knowledgebase Article 937624. I wont go into the details as you can read them yourself, however the solution is a straight forward registry change to configure the ‘EnableLinkedConnections’ registry value.

Either copy the following text into a empty file and rename to fix.reg (or something a bit more memorable if you are going to keep it) and then double-click the file. This will make the registry change for you.

Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System]
"EnableLinkedConnections"=dword:00000001

Alternatively you can follow the manual instructions on the Microsoft Knowledgebase Article.

Tuesday, August 4, 2009

Why wont my Response.WriteFile work through a http proxy ?

Today I've been finally finishing up the literature download functionality for one of our websites. With the dynamic Zip file building sorted using the open source Ionic.Zip component it was just a matter of sending out an e-mail with a unique download link and then serving up the Zip file on demand.

Streaming files in ASP.NET is a topic that is well covered on the Internet and the System.Web.HttpResponse object has an nice easy WriteFile method that takes the full path to a file and returns it to the client. Fine and dandy in testing but when the new functionality was deployed to the live site and a couple of staff had a go, nothing downloaded.

All my testing had been performed either at localhost (IIS7 on Vista Enterprise) or on our development server (IIS6 on Server 2003). The key thing was that all the HTTP traffic had been local. Now the code was on our live website the HTTP traffic was going over the Internet and onto our LAN through our Bloxx proxy server. I had tested the live site succesfully from my PC but I don't use the proxy.

So what was going on. After reconfiguring IE8 for the Bloxx proxy, click the link, the browser window opens but a blank page and a stuck progress bar at the bottom of the page. Turn the proxy off, no problem. Hit the link browser window opens, Open/Save dialog pops up.

So the enevitable Googling starts and I find nothing specfic. Was it the Content-Type of application/octet-stream the problem? Switching that out for application/zip & application/binary was no help. Then I see a web page with something I hadn't seen before; the 'Content-Length' header defining the file size. Compile & commit code, update devel site code, rsync to live site and it works immediately:

Private Sub TransmitFile(ByVal FileName As String)

 Dim objFile As System.IO.FileInfo = New System.IO.FileInfo(FileName)

 If objFile.Exists Then
 
  Dim strTransmitName As String = "download-" & Now.Date.ToShortDateString & "." & objFile.Extension

  '# send file

  Response.Clear()
  Response.BufferOutput = False

  Response.ContentType = "application/octet-stream"
  Response.AppendHeader("Content-Disposition", "attachment; filename=" & strTransmitName)
  Response.AppendHeader("Content-Length", objFile.Length.ToString)

  Response.WriteFile(objFile.FullName)

  HttpContext.Current.ApplicationInstance.CompleteRequest()

 End If

End Sub

The easiest way to get the size of your file for the Content-Length is using the Length property of a System.IO.FileInfo object. This is easy to instantiate with the full path to the file and it also has a handy Exists property so you can test to see if the file is indeed there.

Two further things are worth comment. Firstly the Content-Disposition header gives you a nice easy way of renaming your file as it is streamed back to the browser. Our files are named with GUIDs which we resuse as the download ID. Great for identifying a file in code on the server, not so good for finding the file again when you have downloaded it to your PC. We rename the file with our company name and the date but in this code I've created a name using the word 'download' and the date.

Secondly there is some controversy over the method to end your HTTP stream. Response.End() is the obvious choice but does nasty things to the current thread and fires a ThreadAbortion exception. This is discussed on Rick Strahl's Blog and there doesn't appear to be a fully right answer. Calling HttpContext.Current.ApplicationInstance.CompleteRequest() works for me in this situation but Rick's post and the subsequent comments make interesting reading on alternatives.

Saturday, August 1, 2009

Where are my dynamic TabPanels ?

Suffering from invisible ASP.NET TabPanels when you try to create them dynamically?

I'm currently reimplementing some functionality on one of our main websites at work. The page lists out literature  defined in the backend database and dynamically outputs to the page with thumbnails.

To accomodate an ever increasing amount of litreature we decided to section the page using tabs. Our backend database holds products in a hierarchical tree of groups (litreature is just product from the DB's point of view) so it made sense to generate tabs on the page dynamically using sub-groups of a master litreature group.

ASP.NET 2.0 has a nice AJAX control tookit which is built on top of the ASP.NET AJAX framework (open-source too). I've used some of the other controls on other projects, but not the TabContainer and TabPanels.

Great I thought; create my UserControl, add a TabContainer and then populate the TabContainer from the code-behind as below:

Private Sub LoadTabs()

 Dim MyCatalogueGroups As New Groups

 MyCatalogueGroups = MyCatalogueGroups.GetChildGroups(Me.AppSettings.CatalogueGroup)

 Dim intCurrentTabIndex As Int32
 Dim intDefaultTabIndex As Int32

 For Each MyCatalogueGroup As Group In MyCatalogueGroups

  Dim MyTabPanel As TabPanel = New TabPanel
  
  MyTabPanel.HeaderText = MyCatalogueGroup.Description

  '.....panel contents code removed for brevity
  
  Me.ctlTabContainer.Tabs.Add(MyTabPanel)

  If Me.AppSettings.DefaultCatalogueSubGroup = MyCatalogueGroup.ID Then
   intDefaultTabIndex = intCurrentTabIndex
  End If

  intCurrentTabIndex += 1

 Next

 Me.ctlTabContainer.ActiveTab = Me.ctlTabContainer.Tabs(intDefaultTabIndex)

End Sub

Compile, Debug ... nothing. No error just nothing. Check the browser page source and there they are, but whats all this visible false stuff?

A bit of Googling later and I discover this is a bit of a mystery with the AJAX Tab controls. The solution is to call some JavaScript at the client so that the dynamically generated TabPanels can be made visible client-side. The sample code I found didn't work for me as I got client-side JavaScript errors as it couldn't ID the TabContainer control in the control collection. However it was a massive help and my working code is below:

<script type="text/javascript">
 function pageLoad(){
  // Required when adding TabPanels programmatically.
  var strTabContainerID = '<%= (FindControl("ctlTabContainer")).ClientID %>';
  $get(strTabContainerID).style.visibility = "visible";    
 }
</script>

The key here is line 4 where you specify the ID of your TabContainer control.

JavaScript isn't my thing and through a bit of trial and error I found that the best placement for the code snippet is actually adjacent to the TabContainer markup in your .ascx file.

  function pageLoad(){
   // Required when adding TabPanels programmatically.
   var strTabContainerID = '<%= (FindControl("ctlTabContainer")).ClientID %>';
   $get(strTabContainerID).style.visibility = "visible";    
  }
 </script>

 <cc1:TabContainer ID="ctlTabContainer" runat="server" />
 
</asp:View>

I am using the TabContainer in a MultiView contol and I discovered that placing the JavaScript in the host page head creates client-side JavaScript errors when the View holding the TabContainer is not rendered back to the web browser (obvious really).

The only other thing to watch out for is creating your dynamic tabs as the page initialises rather than when the page loads, otherwise you will get this error if your page causes a postback event:

Specified Argument was out of the range of valid values 
Parameter Name: Index

This is discussed on the ASP.NET Forums and on 4GuysFromRolla.com, but essentially in ASP.NET 2.0 you can implement your own handler for the Page.Init event in the page or UserControl where you create your dynamic controls.

 Protected Sub LoadDynamicControls(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Init

  Me.LoadTabs()

 End Sub

I hope this helps somebody out.