Welcome to zewaren.net. This site presents myself and mostly archives the solutions to some problems I once had.

Building a FreeBSD small office IPBX server. Part 2: Installing and configuring the ISDN Card

Not so frequently asked questions and stuff: 

The FreeBSD logoImageImage

Introduction

In part 1, we installed FreeBSD 10.1 on a Soekris net5501-70.

Now we're going to plug in a ISDN BRI card, and configure the FreeBSD kernel to use it.

Step 4: plugging the card

I bought a second hand QuadBRI card: the Digium B410P. It features a HFC-4S chipset from Cologne and it has 4 BRI ports.

A Digium Euro ISDN BRI - B410P

Notice the BRI ports have jumpers, which should be set to the right position depending on whether those ports should behave in TE or in NT mode.
The jumber configuration of the B410P

The card is correctly detected by FreeBSD:

pciconf -lv
...
none1@pci0:0:14:0:      class=0x020400 card=0xb410d161 chip=0xb410d161 rev=0x01 hdr=0x00
    vendor     = 'Digium, Inc.'
    device     = 'Wildcard B410 quad-BRI card'
    class      = network
    subclass   = ISDN

Step 5: installing and configuring the driver

I'll be using the FreebSD port of DAHDI.

The kernel modules can be installed from the port, as well as the userland binaries.

make -C /usr/ports/misc/dahdi-kmod26 install clean
make -C /usr/ports/misc/dahdi install clean

I'll be loading only the modules I need, hence the following configuration in rc.conf

dahdi_enable="YES"
dahdi_modules="/usr/local/lib/dahdi/wcb4xxp.ko /usr/local/lib/dahdi/dahdi_transcode.ko"

If you load the modules manually using kldload, you'll need to set the correct permission to the /dev/dahdi devices, or asterisk won't be able to access them. This is done by the rc script.

Now the driver needs to be configured, in /usr/local/etc/dahdi/system.conf

The router (a OneAccess 150) from my ISP (Completel) provides 4 French T0 ports, which can be characterized by the following (somewhat hard to find, some are synonyms) keywords:

  • 2B+D
  • Euro-ISDN
  • ETSI
  • DSS1
  • Router in NT, IPBX in TE

Hence, the driver configuration:

loadzone = fr
defaultzone=fr

span=1,1,0,ccs,ami
bchan=1,2
hardhdlc=3

span=2,2,0,ccs,ami
bchan=4,5
hardhdlc=6

span=3,0,0,ccs,ami
bchan=7,8
hardhdlc=9

span=4,0,0,ccs,ami
bchan=10,11
hardhdlc=12

The scan correctly detects the 4 spans:

# dahdi_scan
[1]
active=yes
alarms=OK
description=B4XXP (PCI) Card 0 Span 1
name=B4/0/1
manufacturer=Digium
devicetype=Wildcard B410P
location=PCI Bus 00 Slot 14
basechan=1
totchans=3
irq=0
type=digital-TE
syncsrc=0
lbo=0 db (CSU)/0-133 feet (DSX-1)
coding_opts=B8ZS,AMI,HDB3
framing_opts=ESF,D4,CCS,CRC4
coding=AMI
framing=CCS
[2]
active=yes
alarms=OK
description=B4XXP (PCI) Card 0 Span 2
name=B4/0/2
manufacturer=Digium
devicetype=Wildcard B410P
location=PCI Bus 00 Slot 14
basechan=4
totchans=3
irq=0
type=digital-TE
syncsrc=0
lbo=0 db (CSU)/0-133 feet (DSX-1)
coding_opts=B8ZS,AMI,HDB3
framing_opts=ESF,D4,CCS,CRC4
coding=AMI
framing=CCS
[3]
active=yes
alarms=OK
description=B4XXP (PCI) Card 0 Span 3
name=B4/0/3
manufacturer=Digium
devicetype=Wildcard B410P
location=PCI Bus 00 Slot 14
basechan=7
totchans=3
irq=0
type=digital-TE
syncsrc=0
lbo=0 db (CSU)/0-133 feet (DSX-1)
coding_opts=B8ZS,AMI,HDB3
framing_opts=ESF,D4,CCS,CRC4
coding=AMI
framing=CCS
[4]
active=yes
alarms=OK
description=B4XXP (PCI) Card 0 Span 4
name=B4/0/4
manufacturer=Digium
devicetype=Wildcard B410P
location=PCI Bus 00 Slot 14
basechan=10
totchans=3
irq=0
type=digital-TE
syncsrc=0
lbo=0 db (CSU)/0-133 feet (DSX-1)
coding_opts=B8ZS,AMI,HDB3
framing_opts=ESF,D4,CCS,CRC4
coding=AMI
framing=CCS

The configuration correctly detects the 4*2 B channels (1,2,4,5,7,8,10,11) and the 4 D channels (3,6,9,12).

# dahdi_cfg -vvv
DAHDI Tools Version - 2.4.0-rc1

DAHDI Version:
Echo Canceller(s): HWEC
Configuration
======================

SPAN 1: CCS/ AMI Build-out: 0 db (CSU)/0-133 feet (DSX-1)
SPAN 2: CCS/ AMI Build-out: 0 db (CSU)/0-133 feet (DSX-1)
SPAN 3: CCS/ AMI Build-out: 0 db (CSU)/0-133 feet (DSX-1)
SPAN 4: CCS/ AMI Build-out: 0 db (CSU)/0-133 feet (DSX-1)

Channel map:

Channel 01: Clear channel (Default) (Echo Canceler: none) (Slaves: 01)
Channel 02: Clear channel (Default) (Echo Canceler: none) (Slaves: 02)
Channel 03: Hardware assisted D-channel (Default) (Echo Canceler: none) (Slaves: 03)
Channel 04: Clear channel (Default) (Echo Canceler: none) (Slaves: 04)
Channel 05: Clear channel (Default) (Echo Canceler: none) (Slaves: 05)
Channel 06: Hardware assisted D-channel (Default) (Echo Canceler: none) (Slaves: 06)
Channel 07: Clear channel (Default) (Echo Canceler: none) (Slaves: 07)
Channel 08: Clear channel (Default) (Echo Canceler: none) (Slaves: 08)
Channel 09: Hardware assisted D-channel (Default) (Echo Canceler: none) (Slaves: 09)
Channel 10: Clear channel (Default) (Echo Canceler: none) (Slaves: 10)
Channel 11: Clear channel (Default) (Echo Canceler: none) (Slaves: 11)
Channel 12: Hardware assisted D-channel (Default) (Echo Canceler: none) (Slaves: 12)

