, # Bryan Rite , and Jeff Smith # on Wed, Jan 19, 2006. # # Visit projects.alkaloid.net or podmail.alkaloid.net for PodMail # information and updates. # # PodMail brings together some of the best open source technology to # create a new way of accessing voicemail through Asterisk. # With integration into Asterisk:LDAP or regular Asterisk voicemail, # PodMail has the ability to offer the end-user their voicemail in a # comfortable, popular, and "kewl" format: as a PodCast! # # This is the perfect medium for voicemail as it allows for secure # authentication as well as support on Windows and Mac through # iTunes and Linux through Amarok. Update your personal voicemail # podcast and take your voicemails with you. Update from anywhere # with comfort and security; its faster then checking your email! ### # START CONFIGURATION SECTION ### # # PodMail Backend # # Choose from one of 'file', 'static' or 'ldap'. # For more information on using LDAP see Asterisk::LDAP at # http://projects.alkaloid.net # To verify credentials an authenticated bind is attempted. The user must # have bind access and read access to at least their own 'voiceMailbox' # attribute for PodMail to function correctly. $conf['backend'] = 'ldap'; $conf['file']['location'] = '/etc/asterisk/voicemail.conf'; $conf['ldap']['host'] = 'picasso'; $conf['ldap']['basedn'] = 'ou=Customers,dc=alkaloid,dc=net'; $conf['ldap']['userkey'] = 'mail'; $conf['static']['user'] = '421'; # Don't forget to set $conf['force_context'] with static auth! # Force authentication to a particular context $conf['force_context'] = 'default'; # Set to the location of Asterisk's voicemail area $conf['vmail_basedir'] = '/var/spool/asterisk/voicemail'; # Set to the location of LAME $conf['lame'] = "/usr/bin/lame"; # Use this directory to hold the mp3 caches. In this release of the code # the cache is not managed so you'll have to clean it yourself $conf['tmpdir'] = "/tmp/podmail/ldap"; # # Podcast Metadata # # Below are podcast metadata settings. Tune to your fancy. # Don't worry about special characters, they will be converted before sending. $conf['title'] = 'Alkaloid Networks PodMail: Now with LDAP!'; $conf['subtitle'] = 'This PodMail is integrated with LDAP'; $conf['ttl'] = '60'; $conf['link'] = 'http://projects.alkaloid.net/'; $conf['baseurl'] = 'http://podmail.alkaloid.net/ldap/'; $conf['copyright'] = '2006 Alkaloid Networks'; $conf['description'] = 'Your Voicemail as a Podcast! Brought to you by the Alkaloid Networks'; $conf['author'] = 'Alkaloid Networks'; $conf['summary'] = 'Integrating the best of open source to deliver new ideas, Alkaloid Networks brings you PodMail: Hear your Voicemail as a podcast!'; $conf['owner']['name'] = 'Alkaloid Developers'; $conf['owner']['email'] = 'projects@alkaloid.net'; $conf['image']['url'] = 'http://podmail.alkaloid.net/podmail.png'; ### # END CONFIGURATION SECTION ### if (!isset($_SERVER['PHP_AUTH_USER']) && !($conf['backend'] == 'static')) { notAuthenticated(); } $context = false; $messagelist = false; // Input validation // Only strip the context when using the file backend: LDAP may have a // valid '@' in the username if ($pos = strpos($_SERVER['PHP_AUTH_USER'], '@') && $conf['backend'] == 'file') { $user = substr($_SERVER['PHP_AUTH_USER'], 0, $pos); $pos++; // Skip the '@' } else { $user = $_SERVER['PHP_AUTH_USER']; } // If force_context is supplied we ignore the user-supplied data if (!$conf['force_context'] && $pos) { $context = substr($_SERVER['PHP_AUTH_USER'], $pos); } else { $context = $conf['force_context']; } // If we didn't find a context, oh well. We won't be able to authenticate // below. $pass = $_SERVER['PHP_AUTH_PW']; switch($conf['backend']) { ## # File based authentication (voicemail.conf) ## case 'file': authFile($user, $pass, $context) ? $messagelist = authenticated($user, $context) : notAuthenticated("Invalid credentials"); break; ## # LDAP based authentication ## case 'ldap': if ($user = authLDAP($user, $pass, $context)) { $messagelist = authenticated($user, $context); } else { notAuthenticated("Invalid credentials"); } break; case 'static': $messagelist = authenticated($conf['static']['user'], $context); break; } # At this point we should be authenticated and have a messagelist, # though it may be of 0 length if ($_REQUEST['guid']) { # A file was requested sendFile($messagelist, $_REQUEST['guid']); } else { renderPodcast($messagelist); } ### # End of main logic ### ### # Begin subroutines ### function notAuthenticated($msg = "") { global $conf; header('WWW-Authenticate: Basic realm="'.$conf['title'].'"'); header('HTTP/1.0 401 Unauthorized'); echo "\n"; echo "Bad Authentication\n"; echo "\n"; echo "You are not authorized for this service.
"; echo $msg; echo "\n"; echo "\n"; exit; } function authenticated($user, $context) { global $conf; $mailbox = $conf['vmail_basedir'].'/'.$context.'/'.$user.'/INBOX'; $messagelist = parseMailbox($mailbox); return $messagelist; } function parseMailbox($mailbox) { $dh = opendir($mailbox) or notAuthenticated("Unable to open mailbox $mailbox"); #FIXME Return audio error #die ("Unable to open mailbox $mailbox"); $messagelist = array(); $i = 0; while (($file = readdir($dh)) != false) { if (preg_match('/\.txt$/i', $file)) { $fh = fopen($mailbox.'/'.$file, 'r') or notAuthenticated("Unable to open file $mailbox/$file"); #die ("Unable to open voicemail metadata: $file"); while (!feof($fh)) { $line = fgets($fh, 4096); $token = strpos($line, '='); if ($token) { $var = substr($line, 0, $token); $value = preg_replace('/\n$/', '', substr($line, $token + 1)); $messagelist[$i][$var] = $value; } } fclose($fh); $messagelist[$i]['file'] = $mailbox.'/'.preg_replace('/txt$/', 'wav', $file); # Generate a GUID for this message $messagelist[$i]['guid'] = md5( $messagelist[$i]['origtime']. $messagelist[$i]['callerchan']. $messagelist[$i]['sender'] ); $i++; } } return $messagelist; } function renderPodcast($messagelist = array()) { require 'XML/Util.php'; global $conf; header("Content-type: text/xml"); echo ""; ?> <?php echo XML_Util::replaceEntities($conf['title']); ?> en-US © Clean From: <?php echo XML_Util::replaceEntities($message['callerid']); ?> Voicemail recieved on , from: From .mp3" type="audio/mpeg" /> .mp3 Alkaloid%20Networks,podmail Clean 1234," if (preg_match('/^([0-9\*#]+)\s+=>\s+([0-9\*#]+),/', $line, $matches)) { if ($user == $matches[1] && $pass == $matches[2]) { return(true); break; } } } if ($messagelist === false) { notAuthenticated("Invalid message list"); } } function authLDAP($user, $pass, $context) { global $conf; $user = sanitizeQueryStr($user); $pass = sanitizeQueryStr($pass); $context = sanitizeQueryStr($context); $filter = '(&'; $filter .= '(context='.$context.')'; $filter .= '('.$conf['ldap']['userkey'].'='.$user.')'; $filter .= ')'; $attributes = array('voiceMailbox'); $ldap = @ldap_connect($conf['ldap']['host']) or notAuthenticated("Could not connect to LDAP"); @ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3) or notAuthenticated("Unable to start LDAPv3"); $binddn = $conf['ldap']['userkey'].'='.$user.','.$conf['ldap']['basedn']; @ldap_bind($ldap, $binddn, $pass) or notAuthenticated("Unable to bind to LDAP"); $res = @ldap_list($ldap, $conf['ldap']['basedn'], $filter, $attributes) or notAuthenticated("Unable to search LDAP"); $res = @ldap_first_entry($ldap, $res) or notAuthenticated('Invalid Credentials or LDAP error'); $res = @ldap_get_attributes($ldap, $res) or notAuthenticated('Internal Error'); return ($res['voiceMailbox'][0]); } # This is not the way I want to handle this. From everythign I can tell # these are the only characters that need escaping. I may be wrong but if I am # then someone please tell me a library or function call to use instead? function sanitizeQueryStr($str) { return( preg_replace( array('/\*/', '/\(/', '/\)/', '/\x00/'), array('\2a', '\28', '\29', '\00'), $str) ); }