sendpage-1.000003/0000755000076500007650000000000010737242241012177 5ustar keeskeessendpage-1.000003/sendpage.cf0000644000076500007650000003270110737241466014312 0ustar keeskees############################################################################## # sendpage.cf # ############################################################################## # # There are four majors sections: # - global Any global settings # - "modem" Each modem's settings # - "pc" Each Paging Central's settings # - "recip" Each recipient name's settings # # Except for global, each section starts with the section name in # []'s. So, to define a modem named "sportster", the section name # would be "[modem:sportster]", and all of the sportster's settings # would follow. # # section names cannot have "=", "@", or ":" in their text. # # For each major section, I go through all the default values # and their variable names. If you don't like a default value, # just comment in the variable name, and change the value to something # else. # ############################# # global section ############################# # queue & manager-level debugging. Default is "false" # #debug=true # select-loop debugging. Default is "false" # Leave this as it is unless you're digging around in the select # loop code. It is VERY annoying. :) # #debug-select=true # SNPP activity debugging. Default is "false" # #debug-snpp=true # alias-expansion debugging. Default is "false" # #alias-debug=true # Filename prefix for writing process ID files. # Default is "/var/spool/sendpage/sendpage" # #pidfileprefix = /tmp/var/spool/sendpage/sendpage # Filename prefix for writing UUCP-style device locks. # Default is "/var/lock/LCK.." # #lockprefix = /tmp/var/lock/LCK.. # Directory to store Paging Central pager queues # Default is "/var/spool/sendpage" # #queuedir = /tmp/var/spool/sendpage # Database dbi type/connection info for large user lists # # Defaults are all "" # #dsn = dbi:Pg:Db:dbname=sendpage #dsn = dbi:mysql:database=sendpage;host=db.mydomain.name;port=3306 #dbuser = sendpage #dbpass = mekmitasdigoat #dbtable = sendpage # Username that sendpage should be running as. # Default is "sendpage" #user = nemesis # Group sendpage needs to lock devices. # Default is "uucp" #group-lock=lock # Group sendpage needs to read/write devices. # Default is "tty" #group-tty=users # Email address that page emails claims to be coming from. # Default is "sendpage" # #page-daemon = nemesis@outflux.net # Will page-daemon be Cc'd on email failures? # Default is "true" #cc-on-error = false # By which mechanism should email be delivered? mail, sendmail, or SMTP? # Default is "sendmail" #mail-agent = mail # Should page senders be notified about permanent failures? # Default is "true" #fail-notify = false # Page senders should be notified every Xth temporary failure. # (0 means 'never') # Default is "5". #tempfail-notify-after=10 # How many times does a page hit a temporary error before failing forever? # Default is "20" #max-tempfail=50 # Should syslog be used instead of STDERR for logging? # Default is "true" #syslog=false # When using syslog, which syslog options should be used? # (any of "pid", "ndelay", "cons", or "nowait") # Default is "pid" #syslog-opt= pid, cons # Which syslog facility should be used? man syslog for more info # Default is "daemon" #syslog-facility=local6 # What port sendpage binds to for the SNPP server. # Default is "444" #snpp-port=3044 # What local address sendpage binds the SNPP server to. # *NOTE* # You should set this to "0.0.0.0" if you want to receive pages from # the rest of the world. # # You can use this to limit which IP address SNPP is bound to. # *NOTE* # # Default is "localhost" #snpp-addr=0.0.0.0 # What to do about incoming SNPP requests (multiple entries allowed) # *NOTE* # You should set this to "0.0.0.0/0.0.0.0:ALLOW" if you want # anyone to connect to your SNPP server. # *NOTE* # # Format is "NET/MASK:WAY" where WAY is either "ALLOW" or "DENY" # # ACL processing is done top to bottom, and if no match occurs, # the connection is rejected. # # Default is "127.0.0.1/255.255.255.255:ALLOW" #snpp-acl="128.174.5.0/255.255.255.0:ALLOW" #snpp-acl="128.23.1.10/255.255.255.255:DENY" #snpp-acl="128.23.1.0/255.255.255.0:ALLOW" # List of modems a PC should use if not told explicitly. # Default is all available modems. # #modems = sportster # Default domain for email-cc (not implemented yet) #fallback-email-domain = @cpoint.net # Command to run after each successful or failed page # Default is unset # Command gets contents of page on stdin, and 2 command line # parameters: # arg 1: status (0=page failed, 1=page succeeded) # arg 2: page alias (who was paged) # #completion-cmd = "/usr/local/bin/page-sent" ###################### # modem configuration # Each section should be called "modem". (e.g. "[modem:sportster]") ###################### # My first "modem" section. I named it "sportster" because that's what it is [modem:sportster] # Should this modem's character-level debugging be turned on? # Default is "false" #debug = true # This modem's transmission settings. # Defaults are data=7, parity=even, stop=1, flow=rts, # baud=9600, strict-parity=false #data = 8 #parity = none #stop = 1 #flow = soft #baud = 38400 #strict-parity = true # Which device this modem should use # Default is "/dev/null", so you better specify one. :) dev = /dev/modem # This modem's initialization string # Default is "ATZ" #init = ATE&1 # This modem's "okay" response string (this is a regexp) # Default is "OK" #initok = 0 # What to look for if something has gone wrong while init'ing (this is a regexp) # Default is "ERROR" #error=1 # How many seconds to wait for initok after init with this modem # Default is "4" #initwait=1 # How many times to try to initialize the modem # Default is "2" #initretries=1 # The dialing prefix for this modem # Default is "ATDT" #dial=ATDP # The telephone prefix to get a dialtone out of the building (for PBXs, etc) # Default is "" #dialout="9," # The areacode this modem has (for figuring areacode matches with PCs) # Default is unset # If you never use area code, either make this "-" or don't use "areacode" # options in the PC definitions. #areacode="847" # The dialing prefix for dialing long distance calls (some PBXs do weird stuff) # Default is "1-" #longdist="81" # What to look for after connecting successfully (this is a regexp) # Default is "CONNECT.*\r" #dialok=0 # What to look for if something goes wrong while dialing (this is a regexp) # Default is "ERROR|NO CARRIER|BUSY|NO DIAL|VOICE" #no-carrier=ERROR # How many seconds to wait for dialing to connect # Default is "60" #dialwait=30 # How many times to try and redial (unimplemented, actually...) # Default is "3" #dialretries=1 # How should "carrier detection" be done? "on", "off", "dsr" # "DSR" can be used when a cable or OS doesn't correctly provide CD # Default is "on" #carrier-detect=dsr # How many seconds should the DTR be held down during initialization? # Default is "0.5" #dtrtime=1.5 ## As an example, this is what a directly attached paging terminal ## might look like: ## #[modem:direct] #carrier-detect=off #dtrtime=0 #init="" #initok="" #dial="" #dialok="" #dev=/dev/serial ######################### # Paging central section # each section should be called "pc" (e.g. "[pc:ameritech]") ######################### #[pc:example] # Is this PC enabled? Set to false to stop processing a PC, for example # Default is "true" #enabled= false # This PC's protocol-level debugging. # Default is "false" #debug = true # Email address that page emails claims to be coming from. # Default is unset, and will fall back to the global "page-daemon" setting # #page-daemon = someone@other.place # Will page-daemon be Cc'd on email failures for this PC? # Default is unset, and will fall back to global "cc-on-error" setting #cc-on-error = true # Should page senders be notified about permanent failures? # Default is unset; will fall back to global option #fail-notify = false # Page senders should be notified every Xth temporary failure. # (0 means 'never') # Default is unset; will fall back to global option #tempfail-notify-after=10 # How many times does a page hit a temporary error before failing forever? # Default is unset; will fall back to global option #max-tempfail=50 # Command to run after each successful or failed page, overrides global # Default is unset # Command gets contents of page on stdin, and 2 command line # parameters: # arg 1: status (0=page failed, 1=page succeeded) # arg 2: page alias (who was paged) # #completion-cmd = "/usr/local/bin/page-sent" # If for some reason you need to override the default list of modems, # do it here. # Default is all available modems. #modems = sportster # If you need specific communication settings for this PC, they go here. # Defaults are data=7, parity=even, stop=1, flow=rts, # baud=115200, strict-parity=false #data = 8 #parity = none #stop = 1 #flow = soft #baud = 9600 #strict-parity = true # What areacode is this PC in? If unset, the modem won't match areacodes # Default is unset #areacode= 312 # What phone number to reach this PC at. # Default is "", so you better fill one in #phonenum= 5149243 # How many pages can be sent in each session with this PC? # Default is 0 (unlimited) #maxpages=12 # How many blocks can be sent in each session with this PC? # Default is 0 (unlimited) #maxblocks=60 # How many characters can be sent in each page for this PC? (For UCP, not TAP) # Default is 1024 #maxchars=300 # How many characters per block are allowed during TAP transmission? # The protocol normally has this at "250" (due to the 256 limit, and # encoding requires 6 chars). Making this higher than 250 isn't sensible, # but some TAPs need it smaller. # Default is "250" #chars-per-block=230 # How many times are we allowed to split up a page that exceeds the # max chars limit? (For example, if maxchars was "100" and maxsplits was # "5" and someone sent a 2000 character page, sendpage would generate # five 100-character pages before cutting off the page.) # Default is "6" #maxsplits=2 # Which TAP protocol to use. Should be one of "PG1", "PG3", or "UCP" # Regular TAP PagingCentrals are "PG1". # UCP PagingCentrals will need "UCP". # If you had a "pet3" style PC before, this needs to be "PG3" # Default is "PG1" #proto=PG3 # How many fields does the PC expect to be getting during Block Transmission? # If you had a "pet3" style PC before, this needs to be "3". # Default is "2" #fields=3 # What is the password for accessing this Paging Central? # Default is "000000". Shouldn't be more than 6 characters. #password=123456 # Should we assume strict TAP protocol, and require CR before each answer? # If you can set this to true, do so, as it makes textual response codes # easier to read. However, very few PCs use those codes, and very few # PCs have correctly implemented strict TAP, so it's unlikely you want this. # Default is "false" #stricttap=true # Characters less than 0x20 are allowed in a block's field? # If you can set this, it makes pages prettier (can send tabs, newlines, # etc), but some PCs really don't like this. See 'esc' and 'lfok' options. # Default is "false" #ctrl=true # Can characters less than 0x20 be escaped, as in TAP spec 1.8? # If you can't set "ctrl" to true, see if this one set to true works. # Default is "false" #esc=true # Is LF explicitly allowed by this PC? (only useful if "ctrl=false") # Default is "false" #lfok=true # Can fields be split across blocks? # Default is "true" #fieldsplits=false # How many seconds to wait before sending CR when waiting for the ID= tag? # Default is "2", from the T1 of the TAP protocol #answerwait=10 # How many retries to allow before giving up waiting for the ID= tag? # Default is "3", from the N3 of the TAP protocol #answerretries=3 # How many seconds before we giving up trying to dial this PC? # Default is whatever the modem's dialwait is #dialwait=20 # How many seconds should this PC wait between queue scans? # Default is "20" #rundelay=10 # I have a skytel that only works in Illinois, and I've only called this # number from chicago, but I think it's valid as a national TAP access # number. [pc:skytel] areacode=800 phonenum=7596366 stricttap=true # I have a nextel that should work Nationally, but I've only called this # number from Chicago, while I was IN Chicago with the NexTel. [pc:nextel] areacode=312 phonenum=9076683 stricttap=true # This is the BellSouth RIM pager [pc:rim-pager] areacode=800 phonenum=868-2835 stricttap=true ################### # Recipients # This section starts with "recip". (e.g. "[recip:cook]") # Sorry this section is so clunky... # # 'email-cc' will get passed down to an expanding alias. For # example, look at the "[recip:cook]" below. I have an email-cc, # and it'll stick with the recip alias, even though it finally # expands the "dest" to be "cook_ameritech". Did that make any sense? # # dest: Where to send the page. Either PIN@paging-central, or # another recip name. # email-cc: Where to send an email CC of the page # ################### # My three pagers [recip:cook_ameritech] dest = 1234567@ameritech [recip:cook_nextel] dest = 1234567@nextel [recip:cook_skytel] dest = 1234567@skytel [recip:cook] dest = cook_ameritech email-cc= cooke@cpoint.net # Paul's nextel [recip:pholcomb] dest = 1234567@nextel # Kirsten's ameritech pager [recip:kirsten] dest = 1234567@ameritech email-cc= dragoon@blight.com # Christian's RIM pager [recip:cvoid] dest = 1234567@rim-pager # A group alias for Paul and me [recip:oncall] dest = cook, pholcomb email-cc= qpoint@cpoint.net sendpage-1.000003/t/0000755000076500007650000000000010737242241012442 5ustar keeskeessendpage-1.000003/t/00_init.t0000644000076500007650000000122110737241466014075 0ustar keeskees#!/usr/bin/perl -w -I../lib use Test::More tests => 10; BEGIN { use_ok( 'Sendpage::KeesConf' ); # 1 use_ok( 'Sendpage::KeesLog' ); # 2 use_ok( 'Sendpage::Modem' ); # 3 use_ok( 'Sendpage::Page' ); # 4 use_ok( 'Sendpage::Queue' ); # 5 use_ok( 'Sendpage::PageQueue' ); # 6 use_ok( 'Sendpage::Recipient' ); # 7 use_ok( 'Sendpage::PagingCentral' ); # 8 use_ok( 'Sendpage::SNPPServer' ); # 9 use_ok( 'Sendpage::Db' ); # 10 #use_ok( 'Sendpage::Utilities' ); # 11 #use_ok( 'Sendpage::Device' ); # 12 } sendpage-1.000003/MANIFEST0000644000076500007650000000126210737241550013333 0ustar keeskeesLICENSE README Changes MANIFEST Makefile.PL TODO FEATURES THANKS sendpage.cf sendpage.spec email2page.conf snpp.conf docs/README docs/PagingCentrals.txt docs/sendmail.txt docs/postfix.txt docs/sendpage.php docs/pc-testing.txt docs/sendpage-manual.lyx examples/configure-pc examples/modemtest lib/Sendpage/KeesConf.pm lib/Sendpage/KeesLog.pm lib/Sendpage/Page.pm lib/Sendpage/PageQueue.pm lib/Sendpage/PagingCentral.pm lib/Sendpage/Queue.pm lib/Sendpage/Recipient.pm lib/Sendpage/Modem.pm lib/Sendpage/SNPPServer.pm lib/Sendpage/Db.pm sendpage snpp email2page sendmail2snpp sendpage-db sendpage.init t/00_init.t META.yml Module meta-data (added by MakeMaker) sendpage-1.000003/Makefile.PL0000644000076500007650000000352210737241466014163 0ustar keeskeesuse vars qw($serialport_driver $serialport_version); BEGIN { require 5.006; # Architecture check for SerialPort driver $serialport_driver="Device::SerialPort"; $serialport_version="1.0"; if( $^O =~ /Win/io ) { $serialport_driver="Win32::SerialPort"; $serialport_version="0.07"; } } use ExtUtils::MakeMaker; use strict; use warnings; use Config; # Don't need these yet #use File::Spec::Functions; #use File::Basename; my %args = ( pkg_name => 'sendpage', name => 'sendpage', DESTDIR => undef, ); my @pass_args; while (my $arg = shift @ARGV) { my ($key, $value) = split /=/, $arg; if (exists $args{$key}) { $args{$key} = $value; } else { push @pass_args, $arg; } } @ARGV = @pass_args; my %opts=( 'INSTALLDIRS' => 'site', 'NAME' => $args{'name'}, 'AUTHOR' => 'Kees Cook ', 'VERSION_FROM' => 'sendpage', # finds $VERSION 'ABSTRACT_FROM' => 'sendpage', 'EXE_FILES' => [ qw( sendpage snpp email2page sendmail2snpp sendpage-db ) ], 'PREREQ_PM' => { 'Net::SNPP' => 1.10, 'Mail::Send' => 1.08, $serialport_driver => $serialport_version, 'DBI' => 0, 'Test::More' => 0, }, ); if ($ExtUtils::MakeMaker::VERSION > 5.45) { $opts{'PREREQ_FATAL'} = 1, } # This puts us in the site_perl directory, not dependant on any version # of perl. if (defined($Config{'sitelib_stem'}) && $Config{'sitelib_stem'} ne "") { #print "stem is: $Config{'sitelib_stem'}\n"; $opts{'INSTALLSITELIB'} = ""; $opts{'INSTALLSITELIB'} = $args{'DESTDIR'} if (($] >= 5.008 && $] < 5.008005) || $ExtUtils::MakeMaker::VERSION =~ /5\.9[1-6]|6\.0[0-5]/); $opts{'INSTALLSITELIB'} .= $Config{'sitelib_stem'}; } WriteMakefile(%opts); # /* vi:set ai ts=4 sw=4 expandtab: */ sendpage-1.000003/snpp0000755000076500007650000001201710737241677013122 0ustar keeskees#!/usr/bin/perl # # quick script for sending SNPP messages # # $Id: snpp 316 2008-01-03 20:21:19Z keescook $ # # Copyright (C) 2000-2004 Kees Cook # kees@outflux.net, http://outflux.net/ # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # http://www.gnu.org/copyleft/gpl.html =head1 NAME snpp - send pages via SNPP =head1 SYNOPSIS snpp [OPTIONS] recipient... =head1 OPTIONS =over 4 =item -s SERVER[:PORT] Connect to the specified SERVER (and PORT, if given). Default is "localhost:444". =item -f USER Show that the sent page is coming from USER. Default is the current user. =item -m MESSAGE Send the given MESSAGE instead of reading text from stdin. =item -n Do not notify the 'from' user about the status of the page. =item -d Display SNPP session debugging. =item -C CONF Read CONF instead of /etc/snpp.conf for server default. =item -h Display a summary of all the available command line options. =back =head1 DESCRIPTION This tool is used to send a page via the Simple Network Paging Protocol (level 2). It is designed to be used with 'sendpage', but should work with any other SNPP servers as well. The /etc/snpp.conf file can contain a single line in the form of server:ADDRESS[:PORT] where ADDRESS and PORT are the defaults for snpp. =head1 AUTHOR Kees Cook =head1 BUGS Not much happening in this tool, but I bet the use of "CALLer id" is not standard, and other SNPP server may require this tool run with the '-n' option all the time. =head1 COPYRIGHT snpp is free software; it can be used under the terms of the GNU General Public License. =head1 SEE ALSO perl(1), sendpage(1), Net::SNPP(3) =cut use Getopt::Std; use Net::SNPP; use Sys::Hostname; use IO::File; my %opts; my $VERSION="0.1"; sub Usage { die "Usage: $0 [OPTIONS] pin1 [pin2 [...]] version $VERSION -s SERVER[:PORT] SNPP server to use (default is 'localhost') -f USER force page to be from user USER (default is current user) -m MESSAGE message to send (reads from stdin by default) -n no email carboning to 'from' -d turn debug on -C CONF read CONF instead of /etc/snpp.conf -h you're reading it. :) "; } # get our options if (!getopts('hdns:f:m:C:',\%opts) || $opts{h} || !@ARGV) { Usage(); } ### DEFAULTS # set config file $opts{C}="/etc/snpp.conf" unless ($opts{C}); # set server my $server="localhost"; my $fh = new IO::File $opts{C}, "r"; if (!defined($fh)) { warn "Cannot read '$opts{C}' file (using defaults): $!\n"; } else { my $num=0; my $line; foreach $line (<$fh>) { chomp($line); $num++; # skip comments and blanks next if ($line =~ /^\s*#/ || $line =~ /^\s*$/); ($cmd,$arg)=split(/:/,$line,2); if ($cmd eq "server") { $server=$arg; } else { warn "unknown command '$cmd' in '$opts{C}', line ". "$num\n"; } } undef $fh; } # override config file and defaults $opts{s}=$server unless ($opts{s}); # verify that there is a "SNPP" service (weakness in Net::SNPP...) $proto=getprotobyname("tcp"); if (!defined($proto)) { die "Could not resolve 'tcp' protocol into a protocol number!\nPlease check /etc/protocols\n"; } else { warn "'tcp' is proto $proto\n" if ($opts{d}); } $port=getservbyname("snpp","tcp"); if (!defined($port)) { die "Could not resolve 'snpp' service into a port number!\nPlease check /etc/services\n"; } else { warn "'snpp' is service $port\n" if ($opts{d}); } $snpp = Net::SNPP->new($opts{s}, Debug => $opts{d} ); if (!defined($snpp)) { die "Could not connect to SNPP server '$opts{s}'\n"; } # get the pins my @pins = @ARGV; # get the page text undef $/; my $text = $opts{m} ? $opts{m} : ; $/="\n"; # who is it from? if (!defined($opts{f}) && !$opts{n}) { $opts{f}=scalar(getpwuid($<))."\@"; $opts{f}.=hostname; } # send pins foreach $pin (@pins) { if (!$snpp->pager_id($pin)) { warn $snpp->message(); if ($snpp->status()==4) { exit($snpp->code()-400); } } } # send 'caller id', if we need to if (!$opts{n}) { if (!$snpp->caller_id($opts{f})) { warn $snpp->message(); if ($snpp->status()==4) { exit($snpp->code()-400); } } } # send text if (!$snpp->data($text)) { warn $snpp->message(); if ($snpp->status()==4) { exit($snpp->code()-400); } } # issue the send if (!$snpp->send()) { warn $snpp->message(); exit($snpp->code()%100); } $snpp->quit; undef $snpp; sendpage-1.000003/sendpage-db0000644000076500007650000000465510737241677014321 0ustar keeskees#!/usr/bin/perl # # dbi is the tool that will assist in loading data into a database # # $Id: sendpage-db 316 2008-01-03 20:21:19Z keescook $ # # Copyright (C) 2004 Todd T. Fries # todd@fries.net, http://FreeDaemonConsulting.com/ # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # http://www.gnu.org/copyleft/gpl.html use Getopt::Std; use Sendpage::Db; getopts('hpadvtc:U:P:'); if ($opt_c) { $dbtype = $opt_c; } else { $dbtype = "dbi:mysql:database=test;host=localhost;port=3306"; } if ($opt_U) { $dbuser = $opt_U; } if ($opt_P) { $dbpass = $opt_P; } my $db = Sendpage::Db->new($dbtype,$dbuser,$dbpass); sub HELP_MESSAGE { printf STDERR "Usage: \n"; printf STDERR "-v verbose\n"; printf STDERR "-p show table entries\n"; printf STDERR "-a add an entry\n"; printf STDERR "-d remove an entry\n"; printf STDERR "-c database connection type, e.g. dbi:mysql:\n"; printf STDERR "-U database user (optional)\n"; printf STDERR "-P database password (optional)\n"; exit(0); } if ($opt_h) { HELP_MESSAGE; exit(0); } if ($opt_v) { # verbose $debug = 1; } else { $debug = 0; } if ($opt_p) { $db->show; exit(0); } if ($opt_a) { while() { ($recip,$var,$value) = split(/ /,$_); chomp($value); if ($var eq "email-cc" or $var eq "dest") { if($debug) { print "adding $recip with $var = $value\n"; } $db->update("$recip:$var",$value); } else { print "invalid synatx, parsed: "; print "recip = $recip, var = $var, val = $value\n"; } } if($debug) { $db->show; } exit(0); } if ($opt_d) { while() { chomp($recip = $_); if($debug) { print "deleting $recip info\n"; } $db->delete("$recip:dest"); $db->delete("$recip:email-cc"); } exit(0); } HELP_MESSAGE; 0; sendpage-1.000003/sendpage0000755000076500007650000013360610737241677013740 0ustar keeskees#!/usr/bin/perl # # sendpage is the tool that will handle all the paging functions # # $Id: sendpage 316 2008-01-03 20:21:19Z keescook $ # # Copyright (C) 2000-2004 Kees Cook # kees@outflux.net, http://outflux.net/ # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # http://www.gnu.org/copyleft/gpl.html =head1 NAME sendpage - listen for pages via SNPP, and send pages via modem =head1 SYNOPSIS sendpage [OPTIONS] [recipient ...] =head1 OPTIONS =over 4 =item -bd Start sendpage in "daemon mode" where it will start all the Paging Central queues and wait for pages to be delivered. When sendpage runs as a daemon, it must be running as the 'sendpage' user as specified in the sendpage.cf file. =item -bp Display all the pages waiting in the Paging Central queues. =item -bv Try to expand the "recipient" name, using the recipient aliases specified in the configuration file. =item -bs Shutdown the running sendpage daemon and all its children. If a Paging Central is in the middle of delivering a page, it will finish up and exit as soon as its current page is handled. =item -br This will send a SIGHUP to the master daemon. When the master gets the SIGHUP, it will re-read its configuration file, and restart all the Paging Centrals. It will wait for any busy Paging Centrals to finish before continuing. =item -bq This displays the state of the running daemons: Running or Not running. If a pid file is stale (the file exists, but the process doesn't), it will mark that pid as "Stale". =item -q[R pc] This will send a SIGUSR1 signal to either the master daemon, or, if the Paging Central is specified, just that Paging Central in particular. When the master gets a SIGUSR1, it will send it to each of the running Paging Centrals. If the Paging Central is not busy, it will immediately start a queue run. =item -C FILE Read the configuration file FILE instead of the default /etc/sendpage.cf =item -h Display a summary of all the available command line options. =item -d Turn on debugging (like "debug=true" in /etc/sendpage.cf) =item -f USER Show that the sent page is coming from USER. Default is the current user. =item -m MESSAGE Send the given MESSAGE instead of reading text from stdin. =item -n Do not notify the 'from' user about the status of the page. =back =head1 DESCRIPTION Sendpage can run as the delivery agent, or as a client to insert a page into the paging queue. For the various command-line arguments, the idea here was to use sendmail-style arguments where I can, not to fully implement every option that sendmail has. I just want the learning curve of sendpage to be small for people already familiar with sendmail. =head1 FILES =over 4 =item F Default location for sendpage.cf, which holds all the configuration information for sendpage, including Paging Central definitions, recipients, and various other behaviors. =item F Default directory for all the Paging Central queues and pid files. =item F Default directory to keep the UUCP-style device locks. =back =head1 AUTHOR Kees Cook =head1 BUGS Oh, I bet this code is crawling with them. :) I've done my best to test this code, but I'm only one person. If you find strange behavior, please let me know. =head1 COPYRIGHT sendpage is free software; it can be used under the terms of the GNU General Public License. =head1 SEE ALSO perl(1), kill(1), Device::SerialPort(3), Mail::Send(3), Sendpage::KeesConf(3), Sendpage::KeesLog(3), Sendpage::Modem(3), Sendpage::PagingCentral(3), Sendpage::PageQueue(3), Sendpage::Page(3), Sendpage::Recipient(3), Sendpage::Queue(3) =cut # we need at least this version. FIXME: I forgot why, though. :P require 5.006; # Global variables; $VERSION=1.000_003; # our version! $VERSION_human=sprintf("%d.%d.%d", int(${VERSION}), substr((${VERSION}=~/^\d\.(\d+)$/)[0],0,3), substr((${VERSION}=~/^\d\.(\d+)$/)[0],3,3)); my $config; # holds the configuration object undef $log; # holds logging object my $db; # holds the db object, if used # Module-global variables my %CHILDREN; # who the childrens are my $SHUTDOWN; # when to shutdown my $RELOAD; # when we're reloading my $DEBUG; # for debugging my $DEBUG_SELECT;# for debugging select loop my $DEBUG_SNPP; # for debugging SNPP issues my %opts; # holds the command line args hash undef @modems; # List of available modems undef @pcs; # List of enabled PCs # Global socket variables undef $server; # holds the SNPP server obj undef $s; # holds our Select set undef %CNXNS; # List of open SNPP client pipes by filehandle undef %PIPES; # List of open SNPP client pipes by PID # Global queue run variables my $PC; # holds name of PC for queue runners my $sleeptime; # holds sleeptime for next queue delay # FIXME: load modules in a nice error-correcting fashion (borrow from mr house) use POSIX; use Getopt::Std; use Sendpage::Modem; use Sendpage::KeesConf; use Sendpage::PagingCentral; use Sendpage::PageQueue; use Sendpage::Page; use Sendpage::Recipient; use Sendpage::KeesLog; use Sendpage::SNPPServer; use Sendpage::Db; use Socket; use IO::Select; use IO::Pipe; use Sys::Hostname; sub Usage { die "Usage: $0 [OPTIONS] [alias ...] version $VERSION_human General Options -h you're reading it. :) -d turn debug on -C FILE use FILE as the sendpage.cf file -t test all configured modems Daemon Options -bd run in daemon mode -bp display the queues -bv verify addresses -bs shutdown server -br have server reload configurations -bq query state of daemons -q force a queue run -qR PC force a queue run only for the PC paging central Page Queuing Options -f USER force page to be from USER (default is current user) -m MESSAGE message to send (reads from stdin by default) -n no email status sent to 'from' for this page "; } # Start logging immediately $log=Sendpage::KeesLog->new(Syslog => 0); # get our options if (!getopts('htvdqC:b:R:f:m:n',\%opts) || $opts{h}) { Usage(); } # build default configuration, with any command line info $config=initConfig(\%opts); # load configuration Initialize(); # Restart logging $log->reconfig(Syslog => $config->get("syslog"), Opts => $config->get("syslog-opt"), Facility => $config->get("syslog-facility"), MinLevel => $config->get("syslog-minlevel"), ); # Mode of operation selection # # Modes: # - daemon (spawn queue runners, listen for pages) # - queue display # - address expansion # - force a queue run (optionally for only a certain PC) # if ($opts{t}) { ModemInit(); exit(0); } if ($opts{b}) { QueryDaemons(1) if ($opts{b} eq "q"); SendHUP() if ($opts{b} eq "r"); ShutdownEverything() if ($opts{b} eq "s"); BecomeDaemon() if ($opts{b} eq "d"); DisplayQueue() if ($opts{b} eq "p"); VerifyAddress(@ARGV) if ($opts{b} eq "v"); warn "Unknown run mode: '$opts{b}'\n"; Usage(); } if ($opts{q}) { my $ret=SendSignal('USR1',$opts{R} ? $opts{R} : ""); # FIXME: should run the queue by hand if no one else can die "Failed to notify queue manager: $!\nMaybe you should run me with -bd?\n" if (!defined($ret) || $ret != 0); exit; } die "Must be root to write pages directly to queue. Please use 'snpp' instead.\n" if (!VerifyIdentities()); DropPrivs(); if (!@ARGV) { Usage(); } else { my $msg=$opts{m}; # it's time to write a file directly to the queue # who is it from? if (!defined($opts{f}) && !$opts{n}) { $opts{f}=scalar(getpwuid($<))."\@"; $opts{f}.=hostname(); } if (!defined($msg)) { my $line; while ($line=) { $msg.=$line; } } # generate errors to the stderr ArrayDig(@ARGV); # turn on syslog for queue logging $log->on(); # try to write the pages exit Sendpage::SNPPServer->write_queued_pages(undef,$opts{f},$msg, $config,$log,$DEBUG,@ARGV); } sub QueryDaemons { my($display)=@_; my(@check,$pc,$pid,$state,$running,$disabled); undef $running; @check=@pcs; unshift(@check,""); foreach $pc (@check) { if ($pc ne "" && $config->get("pc:$pc\@enabled")==0) { $disabled=1; } else { $disabled=0; } $pid=PidOf($pc,1); if ($pid==0) { $state="Not running"; } else { undef $!; kill 0, $pid; if ($! == ESRCH) { $state="Stale: not running"; } else { $state="Running"; $running=1; } } printf("%-6d %20s : %s%s\n",$pid, ($pc eq "") ? 'Queue Manager' : $pc, $state, $disabled ? " (disabled)" : "") if ($display); } exit(0) if ($display); return $running; } sub SendHUP { my $ret=SendSignal('HUP',""); die "Failed to notify queue manager: $!\n" if (!defined($ret) || $ret != 0); exit; } sub ShutdownEverything { # there's no need for individual killing is there? #my $ret=SendSignal('QUIT',$opts{R} ? $opts{R} : ""); my $ret=SendSignal('QUIT',""); warn "Failed to notify queue manager: $!\n" if (!defined($ret) || $ret != 0); exit; } sub Initialize { &loadConfig(); $DEBUG=$config->get("debug"); $DEBUG_SELECT=$config->get("debug-select"); $DEBUG_SNPP=$config->get("debug-snpp"); @pcs=$config->instances("pc"); undef %pcs; grep($pcs{$_}=1,@pcs); @modems=$config->instances("modem"); } # plops down a pid file sub RecordPidFile { my($name,$pid)=@_; my($file); $name=".$name" if ($name ne ""); $file=$config->get("pidfileprefix").$name.".pid"; open(FILE,">$file") || $log->do('err',"Cannot write to '$file': %s",$!); print FILE $pid,"\n"; close(FILE); } # deletes a pid file by name sub YankPidFile { my($name)=@_; my($file); $name=".$name" if ($name ne ""); $file=$config->get("pidfileprefix").$name.".pid"; unlink($file) || $log->do('err',"Cannot unlink '$file': %s",$!); } # sends a signal to the specified PID # returns non-0 on failure sub SendSignal { my($sig,$pid)=@_; if ($pid !~ /^\d+$/) { $pid=PidOf($pid); } return undef if ($pid == 0); $log->do('debug',"signalling '$sig' to pid '$pid'") if ($DEBUG); undef $!; kill $sig, $pid; return $!+0; } # tries to find the PID of a certain sendpage # return PID, or 0 or failure sub PidOf { my($name,$quiet)=@_; my($file,$pid); if ($name ne "") { if (!defined($pcs{$name})) { $log->do('warning',"No such PC '$name'"); return 0; } $name=".$name"; } $pid=0; $file=$config->get("pidfileprefix").$name.".pid"; if (-f $file) { my $line; open(FILE,"<$file") || $log->do('err',"Cannot read '$file': %s",$!); chomp($line=); # this is used to untaint for a sendpage -q if ($line=~/^(\d+)$/) { $pid=$1; } close(FILE); } else { my $warning=sprintf("No pid file found for sendpage%s!", $name); $log->do('warning',"%s",$warning) if (!defined($quiet)); } return ($pid+0); } sub NiceShutdown { $SIG{QUIT}=$SIG{INT}=DEFAULT; $SHUTDOWN=1; if ($PC eq "") { my($pc,$cnxn); # Shutdown PCs foreach $pc (@pcs) { if ($config->get("pc:$pc\@enabled")==0) { next; } $log->do('debug',"Signalling '$pc' ...") if ($DEBUG); SendSignal('QUIT',$pc); } # Shut down SNPP clients foreach $cnxn (keys %PIPES) { $log->do('debug',"Signalling SNPP client '$cnxn' ...") if ($DEBUG); SendSignal('QUIT',$cnxn); } } else { $log->do('debug',"Shutting down nicely: '$PC'") if ($DEBUG); } } sub ImmediateShutdown { $SIG{TERM}=DEFAULT; $log->do('debug',"Shutting down immediately") if ($DEBUG); exit(0); } sub QueueRun { #$log->do('debug',"Pid $$ heard signal USR1") if ($DEBUG); # we need to signal all the PC's if we're master if ($PC eq "") { my $pc; foreach $pc (@pcs) { if ($config->get("pc:$pc\@enabled")==0) { next; } $log->do('debug',"Signalling '$pc' ...") if ($DEBUG); SendSignal('USR1',$pc); } } else { # perform queue run $log->do('debug',"QueueRun requested for '$PC' ...") if ($DEBUG); # if we get a request for this DURING a queue run, we # should immediately rescan our queue. To do this, # we set our next sleeptime to 0 $sleeptime=0; } } sub DisplayQueue { my($queue, $waiting, $page, $recip); foreach $pc (@pcs) { $queue=Sendpage::PageQueue->new($config,$config->get("queuedir")."/$pc"); if (($waiting=$queue->ready())>-1) { print "\nin the '$pc' queue: ".($waiting+1)."\n"; while (defined($page=$queue->getPage())) { print "\tqueue filename: ".$queue->file()."\n"; print "\tdeliverable: ".$page->deliverable()."\n"; print "\tattempts: ".$page->attempts()."\n"; print "\tfrom: ".$page->option('from')."\n" if ($page->option('from') ne ""); for ($page->reset(), $page->next(); defined($recip=$page->recip()); $page->next()) { print "\tdest: '".$recip->name()."' (pin '".$recip->pin()."', email '".$recip->datum('email-cc')."')\n"; } $queue->fileDone(); print "\n"; } } } exit(0); } sub VerifyAddress { my ($fail,@recips); ($fail,@recips)=ArrayDig(@ARGV); if ($fail != 0) { exit(1); } foreach $recip (@recips) { print "deliverable: ".$recip->name()." as ".$recip->pin(). " via ".$recip->pc()." (email is '".$recip->datum('email-cc')."')\n"; } exit(0); } sub ModemInit { my $modref; # test modems, keeping functioning ones in a list for the PCs to pick if ($DEBUG) { grep($log->do('debug',"found listing for modem: $_"),@modems); grep($log->do('debug',"found listing for pc: $_"),@pcs); } # should be limit which modems we're using? if (defined($modref=$config->get("modems",1))) { # we should limit the modem list @modems=@{ $modref }; grep($log->do('debug',"using specified modem: $_"),@modems) if ($DEBUG); } else { # pull from instance list @modems=$config->instances("modem"); } # check the modems @modems=verifyModems(@modems); grep($log->do('debug',"found functioning modem: $_"),@modems) if ($DEBUG); $log->do('alert',"no functioning modems!") if (!defined($modems[0])); } sub BlockSignals { # define the signals to block my $sigset = POSIX::SigSet->new(SIGINT, SIGTERM, SIGQUIT, SIGUSR1, SIGHUP, SIGPIPE); # start blocking signals unless (defined sigprocmask(SIG_BLOCK, $sigset)) { $log->do('alert',"Could not block signals!"); } return $sigset; } sub UnblockSignals { # define the signals to block my $sigset = POSIX::SigSet->new(SIGINT, SIGTERM, SIGQUIT, SIGUSR1, SIGHUP, SIGPIPE); # stop blocking signals unless (defined sigprocmask(SIG_UNBLOCK, $sigset)) { $log->do('alert',"Could not unblock signals!"); } } # start one or all the children sub SpawnChildren { my($which)=@_; my($pid,$pc,@which,$sigset); if (defined($which)) { undef @which; push(@which,$which); } else { @which=@pcs; undef %CHILDREN; undef %STARTED; $log->do('info',"starting Queue Manager (sendpage v$VERSION_human)"); } # there was a race condition here between the fork # and the call to "SignalInit" where a child could # think it was still the manager, and receive yet # another signal, and act on it. Now we block signals. # spawn PCs foreach $pc (@which) { if ($config->get("pc:$pc\@enabled")==0) { next; } # start blocking signals $sigset=&BlockSignals(); $pid=fork(); if ($pid<0) { # failure $log->do('emerg',"Cripes! Cannot spawn process: %s",$!); } elsif ($pid>0) { # parent $log->do('debug',"spawned child: $pid for PC '$pc'") if ($DEBUG); $CHILDREN{$pid}=$pc; $STARTED{$pc}=time; RecordPidFile($pc,$pid); } else { # child # set up &DropPrivs(1); &SignalInit(); $log->do('debug',"I am child: $$ for PC '$pc'") if ($DEBUG); &CheckFDs(); &StartQueue($pc,$sigset); $log->do('crit',"PC '$pc' stopped processing! ". "Whoops, that can't happen!"); exit(1); } # stop blocking signals &UnblockSignals($sigset); } } # should they get serial port rights? sub DropPrivs { my ($serial)=@_; my $grouplist="nobody"; if (!defined($setUID)) { $log->do('crit',"Effective User ID unknown -- aborting!"); exit(1); } if (defined($serial)) { if (!defined($lockGID)) { $log->do('crit',"Effective Group ID for locking unknown -- aborting!"); exit(1); } if (!defined($ttyGID)) { $log->do('crit',"Effective Group ID for tty read/write unknown -- aborting!"); exit(1); } $grouplist="$lockGID $ttyGID"; } $(=$)=$grouplist; if ($( != $grouplist) { $log->do('crit',"Could not setgid to '$grouplist': %s -- aborting!",$!); } $<=$>=$setUID; if ($< != $setUID) { $log->do('crit',"Could not setuid: %s -- aborting!",$!); } } # Debugging routine to check on the state of open file descriptors. # I used this will tracking weird problems with the select loop. sub CheckFDs { return unless ($DEBUG); for (my $fd = 0; $fd < 10; $fd ++ ) { my $fh = IO::Handle->new_from_fd( $fd, "r" ); if (defined($fh)) { $log->do('debug',"$fd: open"); } # else { # $log->do('debug',"$fd: $!"); # } } } sub Respawn { my($undef,$forwarded) = @_; my($pid,$pc,$now); $!=0; if ($forwarded) { $pid=$forwarded; } else { $pid=wait; } # quit out if we're done (wait will sleep) return if ($SHUTDOWN==1); if ($pid==-1) { if ($!==ECHILD) { $log->do('warning',"No children on SIGCHLD?! Shutting down..."); $SHUTDOWN=1; return; } else { $log->do('warning',"Oops: waitpid spat totally unexpected error: %s",$!); return; } } if (defined($CHILDREN{$pid})) { $log->do('debug', "pid $pid died: '".$CHILDREN{$pid}."'") if ($DEBUG); $pc=$CHILDREN{$pid}; $now=time; # restart within the same 10 seconds?? if ($now<($STARTED{$pc}+10)) { $log->do('alert',"Ugly nasty problem with $CHILDREN{$pid} queue manager!"); $log->do('alert',"The same PC has died twice rather quickly. Shutting it down."); } else { $log->do('err',"Whoa! The '$CHILDREN{$pid}' PC died unexpectedly -- restarting it."); SpawnChildren($CHILDREN{$pid}); } } elsif (defined($PIPES{$pid})) { $log->do('debug',"SNPP connection (pid $pid) finished") if ($DEBUG_SNPP); # Only handle PID shutdown here # $log->do('debug',"(select: removing pipe ".fileno($PIPES{$pid}).")") # if ($DEBUG_SELECT); # $s->remove($PIPES{$pid}); # don't select on this handle anymore # PipeShutdownPipe($PIPES{$pid}); PipeShutdownPid($pid); } else { # defunct children? no! bastards! :) $log->do('warning',"Bastard child detected! Unknown PID '$pid' was reaped."); } } sub SignalInit { # set up signal handlers for children #$SIG{'CHLD'}='IGNORE'; $SIG{'HUP'}='IGNORE'; $SIG{'USR1'}='IGNORE'; $SIG{'PIPE'} = 'IGNORE'; $SIG{'INT'}=$SIG{'QUIT'}=\&NiceShutdown; $SIG{'TERM'}=\&ImmediateShutdown; } sub VerifyIdentities { my($user,$group,$name); # check for our setuid user $user=$config->get('user'); ($name,undef,$setUID)=getpwnam($user); if (!defined($name)) { $log->do('crit',"There is no such user named '$user'! Aborting..."); return undef; } # check for our locking group $group=$config->get('group-lock'); ($name,undef,$lockGID)=getgrnam($group); if (!defined($name)) { $log->do('crit',"There is no such group named '$group'! Aborting..."); return undef; } # check for our tty r/w group $group=$config->get('group-tty'); ($name,undef,$ttyGID)=getgrnam($group); if (!defined($name)) { $log->do('crit',"There is no such group named '$group'! Aborting..."); return undef; } # are we root? if (0 != $<) { return undef; } return 1; } sub BecomeDaemon { # check to see if we're already running if (QueryDaemons()) { warn "Already running:\n"; QueryDaemons(1); } if (!VerifyIdentities()) { $log->do('crit',"Must be running as 'root' to daemonize! Aborting..."); exit(1); } # daemon mode starts here ModemInit(); SignalInit(); $PC=""; # close file handles. (unless debugging) close(STDIN); close(STDOUT); close(STDERR) unless ($DEBUG || $DEBUG_SELECT || $DEBUG_SNPP); # Become a daemon my $pid = fork; exit if $pid; die "Couldn't fork: $!" unless defined($pid); POSIX::setsid() or die "Can't start a new session: $!"; # reconfig, and reopen syslog connection $log->reconfig(Syslog => $config->get("syslog"), Opts => $config->get("syslog-opt"), Facility => $config->get("syslog-facility"), MinLevel => $config->get("syslog-minlevel"), ); $log->on(); $0="sendpage: accepting connections"; $SHUTDOWN=0; RecordPidFile($PC,$$); # build new loop selector $s=IO::Select->new(); # start the queue runners SpawnChildren(); # listen for USR1 to send USR1s $SIG{'USR1'} = \&QueueRun; # listen for reload info $SIG{'HUP'}=\&Reload; # listen for children death #$SIG{'CHLD'}=\&Respawn; # start the SNPP stuff now StartSNPP(); # Enter the main processing loop MainLoop(); die "parent died: this should never have happened: $!\n"; } sub initSNPP { my $host=$config->get("snpp-addr"); my $port=$config->get("snpp-port"); # need to use "create" so that the "accept"s don't call the constructor $server=Sendpage::SNPPServer->create(Addr => $host, Port => $port); if (defined($server)) { $log->do('debug',"SNPP listener running on %s:%d", $server->sockhost, $server->sockport) if ($DEBUG_SNPP); } else { $log->do('crit',"SNPP listener for '%s:%s' failed: %s", $host,$port,$!); } # Expand our snpp ACLs so we don't have to during each connection @ACLs=(); my $item; foreach $item (@{$config->get("snpp-acl")}) { my ($netmask,$way) = split(/:/,$item,2); $way=uc($way); my ($net_str,$mask_str) = split(/\//,$netmask,2); $log->do('debug',"ACL loaded: '$net_str'/'$mask_str' is '$way'") if ($DEBUG_SNPP); my $net = inet_aton($net_str); my $mask = inet_aton($mask_str); push(@ACLs,[ $net, $mask, $net_str, ($way eq "ALLOW") ]); } } sub StopSNPP { $log->do('debug',"SNPP listeners shutting down") if ($DEBUG_SNPP); $log->do('debug',"(select: dropping ".fileno($server).")") if ($DEBUG_SELECT); $s->remove($server); $server->close(); undef $server; } sub StartSNPP { $log->do('info',"starting SNPP listeners"); initSNPP(); if (defined($server)) { $log->do('debug',"(select: adding server ".fileno($server).")") if ($DEBUG_SELECT); $s->add($server); } } sub RestartSNPP { StopSNPP(); StartSNPP(); } # FIXME: have all the ACLs pre-expanded for us... sub IPAllowed { my $sock=shift; # Verify that this connection is allowed my $peer=$sock->peerhost(); my $other_end = $sock->peername(); if (!defined($other_end)) { $log->do('alert',"SNPP client '$peer' failed getpeername!"); return undef; } my ($port, $iaddr) = unpack_sockaddr_in($other_end); my $other_ip_address = inet_ntoa($iaddr); # Compare this IP address to our ACL list my $item; my $allowed=0; my $found=0; foreach $item (@ACLs) { my ($net,$mask,$net_str,$allow) = @{$item}; # Drop the peer IP through the mask my $net_check = ($iaddr & $mask); my $check_str=inet_ntoa($net_check); if ($DEBUG_SNPP) { my $mask_str=inet_ntoa($mask); $log->do('debug',"ip: '$peer' mask: '$mask_str' ". "masked: '$check_str' net: '$net_str'"); } # if result is our network, we have a hit if ($check_str eq $net_str) { $found=1; $log->do('debug', "Matched ACL") if ($DEBUG_SNPP); if ($allow==1) { $allowed=1; } # if not allow, then reject last; } } if ($DEBUG_SNPP && $found == 0) { $log->do('debug',"No ACL matched '$peer'"); } if ($allowed != 1) { $sock->command("421 Connection denied"); $log->do('info',"SNPP client '$peer' rejected"); return undef; } return 1; } # This main loop cycles through all the pending socket connections: # - SNPP listeners to spawn SNPP clients # - SNPP client pipes to start PC queue runs sub MainLoop { my($fh,$read,$exc,$pipe,$sigset,$match,$pid); while ($SHUTDOWN!=1) { if (!defined($server)) { $log->do('crit',"Cannot start any SNPP listeners -- ". "aborting!"); NiceShutdown(); YankPidFile(""); exit(1); } # reset my containers $read=$exc=undef; if ($DEBUG_SELECT) { grep($log->do('debug',"select set: ".fileno($_)), $s->handles()); } # handle children dying while (($pid = waitpid(-1,&WNOHANG))>0) { &Respawn('',$pid); } if ($s->count()<1) { $log->do('warning',"Whoa! Nothing left in the select array -- restarting SNPP!"); RestartSNPP(); } $log->do('debug',"(select starting)") if ($DEBUG_SELECT); $match=0; $!=0; my @events = IO::Select->select($s,undef,$s,1.0); if (scalar(@events)==0 && $! != 0) { if ($! == &EINTR()) { $match=1; $log->do('debug',"select loop: %s -- continuing",$!) if ($DEBUG_SELECT); } else { $log->do('warning',"select loop failed: %s",$!); } } $log->do('debug',"(select finished)") if ($DEBUG_SELECT); ($read,undef,$exc)=@events; foreach $fh (@$read) { # Handle a new SNPP connection if ($fh == $server) { my $pid; my $sock = $fh->accept; $match=1; if (!defined($sock)) { $log->do('err',"SNPP accept: %s",$!); next; } $log->do('debug',"got connection from %s", $sock->peerhost) if ($DEBUG_SNPP); if (!IPAllowed($sock)) { close($sock); next; } $pipe = new IO::Pipe; $sigset=&BlockSignals(); if(($pid = fork())>0) { # Parent # close other side of pipe $pipe->reader(); # close our forked socket close($sock); $log->do('debug',"(select: adding pipe ".fileno($pipe).")") if ($DEBUG_SELECT); $s->add($pipe); PipeRemember($pipe,$pid); } elsif($pid==0) { # Child # close other side of pipe $pipe->writer(); $pipe->autoflush(1); # close master socket close $fh; # FIXME: close ALL snpp listeners # set up identity $PC="SNPP client"; $0="sendpage: SNPP client: ". $sock->peerhost; &DropPrivs(); &SignalInit(); $log->do('debug',"I am child: $$ for SNPP") if ($DEBUG); &CheckFDs(); # we will unblock signals in # the snpp handler $sock->HandleSNPP( "SNPP Sendpage $VERSION_human", $pipe, $config, $log, $DEBUG_SNPP, $sigset); $log->do('debug', "leaving SNPP client cnxn") if ($DEBUG_SNPP); exit(0); } else { $log->do('err',"SNPP fork: %s",$!); # error on fork close($sock); } &UnblockSignals($sigset); } # Handle SNPP client notifications elsif (defined($pid=$CNXNS{$fh})) { $match=1; # is this pipe shutdown? if ($fh->eof()) { PipeShutdownPipe($fh); $log->do('debug',"(select: removing pipe ".fileno($fh).")") if ($DEBUG_SELECT); $s->remove($fh); close($fh); } # something is readable from a pipe else { chomp(my $pc=<$fh>); SendSignal('USR1',$pc) if ($pc ne ""); } } else { $match=1; # toss any straggling pipes $log->do('debug',"(select: removing stale selectable file handle: ".fileno($fh).")") if ($DEBUG_SELECT); $s->remove($fh); close($fh); } } # Deal with exceptions foreach $fh (@$exc) { if ($fh == $server) { $match=1; $log->do('err',"Whoa! Server socket took a hit! -- reopening it"); RestartSNPP(); } else { $match=1; $log->do('warning',"SNPP connection took a hit!"); $log->do('debug',"(select: removing server ".fileno($fh).")") if ($DEBUG_SELECT); $s->remove($fh); close($fh); } } # if ($match==0) { # $log->do('warning',"Select produced an unmatched file descriptor?!"); # } } $log->do('info',"stopping Queue Manager and SNPP listeners (sendpage v$VERSION_human)"); StopSNPP(); YankPidFile(""); exit(0); } sub PipeShutdownPipe { my $pipe = shift; delete $CNXNS{$pipe}; } sub PipeShutdownPid { my $pid = shift; delete $PIPES{$pid}; } sub PipeRemember { my($pipe,$pid)=@_; $PIPES{$pid}=$pipe; $CNXNS{$pipe}=$pid; } sub StartQueue { my($name,$sigset)=@_; # Queue-runner variables my($rundelay,$pc,$waiting); $rundelay=$config->get("pc:${name}\@rundelay"); #warn "run delay: $rundelay\n"; $PC=$name; $pc=Sendpage::PagingCentral->new($config,$PC,\@modems); # rename myself $0="sendpage: $PC queue"; # set up handler $SIG{'USR1'} = \&QueueRun; $log->do('debug', "starting queue runs for '$PC'") if ($DEBUG); my $dir=$config->get("queuedir")."/$PC"; if (! -d $dir) { if (!mkdir($dir,0700)) { $log->do('alert',"Cannot mkdir '$dir': $!"); exit(1); } } $queue=Sendpage::PageQueue->new($config, $dir); if (!defined($queue)) { $log->do('alert',"Failed to open queue for '$PC' -- exiting"); exit(1); } # stop blocking signals &UnblockSignals($sigset); while ($SHUTDOWN!=1) { # reset our sleep time $sleeptime=$rundelay; # search queue, gathering pages $waiting=$queue->ready()+1; if ($waiting>0) { while (defined($page=$queue->getPage())) { if ($page->deliverable()) { $pc->deliver($page); if ($page->has_recips()) { $log->do('debug',"$PC: rewriting page to queue") if ($DEBUG); # something requires rerun $queue->writePage($page); $queue->fileDone(); } else { $log->do('debug',"$PC: tossing queue file") if ($DEBUG); $queue->fileToss(); } } else { $queue->fileDone(); } } } # don't hang up if need to rescan our queue if ($sleeptime != 0) { $pc->disconnect(); # strange eval needed to wake up on USR1 signals eval { local $SIG{USR1} = sub { die "usr1\n" }; # pause for the next queue run sleep($sleeptime); }; if ($@) { QueueRun(); } else { # nothing: we're done sleeping } } # check and see if we should shutdown (parent is init) if (!$SHUTDOWN && getppid==1) { $log->do('err',"Parent process died -- aborting"); $SHUTDOWN=1; } } $log->do('debug', "Queue runner for '$PC' shutting down") if ($DEBUG); # remove our pid file YankPidFile($PC); exit(0); } sub DoNothing { # no code here, but just have HAVING a signal handler, I'll wake up # during a SIGCHLD for my waitpid } sub Reload { my($pid); $log->do('info', "initiating reload ..."); if ($PC eq "") { my %OLD=%CHILDREN; undef %CHILDREN; undef %STARTED; StopSNPP(); $RELOAD=1; my $finished=0; # shutdown the PCs foreach $pc (@pcs) { if ($config->get("pc:$pc\@enabled")==0) { next; } $log->do('debug', "Stopping '$pc' ...") if ($DEBUG); SendSignal('QUIT',$pc); $finished++; } # shut down all the SNPP clients too foreach $pc (keys %PIPES) { $log->do('debug',"Stopping SNPP client '$pc' ...") if ($DEBUG); SendSignal('QUIT',$pc); $finished++; } # Note: # can't do "ModemInit" until everyone is dead because we # need to re-init (to validate) all the modems. my @keys=keys %OLD; undef $!; $log->do('debug', "Waiting for $finished PCs/SNPPs to die off...") if ($DEBUG); while ($finished!=0) { $pid=wait; if ($pid==-1) { if ($!==ECHILD) { $log->do('warning',"Ran out of children too early?! Continuing anyway..."); $finished=0; } else { $log->do('warning',"Oops: waitpid spat totally unexpected error: %s",$!); } } elsif ($pid==0) { $log->do('warning',"Got 0 pid somehow"); } elsif (defined($OLD{$pid})) { $log->do('debug', "Letting old PC '$OLD{$pid}' rest in peace") if ($DEBUG); $finished--; } elsif (defined($PIPES{$pid})) { PipeShutdownPid($pid); $log->do('debug',"SNPP connection (pid $pid) finished") if ($DEBUG || $DEBUG_SNPP); $finished--; } else { $log->do('warning',"Strange, I got an unknown child PID: '$pid'"); } } $log->do('debug', "Reinitializing...") if ($DEBUG); $RELOAD=0; # reload our configurations Initialize(); ModemInit(); # Restart logging $log->reconfig(Syslog => $config->get("syslog"), Opts => $config->get("syslog-opt"), Facility => $config->get("syslog-facility"), MinLevel => $config->get("syslog-minlevel"), ); $log->on(); # Start up all our children SpawnChildren(); StartSNPP(); } else { $log->do('warning',"Weird: PC '$PC' caught a Reload signal somehow."); } } sub verifyModems { my (@totest) = @_; # find all our valid modems, keeping those that are either in use (e.g. # we have been HUPd) or respond to initialization my $m; my $result; my $modem; my @okay; foreach $modem (@totest) { $m=Sendpage::Modem->new(Name => $modem, Dev => $config->get("modem:${modem}\@dev"), Lockprefix => $config->get("lockprefix"), Debug => $config->get("modem:${modem}\@debug"), Log => $log, Baud => $config->get("modem:${modem}\@baud"), Parity => $config->get("modem:${modem}\@parity"), StrictParity => $config->get("modem:${modem}\@strict-parity"), Data => $config->get("modem:${modem}\@data"), Stop => $config->get("modem:${modem}\@stop"), Flow => $config->get("modem:${modem}\@flow"), Init => $config->get("modem:${modem}\@init"), InitOK => $config->get("modem:${modem}\@initok"), InitWait => $config->get("modem:${modem}\@initwait"), InitRetry => $config->get("modem:${modem}\@initretries"), Error => $config->get("modem:${modem}\@error"), Dial => $config->get("modem:${modem}\@dial"), DialOK => $config->get("modem:${modem}\@dialok"), DialWait => $config->get("modem:${modem}\@dialwait"), DialRetry => $config->get("modem:${modem}\@dialretries"), NoCarrier => $config->get("modem:${modem}\@no-carrier"), DTRToggleTime => $config->get("modem:${modem}\@dtrtime"), CarrierDetect => $config->get("modem:${modem}\@carrier-detect",1), AreaCode => $config->get("modem:${modem}\@areacode",1), LongDist => $config->get("modem:${modem}\@longdist"), DialOut => $config->get("modem:${modem}\@dialout") ); if (!defined($m)) { $log->do('warning',"Cannot find modem '$modem'"); next; } if (!defined($result=$m->init())) { $log->do('alert',"Cannot initialize modem '$modem'"); next; } undef $m; push(@okay,$modem); } return @okay; } sub RecipDig { my($recip,$seen)=@_; my($dests,$one,%hash,$result); if (!defined($seen)) { my %holder; $holder{$recip->name()}=1; $seen=\%holder; } else { $seen->{$recip->name()}++; } if ($seen->{$recip->name()}>1) { $log->do('alert',"Loop found in alias expansion! Culprit recip: '%s'", $recip->name()); exit(1); } # no alias, just return this one (leaf node) return ($recip) if (!$recip->alias()); $log->do('debug',"from: '%s'",$recip->name()) if ($config->get("alias-debug")); # get expanded list $dests=$recip->dests(); # dump list grep($log->do('debug',"starting with: '%s'",$_),@{$dests}) if ($config->get("alias-debug")); # expand each one foreach $one (@{ $dests }) { $log->do('debug',"expanding: '%s'",$one) if ($config->get("alias-debug")); my %copy=%{$seen}; my $r=Sendpage::Recipient->new($config,$db,$one,$recip->data()); if (!defined($r)) { $log->do('err',"undeliverable: '%s'",$one); } else { my @results=RecipDig($r,\%copy); # add them to our hash foreach $result (@results) { $log->do('debug',"got: '%s'",$result->name()) if ($config->get("alias-debug")); $hash{$result->name()}=$result; } } } undef @results; foreach $one (keys %hash) { $log->do('debug',"passing back: '%s'",$hash{$one}->name()) if ($config->get("alias-debug")); push(@results,$hash{$one}); } return @results; } sub ArrayDig { my(@array)=@_; my ($one,$result,%hash,@results,$fail); # did a look-up fail? $fail=0; # dump list grep($log->do('debug',"starting with: '%s'",$_),@array) if ($config->get("alias-debug")); # expand each one foreach $one (@array) { $log->do('debug',"expanding: '%s'",$one) if ($config->get("alias-debug")); my $recip=Sendpage::Recipient->new($config,$db,$one); if (!defined($recip)) { $log->do('err',"undeliverable: '%s'",$one); $fail=1; } else { my @results=RecipDig($recip); # add them to our hash foreach $result (@results) { $log->do('debug',"got: '%s'",$result->name()) if ($config->get("alias-debug")); $hash{$result->name()}=$result; } } } undef @results; foreach $one (keys %hash) { $log->do('debug',"passing back: '%s'",$hash{$one}->name()) if ($config->get("alias-debug")); push(@results,$hash{$one}); } return ($fail,@results); } sub initConfig { my(%opts) = %{ $_[0] }; # set up default values (this is ignored by KeesConf...) my %cfg=( PEDANTIC=> 1, CASE => 1, CREATE => 1, GLOBAL => { DEFAULT => "", ARGCOUNT => ARGCOUNT_ONE, }, ); my $config = Sendpage::KeesConf->new(\%cfg); # global variables $config->define("dsn", { DEFAULT => "" }); $config->define("dbuser", { DEFAULT => "" }); $config->define("dbpass", { DEFAULT => "" }); $config->define("dbtable", { DEFAULT => "" }); $config->define("cfgfile", { DEFAULT => "/etc/sendpage.cf" }); $config->define("pidfileprefix",{ DEFAULT => "/var/spool/sendpage/sendpage" }); $config->define("lockprefix",{ DEFAULT => "/var/lock/LCK.." }); $config->define("queuedir", { DEFAULT => "/var/spool/sendpage" }); $config->define("mail-agent", { DEFAULT => "sendmail" }); $config->define("user", { DEFAULT => "sendpage" }); $config->define("group-lock", { DEFAULT => "uucp" }); $config->define("group-tty", { DEFAULT => "tty" }); $config->define("page-daemon", { DEFAULT => "sendpage" }); $config->define("cc-on-error", { ARGCOUNT => 0, DEFAULT => 1 }); $config->define("modems", { ARGCOUNT => 2 }); $config->define("alias-debug", { ARGCOUNT => 0, DEFAULT => 0 }); $config->define("debug", { ARGCOUNT => 0, DEFAULT => $opts{d} ? 1 : 0 }); $config->define("debug-select", { ARGCOUNT => 0, DEFAULT => $opts{d} ? 1 : 0 }); $config->define("debug-snpp", { ARGCOUNT => 0, DEFAULT => $opts{d} ? 1 : 0 }); # should the sender be notified of failures? $config->define("fail-notify", { ARGCOUNT => 0, DEFAULT => 1 }); # sender should be notified how on every X temp fails? (0=never) $config->define("tempfail-notify-after", { DEFAULT => 5 }); # how many temp fails does it take to produce a perm failure? $config->define("max-tempfail", { DEFAULT => 20 }); # default email CC domain $config->define("fallback-email-domain", { DEFAULT => undef }); # command to run after each successful or failed page $config->define("completion-cmd", { UNSET => 1 }); # syslog toggle: strerr is used if not syslog $config->define("syslog", { DEFAULT => 1, ARGCOUNT => 0 }); # syslog logopt words (any of "pid", "ndelay", "cons", "nowait") $config->define("syslog-opt", { DEFAULT => "pid" }); # syslog facility to log with (one of "auth", "authpriv", "cron", "daemon", # "kern", "local0" through "local7", "lpr", "mail", "news", "syslog", # "user", or "uucp" $config->define("syslog-facility", { DEFAULT => "daemon" }); # syslog reports internally range from debug through crit. Most people # figure out how to configure sendpage to report debug, but don't change # their syslog configs. So, we'll have a "minlevel". Anything below # "minlevel" will be elevated to "minlevel" before being sent to syslog. $config->define("syslog-minlevel", { DEFAULT => "info" }); # SNPP settings $config->define("snpp-port", { DEFAULT => "444" }); $config->define("snpp-addr", { DEFAULT => "localhost" }); $config->define("snpp-acl", { ARGCOUNT => 2, DEFAULT => [ "127.0.0.1/255.255.255.255:ALLOW" ] }); # aliases defaults #$config->define("recip:", { ARGCOUNT => 2 }); # where to send email-cc's of pages. none if blank, defaults to # ALIAS @ fallback-email-domain if unset $config->define("recip:email-cc", { UNSET => 1 }); # page is PIN@PC, alias is just the recip name again $config->define("recip:dest", { ARGCOUNT => 2 }); # modem defaults $config->define("modem:debug", { ARGCOUNT => 0, DEFAULT => $opts{d} ? 1 : 0 }); $config->define("modem:baud", { DEFAULT => 9600 }); $config->define("modem:data", { DEFAULT => 7 }); $config->define("modem:parity", { DEFAULT => "even" }); $config->define("modem:stop", { DEFAULT => 1 }); $config->define("modem:flow", { DEFAULT => "rts" }); $config->define("modem:strict-parity", { ARGCOUNT => 0, DEFAULT => 0 }); $config->define("modem:dev", { DEFAULT => "/dev/null" }); $config->define("modem:carrier-detect", { DEFAULT => "on" }); # time to force DTR down during reset (0 = don't do it at all) $config->define("modem:dtrtime", { DEFAULT => 0.5 }); $config->define("modem:init", { DEFAULT => "ATZ" }); $config->define("modem:initok", { DEFAULT => "OK" }); $config->define("modem:initwait",{ DEFAULT => 4 }); $config->define("modem:initretries",{ DEFAULT => 2 }); $config->define("modem:dial", { DEFAULT => "ATDT" }); $config->define("modem:dialok", { DEFAULT => "CONNECT.*\r" }); $config->define("modem:dialwait",{ DEFAULT => 60 }); $config->define("modem:error", { DEFAULT => "ERROR" }); $config->define("modem:no-carrier", { DEFAULT => "ERROR|NO CARRIER|BUSY|NO DIAL|VOICE" }); $config->define("modem:areacode", { UNSET => 1 }); $config->define("modem:longdist", { DEFAULT => 1 }); $config->define("modem:dialout", { DEFAULT => "" }); # FIXME: # currently not implemented -- perhaps never, better to stall and try again $config->define("modem:dialretries",{ DEFAULT => 3 }); # paging central defaults # Paging centrals can override baud, data, parity, stop, flow, dialwait, # dialretries # modem connect info $config->define("pc:enabled",{ ARGCOUNT => 0, DEFAULT => 1 }); $config->define("pc:debug", { ARGCOUNT => 0, DEFAULT => $opts{d} ? 1 : 0 }); $config->define("pc:page-daemon", { UNSET => 1 }); $config->define("pc:cc-on-error", { ARGCOUNT => 0, UNSET => 1 }); $config->define("pc:fail-notify", { ARGCOUNT => 0, UNSET => 1 }); $config->define("pc:tempfail-notify-after", { UNSET => 1 }); $config->define("pc:max-tempfail", { UNSET => 1 }); # command to run after each successful or failed page $config->define("pc:completion-cmd", { UNSET => 1 }); $config->define("pc:modems", { ARGCOUNT => 2 }); $config->define("pc:baud", { DEFAULT => 9600 }); $config->define("pc:data", { DEFAULT => 7 }); $config->define("pc:parity", { DEFAULT => "even" }); $config->define("pc:stop", { DEFAULT => 1 }); $config->define("pc:flow", { DEFAULT => "rts" }); $config->define("pc:strict-parity", { ARGCOUNT => 0, DEFAULT => 0 }); $config->define("pc:phonenum",{DEFAULT => "" }); $config->define("pc:areacode",{ UNSET => 1 }); # how many chars per page before auto-splitting? $config->define("pc:maxchars",{DEFAULT => 1024 }); # how many page splits allowed per page? $config->define("pc:maxsplits",{DEFAULT => 6 }); # PC uses it's own dialwait for delaying $config->define("pc:dialwait",{ UNSET => 1 }); $config->define("pc:rundelay",{DEFAULT => 30 }); $config->define("pc:dialretries",{ DEFAULT => 3 }); # allow for redefining the proto version $config->define("pc:proto",{DEFAULT => "PG1" }); # allow for forced multiple fields in BlockTrans $config->define("pc:fields",{DEFAULT => 2 }); $config->define("pc:password",{DEFAULT => "000000" }); # proto establishment info $config->define("pc:answerwait", { DEFAULT => 2 }); $config->define("pc:answerretries", { DEFAULT => 3 }); # protocol settings # MUST have the leading "" for each answer? $config->define("pc:stricttap", { DEFAULT => 0, ARGCOUNT => 0 }); # chars less than 0x20 are allowed in a field $config->define("pc:ctrl", { DEFAULT => 0, ARGCOUNT => 0 }); # chars CAN be escaped (if false, "LF" is allowed, it seems?) $config->define("pc:esc", { DEFAULT => 0, ARGCOUNT => 0 }); # is LF allowed (some PCs allow it, but no other ctrl chars) $config->define("pc:lfok", { DEFAULT => 0, ARGCOUNT => 0 }); # fields cannot be split across blocks? (FIXME: unimplemented) $config->define("pc:fieldsplits", { DEFAULT => 1, ARGCOUNT => 0 }); # paging central limits # max blocks per connection (0 = unlimited) $config->define("pc:maxblocks", { DEFAULT => 0 }); # max pages per connection (0 = unlimited) $config->define("pc:maxpages", { DEFAULT => 0 }); # max chars per block: 250 is protocol standard: 256 - 3 ctrl - 3 chksum $config->define("pc:chars-per-block", { DEFAULT => 250 }); return $config; } sub loadConfig { my($cfgfile); $cfgfile=$config->get("cfgfile"); $cfgfile=$opts{C} if (defined($opts{C})); # toss our config $config->dump(); # yes, this seems silly, but we allow cmdline options to change # various defaults, including this one $config->file($cfgfile); if($config->get("dsn")) { my ($dsn, $dbuser, $dbpass, $dbtable); $dsn = $config->get("dsn"); $dbuser = $config->get("dbuser"); $dbpass = $config->get("dbpass"); $dbtable = $config->get("dbtable"); if($db) { $db->setdb($dsn,$dbuser,$dbpass,$dbtable); } else { $db = Sendpage::Db->new($dsn,$dbuser,$dbpass,$dbtable); } } } # # file locking example from the Perl Cookbook # # use Fcntl qw(:DEFAULT :flock); # # sysopen(FH, "numfile", O_RDWR|O_CREAT) # or die "can't open numfile: $!"; # flock(FH, LOCK_EX) or die "can't write-lock numfile: $!"; # # Now we have acquired the lock, it's safe for I/O # $num = || 0; # DO NOT USE "or" THERE!! # seek(FH, 0, 0) or die "can't rewind numfile : $!"; # truncate(FH, 0) or die "can't truncate numfile: $!"; # print FH $num+1, "\n" or die "can't write numfile: $!"; # close(FH) or die "can't close numfile: $!"; # # sendpage-1.000003/docs/0000755000076500007650000000000010737242241013127 5ustar keeskeessendpage-1.000003/docs/sendpage.php0000644000076500007650000000467010737241677015451 0ustar keeskees Send Page