12 channels to configure.

Setting echocan for channel 1 to none
Setting echocan for channel 2 to none
Setting echocan for channel 3 to none
Setting echocan for channel 4 to none
Setting echocan for channel 5 to none
Setting echocan for channel 6 to none
Setting echocan for channel 7 to none
Setting echocan for channel 8 to none
Setting echocan for channel 9 to none
Setting echocan for channel 10 to none
Setting echocan for channel 11 to none
Setting echocan for channel 12 to none

Step 6: Configuring the card in Asterisk

Let's install Asterisk.

make -C /usr/ports/net/asterisk/ install clean

Makes sure you configured to build to have support for DAHDI.

Configure DAHDI accordingly to your requirements in /usr/local/etc/asterisk/chan_dahdi.conf. Here's my configuration:

[trunkgroups]

[channels]
language=fr
switchtype=euroisdn
usecallerid=yes
cidsignalling=v23
callwaiting=no
usecallingpres=yes
callwaitingcallerid=yes
threewaycalling=no
transfer=no
canpark=no
cancallforward=no
callreturn=yes
echocancel=yes
echocancelwhenbridged=no
;pridialplan=dynamic
pridialplan=unknown
prilocaldialplan=dynamic
context=from-isdn
callerid = Rien SAS 
group=1
callgroup=1
pickupgroup=1
signalling=bri_cpe
channel => 1,2,4,5,7,8,10,11

Start Asterisk and check the card status:

ipbx50*CLI> dahdi show status
Description                              Alarms  IRQ    bpviol CRC    Fra Codi Options  LBO
B4XXP (PCI) Card 0 Span 1                OK      0      0      0      CCS AMI           0 db (CSU)/0-133 feet (DSX-1)
B4XXP (PCI) Card 0 Span 2                OK      0      0      0      CCS AMI           0 db (CSU)/0-133 feet (DSX-1)
B4XXP (PCI) Card 0 Span 3                OK      0      0      0      CCS AMI           0 db (CSU)/0-133 feet (DSX-1)
B4XXP (PCI) Card 0 Span 4                OK      0      0      0      CCS AMI           0 db (CSU)/0-133 feet (DSX-1)

List the available channels:

ipbx50*CLI> dahdi show channels
   Chan Extension       Context         Language   MOH Interpret        Blocked    State      Description
 pseudo                 default                    default                         In Service
      1                 from-isdn       fr         default                         In Service
      2                 from-isdn       fr         default                         In Service
      4                 from-isdn       fr         default                         In Service
      5                 from-isdn       fr         default                         In Service
      7                 from-isdn       fr         default                         In Service
      8                 from-isdn       fr         default                         In Service
     10                 from-isdn       fr         default                         In Service
     11                 from-isdn       fr         default                         In Service

If everything's fine, you should be able to call a phone number that should end up in Asterisk, and see in the console that the extension does not exist.

You now have a default Asterisk server which can now act as a BRI gateway.

A Soekris net5501 in a case with a B410P ISDN PCI CardA Soekris net5501 in a case with a B410P ISDN PCI Card, plugged to a router

Incoming calls

Incoming calls are handled exactly like with any other channel type:

[from-isdn]
exten => 1234,1,Dial(SIP/undeuxtroisquatre,20)
exten => 1234,n,Hangup()

exten => s,1,Dial(SIP/mydefaultsipaccount,20)
exten => s,n,Hangup()

Here's a log on my system for an incoming call (from 0100000000 to XXXXXX1211):

    -- Accepting call from '0100000000' to '1211' on channel 0/1, span 2
    -- Executing [1211@from-isdn:1] Dial("DAHDI/i2/0100000000-2", "SIP/sipaccount2,20") in new stack
    -- Called SIP/sipaccount2
    -- SIP/sipaccount2-00000001 is ringing
    -- SIP/sipaccount2-00000001 answered DAHDI/i2/0100000000-2
       > 0x2a1aa000 -- Probation passed - setting RTP source address to 192.168.6.100:5010
    -- Span 2: Channel 0/1 got hangup request, cause 16
  == Spawn extension (from-isdn, 1211, 1) exited non-zero on 'DAHDI/i2/0100000000-2'
    -- Hungup 'DAHDI/i2/0100000000-2'

Outgoing calls

Here's the format of the Dial function:

/*
* data is —v
* Dial(DAHDI/pseudo[/extension[/options]])
* Dial(DAHDI/[c|r|d][/extension[/options]])
* Dial(DAHDI/![c|r|d][/extension[/options]])
* Dial(DAHDI/i[/extension[/options]])
* Dial(DAHDI/[i-](g|G|r|R)[c|r|d][/extension[/options]])
*
* i – ISDN span channel restriction.
* Used by CC to ensure that the CC recall goes out the same span.
* Also to make ISDN channel names dialable when the sequence number
* is stripped off. (Used by DTMF attended transfer feature.)
*
* g – channel group allocation search forward
* G – channel group allocation search backward
* r – channel group allocation round robin search forward
* R – channel group allocation round robin search backward
*
* c – Wait for DTMF digit to confirm answer
* r – Set distintive ring cadance number
* d – Force bearer capability for ISDN/SS7 call to digital.
*/

For instance, to have french numbers get through group 1:

[from-sip]
exten => _0XXXXXXXXX,1,Dial(DAHDI/g1/${EXTEN})
exten => _0XXXXXXXXX,n,Hangup()

Since I defined that group with all the card's channels, I should be able to make 8 concurrent outgoing calls.

Here's a log from my system for an outgoing call (from a SIP phone to 0600000000, setting 0400000000 as caller id):

  == Spawn extension (from-sip, 0600000001, 4) exited non-zero on 'SIP/sipaccount-00000002'
    -- Executing [0600000000@from-sip:1] Verbose("SIP/sipaccount-00000003", "0, sipaccount") in new stack
 sipaccount
    -- Executing [0600000000@from-sip:2] Set("SIP/sipaccount-00000003", "CALLERID(name)=Erwan Martin") in new stack
    -- Executing [0600000000@from-sip:3] Set("SIP/sipaccount-00000003", "CALLERID(num)=0400000000") in new stack
    -- Executing [0600000000@from-sip:4] Dial("SIP/sipaccount-00000003", "DAHDI/g1/0600000000") in new stack
    -- Requested transfer capability: 0x00 - SPEECH
    -- Called DAHDI/g1/0600000000
    -- DAHDI/i1/0600000000-4 is proceeding passing it to SIP/sipaccount-00000003
    -- DAHDI/i1/0600000000-4 is making progress passing it to SIP/sipaccount-00000003
    -- DAHDI/i1/0600000000-4 is ringing
    -- DAHDI/i1/0600000000-4 answered SIP/sipaccount-00000003
    -- Span 1: Channel 0/1 got hangup request, cause 16
    -- Hungup 'DAHDI/i1/0600000000-4'
  == Spawn extension (from-sip, 0600000000, 4) exited non-zero on 'SIP/sipaccount-00000003'

