Well, your idea was good and I had to think this more deeply...
I actually had some pieces of a code in my mail server, where incoming mail was notified using SMS.
Here is the implementation for smstools3. It differs slightly from your definition, but generally taken does the same.
Messages which may be scheduled to be sent later should have an additional header:
Urgency: low. Without this header message is sent without delays.
The original sample of checkhandler which handles multiple recipients needs some additions (highlighted):
#!/bin/bash
# Sample script to allow multiple recipients in one message file.
# Define this script as a checkhandler.
outgoing="/var/spool/sms/outgoing"
scheduler="/usr/local/bin/smsd_scheduler.sh"
recipients=`formail -zx "To:" < "$1"`
#count=`echo "$recipients" | wc -l`
count=`echo "$recipients" | wc -w`
if [ $count -gt 1 ]; then
# Will need echo which accepts -n argument:
ECHO=echo
case `uname` in
SunOS)
ECHO=/usr/ucb/echo
;;
esac
messagebody=`sed -e '1,/^$/ d' < "$1"`
headers=`formail -X "" -I "To:" -f < "$1"`
for recipient in $recipients
do
file=`mktemp $outgoing/send_XXXXXX`
$ECHO "To: $recipient" > $file
if [ "x$headers" != "x" ]; then
$ECHO "$headers" >> $file
fi
$ECHO "" >> $file
$ECHO -n "$messagebody" >> $file
done
# Remove processed file:
rm $1
# Tell to smsd that checkhandler has spooled this message:
exit 2
else
# Run scheduler:
$scheduler "$1"
i=$?
exit $i
fi
exit 0
'c' Syntax Highlight powered by GeSHi The script
/usr/local/bin/smsd_scheduler.sh:
#!/bin/bash
silence_conf="/etc/sms_silence.conf"
scheduled_dir="/var/spool/sms/scheduled"
#--------------------------------------------------------------------------
date2stamp()
{
local DATE="$1"
[ -z "$DATE" ] && DATE=$(date +"%Y-%m-%d %T")
date --utc --date "$DATE" +%s
}
#--------------------------------------------------------------------------
stamp2date()
{
date --utc --date "1970-01-01 $1 sec" "+%Y-%m-%d %T"
}
#--------------------------------------------------------------------------
is_time_between()
{
# $1 = time
# $2 = later time
# $3 = optional: time to test (timestamp)
local DATE
local stamp stamp1 stamp2
local result=0
if [ -n "$3" ]; then
DATE=$(stamp2date "$3")
else
DATE=$(date +"%Y-%m-%d %T")
fi
stamp=$(date2stamp "$DATE")
stamp1=$(date2stamp "${DATE:0:10} $1")
stamp2=$(date2stamp "${DATE:0:10} $2")
if [ $stamp2 -le $stamp1 ]; then
if [ $stamp -le $stamp2 ]; then
stamp1=$(($stamp1 - 86400))
else
stamp2=$(($stamp2 + 86400))
fi
fi
[ $stamp -ge $stamp1 ] && [ $stamp -le $stamp2 ] && result=1
echo "$result"
}
#--------------------------------------------------------------------------
test_silence()
{
# $1 = timestamp
# $2 = definition of silence
local result=0
local timestamp="$1"
local silence="$2"
local tmp
if [[ "$silence" == *$'-'* ]]; then
silence=${silence// /}
tmp=$(is_time_between "${silence:0:5}:00" "${silence:6:5}:00" "$timestamp")
[ $tmp -gt 0 ] && result=1
else
tmp=$(date --utc --date "1970-01-01 $timestamp sec" +"%H")
if [[ "$silence" == *${tmp}* ]]; then
result=1
fi
fi
if [ $result -eq 0 ]; then
tmp=$(date --utc --date "1970-01-01 $timestamp sec" +"%a" | tr a-z A-Z)
if [[ "$silence" == *${tmp}* ]]; then
result=1
fi
fi
echo "$result"
}
#--------------------------------------------------------------------------
remove_header()
{
# $1 = filename
# $2 = header
local tmp=$(formail -zx "$2" < "$1")
if [ -n "$tmp" ]; then
tmp=$(mktemp /tmp/smsd__XXXXXX)
cp "$1" $tmp
formail -f -I $2 < $tmp > "$1"
unlink $tmp
fi
}
#--------------------------------------------------------------------------
set_header()
{
# $1 = filename
# $2 = header
local tmp=$(mktemp /tmp/smsd_XXXXXX)
cp "$1" $tmp
formail -f -I "$2" < $tmp > "$1"
unlink $tmp
}
#--------------------------------------------------------------------------
urgency=$(formail -zx Urgency: < $1 | tr a-z A-Z)
if [ "$urgency" = "LOW" ]; then
TO=$(formail -zx To: < $1)
silence=$(formail -zx ${TO}: < "$silence_conf")
[ -z "$silence" ] && silence=$(formail -zx DEFAULT: < "$silence_conf")
if [ -n "$silence" ]; then
DATE=$(date +"%Y-%m-%d %T")
timestamp=$(date2stamp "$DATE")
tmp=$(test_silence "$timestamp" "$silence")
if [ $tmp -gt 0 ]; then
DATE="${DATE:0:14}00:01"
timestamp=$(date2stamp "$DATE")
timestamp=$(($timestamp + 3600))
stampmax=$(($timestamp + 604800))
while [ $timestamp -lt $stampmax ]; do
tmp=$(test_silence "$timestamp" "$silence")
if [ $tmp -eq 0 ]; then
break
fi
timestamp=$(($timestamp + 3600))
done
if [ $timestamp -ge $stampmax ]; then
echo "ERROR. Definition of silence is not valid."
exit 1
else
DATE=$(stamp2date "$timestamp")
set_header "$1" "Send: $DATE"
set_header "$1" "Silence: $silence"
set_header "$1" "Spooled: $(date +"%Y-%m-%d %T")"
remove_header "$1" "Urgency:"
mv $1 $scheduled_dir
fi
exit 2
fi
fi
fi
exit 0
'bash' Syntax Highlight powered by GeSHi Now the file
/etc/sms_silence.conf, for example:
In this file there is a GSM number as a header, and definition of silence as a value. Special header DEFAULT is used, if a GSM number has no it's own definition. If definition contains minus sign, it is assumed that the definition begins with a "HH:MM - HH:MM" range. Only one range can be defined. If there is no minus sign, it is assumed that hours are listed as in the DEFAULT case. Additionally short names of weekdays can be defined too.
Now, when outgoing SMS is like:
To: 358401111111 358402222222
To: 358403333333
Urgency: low
This is an alarm from the server.
'sms' Syntax Highlight powered by GeSHi ...checkhandler will first create single SMS file for each recipient, and tell to smsd that SMS was spooled by the checkhandler. Next smsd will find files with single recipient, and scheduler is executed for each file. If there is "Urgency: low" header and sms_silence.conf defines that message should be sent later, the script will find the first possible time to send and moves message file to the scheduled directory.
Scheduled files will have some additional information, for example:
To: 358401234567
Send: 2011-01-16 08:00:01
Silence: 16:00 - 08:00 MON TUE WED THU FRI SAT
Spooled: 2011-01-12 13:16:35
This is an alarm from the server.
'sms' Syntax Highlight powered by GeSHi Simply, "Spooled" tell when the SMS was handled, and "Silence" tells what was the definition when SMS was scheduled. This allows that later some external script can poll scheduled messages, and if some definition of silence has changed, SMS can be sent earlier or later what was originally defined. This implementation is not included in this post.
Next, regular_run shown in post #2 is required, as it will handle messages later.
In some servers smsd_scheduler.sh may be very slow, because bash script is not the fastest possible. If continuous time of silence is very long, it may take couple of seconds to find the first allowed time to send. If PHP is available, here is the version of scheduler which is much more faster:
#!/usr/bin/php
<?php
$silence_conf = "/etc/sms_silence.conf";
$scheduled_dir = "/var/spool/sms/scheduled";
//-------------------------------------------------------------------------
function date2stamp($str = "")
{
$dt = $str;
if (empty($dt))
$dt = date("Y-m-d H:i:s");
return strtotime($dt);
}
//-------------------------------------------------------------------------
function stamp2date($value)
{
return date("Y-m-d H:i:s", $value);
}
//-------------------------------------------------------------------------
function is_time_between($time1, $time2, $time_to_test = 0)
{
$result = 0;
if ($time_to_test)
$dt = stamp2date($time_to_test);
else
$dt = date("Y-m-d H:i:s");
$stamp = date2stamp($dt);
$stamp1 = date2stamp(substr($dt, 0, 10) ." " .$time1);
$stamp2 = date2stamp(substr($dt, 0, 10) ." " .$time2);
if ($stamp2 <= $stamp1)
{
if ($stamp <= $stamp2)
$stamp1 -= 86400;
else
$stamp2 += 86400;
}
if ($stamp >= $stamp1 && $stamp <= $stamp2)
$result = 1;
return $result;
}
//-------------------------------------------------------------------------
function test_silence($timestamp, $silence)
{
$result = 0;
if (strpos($silence, "-") !== false)
{
$silence = str_replace(" ", "", $silence);
$time1 = substr($silence, 0, 5) .":00";
$time2 = substr($silence, 6, 5) .":00";
if (is_time_between($time1, $time2, $timestamp))
$result = 1;
}
else
{
$tmp = date("H", $timestamp);
if (strpos($silence, $tmp) !== false)
$result = 1;
}
if (!$result)
{
$tmp = strtoupper(date("D", $timestamp));
if (strpos($silence, $tmp) !== false)
$result = 1;
}
return $result;
}
//-------------------------------------------------------------------------
$exitvalue = 0;
$sms_file = $argv[1];
$sms_file_content = file_get_contents($sms_file);
$i = strpos($sms_file_content, "\n\n");
$sms_headers_part = substr($sms_file_content, 0, $i);
$sms_message_body = substr($sms_file_content, $i + 2);
$sms_header_lines = split("\n", $sms_headers_part);
$sms_headers = array();
foreach ($sms_header_lines as $header)
{
$i = strpos($header, ":");
if ($i !== false)
$sms_headers[substr($header, 0, $i)] = substr($header, $i + 2);
}
//-------------------------------------------------------------------------
if (isset($sms_headers['Urgency']) && strtoupper($sms_headers['Urgency']) == "LOW")
{
$silence_file_content = file_get_contents($silence_conf);
$i = strpos($silence_file_content, "\n\n");
if ($i !== false)
$silence_headers_part = substr($silence_file_content, 0, $i);
else
$silence_headers_part = $silence_file_content;
$silence_header_lines = split("\n", $silence_headers_part);
$silence_headers = array();
foreach ($silence_header_lines as $header)
{
$i = strpos($header, ":");
if ($i !== false)
$silence_headers[substr($header, 0, $i)] = substr($header, $i + 2);
}
$silence = "";
if (isset($silence_headers[$sms_headers['To']]))
$silence = $silence_headers[$sms_headers['To']];
else
if (isset($silence_headers['DEFAULT']))
$silence = $silence_headers['DEFAULT'];
if (!empty($silence))
{
$dt = date("Y-m-d H:i:s");
$timestamp = date2stamp($dt);
if (test_silence($timestamp, $silence) > 0)
{
$dt = substr($dt, 0, 14) ."00:01";
$timestamp = date2stamp($dt);
$timestamp += 3600;
$stampmax = $timestamp + 604800;
while ($timestamp < $stampmax)
{
if (!test_silence($timestamp, $silence))
break;
$timestamp += 3600;
}
if ($timestamp >= $stampmax)
{
echo "ERROR. Definition of silence is not valid.";
$exitvalue = 1;
}
else
{
$dt = stamp2date($timestamp);
$sms_headers['Send'] = $dt;
$sms_headers['Silence'] = $silence;
$sms_headers['Spooled'] = date("Y-m-d H:i:s");
unset($sms_headers['Urgency']);
$sms_file_content = "";
foreach ($sms_headers as $key => $val)
$sms_file_content .= $key .": " .$val ."\n";
$sms_file_content .= "\n";
$sms_file_content .= $sms_message_body;
$new_sms_file = $scheduled_dir .strrchr($sms_file, '/');
//Not in PHP 4: file_put_contents($new_sms_file, $sms_file_content);
if (($handle = fopen($new_sms_file, "w")) !== false)
{
fwrite($handle, $sms_file_content);
fclose($handle);
}
unlink($sms_file);
$exitvalue = 2;
}
}
}
}
exit($exitvalue);
?>
'php' Syntax Highlight powered by GeSHi Finally, as an example, an eventhandler which can be used for the maintenance of silence definitions:
#!/bin/bash
silence_conf="/etc/sms_silence.conf"
scheduler="/usr/local/bin/smsd_scheduler.sh" # Change if using PHP
#--------------------------------------------------------------------------
send_sms()
{
# $1 = To
# $2 = Message
# $3 = Provider (optional)
local FILE=$(mktemp /tmp/smsd_send_XXXXXX)
echo "To: $1" >> $FILE
if [ "x$3" != "x" ]; then
echo "Provider: $3" >> $FILE
fi
echo "Comment: smsd eventhandler auto-answer" >> $FILE
echo "" >> $FILE
echo -n "$2" >> $FILE
local FILE2=`mktemp /var/spool/sms/outgoing/send_XXXXXX`
mv $FILE $FILE2
}
#--------------------------------------------------------------------------
set_header()
{
# $1 = filename
# $2 = header
local tmp=$(mktemp /tmp/smsd_eventhandler_XXXXXX)
cp "$1" $tmp
formail -f -I "$2" < $tmp > "$1"
unlink $tmp
}
#--------------------------------------------------------------------------
if [ "$1" = "RECEIVED" ]; then
FROM=$(formail -zx From: < $2)
TEXT=$(sed -e '1,/^$/ d' < $2)
keyword=$(echo ${TEXT:0:8} | tr a-z A-Z)
if [ "$keyword" = "SILENCE?" ]; then
silence=$(formail -zx ${FROM}: < "$silence_conf")
if [ -z "$silence" ]; then
silence=$(formail -zx DEFAULT: < "$silence_conf")
fi
if [[ "$scheduler" == *$'.sh' ]]; then
days=""
for ((i = 12; i < 19; i++)); do
[ -n "$days" ] && days="$days "
days="${days}$(date --utc --date "1970-01-${i}" "+%a")"
done
days=$(echo "$days" | tr a-z A-Z)
else
days="MON TUE WED THU FRI SAT SUN"
fi
if [ -z "$silence" ]; then
message="Silence not set"
else
message="Silence: $silence"
fi
send_sms $FROM "$message (Abbreviated names of the days: $days)"
elif [ "$keyword" = "SILENCE:" ]; then
silence=$(echo ${TEXT:8} | tr a-z A-Z)
[ -n "$silence" ] && silence=" $silence"
set_header "$silence_conf" "${FROM}:$silence"
tmp=${silence// /}
if [ -z "$tmp" ]; then
silence=" DEFAULT"
fi
send_sms $FROM "Silence set to:$silence"
fi
fi
exit 0
'bash' Syntax Highlight powered by GeSHi When SMS starting with "
Silence?" is received, eventhandler will send an answer containing current setting and list of abbreviated names of the days. Received SMS starting with "
Silence:" sets the definition.