Send Page


    >8)&0xFF; $snpp=($ret == 0); } if (!$snpp) { ?>Sorry, an error occured sending your page! Page sent:
    To: 
    
    From: 
    
    Text:
    
    

Please fill out the following information to send a page....

>To:
>From:
>Text:


sendpage-1.000003/docs/PagingCentrals.txt0000644000076500007650000030707010737241677016614 0ustar keeskees# # $Id: PagingCentrals.txt 316 2008-01-03 20:21:19Z keescook $ # # I realize one of the biggest problems with sending pages directly is the # lack of vendor-advertised paging central phone numbers. So, I figure we # should try to collect a list of them. Should make things a lot easier. # # You can find this online at http://sendpage.org/pc.php (searchable) # or as http://sendpage.org/PagingCentrals.txt in raw text. # # To make changes, corrections, etc, please send me some email at # pc-updates@outflux.net # # Generated from http://sendpage.org/pc.php # # SELECT PC.id, V.descriptor vendor, R.descriptor region, PC.phone, # F.descriptor format, S.descriptor speed, PC.quirks, PC.notes # FROM PagingCentral PC # LEFT JOIN Vendor V ON PC.vendor _id=V.id # LEFT JOIN Region R ON PC.region_id=R.id # LEFT JOIN Format F ON PC.format _id=F.id # LEFT JOIN Speed S ON PC.speed_id=S.id; # +-----+--------------------------+-------------------------------+---------------------+------------+------------+----------------------------------------------------------+----------------------------------------------+ | id | vendor | region | phone | format | speed | quirks | notes | +-----+--------------------------+-------------------------------+---------------------+------------+------------+----------------------------------------------------------+----------------------------------------------+ | 1 | Skytel | USA, Nationwide | 800-759-6366 | 7e1 | max | ctrl=false | | | 2 | Skytel | USA, Nationwide | 800-933-6384 | 7e1 | max | | untested | | 3 | Skytel | USA, Nationwide | 800-679-2778 | 7e1 | max | | untested | | 4 | AT&T Paging | USA, Nationwide | 800-724-7784 | 7e1 | max | | untested | | 5 | Ameritech | USA, IL, Chicago | 312-514-9243 | 7e1 | max | shorttap=true | | | 6 | AirTouch | USA, CA, San Diego | 858-576-1783 | 7e1 | max | | untested | | 7 | AT&T Paging | USA, CA, San Diego | 619-893-0099 | 7e1 | max | | untested | | 8 | Beeper Communications | Israel | 972-3-6100081 | 7e1 | max | | untested | | 9 | British Telecom | UK | +44 (0)345 581 354 | 7e1 | max | | untested | | 10 | Cellcom | Israel | 972-53-446-622 | 7e1 | max | | untested | | 11 | Cellcom | Israel | 972-53-446-633 | 7e1 | max | | untested | | 12 | Hutchison Telecom | UK | +44 (0)941 100400 | 7e1 | max | | untested | | 13 | Hutchison Telecom | UK | +44 (0)973 100602 | 7e1 | max | | untested,interactive | | 14 | ISDN | Germany | 0 16 90 | 7e1 | max | | untested | | 15 | Telstra | Australia | 018018767 | 7e1 | max | | untested | | 16 | PageOne Communications | UK | +44 1523 530 300 | 7e1 | max | | untested | | 17 | Telecom | New Zealand | 026-4001283 | 7e1 | max | | Pagers,untested | | 18 | Telecomi | New Zealand | 04-3844476 | 7e1 | max | | Digital Phones,untested | | 19 | A Better Beep | USA, CA, Bakersfield | 805-334-7002 | 7e1 | max | | untested | | 20 | A Better Beep | USA, CA, Fresno | 209-778-9451 | 7e1 | max | | untested | | 21 | A Better Beep | USA, CA, San Luis Obispo | 805-542-4050 | 7e1 | max | | untested | | 22 | A Better Beep | USA, CA, Santa Barbara | 805-730-3118 | 7e1 | max | | untested | | 23 | A Better Beep | USA, CA, Santa Rosa | 707-523-6571 | 7e1 | max | | untested | | 24 | A Better Beep | USA, CA, Stockton | 209-762-3094 | 7e1 | max | | untested | | 25 | A-Plus Communications | USA, AL | 800-921-6625 | 7e1 | max | | untested | | 26 | AT&T Alphanumeric | USA, TX, Dallas | 972-934-2797 | 7e1 | max | | untested | | 28 | AT&T PCS Text Messaging | USA, Nationwide | 800-841-8837 | 7e1 | max | | Cellphones,untested | | 29 | AT&T Paging | Unknown | 801-596-9364 | 7e1 | max | | untested | | 30 | AT&T Wireless | Unknown | 800-368-4133 | 7e1 | max | | untested | | 31 | AT&T Wireless | Unknown | 888-299-9550 | 7e1 | max | | untested | | 32 | AT&T Wireless | USA, WA, Seattle | 206-622-7585 | 7e1 | max | | untested | | 33 | AT&T Wireless | USA, TX, Corpus Christi | 361-857-8231 | 7e1 | max | | untested | | 34 | Advanced Paging | USA, LA, New Orleans | 888-723-2337 | 7e1 | max | | untested | | 35 | AirTouch | Unknown | 800-326-0038 | 7e1 | max | | untested | | 36 | AirTouch | USA, CA | 888-287-7108 | 7e1 | max | | CDMA cells,untested | | 37 | AirTouch | USA, NM, Albuquerque | 505-883-1977 | 7e1 | max | | untested | | 38 | AirTouch | USA, GA, Atlanta | 404-873-6337 | 7e1 | max | | untested | | 39 | AirTouch | USA, TX, Austin | 512-873-8719 | 7e1 | max | | untested | | 40 | AirTouch | USA, ID, Boise | 208-869-0004 | 7e1 | max | | untested | | 41 | AirTouch | USA, TX, Dallas/Fort Worth | 817-265-1848 | 7e1 | max | | untested | | 42 | AirTouch | USA, IN, Indianapolis | 317-553-2471 | 7e1 | max | | untested | | 43 | AirTouch | USA, KS, Kansas City | 800-546-7243 | 7e1 | max | | untested | | 44 | AirTouch | USA, MI | 800-371-8800 | 7e1 | max | | untested | | 45 | AirTouch | USA, MI | 810-539-3917 | 7e1 | max | | untested | | 46 | AirTouch | USA, Nationwide | 800-326-4724 | 7e1 | max | | untested | | 47 | AirTouch | USA, Nationwide | 800-793-4549 | 7e1 | max | | untested | | 48 | AirTouch | USA, Nationwide | 800-347-7243 | 7e1 | max | | untested | | 49 | AirTouch | USA, FL, Orlando | 407-645-3103 | 7e1 | max | | untested | | 50 | AirTouch | USA, AZ, Phoenix | 602-253-4297 | 7e1 | max | | untested | | 51 | AirTouch | USA, CA, Sacramento | 916-447-6053 | 7e1 | max | | untested | | 52 | AirTouch | USA, CA, San Deigo | 619-296-0772 | 7e1 | max | | untested | | 54 | AirTouch | USA, CA, Sunnyvale | 408-950-0572 | 7e1 | max | | untested | | 55 | AirTouch | USA, TX | 800-672-4371 | 7e1 | max | | untested | | 56 | AirTouch | USA, AZ, Tucson | 520-622-3122 | 7e1 | max | | untested | | 57 | Alert | Unknown | 702-362-1748 | 7e1 | max | | untested | | 58 | American Pager | Unknown | 210-657-5562 | 7e1 | max | | untested | | 59 | American Pager | Unknown | 800-660-5747 | 7e1 | max | | untested | | 60 | American Paging | Unknown | 512-451-0777 | 7e1 | max | | untested | | 61 | American Paging | USA, AZ | 602-265-5203 | 7e1 | max | | untested | | 62 | American Paging | USA, AZ | 800-364-0816 | 7e1 | max | | untested | | 63 | American Paging | USA, FL, Orlando | 800-233-9977 | 7e1 | max | | untested | | 64 | American Paging | USA, Washington DC | 800-459-2033 | 7e1 | max | | untested | | 65 | American Paging | USA, WI | 800-642-4152 | 7e1 | max | | untested | | 67 | Ameritech | Unknown | 616-201-2000 | 7e1 | max | | untested | | 68 | Ameritech | USA, OH, Cleveland | 216-670-7243 | 7e1 | max | | untested | | 69 | Ameritech | USA, WI, Madison | 800-677-7454 | 7e1 | max | | untested | | 70 | Ameritech | USA, IL | 708-563-0024 | 7e1 | max | | untested | | 71 | Ameritech | USA, MO, St. Louis | 314-973-9739 | 7e1 | 300 | | untested | | 72 | Ameritech | USA, MO, St. Louis | 314-841-8410 | 7e1 | 1200 | | untested | | 73 | Answer Page | USA, OR, Grants Pass | 541-471-5000 | 7e1 | max | | untested | | 74 | Answer Page | USA, OR, Medford | 541-770-0000 | 7e1 | max | | untested | | 75 | Arch Communications | USA, PA, Erie | 814-864-1735 | 7e1 | max | | untested | | 76 | Arch Communications | USA, PA, Erie | 814-868-5576 | 7e1 | max | | untested | | 77 | Arch Communications | USA, PA, Erie | 800-800-2995 | 7e1 | max | | untested | | 79 | Arch Paging | USA, Nationwide | 800-741-2337 | 7e1 | max | | untested | | 80 | Arch Paging | USA, OH, Cleveland | 216-464-3185 | 7e1 | max | | untested | | 81 | Arch Paging | USA, OH, Cleveland | 216-591-0925 | 7e1 | max | | untested | | 82 | Arch Paging | USA, CO, Colorado Springs | 800-490-1320 | 7e1 | max | | untested | | 83 | Arch Paging | USA, SC, Columbia | 888-377-4231 | 7e1 | max | | untested | | 84 | Arch Paging | USA, OR, Eugene | 541-342-5214 | 7e1 | max | | untested | | 85 | Arch Paging | USA, NC, Greensboro | 888-377-4229 | 7e1 | max | | untested | | 86 | Arch Paging | USA, SC, Greenville | 888-377-4230 | 7e1 | max | | untested | | 87 | Arch Paging | USA, MI | 800-613-1108 | 7e1 | max | | untested | | 88 | Arch Paging | USA, MI | 800-613-1107 | 7e1 | max | | untested | | 89 | Arch Paging | USA, MI | 800-613-5712 | 7e1 | max | | untested | | 90 | Arch Paging | USA, OR, Portland | 503-228-7660 | 7e1 | max | | untested | | 91 | Arch Paging | USA, NC, Raliegh | 888-377-4228 | 7e1 | max | | untested | | 92 | Arch Paging | USA, NY, Rochester | 716-232-7324 | 7e1 | max | | untested | | 93 | Arch Paging | USA, WA, Seattle | 800-695-6285 | 7e1 | max | | untested | | 94 | Arch Paging | USA, MO, St. Louis | 800-294-9988 | 7e1 | max | | untested | | 95 | Arch Paging | USA, MO, St. Louis Metro | 618-549-4685 | 7e1 | max | | untested | | 96 | Arch Paging | USA, MA, Western Mass. area | 800-648-8233 | 7e1 | max | | untested | | 97 | Beep Call | Unknown | 888-624-6703 | 7e1 | max | | untested | | 472 | PageUSA | USA, CA, Sacramento | 0352402 | 7e1 | max | 1200 | | | 475 | Cingular | Unknown | | 208-9875 | max | | | | 100 | Bell Mobility | Canada, Toronto | 416-285-7267 | 7e1 | max | | untested | | 101 | Bell Mobility | Canada, Toronto | 514-931-0441 | 7e1 | max | | untested | | 102 | Beta Tele-Page | Unknown | 800-409-6979 | 7e1 | max | | untested | | 103 | Cantel | Canada, Alberta, Edmonton | 403-990-1042 | 7e1 | max | | untested | | 104 | Cantel | Canada, Alberta, Edmonton | 800-387-8484 | 7e1 | max | | untested | | 105 | Cantel | USA, QC, Montreal | 514-862-0630 | 7e1 | max | | untested | | 106 | Cantel | USA, ON, Ottawa | 613-820-8631 | 7e1 | max | | untested | | 108 | Cantel | Canada, Toronto | 416-488-9494 | 7e1 | max | | untested | | 109 | Cantel | USA, MB, Winnipeg | 819-572-1847 | 7e1 | max | | untested | | 111 | Cellular One PCS | USA, Nationwide | 800-477-4550 | 7e1 | max | | untested | | 112 | Cellular One PCS | USA, Nationwide | 888-878-7727 | 7e1 | max | | untested | | 113 | Cellular One | USA, KS, Kansas City | 913-236-4065 | 7e1 | max | | untested | | 114 | ComTech | Unknown | 800-755-4585 | 7e1 | max | | untested | | 115 | Consolidated Comm | Unknown | 800-533-3843 | 7e1 | max | | untested | | 116 | Cook Paging | Unknown | 206-469-0000 | 7e1 | max | | untested | | 117 | Cook Paging | Unknown | 209-279-0000 | 7e1 | max | | untested | | 118 | Cook Paging | Unknown | 415-679-0000 | 7e1 | max | | untested | | 119 | Fido | Canada, Quebec, Montreal | 514-937-9593 | 7e1 | max | | untested | | 120 | Fido | Canada | 888-322-3436 | 7e1 | max | | untested | | 121 | GTE | Unknown | 800-483-2340 | 7e1 | max | | untested | | 122 | LDDS Worldcom | Unknown | 800-940-1010 | 7e1 | max | | untested | | 123 | Lehigh Valley Page | Unknown | 800-873-2433 | 7e1 | max | | untested | | 124 | LA Cellular | USA, CA, Los Angeles | 800-807-7274 | 7e1 | max | | untested | | 125 | MAP | USA, CA, Southern | 800-456-2190 | 7e1 | max | | untested | | 126 | MCC Paging | Unknown | 800-529-8663 | 7e1 | max | | untested | | 127 | MCI Page | Unknown | 800-555-0909 | 7e1 | max | | untested | | 128 | MCI Page | Unknown | 800-487-9297 | 7e1 | max | | untested | | 130 | Maximum | Unknown | 800-589-3860 | 7e1 | max | | untested | | 131 | Metrocall | Unknown | 800-926-7272 | 7e1 | max | | untested | | 132 | Metrocall | Unknown | 917-895-5376 | 7e1 | max | | untested | | 133 | Metrocall | USA, VA | 703-768-5400 | 7e1 | max | | 2-way,untested | | 134 | Metrocall | USA, CA | 213-941-0000 | 7e1 | max | | 2-way,untested | | 135 | Metrocall | USA, Nationwide | 800-418-8283 | 7e1 | max | | LA Term 324,untested | | 136 | Metrocall | USA, Nationwide | 619-331-4950 | 7e1 | max | | Term 327,untested | | 137 | Metrocall | USA, VA, Alexandria | 800-245-0004 | 7e1 | max | | untested | | 138 | Metrocall | USA, MA | 617-362-6600 | 7e1 | max | | untested | | 139 | Metrocall | USA, NJ | 800-917-1168 | 7e1 | max | | untested | | 140 | Metrocall | USA, VA, Roanoke | 800-898-7247 | 7e1 | max | | untested | | 141 | Metrocall | USA, CA, San Diego | 619-331-0041 | 7e1 | max | | untested | | 142 | Metrocall | USA, CA, San Francisco | 800-971-7919 | 7e1 | max | | untested | | 145 | Metrocall | USA, OK, Tulsa | 918-664-8811 | 7e1 | max | | untested | | 146 | Metrocall | USA, Washington DC | 800-971-5088 | 7e1 | max | | untested | | 147 | Metrocall | USA, KS, Wichita | 316-636-4110 | 7e1 | max | | untested | | 148 | Minncomm Paging | USA, MN, Minneapolis | 763-521-3541 | 7e1 | max | | tested | | 149 | Mobile Media | Unknown | 800-622-5742 | 7e1 | max | | untested | | 150 | Mobile Media | USA, FL | 800-872-6755 | 7e1 | max | | untested | | 151 | Mobile Media | USA, NC | 800-365-2689 | 7e1 | max | | untested | | 153 | MobileComm | Unknown | 800-946-4644 | 7e1 | max | | untested | | 155 | MobileComm | USA, AL | 800-848-8436 | 7e1 | max | | untested | | 156 | MobileComm | USA, MA, Boston | 800-287-0143 | 7e1 | max | | untested | | 157 | MobileComm | USA, SC, Charleston | 800-277-1266 | 7e1 | max | | untested | | 158 | MobileComm | USA, IL, Chicago | 847-490-0174 | 7e1 | max | | untested | | 159 | MobileComm | USA, Washington DC | 800-328-7587 | 7e1 | max | | untested | | 160 | MobileComm | USA, MI, Detroit | 810-559-8191 | 7e1 | max | | untested | | 161 | MobileComm | USA, AL, Huntsville | 800-239-7817 | 7e1 | max | | untested | | 162 | MobileComm | USA, NY, Long Island | 800-657-2487 | 7e1 | max | | untested | | 163 | MobileComm | USA, MI | 800-837-5101 | 7e1 | max | | untested | | 164 | MobileComm | USA, CA, Northern | 800-655-6555 | 7e1 | max | | untested | | 165 | MobileComm | USA, CA, Northern | 916-444-6383 | 7e1 | max | | untested | | 166 | MobileComm | USA, VA, Richmond | 804-270-2271 | 7e1 | max | | untested | | 167 | MobileComm | USA, CA, San Luis Obispo | 805-544-1162 | 7e1 | max | | untested | | 168 | MobileComm | USA, CA, Santa Barbara | 805-564-6049 | 7e1 | max | | untested | | 169 | MobileComm | USA, CA, Santa Barbara | 805-564-8817 | 7e1 | max | | untested | | 170 | MobileComm | USA, CA, Santa Maria | 805-349-1140 | 7e1 | max | | untested | | 171 | MobileComm | USA, MO, St. Louis | 800-250-6325 | 7e1 | max | | untested | | 172 | MobileComm | USA, TX | 800-435-3582 | 7e1 | max | | untested | | 173 | MobileComm | USA, TX | 800-344-2270 | 7e1 | max | | untested | | 174 | MobileComm | USA, WA/OR | 800-734-0096 | 7e1 | max | | untested | | 175 | MobileComm BSE | USA, MI | 800-837-1202 | 7e1 | max | | untested | | 176 | MobileFone | Unknown | 800-632-5742 | 7e1 | max | | tested | | 177 | MobileFone | USA, KS, Kansas City | 816-842-6032 | 7e1 | max | | tested | | 179 | Nextel | USA, IL, Chicago | 312-514-6683 | 7e1 | max | | untested | | 180 | Nextel | Unknown | 415-559-6683 | 7e1 | max | | untested | | 181 | Nextel | Unknown | 617-839-6683 | 7e1 | max | | untested | | 182 | Nextel | USA, NY, New York | 917-299-6683 | 7e1 | max | | untested | | 183 | Nextel | USA, IN, Indianapolis | 317-710-6683 | 7e1 | max | | untested | | 184 | Nextel | USA, WA, Seattle | 206-396-6683 | 7e1 | max | | untested | | 185 | Pacific Bell PCS | Unknown | 888-600-7267 | 7e1 | max | | untested | | 186 | PacTel | Unknown | 800-662-5742 | 7e1 | max | | untested | | 188 | Page New England | USA, MA, Boston | 800-782-0034 | 7e1 | max | | untested | | 189 | Page New England | USA, CT | 800-844-7243 | 7e1 | max | | untested | | 190 | Page New England | USA, ME | 800-639-6901 | 7e1 | max | | untested | | 191 | Page New England | USA, Nationwide | 800-844-8089 | 7e1 | max | | untested | | 192 | Page New England | USA, Nationwide | 800-543-6532 | 7e1 | max | | untested | | 193 | PageAmerica | Unknown | 800-843-9187 | 7e1 | max | | untested | | 194 | PageAmerica | USA, IL | 800-637-0672 | 7e1 | max | | untested | | 195 | PageMart | Unknown | 800-864-9499 | 7e1 | max | | untested | | 196 | PageMart | Unknown | 800-324-4533 | 7e1 | max | | untested | | 197 | PageNet | Unknown | 800-353-5100 | 7e1 | max | | untested | | 198 | PageNet | Unknown | 800-766-9619 | 7e1 | max | | untested | | 199 | PageNet | Unknown | 800-854-1733 | 7e1 | max | | untested | | 200 | PageNet | USA, NY, Albany | 518-869-0590 | 7e1 | max | | untested | | 201 | PageNet | USA, NM, Albuquerque | 505-883-1203 | 7e1 | max | | untested | | 202 | PageNet | USA, CA, Anaheim | 714-540-9301 | 7e1 | max | | untested | | 203 | PageNet | USA, CA, Anaheim | 714-666-4700 | 7e1 | max | | untested | | 204 | PageNet | USA, AK, Anchorage | 907-522-8802 | 7e1 | max | | untested | | 205 | PageNet | USA, SC, Anderson | 803-964-3507 | 7e1 | max | | untested | | 206 | PageNet | USA, AZ | 800-848-7037 | 7e1 | max | | untested | | 207 | PageNet | USA, CA, Atascadero | 805-460-7045 | 7e1 | max | | untested | | 208 | PageNet | USA, GA, Atlanta | 404-390-9176 | 7e1 | max | | untested | | 209 | PageNet | USA, GA, Atlanta | 404-722-2000 | 7e1 | max | | untested | | 210 | PageNet | USA, GA, Augusta | 706-724-7133 | 7e1 | max | | untested | | 211 | PageNet | USA, TX, Austin | 512-476-2893 | 7e1 | max | | untested | | 212 | PageNet | USA, CA, Bakersfield | 805-336-4716 | 7e1 | max | | untested | | 213 | PageNet | USA, MD, Baltimore | 410-356-0613 | 7e1 | max | | untested | | 214 | PageNet | USA, CA, Barstow | 760-957-2000 | 7e1 | max | | untested | | 215 | PageNet | USA, TX, Beaumont | 409-724-7445 | 7e1 | max | | untested | | 216 | Southwestern Bell | USA, TX, Beaumont | 409-832-1696 | 208-9875 | max | | untested | | 217 | PageNet | USA, AL, Birmingham | 205-510-2020 | 7e1 | max | | untested | | 218 | PageNet | USA, AL, Birmingham | 205-770-0000 | 7e1 | max | | untested | | 219 | PageNet | USA, ID, Boise | 208-375-2792 | 7e1 | max | | untested | | 220 | PageNet | USA, MA, Boston | 617-273-4535 | 7e1 | max | | untested | | 221 | PageNet | USA, MA, Boston | 781-273-4535 | 7e1 | max | | untested | | 222 | PageNet | USA, CT, Bridgeport | 203-523-9021 | 7e1 | max | | untested | | 223 | PageNet | USA, CT, Bridgeport | 203-380-1475 | 7e1 | max | | untested | | 224 | PageNet | USA, FL, Brooksville | 352-848-2850 | 7e1 | max | | untested | | 225 | PageNet | USA, FL, Broward | 954-402-7243 | 7e1 | max | | untested | | 226 | PageNet | USA, NY, Buffalo | 716-633-2568 | 7e1 | max | | untested | | 227 | PageNet | USA, CA, Burbank E SF Valley | 818-556-0050 | 7e1 | max | | untested | | 228 | PageNet | USA, CA, Burbank | 818-846-6326 | 7e1 | max | | untested | | 229 | PageNet | USA, CA, W SF Valley | 818-596-3000 | 7e1 | max | | untested | | 230 | PageNet | USA, SC, Charleston | 803-219-0000 | 7e1 | max | | untested | | 231 | PageNet | USA, NC, Charlotte | 704-527-2574 | 7e1 | max | | untested | | 232 | PageNet | USA, IL, Chicago | 630-325-7842 | 7e1 | max | | untested | | 233 | PageNet | USA, IL, Chicago | 708-325-7842 | 7e1 | max | | untested | | 234 | PageNet | USA, OH, Cincinnati | 513-408-9999 | 7e1 | max | | untested | | 235 | PageNet | USA, OH, Cincinnati | 513-434-9694 | 7e1 | max | | untested | | 236 | PageNet | USA, OH, Cincinnati | 513-772-0941 | 7e1 | max | | untested | | 237 | PageNet | USA, OH, Cleveland | 216-573-7933 | 7e1 | max | | untested | | 238 | PageNet | USA, OH, Cleveland | 440-303-7243 | 7e1 | max | | untested | | 239 | PageNet | USA, FL, Coco Beach | 407-639-1244 | 7e1 | max | | untested | | 240 | PageNet | USA, CO, Colorado Springs | 719-570-7055 | 7e1 | max | | untested | | 241 | PageNet | USA, CA, Colton | 909-410-6600 | 7e1 | max | | untested | | 242 | PageNet | USA, SC, Columbia | 803-301-0000 | 7e1 | max | | untested | | 243 | PageNet | USA, OH, Columbus | 614-846-4515 | 7e1 | max | | untested | | 244 | PageNet | USA, CA, Compton | 310-509-0001 | 7e1 | max | | untested | | 245 | PageNet | USA, CA, Corona | 909-270-6584 | 7e1 | max | | untested | | 246 | PageNet | USA, TX, Corpus Christi | 512-844-1622 | 7e1 | max | | untested | | 247 | PageNet | USA, TX, Dallas/Ft. Worth | 214-835-7243 | 7e1 | max | | untested | | 248 | PageNet | USA, OH, Dayton | 937-370-9999 | 7e1 | max | | untested | | 249 | PageNet | USA, FL, Daytona Beach | 904-252-2731 | 7e1 | max | | untested | | 250 | PageNet | USA, CO, Denver | 303-721-7595 | 7e1 | max | | untested | | 251 | PageNet | USA, CO, Denver | 719-520-1253 | 7e1 | max | | untested | | 252 | PageNet | USA, MI, Detroit | 248-827-7399 | 7e1 | max | | untested | | 253 | PageNet | USA, MD, Eastern Shore | 410-749-0286 | 7e1 | max | | untested | | 254 | PageNet | USA, OR, Eugene | 503-484-1369 | 7e1 | max | | untested | | 255 | PageNet | USA, OR, Eugene | 541-484-1369 | 7e1 | max | | untested | | 256 | PageNet | USA, TX, Freeport | 409-233-5524 | 7e1 | max | | untested | | 257 | PageNet | USA, CA, Fresno | 209-699-0001 | 7e1 | max | | untested | | 258 | PageNet | USA, FL, Ft. Lauderdale | 305-767-8000 | 7e1 | max | | untested | | 259 | PageNet | USA, FL, Ft. Myers | 941-339-5299 | 7e1 | max | | untested | | 260 | PageNet | USA, FL, Gainesville | 352-376-8251 | 7e1 | max | | untested | | 261 | PageNet | USA, TX, Galveston | 409-942-1042 | 7e1 | max | | untested | | 262 | PageNet | USA, CA, Gardena | 310-298-2995 | 7e1 | max | | untested | | 263 | PageNet | USA, NC, Greensboro | 336-321-3000 | 7e1 | max | | untested | | 264 | PageNet | USA, NC, Greensboro | 910-855-7599 | 7e1 | max | | untested | | 265 | PageNet | USA, SC, Greenville | 803-217-0000 | 7e1 | max | | untested | | 266 | PageNet | USA, SC, Greenville | 864-216-0000 | 7e1 | max | | untested | | 267 | PageNet | USA, SC, Greenville | 864-217-0000 | 7e1 | max | | untested | | 268 | PageNet | USA, SC, Greenville | 864-964-3500 | 7e1 | max | | untested | | 269 | PageNet | USA, CT, Hartford | 860-948-2000 | 7e1 | max | | untested | | 270 | PageNet | USA, CA, Hayward | 510-920-1100 | 7e1 | max | | untested | | 271 | PageNet | USA, CA, Hemet | 909-617-0000 | 7e1 | max | | untested | | 272 | PageNet | USA, TX, Houston | 281-480-9814 | 7e1 | max | | untested | | 273 | PageNet | USA, TX, Houston | 713-781-8152 | 7e1 | max | | untested | | 274 | PageNet | USA, TX, Houston | 713-781-8163 | 7e1 | max | | untested | | 275 | PageNet | USA, AL, Huntsville | 205-860-0000 | 7e1 | max | | untested | | 276 | PageNet | USA, IN | 800-838-6138 | 7e1 | max | | untested | | 277 | PageNet | USA, IN, Indianapolis | 317-575-6138 | 7e1 | max | | untested | | 278 | PageNet | USA, CA, Irvine | 714-451-7144 | 7e1 | max | | untested | | 279 | PageNet | USA, FL, Jacksonville | 904-363-2103 | 7e1 | max | | untested | | 280 | PageNet | USA, FL, Jacksonville | 904-519-9102 | 7e1 | max | | untested | | 281 | PageNet | USA, KS, Kansas City | 913-408-7243 | 7e1 | max | | untested | | 282 | PageNet | USA, MO, Kansas City | 800-500-6630 | 7e1 | max | | untested | | 283 | PageNet | USA, TN, Knoxville | 423-301-9998 | 7e1 | max | | untested | | 284 | PageNet | USA, TN, Knoxville | 423-301-9999 | 7e1 | max | | untested | | 286 | PageNet | USA, NV, Las Vegas | 702-661-7243 | 7e1 | max | | untested | | 287 | PageNet | USA, NV, Las Vegas | 702-732-1957 | 7e1 | max | | untested | | 288 | PageNet | USA, KY, Lexington | 606-532-9999 | 7e1 | max | | untested | | 289 | PageNet | USA, CA, Long Beach | 310-327-5450 | 7e1 | max | | untested | | 290 | PageNet | USA, NY, Long Island | 516-745-0055 | 7e1 | max | | untested | | 291 | PageNet | USA, CA, Los Alamitos | 562-744-6300 | 7e1 | max | | untested | | 292 | PageNet | USA, CA, Los Angeles | 213-303-9372 | 7e1 | max | | untested | | 293 | PageNet | USA, CA, Los Angeles | 213-750-0450 | 7e1 | max | | untested | | 294 | PageNet | USA, LA | 800-762-2348 | 7e1 | max | | untested | | 295 | PageNet | USA, KY, Louisville | 502-581-8799 | 7e1 | max | | untested | | 296 | PageNet | USA, GA, Macon | 912-743-7647 | 7e1 | max | | untested | | 297 | PageNet | USA, WI, Madison | 608-682-1116 | 7e1 | max | | untested | | 298 | PageNet | USA, CA, Maplegrove | 818-850-6773 | 7e1 | max | | untested | | 299 | PageNet | USA, MA | 401-861-8130 | 7e1 | max | | untested | | 300 | PageNet | USA, TN, Memphis | 901-769-9998 | 7e1 | max | | untested | | 301 | PageNet | USA, TN, Memphis | 901-769-9999 | 7e1 | max | | untested | | 302 | PageNet | USA, CA, Merced | 209-724-7002 | 7e1 | max | | untested | | 303 | PageNet | USA, FL, Miami | 305-264-6133 | 7e1 | max | | untested | | 304 | PageNet | USA, FL, Miami | 305-287-7243 | 7e1 | max | | untested | | 305 | PageNet | USA, MI | 800-955-7243 | 7e1 | max | | untested | | 306 | PageNet | USA, MI | 810-827-7399 | 7e1 | max | | untested | | 307 | PageNet | USA, WI, Milwaukee | 414-230-1116 | 7e1 | max | | untested | | 308 | PageNet | USA, MN, Minneapolis | 612-942-8176 | 7e1 | max | | untested | | 309 | PageNet | USA, AL, Mobile | 334-568-0000 | 7e1 | max | | untested | | 310 | PageNet | USA, CA, Modesto | 209-527-9101 | 7e1 | max | | untested | | 311 | PageNet | Canada, Quebec, Montreal | 514-736-8008 | 7e1 | max | | untested | | 312 | PageNet | USA, NC/SC | 800-766-9731 | 7e1 | max | | untested | | 313 | PageNet | USA, Nationwide | 800-807-0616 | 7e1 | max | | untested | | 314 | PageNet | USA, NY, NYC Brooklyn | 718-355-2840 | 7e1 | max | | untested | | 315 | PageNet | USA, NY, NYC Manhattan | 718-355-2820 | 7e1 | max | | untested | | 316 | PageNet | USA, TN, Nashville | 615-303-9998 | 7e1 | max | | untested | | 317 | PageNet | USA, TN, Nashville | 615-303-9999 | 7e1 | max | | untested | | 318 | PageNet | USA, Nationwide | 800-720-8398 | 7e1 | max | | untested | | 319 | PageNet | USA, Nationwide | 888-720-8398 | 7e1 | max | | untested | | 320 | PageNet | USA, Nationwide | 888-589-8600 | 7e1 | max | | untested | | 321 | PageNet | USA, WI, Green Bay | 920-440-1116 | 7e1 | max | | untested | | 322 | PageNet | USA, NH, New Hampshire | 603-497-8467 | 7e1 | max | | untested | | 323 | PageNet | USA, NJ | 800-437-0528 | 7e1 | max | | untested | | 324 | PageNet | USA, NJ | 908-686-9053 | 7e1 | max | | untested | | 325 | PageNet | USA, NJ, New Jersey | 201-878-7243 | 7e1 | max | | untested | | 326 | PageNet | USA, NJ, New Jersey | 908-983-7243 | 7e1 | max | | untested | | 327 | PageNet | USA, LA, New Orleans | 504-835-6124 | 7e1 | max | | untested | | 328 | PageNet | USA, FL, New Port Richey | 813-843-5299 | 7e1 | max | | untested | | 329 | PageNet | USA, NY | 917-252-2000 | 7e1 | max | | untested | | 330 | PageNet | USA, NY, Newburgh | 914-438-0000 | 7e1 | max | | untested | | 331 | PageNet | USA, CA, Santa Clarita Valley | 805-287-3000 | 7e1 | max | | untested | | 332 | PageNet | USA, VA, Norfolk | 757-366-1044 | 7e1 | max | | untested | | 333 | PageNet | USA, VA, Norfolk | 804-366-5349 | 7e1 | max | | untested | | 334 | PageNet | USA, NC | 800-685-4399 | 7e1 | max | | untested | | 336 | PageNet | USA, CA, Norwalk | 562-707-1000 | 7e1 | max | | untested | | 337 | PageNet | USA, CA, Oakland | 510-319-2436 | 7e1 | max | | untested | | 338 | PageNet | USA, CA, Oakland | 510-638-3526 | 7e1 | max | | untested | | 339 | PageNet | USA, OK, Oklahoma | 405-980-7243 | 7e1 | max | | untested | | 340 | PageNet | USA, CA, Ontario/Claremont | 909-448-2100 | 7e1 | max | | untested | | 341 | PageNet | USA, FL, Orlando | 407-974-7099 | 7e1 | max | | untested | | 342 | PageNet | USA, FL, Orlando | 407-839-0712 | 7e1 | max | | untested | | 343 | PageNet | USA, CA, Palm Springs | 760-833-1234 | 7e1 | max | | untested | | 344 | PageNet | USA, CA, Palmdale/Lancaster | 805-540-1005 | 7e1 | max | | untested | | 345 | PageNet | USA, PA, Philadelphia | 610-834-1572 | 7e1 | max | | untested | | 346 | PageNet | USA, PA, Philadelphia | 800-437-0529 | 7e1 | max | | untested | | 347 | PageNet | USA, AZ, Phoenix | 602-591-6000 | 7e1 | max | | untested | | 348 | PageNet | USA, PA, Pittsburgh | 412-788-2878 | 7e1 | max | | untested | | 349 | PageNet | USA, OR, Portland | 503-232-3787 | 7e1 | max | | untested | | 350 | PageNet | USA, RI, Providence | 401-647-5457 | 7e1 | max | | untested | | 351 | PageNet | USA, NC, Raleigh | 919-505-0000 | 7e1 | max | | untested | | 352 | PageNet | USA, NV, Reno | 702-329-4506 | 7e1 | max | | untested | | 353 | PageNet | USA, VA, Richmond | 804-278-7844 | 7e1 | max | | untested | | 354 | PageNet | USA, NY, Rochester | 716-306-0000 | 7e1 | max | | untested | | 355 | PageNet | USA, CA, Sacramento | 209-473-7506 | 7e1 | max | | untested | | 356 | PageNet | USA, CA, Sacramento | 916-981-2000 | 7e1 | max | | untested | | 357 | PageNet | USA, CA, Sacramento | 916-929-7686 | 7e1 | max | | untested | | 358 | PageNet | USA, CA, Saddleback | 714-227-0014 | 7e1 | max | | untested | | 359 | PageNet | USA, OR, Salem | 503-391-4439 | 7e1 | max | | untested | | 360 | PageNet | USA, UT, Salt Lake City | 801-919-0008 | 7e1 | max | | untested | | 361 | PageNet | USA, TX, San Antonio | 210-342-8831 | 7e1 | max | | untested | | 362 | PageNet | USA, CA, San Carlos | 415-572-1411 | 7e1 | max | | untested | | 363 | PageNet | USA, CA, San Diego | 619-232-8425 | 7e1 | max | | untested | | 364 | PageNet | USA, CA, San Diego | 619-918-8808 | 7e1 | max | | untested | | 365 | PageNet | USA, CA, San Fernando | 818-328-0020 | 7e1 | max | | untested | | 366 | PageNet | USA, CA, San Francisco | 415-207-0057 | 7e1 | max | | untested | | 367 | PageNet | USA, CA, San Gabriel Valley | 818-292-3012 | 7e1 | max | | untested | | 368 | PageNet | USA, CA, San Jose | 408-428-9729 | 7e1 | max | | untested | | 369 | PageNet | USA, CA, San Jose | 408-798-7201 | 7e1 | max | | untested | | 370 | PageNet | USA, CA, San Luis Obispo | 805-782-2416 | 7e1 | max | | untested | | 371 | PageNet | USA, CA, San Mateo | 650-371-0040 | 7e1 | max | | untested | | 372 | PageNet | USA, CA, Santa Barbara | 805-887-1684 | 7e1 | max | | untested | | 373 | PageNet | USA, CA, Santa Maria | 805-349-3025 | 7e1 | max | | untested | | 374 | PageNet | USA, CA, Santa Monica | 310-239-5116 | 7e1 | max | | untested | | 375 | PageNet | USA, CA, Santa Rosa | 707-316-0002 | 7e1 | max | | untested | | 376 | PageNet | USA, FL, Sarasota | 941-569-7299 | 7e1 | max | | untested | | 377 | PageNet | USA, WA, Seattle | 800-795-4563 | 7e1 | max | | untested | | 378 | PageNet | USA, WA, Seattle | 206-747-9674 | 7e1 | max | | untested | | 379 | PageNet | USA, WA, Seattle | 425-747-1401 | 7e1 | max | | untested | | 381 | PageNet | USA, WA, Spokane | 509-490-0000 | 7e1 | max | | untested | | 382 | PageNet | USA, MA, Springfield | 413-290-0101 | 7e1 | max | | untested | | 383 | PageNet | USA, FL, St. Augustine | 904-823-1600 | 7e1 | max | | untested | | 384 | PageNet | USA, MO, St. Louis | 314-984-8710 | 7e1 | max | | untested | | 385 | PageNet | USA, CA, Stockton | 209-889-4100 | 7e1 | max | | untested | | 386 | PageNet | USA, NY, Syracuse | 315-449-2502 | 7e1 | max | | untested | | 387 | PageNet | USA, WA, Tacoma | 253-572-4019 | 7e1 | max | | untested | | 388 | PageNet | USA, FL, Tallahassee | 850-224-4492 | 7e1 | max | | untested | | 389 | PageNet | USA, FL, Tampa | 813-219-7299 | 7e1 | max | | untested | | 390 | PageNet | USA, CA, Temecula | 909-507-1234 | 7e1 | max | | untested | | 391 | PageNet | USA, CA, Simi Valley | 805-494-5818 | 7e1 | max | | untested | | 392 | PageNet | USA, AZ, Tucson | 520-930-7243 | 7e1 | max | | untested | | 393 | PageNet | USA, CA, Ventura/Oxnard | 805-671-0143 | 7e1 | max | | untested | | 394 | PageNet | USA, CA, Victorville | 619-955-9099 | 7e1 | max | | untested | | 395 | PageNet | USA, CA, Visalia | 209-741-5506 | 7e1 | max | | untested | | 396 | PageNet | USA, Washington DC | 703-442-7606 | 7e1 | max | | untested | | 397 | PageNet | USA, Washington DC | 301-474-4657 | 7e1 | max | | untested | | 398 | PageNet | USA, FL, West Palm Beach | 561-551-7243 | 7e1 | max | | untested | | 399 | PageNet | USA, NY, Westchester | 914-284-0000 | 7e1 | max | | untested | | 400 | PageNet | USA, NY, Westchester | 914-347-3136 | 7e1 | max | | untested | | 401 | PageNet | USA, WI | 800-471-3408 | 7e1 | max | | untested | | 402 | PageNet | USA, OH, Youngstown/Warren | 330-380-7243 | 7e1 | max | | untested | | 403 | Pagers Plus | Unknown | 800-257-2307 | 7e1 | max | | untested | | 404 | Paging Plus | Unknown | 800-848-6122 | 7e1 | max | | untested | | 405 | PageUSA | USA, CA | 925-803-1180 | 7e1 | max | | untested | | 406 | Paging, Inc. | Unknown | 800-888-8757 | 7e1 | max | | untested | | 407 | Phoenix Paging | Unknown | 602-929-0500 | 7e1 | max | | untested | | 408 | Porta-Phone | Unknown | 800-443-8931 | 7e1 | max | | untested | | 409 | PrimeCo Messaging | Unknown | 800-659-7514 | 7e1 | max | | untested | | 410 | ProNet | USA, FL, Tallahassee | 800-723-9036 | 7e1 | max | | untested | | 411 | ProNet | USA, TX | 800-374-3801 | 7e1 | max | | untested | | 412 | ProNet | USA, WI | 800-366-1936 | 7e1 | max | | untested | | 413 | RAMPage | Unknown | 800-718-1448 | 7e1 | max | | untested | | 414 | Radiofone | USA, LA, New Orleans | 800-431-2731 | 7e1 | max | | untested | | 415 | Range | USA, MI, Upper Peninsula | 906-222-1000 | 7e1 | max | | untested | | 416 | Seiko Message watch | Unknown | 213-613-0224 | 7e1 | max | | untested | | 417 | Seiko Message watch | USA, OR, Portland | 503-243-3019 | 7e1 | max | | untested | | 418 | Seiko Message watch | USA, WA, Seattle | 360-621-0830 | 7e1 | max | | untested | | 419 | Shaw Paging | Canada, Quebec, Montreal | 514-336-2290 | 7e1 | max | | untested | | 476 | Cingular | USA, CA, San Jose | 8312030032 | 208-9875 | max | | | | 421 | Skytel | USA, Nationwide | 800-258-9880 | 7e1 | max | | untested | | 422 | SkyTel ASCII | Unknown | 800-759-9673 | 7e1 | max | | untested | | 425 | Skytel | USA, UT, Salt Lake City | 801-488-6473 | 7e1 | max | | untested | | 427 | Sprint PCS | USA, Nationwide | 888-656-1727 | 7e1 | max | | tested | | 428 | Sprint Paging | Unknown | 800-785-5629 | 7e1 | max | | untested | | 429 | Sprint Spectrum | Unknown | 800-272-6505 | 7e1 | max | | untested | | 430 | SuperCom | USA, MI, Upper Peninsula | 906-777-8880 | 7e1 | max | | untested | | 431 | TNI Paging | Unknown | 800-666-3282 | 7e1 | max | | untested | | 432 | TSR Wireless | USA, NY, Long Island | 516-426-4110 | 7e1 | max | | untested | | 433 | TSR Wireless | USA, VA Northern | 703-331-2808 | 7e1 | max | | untested | | 434 | TSR Wireless | USA, AZ, Phoenix | 602-216-1000 | 7e1 | max | | untested | | 435 | Telus | Canada, AB, Calgary | 403-247-2635 | 7e1 | max | | untested | | 436 | Telus | Canada, AB, Edmonton | 780-916-8118 | 7e1 | max | | untested | | 437 | Telus | USA, Nationwide | 888-442-0454 | 7e1 | max | | untested | | 438 | Touch Tel Paging | Unknown | 510-748-2097 | 7e1 | max | | untested | | 439 | Tri-State | USA, New England | 617-668-0003 | 7e1 | max | | untested | | 440 | US West | Unknown | 520-327-8944 | 7e1 | max | | untested | | 441 | US West | Unknown | 888-823-3434 | 7e1 | max | | untested | | 442 | US West AdvancedPCS | Unknown | 888-823-8234 | 7e1 | max | | untested | | 443 | USA Mobile | Unknown | 513-563-5067 | 7e1 | max | | untested | | 444 | USA Mobile | Unknown | 800-589-9776 | 7e1 | max | | untested | | 445 | USA Mobile | Unknown | 800-840-0075 | 7e1 | max | | untested | | 446 | USA Mobile | USA, OH, Akron | 800-688-9259 | 7e1 | max | | untested | | 447 | USA Mobile | USA, AL | 800-239-7749 | 7e1 | max | | untested | | 448 | USA Mobile | USA, KY, Eastern | 800-944-1233 | 7e1 | max | | untested | | 449 | USA Mobile | USA, OH, Findley | 800-686-1090 | 7e1 | max | | untested | | 450 | USA Mobile | USA, IN, Fort Wayne | 800-491-2090 | 7e1 | max | | untested | | 451 | USA Mobile | USA, OH | 800-686-9900 | 7e1 | max | | untested | | 452 | Voice Stream | Unknown | 800-937-8941 | 7e1 | max | | PINs require a leading '1', e.g. 18085551212 | | 453 | VPS Paging | Unknown | 605-338-7794 | 7e1 | max | | untested | | 454 | Weblink Wireles/PageMart | Unknown | 800-557-8081 | 7e1 | max | | 2 way,untested | | 455 | Westlink | USA, MN, Minneapolis | 800-793-6280 | 7e1 | max | | untested | | 456 | Westlink Paging | Unknown | 602-231-9447 | 7e1 | max | | untested | | 457 | Westlink Paging | Unknown | 800-231-7886 | 7e1 | max | | untested | | 458 | Westlink Paging | Unknown | 801-483-1081 | 7e1 | max | | untested | | 459 | Bell South Wireless RIM | USA, Nationwide | 800-868-2835 | 7e1 | max | | not fully tested | | 460 | Cantel | Canada | 604-685-3612 | 7e1 | max | | untested | | 461 | USA Mobile | USA, IL | 800-666-0038 | 7e1 | max | | untested | | 462 | Breaking News Network | Unknown | 201-947-9049 | 7e1 | max | | untested | | 463 | PageNet | USA, Northeastern | 917-973-7000 | 7e1 | max | | untested | | 464 | Vodafone | Australia | 0414100200 | 8n1 | 4800 | | untested | | 465 | Verizon | USA, Nationwide | 866-823-0501 | 7e1 | max | | | | 466 | Cingular | USA, Nationwide | 800-469-5351 | 7e1 | max | | | | 467 | Nextel | USA, KS, Kansas City | 913-927-6683 | 7e1 | max | | | | 468 | TeleBeep Wireless | USA, NY | 402-644-2400 | 7e1 | 300 | | Pagers only? | | 469 | Verizon | USA, MI, Grand Rapids | 616-478-7243 | 7e1 | 1200 | | untested (baud may be max) | | 470 | Sky Comm USA, INC | USA, FL | 352-401-9471 ext 21 | 7e1 | max | | Pagers Only | | 473 | Bell Mobility | Canada, Ontario, Ottawa | +1 613 744 4822 | 7e1 | 1200 | ID= may not appear right away, don't give up too quickly | TAP | | 474 | PageNet | Canada, Ontario, Ottawa | +1 613 722 0337 | 7e1 | 1200 | | | +-----+--------------------------+-------------------------------+---------------------+------------+------------+----------------------------------------------------------+----------------------------------------------+ 453 rows in set (0.00 sec) sendpage-1.000003/docs/pc-testing.txt0000644000076500007650000000472110737241466015761 0ustar keeskeesThis document is a really rough example of how to examine a paging central to figure out the paging limits for a particular pager or service. (See "examples/configure-pc" for a quick perl script that does this part.) start with PC configuration defaults. send a page just to make sure you CAN get a page. set "esc=true" Send page with "\n" and "\t"'s (eg "This\nis a CR and this\tis a tab.") if you see \n and \t okay, you have a real 1.8 TAP, and you're done. otherwise: remove "esc=true" set "ctrl=true" Send page with "\n" and "\t"'s (eg "This\nis a CR and this\tis a tab.") if both \n and \t show up correctly, you have a weak TAP, but you're done. if only the \n was displayed correctly, you can pass \n's only, so: unset "ctrl=true" set "lfok=true" and you're done otherwise: unset "ctrl=true" you can't send ctrl chars at all. sorry! now, gauge the size of your paging length (test with ctrl/esc/lfok off): page of 100 chars: a123456789b123456789c123456789d123456789e123456789f123456789g123456789h123456789i123456789j123456789 page of 200 chars: a123456789b123456789c123456789d123456789e123456789f123456789g123456789h123456789i123456789j123456789A123456789B123456789C123456789D123456789E123456789F123456789G123456789H123456789I123456789J123456789 page of 240 chars: a123456789b123456789c123456789d123456789e123456789f123456789g123456789h123456789i123456789j123456789A123456789B123456789C123456789D123456789E123456789F123456789G123456789H123456789I123456789J123456789a123456789b123456789c123456789d123456789 page of 250 chars: a123456789b123456789c123456789d123456789e123456789f123456789g123456789h123456789i123456789j123456789A123456789B123456789C123456789D123456789E123456789F123456789G123456789H123456789I123456789J123456789a123456789b123456789c123456789d123456789e123456789 page of 300 chars: a123456789b123456789c123456789d123456789e123456789f123456789g123456789h123456789i123456789j123456789A123456789B123456789C123456789D123456789E123456789F123456789G123456789H123456789I123456789J123456789a123456789b123456789c123456789d123456789e123456789f123456789g123456789h123456789i123456789j123456789 start with X=100 chars start with SIZE=100 chars try to send a page: send a page with SIZE chars if it xmit fail or go through truncated? decrease X by half decrease SIZE by X else increase SIZE by X try page again Some PCs will stall on a "continued.." block, so you may need to force splits at 250 - pinsize - 2 chars (usually 240). -Kees Cook kees@outflux.net sendpage-1.000003/docs/postfix.txt0000644000076500007650000000221410737241466015373 0ustar keeskeesPostfix to Sendpage Email Configuration, from Randy Emler with additions by Chris Hubbell: Modify the /etc/postfix/transport file to include sendpage:localhost Example: spage.yourdomain.com sendpage:localhost Modify the /etc/postfix/master.cf file to include sendpage unix - n n - - pipe flags= user=nobody argv=/usr/bin/sendmail2snpp -f $(user) $(sender) Just make sure the "argv=" points to the proper path for the agent. Modify the /etc/postfix/main.cf file to include transport_maps = hash:/etc/postfix/transport Uncomment "local_recipient_maps" in main.cf: local_recipient_maps = Run: "postmap transport" to create the transport.db file Make sure your paging box accepts smtp connections from your corporate mail gateway box. Run: "service postfix stop", "service postfix start" Make sure recipients are setup in the sendpage.cf file like the following [recip:joe_blow] dest = 5551212@mypagingqueue Try sending an email page addressed like the following To:joe_blow@spage.yourdomain.com Subject: Test Page Hello, I am a test page thru email using sendpage. sendpage-1.000003/docs/sendmail.txt0000644000076500007650000000625510737241677015510 0ustar keeskees$Id: sendmail.txt 316 2008-01-03 20:21:19Z keescook $ Just to note, this documentation assumes you're using sendmail version 8.8.x or better. To get sendpage working with sendmail, there are a few things you'll need: 1) set up sendpage (and test) 2) get yourself a paging domain 3) configure sendmail to use your domain 4) configure email2page.conf 5) test your set up 6) add stuff to your sendmail.cf file Step 1: Set up sendpage (and test) ---------------------------------- Assuming you've already configured sendpage, and tested it with "snpp", and you've gotten your page, you're ready to move on to step 2. Step 2: Get yourself a paging domain ------------------------------------ Since I get most of my email from "outflux.net", I decided I'd make my paging domain "pager.outflux.net". So I added this to my DNS information, and refreshed my DNS server. Once you have a domain resolving with your DNS server, continue to step 3. Step 3: Configure sendmail to use your domain --------------------------------------------- Add your domain to either the "Cw" line in sendpage, or add it to the /etc/sendmail.cw file (recommended). Restart sendmail, and continue to step 4. Step 4: Configure email2page.conf --------------------------------- Take a look at /etc/email2page.conf and set up the header rules to suite your needs. The file is fairly well documented. When finished, continue to step 5. Step 5: Test your set up ------------------------ Send a page by piping an email (with headers) into "sendmail2snpp". This tool should run "email2page" and "snpp" for you. Once you get it working, continue to step 6. Step 6: Add stuff to your sendmail.cf file ------------------------------------------ I recommend using style a), but you can choose either: a) add stuff to your sendmail.mc file (and regenerate your sendmail.cf file). Change "pager.outflux.net" and the sendmail2snpp path, if you need to: dnl dnl Kees' changes for sendpage dnl # for pager gateway CGpager.outflux.net LOCAL_RULE_0 # sendpage R$+<@$=G.> $#sendpage $:$1 define(`SENDPAGE_MAILER_PATH',`/usr/local/bin/sendmail2snpp') define(`SENDPAGE_MAILER_FLAGS',`lsPn') define(`SENDPAGE_MAILER_ARGS',`sendmail2snpp -f $f') MAILER_DEFINITIONS Msendpage, P=SENDPAGE_MAILER_PATH, F=SENDPAGE_MAILER_FLAGS, S=10, R=20, A=SENDPAGE_MAILER_ARGS $u -or- b) add stuff to your sendmail.cf file directly. Change "pager.outflux.net" and the sendmail2snpp path, if you need to. Near the top: # for pager gateway CGpager.outflux.net At the end of rule "S98": # sendpage R$+<@$=G.> $#sendpage $:$1 Near your mailer definitions: Msendpage, P=/usr/local/bin/sendmail2snpp, F=lsPn, S=10, R=20, A=sendmail2snpp -f $f $u Once that's done, restart sendmail with your new .cf file, and you should be ready to rock and roll. Troubleshooting --------------- On RedHat systems, or machines using "smrsh", try this: Here is how to get smrsh to work: cd /etc/smrsh ln -s /usr/bin/sendmail2snpp sendmail2snpp restart sendmail edit /etc/aliases to include: ALIAS: "|/etc/smrsh/sendmail2snpp PIN@PC" run newaliases From Brett Carroll. -Kees Cook kees@outflux.net sendpage-1.000003/docs/README0000644000076500007650000000236410737241573014023 0ustar keeskees The TAP protocol specifications appear to be under copyright, so I can't include the specification with this code. You'll need to check with the PCIA: http://www.pcia.com/WirelessCenter/protocol/tapv1p8/ or with Motorola: http://www.motorola.com/MIMS/MSPG/pcia_protocols/tap_v1p8/ HTML version of TAP v1.8 can be found at http://www.pcia.com/WirelessCenter/protocol/tapv1p8/toc.htm PDF: http://www.pcia.com/WirelessCenter/protocol/tapv1p8/tap_v1p8.pdf PostScript: http://www.pcia.com/WirelessCenter/protocol/tapv1p8/tap_v1p8.ps Contact me if you need a copy of the specification and none of the above URLs work. I'll help you find a copy. ;) The SNPP specifications are published in RFC1861: http://www.faqs.org/rfcs/rfc1861.html The "PagingCentrals.txt" file contains a database dump of all the paging centrals I know about. "sendmail.txt" is a quick tutorial on how to get sendpage working with a standard v8.8+ install of sendmail. "postfix.txt" is a quick tutorial on how to get sendpage working with a standard install of postfix. "sendpage.php" is a quick PHP script for sending pages using "snpp" from a web page. "pc-testing.txt" is a rough draft of how to examine the limits of a Paging Central. -Kees Cook kees@outflux.net sendpage-1.000003/docs/sendpage-manual.lyx0000644000076500007650000010473210737241677016751 0ustar keeskees#LyX 1.2 created this file. For more info see http://www.lyx.org/ \lyxformat 220 \textclass book \language english \inputencoding auto \fontscheme default \graphics default \paperfontsize default \spacing single \papersize Default \paperpackage a4 \use_geometry 0 \use_amsmath 0 \use_natbib 0 \use_numerical_citations 0 \paperorientation portrait \secnumdepth 2 \tocdepth 3 \paragraph_separation indent \defskip medskip \quotes_language english \quotes_times 2 \papercolumns 1 \papersides 1 \paperpagestyle default \layout Title Using Sendpage \newline (the Sendpage Manual) \layout Author Kees Cook \newline \family typewriter kees@outflux.net \layout Date $Revision: 316 $ $Date: 2008-01-03 12:21:19 -0800 (Thu, 03 Jan 2008) $ \newline $RCSfile$ \layout Standard \begin_inset LatexCommand \tableofcontents{} \end_inset \layout Chapter How to Install the Sendpage Daemon \layout Section Prerequisites \layout Standard In order for sendpage to operate correctly, you need several things installed and configured properly on your system. The three major items are the correct set of Perl modules, the correct syslog configuration, and a properly identified operating environment for your sendpage program. \layout Subsection Perl Modules \layout Standard All of the perl modules you need for sendpage are available from \family typewriter http://www.cpan.org/ \family default and instructions on how to install them can be found there. What each module does for you is described below. \layout Subsubsection \family typewriter Net::SNPP \family default (libnet) \layout Standard To run the SNPP client \family typewriter snpp \family default that comes with the sendpage package, you need the Perl module that will talk SNPP with an SNPP server. If you've got a proper install of Perl on your system, it is likely that you already have the \family typewriter libnet \family default bundle, which includes \family typewriter Net::SNPP \family default . \layout Subsubsection \family typewriter Mail::Send (MailTools) \layout Standard To send email notifications with the sendpage package, you need the \family typewriter Mail::Send \family default module, which may also already be installed with Perl, depending on your distribution. This module is found in the \family typewriter MailTools \family default bundle. \layout Subsubsection \family typewriter Device::SerialPort \layout Standard Of all the prerequisites for sendpage, the \family typewriter Device::SerialPort \family default module is the most important. As of version 0.20, this module has been tested to run fine under Linux, Solaris, and BSD. It should operate under AIX and HPUX, but may require some testing. To read more on how to install this module correctly, please read the \family typewriter Device::SerialPort \family default \family typewriter README \family default file that comes with that package. \layout Standard If you're running under Windows, the \family typewriter Win32::SerialPort \family default module should also work. As of sendpage version 0.9.9, this is an untested configuration, and may take a good deal of work to get it functioning correctly. \layout Subsection Syslog Configuration \layout Standard To properly examine debugging information to help you troubleshoot problems with sendpage, you need to have your syslog configuration set to correctly capture all that sendpage sends to it. Sendpage uses the \family typewriter local6 \family default syslog facility by default, and a variety of syslog levels from \family typewriter debug \family default through \family typewriter alert \family default . With a stock syslog configuration, it is likely that everything below \family typewriter info \family default will not be recorded, so you may need to edit your \family typewriter /etc/syslog.conf \family default file. \layout Subsubsection Same file or separate files \layout Standard If your syslog configuration looks like this: \layout Quotation \family typewriter *.info;mail.none;authpriv.none;kern.none\SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ /var/log/messages \layout Standard The easiest way to add \family typewriter local6 \family default to your syslog configuration would be: \layout Quotation \family typewriter *.info; \series bold local6.*; \series default mail.none;authpriv.none;kern.none\SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ /var/log/messages \layout Standard If you want your sendpage output sent to a separate file, your syslog configurat ion would be: \layout Quotation \family typewriter *.info; \series bold local6.none; \series default mail.none;authpriv.none;kern.none\SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ /var/log/messages \layout Quotation \family typewriter \series bold local6.*\SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ /var/log/sendpage \layout Subsubsection Split off debug output \layout Standard If you want to separate your debug output from all your other syslog files, another configuration could be: \layout Quotation \family typewriter *.info; \series bold local6.none; \series default mail.none;authpriv.none;kern.none\SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ /var/log/messages \layout Quotation \family typewriter \series bold local6.info\SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ /var/log/sendpage \layout Quotation \family typewriter \series bold *.=debug\SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ /var/log/debug \layout Standard This will allow you to view all your debug output in a single separate file. \layout Standard I find that the following syslog configuration allows me to view sendpage logging either way I want: with debugging or without debugging: \layout Quotation \family typewriter *.info; \series bold local6.none; \series default mail.none;authpriv.none;kern.none\SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ /var/log/messages \layout Quotation \family typewriter \series bold local6.info\SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ /var/log/sendpage \layout Quotation \family typewriter \series bold local6.debug\SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ /var/log/sendpage.debug \layout Subsection \family typewriter sendpage \family default user environment \layout Standard Sendpage performs 3 major duties on your system: 1) operates on your serial port to use the modem, 2) writes to disk to store page information, 3) operates on a network socket to receive new pages. To get these things done in a secure fashion, sendpage tries to \series bold not \series default run as the \family typewriter root \family default user. For some things, it must; binding to network ports below 1024 requires \family typewriter root \family default permissions. Also, the serial port needs to be reserved for use (with UUCP-style locking) so that other processes can share the modem with sendpage. Finally, the serial port itself will require some sort of privileges to read and write to it. \layout Subsubsection Creating a \family typewriter sendpage \family default user \layout Standard To store pages and other information to disk, and to run network sessions \series bold not \series default as the \family typewriter root \family default user, a regular user is required. For the rest of this document, it is assumed that this user is named \begin_inset Quotes eld \end_inset sendpage \begin_inset Quotes erd \end_inset . This user should have no special privileges, and can be an presently unused user ID on your system. If this user is not known as \begin_inset Quotes eld \end_inset sendpage \begin_inset Quotes erd \end_inset , the sendpage daemon needs to know, and this can be changed using the \family typewriter user \family default configuration option. \layout Subsubsection Finding locking group \layout Standard To let sendpage reserve a serial port for use, the daemon will attempt to use UUCP-style file locking. Finding where this happens is important (and is set using the \family typewriter lockprefix \family default option). Usually the locking directory is either \family typewriter /var/lock \family default or \family typewriter /var/spool/lock \family default , and a single group will have permission to read and write in that directory. Usually this group is the \family typewriter uucp \family default group. If not, the locking group must be set using the \family typewriter group-lock \family default option. \layout Subsubsection Finding tty-use group \layout Standard To let sendpage read and write to a serial port, the daemon must know which group to use to open the device. By default, sendpage expects to use the \family typewriter tty \family default group, but sometimes this is the \family typewriter uucp \family default group. This is set using the \family typewriter group-tty \family default option. Sometimes there is no group that can read and write to the device. In these cases, you will need to change the permissions on your serial port so that some group can read and write your serial port. \layout Standard For example, if you want to use the \family typewriter tty \family default group, and your serial port looks like this: \layout Quotation \family typewriter # ls -l /dev/ttyS0 \layout Quotation \family typewriter crw------- 1 root root 4, 64 Mar 1 18:37 /dev/ttyS0 \layout Standard Then you would need to change the group and permissions like this: \layout Quotation \family typewriter # chgrp tty /dev/ttyS0 \layout Quotation \family typewriter # chmod g+rw /dev/ttyS0 \layout Quotation \family typewriter # ls -l /dev/ttyS0 \layout Quotation \family typewriter crw- \series bold rw \series default ---- 1 root \series bold tty\SpecialChar ~ \SpecialChar ~ \series default 4, 64 Mar 1 18:37 /dev/ttyS0 \layout Section Compilation \layout Standard Since sendpage is mostly a collection of Perl modules, compilation is technicall y not required. After you've unpacked the tar file and have all your prerequisites completed, it's a simple matter of running: \layout Quotation \family typewriter perl Makefile.PL \layout Quotation \family typewriter make \layout Standard and you're done \begin_inset Quotes eld \end_inset compiling \begin_inset Quotes erd \end_inset \layout Section Installation \layout Subsection Install the scripts and modules \layout Standard This part is easy. Just run: \layout Quotation \family typewriter make install \layout Subsection Queue directory \layout Standard Sendpage needs to store pages and process information somewhere. By default, this is \family typewriter /var/spool/sendpage \family default . If you want to create this directory somewhere else, you can change it using the \family typewriter queuedir \family default and \family typewriter pidfileprefix \family default options. Since the queue directory isn't there with a fresh install, you will need to create it, make it available to the \family typewriter sendpage \family default user, and secure it from prying eyes: \layout Quotation \family typewriter mkdir -p /var/spool/sendpage \layout Quotation \family typewriter chown sendpage /var/spool/sendpage \layout Quotation \family typewriter chmod og-rwx /var/spool/sendpage \layout Subsection Configuration files \layout Standard Now you will need to copy the configuration files for \family typewriter sendpage \family default , \family typewriter snpp \family default , and \family typewriter email2page \family default into your \family typewriter /etc \family default directory: \layout Quotation \family typewriter cp sendpage.cf snpp.conf email2page.conf /etc \layout Standard You will be editing these files in the \family typewriter /etc \family default directory to control how \family typewriter sendpage \family default , \family typewriter snpp \family default , and \family typewriter email2page \family default behave. \layout Section Configuration \layout Standard Changing how sendpage behaves is a matter of making changes to the \family typewriter /etc/sendpage.cf \family default file, and reloading the daemon. A complete list of all the options available is documented in the file itself. However, some of these are covered here for your initial configuration. \layout Subsection General debugging \layout Standard The general operations of the sendpage daemon and the functions of the SNPP server can be debugged by setting the \family typewriter debug \family default option to \begin_inset Quotes eld \end_inset true \begin_inset Quotes erd \end_inset . To debug Paging Centrals or Modems, their debugging is turned on separately in their respective sections. \layout Subsection How the \family typewriter page-daemon \family default alias works \layout Standard When sendpage delivers (or fails to deliver) pages, it will generate email back to the sender (if there was a recorded sender), to the recipient (if there was an \family typewriter email-cc \family default for the recipient), and possibly to the user set by the \family typewriter page-daemon \family default option (in the case of errors). \layout Subsubsection As the 'from' address \layout Standard Since email needs to come from somewhere, the user specified in the \family typewriter page-daemon \family default option (normally \begin_inset Quotes eld \end_inset sendpage \begin_inset Quotes erd \end_inset ) will be used as the \begin_inset Quotes eld \end_inset From \begin_inset Quotes erd \end_inset for all email generated. This can be changed globally, or it can be changed for each configured Paging Central. \layout Subsubsection For errors and warnings \layout Standard When a page fails to be delivered, the \family typewriter page-daemon \family default user can also get a notification. In this way, system administrators can be notified of any failures. If this is not a desired action, it can be disabled by setting the \family typewriter cc-on-error \family default option to \begin_inset Quotes eld \end_inset false \begin_inset Quotes erd \end_inset . This can be configured globally, or on a per-PC basis. In the case of temporary failures, the \family typewriter tempfail-notify-after \family default option can be set to how many temporary failures you want to have happen before anyone is notified. This is 10 by default. \layout Subsection SNPP server settings \layout Standard SNPP stands for \begin_inset Quotes eld \end_inset Simple Network Paging Protocol \begin_inset Quotes erd \end_inset . This is the protocol that is used to send pages to an SNPP server for delivery, similar to the Internet email protocol SMTP ('Simple Mail Transfer Protocol \begin_inset Quotes erd \end_inset ). Sendpage has two SNPP parts: the server and the client. The server portion is built in to the sendpage daemon, and can be configured to listen on the network for clients who wish to send pages. The client portion is the tool that users or other delivery processes (like email delivery agents) will use to send pages through sendpage. The client tool is known as \family typewriter snpp \family default and is covered in the \begin_inset Quotes eld \end_inset How to send pages \begin_inset Quotes erd \end_inset section. \layout Standard The SNPP server presently has two configurable options: which address it binds to, and what port it listens on. The default port is 444, which is the standard SNPP port. By default, sendpage will only listen on the 127.0.0.1 (localhost) address. To change these settings change the following values: \layout Subsubsection \family typewriter snpp-port \layout Standard This is normally \begin_inset Quotes eld \end_inset 444 \begin_inset Quotes erd \end_inset , but can be changed to any available port on your local system. \layout Subsubsection \family typewriter snpp-addr \layout Standard This is normally \begin_inset Quotes eld \end_inset localhost \begin_inset Quotes erd \end_inset , but can be changed to any IP address available on your local system. The special IP address \begin_inset Quotes eld \end_inset 0.0.0.0 \begin_inset Quotes erd \end_inset means \begin_inset Quotes eld \end_inset all IP addresses \begin_inset Quotes erd \end_inset on the local system. \layout Section Running the \family typewriter sendpage \family default daemon \layout Standard Even though you need to configure your modem and Paging Centrals first, this section covers how to start and stop the sendpage daemon. A SysV-style init script named \family typewriter sendpage.init \family default was included in the package, and can be used to perform these functions for you, if you wish. To control the daemon directly, here are the methods: \layout Subsection Starting daemon \layout Standard To start sendpage, run: \layout Quotation \family typewriter sendpage -bd \layout Standard The silly mnemonic is \begin_inset Quotes eld \end_inset \series bold b \series default e a \series bold d \series default aemon \begin_inset Quotes erd \end_inset . \layout Subsection Stopping daemon \layout Standard To stop sendpage, either send the master process a \family typewriter QUIT \family default signal, or run: \layout Quotation \family typewriter sendpage -bs \layout Standard The silly mnemonic here is \begin_inset Quotes eld \end_inset \series bold b \series default e \series bold s \series default topped \begin_inset Quotes erd \end_inset . \layout Subsection Reloading daemon \layout Standard To reload sendpage, either send the master process a \family typewriter HUP \family default signal, or run: \layout Quotation \family typewriter sendpage -br \layout Standard Mnemonic is \begin_inset Quotes eld \end_inset \series bold b \series default e \series bold r \series default eloaded \begin_inset Quotes erd \end_inset . \layout Subsection Checking status of daemon \layout Standard To see if sendpage is already running (the \family typewriter -bd \family default option does this too, if sendpage is already running): \layout Quotation \family typewriter sendpage -bq \layout Standard Mnemonic is \begin_inset Quotes eld \end_inset \series bold b \series default e \series bold q \series default uestioned \begin_inset Quotes erd \end_inset . \layout Subsection Checking status of queues \layout Standard To see what is in the page queues, run: \layout Quotation \family typewriter sendpage -bp \layout Standard Mnemonic is \begin_inset Quotes eld \end_inset \series bold b \series default e nice and \series bold p \series default rint the contents of your queues \begin_inset Quotes erd \end_inset . (Okay, so I ran out of mnemonics; the options were based on \family typewriter sendmail \family default 's options anyway...) \layout Section What are all these processes? \layout Standard Sendpage runs a separate process for each Paging Central queue that it maintains. The master process will spawn the PC children, and run the SNPP server. New SNPP connections will be forked off as children as well. Only the master process runs as \family typewriter root \family default , and the children run as the \family typewriter sendpage \family default user. The PC children additionally run with the group IDs needed to lock and open the serial port. \layout Standard Each process will identify itself on the command line portion visible in the process list. For example: \layout Quotation \family typewriter # ps -f -p 20900 -u sendpage \layout Quotation \family typewriter UID\SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ PID\SpecialChar ~ \SpecialChar ~ \SpecialChar ~ PPID\SpecialChar ~ \SpecialChar ~ C\SpecialChar ~ STIME\SpecialChar ~ TTY\SpecialChar ~ TIME\SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ CMD \layout Quotation \family typewriter root\SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ 20900\SpecialChar ~ 1\SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ \SpecialChar ~ 0\SpecialChar ~ Mar02\SpecialChar ~ \SpecialChar ~ ?\SpecialChar ~ \SpecialChar ~ 00:00:00\SpecialChar ~ sendpage:\SpecialChar ~ accepting\SpecialChar ~ connections \layout Quotation \family typewriter sendpage\SpecialChar ~ 20901\SpecialChar ~ 20900\SpecialChar ~ 0\SpecialChar ~ Mar02\SpecialChar ~ \SpecialChar ~ ?\SpecialChar ~ \SpecialChar ~ 00:00:01\SpecialChar ~ sendpage:\SpecialChar ~ ameritech\SpecialChar ~ queue \layout Quotation \family typewriter sendpage\SpecialChar ~ 20902\SpecialChar ~ 20900\SpecialChar ~ 0\SpecialChar ~ Mar02\SpecialChar ~ \SpecialChar ~ ?\SpecialChar ~ \SpecialChar ~ 00:00:00\SpecialChar ~ sendpage:\SpecialChar ~ attpcs\SpecialChar ~ queue \layout Quotation \family typewriter sendpage\SpecialChar ~ 20903\SpecialChar ~ 20900\SpecialChar ~ 0\SpecialChar ~ Mar02\SpecialChar ~ \SpecialChar ~ ?\SpecialChar ~ \SpecialChar ~ 00:00:00\SpecialChar ~ sendpage:\SpecialChar ~ nextel\SpecialChar ~ queue \layout Quotation \family typewriter sendpage\SpecialChar ~ 20904\SpecialChar ~ 20900\SpecialChar ~ 0\SpecialChar ~ Mar02\SpecialChar ~ \SpecialChar ~ ?\SpecialChar ~ \SpecialChar ~ 00:00:00\SpecialChar ~ sendpage:\SpecialChar ~ pagenet\SpecialChar ~ queue \layout Quotation \family typewriter sendpage\SpecialChar ~ 20905\SpecialChar ~ 20900\SpecialChar ~ 0\SpecialChar ~ Mar02\SpecialChar ~ \SpecialChar ~ ?\SpecialChar ~ \SpecialChar ~ 00:00:00\SpecialChar ~ sendpage:\SpecialChar ~ rim-pager\SpecialChar ~ queue \layout Quotation \family typewriter sendpage\SpecialChar ~ 20906\SpecialChar ~ 20900\SpecialChar ~ 0\SpecialChar ~ Mar02\SpecialChar ~ \SpecialChar ~ ?\SpecialChar ~ \SpecialChar ~ 00:00:00\SpecialChar ~ sendpage:\SpecialChar ~ skytel\SpecialChar ~ queue \layout Quotation \family typewriter sendpage\SpecialChar ~ 29833\SpecialChar ~ 20900\SpecialChar ~ 0\SpecialChar ~ 13:53\SpecialChar ~ \SpecialChar ~ ?\SpecialChar ~ \SpecialChar ~ 00:00:00\SpecialChar ~ sendpage:\SpecialChar ~ SNPP\SpecialChar ~ client:\SpecialChar ~ 127.0.0.1 \layout Standard You'll see that process 20900 is the master and is taking SNPP connections. Each of the PCs is identified by the name of the queue, and each attached SNPP client shows the IP address of the peer. \layout Chapter How to add a modem \layout Section How are modems used? \layout Standard Since TAP ( \begin_inset Quotes eld \end_inset Telocator Alphanumeric Protocol \begin_inset Quotes erd \end_inset ) is an ASCII-based communication system, a modem is used to send pages. When using sendpage, you can specify any number of modems. They will all be initialized during daemon start-up to verify that they are functioning. After that, each Paging Central queue will be able to use any of the defined modems. Sendpage uses UUCP-style file locking so that your modem devices can continue to be shared with other processes. \layout Standard Once a PC queue locks a modem, it will dial out and attempt to reach the remote Paging Central to deliver any pages in the queue. Once all the pages have been sent (or some predefined delivery limit is reached), the PC will hang up, unlock the modem, and continue waiting for more pages. \layout Section Configurable options \layout Standard To define a new modem, you must start a new \begin_inset Quotes eld \end_inset section \begin_inset Quotes erd \end_inset in your \family typewriter /etc/sendpage.cf \family default file. A modem section is written as: \layout Quotation \family typewriter [modem:NAME] \layout Standard where \family typewriter NAME \family default is what you want to identify your modem as. The only required option in this section is the \family typewriter dev \family default option, which specifies which serial port this modem is attached to. \layout Subsection Debugging \layout Standard Within the modem section, setting the debug option to \begin_inset Quotes eld \end_inset true \begin_inset Quotes erd \end_inset will enable a character by character debugging dump of what is going in and out of the modem. All expected values will be reported, and all settings used to configure the modem will be shown. \layout Subsection Modem defaults \layout Standard Each modem has a set of communication parameters. By default, these settings are 9600 baud, 7 data bits, even parity, and 1 stop bit. This is the default used by most TAP Paging Centrals. \layout Standard For initialization, dialing, connect, and error strings, please see the \family typewriter /etc/sendpage.cf \family default file for a full run down on each of these parameters. \layout Subsection Dialing out, area codes, and long distance \layout Standard Since each modem is attached to some phone line somewhere, it is possible for sendpage to automatically determine when dialing a Paging Central if it is considered \begin_inset Quotes eld \end_inset long distance \begin_inset Quotes erd \end_inset . To allow for this, you can specify the \family typewriter areacode \family default option for a modem, along with the \family typewriter longdist \family default option to define the prefix to dial for long distance dialing. In addition to these options, there is also the \family typewriter dialout \family default option used in situations where there is a constant prefix to get an outside dial-tone (in the case of an office phone switch, PBX, or similar). \layout Standard For example, imagine you had a single modem configured in an office building in the United States with a PBX and the only way to get an outside line was to dial \begin_inset Quotes eld \end_inset 9 \begin_inset Quotes erd \end_inset first. Say this PBX was in the 312 area code, and you had at least one PC configured in the 630 area code. To dial the 630 PC, your modem would additionally need to dial \begin_inset Quotes eld \end_inset 1 \begin_inset Quotes erd \end_inset for long distance before dialing the PC. The configuration for this modem would look something like: \layout Quotation \family typewriter [modem:hayes] \layout Quotation \family typewriter dev = /dev/ttyS0 \layout Quotation \family typewriter dialout = \begin_inset Quotes eld \end_inset 9, \begin_inset Quotes erd \end_inset \layout Quotation \family typewriter areacode = 312 \layout Quotation \family typewriter longdist = 1 \layout Standard When dialing a PC whose areacode was \begin_inset Quotes eld \end_inset 312 \begin_inset Quotes erd \end_inset and phone number was \begin_inset Quotes eld \end_inset 555-1234 \begin_inset Quotes erd \end_inset , sendpage would compare the area codes, realize the call was local, and dial \family typewriter dialout \family default + PC phone number: \begin_inset Quotes eld \end_inset 9,555-1234 \begin_inset Quotes erd \end_inset . \layout Standard When dialing a PC whose areacode was \begin_inset Quotes eld \end_inset 630 \begin_inset Quotes erd \end_inset and phone number was \begin_inset Quotes eld \end_inset 555-6789 \begin_inset Quotes erd \end_inset , sendpage would compare the area codes, realize the call was long distance, and dial \family typewriter dialout \family default + \family typewriter longdist \family default + PC area code + PC phone number: \begin_inset Quotes eld \end_inset 9,1630555-6789 \begin_inset Quotes erd \end_inset . \layout Standard Technically, you would not have to specify \begin_inset Quotes eld \end_inset \family typewriter longdist = 1 \family default \begin_inset Quotes erd \end_inset since that is the default. Since a modem's area code is unset by default, the \family typewriter longdist \family default default would have no effect. \layout Section The \family typewriter modemtest \family default tool \layout Standard Since modems (and serial ports) can be a real pain to get running correctly, there is a tool included in the sendpage package directory called \family typewriter modemtest \family default . This is a quick-and-dirty Perl script that will attempt to operate on a given serial port and talk to the modem in a way similar to how sendpage would attempt communication. \layout Standard The idea is, if \family typewriter modemtest \family default can talk to your modem, then sendpage should be able to as well. This tool was provided to help troubleshoot any problems with a serial port, the \family typewriter Device::SerialPort \family default module, or the modem itself. \layout Chapter How to add a Paging Central \layout Section What is a Paging Central? \layout Section Configurable options \layout Subsection Debugging \layout Subsection Communication settings (and modem fall-back) \layout Subsection Character control settings \layout Subsection Queue run settings \layout Chapter How to add a Recipient \layout Section What is a recipient? \layout Subsection PIN@PC style \layout Subsection Alias style \layout Section Configurable options \layout Subsection Debugging \layout Subsection \family typewriter email-cc \layout Subsection \family typewriter dest \layout Section How to test aliases \layout Chapter How to send pages \layout Section From the command line \layout Subsection Local use of \family typewriter snpp \layout Subsection Remote use of \family typewriter snpp \layout Subsubsection Making changes to \family typewriter /etc/snpp.conf \layout Subsection Third-party SNPP clients \layout Section From an email gateway \layout Subsection How \family typewriter email2page \family default works \layout Subsubsection Header rules \layout Subsubsection Body rules \layout Subsubsection Prefix, suffix, and joining strings \layout Subsection How \family typewriter sendmail2snpp \family default works \layout Subsection Example of how to configure with \family typewriter sendmail \layout Subsection Example of how to configure with \family typewriter postfix \layout Section From a web page \layout Chapter Paging Central configuration tuning guidelines \layout Section TAP quirks \layout Subsection TAP specification \layout Subsection Control characters in a page \layout Subsection Response codes from the Paging Central \layout Subsection Block sizes \layout Subsection Page limits \layout Subsection Block limits \layout Section How to figure out which of \family typewriter esc \family default , \family typewriter ctrl \family default , or \family typewriter lfok \family default to use for a PC \layout Section How to figure out \family typewriter maxchars \family default for a PC \layout Subsection Errors vs timeouts during transmission \layout Subsection Example method \layout Subsubsection Try 10 characters \layout Subsubsection Try half block limit \layout Subsubsection Try half block limit + 1 \layout Subsubsection Try block limit \layout Subsubsection Try block limit + 1 \layout Chapter Trouble shooting \layout Section Does the modem work? \layout Section Does the Paging Central work? \layout Chapter Configuration Options \layout Section sendpage.cf \layout Subsection Global \layout Subsection Modem \layout Subsection Paging Central \layout Subsection Recipient \layout Section snpp.conf \layout Section email2page.conf \the_end sendpage-1.000003/sendpage.init0000755000076500007650000000242610737241466014671 0ustar keeskees#!/bin/sh # # Startup script for Sendpage # # chkconfig: 345 85 15 # description: Sendpage sends alphanumeric pages via a modem, received \ # via SNPP. # processname: sendpage # pidfile: /var/spool/sendpage/sendpage.pid # config: /etc/sendpage.cf # ### BEGIN INIT INFO # Provides: sendpage # Required-Start: $network $syslog # Required-Stop: $network $syslog # Default-Start: 3 5 # Default-Stop: 0 1 2 4 6 # Description: Alphanumeric TAP paging daemon ### END INIT INFO # try our two common locations SENDPAGE=/usr/bin/sendpage if [ ! -x $SENDPAGE ]; then SENDPAGE=/usr/local/bin/sendpage if [ ! -x $SENDPAGE ]; then echo 'Cannot execute sendpage !' fi fi # See how we were called. case "$1" in start) echo -n "Starting sendpage: " if $SENDPAGE -bd ; then echo "done" else echo "failed" fi ;; stop) echo -n "Shutting down sendpage: " if $SENDPAGE -bs ; then echo "done" else echo "failed" fi ;; status) $SENDPAGE -bq ;; restart) $0 stop $0 start ;; reload) echo -n "Reloading sendpage: " if $SENDPAGE -br ; then echo "done" else echo "failed" fi ;; *) echo "Usage: $0 {start|stop|restart|reload|status}" exit 1 esac exit 0 sendpage-1.000003/README0000644000076500007650000003356010737241677013102 0ustar keeskeesSendpage What this tool does ------------------- Sendpage is designed to speak SNPP on one end and TAP (or UCP) on the other. It gets pages from the network via SNPP, and then uses a modem or a direct serial connection to deliver the pages to a Paging Central (or "paging terminal"). Sendpage requires, for modem use, that you know your PC's access number (which is not usually advertised by your paging provider), and you need to know the PINs of the pagers you want to deliver pages to. All of this information is known by your paging provider. If you ARE a paging provider, your job is much easier. ;) Quick Start (for RPM-based Linux distro) ---------------------------------------- Why are you reading this README? Just run "rpmbuild -ta inkscape-*.tar.gz" There is a .spec file included for easy building. :) I've only tested it on SuSE 9.2, so please send me patches if you uncover problems with other distros. Quick Start (for non-RPM Linux distro) -------------------------------------- Requirements. - Net::SNPP (part of the libnet Perl module bundle) - Device::SerialPort version 1.02+ (or Win32::SerialPort, untested) - MailTools (provides Mail::Send) Installation. - run the following commands: perl Makefile.PL make make install - copy 'sendpage.cf' into /etc, and edit to your needs - copy 'email2page.conf' into /etc, and edit to your needs - copy 'snpp.conf' into /etc, and edit to your needs - if you use SysV-style init scripts, you can use "sendpage.init" to start and stop sendpage. Copy it to where you keep your rc files. - create the user "sendpage" - figure out which group ID can write to your locks directory (usually "uucp" can write in "/var/lock") - figure out which group ID can write to your modem device (usually "tty" can write to "/dev/ttyS0") - make the queue area: mkdir -p /var/spool/sendpage chown sendpage /var/spool/sendpage chmod og-rwx /var/spool/sendpage - start the sendpage daemon as the root user (it will change user id to 'sendpage'): sendpage -bd - go check your /var/log/messages for the syslog line saying that sendpage started up: starting Queue Manager and SNPP listener (sendpage vX.X.X) - you can also use the "sendpage.init" file for SysV-style init script control. - make sure you have an entry in /etc/services for "snpp", which should be port 444. Sending a page. - run the "snpp" command to send yourself a test page: snpp -d -f me@yourdomain.org -m 'hi there!' yourPIN@yourPC - if you want to write pages directly to the paging queue instead of using SNPP, you can use "sendpage" itself, but you must be root: sendpage -f me@place.org -m 'hello again!' yourPIN@yourPC the sendpage daemon must be running for this to actually be delivered. Long Version ------------ Sendpage (the C program) was originally written in 1995 almost entirely by Mark Fullmer. Version "7" was when I became aware of it picked up the code, since Mark didn't want to maintain it any more. I developed a "configure" script to make compiling easier, started working on merging some features that other people had written, and stomping out a few bugs. I released a series called "0.8", which still had a bunch of problems, but seemed to mostly work for most people. Then after not looking at the C code for about a year, it was getting rather dusty. I was about to work on porting it up to RedHat 6.1 and realized it would be easier to redesign it from scratch. I decided to use Perl because of all the great modules I could use to help me get my job done. And then I could spread the blame around when things didn't work right. ;) And at this point I'd like to apologize here for my sloppy Perl modules. I was new to writing *modules* under Perl, so I fear that I've violated module and CPAN specs left and right. I will try to correct any of these problems as I learn about them. My final goal is to produce a set of modules that would be useful to any other packages that need to send pages directly, and could be installed via CPAN. To get it started you'll need the following Perl modules installed: - a serial port module. Either Device::SerialPort (for POSIX (unix)) -or- Win32::SerialPort (for Windoze) - Mail::MailTools - Net::SNPP You can get these from http://cpan.perl.org/ Now, build sendpage itself. Right now, just do: cd sendpage-X.X.X perl Makefile.PL make make install to install this package. The important man page (sendpage's) is done. The others are mostly just overviews. I need to finish those. If you want to put the files somewhere besides where Perl wants to put them, you can always create the Makefile is "perl Makefile.PL PREFIX=/usr/local" or where ever you want the files to be forced to go. Make sure you create a sendpage user, and identify the group who has write access to /var/lock, and the group who has write access to your modem device. Once that's working, take a look at "sendpage.cf", and configure it for your modem, paging central, etc. Turn on the debugging if you're interested. Note that "sendpage.cf" needs to live in /etc, or where ever you run "sendpage -C" with. Please note, that you can safely ignore any "Attempt to free unreferenced scalar." messages you get while shutting down sendpage if you have debugging turned on. This is just Perl complaining about the paging centrals for some reason. Hopefully these should be gone now. Install and edit the "email2page.conf" file for the email-to-page converting tool. Documentation is in the conf file for that one right now. Install and edit the "snpp.conf" file for the snpp's default server. Documentation is in the conf file for that one right now. Now there is the "sendpage" daemon itself. Run (as root) the command: sendpage -bd to start up the daemon mode. (Make sure that "sendpage" is in the PATH for the root user.) The SNPP server connections and Paging Centrals will lose root privs when the fork, so that should be safe. "sendpage -bs" will stop sendpage. The file "sendpage.init" has been included to run it as a SysV-style init script, which takes "start" and "stop" arguments. This script assumes that sendpage is installed in /usr/bin, so be sure to change it if you need to. To put a page into the queue, run "snpp RECIP" where RECIP is either a valid alias "recip" section in /etc/sendpage.cf, or has the form PIN@PC, where the PC is a valid "pc" section in /etc/sendpage.cf. It will deliver directly to the named PIN. "snpp" will read from STDIN for the message, unless you use the '-m' flag. To use "snpp" from other networked machines, you'll need to edit sendpage.cf to bind snpp to "0.0.0.0" or some other network interface, and then use "snpp -s SERVER" from the remote client. Be sure to read and set at least one "snpp-acl" line so that your remote machine will have permission to connect to the SNPP server. To force a queue run immediately, run (as root) "sendpage -q". If you have debugging turned on, it should be flooding your syslog with lots of fun information while delivering the pages. Look in the "docs" and "examples" directories for various misc information files, including the TAP specifications, the SNPP RFC, and a list of the known paging centrals in the file "PagingCentrals.txt". See if the PCs for your area are listed. If not, and you locate them, you should email them to me, and I'll update this list. There is also a searchable database of these available at the website below. For an example of how to set up sendpage as an email gateway, read "sendmail.txt". For an example of how to make a web page to send pages, look at the "sendpage.php" PHP script. Also in this directory is the beginnings of my official "Sendpage Manual" in LyX format. Logging ------- By default, sendpage uses syslog facility "daemon" to do it's reporting, with all messages set to "info" or higher. This should be seen by any sanely configured syslog daemon. For myself, I wanted to keep my sendpage syslogs out of my general syslog file ("/var/log/messages"), and I wanted debugging to be reported at the syslog level "debug". I changed the facility to "local6", and the minimum level to "debug" with the following lines in my sendpage.cf: syslog-facility = local6 syslog-minlevel = debug and changed my /etc/syslog.conf looks like this: *.info;mail.none;news.none;authpriv.none;local6.none /var/log/messages local6.* /var/log/sendpage local6.=debug /var/log/sendpage.debug local6.info /var/log/sendpage.info This way "messages" doesn't get anything from "local6" (sendpage), and I can watch the sendpage output in three ways: either everything going to local6 ("/var/log/sendpage"), JUST the stuff *at* the "debug" level ("/var/log/sendpage.debug"), or everything "info" and higher ("/var/log/sendpage.info"). Compatibility ------------- Linux: This tool (and Device::SerialPort) was written and tested under Linux. There really shouldn't be any compatibility issues under Linux. Solaris: Some headers under Solaris don't behave very well, so I had to define "__sparc" explicitly in my patch to Device::SerialPort. Also, under Solaris, many of the "make test" tests for Device::SerialPort seem to hang and/or fail. Most of these seem safe to ignore as far as using sendpage with Device::SerialPort, as the test failures seem to be with timings and/or flushes. Most of that is unreliable anyway under Linux, so sendpage has already worked around these bugs. From the following URL: http://www.stokely.com/unix.serial.port.resources/tutorials.html DTR Delay Problems: By default, Suns have a three second delay in toggling dtr. If your Sun has a zs serial port you can set the variable default_dtrlow to control the number of seconds of the delay. If the variable is set to zero, dtr can be toggled many times a second. For example, in /etc/system add the line "set zs:default_dtrlow=1" to have a 1 second delay. If your workstation has an se serial port, the /etc/system line should be "set se:se_default_dtrlow = 1". However, in initial versions of the se driver, the delay was the value of (se_default_dtrlow + 1) seconds. If you have this version of the se driver, don't set the value to -1 in /etc/system or the port will hang on open. If you need to toggle dtr quickly, you can still set the value to -1 after the terminal is opened by using adb to set the variable manually. All this is Sun bugid 4230310, fixed by patch 105924-09 or higher. The patch makes se_default_dtrlow behave like default_dtrlow (i.e. setting se_default_dtrlow to 0 will allow rapid toggling of dtr instead of once per second). So, since I have zs serial ports on my Sun, I added the line: set zs:default_dtrlow=0 so I could toggle the DTR rapidly. Also, I've seen trouble using Syslog correctly under Solaris. Once again, this appears to be Perl's fault. If it breaks ("Your vendor has not defined the Sys::Syslog macro _PATH_LOG") then just edit KeesLog.pm and remove the calls to "setlogsock". AIX: Tested with 4.3.2, and works fine, some modem lines don't work. *BSD: Tested. Carrier detection works after Sendpage 0.9.13. HPUX: Untested so far... SCO7: Install notes from Pat Gunn: Removed system perl first (MUST happen before installing skunkperl or perl will become messed up) Then installed skunkperl from skunkware (stage.caldera.com/uw7/Packages/) Used CPAN module to upgrade prerequisites (didn't allow it to rebuild Perl itself. I have GCC and other gnu tools installed) Installed Device::Serialport (after these mods, it might work through CPAN, but you're better off doing it by hand so you can troubleshoot problems more easily) Installed sendpage. Created sendpage user, created /var/lock, set its group to uucp and changed its mode to 775, created sendpage spool directory, configured sendpage.conf and snpp.conf. Note that syslog does not appear to work with sendpage's logging component, so it's probably better to log to stderr. Change snpp.conf accordingly. It might be possible to fix this with some tinkering, but I'm not interested enough to take the time. The modem device on my system (serial port A) is /dev/term/00m, although I made a symlink to there from /dev/modem, and put /dev/modem in the configfile. Porting Device::SerialPort -------------------------- If you're trying to get Device::SerialPort to work on an untested platform, please take a look at "configure.ac" for the list of files being included. Basically, SerialPort.pm needs the following constants from .h files to operate correctly: Must have these: TIOCM_RTS TIOCM_DTR TIOCMBIS TIOCMBIC TIOCMGET Optional: I have only found these on OpenBSD (and are required for OpenBSD): TIOCSDTR TIOCCDTR Optional: I have only found *both* of these under Linux: TIOCINQ TIOCOUTQ Optional: I have only found this one under Linux: TIOCSERGETLSR Once you get SerialPort.pm running happily (you can set $DEBUG=1 near the top), try modifying (from the sendpage package) the script "modemtest" in the "examples" directory to use your serial port with a modem attached. This script will try to examine all the possible settings for your vendor's serial port, and will try to talk to the modem using Device::SerialPort. If you can get this script talking to the modem, sendpage should operate just fine. Misc ---- The TODO file lists a large number of things I still want to have done. For further information, check out the sendpage web site at: http://sendpage.org/ For CVS updates, check out the sourceforge project at: http://sourceforge.net/projects/sendpage Thanks for using sendpage! -Kees Cook kees@outflux.net # $Id: README 316 2008-01-03 20:21:19Z keescook $ sendpage-1.000003/THANKS0000644000076500007650000000773310737241677013140 0ustar keeskeesThis file contains a mostly order-less list of people I have to thank for help with the Sendpage project. When I can remember what they did in particular, I've noted it. -Kees Cook , http://outflux.net/ Mark Fullmer For creating the original Sendpage and making it open source. :) Jerry Paul Taft Born Israel Kotchka 8/10/45 in Russia Died 6/21/00 in Chicago He was a camper and counselor for many years at Camp Chi in Wisconsin. He was offered a graduate fellowship in philosophy at Stanford University. He opposed the war in Vietnam. He was a co-founder of the Revolutionary Automobile Cooperative in Chicago and worked as a mechanic there. He drove a cab. He lived and worked on Kibbutz Be'eri in Israel. He struggled for almost 30 years with manic-depressive illness, struggling also to remain as independent as possible. He loved coffee and cigarettes. He helped care for his parents when they became ill. He was a faithful friend. He remained opposed to injustice and nonsense his entire life, never lost his ideals, and lived his life with integrity and without pretension. He will be missed. Karsten Hilbert Notified me of a subtle formatting problem in the modem lock files. Mark Frey Found a strange bug (in Perl?) where Modem.pm wasn't closing the device on a DESTROY. Also found where lockfiles made by minicom weren't be handled correctly, and found a bug with the -f option. Found a bug in the 8-bit to 7-bit conversion routine. Steve Szabo Let me use and abuse his Sun Blade running Solaris 8. Nailed down the DTR/Termios bug that has plagued me for a year. Randy Emler Showed me a bug in my select loop. Turned out to be a few silly typos, but finding them resulted in further debugging additions. Also, donated his postfix configuration documentation. Noah Meyerhans Helped me notice a typo in sendpage configuration defaults. Jason Ernst For totally redesigning the sendpage website to make it look great, and for making adorable little raccoon images to go with it. :) Steve Brazill Found a bug with PC logon timeouts not failing correctly. Aaron Moore Found a bug with "nobody" slipping into split pages. Jonathan C. Detert Found a bug with the "dialout" modem setting. Todd M. Lewis Suggested a change to comment parsing to allow for leading whitespace. Corrected a parsing bug in email2page. Jon Scarbrough Found a bug where modem settings were not being passed through correctly (PC default were set instead). Kendall P. Bullen Always finds my bugs as soon as I release a version. Philip Moors Found a bug in the Modem disconnect function. I wasn't actually letting go of the modem on a chat failure. John Furman Found a setgid taint bug in Modem.pm for me. Drew Smith Reported and helped me track down a numeric-only paging problem. Andy Wardley for giving me an API that I liked and adapted for my own use, even if I didn't use any of the code (AppConfig). Eric Schenk for giving me a clear example of how to create a uucp-style lock file for a serial device. (And for teaching me about "kill(0, pid)"!) tomiii for a huge list of PC information. Kurt Boyack for paging central information. Aaron Botsis for making me realize that other people besides me, do, in fact, use sendpage. Paul Holcomb for endless support and encouragement. Steve Kennedy for always having a patch ready to help me out. Jason Winget for letting me spam his pager while doing TAP testing. Steven Bishop for reporting to me that Sendpage works with AIX 4.3.2. Adrian Steiner for writing UCP support! Pat Gunn For sending me detailed SCO7 installation notes. David St. John For hunting down and testing my fix for block-spanning pages. And for donating $100 to the project! Thanks! David also funded some bug fixing work for weird SNPP hangs. Terrific! Todd T. Fries Wrote a full DBI-based pager lookup mechanism. Great! $Id: THANKS 316 2008-01-03 20:21:19Z keescook $ sendpage-1.000003/sendmail2snpp0000755000076500007650000000230410737241677014717 0ustar keeskees#!/usr/local/bin/perl # # quick script for sending pages through sendmail # # $Id: sendmail2snpp 316 2008-01-03 20:21:19Z keescook $ # # Copyright (C) 2000-2004 Kees Cook # kees@outflux.net, http://outflux.net/ # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # http://www.gnu.org/copyleft/gpl.html # to tee off the pages, use this: #$cmd="tee /tmp/page.$$ | email2page | snpp ".join(" ",@ARGV); $cmd="email2page | snpp ".join(" ",@ARGV); system($cmd); exit ($?>>8); # # This is really the same as a shell script doing the following: # # email2page | snpp "$@" # sendpage-1.000003/email2page.conf0000644000076500007650000000154010737241466015064 0ustar keeskees# This file describes how an email message will be rewritten for paging # by the 'email2page' too. # # How many lines of the body to actually filter (0=unlimited) maxlines:200 # What to start the page with (must be in quotes) prefix:"|" # What to end the page with (must be in quotes) suffix:"-STOP-" # How matched results should be joined (must be in quotes) headerjoin:"|" # How the headers and body should be joined (must be in quotes) headbodyjoin:"|" # # List of header regexs, if any. # Only headers that get matched/changed are included in the final page. header:s/^Subject:\s+(.*)/S:$1/i; header:s/^From:\s+(.*)/F:$1/i; # rewrite all headers to their first three letters in their tag #header:s/^([^:]{1,3})[^:]*:(.*)/$1:$2/; # to leave all headers intact #header:s/^(.*)/$1/; # # List of body substitutions, if any # drop all blank lines #body:s/^\s*$//g; sendpage-1.000003/lib/0000755000076500007650000000000010737242241012745 5ustar keeskeessendpage-1.000003/lib/Sendpage/0000755000076500007650000000000010737242241014473 5ustar keeskeessendpage-1.000003/lib/Sendpage/KeesLog.pm0000644000076500007650000000727010737241677016404 0ustar keeskees# # KeesLog.pm implements a logging subsystem involving syslog and/or stderr # # $Id: KeesLog.pm 316 2008-01-03 20:21:19Z keescook $ # # Copyright (C) 2000-2004 Kees Cook # kees@outflux.net, http://outflux.net/ # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # http://www.gnu.org/copyleft/gpl.html package Sendpage::KeesLog; use Sys::Syslog qw(:DEFAULT setlogsock); =head1 NAME Sendpage::KeesLog - implements a logging subsystem =head1 SYNOPSIS $log=Sendpage::KeesLog->new(); $log->on(); $log->do('crit',"Something bad happened"); $log->reconfig($config); $log->do('debug',"I'm doing things"); $log->off(); $log->do('info',"Look at me, I'm writing to stderr now"); =head1 DESCRIPTION This module is used in sendpage(1). =head1 BUGS I need to write more docs for it. =cut my %LogLevels = ( debug => 0, info => 1, notice => 2, warning => 3, err => 4, crit => 5, alert => 6, emerg => 7, ); # FIXME: can I have this thing detect if STDERR has already been closed and # kick and scream some other way? # takes parameters "Syslog" (1 or 0), "Opts", "Facility", "MinLevel" sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = {}; bless($self,$class); $self->reconfig(@_); return $self; } # restarts logging with config'd values sub reconfig { my $self = shift; my %arg = @_; $self->{SYSLOG} = $arg{Syslog}; $self->{OPTS} = $arg{Opts}; $self->{FACILITY}=$arg{Facility}; $self->{MINLEVEL}=$arg{MinLevel}; $self->{MINLEVEL}="debug" if (!defined($LogLevels{$self->{MINLEVEL}})); if (!defined($self->{SYSLOG})) { $self->{SYSLOG}=0; $self->off(); } else { $self->on() if (defined($self->{OPEN})); } } sub off { my $self=shift; if (defined($self->{OPEN})) { closelog; undef $self->{OPEN}; } } sub on { my $self=shift; $self->off(); if ($self->{SYSLOG}==1) { # Comment out the following three lines if Solaris complains # about syslog. if (!defined(setlogsock('unix'))) { setlogsock('inet'); } my $ret=openlog "sendpage", $self->{OPTS}, $self->{FACILITY}; $self->{OPEN}=1; } } # perform a logging function sub do { my($self,$pri,$format,@args)=@_; $pri=$self->{MINLEVEL} if ($LogLevels{$pri}<$LogLevels{$self->{MINLEVEL}}); # convert tabs since syslog doesn't like them $format=~s/\t/ /g; # question is: who adds the "\n"? Me or syslog? I assume me now. if (!defined($self->{OPEN})) { my $str=sprintf("%s [$$ $pri]: $format", scalar(localtime()),@args); warn $str."\n"; } else { # FIXME: shouldn't I check error codes? syslog($pri,$format,@args); } } sub DESTROY { my($self)=@_; $self->off(); } 1; __END__ =head1 AUTHOR Kees Cook =head1 SEE ALSO perl(1), sendpage(1), Sendpage::KeesConf(3), Sendpage::Modem(3), Sendpage::PagingCentral(3), Sendpage::PageQueue(3), Sendpage::Page(3), Sendpage::Recipient(3), Sendpage::Queue(3) =head1 COPYRIGHT Copyright 2000 Kees Cook. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut sendpage-1.000003/lib/Sendpage/KeesConf.pm0000644000076500007650000003222710737241677016550 0ustar keeskees# # KeesConf.pm implements a quick-and-dirty configfile parser # # $Id: KeesConf.pm 316 2008-01-03 20:21:19Z keescook $ # # Copyright (C) 2000-2004 Kees Cook # kees@outflux.net, http://outflux.net/ # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # http://www.gnu.org/copyleft/gpl.html package Sendpage::KeesConf; use Carp; =head1 NAME Sendpage::KeesConf - implements a configuration file reader =head1 SYNOPSIS use Sendpage::KeesConf; $config = Sendpage::KeesConf->new(); $config->define("variable", { DEFAULT => "setting" }); $config->file("config.cfg"); $setting=$config->get("variable"); =head1 DESCRIPTION I have borrowed VERY heavily from Andy Wardley's (abw@cre.canon.co.uk) C tool, which can be found on CPAN (http://cpan.perl.org) but I found it not dynamic enough for multi-instance variable defaults. As a result, I wrote this massively trimmed-down version for my use. The following methods are available: =over 4 =cut # off-limits chars in section names are : @ = # # -Kees # argument count types $ARGCOUNT_NONE = 0; $ARGCOUNT_ONE = 1; $ARGCOUNT_LIST = 2; #$ARGCOUNT_HASH = 3; =item $config = Sendpage::KeesConf->new(); The constructor doesn't take an arguement, but it should in the future. =cut sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = {}; # get our args my $config = shift; $self->{DEFAULTS} = undef; $self->{KNOWN} = undef; bless($self,$class); return $self; } =item $config->forget(); This call will make $config forget about any variables it has loaded. It does NOT forget Cd variables, just instantiated ones via C. =cut # forget all configurations sub dump { my $self = shift; $self->{KNOWN} = undef; $self->{SECTIONS} = undef; } =item $config->define($name, $options); This will define a variable by the name of $name. $options can contain: =over 4 =item ARGCOUNT What type of variable this should be. Default value is "1". The available types are: =over 4 =item 0 Boolean (true/false, yes/no, 1/0) =item 1 Scalar (any string) =item 2 List (an array of strings) =back =item DEFAULT The default value the variable should have if it is not overridden during the call to C. The DEFAULT must be the same data type as ARGCOUNT. The default DEFAULT is the string "". =item UNSET set this to 1 if you want the default value to be undefined. This is a hack to get around the default DEFAULT. =back =cut # define a variable sub define { my $self = shift; my ($name, $vars)=@_; my $default; $self->{DEFAULTS}->{$name}->{ARGCOUNT}=defined($vars->{ARGCOUNT}) ? $vars->{ARGCOUNT} : $ARGCOUNT_ONE; if ($self->{DEFAULTS}->{$name}->{ARGCOUNT} == $ARGCOUNT_LIST) { $self->{DEFAULTS}->{$name}->{DEFAULT}= defined($vars->{DEFAULT}) ? $vars->{DEFAULT} : undef ; } else { $self->{DEFAULTS}->{$name}->{DEFAULT}= defined($vars->{DEFAULT}) ? $vars->{DEFAULT} : ""; } undef $self->{DEFAULTS}->{$name}->{DEFAULT} if (defined($vars->{UNSET})); # warn "'$name' defined with '".$self->{DEFAULTS}->{$name}->{ARGCOUNT}. # "' and '".$self->{DEFAULTS}->{$name}->{DEFAULT}."'\n"; } =item $config->instance_exists($name); This tests to see if there is a section loaded named $name =cut # check to see if a section exists in the KNOWN space sub instance_exists { my ($self,$name)=@_; #warn "\tchecking for instance: '$name'\n"; my(%hash, $thing); foreach $thing (@{ $self->{SECTIONS} }) { $hash{$thing}=1; #warn "\t\tI have: '$thing'\n"; } return defined($hash{$name}); } =item $var=$config->ifset($name); This call will search for the variable named $name. If it is not found, it will return undef. If the value exists, it will return the value. This is a way to call "get" without having a default passed through. =cut sub ifset { my $self = shift; my ($whole)=@_; return ($self->exists($whole) ? $self->get($whole) : undef); } =item $var=$config->exists($name); This call will search for the variable named $name. If it is not found, it will return false. If the value exists, it will return true. This is a way for the user to find out if they will get a "default" on a call to "get". =cut # return a variable or default for that variable sub exists { my $self = shift; my ($whole)=@_; return (defined($self->{KNOWN}->{$whole})); } =item $var=$config->fallbackget($name,$quiet); This call will search for the variable named $name. If it is not found, the section portion will be removed, and retried for a sectionless "get" call. That way, global variables can be overridden by section-specific variables. If "SECTION:Instance@name" does not exist, "name" will be tried. =cut sub fallbackget { my $self = shift; my ($whole,$quiet)=@_; my ($class,$instance,$name,$var); #warn "trying '$whole'...\n"; $var=$self->get($whole,1); if (!defined($var)) { ($class,$instance,$name)=$self->breakdown($whole); #warn "now trying '$name'...\n"; $var=$self->get($name,$quiet); } return $var; } =item $var=$config->get($name); This call will search for the variable named $name. If it is not found, it will fall back to the default for the section. Sections are explained in more detail later. =cut # return a variable or default for that variable sub get { my $self = shift; my ($whole,$quiet)=@_; my ($name,$class,$instance,$var,@parts); # Vars can be in CLASS:Instance@variable format # knowns use the entire name, # defaults use CLASS:variable format undef $name; #warn "asking for '$whole'\n"; $value=$self->{KNOWN}->{$whole}; if (!defined($value)) { # save our original value $name=$whole; ($class,$instance,$name)=$self->breakdown($name); # reduce our variable to just class/var $whole=sprintf("%s$name",$class ? "$class:" : ""); #warn "getting default for '$whole'\n"; my $def=$self->{DEFAULTS}->{$whole}; if (!defined($def) && $class) { $def=$self->{DEFAULTS}->{"$class:"}; } if (defined($def)) { # getting classed default #warn "found default for '$whole'\n"; $value=$def->{DEFAULT}; } } if (!defined($value) && !$quiet) { croak "'$whole' not defined"; } return $value; } =item $config->instances($class); Returns an array of the names of all the variables in the class $class. =cut sub instances { my $self = shift; my($class)=@_; my @array=sort @{ $self->{SECTIONS} }; grep(s/^${class}://, @array); } =item $config->file('program.cfg'); Loads variables from the named file. Syntax for this file is: [SECTION:INSTANCE] VARIABLE1 = VALUE1 VARIABLE2 = VALUE2 . . . If VARIABLE is an array, VALUE is loaded using commas (,) as the list separator. The variable will be available under the name of the section. For example, to see VALUE2, it would be accessed as: $config->get("SECTION:INSTANCE\@VARIABLE2"); Notice, that "=", ":", and "@" are all not allowed in section or variable names. =cut # load variables from a file sub file { my $self=shift; my $filename=shift; my (@lines,@merged,$line); # for parsing, I prefer this methodology: # 1) strip all lines starting with a "#" # 2) join any lines that have a "\" as the last character # 3) drop any blank lines # 4) parse, one line at a time open(FILE,"<$filename") || die "Cannot read '$filename'\n"; @lines=grep(!/^\s*#/,); # drop any lines starting with # close(FILE); # merge any line with a trailing \ undef @merged; undef $line; while ($#lines>=0) { $line=shift @lines; chomp($line); # drop crs while ($line =~ /\\$/ && $#lines>=0) { $line.=shift @lines; } push(@merged,$line); undef $line; } @lines=grep(!/^\s*$/,@merged); # drop any blank lines my $section=""; foreach $line (@lines) { #warn "saw line '$line'\n"; my ($token,$value)=split(/=/,$line,2); # drop any white-space surrounding the token $token=~s/^\s*//; $token=~s/\s*$//; if ($token =~ /^\[([^\]]+)\]/) { $section=$1; # drop any white-space surrounding the section $section=~s/^\s*//; $section=~s/\s*$//; # clean up section name (no @s) $section=~s/\@//g; #warn "saw section '$section'\n"; if ($self->instance_exists($section)) { $main::log->do('warning', "section '$section' already defined -- merging!"); } else { push(@{ $self->{SECTIONS} },$section); } $section.="\@"; next; } # drop any white-space surrounding the value $value=~s/^\s*//; $value=~s/\s*$//; # drop any quotes (not really syntax-smart, ya know?) $value=~s/^"//; $value=~s/"$//; # add our section header $token="${section}${token}"; #warn "token: '$token' value: '$value'\n"; # now our token/values are "clean". Let's insert them # into our various structures #warn "Checking on defaults for '$token'\n"; my ($class,$instance,$name)=$self->breakdown($token); #warn "got '$class' : '$instance' \@ '$name'\n"; # reduce our variable to just class/var my $whole=sprintf("%s$name",$class ? "$class:" : ""); #warn "Checking on defaults for '$whole'\n"; my $def=$self->{DEFAULTS}->{$whole}; if (!defined($def) && $class) { $def=$self->{DEFAULTS}->{"$class:"}; #warn "tried '$class:'\n"; } if (defined($def)) { if ($def->{ARGCOUNT} == $ARGCOUNT_NONE) { if ($value=~/^[ty1]/i) { $self->{KNOWN}->{$token}=1; #warn "stored '$token' as '1'\n"; } elsif ($value =~ /^[fn0]/i) { $self->{KNOWN}->{$token}=0; #warn "stored '$token' as '0'\n"; } else { $main::log->do('warning', "value for '$token' not true/false, yes/no, 1/0"); } } elsif ($def->{ARGCOUNT} == $ARGCOUNT_ONE) { $self->{KNOWN}->{$token}=$value; #warn "stored '$token' as '$value'\n"; } elsif ($def->{ARGCOUNT} == $ARGCOUNT_LIST) { #warn "adding to '$token'\n"; my @parts=split(/[\s,]+/,$value); my $item; foreach $item (@parts) { # drop white space $item=~s/^\s*//; $item=~s/\s*$//; #warn "\t'$item'\n"; push(@{$self->{KNOWN}->{$token}},$item); } } else { $main::log->do('warning', "default for '$whole' has strange ARGCOUNT"); } } else { $main::log->do('warning',"unknown variable '$token' found in file '$filename'"); } } return 1; } # "dangerous" hack to set a variable sub set { my($self,$var,$value)=@_; $self->{KNOWN}->{$var}=$value; } # breakdown a variable name into class, instance, and variable # # input string: "CLASS:INSTANCE@NAME" where "CLASS:" is optional # and "INSTANCE@" is optional # sub breakdown { my $self=shift; my ($name)=@_; my (@parts,$class,$instance); # strip off the class, if it exists @parts=split(/:/,$name,2); $class=$parts[0]; if ($class eq $name) { undef $class; } else { #warn "class: '$class'\n"; $name=$parts[1]; } # strip off the instance if it exists @parts=split(/\@/,$name,2); $instance=$parts[0]; if ($instance eq $name) { undef $instance; } else { #warn "instance: '$instance'\n"; $name=$parts[1]; } return ($class,$instance,$name); } 1; __END__ =back Sections can be defined (and loaded) so that defaults can pass back to a defined section default. For example, lets say that you have several modems, and most of them have different settings. You can define all the modem variables like so: $config->define("modem:baud",{ DEFAULT => 9600 }); $config->define("modem:flowctl",{ DEFAULT => "hardware" }); Then, when you load them, let's say the config file has: [modem:sportster] baud = 115200 [modem:hayes] The baud rate for the sportster will come back as 115200, but the hayes will fall back during a C call, and find the default for the modem section: 9600. Both fallback to have "flowctl" as "hardware": # returns specific value 115200 $config->get("modem:sportster\@baud"); # returns default value 9600 $config->get("modem:hayes\@baud"); # both return default value "hardware" $config->get("modem:sportster\@flowctl"); $config->get("modem:hayes\@flowctl"); =head1 CAVEATS =over 4 =item character limitations As mentioned above, variable names (and section names) cannot have the characters ":", "@", or "=" in them. =item default defaults There should be a way to pass default defaults into C. That would be handy, and could eliminate the need for the UNSET option in C. =back =head1 AUTHOR Kees Cook =head1 SEE ALSO perl(1), sendpage(1), Sendpage::KeesLog(3), Sendpage::Modem(3), Sendpage::PagingCentral(3), Sendpage::PageQueue(3), Sendpage::Page(3), Sendpage::Recipient(3), Sendpage::Queue(3) =head1 COPYRIGHT Copyright 2000 Kees Cook. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut sendpage-1.000003/lib/Sendpage/PagingCentral.pm0000755000076500007650000010622410737241677017573 0ustar keeskees# # PagingCentral.pm implements the TAP protocol # # $Id: PagingCentral.pm 316 2008-01-03 20:21:19Z keescook $ # # Copyright (C) 2000-2004 Kees Cook # kees@outflux.net, http://outflux.net/ # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # http://www.gnu.org/copyleft/gpl.html package Sendpage::PagingCentral; use Sendpage::Modem; use Sendpage::KeesConf; use Mail::Send; =head1 NAME PagingCental.pm - implements the TAP protocol over the Modem module =head1 SYNOPSIS $pc=Sendpage::PagingCentral->new($config,$name); $rc=$pc->start_proto(); $rc=$pc->send($pin,$text); ... $pc->disconnect(); $rc=$pc->deliver($page); $pc->SendMail($to,$from,$cc,$errorsto,$subject,$body); =head1 DESCRIPTION This is a module for use in sendpage(1). =head1 BUGS Need to write more docs. =cut # Various return code literals $SKIP_MSG = 4; $PERM_ERROR = 3; $TEMP_ERROR = 2; $SUCCESS = 1; # Timings my @T=(undef, 2, 1, 10, 4, 8); # Retries my @N=(1, 3, 3, 3); # Sequence Codes my %SeqMajor = ( 100 => "Informational Text", 200 => "Positive Completion", 300 => "Unused", 400 => "Unused", 500 => "Negative Completion", 600 => "Unused", 700 => "Unused", 800 => "Unused", 900 => "Unused" ); my %SeqMinor = ( 110 => "Paging Terminal TAP Specification Supported", 111 => "Paging terminal is processing the previous input -- please wait", 112 => "Maximum pages enter for session", 113 => "Maximum time reached for session", 114 => "Welcome banners", 115 => "Exit Messages", 211 => "Page(s) Sent Successfully", 212 => "Long message truncated and sent", 213 => "Message accepted - held for deferred delivery", 214 => "Character maximum, message has been truncated and sent", 501 => "A time-out occurred waiting for user input", 502 => "Unexpected characters received before the start of a transaction", 503 => "Excessive attempts to send/re-send a transaction with checksum errors", 504 => "The message field of the TAP transaction contained characters, but message characters are not allowed for the Pager format. Perhaps the paging receiver for the given PIN is a 'Tone Only' pager.", 505 => "Message portion of the TAP transaction contained alphabetic characters, but alphabetics characters are not allowed for the Pager format. Perhaps the paging receiver for the given PIN is a 'numeric' pager.", 506 => "Excessive invalid pages received", 507 => "Invalid Logon attempt: incorrectly formed login sequence", 508 => "Invalid Login attempt: Service type and category given is not supported", 509 => "Invalid Login attempt: Invalid password supplied", 510 => "Illegal Pager ID - The pager ID contains illegal characters or is too long or short", 511 => "Invalid Pager ID - There is no subscriber to match this ID", 512 => "Temporarily cannot deliver to Pager ID - Try Later", 513 => "Long message rejected for exceeding maximum character length", 514 => "Checksum error", 515 => "Message format error", 516 => "Message quota temporarily exceeded", 517 => "Character maximum, message rejected" ); my $CR="\x0d"; my $LF="\x0a"; my $ESC="\x1b"; my $ACK="\x06"; my $NAK="\x15"; my $STX="\x02"; my $ETX="\x03"; my $EOT="\x04"; my $RS="\x1e"; my $US="\x1f"; my $ETB="\x17"; my $SUB="\x1a"; my $TRN="\x2f"; my $OP="\x4f"; my $RE="\x52"; sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = {}; $self->{CONFIG} = shift; $self->{NAME} = shift; $self->{MODEMS} = shift; # load config information $self->{DEBUG} = $self->{CONFIG}->get("pc:$self->{NAME}\@debug"); # TAP protocol/block handling options $self->{AnswerWait}=$self->{CONFIG}->get("pc:$self->{NAME}\@answerwait"); $self->{AnswerRetries}=$self->{CONFIG}->get("pc:$self->{NAME}\@answerretries"); $self->{CharsPerBlock}=$self->{CONFIG}->get("pc:$self->{NAME}\@chars-per-block"); $self->{MAXCHARS}=$self->{CONFIG}->get("pc:$self->{NAME}\@maxchars"); $self->{FIELDS} = $self->{CONFIG}->get("pc:$self->{NAME}\@fields"); $self->{MAXSPLITS}=$self->{CONFIG}->get("pc:$self->{NAME}\@maxsplits"); $self->{MaxPages}=$self->{CONFIG}->get("pc:$self->{NAME}\@maxpages"); $self->{MaxBlocks}=$self->{CONFIG}->get("pc:$self->{NAME}\@maxblocks"); # TAP character translation options $self->{ESC} = $self->{CONFIG}->get("pc:$self->{NAME}\@esc"); $self->{CTRL} = $self->{CONFIG}->get("pc:$self->{NAME}\@ctrl"); $self->{LFOK} = $self->{CONFIG}->get("pc:$self->{NAME}\@lfok"); # Serial characteristics $self->{Baud} = $self->{CONFIG}->ifset("pc:$self->{NAME}\@baud"), $self->{Parity} = $self->{CONFIG}->ifset("pc:$self->{NAME}\@parity"), $self->{StrictParity} = $self->{CONFIG}->ifset("pc:$self->{NAME}\@strict-parity"), $self->{Data} = $self->{CONFIG}->ifset("pc:$self->{NAME}\@data"), $self->{Stop} = $self->{CONFIG}->ifset("pc:$self->{NAME}\@stop"), $self->{Flow} = $self->{CONFIG}->ifset("pc:$self->{NAME}\@flow"); # Modem control options $self->{AreaCode}=$self->{CONFIG}->get("pc:$self->{NAME}\@areacode",1); $self->{PhoneNum}=$self->{CONFIG}->get("pc:$self->{NAME}\@phonenum"); $self->{DialWait}=$self->{CONFIG}->get("pc:$self->{NAME}\@dialwait",1); $self->{DialRetries}=$self->{CONFIG}->get("pc:$self->{NAME}\@dialretries"); # Email control settings $self->{NotifyAfter}=$self->{CONFIG}->fallbackget("pc:$self->{NAME}\@tempfail-notify-after"); $self->{FailNotify}=$self->{CONFIG}->fallbackget("pc:$self->{NAME}\@fail-notify"); $self->{MaxTempFail}=$self->{CONFIG}->fallbackget("pc:$self->{NAME}\@max-tempfail"); # Completion commands $self->{CompletionCmd}=$self->{CONFIG}->fallbackget("pc:$self->{NAME}\@completion-cmd",1); $self->{LEAD}=""; $self->{LEAD}=$CR if ($self->{CONFIG}->get("pc:$self->{NAME}\@stricttap")); # get the daemon info, allowing for fall-back $self->{PageDaemon}=$self->{CONFIG}->fallbackget("pc:$self->{NAME}\@page-daemon"); $self->{CConErr}=$self->{CONFIG}->fallbackget("pc:$self->{NAME}\@cc-on-error"); bless($self,$class); return $self; } # Clear work-tracking counters sub clear_counters { my $self=shift; # clear counters $self->{PagesProcessed}=0; $self->{BlocksProcessed}=0; } # Get a modem, init, dial, and authenticate to TAP sub start_proto { my $self=shift; my(@modems, $modem, $name, $report, $ref); # find an available modem $ref=$self->{CONFIG}->fallbackget("pc:$self->{NAME}\@modems",1); if (!defined($ref)) { @modems=@{ $self->{MODEMS} }; # use all known available } else { @modems=@{ $ref }; } # we need to make sure that we only use the modems that # we're allowed to use and that # were detected as "functioning" during startup my(%avail,@okay); # which are available? foreach $modem (@{ $self->{MODEMS} }) { $avail{$modem}=1; } undef @okay; foreach $modem (@modems) { push(@okay,$modem) if (defined($avail{$modem})); } @modems=@okay; my $config=$self->{CONFIG}; # try each modem, FIXME: should we do some sort of round-robin? foreach $name (@modems) { $modem = Sendpage::Modem->new(Name => $name, Dev => $config->get("modem:${name}\@dev"), Lockprefix => $config->get("lockprefix"), Debug => $config->get("modem:${name}\@debug"), Log => $main::log, Baud => $config->get("modem:${name}\@baud"), Parity => $config->get("modem:${name}\@parity"), StrictParity => $config->get("modem:${name}\@strict-parity"), Data => $config->get("modem:${name}\@data"), Stop => $config->get("modem:${name}\@stop"), Flow => $config->get("modem:${name}\@flow"), Init => $config->get("modem:${name}\@init"), InitOK => $config->get("modem:${name}\@initok"), InitWait => $config->get("modem:${name}\@initwait"), InitRetry => $config->get("modem:${name}\@initretries"), Error => $config->get("modem:${name}\@error"), Dial => $config->get("modem:${name}\@dial"), DialOK => $config->get("modem:${name}\@dialok"), DialWait => $config->get("modem:${name}\@dialwait"), DialRetry => $config->get("modem:${name}\@dialretries"), NoCarrier => $config->get("modem:${name}\@no-carrier"), DTRToggleTime => $config->get("modem:${name}\@dtrtime"), CarrierDetect => $config->get("modem:${name}\@carrier-detect",1), AreaCode => $config->get("modem:${name}\@areacode",1), LongDist => $config->get("modem:${name}\@longdist"), DialOut => $config->get("modem:${name}\@dialout") ); last if (defined($modem)); } # make sure we got one if (!defined($modem)) { $main::log->do('crit',"No modems available"); return (undef,"All modems presently in use"); } # Clear counters $self->clear_counters(); # Init modem my $result=$modem->init( $self->{Baud}, $self->{Parity}, $self->{Data}, $self->{Stop}, $self->{Flow}, undef, # init string: use modem default $self->{StrictParity}); if (!defined($result)) { $main::log->do('alert',"Failed to initialize modem"); return (undef,"Could not initialize modem"); } # Dial $result=$modem->dial($self->{AreaCode}, $self->{PhoneNum}, $self->{DialWait}); if (!defined($result)) { $main::log->do('crit',"Failed to dial modem"); return (undef,"Could not dial out"); } my $SST=$self->{CONFIG}->get("pc:$self->{NAME}\@proto"); # Get proto typ # Starting implementation of UCP # ------------------------------ # I did a few new routines to handle UCP: # - HandleUCPMessage # - AssambleUCPMessage # - CalcUCPChecksum # - CreateUCPMessage # - CreateUCPHeader # - TranmistUCPMessage # And I modified the following routines: # - send # - disconnect # - and this one (start_proto) # UCP doesn't need to loggon. So we just skip that for UCP if($SST ne "UCP"){ # Proto is PG1 or PG3 # wait for ID= # timeout("\r") $result=$modem->chat("\r","\r","ID=",$self->{AnswerWait}, $self->{AnswerRetries}); if (!defined($result)) { $main::log->do('crit',"PC did not send 'ID=' tag"); return (undef,"Could not perform protocol startup"); } # Try to log on # ID= # \033PG1${PASS}\r my $LEAD=$self->{LEAD}; my $LOGONretries=3; # this is protocol-defined # my $SST=$self->{CONFIG}->get("pc:$self->{NAME}\@proto"); # PG1, etc my $PASS=$self->{CONFIG}->get("pc:$self->{NAME}\@password"); # adjust the length of the password to MAKE SURE it's 6 chars if (length($PASS)>6) { $PASS=substr($PASS,0,6); } elsif (length($PASS)<6) { # should I be back-filling this password? #$PASS=sprintf("%06s",$PASS); } # supposedly, we can get a go-head here too, so we should handle it my $early_go_ahead; undef $early_go_ahead; my $logged_in=0; while (!$logged_in && $LOGONretries) { $result=$modem->chat("${ESC}${SST}${PASS}\r","", "(${LEAD}(${ACK}|${NAK}|${ESC}${EOT})${CR}|${ESC}\\[p${CR})", $T[3],$N[0]); # the N here is not spec'd if (!defined($result)) { $main::log->do('crit',"PC timed out during logon handshake"); return (undef,"Paging Central timed out during logon handshake"); } $modem->HexDump($result) if ($self->{DEBUG}); # something\rcode\r # nak: retry # ack: followed with go ahead # eot: failure # show any messages $report=$self->ReportMsgSeq($result); if ($result =~ /${ESC}\[p${CR}/) { # got an early go ahead, skip next chat $main::log->do('debug',"Got early go-ahead") if ($self->{DEBUG}); # FIXME: we're pattern matching on the entire string # instead of feeding the "leftovers" back into # the "chat" tool $logged_in=1; $early_go_ahead=1; } elsif ($result =~ /${LEAD}${ACK}${CR}/) { # Logon accepted $logged_in=1; $main::log->do('debug',"Logon success!") if ($self->{DEBUG}); } elsif ($result =~ /${LEAD}${NAK}${CR}/) { # Logon requested again $LOGONretries--; $main::log->do('debug',"Logon needs to be retried") if ($self->{DEBUG}); } elsif ($result =~ /${LEAD}${ESC}${EOT}${CR}/) { # Forced disconnected $main::log->do('crit',"PC requested immediate disconnect"); return (undef,"Immediate disconnect requested: $report"); } # make report on failure or debug $main::log->do($logged_in==1 ? 'debug' : 'crit', "proto_startup: %s",$report) if ($report ne "" && ($self->{DEBUG} || $logged_in!=1)); } if (!$logged_in) { $main::log->do('crit',"Tried to log in $LOGONretries times and failed"); return undef; } if (!defined($early_go_ahead)) { # wait for them to be done announcing crap # ${GO_AHEAD}\r # n is not spec'd here $result=$modem->chat("","","${ESC}\\[p${CR}",$T[3],$N[0]); if (!defined($result)) { $main::log->do('crit',"PC timed out during logon speech"); return (undef,"Protocol timed out"); } $modem->HexDump($result) if ($self->{DEBUG}); $report=$self->ReportMsgSeq($result); $main::log->do('debug',"go ahead: %s",$report) if ($report ne "" && $self->{DEBUG}); } } $self->{MODEM}=$modem; return (1,"Proto startup success",$SST); } sub send { my $self = shift; my ($PIN,$text) = @_; my ($report,@result,$proto); if (!defined($self->{MODEM})) { ($rc,$report,$proto)=$self->start_proto(); if (!defined($rc)) { $main::log->do('crit',"proto startup failed (%s)",$report); return ($TEMP_ERROR,$report); # temp failure } } # now we are at step 8, and we can send pages my @fields=($PIN,$text); if ($proto eq "UCP"){ # UCP has his own message-handler @result=$self->HandleUCPMessage(@fields); } else{ @result=$self->HandleMessage(@fields); } # Handle any post-processing (maxpages, etc) $self->{PagesProcessed}++; if ($self->{MaxPages}>0 && $self->{PagesProcessed}>=$self->{MaxPages}) { # shouldn't send any more pages # make a note in the logs $main::log->do('info',"Disconnecting from Paging Central: %d page limit reached.",$self->{MaxPages}); # drop the connection (don't check for errors...) $self->disconnect(); } return @result; } sub deliver { my($self,$page)=@_; my($rc, $report); my($to,$cc,$extra,$attempts); my($queuedir); $queuedir=$self->{CONFIG}->get("queuedir"); for ($page->reset(), $page->next(); defined($recip=$page->recip()); $page->next()) { # gather info from the page $attempts=$page->attempts(); $to=$page->option('from'); $cc=$recip->datum('email-cc'); # attempt to send the page ($rc,$report) = $self->send($recip->pin(),$page->text()); my $now=time; # push temp error into a perm fail if needed if ($rc == $TEMP_ERROR && $attempts > $self->{MaxTempFail}) { $rc=$PERM_ERROR; $report.="\n'Too many errors ($attempts) -- giving up.'"; } # gather the reported info $extra=""; if (defined($report) && $report ne "") { $extra="Paging Central reported:\n$report"; } # delay information if ($now < $page->option('when')) { $main::log->do('warning',"Weird. Page got delivered before it was ready to be sent."); } else { $now-=$page->option('when'); $extra=sprintf("Delivery delay: %d second%s.\n", $now,$now == 1 ? "" : "s").$extra; } if ($extra ne "") { $extra="---diagnostics---\n".$extra; } # logging info my $paged=$recip->name(); my $pc=$self->{NAME}; my $file=$page->option('FILE'); my $sender=$to; $sender="nobody" if ($sender eq ""); my $diag=""; $diag="PC=$report" if ($report ne ""); # eliminate ctrl chars in "diag" $diag=Sendpage::Modem->HexStr($diag); # call this directly my $state="unknown"; $state="Sent" if ($rc == $SUCCESS); $state="Temp-Failure" if ($rc == $TEMP_ERROR); $state="Abandoned" if ($rc == $PERM_ERROR); # log our page's state $main::log->do('info', "$pc/$file: state=$state, to=%s, from=%s, ". "size=%d%s", $paged,$sender,length($page->text()), $diag eq "" ? "" : ", $diag"); if ($rc == $SUCCESS) { # success # remove recipient from list $page->drop_recip(); # Send email notification if ($to ne "" || $cc ne "") { $self->SendMail($to, $self->{PageDaemon}, $cc, $self->{PageDaemon}, "Page delivered", "The following page was delivered to ". "$paged:\n\n" . $page->text() . "\n\n" . $extra); } # external commands... if (defined($self->{CompletionCmd})) { open(CMD,"|$self->{CompletionCmd} 1 $paged $queuedir/$pc/$file"); print CMD $page->text(); close(CMD); } } elsif ($rc == $TEMP_ERROR) { # temp failure # add page-daemon to CC possibly my $errcc=$cc; if ($self->{CConErr}) { if ($errcc eq "") { $errcc=$self->{PageDaemon}; } else { $errcc="$errcc, $self->{PageDaemon}"; } } # Send email notification if ($self->{NotifyAfter} > 0 && $attempts > 0 && ($attempts % $self->{NotifyAfter} == 0) && ($to ne "" || $errcc ne "")) { $self->SendMail($to, $self->{PageDaemon}, $errcc, $self->{PageDaemon}, "Page temporarily failed", "The following page is still trying to be delivered to " . $recip->name() . ":\n\n" . $page->text() . "\n\n" . $extra ); } } elsif ($rc == $PERM_ERROR) { # total failure # remove recipient from list $page->drop_recip(); # add page-daemon to CC possibly my $errcc=$cc; if ($self->{CConErr}) { if ($errcc eq "") { $errcc=$self->{PageDaemon}; } else { $errcc="$errcc, $self->{PageDaemon}"; } } # Send email notification if ($self->{FailNotify} && ($to ne "" || $errcc ne "")) { $self->SendMail($to, $self->{PageDaemon}, $errcc, $self->{PageDaemon}, "Page NOT delivered", "The following page has FAILED to be delivered to " . $recip->name() . ":\n\n" . $page->text() . "\n\n" . $extra ); } # external commands... if (defined($self->{CompletionCmd})) { open(CMD,"|$self->{CompletionCmd} 0 $paged $queuedir/$pc/$file"); print CMD $page->text(); close(CMD); } } else { # truely weird $main::log->do('warning',"PagingCentral: weird. Bad return code"); $main::log->do('info',"from PC: %s",$report) if ($report ne ""); } } $page->attempts(1); } sub dropmodem { my $self = shift; if (!defined($self->{MODEM})) { # already dropped return 1; } # give up the modem #$self->{MODEM}->unlock(); undef $self->{MODEM}; return 1; } sub disconnect { my $self = shift; my $report; # clear our counters $self->clear_counters(); if (!defined($self->{MODEM})) { # already disconnected return 1; } $main::log->do('debug',"PagingCentral '$self->{NAME}' disconnecting") if ($self->{DEBUG}); if($self->{CONFIG}->get("pc:$self->{NAME}\@proto") ne "UCP"){ #neither t nor n spec'd my $result=$self->{MODEM}->chat("${EOT}${CR}","","${CR}",$T[1],$N[0]); if (!defined($result)) { $main::log->do('crit',"disconnect chat failed -- continuing"); $result=1; } else { $self->{MODEM}->HexDump($result) if ($self->{DEBUG}); $report=$self->ReportMsgSeq($result); if ($result =~ /${RS}${CR}/) { $main::log->do('crit', "transaction broken"); $result=undef; } elsif ($result =~ /${ESC}${EOT}${CR}/) { $main::log->do('debug', "transcation complete") if ($self->{DEBUG}); $result=1; } $main::log->do('debug',"PagingCentral '%s' reported '%s'", $self->{NAME},$report) if ($self->{DEBUG}); # report on failure or debug $main::log->do($result!=1 ? 'crit' : 'debug', "disconnect: %s",$report) if ($report ne "" && ($self->{DEBUG} || $result!=1)); } } else{ # UCP has no loggoff sequence, so we just skip a protocol hangup } $self->dropmodem(); $main::log->do('debug',"PagingCentral '$self->{NAME}' disconnected") if ($self->{DEBUG}); return $result; } sub GenerateBlocks { my $self=shift; my @fields=@_; my(@blocks,$field,$fields,$origfield,$newfield,$chunk,$block); $fields=$#fields+1; # count fields (that many more control chars) # allow for extra fields (what was called "PET3" in old sendpage) $fields=$self->{FIELDS} if ($fields < $self->{FIELDS}); if ($self->{DEBUG}) { $main::log->do('debug', "\t\tFields to send: %s:",$fields); grep($main::log->do('debug',"\t\t\t%s",$_),@fields); } # Build a message block. Cannot exceed 256 characters. # (250 + 3 control chars + 3 checksum chars) == 256 chars) # so $self->{CharsPerBlock} == 250 normally @blocks=(); $chunk=$block=""; undef $field; while ((defined($field) && length($field)>0) || ($#fields>=0)) { if (!defined($field) || $field eq "") { $field=shift(@fields); $origfield=$field; # save a copy for the future } # warn "origfield: '$origfield'\n"; # warn "field: '$field'\n"; # pull the next char and translate and escape it if we need to my($chunk,$newfield)=$self->PullNextChar($field); # warn "chunk: '$chunk'\n"; # warn "newfield: '$newfield'\n"; if (length($chunk)+length($block)<=($self->{CharsPerBlock}-$fields)) { $block.=$chunk; # did we just exhaust a field? if (!defined($newfield) || $newfield eq "") { undef $field; # clear it for the next field $block.=$CR; # attach a CR $fields--; # drop the count of fields } else { $field=$newfield; # drop that leading char } } else { # we are now at our maximum block size # if we didn't finish the field, we need to use a # "US" marker to continue the field in the next block # if we have more blocks to send, we need to use "ETB" # if we're done sending, we send "ETX" if ($field eq $origfield) { # if $field is untouched, we're not in the # middle of a field on this block $sep=(length($field)>0 || defined($fields[0])) ? $ETB : $ETX; } else { $sep = $US; } push(@blocks,[ $block, $sep ]); $part++; # now on to the next part? $block=""; } } if (defined($block)) { # done with everything, store the final block push(@blocks,[ $block, $ETX ]); } return @blocks; } # Handling the UCP Message sub HandleUCPMessage{ my $self = shift; my($pin,$msgtext)=@_; # checking maxlenth of Text to send if (length($msgtext) > $self->{MAXCHARS}){ $main::log->do('crit',"Cannot send message!". " Message with %d chars to long.",$self->{MAXCHARS}); $self->disconnect(); } # Create the hole message for sending, including header and checksum $msg=$self->AssembleUCPMessage($pin,$msgtext); # Transmit the message ($result,$report)=$self->TransmitUCPmsg($msg); $main::log->do('info',"RETURN: %s",$result); print length($fields[1])."\n"; return ($result,$report); } # Putting the whole message together sub AssembleUCPMessage{ my $self = shift; my($pin,$msgtext)=@_; my($field, $UCPlength, $UCPChecksum, $HEADERchksum, $HEADER, $HEADERlen, $ASCIImsg, $MSG); chop($msgtext); $ASCIImsg=$self->CreateUCPMessage($msgtext); $UCPlength = length($pin) + length($ASCIImsg); ($HEADER,$HEADERlen,$HEADERchksum)=$self->CreateUCPHeader($UCPlength); $MSG = $HEADER.$pin.$TRN.$TRN.$TRN."3".$TRN.$ASCIImsg.$TRN; $UCPChecksum=$self->CalcUCPChecksum($MSG); return $MSG.$UCPChecksum; } # Calculating the UCP Checksum sub CalcUCPChecksum{ my $self = shift; my($HoleMSG)=@_; my($CHKtotal,@bytes,$CHKbin,$i,$int); @chars = split(//,$HoleMSG); foreach $char (@chars){ $CHKtotal += ord($char); } $CHKbin = sprintf("%b",$CHKtotal); push(@bytes,substr($CHKbin,length($CHKbin)-8,4)); push(@bytes,substr($CHKbin,length($CHKbin)-4,4)); undef $CHKtotal; for($i=0;$i<$#bytes+1;$i++){ $int = oct("0b".$bytes[$i]); if ($int <= 9){ $int += 48; } else{ $int += 55; } $CHKtotal.= chr($int); } return $CHKtotal; } # Translate Messagepart to ASCII sub CreateUCPMessage{ my $self = shift; my($field)=@_; my($str,$newfield,$origfield,$chunk,$chksum,$i,$length); $origfield=$field; for($i=0;$iPullNextChar($origfield); $str.=sprintf("%02X",ord($chunk)); $origfield=$newfield; } return $str; } # UCP Header sub CreateUCPHeader{ my $self = shift; my($UCPlength)=@_; my($HDtext,$HDmsg,$HDlen,$HDchksum,$totalLength); $totalLength=sprintf("%05u",($UCPlength + 22)); $HDmsg = "01".$TRN.$totalLength.$TRN."O".$TRN."01".$TRN; return $HDmsg; } sub HandleMessage { my $self = shift; my(@fields)=@_; my($i,@blocks,$block,$result,$report,$rc); my $send=undef; # new process needed here to support "maxblocks": # 1) generate full translated/escape text *first* # 2) figure out how many blocks it will take @blocks=$self->GenerateBlocks(@fields); # 3) sanity-check the "maxblocks" setting to make sure we could EVER # send the page if ($self->{MaxBlocks}>0) { if ($#blocks+1 > $self->{MaxBlocks}) { $main::log->do('crit',"HandleMessage: could NEVER send this ". "page if 'maxblocks' is %d!",$self->{MaxBlocks}); } # 4) decide if we drop the connection (enough spare blocks to send message?) elsif ($self->{BlocksProcessed}+$#blocks+1>$self->{MaxBlocks}) { $main::log->do('info',"Disconnecting from Paging Central: %d ". "block limit reached.",$self->{MaxBlocks}); $self->disconnect(); } } # 5) verify TAP connectivity (and establish if we need to, like "send") if (!defined($self->{MODEM})) { ($rc,$report)=$self->start_proto(); if (!defined($rc)) { $main::log->do('crit',"proto startup failed (%s)",$report); return ($TEMP_ERROR,$report); # temp failure } } # 6) go ahead with regular block processing $result=$SUCCESS; $report=""; foreach $block (@blocks) { my($blockbody,$blocksep)=@$block; ($result,$report)=$self->TransmitBlock($blockbody,$blocksep); if ($result == $SKIP_MSG) { return ($PERM_ERROR,$report); } elsif ($result != $SUCCESS) { return ($result,$report); } } return ($result,$report); } sub PullNextChar { my $self=shift; my($text)=@_; my($char,$left); $left=$text; # stop loops return ("","") if ($left eq ""); do { # yank the first char and encode it if need be $char=substr($left,0,1); # yank first char # FIXME: more efficient test for "end of string" if ($char ne $left) { $left=substr($left,1); # keep the rest } else { $left=""; } # drop chars to 7 bits, as required by TAP protocol if (ord($char) != (ord($char) & 0x7f)) { $main::log->do('warning',"hi-bit character reduced to 7 bits: '%s'",$char); $char=chr(ord($char) & 0x7f); } } while (!$self->CharOK($char)); # don't check empties return ("","") if ($char eq ""); # escape low chars if the PC supports it if ($self->{ESC}) { if (ord($char) < 0x20) { $char=chr(ord($char)+0x40); $char="${SUB}$char"; } } return($char,$left); } sub CharOK { my $self=shift; my($char)=@_; # # for some PCs, the TAP control chars can't be used, but all # # the others are trasmitable (in this case, they don't recognize # # the ${SUB} escape codes # my $not_allowed="($CR|$ESC|$STX|$ETX|$US|$ETB|$EOT)"; # # return undef if ($char =~ /^$not_allowed/); # # return 1; # don't bother checking empties return 1 if ($char eq ""); if (ord($char) < 0x20 && !$self->{CTRL} && !$self->{ESC} && ($char ne $LF || !$self->{LFOK})) { # be more silent about dropping $LF (e.g., for numeric pagers) $main::log->do('warning',"Dropping bad char 0x". sprintf("%02X",ord($char))) if ($char ne $LF || $self->{DEBUG}); return undef; } return 1; } # UCP is much simpler than IOX so we need a # different Transmit routine sub TransmitUCPmsg{ my $self = shift; my($block)=@_; my($result,$done,$retries,$report); if (!defined($self->{MODEM})) { $main::log->do('warning', "Yikes! The modem object disappeared!"); return ($TEMP_ERROR,"Lost modem object"); } $block=${STX}.$block.${ETX}; my $LEAD=$self->{LEAD}; $main::log->do('debug', "Block to trans (%d): ". Sendpage::Modem->HexStr($block),length($block)) if ($self->{DEBUG}); # count this block as being sent $self->{BlocksProcessed}++; undef $done; $retries=0; while (!defined($done) && $retries <= $N[2]) { # make sure the modem stays connected if (!$self->{MODEM}->ready("TransmitBlock")) { $self->dropmodem(); return ($TEMP_ERROR,"Lost modem connection"); } # transmit block here $result=$self->{MODEM}->chat($block,"", "\x0A", $T[3],1); if (!defined($result)) { $main::log->do('warning',"total block xmit failure--retrying"); $retries++; next; # restart block xmit } $self->{MODEM}->HexDump($result) if ($self->{DEBUG}); # show any messages $report=$self->ReportMsgSeq($result); $done=$SUCCESS; } # assume a temporary error unless we already know our state $done=$TEMP_ERROR if (!defined($done)); return ($done,$report); } sub TransmitBlock { my $self = shift; my($block,$sep)=@_; my($result,$done,$retries,$report); if (!defined($self->{MODEM})) { $main::log->do('warning', "Yikes! The modem object disappeared!"); return ($TEMP_ERROR,"Lost modem object"); } $block=${STX}.$block.$sep; $block=$block.$self->TAPCheckSum($block).$CR; my $LEAD=$self->{LEAD}; $main::log->do('debug', "Block to trans (%d): ". Sendpage::Modem->HexStr($block),length($block)) if ($self->{DEBUG}); # count this block as being sent $self->{BlocksProcessed}++; undef $done; $retries=0; while (!defined($done) && $retries <= $N[2]) { # make sure the modem stays connected if (!$self->{MODEM}->ready("TransmitBlock")) { $self->dropmodem(); return ($TEMP_ERROR,"Lost modem connection"); } # transmit block here $result=$self->{MODEM}->chat($block,"", "${LEAD}(${ACK}|${NAK}|${RS}|${ESC}${EOT})${CR}", $T[3],1); if (!defined($result)) { $main::log->do('warning',"total block xmit failure--retrying"); $retries++; next; # restart block xmit } $self->{MODEM}->HexDump($result) if ($self->{DEBUG}); # show any messages $report=$self->ReportMsgSeq($result); # check for answer here if ($result =~ /${LEAD}${ACK}${CR}/) { $main::log->do('debug', "block taken") if ($self->{DEBUG}); $done=$SUCCESS; } elsif ($result =~ /${LEAD}${NAK}${CR}/) { $main::log->do('debug', "retrans block needed") if ($self->{DEBUG}); $retries++; } elsif ($result =~ /${LEAD}${RS}${CR}/) { $main::log->do('debug', "skipping block") if ($self->{DEBUG}); $done=$SKIP_MSG; } elsif ($result =~ /${LEAD}${ESC}${EOT}${CR}/) { $main::log->do('crit',"immediate disconnect requested!"); $self->disconnect(); $done=$TEMP_ERROR; } } # assume a temporary error unless we already know our state $done=$TEMP_ERROR if (!defined($done)); return ($done,$report); } # calculate the 3-char checksum for a block sub TAPCheckSum { my $self=shift; my($data)=@_; my($sum,@chars,$c,@check); $sum=0; @chars=split(//,$data); foreach $c (@chars) { $sum += (ord($c) & 0x7f); # drop hi bits (shouldn't be there) } # /* the checksum is represented as 3 ascii characters having the values # between 0x30 and 0x3f */ $check[2] = chr(0x30 + ($sum & 0x0f)); $sum >>= 4; $check[1] = chr(0x30 + ($sum & 0x0f)); $sum >>= 4; $check[0] = chr(0x30 + ($sum & 0x0f)); return join("",@check); } # # ${STX}${FIELD1}\r${FIELD2}\r${ETX}${CHECKSUM}\r # # (note: pages can be broken into multiple packets, separated by "ETB") # # seq\rcode\r # nak: retry # ack: got it # rs: skip this one # eot: hang up NOW # # ${EOT}\r # something\r # seq: all good # rs: something broken # eot: goodbye sub ReportMsgSeq { my $self = shift; my($seq)=@_; my(@lines,$line,$msg,@msgs,$num,$text,$str); @lines=split(/${CR}/,$seq); undef @msgs; undef $msg; $str=""; foreach $line (@lines) { if ($line =~ /^(\d\d\d)\D/) { if (defined($msg)) { push(@msgs,$msg); } # extract the sequence msgs number $line=~/^(\d\d\d)(.*)$/; $num=$1; $text=$2; # prepend ": " if any text exists $text=": $text" if ($text !~ /^\s*$/); # decode our message if (defined($SeqMinor{$num})) { $msg="$SeqMinor{$num}$text"; } else { $msg="(undefined Sequence: $num)$text"; } } else { $msg.=$line; } } push (@msgs,$msg) if (defined($msg)); foreach $msg (@msgs) { # drop standard signalling messages $msg =~ s/($ESC(\[p|$EOT)*|$ACK|$NAK|$RS)//g; if ($msg !~ /^[\s\n\r]*$/) { $str.="'".Sendpage::Modem->HexStr($msg)."'\n"; } } return $str; } sub maxchars { my($self)=@_; $self->{MAXCHARS}; } sub maxsplits { my($self)=@_; $self->{MAXSPLITS}; } sub SendMail { my($self,$to,$from,$cc,$errorsto,$subject,$body)=@_; my($msg,$fh); $msg = new Mail::Send; if ($self->{DEBUG}) { $main::log->do('debug',"Emailing: To: '%s', Cc: '%s', ". "From: '%s', Subject: '%s'", $to,$cc,$from,$subject); } if (!defined($msg)) { $main::log->do('crit',"Cannot deliver email! Mail::Send won't start"); } else { $msg->to($to) if (defined($to) && $to ne ""); $msg->cc($cc) if (defined($cc) && $cc ne ""); $msg->set('X-Pager',"sendpage v$main::VERSION"); $msg->set('Errors-To',"<$errorsto>") if (defined($errorsto) && $errorsto ne ""); $msg->set('From',$from); $msg->subject($subject); # use mail-agent, see if the from gets passed now $fh = $msg->open($self->{CONFIG}->get("mail-agent")); if (!defined($fh)) { $main::log->do('crit',"Cannot deliver email! Mail::Send won't open -- check your 'mail-agent' setting"); } else { print $fh $body || $main::log->do('crit',"Error writing email: %s",$!); $fh->close || $main::log->do('crit',"Error closing email -- check your 'mail-agent' setting: %s",$!); } } } sub DESTROY { my $self = shift; $main::log->do('debug', "PagingCentral object '$self->{NAME}' being destroyed") if ($self->{DEBUG}); $self->disconnect(); } 1; __END__ =head1 AUTHOR Kees Cook =head1 SEE ALSO perl(1), sendpage(1), Sendpage::KeesConf(3), Sendpage::KeesLog(3), Sendpage::Modem(3), Sendpage::PageQueue(3), Sendpage::Page(3), Sendpage::Recipient(3), Sendpage::Queue(3) =head1 COPYRIGHT Copyright 2000 Kees Cook. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut sendpage-1.000003/lib/Sendpage/Queue.pm0000644000076500007650000002074510737241677016141 0ustar keeskees# # this tool handles dealing with a single queue directory # it processes *one* file at a time with a few functions # # $Id: Queue.pm 316 2008-01-03 20:21:19Z keescook $ # # Copyright (C) 2000-2004 Kees Cook # kees@outflux.net, http://outflux.net/ # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # http://www.gnu.org/copyleft/gpl.html package Sendpage::Queue; use FileHandle; my $DEBUG=0; =head1 NAME Queue.pm - implements a simple directory-based file queue =head1 SYNOPSIS $queue=Sendpage::Queue->new($dir); while ($queue->ready()) { $filename=$queue->file(); $fh=$queue->getReadyFile(); if ($can_remove_file) { $queue->fileToss(); } else { $queue->fileDone(); } } # open a new queue file $fh=$queue->getNewFile(); # ... do things to the file handle here # release the file $queue->doneNewFile(); =head1 DESCRIPTION This is a module for use in sendpage(1). =head1 BUGS Need to write more docs. =cut sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = {}; $self->{DIR}=shift; # location of my queue @{$self->{FILES}}=undef; # where to store our directory list $self->{OPEN}=undef; # current open file $self->{COUNTER}=0; # for the unique filename if (! -d $self->{DIR}) { $main::log->do('alert',"'".$self->{DIR}."' is not a directory!"); return undef; } if (! -w $self->{DIR}) { $main::log->do('alert', "Cannot write to '".$self->{DIR}."' directory!"); return undef; } if (! -r $self->{DIR}) { $main::log->do('alert', "Cannot read '".$self->{DIR}."' directory!"); return undef; } bless($self,$class); return $self; } sub file { my $self = shift; return $self->{FILES}[0]; } # is the queue ready to have files taken from it? sub ready { my $self = shift; if ($self->{OPEN}) { $main::log->do('alert', "File '".$self->{FILES}[0]."' still open while checking queue '".$self->{DIR}."' -- restarting queue!"); #return -2; } opendir(DIRHANDLE,$self->{DIR}) || $main::log->do('alert', "Cannot access '".$self->{DIR}."': $!"); my @files=readdir(DIRHANDLE); close(DIRHANDLE); grep(warn("$$: in '".$self->{DIR}."': $_\n"),@files) if ($DEBUG); @{$self->{FILES}}=grep(/^q/,@files); @files=@{$self->{FILES}}; grep(warn("$$: in FILES: $_\n"),@files) if ($DEBUG); warn "$$: ready will be: ".$#files."\n" if ($DEBUG); return $#files; } # get a file handle from the queue # handle is locked, and must be release with "fileDone" sub getReadyFile { my $self = shift; my $fh = new FileHandle; if ($self->{OPEN}) { $main::log->do('alert', "Cannot read next file from queue '".$self->{DIR}."' with open file (".$self->{FILES}[0].")!"); return undef; } warn "$$: in getReadyFile\n" if ($DEBUG); my(@filelist)=@{$self->{FILES}}; my($file)=shift @filelist; if (!defined($file)) { warn "$$: no more files in queue\n" if ($DEBUG); $main::log->do('debug',"No more files in queue") if ($main::DEBUG); return undef; } my $err="queue '$file' from '".$self->{DIR}."':"; my $fname = $self->{DIR}."/$file"; warn "$$: fname is '$fname'\n" if ($DEBUG); # create new queue files if (!-f $fname) { warn "$$: creating '$fname'\n" if ($DEBUG); open($fh,">$fname") || $main::log->do('alert', "Cannot write $err $!"); close($fh); } # open queue files read/write if (!open($fh,"+<$fname")) { warn "$$: cannot read $err $!\n" if ($DEBUG); $main::log->do('alert', "Cannot read $err $!"); # try the next file shift @{$self->{FILES}}; return $self->getReadyFile(); } if (!$self->lockFile($fname)) { warn "$$: cannot lock $err $!\n" if ($DEBUG); $main::log->do('alert', "Cannot lock $err $!"); close($fh); # try the next file shift @{$self->{FILES}}; return $self->getReadyFile(); } if (! -f $fname) { warn "$$: cannot find '$fname'\n" if ($DEBUG); # someone deleted the file while they had it locked, close($fh); # we should try for the next file in the queue shift @{$self->{FILES}}; return $self->getReadyFile(); } warn "$$: file handle is '$fh'\n" if ($DEBUG); $self->{OPEN}=$fh; return $self->{OPEN}; } # releases locks, closes file, removes file, etc sub fileToss { my($self,@args)=@_; if (!$self->{OPEN}) { $main::log->do('alert', "Cannot call fileToss without an open file!"); return undef; } # rename before unlock: no one can get it then FIXME: this is not right my $fname = $self->{DIR}."/".$self->{FILES}[0]; # my $newname=$fname; # $newname =~ s/^./X/; # # # need the queue dirs here, too # $fname=$self->{DIR}."/$fname"; # $final=$self->{DIR}."/$newname"; # if (!rename($fname,$final)) { # $main::log->do('crit', "Cannot rename '$fname' -> '$final': $!\n"); # } if (unlink($fname)<1) { $main::log->do('alert', "Could not delete file '$fname': $!"); } $self->unlockFile($fname); close($self->{OPEN}); $self->{OPEN}=undef; # drop the filename shift @{$self->{FILES}}; return 1; } # releases locks, closes file, assumes that it should stay sub fileDone { my($self)=shift; if (!$self->{OPEN}) { $main::log->do('alert', "Cannot call fileDone without an open file!"); return undef; } my $fname = $self->{DIR}."/".$self->{FILES}[0]; $self->unlockFile($fname); close($self->{OPEN}); $self->{OPEN}=undef; shift @{$self->{FILES}}; # drop the leading filename return 1; } # gets a new file handle, must be released with "doneNewFile" sub getNewFile { my($self)=shift; if ($self->{OPEN}) { $main::log->do('alert', "Cannot create new file for queue '".$self->{DIR}."' with open file (".$self->{FILES}[0].")!"); return undef; } # createUniqueName only works sanely if we don't re-instantiate # the same PagingQueue multiple times within the same process within # the same second. (Since the COUNTER would be reset to zero each # time) :( As a result, we must test for pre-existing queue filenames. my $name; do { $name = $self->createUniqueName(); } while (-f $self->{DIR}."/q".$name); unshift(@{$self->{FILES}},"Q".$name); return $self->getReadyFile(); } sub doneNewFile { my($self)=shift; my($fname,$final); if (!defined($self->{OPEN})) { $main::log->do('alert', "Cannot close new file while no file is open!"); return undef; } $fname=$self->{FILES}[0]; if ($fname !~ /^Q/) { $main::log->do('alert', "Not operating on a new Queue file"); return undef; } my $newname=$fname; $newname =~ s/^Q/q/; # need the queue dirs here, too $fname=$self->{DIR}."/$fname"; $final=$self->{DIR}."/$newname"; if (!rename($fname,$final)) { $main::log->do('crit', "Cannot rename '$fname' -> '$final': $!\n"); } # done with this handle if ($self->fileDone()) { return $newname; } return undef; } ############# # internal functions ############# # locks a single file FIXME sub lockFile { my($self,$file)=@_; $main::log->do('debug',"need to be locking '$file'") if ($main::DEBUG); return 1; } # unlocks a single file FIXME sub unlockFile { my($self,$file)=@_; $main::log->do('debug',"need to be unlocking '$file'") if ($main::DEBUG); return 1; } # locks the queue run (do we really need this?) sub lockQueue { } # unlocks the queue directory (may not need this...) sub unlockQueue { } # returns a name based on time, process id, hostname, and cycle # FIXME: USE the hostname # FIXME: if you re-instantiate the same queue within the same # second, within the same process, this will NOT produce # a unique name! Argh. sub createUniqueName { my($self)=shift; return sprintf("%010d%05d%03d",time(),$$,$self->{COUNTER}++); } 1; __END__ =head1 AUTHOR Kees Cook =head1 SEE ALSO perl(1), sendpage(1), Sendpage::KeesConf(3), Sendpage::KeesLog(3), Sendpage::Modem(3), Sendpage::PagingCentral(3), Sendpage::PageQueue(3), Sendpage::Page(3), Sendpage::Recipient(3) =head1 COPYRIGHT Copyright 2000 Kees Cook. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut sendpage-1.000003/lib/Sendpage/Db.pm0000644000076500007650000001173410737241677015400 0ustar keeskees# # this package will access databases for looking up recipients # # $Id: Db.pm 316 2008-01-03 20:21:19Z keescook $ # # Copyright (C) 2004 Todd Fries # todd@fries.net, http://FreeDaemonConsulting.com/ # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # http://www.gnu.org/copyleft/gpl.html package Sendpage::Db; use DBI; use Carp; #use strict; =head1 NAME Db.pm - encapsulates the data of a single recipient =head1 SYNOPSIS $db = Sendpage::Db->new($dsn); $db->setdb($dsn, $user, $pass, $table); $db->check("$name:$type"); $db->update("$name:$type","$value"); $db->delete("$name:$type"); =head1 DESCRIPTION This is a module for use in sendpage(1). =head1 BUGS Need to write more docs. =cut sub new { my ($class, $dsn, $user, $pass, $table) = @_; my ($self) = {}; bless $self, $class; if ( setdb($self, $dsn, $user, $pass, $table) ) { return undef; } return $self; } sub setdb { my ($self, $dsn, $user, $pass, $table) = @_; my ($rv); $self->{TABLE} = $table || "sendpage"; $self->{DSN} = $dsn; if ($user) { $self->{USER} = $user; if ($pass) { $self->{PASS} = $pass; } else { if ($self->{PASS}) { $self->{PASS} = undef; } } } else { if ($self->{USER}) { $self->{USER} = undef; } } return $self->connect; } sub connect { my ($self) = @_; my ($dsn, $user, $pass, $table, $rv); $dsn = $self->{DSN}; $user = $self->{USER} if $self->{USER}; $pass = $self->{PASS} if $self->{PASS}; $table = $self->{TABLE}; $rv = 0; if ($self->{DBH}) { $self->{DBH}->disconnect; } if ($self->{DBH} = DBI->connect($dsn, $user, $pass)) { return 0; } else { printf STDERR carp("DB connection to $dsn failed!\n"); return 1; } } sub check { my ($self, $key) = @_; my ($result); my ($sth, $table, $query, @result); $key = $self->quote($key); $table = $self->{TABLE}; $query = "select v from $table where k = $key"; $sth = $self->query($query); if (! $sth || $sth->rows < 1) { return (@result); } $result = $sth->fetchrow_array; if ($result =~ m/[\s,]/) { my @parts = split(/[\s,]+/,$result); my $item; foreach $item (@parts) { # drop white space $item=~s/^\s*//; $item=~s/\s*$//; push(@result,$item); } } else { @result = ($result); } return (@result); } sub show { my ($self) = @_; my ($sth,$key,$table,$query); $table = $self->{TABLE}; $query = "select k,v from $table"; $sth = $self->query($query); if (! $sth) { return undef; } while (($key,$val) = $sth->fetchrow_array) { print "$key\t=\t$val\n"; } $sth->finish; return 0; } sub update { my ($self,$key,$val) = @_; my ($sth,$table,$query,$rv); $table = $self->{TABLE}; $key = $self->quote($key); $val = $self->quote($val); $query = "select k,v from $table where k = $key"; $sth = $self->query($query); if (! $sth) { return undef; } $sth->finish; if ($sth->rows > 0) { $query = "update $table set v = $val where k = $key"; $sth = $self->query($query); } else { $query = "insert into $table values ($key,$val)"; $sth = $self->query($query); } if (! $sth) { return undef; } $sth->finish; return 0; } sub delete { my ($self,$key) = @_; my ($sth, $table, $query); $table = $self->{TABLE}; $key = $self->quote($key); $query = "delete from $table where k = $key"; $sth = $self->query($query); if (! $sth) { return undef; } $sth->finish; return 0; } # Now for some db helper functions, not called by external modules sub prepare { my ($self, $query) = @_; return $self->{DBH}->prepare($query); } sub quote { my ($self, $string) = @_; return $self->{DBH}->quote($string); } sub query { my ($self, $query) = @_; my ($sth, $rv); #warn "preparing [$query]\n"; $sth = $self->prepare($query); if (! ($rv = $sth->execute) ) { printf STDERR carp("[$query] failed, returned $rv\n"); return undef; } $rv = $sth->rows; if ( $rv < 0 ) { $sth->finish; printf STDERR carp("[$query] returned $rv rows\n"); return undef; } return $sth; } 1; __END__ =head1 AUTHOR Todd T. Fries =head1 SEE ALSO perl(1), sendpage(1), Sendpage::KeesConf(3), Sendpage::KeesLog(3), Sendpage::Modem(3), Sendpage::PagingCentral(3), Sendpage::PageQueue(3), Sendpage::Page(3), Sendpage::Queue(3) =head1 COPYRIGHT Copyright 2004 Todd T. Fries. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut sendpage-1.000003/lib/Sendpage/Recipient.pm0000644000076500007650000001177610737241677017003 0ustar keeskees# # this package will encapsulate the data of a single recipient # # $Id: Recipient.pm 316 2008-01-03 20:21:19Z keescook $ # # Copyright (C) 2000-2004 Kees Cook # kees@outflux.net, http://outflux.net/ # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # http://www.gnu.org/copyleft/gpl.html package Sendpage::Recipient; =head1 NAME Recipient.pm - encapsulates the data of a single recipient =head1 SYNOPSIS $recip=Sendpage::Recipient->new($config,$db,$name,$data); $alias=$recip->alias(); @dests=@{ $recip->dests() }; @data=@{ $recip->data() }; $value=$recip->datum("field"); $pin=$recip->pin(); $pc=$recip->pc(); $name=$recip->name(); =head1 DESCRIPTION This is a module for use in sendpage(1). =head1 BUGS Need to write more docs. =cut sub new { # spec for data mapping my @RecipSpec = ( "email-cc" ); my $proto = shift; my $class = ref($proto) || $proto; my $self = {}; $self->{CONFIG}= shift; # configuration my $db = shift; # db info, if using a db my $name = shift; # our look-up name my $data = shift; # ref to hash of misc info my @list; # who are all our destinations? my $list = $self->{CONFIG}->get("recip:${name}\@dest",1); if (!defined($list) && $name !~ /\@/) { # try looking in the db if it is in use if ($db) { #warn "db check ${name} dest\n"; @list = $db->check("${name}:dest"); } $list = {@list}; if (!defined($list)) { $main::log->do('debug',"No such recipient: '%s'",$name) if ($self->{CONFIG}->get("alias-debug")); return undef; } } else { @list=@{ $list }; } #warn "DESTS:\n"; #my $cow; #foreach $cow (@list) { # warn "dest: $cow\n"; #} #warn "--end-($#list)\n"; # do we exist at all? if (!defined($list) || $#list < 0) { # if we don't exist as a valid "alias" with options, # then we should allow expansion if we contain "@", # and a valid PC identifier if ($name =~ /\@/) { my @parts; @parts=split(/\@/,$name,2); $self->{PIN}=$parts[0]; $self->{PC}=$parts[1]; # verify that we have a legit PC if (!$self->{CONFIG}->instance_exists("pc:".$self->{PC})) { $main::log->do('warning', "no such PC '%s' defined for '%s'!",$self->{PC},$self->{PIN}); return undef; } # build up the destination "list" undef @list; push(@list, $name ); $list=\@list; } else { return undef; } } # are we a single entity, that isn't an alias? elsif ($#list == 0 && ($list[0]=~ /\@/ || !$self->{CONFIG}->instance_exists("recip:".$list[0]))) { # we are at the end of the line $self->{ALIAS}=0; my @parts; @parts=split(/\@/,$list[0],2); $self->{PIN}=$parts[0]; $self->{PC}=$parts[1]; # verify that we have a legit PC if (!$self->{CONFIG}->instance_exists("pc:".$self->{PC})) { $main::log->do('warning', "no such PC '%s' defined for '%s'!",$self->{PC},$self->{PIN}); return undef; } } else { $self->{ALIAS}=1; } $self->{DEST} = $list; $self->{DATA} = (); my $thing; # handle passing generics down to recipient foreach $thing (@RecipSpec) { # FIXME: clean this up my($generic,$datum); undef $generic; # first, get generic from passed in variables $generic=$data->{$thing} if defined($data->{$thing}); # next, get from specific recip entry if it exists $datum=$self->{CONFIG}->get("recip:${name}\@${thing}",1); if (defined($datum) || defined($generic)) { $self->{DATA}->{$thing}= defined($datum) ? $datum : $generic; #printf "datum '%s' is '%s'\n", # $thing, # $self->{DATA}->{$thing}; } } $self->{NAME} = $name; #warn "Recipient '$name' built\n"; bless($self,$class); return $self; } sub alias { my($self)=shift; $self->{ALIAS}; } sub dests { my($self)=shift; $self->{DEST}; } sub data { my($self)=shift; $self->{DATA}; } sub datum { my($self,$want)=@_; $self->{DATA}->{$want}; } sub pin { my($self)=shift; $self->{PIN}; } sub pc { my($self)=shift; $self->{PC}; } sub name { my($self)=shift; $self->{NAME}; } 1; __END__ =head1 AUTHOR Kees Cook =head1 SEE ALSO perl(1), sendpage(1), Sendpage::KeesConf(3), Sendpage::KeesLog(3), Sendpage::Modem(3), Sendpage::PagingCentral(3), Sendpage::PageQueue(3), Sendpage::Page(3), Sendpage::Queue(3) =head1 COPYRIGHT Copyright 2000 Kees Cook. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut sendpage-1.000003/lib/Sendpage/SNPPServer.pm0000644000076500007650000002416310737241677017022 0ustar keeskees# # implements the snppserver portions of sendpage # (large parts shamelessly stolen from Net::DummyInetd) # # $Id: SNPPServer.pm 316 2008-01-03 20:21:19Z keescook $ # # Copyright (C) 2000-2004 Kees Cook # kees@outflux.net, http://outflux.net/ # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # http://www.gnu.org/copyleft/gpl.html package Sendpage::SNPPServer; require 5.002; use strict; use vars qw(@ISA @EXPORT); use Socket 1.3; use Carp; use IO::Socket; use Net::Cmd; use Sendpage::PagingCentral; use Sendpage::PageQueue; use POSIX; @ISA = qw(Net::Cmd IO::Socket::INET); @EXPORT = (@Net::Cmd::EXPORT); # FIXME: implement a input-alarm handler that will disconnect after 1 minute # of inactivity: # 421 Timeout, Goodbye sub HandleSNPP { our $sock = shift; my $banner = shift; my $pipe = shift; my $config = shift; our $log = shift; our $DEBUG = shift; my $sigset = shift; # for unblocking the signal handlers our($pin,@PINS,$pc,$recips,$recip,@recips,$fail,$text,$caller); # how far are we in the process? our $NEED_PIN=1; our $NEED_TEXT=1; # Get our string for the peer our $peer=$sock->peerhost(); sub shutdown { $log->do('debug',"SNPP client '%s' signalled down",$peer) if ($DEBUG); $sock->command("221 administratively down"); exit(0); } # drop cnxn on signal $SIG{QUIT}=\&shutdown; $SIG{INT}=\&shutdown; $SIG{HUP}=\&shutdown; # unblock signal handlers unless (defined sigprocmask(SIG_UNBLOCK, $sigset)) { $log->do('alert',"Could not unblock signals!"); } # FIXME: use some other debug value to debug SNPP sessions #$sock->debug($DEBUG); sub reset_inputs { @PINS=(); @recips=(); $caller=$pin=$pc=$recips=$recip=$fail=$text=undef; # start off looking for a pin & text $NEED_PIN=1; $NEED_TEXT=1; } reset_inputs(); $log->do('debug',"Handling SNPP connection from %s",$peer) if ($DEBUG); # What is my hostname, for the banner? my $hostname = gethostbyaddr($sock->sockaddr, AF_INET); if ($hostname eq "") { $hostname=$sock->sockhost; } for (;;) { # Figure the RFC822 time, for fun my $now = strftime "%a, %d %b %Y %T %z", gmtime; $sock->command("220 $hostname $banner $now"); # begin the input loop while (1) { my $input=$sock->getline(); if (!defined($input)) { $log->do('info',"Lost connection from %s",$peer); return; } $sock->debug_print(0,$input) if (${*$sock}{'net_cmd_debug'}); # drop our trailing crlf $input=~s/\r?\n$//; # parse out our text my ($cmd,$args); $cmd=$args=""; ($cmd,$args)=split(/\s+/,$input,2); $cmd=~tr/a-z/A-Z/; # SNPP level 1 commands if ($cmd =~ /^QUIT/) { $sock->command("221 $hostname closing connection"); return; } elsif ($cmd =~ /^PAGE/) { my($pin,$pass)=split(/\s+/,$args,2); # collect pager ids here my @pins=($pin); # validate pager ids ($fail,@recips)=main::ArrayDig(@pins); if ($fail == 0 && $#recips > -1) { $sock->command("250 Pager ID Accepted: '$pin'"); push(@PINS,$pin); $NEED_PIN=0; } else { $sock->command("550 Error, Invalid Pager ID: '$pin'"); next; } } # includes Level 2 command "data" here elsif ($cmd =~ /^(MESS|DATA)/) { if (!$NEED_TEXT) { $sock->command("503 ERROR, Message Already Entered"); next; } if ($cmd =~ /^M/) { # SNPP v1 "MESS" collection $text=$args; } else { # SNPP v2 "DATA" collection $sock->command("354 Begin Input; End with '.'"); # collect lines my $lines=$sock->read_until_dot; $text=""; $text=join("",@$lines) if (defined($lines)); } # trans crlf into lf for pagers $text=~s/\r\n/\n/g; if ($text ne "") { $sock->command("250 Message OK"); $NEED_TEXT=0; } else { $sock->command("550 ERROR, Blank Message"); next; } } elsif ($cmd =~ /^RESE/) { reset_inputs(); $sock->command("250 RESET OK"); } elsif ($cmd =~ /^SEND/) { if ($NEED_PIN) { $sock->command("503 Error, Pager ID needed"); next; } if ($NEED_TEXT) { $sock->command("503 Error, Message needed"); next; } my $queued=$sock->write_queued_pages($pipe,$caller,$text,$config,$log,$DEBUG,@PINS); if ($queued>0) { $sock->command("250 $queued Queued Successfully (caller: '$caller')"); } else { $sock->command("554 Error, queuing failed -- contact admin"); } # reset ourselves reset_inputs(); } elsif ($cmd =~ /^HELP/) { my $line; foreach $line ( "Commands:", " PAGE [ID] - send a page to ID", " MESS [text] - attach text", " DATA - start '.'-ended text input", " SEND - send the page", " RESE - reset the input", " QUIT - hang up", " CALL [email] - email address this page is from", " HELP - this help" ) { $sock->command("214 $line"); } $sock->command("250 End of Help Information"); } # SNPP level 2 commands # 'DATA' implemented above in 'MESS' elsif ($cmd =~ /^CALL/) { if ($caller ne "") { $sock->command("503 ERROR, Caller ID already entered"); next; } # who sent this page? $caller=$args; if ($caller ne "") { $sock->command("250 Caller ID Accepted"); } else { $sock->command("550 Error, Invalid Caller ID: blank"); } } # FIXME: implement hold time #elsif ($cmd =~ /^HOLD/) { #} # SNPP level 3 commands # FIXME: find out how the TAP protocol deals with 2way # Unknown commands else { $sock->command("500 Unknown command"); } } } return 0; } sub write_queued_pages { my ($self,$pipe,$from,$text,$config,$log,$DEBUG,@PINS)=@_; my ($pc,$recips,%QPCS,$fail,@recips,$recip,$repfrom); my $queued=0; my $client; if ($self eq "Sendpage::SNPPServer") { # generic call without real connection $client="localhost"; } else { $client=$self->peerhost; } ($fail,@recips)=main::ArrayDig(@PINS); if ($fail == 0 && $#recips > -1) { # sort them into PC bins foreach $recip (@recips) { # make list of PCs push(@{ $QPCS{$recip->pc()} },$recip); } } my $mask=umask(0077); # allow only user read/write foreach $pc (sort keys %QPCS) { # get our PC-list of recipients $recips=$QPCS{$pc}; $log->do('debug',"opening queue for '%s'",$pc) if ($DEBUG); # write a queue file with associated PINs my $queue=Sendpage::PageQueue->new($config, $config->get("queuedir")."/$pc" ); if (!defined($queue)) { $log->do('err', "cannot find queue for PC '%s'",$pc); next; } my $pagetext; my @pages; @pages=(); my $pagingcentral=Sendpage::PagingCentral->new($config,$pc); if (length($text) > $pagingcentral->maxchars()) { $log->do('debug',"Splitting due to PC '%s' maxchar ". "limit: %d",$pc,$pagingcentral->maxchars()) if ($DEBUG); my($newtext,$i,$splittext); my $maxsplits=$pagingcentral->maxsplits(); my $format=length($maxsplits); my $availlen=$pagingcentral->maxchars()-($format * 2)-2; my $chunks=POSIX::ceil(length($text)/$availlen); # never send more than $maxsplits pages from one text $chunks=$maxsplits if ($chunks>$maxsplits); $splittext=$text; for ($i=0; $i<$chunks; $i++) { $newtext=sprintf("%0${format}d/%0${format}d:", $i+1,$chunks); $newtext.=substr($splittext,0,$availlen); $splittext=substr($splittext,$availlen); push(@pages,$newtext); } if ($splittext ne "") { $log->do('warning',"threw away %d extra chars at the end of a page with more than %d splits",length($splittext),$chunks); } } else { push(@pages,$text); } foreach $pagetext (@pages) { my $file; if (!defined($file=$queue->addPage(Sendpage::Page->new($recips,\$pagetext, { 'when' => time, 'from' => ($from ne "") ? $from : undef })))) { $log->do('err', "cannot send this page: queue failed"); } else { $queued++; # gather list of names we just queued my @tolist=(); my $r; # recip foreach $r (@{$recips}) { push(@tolist,$r->name()); } $repfrom=$from; $repfrom="nobody" if ($repfrom eq ""); # log our enqueuement (new word?) $log->do('info', "$pc/$file: state=Queued, to=%s, ". "from=%s(%s), size=%d", join(",",@tolist),$repfrom,$client, length($pagetext)); } } print $pipe "$pc\n" if (defined($pipe)); } umask($mask); return $queued; } sub create { my $proto = shift; my $class = ref($proto) || $proto; my %arg = @_; return $class->SUPER::new( Listen => $arg{Listen} || 20, LocalAddr => $arg{Addr}, LocalPort => $arg{Port} || "snpp(444)", Timeout => $arg{Timeout}, Proto => 'tcp', Reuse => 1 ); } 1; sendpage-1.000003/lib/Sendpage/PageQueue.pm0000644000076500007650000001107710737241677016734 0ustar keeskees# # this module uses the Queue module, but plays with Pages on top of it # # $Id: PageQueue.pm 316 2008-01-03 20:21:19Z keescook $ # # Copyright (C) 2000-2004 Kees Cook # kees@outflux.net, http://outflux.net/ # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # http://www.gnu.org/copyleft/gpl.html package Sendpage::PageQueue; # we're extending the Queue module, which is only file based, and in # the hopes that we can attach this to a better Queue module if one # ever surfaces on CPAN use Sendpage::Queue; use strict; use vars qw(@ISA); @ISA = ("Sendpage::Queue"); # other stuff use Sendpage::Page; use Sendpage::Recipient; =head1 NAME PageQueue.pm - extends the Queue module, adding the Page module smarts =head1 SYNOPSIS $pqueue=Sendpage::PageQueue($config); # read waiting pages while ($fh=$pqueue->getPage($db)) { # build up $page @stuff=$pqueue->pullPageFromFile($db,$fh); $page=Sendpage::Page->new(@stuff); # do something to change $page # write changes back to queue $pqueue->writePage($page); $pqueue->fileDone(); } # add a new page $fh=$pqueue->addPage($page); =head1 DESCRIPTION This is a module for use in sendpage(1). =head1 BUGS Obviously, needs more docs. =cut sub new { # get our args my $proto = shift; my $config = shift; # we'll need the config info my $class = ref($proto) || $proto; my $self = $class->SUPER::new(@_); $self->{CONFIG}=$config; bless($self, $class); return $self; } sub getPage { my $self = shift; my $db = shift; my $handle = $self->getReadyFile(); if (defined($handle)) { # read the data my $page=Sendpage::Page->new($self->pullPageFromFile($db,$handle)); if ($page) { $page->option('FILE',$self->file()); } return $page; } else { return undef; } } sub pullPageFromFile { my $self=shift; my $db =shift; my $fh =shift; my($line,$body,@lines,@recips,$text,%options,$recip); # rewind our file seek $fh, 0, 0; # load everything @lines=<$fh>; # clear everything! $body=0; undef @recips; undef %options; undef $text; foreach $line (@lines) { chomp($line); #print STDERR "read line '$line' "; if ($body == 1) { #warn "(body)\n"; $text.=$line."\n"; } else { if ($line =~ /^\s*$/) { # header/body break $body=1; #warn "\n"; } else { my($key, $value); ($key,$value)=split(/:\s*/,$line,2); #warn "(header: '$key' -> '$value')\n"; if ($key eq "to") { my(@parts,%data,$key,$line,$datum); undef %data; @parts=split(/,/,$value); $value=shift @parts; foreach $line (@parts) { ($key,$datum)=split(/=/,$line,2); $data{$key}=$datum; } if (defined($recip=Sendpage::Recipient->new($self->{CONFIG},$db,$value,\%data))) { push(@recips,$recip); } else { $main::log->do('warning', "bad recip: '%s'",$value); } } else { $options{$key}=$value; } } } } # rewind our file seek $fh, 0, 0; # drop last CR .... FIXME: is this right? Hm. chomp($text); return (\@recips, \$text, \%options); } sub addPage { my ($self,$page) = @_; my($rc,$filename); my $handle = $self->getNewFile(); if (!defined($handle)) { return undef; } $rc=$self->writePage($page); $filename=$self->doneNewFile(); if ($rc) { return $filename; } return $rc; } sub writePage { my($self,$page)=@_; my $handle = $self->{OPEN}; return undef if (!defined($handle)); # clear this file, just in case seek $handle, 0, 0; truncate $handle, 0; print $handle $page->dump(); return 1; } 1; __END__ =head1 AUTHOR Kees Cook =head1 SEE ALSO perl(1), sendpage(1), Sendpage::KeesConf(3), Sendpage::KeesLog(3), Sendpage::Modem(3), Sendpage::PagingCentral(3), Sendpage::Page(3), Sendpage::Recipient(3), Sendpage::Queue(3) =head1 COPYRIGHT Copyright 2000 Kees Cook. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut sendpage-1.000003/lib/Sendpage/Page.pm0000644000076500007650000001174510737241677015731 0ustar keeskees# # this package will encapsulated the data of an actual page to be # send, including all the recipients. # # $Id: Page.pm 316 2008-01-03 20:21:19Z keescook $ # # Copyright (C) 2000-2004 Kees Cook # kees@outflux.net, http://outflux.net/ # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # http://www.gnu.org/copyleft/gpl.html package Sendpage::Page; =head1 NAME Sendpage::Page - encapsulates the data of an actual page =head1 SYNOPSIS This module gets used in sendpage(1). =head1 DESCRIPTION $page=Sendpage::Page->new(\@recipients, \$text, \%options); $data=$page->dump(); # storable dump of page data if ($page->deliverable()) { for ($page->reset(), $page->next(); $recip=$page->recip(); $page->next()) { $text=$page->text(); # get text of page $page->drop_recip(); # discard current recipient } $page->attempts(1); } $anyone_left=$page->has_recips(); $attempts=$page->attempts(); $page->option('from',"someone else"); # set option named 'from' $from=$page->option('from'); # get option named 'from' $page->option('from',$from,1); # delete option named 'from' =head1 BUGS This needs more docs. =cut sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = {}; my $o=$_[0]; my $w=$_[1]; my $t=$_[2]; #warn "Page: 1: $o 2: $w 3: $t\n"; $self->{RECIPS}=$o; # deref the recipients reference list $self->{TEXT}=${ $w }; # text of the page $self->{DATA}=$t; # hash of delivery options # dump what we just loaded #warn "loading page with:\n\ttext: '".$self->{TEXT}."'\n"; #foreach $key (sort keys %{ $self->{DATA} }) { # warn "\toption: $key -> ".$self->{DATA}->{$key}."\n"; #} #foreach $recip (@{$self->{RECIPS}}) { # warn "\trecip: $recip\n"; #} # our "internal" counters $self->{DATA}->{'attempts'}+=0; $self->{ACTIVE}=undef; # which recipient is active (array loop) bless($self,$class); return $self; } # return the text sub text { my($self)=shift; return $self->{TEXT}; } # we need a way to loop through all the recipients. # I think I'm going to borrow from the PHP-style of array stepping sub reset { my($self)=shift; $self->{ACTIVE}=-1; } # which recip is up next? sub next { my($self)=shift; $self->{ACTIVE}+=1; } # show a recipient sub recip { my($self)=shift; #warn "returning RECIP: ".$self->{ACTIVE}."\n"; return $self->{RECIPS}->[$self->{ACTIVE}]; } # do we have any recips left? sub has_recips { my($self)=shift; return defined($self->{RECIPS}->[0]); } # drop a recipient (total failure or success) sub drop_recip { my($self)=shift; splice(@{ $self->{RECIPS} }, $self->{ACTIVE}, 1); # need to drop the ACTIVE counter, don't I, so the next "next" # will work... $self->{ACTIVE}--; } # is the page deliverable? sub deliverable { my($self)=shift; # right now, we can support the "when to schedule" option, # but in theory, we should be able to extend this to anything # else we can think of. return 1 if (time >= $self->{DATA}->{'when'}); return undef; } sub option { my($self,$opt,$value,$delete)=@_; if (defined($value)) { if (defined($delete) && $value eq $self->{DATA}->{$opt}) { delete $self->{DATA}->{$opt}; } else { $self->{DATA}->{$opt}=$value; } } $self->{DATA}->{$opt}; } sub attempts { my($self,$inc)=@_; $inc=0 if (!defined($inc)); $self->{DATA}->{'attempts'}+=$inc; } sub dump { my($self)=@_; my($str,$recip,$key); $str=""; for ($self->reset(), $self->next(); defined($recip=$self->recip()); $self->next()) { my(@list); undef @list; push(@list,$recip->name()); if (defined($recip->data())) { foreach $key (keys %{ $recip->data() }) { push(@list,"${key}=".$recip->datum($key)); } } $str.="to: ".join(",",@list)."\n"; } foreach $key (sort keys %{ $self->{DATA} }) { $str.="$key: ".$self->{DATA}->{$key}."\n"; } $str.="\n".$self->{TEXT}."\n"; return $str; } 1; __END__ =head1 AUTHOR Kees Cook =head1 SEE ALSO perl(1), sendpage(1), Sendpage::KeesConf(3), Sendpage::KeesLog(3), Sendpage::Modem(3), Sendpage::PagingCentral(3), Sendpage::PageQueue(3), Sendpage::Recipient(3), Sendpage::Queue(3) =head1 COPYRIGHT Copyright 2000 Kees Cook. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut sendpage-1.000003/lib/Sendpage/Modem.pm0000644000076500007650000004665710737241677016130 0ustar keeskees# # Modem.pm extends the Device::SerialPort package, and adds a few things # # $Id: Modem.pm 316 2008-01-03 20:21:19Z keescook $ # # Copyright (C) 2000-2004 Kees Cook # kees@outflux.net, http://outflux.net/ # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # http://www.gnu.org/copyleft/gpl.html package Sendpage::Modem; use POSIX; use IO::Handle; use Sendpage::KeesLog; # FIXME: Hey! Duh! I should use the OS auto-discovery system to get the # right serial device module here! use Device::SerialPort; @ISA = ("Device::SerialPort"); =head1 NAME Sendpage::Modem.pm - extends the Device::SerialPort package =head1 SYNOPSIS $modem=Sendpage::Modem->new($params); $modem->init($baud,$parity,$data,$stop,$flow,$str); $modem->ready($functionname); $modem->dial($areacode,$phonenumber,$timeout); $modem->chat($send,$resend,$expect,$timeout,$retries,$dealbreaker, $carrier); $modem->hangup(); $str=Sendpage::Modem->HexStr("tab:\t cr:\r"); =head1 DESCRIPTION This is a module for use in sendpage(1). =head1 BUGS This needs more docs. =cut # globals my $SPEED=10; # arbitrary: how much to speed up the char reader timeout # new methods here are: # init - inits modem # dial - dials number (returns like "write") # hangup - hangs up modem # new modem # takes: # modem parameters # sub new { # local vars my($lockfile,$realdev,$pid); # get our args my $proto = shift; my %arg = @_; my $name = $arg{Name}; my $dev = $arg{Dev}; my $lockprefix=$arg{Lockprefix}; my $debug = $arg{Debug}; my $log = $arg{Log}; if (!defined($log)) { $log = new Sendpage::KeesLog(Syslog => 0); } # sanity check our config options if (!defined($lockprefix)) { $log->do('alert',"Modem '$name' has no lockprefix defined"); undef $log; return undef; } if (!defined($dev) || $dev eq "/dev/null") { $log->do('alert',"Modem '$name' has no device defined"); undef $log; return undef; } # We need to build the name of the lock file $lockfile=$lockprefix; # FIXME: I need clarification on this: should we discover the # true name of the device or not? ## figure out what the REAL device name is #if (!defined($realdev=readlink($dev))) { # # not a symlink $realdev=$dev; #} # now, chop the name of the dev off my @parts=split(m#/#,$realdev); $lockfile.=pop(@parts); # $lockfile should now be in the form "/var/lock/LCK..ttyS0" $log->do('debug', "Locking with '$lockfile' ...") if ($debug); while (!defined(sysopen(LOCKFILE, "$lockfile", O_EXCL | O_CREAT | O_RDWR, 0644))) { if ($! == EEXIST) { if (defined(sysopen(LOCKFILE, "$lockfile", O_RDONLY))) { # read pid chomp($line=); close(LOCKFILE); $pid=-1; if ($line=~/^\s*(\d+)/) { $pid=$1; } # whoa: we need to clear this undef $!; if ($pid>0) { kill 0, $pid; if ($! == ESRCH) { # pid does not exist, remove the lock $log->do('debug', "Modem '$name': stale lockfile from PID $pid removed"); unlink("$lockfile"); next; } } elsif ($pid<0) { $log->do('warning', "Modem '$name': malformated lockfile being removed"); unlink("$lockfile"); next; } # allow PID '0' from the "lockfile" program # to exist indefinitely. } # cannot touch lockfile $log->do('warning',"Modem '$name': '$dev' is locked by process '$pid'"); undef $log; return undef; } else { $log->do('alert',"Modem '$name': cannot access lockfile '$lockfile': %s",$!); undef $log; return undef; } } # we have the lock file now print LOCKFILE sprintf("%10d\n",$$); close(LOCKFILE); # handle inheritance? my $class = ref($proto) || $proto; my $self = $class->SUPER::new($dev); # this should be SerialPort my $ref; if (!defined($self)) { $log->do('crit',"Modem '$name': could not start Device::Serial port: %s",$!); unlink $lockfile; undef $log; return undef; } # save our stateful information $self->{MYNAME} =$name; # name of the modem $self->{LOCKFILE} = $lockfile; # where our lockfile is $self->{DEBUG} = $debug; # debug mode? $self->{INITDONE} = 0; # we have not run "init" # internal buffer for 'chat' $self->{BUFFER} = ""; bless($self, $class); # Do Device::SerialPort capability sanity checking if (!$self->can_ioctl()) { $log->do('crit',"Modem '$name' cannot do ioctl's. Did 'configure' run correctly when you built sendpage?"); # get rid of modem $self->unlock(); undef $log; undef $self; } # grab config settings my $index; foreach $index (qw(Baud Parity StrictParity Data Stop Flow Init InitOK InitWait InitRetry Error Dial DialOK DialWait DialRetry NoCarrier CarrierDetect DTRToggleTime AreaCode LongDist DialOut)) { if (defined($arg{$index})) { $self->{$index} = $arg{$index}; $log->do('debug',"Modem '$name' setting '$index': '". $self->{$index}."'") if ($self->{DEBUG}); } } $self->{LOG} = $log; # get the log object return $self; } # init settings and send init string # takes: # baud, parity, data, stop, flow, init str sub init { my $self = shift; my($baud,$parity,$data,$stop,$flow,$str,$strict_parity) = @_; $name="Modem '$self->{MYNAME}'"; if (!defined($self->{LOCKFILE})) { $self->{LOG}->do('crit',"init: $name not locked"); return undef; } $baud = $self->{Baud} unless ($baud); $parity = $self->{Parity} unless ($parity); $data = $self->{Data} unless ($data); $stop = $self->{Stop} unless ($stop); $flow = $self->{Flow} unless ($flow); $str = $self->{Init} unless ($str); $strict_parity = $self->{StrictParity} unless ($strict_parity); my $ok = $self->{InitOK}; my $initwait=$self->{InitWait}; my $initretries=$self->{InitRetry}; # sanity check our config options if (!defined($baud)) { $self->{LOG}->do('alert', "$name has no baud rate defined!"); return undef; } if (!defined($parity)) { $self->{LOG}->do('alert', "$name has no parity defined!"); return undef; } if (!defined($data)) { $self->{LOG}->do('alert', "$name has no data bits defined!"); return undef; } if (!defined($stop)) { $self->{LOG}->do('alert', "$name has no stop bits defined!"); return undef; } if (!defined($flow)) { $self->{LOG}->do('alert', "$name has no flow control defined!"); return undef; } # if (!defined($str)) { # $self->{LOG}->do('alert', "$name has no init string defined!"); # return undef; # } # pass various settings through to the serial port $self->alias($self->{MYNAME}); my $baud_set=$self->baudrate($baud); $self->{LOG}->do('debug', "baud requested: '$baud' baud set: '$baud_set'") if ($self->{DEBUG}); if ($baud ne $baud_set) { $self->{LOG}->do('alert', "$name failed to set baud rate!"); return undef; } my $parity_set=$self->parity($parity); $self->{LOG}->do('debug', "parity requested: '$parity' parity set: '$parity_set'") if ($self->{DEBUG}); if ($parity ne $parity_set) { $self->{LOG}->do('alert', "$name failed to set parity!"); return undef; } # Make sure we're backward compatible with Win32 if ($self->can("stty_inpck") && $self->can("stty_istrip")) { if ($strict_parity) { $self->stty_inpck(1); $self->stty_istrip(1); } else { $self->stty_inpck(0); $self->stty_istrip(0); } } my $data_set=$self->databits($data); $self->{LOG}->do('debug', "databits requested: '$data' databits set: '$data_set'") if ($self->{DEBUG}); if ($data ne $data_set) { $self->{LOG}->do('alert', "$name failed to set databits!"); return undef; } my $stop_set=$self->stopbits($stop); $self->{LOG}->do('debug', "stopbits requested: '$stop' stopbits set: '$stop_set'") if ($self->{DEBUG}); if ($stop ne $stop_set) { $self->{LOG}->do('alert', "$name failed to set stopbits!"); return undef; } my $flow_set=$self->handshake($flow); $self->{LOG}->do('debug', "flow requested: '$flow' flow set: '$flow_set'") if ($self->{DEBUG}); if ($flow ne $flow_set) { $self->{LOG}->do('alert', "$name failed to set flow control!"); return undef; } # set a char timeout for modem commands $self->read_char_time(0); # avg time between read char $self->read_const_time(1000/$SPEED); # delay between calls if ($self->{DTRToggleTime} != 0) { # hang up just in case $self->{LOG}->do('debug', "reseting DTR ...") if ($self->{DEBUG}); # force the dtr down $self->dtr_active(0); select(undef,undef,undef,$self->{DTRToggleTime}); $self->dtr_active(1); } else { $self->{LOG}->do('debug',"skipping DTR toggle ...") if ($self->{DEBUG}); } # make sure the RTS is up $self->{LOG}->do('debug', "forcing RTS ...") if ($self->{DEBUG}); $self->rts_active(T); my $result = undef; # allow for blank inits (direct attaches) if ($str eq "") { $self->{LOG}->do('debug',"skipping init string ...") if ($self->{DEBUG}); $result=1; } else { # send the init string through $self->{INITDONE}=1; # frame this to let chat work $result=$self->chat("$str\r","$str\r",$ok,$initwait, $initretries, $self->{Error},"off"); $self->{INITDONE}=0; # disable again } if (defined($result)) { $self->{INITDONE}=1; } return $result; } sub ready { my $self=shift; my $func=shift; if (!defined($self->{LOCKFILE})) { $self->{LOG}->do('crit',"$func: Modem '$self->{MYNAME}' not locked"); return undef; } if (!$self->{INITDONE}) { $self->{LOG}->do('crit',"$func: Modem '$self->{MYNAME}' not initialized"); return undef; } return 1; } # FIXME: implement dial retries sub dial { my $self=shift; my $dial_areacode=shift; my $dial_num=shift; my $dialwait=shift; my $dialretries=shift; return undef unless $self->ready("dial"); my $modem_dial = $self->{Dial}; my $modem_areacode = $self->{AreaCode}; my $modem_longdist = $self->{LongDist}; my $modem_dialout = $self->{DialOut}; $dialwait = $self->{DialWait} if (!defined($dialwait)); $dialretries = $self->{DialRetry} if (!defined($dialretries)); # allow for blank dial strs (direct attaches) if ($modem_dial eq "") { $self->{LOG}->do('debug',"skipping dial ...") if ($self->{DEBUG}); return 1; } if (!defined($dial_num) || $dial_num eq "") { $self->{LOG}->do('err',"Nothing to dial (no phone number)"); return undef; } my $actual_num=""; my $report=""; if (defined($dial_areacode) && defined($modem_areacode)) { if ($dial_areacode != $modem_areacode) { $actual_num=$modem_longdist.$dial_areacode; $report="LongDist: '$modem_longdist' "; $report.="PCAreaCode: '$dial_areacode' "; } else { $report="(Not LongDist) "; } } else { # add the area code anyway $actual_num=$dial_areacode; if (defined($dial_areacode)) { $report="(No Modem AreaCode) "; $reprot.="PCAreaCode: '$dial_areacode' " } } # we always need to end the dialing with the phone number... $actual_num.=$dial_num; $report.="Num: '$dial_num'"; if ($modem_dialout ne "") { $report="DialOut: '$modem_dialout' ".$report; } $self->{LOG}->do('debug',"Calling with %s",$report) if ($self->{DEBUG}); return $self->chat($modem_dial.$modem_dialout.$actual_num."\r","", $self->{DialOK},$dialwait,1, $self->{NoCarrier},"off"); } sub safe_write { my ($self,$text) = @_; my($textlen,$written); if (!defined($self->{LOCKFILE})) { $self->{LOG}->do('crit',"safe_write: Modem '$self->{MYNAME}' not locked"); return undef; } $textlen=length($text); do { $written=$self->write($text); if (!defined($written)) { $self->{LOG}->do('crit',"write totally failed"); return undef; } elsif ($written != $textlen) { $self->{LOG}->do('warning',"write was incomplete!?! retrying..."); $text=substr($text,$written); } if ($self->{DEBUG}) { $self->{LOG}->do('debug',"wrote: %d %s", $written, $self->HexStr(substr($text,0,$written))); } $textlen-=$written; } while ($textlen>0); return 1; } # FIXME: more docs here # This function examines a stream and interacts like "expect" to find and # respond to strings, using regular expressions. # Args: # send: what to immediately send now # kicker: what to send after a timeout waiting for the expected text # expect: what to look for (perl regexp) # timeout:time in seconds to wait for the "expect"ed text # retries:how many times to send the kicker and restart the timeout # dealbreaker:a regexp that indicates total failure (NO CARRIER, etc) # carrier:should the carrier detect signal on the modem # be ignored during this chat, or use DSR? ("on","off", "dsr") sub chat { my $self = shift; my ($send,$kicker,$expect,$timeout,$retries,$dealbreaker, $carrier)=@_; my ($avail,$got); return undef unless $self->ready("chat"); $carrier=$self->{CarrierDetect} if (!defined($carrier)); $got=$self->{BUFFER}; if ($self->{DEBUG}) { $self->{LOG}->do('debug',"\tto send: %s",$self->HexStr($send)); $self->{LOG}->do('debug',"\twant: %s",$self->HexStr($expect)); $self->{LOG}->do('debug',"\tkicker: %s",$self->HexStr($kicker)); $self->{LOG}->do('debug',"\ttimeout: $timeout retries: $retries"); $self->{LOG}->do('debug', "\thave: %s",$self->HexStr($got)); } # useful variables: # $got contains the full text of chars read # $avail is what we JUST read #LOOP: # send initial text # start retry loop # start timeout loop while reading chars # try to read char # check for sucess # end loop # send kicker # end loop # send initial text no matter what if ($send ne "" && !defined($self->safe_write($send))) { $self->{LOG}->do('alert',"safe_write failed!"); } if ($expect eq "") { $self->{LOG}->do('debug',"chat defaulted to success: no 'expect' regexp"); return ""; } # initial check for sucess if ($got =~ /($expect)/) { my $matched=$1; my $upto=$`.$1; $self->{BUFFER}=$'; # keep right of match $self->{LOG}->do('debug',"chat success: %s",$self->HexStr($matched)) if ($self->{DEBUG}); return $upto; } if (defined($dealbreaker) && $got =~ /($dealbreaker)/) { my $matched=$1; my $upto=$`.$1; $self->{BUFFER}=$'; # keep right of match $self->{LOG}->do('debug',"chat failure: %s",$self->HexStr($matched)) if ($self->{DEBUG}); return undef; } # up our timeout to tenths $timeout*=$SPEED; # start retry loop my $tries; for ($tries=0; $tries<$retries; $tries++) { # send kicker (unless this is the first time through) if ($kicker ne "" && $tries>0) { $self->{LOG}->do('debug', "timed out, sending kicker") if ($self->{DEBUG}); if (!defined($self->safe_write($kicker))) { $self->{LOG}->do('alert',"safe_write failed!"); } } # start timeout loop while reading chars my $timeleft; for ($timeleft=0; $timeleft<$timeout; $timeleft++) { # do carrier check my $has_carrier=$self->carrier($carrier); if (!$has_carrier) { $self->{LOG}->do('warning', "lost carrier during chat"); # modem no longer valid $self->{INITDONE}=0; return undef; } # try to read char ($cnt,$avail)=$self->read(255); if ($cnt > 0) { $self->{LOG}->do('debug', "$cnt seen: %s",$self->HexStr($avail)) if ($self->{DEBUG}); $got.=$avail; $self->{LOG}->do('debug', "have: %s",$self->HexStr($got)) if ($self->{DEBUG}); } elsif ($self->{DEBUG}) { my $msg=sprintf("(timeout: %d/%d, retries: %d/%d)", $timeleft/$SPEED,$timeout/$SPEED, $tries,$retries); $self->{LOG}->do('debug', "%s", $msg) if (($timeleft % $SPEED) == 0); } # check for sucess if ($got =~ /($expect)/) { my $matched=$1; my $upto=$`.$1; $self->{BUFFER}=$'; # keep right of match $self->{LOG}->do('debug', "chat success: %s",$self->HexStr($matched)) if ($self->{DEBUG}); return $upto; } if (defined($dealbreaker) && $got =~ /($dealbreaker)/) { my $matched=$1; my $upto=$`.$1; $self->{BUFFER}=$'; # keep right of match $self->{LOG}->do('debug', "chat failure: %s",$self->HexStr($matched)) if ($self->{DEBUG}); return undef; } } } # failure $self->{LOG}->do('debug', "chat failed") if ($self->{DEBUG}); return undef; } # what is the state of the carrier bit? sub carrier { my $self=shift; my $way=shift; # "on", "off", or "dsr" if (!defined($self->{LOCKFILE})) { $self->{LOG}->do('crit',"carrier: Modem '$self->{MYNAME}' not locked"); return undef; } return 1 if ($way =~ /off/i); if ($way =~ /on/i) { my $ModemStatus = $self->modemlines; return (($ModemStatus & $self->MS_RLSD_ON) == $self->MS_RLSD_ON); } if ($way =~ /dsr/i) { my $ModemStatus = $self->modemlines; return (($ModemStatus & $self->MS_DSR_ON) == $self->MS_DSR_ON); } $self->{LOG}->do('crit',"carrier: Modem '$self->{MYNAME}' unknown carrier check '$way'"); return undef; } # drop the carrier if it's there sub hangup { my $self=shift; if (!defined($self->{LOCKFILE})) { $self->{LOG}->do('crit',"hangup: Modem '$self->{MYNAME}' not locked"); return undef; } if ($self->{CarrierDetect}!~/off/i && $self->carrier($self->{CarrierDetect})) { $self->{LOG}->do('debug',"toggling DTR to hang up Modem '$self->{MYNAME}'") if ($self->{DEBUG}); $self->pulse_dtr_off(500); } return 1; } # give up everything sub unlock { my $self=shift; if (!defined($self->{LOCKFILE})) { $self->{LOG}->do('crit',"unlock: Modem '$self->{MYNAME}' not locked"); return undef; } $self->hangup(); if (defined($self->{LOCKFILE})) { $self->{LOG}->do('debug',"unlocking Modem '$self->{MYNAME}'") if ($self->{DEBUG}); unlink($self->{LOCKFILE}); undef $self->{LOCKFILE}; } } # what happens when we get destroyed sub DESTROY { my $self = shift; # since I call "close", a weird double-destroy happens, need these # for final logging #my $log=$self->{LOG}; #my $name=$self->{MYNAME}; #my $debug=$selft->{DEBUG}; $self->{LOG}->do('debug',"Modem Object '$self->{MYNAME}' being destroyed") if ($self->{DEBUG}); $self->unlock() if (defined($self->{LOCKFILE})); # call parent destructor $self->SUPER::DESTROY; $self->{LOG}->do('debug',"Modem Object '$self->{MYNAME}' destroyed") if ($self->{DEBUG}); } # extra bits... sub HexDump { my($self,$text)=@_; my $str=$self->HexStr($text); $self->{LOG}->do('debug', "len %d: %s",length($text),$str); } sub HexStr { my $self = shift; my($text)=@_; my(@chars,$i,$str); $str=""; if (defined($text)) { @chars=split(//,$text); foreach $i (@chars) { if ($i !~ /^[\040-\176]$/) { $str.=sprintf("{0x%02X}",ord($i)); } else { $str.=$i; } } } else { $str.="-undef-"; } return $str; } 1; __END__ =head1 AUTHOR Kees Cook =head1 SEE ALSO perl(1), sendpage(1), Sendpage::KeesConf(3), Sendpage::KeesLog(3), Sendpage::PagingCentral(3), Sendpage::PageQueue(3), Sendpage::Page(3), Sendpage::Recipient(3), Sendpage::Queue(3) =head1 COPYRIGHT Copyright 2000-2003 Kees Cook. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut sendpage-1.000003/examples/0000755000076500007650000000000010737242241014015 5ustar keeskeessendpage-1.000003/examples/modemtest0000755000076500007650000000733110737241677015764 0ustar keeskees#!/usr/local/bin/perl # # This tool is used to check how Device::SerialPort is behaving on # your machine. It will list all the possible values for each function # as it runs. Edit this tool to test various settings. # # $Id: modemtest 316 2008-01-03 20:21:19Z keescook $ # # Copyright (C) 2000-2004 Kees Cook # kees@outflux.net, http://outflux.net/ # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # http://www.gnu.org/copyleft/gpl.html use Device::SerialPort; print "Module loaded\n"; # Edit this for your serial port. (I was testing under Solaris...) $port=new Device::SerialPort("/dev/modem") || die "new: $!\n"; print "Port open\n"; # Are the ioctls loaded? $bool=$port->can_ioctl(); print "can ioctl: ",($bool ? "Yes" : "No"),"\n"; if (!$bool) { die "The rest of this test is useless without ioctl methods.\n"; } # Handshaking @handshakes=$port->handshake; print "Handshakes:\n"; grep(print("\t$_\n"),sort(@handshakes)); $handshake=$port->handshake("none"); print "Port handshake: $handshake\n"; # Baud rate @bauds=$port->baudrate; print "Bauds:\n"; grep(print("\t$_\n"),sort(@bauds)); $baudrate=$port->baudrate("9600"); print "Port baud: $baudrate\n"; # Databits @databits=$port->databits; print "Databits:\n"; grep(print("\t$_\n"),sort(@databits)); $databits=$port->databits(8); print "Port databits: $databits\n"; # Parity @parity=$port->parity; print "Parity:\n"; grep(print("\t$_\n"),sort(@parity)); $parity=$port->parity("none"); print "Port parity: $parity\n"; # Stopbits @stopbits=$port->stopbits; print "Stopbits:\n"; grep(print("\t$_\n"),sort(@stopbits)); $stopbits=$port->stopbits(1); print "Port stopbits: $stopbits\n"; # Flip on DTR and RTS $dtr=$port->dtr_active(1); $rts=$port->rts_active(1); print "Port DTR ($dtr) and RTS ($rts) activated\n"; $rts=$port->rts_active(0); print "Flipped RTS to off ($rts) (pausing for 5 seconds)\n"; sleep 5; $rts=$port->rts_active(1); print "Flipped RTS to on ($rts)\n"; # Flip OFF dtr $dtr=$port->dtr_active(0); print "Flipped DTR to off ($dtr) (pausing for 5 seconds)\n"; sleep 5; $dtr=$port->dtr_active(1); print "Flipped DTR to on ($dtr)\n"; # Just in case: reset our timing and buffers $port->lookclear(); $port->read_const_time(100); $port->read_char_time(5); # Read a chunk sleep 1; ($count,$str)=$port->read(1); $cnt=$count; while ($count>0) { ($count,$got)=$port->read(1); $str.=$got; $cnt+=$count; } print "read: $cnt\n"; print "-:$str:-\n"; # Write an INIT to the modem ($count)=$port->write("ATZ\r"); print "written: $count\n"; # Read a chunk sleep 1; ($count,$str)=$port->read(1); $cnt=$count; while ($count>0) { ($count,$got)=$port->read(1); $str.=$got; $cnt+=$count; } print "read: $cnt\n"; print "-:$str:-\n"; # and another sleep 1; ($count,$str)=$port->read(1); $cnt=$count; while ($count>0) { ($count,$got)=$port->read(1); $str.=$got; $cnt+=$count; } print "read: $cnt\n"; print "-:$str:-\n"; # and one more sleep 1; ($count,$str)=$port->read(1); $cnt=$count; while ($count>0) { ($count,$got)=$port->read(1); $str.=$got; $cnt+=$count; } print "read: $cnt\n"; print "-:$str:-\n"; # close the port undef $port; print "Port closed\n"; sendpage-1.000003/examples/configure-pc0000755000076500007650000000716410737241466016344 0ustar keeskees#!/usr/bin/perl use strict; use warnings; select STDOUT; $|=1; # TODO: make options my $PC="skytel"; my $PAGER="osdl"; my $CFG_REAL="/etc/sendpage.cf"; my $CFG_TEST="/tmp/sendpage.cf"; # TODO: use real tmp file name my %cfg; # hash contained PC config under test # Uses globals $CFG_REAL, $CFG_TEST, and %cfg sub update_PC_config { print "Updating sendpage test configuration...\n"; my %config = %cfg; # build a copy of the hash my $line; open(INPUT,"<$CFG_REAL") || die "$CFG_REAL: $!\n"; open(OUTPUT,">$CFG_TEST") || die "$CFG_TEST: $!\n"; # write everything until PC line while (($line=) !~ /^\[pc:$PC\]$/) { print OUTPUT $line; } print OUTPUT $line; # write PC line # process the parameters while (($line=) !~ /^\[/) { chomp($line); if ($line=~/^(\S+)\s*=\s*(.*)$/) { my ($name,$value)=($1,$2); # If the setting is different, set it if (defined($config{$name}) && $value ne $config{$name}) { $line="$name = $config{$name}"; delete $config{$name}; } } print OUTPUT $line,"\n"; } # Print remaining settings foreach my $name (sort keys %config) { print OUTPUT "$name = $config{$name}\n"; } # print the next [] section print OUTPUT $line; # Write the rest of the file while ($line=) { print OUTPUT $line; } close(INPUT); close(OUTPUT); } sub stop_sendpage { system("sendpage -bs >/dev/null 2>&1"); } sub restart_sendpage { stop_sendpage(); system("sendpage -C $CFG_TEST -bd >/dev/null 2>&1"); } sub send_test_page { my ($msg,$num)=@_; my $pretty="\t--- test page $num ---\n".$msg; $pretty=~s/\n/\n\t/g; $pretty.="\n\t--- end page $num ---"; open(PAGE,"|snpp -n $PAGER") || die "snpp: $!\n"; print PAGE $msg; close(PAGE); print "Sending page ...\n$pretty\n"; } sub get_yes_no { print "[Y/n]: "; my $answer; while ($answer=) { chomp($answer); return 1 if ($answer=~/^([yY].*|$)/); return 0 if ($answer=~/^\s*[nN]/); } die "STDIN closed!\n"; } sub Done { print "Your configuration adjustment should be:\n"; foreach my $name (sort keys %cfg) { print "$name = $cfg{$name}\n"; } stop_sendpage(); exit(0); } my $msg; my $okay; ############################## update_PC_config(); restart_sendpage(); $msg="0:test page"; send_test_page($msg,0); print "Did test page 0 get delivered and look correct?\n"; $okay = get_yes_no(); if ($okay) { print "Good. You can send simple pages at least. :)\n"; } else { print "That's not good. Better get sendpage configured correctly.\n"; stop_sendpage(); exit(1); } ############################## # Start by turning on esc'd characters via the 1.8 TAP spec $cfg{'esc'}="true"; update_PC_config(); restart_sendpage(); $msg="1:This\nis a CR and this\tis a tab."; send_test_page($msg,1); print "Did test page 1 get delivered and look correct?\n"; $okay = get_yes_no(); if ($okay) { print "You're done! Congratulations, you have a 1.8 TAP PC!\n"; Done(); } delete $cfg{'esc'}; ############################## $cfg{'ctrl'}="true"; update_PC_config(); restart_sendpage(); $msg="2:This\nis a CR and this\tis a tab."; send_test_page($msg,2); print "Did test page 2 get delivered with *both* the CR and tab shown correct?\n"; $okay = get_yes_no(); if ($okay) { print "You're done! Your PC lets control characters through.\n"; Done(); } else { delete $cfg{'ctrl'}; print "Did the CR show up correctly?\n"; $okay = get_yes_no(); if ($okay) { $cfg{'lfok'}='true'; print "You're done! You have an LF-okay PC!\n"; Done(); } } print "You're done! Sorry, your PC doesn't allow control characters. :(\n"; Done(); # TODO: Do length tests, but that tends to be pager-specific sendpage-1.000003/FEATURES0000644000076500007650000000116410737241467013353 0ustar keeskees Implemented ----------- UCP communication TAP communication Daemonized queue manager Multiple modem support Modem sharing Multiple Paging Centrals Parallel Paging Central delivery Dynamic reconfiguration Modem-level debugging Paging Central-level debugging Pager alias support Pager alias-level debugging Syslog notification Progress logging Email notification on success/failure SNPP Server SNPP Client Direct-queue Client DBI support for pager database Partially Implemented --------------------- Delayed delivery (needs SNPP "HOLD" implementation) To Be Implemented ----------------- Character based translation tables sendpage-1.000003/Changes0000644000076500007650000002367610737241677013524 0ustar keeskees$Id: Changes 316 2008-01-03 20:21:19Z keescook $ 1.0.3: (2008-01-03) - drop RFC documentation -- the license isn't free 1.0.2: (2006-06-16) - fixed time-out code to handle endless failure situation - fixed recipient bleed-through across SEND/RESE in SNPP - fixed queue filename collision - added minimal "use" testing - corrected version number reporting on banner 1.0.1: (2005-06-10) - fixed version report (backport from 1.1.0) - fixed reload failures (backport from 1.1.0) 1.0.0: (2005-02-23) - changed how email2page handles the header regex list. This will allow each rule to match each header line, rather that just the first match. Recommended by Erastus Z Allen. - changed syslog-facility default to "daemon". To keep prior behavior, set "syslog-facility = local6". - created syslog-minlevel with default of "info". To keep prior behavior, set "syslog-minlevel = debug". - creating a "stable" version since I'll never reach 1.0.0 :) - updated copyright years - added older debian rules to CVS - created sendpage.spec from many sources - handling "strict parity" from Device::SerialPort - added "chars-per-block" config to the sendpage.cf examples - fixed field-spanning block code bug noticed by David St. John - added DBI support from Todd T. Fries - found evil SNPP hang bug, funded by David St. John - added few more comments, and renamed functions more sensibly - fixed up calls to KeesLog to not allow format strings to show up (thanks to Peter Smith for persisting with his queries about this) 0.9.14: (31May2002) - oops! I recreated an earlier bug where splits would destroy the page text for the next person in an alias. Fixed in SNPPServer.pm - corrected 8-bit CRC bug, thanks to Mark Frey - added UCP support, thanks to a great patch from Adrian Steiner 0.9.13: (04Nov2001) - added parameters and code for completion command execution. - fixed lock-file parsing to handle minicom's strange format. - allowed pid "0" to exist in lockfiles - wasn't including Sys::Hostname. whoops! - changed "ignore-carrier" to "carrier-detect" multi-selection - FINALLY found the DESTROY bug with Modem.pm. - Requires Device::SerialPort 0.12 now (goes with DESTROY bug) 0.9.12: (17Jul2001) - changed Modem.pm to deal with vmin problems under Solaris - fixed bug in email2page where bodies weren't being chopped correctly - "fixed" Modem.pm to close the device opened by Device::SerialPort - corrected the format of the PID printing in the lock file 0.9.11: (15May2001) - added "maxlines" to email2page. - changed (once again) how I reap dead children. (sendpage) - added the ability to write directly to the queues. (sendpage) - updated documentation slightly, some fixes in SNPP module for direct writing. 0.9.10: (20Apr2001) - tempfail-notify-after was going off on the very first temp failure, if it was set at all. (PagingCental.pm) - ignore-carrier was not boolean. (sendpage) - claim shutdown source correctly on children. (sendpage) - removed closed pipes from select set correctly (typos). (sendpage) - closed the RIGHT file descriptors. (typos, sendpage) - added "debug-snpp" for SNPP debugging. (sendpage) - added "debug-select" for select-loop debugging. (sendpage) - added Randy Emler's postfix instructions to docs/. - increased accept queue to 20 in SNPPServer.pm - I've wrapped the signal blocker around everything but the select now. I'm worried this might do bad things, but I haven't been able to prove it yet. Hmm. 0.9.9: (06Mar2001) - improved call-out debugging in Modem.pm - corrected pet3 handling, added the 'proto' option to PCs - allow whitespace to preceed comments in sendpage.cf - fixed "dialout" typo in Modem.pm - changed the email-sending error messages - fixed bug with empty "from" getting filled to "nobody" on splits - improved sendpage.init to be a little more flexible - added "Errors-To" field for outbound email - fixed bug in "PC logon handshake timeout" in PagingCentral.pm - added page-daemon on CC for any failures, if configured - added new "fallbackget" for KeesConf to pull PC-overridden variables - added "cc-on-error" to both global and PC sections to control if page-daemon will actually get CC's of failed pages - implemented "maxpages" variable to force PC to hang up after handling a certain number of pages - implemented "maxblocks" variable to force PC to hang up after transmitting a certain number of blocks - added email debugging to PagingCentral.pm - whoops: fixed total lack of "snpp-addr" working - fixed bug in SNPPServer.pm during unexpected loss of socket - implemented "/etc/snpp.conf" file for snpp defaults - fixed bug with "esc=true" for PagingCentral.pm - added "disabled" notice to the "-bq" output - added proper signal handler control during forks - fixed bug with hanging SNPP clients during a HUP - fixed "fail-notify" to be a boolean - added "fail-notify", "tempfail-notify-after", and "max-tempfail" to PC section, allowing for global fall back - added first pass at IP address Access Control Lists - wrote a simple PHP script to demo CGI-style delivery - updated README to reflect changes in docs directory 0.9.8: (30Jan2001) - further updates to Device::SerialPort and associated documentation - further updates to signal handlers - fixed bug in queue directory ownership during creation - added script to push email through email2page into snpp for sendmail - added page queue/delivery logging - fixed bug in expanding multiple pins sent to SNPP server - fixed error handler for calls into SNPPServer->create - added an init-script 0.9.7: (23Jan2001) - implemented level 2 SNPP server - added SNPP config options - added "enabled" flag to PCs - added areacode handling on modems and PCs - corrected some queue file logic - added immediate notification of pages coming from SNPP clients - added parent-death awareness - corrected USR1 handling in PC queue runners - implemented priv dropping for spawned children (no more craziness with setgid/setuid perl settings, etc) - created SNPP client - created email-parsing tool (with config file) - fixed signal handler inheritance bug 0.9.6: (12/31/2000) - ported KeesLog.pm's "new" function to use a hash (and fixed calls) - ported Modem.pm's "new" function to use a hash (and fixed calls) - started work on the SNPP Server functions - added documentation on how to port Device::SerialPort - added additional debugging to modem settings - created "dtrtime" to allow the DTR toggling timeout to be config'd - added logic to fully allow a "directly attached" modem to work - fixed a few small typos and other nonesense - improved handling of dropped-carrier conditions - changed device locking behavior to act like minicom and pppd 0.9.5: (09/27/2000) - hopefully fixed queue lock-up bug. if not, it'll warn now instead of looping forever. - added portability support for Solaris, and hopefully for AIX and HPUX. (and SCO, too.) - misc typos and corrections - fixed bug where modem settings weren't being passed to the modem when dialing a PC (PC defaults were being used) - started to correct problems with Device::SerialPort under other OS's. - move kicker section of "chat" in Modem.pm to the front, so kickers won't be sent during the last timeout cycle. - created an "answerretries" variable for the Paging Central to match "answerwait" for some slow Paging Centrals. 0.9.4c: (07/28/2000, argh: deadly typo. I should be more careful) - fixed a stupid typo in Modem.pm 0.9.4b: (07/28/2000, another point-release due to serious bug fixes) - made sendpage fail with exit codes on bad pager aliases - smooshed setgid taint removed in Modem.pm - smooshed modem disconnect chat failure buf in Modem.pm - queue manager announces it's version (for people sending debugging) 0.9.4a: (07/13/2000, point-release due to serious numeric-only paging bug) - created the "lfok" option and corrected the ctrl-char detection code. This bug made paging numeric pagers impossible because of the trailing LF. - fixed some typos in the documentation 0.9.4: (06/30/2000) - email notification completed - error reporting cleaned up further - temporary failures will eventually time out - added a bunch of documentation - converted all the package names to be correct - fixed a bug with alias expansion across multiple PCs - changed how we listen for children (now a wait loop, not SIGCHLD) - fixed up modem ioctl checking - allowed any PIN@PC as recipient if the PC exists - fixed the stupid damn error I've ever seen with Perl. The ExtUtils::MakeMaker doesn't let you have imbedded PODs inside .pm files that have "config" or "setup" in their name. STUPID! I had to change KeesConfig to KeesConf. I guess it has a nicer ring to it, but that's just plain dumb. 0.9.3: (06/21/2000) - created the logging subsystem and migrated to it - working more issues out of the TAP implementation - more and more error checking - started the framework for email support - created Makefile.PL and started adding pod documentation 0.9.2: (06/17/2000) - queue manager fully implemented and tested. should be stable. - debugging cleaned up: one line at a time, prep'ing for syslog. - continued misc bug fixes. 0.9.1: (06/14/2000) - queue manager written and mostly functioning. - Device::Serial buffer-flushing bug worked around. - various improvements and bug fixes to TAP implementation. - various improvements to config engine. - tested with 2 more PCs. Seems totally fine. 0.9.0: (04/27/2000) - initial public release of new perl version (0.9.x+ tree). - TAP protocol implemented and tested. - configuration engine implemented and tested. - I registered a Sourceforge account. - Code is FAR from finished, but it at least controls the modem correctly (one hard part), has a configuration engine (another hard part), and speaks TAP within most specs that I can find. And I've tested it with a whole *2* paging centrals. :) sendpage-1.000003/TODO0000644000076500007650000001021010737241677012675 0ustar keeskees# $Id: TODO 316 2008-01-03 20:21:19Z keescook $ Things that need to be done... Next ---- *Code - have PCs shutdown on signal if active, but not sending a page. - clean up stale/running test during BecomeDaemon - option to not bounce "user not found" errors (to stop domain scanning) - have a "quiet" startup mode *Docs - finish sendpage-manual - add info about "snpp-acl" - find counting error for tempfail - document sendpage-db - document DBI schema Next Next --------- - implement a list of addresses for SNPP server to listen on (for multihomed) - do something so that queues that are waiting on a modem will run as soon as the modem is available. How can I do this when there are multiple modems available? Weird multi semaphore notifications queues based on modems? Gah. Strange. - implement "dialretries" (attempt dialing again for PC) - implement character-based translation tables - create more fallback PC config options (temp-fail, etc) Would Be Nice ------------- *Code - implement SNPP "HOLD" function - add warnings when seeing {0x00} chars in modem buffers. Possible incorrect comm settings 8N1 vs 7E1, etc. - do something about the 500 functions packed into "sendpage" instead of cleanly implemented in modules elsewhere. - implement email notification fall-back for recips - have all the conf files get handled by Makefile.PL -- /etc dir? - change PagingCentral module to deal with modems differently so we don't have to use a KeesConf object. - change Recipient module to deal with macros differently so we don't have to use a KeesConf object. - figure out how to do filelocking under Windows - figure out what to replace my Syslog calls with under Windows - add "include" verb to sendpage.cf for sucking in other files? This allows for separate files for PCs and recip lists. - implement "fieldsplits" (fields cannot be split across blocks for PC) - handle calculating parity as described by Shaun Dawson (is this needed after the parity improvements in Device::SerialPort?) - TCP modems (open a socket instead of a file) by Jim Gottlieb - generalize the "xmit message" code to deal with whatever type of sending we're doing, TAP, UCP, SNPP, etc. (from Jon Meek) - figure out what the cryptic message "ESC EOT hangups" meant, and fix it *Docs - update API doc to reflect changes made for PageQueue, Page, and Recipient - more general in-code commenting & documentation - more API documentation for all the modules via POD - write a data flow graph Stuff I Thought Of, But Will Most Likely Never Need --------------------------------------------------- - implement multiple TAP protocols? (are there real differences? PG1, PG3, etc) I don't think I'll ever need this due to the "pc:fields", and "pc:proto" variables. I'm Probably Never Going To Implement These Crazy Ideas ------------------------------------------------------- - if you have A LOT of pages, you'd want multiple modems running for a single PC. Since that would REALLY be a lot of pages, I'm going to leave out the idea of parallel tasking modems for the same PC. That seems like insane overkill, but it's something that would be pretty cool for HIGH volume paging centers. Some day, perhaps. Detailed Notes -------------- Parity - Shaun Dawson After a long struggle, I think I've figured out what's going on here, and have fixed it. Even though the serial port is being set to even parity, I don't believe that the parity calculations are being done by the system. I believe that both the incoming and outgoing parity calculations will need to be done inside sendpage. I added a function to AND out the 8th bit in the incoming byte stream, and I stopped getting garbage from any of the paging terminals, but some subset of the ones I had problems with didn't see the 0x0D byte I sent, and so didn't return 'ID='. This led me to conclude that some of the paging terminals set to even parity just did the same thing that I did, which was to AND out the parity bit (and those worked fine), but some others discarded bytes with invalid parity. Since 0x0D has an odd parity, that byte was discarded, and sendpage was never able to negotiate with those terminals. sendpage-1.000003/LICENSE0000644000076500007650000004311010737241467013214 0ustar keeskees GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. sendpage-1.000003/META.yml0000644000076500007650000000075410737242242013457 0ustar keeskees# http://module-build.sourceforge.net/META-spec.html #XXXXXXX This is a prototype!!! It will change in the future!!! XXXXX# name: sendpage version: 1.000003 version_from: sendpage installdirs: site requires: DBI: 0 Device::SerialPort: 1.0 Mail::Send: 1.08 Net::SNPP: 1.1 Test::More: 0 distribution_type: module generated_by: ExtUtils::MakeMaker version 6.30_01 sendpage-1.000003/email2page0000755000076500007650000001066310737241677014155 0ustar keeskees#!/usr/local/bin/perl # # tool designed to re-write emails into pages # # $Id: email2page 316 2008-01-03 20:21:19Z keescook $ # # Copyright (C) 2000-2004 Kees Cook # kees@outflux.net, http://outflux.net/ # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # http://www.gnu.org/copyleft/gpl.html =head1 NAME email2page - converts RFC822 email text into text suitable for paging =head1 SYNOPSIS email2page [-C CONF] [-h] =head1 OPTIONS =over 4 =item -C CONF Read the configuration file CONF instead of /etc/email2page.conf for the rewriting rules. =item -h Display a summary of all the available command line options (and there sure aren't many). =back =head1 DESCRIPTION This tool is used to break down an email into a shortened version, using a configurable set of rewriting rules, found in /etc/email2page.conf. email2page reads STDIN, and writes to STDOUT. Any errors will be reported on STDERR. It was designed to be used with 'sendpage'. =head1 AUTHOR Kees Cook =head1 BUGS All the bugs with the program will probably come from the config file, as several of the items are run with Perl's 'eval' statement. Please see the documentation in the /etc/email2page.conf file. =head1 COPYRIGHT email2page is free software; it can be used under the terms of the GNU General Public License. =head1 SEE ALSO perl(1), sendpage(1), Mail::Internet(3) =cut use Getopt::Std; use Mail::Internet; use Mail::Header; use IO::File; my %opts; my $VERSION="0.1"; sub Usage { die "Usage: $0 [OPTIONS] version $VERSION Parses an email message based on the values of the conf file. Reads stdin and produces results to stdout. -C CONF read CONF instead of /etc/email2page.conf -h you're reading it already. :) "; } my $maxlines=0; # how many lines to process of the body my $prefix=""; # prefix written to page text my $suffix=""; # suffix written to page text my $headerjoin="|"; # how to join header tags in the page text my $headbodyjoin="\n"; # how to join the header and body section my @headrules=(); # rules for handling header tags my @bodyrules=(); # rules for handling body text # get our options if (!getopts('hC:',\%opts) || $opts{h}) { Usage(); } $opts{C}="/etc/email2page.conf" unless ($opts{C}); $fh = new IO::File $opts{C}, "r"; if (!defined($fh)) { die "Cannot read '$opts{C}' file: $!\n"; } $num=0; foreach $line (<$fh>) { chomp($line); $num++; # skip comments and blanks next if ($line =~ /^\s*#/ || $line =~ /^\s*$/); ($cmd,$arg)=split(/:/,$line,2); if ($cmd eq "headerjoin") { $headerjoin=$arg; } elsif ($cmd eq "headbodyjoin") { $headbodyjoin=$arg; } elsif ($cmd eq "header") { push(@headrules,$arg); } elsif ($cmd eq "body") { push(@bodyrules,$arg); } elsif ($cmd eq "prefix") { $prefix=$arg; } elsif ($cmd eq "suffix") { $suffix=$arg; } elsif ($cmd eq "maxlines") { $maxlines=$arg; } else { warn "unknown command '$cmd' in '$opts{C}', line $num\n"; } } undef $fh; # read in our email message my $mail=Mail::Internet->new(\*STDIN); my $head=$mail->head(); my $body=$mail->body(); @body=@$body; # trim the body down to "maxlines" if ($maxlines > 0 && $#body > $maxlines) { splice @body, $maxlines; } my @okheads=(); # handle rewriting the headers foreach $tag ($head->tags()) { foreach $index (0 .. ($head->count($tag)-1)) { $matched=0; $text="$tag: ".$head->get($tag,$index); chomp($text); foreach $rule (@headrules) { if (eval "\$text =~ $rule") { $matched=1; } } if ($matched) { push(@okheads,$text); } } } # handle rewriting the body foreach $rule (@bodyrules) { foreach $index (0 .. $#body) { eval "\$body[$index] =~ $rule"; } } $result=eval $prefix; $result.=eval "join($headerjoin,\@okheads)"; $result.=eval $headbodyjoin; $result.=join("",@body); $result.=eval $suffix; print $result; sendpage-1.000003/sendpage.spec0000644000076500007650000001255010737241467014655 0ustar keeskees# # This spec file was originally generated by cpan2rpm v2.019 # For more information please visit: http://perl.arix.com/ # %define pkgname sendpage %define filelist %{pkgname}-%{version}-filelist %define maketest 1 # Be sure to change both version numbers -- perl has dumb package numbering %define pkgversion 1 %define rpmversion 1.0.0 %define namever %{pkgname}-%{pkgversion} %define maketest 0 # Install bits %define user sendpage %define spool /var/spool/sendpage name: sendpage summary: sendpage - listen for pages via SNPP, and send pages via modem epoch: 1 version: %{rpmversion} release: 1 vendor: Kees Cook packager: Arix International license: GPL group: Applications/CPAN url: http://sendpage.org/ buildroot: %{_tmppath}/%{name}-%{version}-%(id -u -n) buildarch: noarch source: %{namever}.tar.gz Requires: perl perl-Device-SerialPort >= 1.0.2 perl-Net-SNPP perl-MailTools BuildRequires: perl perl-Device-SerialPort >= 1.0.2 perl-Net-SNPP perl-MailTools %description Sendpage is designed to speak SNPP on one end and TAP (or UCP) on the other. It gets pages from the network via SNPP, and then uses a modem or a direct serial connection to deliver the pages to a Paging Central (or "paging terminal"). Sendpage requires, for modem use, that you know your PC's access number (which is not usually advertised by your paging provider), and you need to know the PINs of the pagers you want to deliver pages to. All of this information is known by your paging provider. If you ARE a paging provider, your job is much easier. ;) # # This package was originally generated with the cpan2rpm # utility. To get this software or for more information # please visit: http://perl.arix.com/ # %prep %setup -q -n %{namever} chmod -R u+w %{_builddir}/%{namever} %build CFLAGS="$RPM_OPT_FLAGS" %{__perl} Makefile.PL DESTDIR=%{buildroot} `%{__perl} -MExtUtils::MakeMaker -e ' print qq|PREFIX=%{buildroot}%{_prefix}| if \$ExtUtils::MakeMaker::VERSION =~ /5\.9[1-6]|6\.0[0-5]/ '` %{__make} %if %maketest %{__make} test %endif %install [ "%{buildroot}" != "/" ] && rm -rf %{buildroot} %{makeinstall} `%{__perl} -MExtUtils::MakeMaker -e ' print \$ExtUtils::MakeMaker::VERSION <= 6.05 ? qq|PREFIX=%{buildroot}%{_prefix}| : qq|DESTDIR=%{buildroot}| '` cmd=/usr/share/spec-helper/compress_files [ -x $cmd ] || cmd=/usr/lib/rpm/brp-compress [ -x $cmd ] && $cmd # SuSE Linux if [ -e /etc/SuSE-release ]; then %{__mkdir_p} %{buildroot}/var/adm/perl-modules %{__cat} `find %{buildroot} -name "perllocal.pod"` \ | %{__sed} -e s+%{buildroot}++g \ > %{buildroot}/var/adm/perl-modules/%{name} fi # remove special files find %{buildroot} -name "perllocal.pod" \ -o -name ".packlist" \ -o -name "*.bs" \ |xargs -i rm -f {} # no empty directories find %{buildroot}%{_prefix} \ -type d -depth \ -exec rmdir {} \; 2>/dev/null %{__perl} -MFile::Find -le ' find({ wanted => \&wanted, no_chdir => 1}, "%{buildroot}"); print "%defattr(-,root,root)"; print "%doc Changes README FEATURES LICENSE THANKS TODO examples docs"; for my $x (sort @dirs, @files) { push @ret, $x unless indirs($x); } print join "\n", sort @ret; sub wanted { return if /auto$/; local $_ = $File::Find::name; my $f = $_; s|^%{buildroot}||; return unless length; return $files[@files] = $_ if -f $f; $d = $_; /\Q$d\E/ && return for reverse sort @INC; $d =~ /\Q$_\E/ && return for qw|/etc %_prefix/man %_prefix/bin %_prefix/share|; $dirs[@dirs] = $_; } sub indirs { my $x = shift; $x =~ /^\Q$_\E\// && $x ne $_ && return 1 for @dirs; } ' > %filelist # Install config files %{__mkdir_p} %{buildroot}/etc for i in email2page.conf sendpage.cf snpp.conf; do %{__install} -m 644 $i %{buildroot}/etc/$i echo "%config /etc/$i" >> %filelist done # Create spool %{__mkdir_p} %{buildroot}%{spool} echo "%attr(0770,%{user},root) %{spool}" >> %filelist # Write init file %{__mkdir_p} %{buildroot}/etc/init.d %{__install} -m 755 sendpage.init %{buildroot}/etc/init.d/sendpage echo "%config /etc/init.d/sendpage" >> %filelist # Set up system permission defaults GROUP_LOCK=`ls -ld /var/lock | awk '{print $4}'` GROUP_TTY=`ls -ld /dev/ttyS0 | awk '{print $4}'` if [ ! -z $GROUP_LOCK ]; then %{__perl} -pi -e "s/^#group-lock.*/group-lock=$GROUP_LOCK/;" %{buildroot}/etc/sendpage.cf fi if [ ! -z $GROUP_TTY ]; then %{__perl} -pi -e "s/^#group-tty.*/group-tty=$GROUP_TTY/;" %{buildroot}/etc/sendpage.cf fi #cat %filelist [ -z %filelist ] && { echo "ERROR: empty %files listing" exit -1 } grep -rsl '^#!.*perl' Changes README FEATURES LICENSE THANKS TODO examples docs | grep -v '.bak$' |xargs --no-run-if-empty \ %__perl -MExtUtils::MakeMaker -e 'MY->fixin(@ARGV)' %clean [ "%{buildroot}" != "/" ] && rm -rf %{buildroot} # Add the user and group %pre id %{user} >/dev/null 2>&1 || \ /usr/sbin/useradd -c 'Sendpage System' -d %{spool} -r -M %{user} %post if [ $1 = 1 ]; then /sbin/chkconfig --add %{name} >/dev/null fi %preun if [ $1 = 0 ]; then /sbin/chkconfig --del %{name} >/dev/null fi %postun if [ $1 -ge 1 ]; then /sbin/chkconfig %{name} >/dev/null && /sbin/service %{name} restart >/dev/null 2>&1 fi %files -f %filelist %changelog * Fri Feb 18 2005 kees@outflux.net - Tweaking for actual use under SuSE 9.2 * Fri Jan 16 2004 kees@outflux.net - Initial build, with lots of input from various folks that sent in spec files. sendpage-1.000003/snpp.conf0000644000076500007650000000034510737241467014041 0ustar keeskees# This file contains the default SNPP server that "snpp" will use for # sending pages. The format for this line is: # # server:ADDRESS[:PORT] # # for the server ADDRESS, and optionally the PORT to connect to. # server:localhost