Conclusion

This two part article shows that FreeBSD can be successfully used on a small server as a SIP IPBX with a 4 port ISDN connection.
The RAM usage is 64MB when the system is idle, and the CPU doesn't flinch when making 4 concurrent calls.

That setup is perfect to make a cheap, low power, very reliable and very customizable small office IPBX.

512 MHz and 512 MB of RAM may seem very little nowadays, but this shows once again that you don't need much when you have good reliable hardware and good reliable software.

Building a FreeBSD small office IPBX server. Part 1: Installing and configuring FreeBSD

The FreeBSD logoImageImage

Introduction

Let's build a small office SIP IPBX server.

What I'm going to use

The hardware will be:

  • A Soekris net5501. Great card. I got it for cheap on Ebay.
  • A spare 2.5" SATA hard drive
  • A B410P quadbri PCI card to connect to my ISDN phone provider.

A Soekris net5501-70

And the software:

Step 1: upgrade the Soekris board's BIOS (optional)

Connect to the board using a classic 9600.8N1 serial connection.

Update the BIOS normally. I use TeraTerm on Windows to send the file using XMODEM.

comBIOS ver. 1.32i 20071005  Copyright (C) 2000-2007 Soekris Engineering.

net5501

0512 Mbyte Memory                        CPU Geode LX 500 Mhz

Pri Mas  SAMSUNG HM160HC                 LBA Xlt 1024-255-63  134 Gbyte

Slot   Vend Dev  ClassRev Cmd  Stat CL LT HT  Base1    Base2   Int
-------------------------------------------------------------------
0:01:2 1022 2082 10100000 0006 0220 08 00 00 A0000000 00000000 10
0:06:0 1106 3053 02000096 0117 0210 08 40 00 0000E101 A0004000 11
0:07:0 1106 3053 02000096 0117 0210 08 40 00 0000E201 A0004100 05
0:08:0 1106 3053 02000096 0117 0210 08 40 00 0000E301 A0004200 09
0:09:0 1106 3053 02000096 0117 0210 08 40 00 0000E401 A0004300 12
0:20:0 1022 2090 06010003 0009 02A0 08 40 80 00006001 00006101
0:20:2 1022 209A 01018001 0005 02A0 08 00 00 00000000 00000000
0:21:0 1022 2094 0C031002 0006 0230 08 00 80 A0005000 00000000 15
0:21:1 1022 2095 0C032002 0006 0230 08 00 00 A0006000 00000000 15

 5 Seconds to automatic boot.   Press Ctrl-P for entering Monitor.

comBIOS Monitor.   Press ? for help.

>download

Start sending file using XMODEM/CRC protocol.

File downloaded succesfully, size 784 Blocks.

> flashupdate
Updating BIOS Flash ,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,,,,.... Done.

Image

Step 2: build a customized FreeBSD 10.1 PXE boot image

Building a kernel

I'm going to build a tailor-made FreeBSD kernel, including what's needed for the net5501, and excluding drivers that are not needed.

Here's the kernel config file:

# Based on GENERIC:
#   FreeBSD: releng/10.1/sys/i386/conf/GENERIC 271234 2014-09-07 18:43:26Z markj
# To be used with soekris net5501

cpu     I486_CPU
cpu     I586_CPU
ident   SOEKRIS

options CPU_GEODE
options CPU_SOEKRIS

makeoptions	DEBUG=-g		# Build kernel with gdb(1) debug symbols
makeoptions	WITH_CTF=1		# Run ctfconvert(1) for DTrace support

hints		"GENERIC.hints"		# Default places to look for devices.

options 	SCHED_ULE		# ULE scheduler
options 	PREEMPTION		# Enable kernel thread preemption
options 	INET			# InterNETworking
options 	INET6			# IPv6 communications protocols
options 	TCP_OFFLOAD		# TCP offload
options 	SCTP			# Stream Control Transmission Protocol
options 	FFS			# Berkeley Fast Filesystem
options 	SOFTUPDATES		# Enable FFS soft updates support
options 	UFS_ACL			# Support for access control lists
options 	UFS_DIRHASH		# Improve performance on big directories
options 	UFS_GJOURNAL		# Enable gjournal-based UFS journaling
options 	QUOTA			# Enable disk quotas for UFS
options 	MD_ROOT			# MD is a potential root device
#options 	NFSCL			# New Network Filesystem Client
#options 	NFSD			# New Network Filesystem Server
#options 	NFSLOCKD		# Network Lock Manager
#options 	NFS_ROOT		# NFS usable as /, requires NFSCL
options 	MSDOSFS			# MSDOS Filesystem
options 	CD9660			# ISO 9660 Filesystem
options 	PROCFS			# Process filesystem (requires PSEUDOFS)
options 	PSEUDOFS		# Pseudo-filesystem framework
options 	GEOM_PART_GPT		# GUID Partition Tables.
options 	GEOM_RAID		# Soft RAID functionality.
options 	GEOM_LABEL		# Provides labelization
options 	COMPAT_FREEBSD4		# Compatible with FreeBSD4
options 	COMPAT_FREEBSD5		# Compatible with FreeBSD5
options 	COMPAT_FREEBSD6		# Compatible with FreeBSD6
options 	COMPAT_FREEBSD7		# Compatible with FreeBSD7
#options 	SCSI_DELAY=5000		# Delay (in ms) before probing SCSI
options 	KTRACE			# ktrace(1) support
options 	STACK			# stack(9) support
options 	SYSVSHM			# SYSV-style shared memory
options 	SYSVMSG			# SYSV-style message queues
options 	SYSVSEM			# SYSV-style semaphores
options 	_KPOSIX_PRIORITY_SCHEDULING # POSIX P1003_1B real-time extensions
options 	PRINTF_BUFR_SIZE=128	# Prevent printf output being interspersed.
options 	KBD_INSTALL_CDEV	# install a CDEV entry in /dev
options 	HWPMC_HOOKS		# Necessary kernel hooks for hwpmc(4)
options 	AUDIT			# Security event auditing
options 	CAPABILITY_MODE		# Capsicum capability mode
options 	CAPABILITIES		# Capsicum capabilities
options 	PROCDESC		# Support for process descriptors
options 	MAC			# TrustedBSD MAC Framework
options 	KDTRACE_HOOKS		# Kernel DTrace hooks
options 	DDB_CTF			# Kernel ELF linker loads CTF data
options 	INCLUDE_CONFIG_FILE	# Include this file in kernel

# Debugging support.  Always need this:
options 	KDB			# Enable kernel debugger support.
options 	KDB_TRACE		# Print a stack trace for a panic.

# To make an SMP kernel, the next two lines are needed
options 	SMP			# Symmetric MultiProcessor Kernel
device		apic			# I/O APIC

# CPU frequency control
device		cpufreq

# Bus support.
device		acpi
device		eisa
device		pci
device              pass

# ATA and ATAPI devices
device      ata
device      atadisk         # ATA disk drives
options     ATA_STATIC_ID   # Static device numbering

# SCSI peripherals
device      scbus   # SCSI bus (required for SCSI)
device      da      # Direct Access (disks)
device      cd      # CD

# The following are not needed, but the kernel wouldn't compile without them.
device          atkbdc                  # AT keyboard controller
device          atkbd                   # AT keyboard
device          psm                     # PS/2 mouse
device          kbdmux                  # keyboard multiplexer
device          vga                     # VGA video card driver
options         VESA                    # Add support for VESA BIOS Extensions (VBE)

# syscons is the default console driver, resembling an SCO console
device		sc

# Add suspend/resume support for the i8254.
device		pmtimer

# Serial (COM) ports
device      uart            # Generic UART driver

# PCI Ethernet NICs that use the common MII bus controller code.
# NOTE: Be sure to keep the 'device miibus' line in order to use these NICs!
device		miibus			# MII bus support
device		vr			# VIA Rhine, Rhine II

# Pseudo devices.
device		loop			# Network loopback
device		random			# Entropy device
device		padlock_rng		# VIA Padlock RNG
device		rdrand_rng		# Intel Bull Mountain RNG
device		ether			# Ethernet support
device		vlan			# 802.1Q VLAN support
device		tun			# Packet tunnel.
device		md			# Memory "disks"
device		gif			# IPv6 and IPv4 tunneling
device		faith			# IPv6-to-IPv4 relaying (translation)
device		firmware		# firmware assist module

# The `bpf' device enables the Berkeley Packet Filter.
# Be aware of the administrative consequences of enabling this!
# Note that 'bpf' is required for DHCP.
device		bpf			# Berkeley packet filter

# USB support
options 	USB_DEBUG		# enable debug msgs
device		uhci			# UHCI PCI->USB interface
device		ohci			# OHCI PCI->USB interface
device		ehci			# EHCI PCI->USB interface (USB 2.0)
device		usb			# USB Bus (required)
device		ukbd			# Keyboard
device		umass			# Disks/Mass storage - Requires scbus and da

#Firewall related
device      pf          #PF OpenBSD packet-filter firewall
device      pflog           #logging support interface for PF
options ALTQ
options ALTQ_CBQ
options ALTQ_RED
options ALTQ_RIO
options ALTQ_HFSC
options ALTQ_CDNR
options ALTQ_PRIQ

options     NULLFS          #NULL filesystem

You can also add some options in your make.conf:

CPUTYPE?=geode
OPTIMIZED_CFLAGS=YES
BUILD_OPTIMIZED=YES
WITH_CPUFLAGS=YES
WITH_OPTIMIZED_CFLAGS=YES

I'm going to build this i386 kernel on a amd64 machine, hence the additional parameters:

cd /usr/src/
make -j2 kernel-toolchain TARGET=i386
make -j2 buildworld TARGET=i386 TARGET_ARCH=i386
make -j2 buildkernel KERNCONF=SOEKRIS TARGET=i386 TARGET_ARCH=i386

If you didn't compile the device hints statically into the kernel, or if you didn't add a device.hints file in the boot folder of the image, you might get the following panic later:

panic: No usable event timer found!
cpuid = 0
KDB: stack backtrace:
#0 0xc0679362 at kdb_backtrace+0x52
#1 0xc063bd1f at panic+0x11f
#2 0xc090bf25 at cpu_initclocks_bsp+0x495
#3 0xc05e906f at initclocks+0x2f
#4 0xc05e5607 at mi_startup+0xe7
#5 0xc0460a57 at begin+0x2c
Uptime: 1s

Building a mfsbsd image

mfsbsd images are great since they can be booted directly using PXE.

Download, extract and go to mfsbsd:

fetch -o mfsbsd.zip https://github.com/mmatuska/mfsbsd/archive/master.zip --no-verify-peer
unzip mfsbsd.zip
cd mfsbsd-master

Copy the example config files:

#/bin/sh
for fn in *.sample; do
cp ${fn} ${fn%.sample}
done

Enable the serial console in the loader config file, and set the root password:

cat conf/loader.conf:
...
mfsbsd.rootpw="mfsroot"
console="comconsole"

Build the image:

make CUSTOM=1 TARGET=i386 TARGET_ARCH=i386 KERNCONF=SOEKRIS

This will use the kernel and userland made in the previous section.

Build and serve the dist files (base.txz, kernel.txz)

Build the dist files:

cd /usr/src/release
make -j2 NODOC=YES NOPORTS=YES NOSRC=YES NOGAMES=YES KERNCONF=SOEKRIS TARGET=i386 TARGET_ARCH=i386 system

Serves the dist files using HTTP or FTP. I use python to start a quick and dirty HTTP server.

cd /usr/src/release/dist
python -m SimpleHTTPServer 8080

Configure pxelinux to boot the file

The usual pxelinux.0 binary file has problems with the Soekris serial connection (entire screen truncated to 16 characters), so we'll need a patched one, which can be found here: https://centerclick.org/net4801/pxelinux/.

Download memdisk. You can find it packaged with syslinux: http://www.kernel.org/pub/linux/utils/boot/syslinux/.

Configure pxelinux in pxelinux.cfg/default:

DEFAULT fbsd
 
LABEL fbsd
 kernel memdisk
 append initrd=/mfsbsd-10.1-RELEASE-p12-i386.img harddisk raw

Step 3: Install FreeBSD on the board

Start your favorite TFTP server and boot pxelinux.0 with the Soekris.

> boot f0

Start bsdinstall and install the system as usual. When asked what mirror you wish to use, select "Other" and enter the location of your dist files (for instance: http://192.168.4.2:8080).

Partitioning

I partitioned the hard drive manually, using the shell. I wish I could have used ZFS, but with 512MB of RAM, well...

Create the partition:

gpart create -s mbr ada0
gpart add -t freebsd ada0
gpart create -s bsd ada0s1
# ada0s1a => /
gpart add -s 32G -t freebsd-ufs ada0s1
# ada0s1b => Swap
gpart add -s 2G -t freebsd-swap ada0s1
# ada0s1c => /var
gpart add -s 32G -t freebsd-ufs ada0s1
# ada0s1d => /usr
gpart add -s 32G -t freebsd-ufs ada0s1
# ada0s1e => /usr/home
gpart add -t freebsd-ufs ada0s1

Install the boot loader and set the first partition as active.

gpart set -a active -i 1 ada0
gpart bootcode -b /boot/mbr ada0
gpart bootcode -b /boot/boot ada0s1

Create the label and filesystems:

# Labels and filesystems
glabel label swap0 /dev/ada0s1b
newfs -L rootfs -U /dev/ada0s1a
newfs -L varfs  -U /dev/ada0s1d
newfs -L usrfs  -U /dev/ada0s1e
newfs -L usrhomefs  -U /dev/ada0s1f

Write the fstab file in /tmp/bsdinstall_etc/fstab:

# Device            Mountpoint      FStype  Options     Dump    Pass#
/dev/ufs/rootfs     /               ufs     rw          1       1
/dev/label/swap0    none            swap    sw          0       0
/dev/ufs/varfs      /var            ufs     rw          1       2
/dev/ufs/usrfs      /usr            ufs     rw          1       3
/dev/ufs/usrhomefs  /usr/home       ufs     rw          1       4

Mount the systems so that the installer can write into them:

mount /dev/ufs/rootfs /mnt/
mkdir /mnt/var
mount /dev/ufs/varfs /mnt/var/
mkdir /mnt/usr
mount /dev/ufs/usrfs /mnt/usr/
mkdir /mnt/usr/home
mount /dev/ufs/usrhomefs /mnt/usr/home

Continuing the installation

Exit the shell, and let the installer fetch the packages and do its magic.

Before rebooting, don't forget to enable the console and set the boot partition in /boot/loader.conf:

console="comconsole"
vfs.root.mountfrom="ufs:/dev/ufs/rootfs"

You now have FreeBSD installed on your Soekris net5501.

Going to the next part

In the next part, we'll install the ISDN quadBRI card, and connect to the outside world.

Using Intellij IDEA to write and debug Erlang code

Not so frequently asked questions and stuff: 

ImageImage
Written 2015-02-02.

Requirements

My system is a Windows 8.1 (6.3 build 9600).

Download IntelliJ IDEA from JetBrains (I use the Community Edition, currently ideaIC-14.0.3.exe).

My installed erlang is:

Erlang/OTP 17 [erts-6.2] [64-bit] [smp:8:8] [async-threads:10]

Start IDEA, and go the plugin page (click "Configure"). Install the very nice Erlang plugin (currently ver 0.5.9 (2014-11-18)).

Image

Install rebar somewhere on your system (clone the repository and run bootstrap.bat with the erlang binaries in your PATH).

Configure the IDE

In the IDE's settings window, go to "Other Settings / Erlang External Tools" and input the location of your rebar binaries.

Image

In "Build, Execution, Deployment/Erlan Compiler", check "Compile project with rebar".

Image

Create a new project, and select Erlang. Leave "Additional Libraries and Frameworks" empty and click Next.

Click on "Configure" and input the location of your Erlang SDK (exemple: "C:\Program Files\erl6.2").

If you did well, the SDK is recognized correctly.

Image

Debugging a simple file

Let's create a simple Erlang code and try to debug it.

Add a file, input some code, and click "Build / Make".

If your code is incorrect, the list of warnings and errors appears, and you can double click on the items to go directly to the source of error (like in any good IDE).

Image

If your code is correct, the project is compiled.

A simple default debug configuration is created. You can put a breakpoint in your code and debug it.

ImageImage

So far, so good.

Debugging standard OTP applications

Now let's try to debug something more serious, like an OTP application.

rebarcreate-app appid=testerlangide
==> testerlangide (create-app)
Writing src/testerlangide.app.src
Writing src/testerlangide_app.erl
Writing src/testerlangide_sup.erl

Let's create a gen_server which prints something every 5 seconds

...

-define(DELAY_BETWEEN_LOOKUPS, 5000).

-record(state, {timer}).

init([]) ->
  Timer = erlang:send_after(?DELAY_BETWEEN_LOOKUPS, self(), timer_ticked),
  {ok, #state{timer=Timer}}.

...

handle_info(timer_ticked, #state{timer=OldTimer}=State) ->
  erlang:cancel_timer(OldTimer),
  io:format("Tick!~n"),
  Timer = erlang:send_after(?DELAY_BETWEEN_LOOKUPS, self(), timer_ticked),
  {noreply, State#state{timer=Timer}};

...

I've tried debugging the application directly, by having the debugger directly start the application (application:start(testerlangide)), but it didn't work well:

  • When "Stop Erlang interpreter automatically after execution" is ticked, the Erlang node is stopped directly after the app is started, which is obviously not good.
  • When "Stop Erlang interpreter automatically after execution" is not ticked, the breakpoints in the gen_server do not work, because the node is considered by the IDE to be stopped. Thus you can't stop it, which is also not good.

What I ended up doing was the creation of a "proxy" file, that starts the app, and does nothing until the node is stopped. That way, the breakpoints in the gen_servers work, and the node can be stopped by clicking on the red square button in the IDE.

-export([debug/0]).

loop_sleep() ->
  timer:sleep(5000),
  loop_sleep().

debug() ->
  %% Start your dependencies here
  application:start(testerlangide),
  loop_sleep().

ImageImage

Debugging a remote node

Using Intellij on Windows works fine, but many Erlang libraries don't. In my opinion, Erlang is much more pleasant on UNIX systems. Still, that shouldn't prevent us from debugging.

Fortunately, the author of the plugin included a way to debug remote nodes. A local node will be spawned on the IDE's computer, connect to the target node, and fire up the debugger.

Image

If your remote node is using long names, please be sure that your intellij-erlang include this commit and this commit. Otherwise, your local node won't be started with a correct name and debugging will fail.

Image

You can even connect to an already running system, debug a few lines, and let it live again normally. That's perfect to debug hard live problems.

Peregrinations in the realm of PostgreSQL upgrades

Not so frequently asked questions and stuff: 

Posted on 2015-01-03.

The FreeBSD logoThe PostgreSQL Logo

The system used is FreeBSD 10.1 STABLE.

Situation

You upgraded your PostgreSQL v9.4 database server, and now it won't start.

Starting the server says:

FATAL:  database files are incompatible with server
DETAIL:  The database cluster was initialized with PG_CONTROL_VERSION 937, but the server was compiled with PG_CONTROL_VERSION 942.
HINT:  It looks like you need to initdb.
pg_ctl: could not start server
Examine the log output.

Installing an older 9.3 and starting it yields:

FATAL:  database files are incompatible with server
DETAIL:  The data directory was initialized by PostgreSQL version 9.4, which is not compatible with this version 9.3.5.
pg_ctl: could not start server
Examine the log output.

So 9.4 tells us that the files belong to 9.3.7 (does such a thing even exist?), and 9.3 tells us that the files belong to 9.4.

Huh.

Explanation

According to this anwser to this question, the format of some database/control files changed right after 9.4b1.

Solving the problem

We're going to upgrade the database manually.

Install portdowngrade.

make -C /usr/ports/ports-mgmt/portdowngrade install clean

Use it to download version 9.4b1 of the PostgreSQL port.

portdowngrade databases/postgresql94-server r366094

Build the port into a separate directory.

make -C /root/postgresql94-server WRKDIRPREFIX=/root/pgsql/old

Copy your old data nearby

cp -rp /.zfs/snapshot/2015-01-03/usr/local/pgsql/data /root/pgsql/data-old

Build and install the latest version of the port.

make -C /usr/ports/databases/postgresql94-server install clean

Also install the latest contrib tools (to get pg_upgrade).

make -C /usr/ports/databases/postgresql94-contrib install clean

Initialize the new data files.

/usr/local/etc/rc.d/postgresql initdb

And now the most important part: converting the data files:

su -l -c default pgsql -c 'pg_upgrade -d /root/pgsql/data-old -D /usr/local/pgsql/data -b /root/pgsql/old/usr/ports/databases/postgresql94-server/work/stage/usr/local/bin -B /usr/local/bin -v'

Who said FreeBSD couldn't use pg_upgrade!

Once upgraded, the server is ready to be started again.

/usr/local/etc/rc.d/postgresql start

Of course, this situation can never happen to you since your backups are always up to date and since you upgrade PostgreSQL using the FreeBSD method correctly (dump and restore).

"error: cannot combine with previous 'int' declaration specifier" building FreeBSD 10.0 releng

Not so frequently asked questions and stuff: 

I was trying to build the FreeBSD kernel today (2014-12-10), branch 10.0 releng (r), and was greeted by the following clang error message:

===> drm2/radeonkms (all)
cc  -O2 -pipe -march=native -fms-extensions -fno-strict-aliasing -D_KERNEL -DKLD_MODULE -nostdinc  -I/usr/src/sys/modules/drm2/radeonkms/../../../dev/drm2/radeon -
DHAVE_KERNEL_OPTION_HEADERS -include /usr/obj/usr/src/sys/ENCEINTE/opt_global.h -I. -I@ -I@/contrib/altq -fno-common -g -fno-omit-frame-pointer -mno-omit-leaf-fram
e-pointer -I/usr/obj/usr/src/sys/ENCEINTE  -mno-aes -mno-avx -mcmodel=kernel -mno-red-zone -mno-mmx -mno-sse -msoft-float  -fno-asynchronous-unwind-tables -ffreest
anding -fstack-protector -std=iso9899:1999 -Qunused-arguments -fstack-protector -Wall -Wredundant-decls -Wnested-externs -Wstrict-prototypes  -Wmissing-prototypes
-Wpointer-arith -Winline -Wcast-qual  -Wundef -Wno-pointer-sign   -Wmissing-include-dirs -fdiagnostics-show-option  -Wno-error-tautological-compare -Wno-error-empt
y-body  -Wno-error-parentheses-equality -Wno-format -Wno-format -c /usr/src/sys/modules/drm2/radeonkms/../../../dev/drm2/radeon/radeon_acpi.c
In file included from /usr/src/sys/modules/drm2/radeonkms/../../../dev/drm2/radeon/radeon_acpi.c:27:
In file included from @/dev/drm2/drmP.h:43:
In file included from @/sys/param.h:88:
In file included from @/sys/types.h:44:
In file included from /usr/obj/usr/src/sys/ENCEINTE/machine/endian.h:6:
In file included from /usr/obj/usr/src/sys/ENCEINTE/x86/endian.h:37:
In file included from @/sys/_types.h:33:
In file included from /usr/obj/usr/src/sys/ENCEINTE/machine/_types.h:6:
/usr/obj/usr/src/sys/ENCEINTE/x86/_types.h:145:14: error: cannot combine with previous 'int' declaration specifier
typedef int             __wchar_t;
                        ^
/usr/obj/usr/src/sys/ENCEINTE/x86/_types.h:145:1: warning: typedef requires a name [-Wmissing-declarations]
typedef int             __wchar_t;
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 warning and 1 error generated.
*** Error code 1

Stop.
bmake[5]: stopped in /usr/src/sys/modules/drm2/radeonkms
*** Error code 1

Stop.
bmake[4]: stopped in /usr/src/sys/modules/drm2
*** Error code 1

Stop.
bmake[3]: stopped in /usr/src/sys/modules
*** Error code 1

Stop.
bmake[2]: stopped in /usr/obj/usr/src/sys/ENCEINTE
*** Error code 1

Stop.
bmake[1]: stopped in /usr/src
*** Error code 1

Stop.
make: stopped in /usr/src

The problem is that clang 3.4 with -fms-extensions defines things that are already defined in the code, hence the error.

The fix is in r260495.

Playing with rrdtool counter resets

Not so frequently asked questions and stuff: 

Creating and plotting some data normally

Let's create a standard rdd file, with a counter:

rrdtool create test.rrd --start $start --step 60 DS:something:COUNTER:600:0:U RRA:AVERAGE:0.5:1:1440

Now let's update it with some values.

#!/usr/bin/env perl -w

use strict;
use warnings;

my $start = 1417392000;
my $counter = 0;

for my $x (1..60*24) {
    my $time = $start + 60*$x;
    $counter += 1000+ int(250*sin(1/(48)*$x));
    my $command = "rrdtool update test.rrd $time:$counter";
    system($command);
}

Let's graph!

rrdtool graph test.png -a PNG --start 1417392000 --end 1417478400 -t "Sure looks like a sine" -v "avg potato per second" \
    DEF:something=test.rrd:something:AVERAGE \
    LINE1:something#111111:"My superb value"

Image

So far, so good.

Having a counter reset

Now, let's recreate the same data, but with a counter reset in the middle.

    if ($x == 1000) {
        $counter = 0;
    }

The dreaded spike appears!

Image

Folder http://oss.oetiker.ch/rrdtool/pub/contrib/ contains many tools to remove spikes from rrd data files. My favorite one is spikekill (spikekill-1.1-1.tar.gz).

php removespikes.php -d -A=avg -M=variance -P=5000 -R=test.rrd

The spike is then replaced by a small innocent glitch.

Image

To prevent counter reset spikes, you should use DERIVE instead of COUNTER. Tuning the rrd file solves the problem.

rrdtool tune test.rrd --data-source-type something:DERIVE

Drupal: show/hide location fields with module Conditional Fields

Not so frequently asked questions and stuff: 

Image

Situation

You're using module Conditional Fields to show/hide a few fields depending on another field value. You notice that everything works except your location fields, which don't respond to any trigger.

Example: Let's say you have the following profile2:

Field Type Widget Misc
field_champ1 List(text) Select list Values: "Valeur 1", "Valeur 2" or "Valeur 3"
field_champ2 Location Location field
field_champ3 Text Text

You set up the following conditional interactions:

  • field_champ2 is only visible when field_champ1 has value "Valeur 2"
  • field_champ3 is only visible when field_champ1 has value "Valeur 1"

Then, you open an edit form of this profile, and notice that only the second interaction works.

Finding what cause the problem

Let's dig into the code.

In modules/conditional_fields/conditional_fields.module, function conditional_fields_element_after_build(...) seems to be the one that attach the dependencies from the form.

  if (isset($dependencies['dependents'][$field['#field_name']])) {
    foreach ($dependencies['dependents'][$field['#field_name']] as $id => $dependency) {
      if (!isset($form['#conditional_fields'][$field['#field_name']]['dependees'][$id])) {
        conditional_fields_attach_dependency($form, array('#field_name' => $dependency['dependee']), $field, $dependency['options'], $id);
      }
    }
  }

  // Attach dependee.
  // TODO: collect information about every element of the dependee widget, not
  // just the first encountered. This bottom-up approach would allow us to
  // define per-element sets of dependency values.
  if (isset($dependencies['dependees'][$field['#field_name']])) {
    foreach ($dependencies['dependees'][$field['#field_name']] as $id => $dependency) {
      if (!isset($form['#conditional_fields'][$field['#field_name']]['dependents'][$id])) {
        conditional_fields_attach_dependency($form, $field, array('#field_name' => $dependency['dependent']), $dependency['options'], $id);
      }
    }
  }

Debugging shows that our location field does not validate the preceding condition:

  // Some fields do not have entity type and bundle properties. In this case we
  // try to use the properties from the form. This is not an optimal solution,
  // since in case of fields in entities within entities they might not correspond,
  // and their dependencies will not be loaded.
  if (isset($field['#entity_type'], $field['#bundle'])) {
    $entity_type = $field['#entity_type'];
    $bundle = $field['#bundle'];
  }
  elseif (isset($form['#entity_type'], $form['#bundle'])) {
    $entity_type = $form['#entity_type'];
    $bundle = $form['#bundle'];
  }
  else {
    return $element;
  }

This means that our field was not build with a reference to its profile2 entity.

These field form entries are built by field_multiple_value_form(...) in modules/field/field.form.inc:

      $element = array(
        '#entity_type' => $instance['entity_type'],
        '#entity' => $form['#entity'],
        '#bundle' => $instance['bundle'],
        '#field_name' => $field_name,
        '#language' => $langcode,
        '#field_parents' => $parents,
        '#columns' => array_keys($field['columns']),
        // For multiple fields, title and description are handled by the wrapping table.
        '#title' => $multiple ? '' : $title,
        '#description' => $multiple ? '' : $description,
        // Only the first widget should be required.
        '#required' => $delta == 0 && $instance['required'],
        '#delta' => $delta,
        '#weight' => $delta,
      );
      if ($element = $function($form, $form_state, $field, $instance, $langcode, $items, $delta, $element)) {
...

Hum, once build, it's passed into another function. Let's find it.

It's location_cck_field_widget_form(...) in modules/location/contrib/location_cck/location_cck.module:

    $element = array(
      '#type' => 'location_element',
      '#has_garbage_value' => TRUE,
      '#value' => '',
      '#title' => t($instance['label']),
      '#description' => t($instance['description']),
      '#required' => $instance['required'],
      '#location_settings' => $settings,
      '#default_value' => $location,
    );

    return $element;

Since the returned element doesn't care about what was passed to it, the entity elements are indeed missing.

Working around the problem

One solution would be to patch module location.

Another one is to alter the form and add the missing entries manually.

function my_module_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
  if ($entity_type == 'profile2' && $entity->type == 'profile_debug') {
    $form['field_champ2']['#entity_type'] = $entity_type;
    $form['field_champ2']['#bundle'] = $entity->type;

    if (isset($form['field_champ2'][LANGUAGE_NONE])) {
      $form['field_champ2'][LANGUAGE_NONE]['#entity_type'] = $entity_type;
      $form['field_champ2'][LANGUAGE_NONE]['#bundle'] = $entity->type;
    }
  }
}

That's it: location field field_champ2 now behaves correctly.

How to write a table into a PDF using Python and Reportlab

Not so frequently asked questions and stuff: 

Image

This code will output a table into a lanscaped A4 PDF, with word wrap enabled.

#!/usr/local/bin/python

from reportlab.lib import colors
from reportlab.lib.pagesizes import A4, inch, landscape
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph
from reportlab.lib.styles import getSampleStyleSheet

doc = SimpleDocTemplate("test_report_lab.pdf", pagesize=A4, rightMargin=30,leftMargin=30, topMargin=30,bottomMargin=18)
doc.pagesize = landscape(A4)
elements = []

data = [
["Letter", "Number", "Stuff", "Long stuff that should be wrapped"],
["A", "01", "ABCD", "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"],
["B", "02", "CDEF", "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"],
["C", "03", "SDFSDF", "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"],
["D", "04", "SDFSDF", "DDDDDDDDDDDDDDDDDDDDDDDD DDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"],
["E", "05", "GHJGHJGHJ", "EEEEEEEEEEEEEE EEEEEEEEEEEEEEEEE EEEEEEEEEEEEEEEEEEEE"],
]

#TODO: Get this line right instead of just copying it from the docs
style = TableStyle([('ALIGN',(1,1),(-2,-2),'RIGHT'),
                       ('TEXTCOLOR',(1,1),(-2,-2),colors.red),
                       ('VALIGN',(0,0),(0,-1),'TOP'),
                       ('TEXTCOLOR',(0,0),(0,-1),colors.blue),
                       ('ALIGN',(0,-1),(-1,-1),'CENTER'),
                       ('VALIGN',(0,-1),(-1,-1),'MIDDLE'),
                       ('TEXTCOLOR',(0,-1),(-1,-1),colors.green),
                       ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
                       ('BOX', (0,0), (-1,-1), 0.25, colors.black),
                       ])

#Configure style and word wrap
s = getSampleStyleSheet()
s = s["BodyText"]
s.wordWrap = 'CJK'
data2 = [[Paragraph(cell, s) for cell in row] for row in data]
t=Table(data2)
t.setStyle(style)

#Send the data and build the file
elements.append(t)
doc.build(elements)

Here's the output.
Image

If you use the default word wrap configuration, words that are too long won't be wrapped:

s.wordWrap = 'LTR'

Image

How to use user/password authentication with OpenVPN on FreeBSD

Not so frequently asked questions and stuff: 

ImageThe FreeBSD logo

2014-11-03.

Configure the system

To use a textfile password database, install pam_pwdfile:

make -C /usr/ports/security/pam_pwdfile install clean

Generate your password using OpenSSL or anything else.

openssl passwd -crypt superpassword

Populate /usrl/local/etc/ovpn/passwd with your user/password database.

guest1:VgocPdscg2SzY
guest2:2aRomUmF3ALJU
guest3:fW4lUmBwz56Rg
guest4:Rcf3wcHgMhPkM
guest5:9CVFYqvOjgw.Y

Configure pam in /etc/pam.d/openvpn:

auth            required        /usr/local/lib/pam_pwdfile.so pwdfile=/usr/local/etc/ovpn/passwd
account         required        pam_permit.so
session         required        pam_permit.so
password        required        pam_deny.so

To test your PAM setup, install pamtester and use it.

# make -C /usr/ports/security/pamtester install clean
[...]

# pamtester -v openvpn guest1 authenticate
pamtester: invoking pam_start(openvpn, guest1, ...)
pamtester: performing operation - authenticate
Password:
pamtester: successfully authenticated

On your server configuration file, add:

plugin /usr/local/lib/openvpn/plugins/openvpn-plugin-auth-pam.so openvpn
duplicate-cn   #add this if you want to have multiple connections using the same certificate. Otherwise, they'all get the same IP and you'll have problems.

On your clients, add:

auth-user-pass

LDAP Auth

Instal pam_ldap.

make -C /usr/ports/security/pam_ldap install clean

Create a configuration file, for example: /usr/local/etc/ovpn/ldap.conf, and populate it as usual.

host ldap-server.example.com
base dc=example,dc=com
ldap_version 3
binddn cn=pam,ou=services,dc=example,dc=com
bindpw DY5K82cG5avkCkz
port 389
scope sub
bind_timelimit 10
bind_policy soft
pam_filter objectclass=inetOrgPerson
pam_login_attribute uid
pam_password exop
nss_base_passwd         ou=people,dc=example,dc=com
nss_base_group          ou=group,dc=example,dc=com

To have your users be able to authenticate using both the text files and LDAP, use a configuration like this one:

auth            sufficient      /usr/local/lib/pam_ldap.so no_warn try_first_pass config=/usr/local/etc/ovpn/ldap.conf
auth            required        /usr/local/lib/pam_pwdfile.so pwdfile=/usr/local/etc/ovpn/passwd
account         required        pam_permit.so
session         required        pam_permit.so
password        required        pam_deny.so

Otherwise, create your PAM configuration as usual.

How to verify DKIM signatures manually

Posted 2014-10-31.
ImageImageImage

Situation

You're setting up DKIM on your SMTP servers. You'd like to be able to check if your emails are signed correctly.

Using perl

Use Mail::DKIM::Verifier.

As per the documentation:

use Mail::DKIM::Verifier;

# create a verifier object
my $dkim = Mail::DKIM::Verifier->new();

# read an email from a file handle
#$dkim->load(*STDIN);

# or read an email and pass it into the verifier, incrementally
while ()
{
    # remove local line terminators
    chomp;
    s/\015$//;

    # use SMTP line terminators
    $dkim->PRINT("$_\015\012");
}
$dkim->CLOSE;

# what is the result of the verify?
my $result = $dkim->result;

# there might be multiple signatures, what is the result per signature?
foreach my $signature ($dkim->signatures)
{
    print "signature identity: " . $signature->identity . "\n";
    print "verify result: " . $signature->result_detail . "\n";
}

# the alleged author of the email may specify how to handle email
foreach my $policy ($dkim->policies)
{
    die "fraudulent message" if ($policy->apply($dkim) eq "reject");
}

Usage:

perl dkim_checker.pl 

Using PHP

Download php-dkim and phpseclib into the same folder. Write a sample code to use the classes:
validate();
print_r($r);

Usage:

php dkim_checker.php  Array
        (
            [0] => Array
                (
                    [status] => pass
                    [reason] => Success!
                )

        )

)

php dkim_checker.php  Array
        (
            [0] => Array
                (
                    [status] => permfail
                    [reason] => signature did not verify (example.com key #0)
                )

        )

)

Using python

Install pydkim.

cd dkimpy-0.5.4 && python setup.py install

Usage:

/usr/local/bin/dkimverify.py 

Pages

Subscribe to Front page feed