Merge sms stuff and move to "utils" subdir (bug #2973)

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@4429 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
Mark Spencer
2004-12-11 22:36:27 +00:00
parent 442e1403a8
commit 30bc3eec6e
7 changed files with 2968 additions and 1647 deletions

View File

@@ -187,7 +187,7 @@ CFLAGS+= $(MALLOC_DEBUG)
CFLAGS+= $(BUSYDETECT)
CFLAGS+= $(OPTIONS)
CFLAGS+=# -fomit-frame-pointer
SUBDIRS=res channels pbx apps codecs formats agi cdr astman stdtime
SUBDIRS=res channels pbx apps codecs formats agi cdr utils stdtime
ifeq (${OSARCH},Linux)
LIBS=-ldl -lpthread
endif

File diff suppressed because it is too large Load Diff

View File

@@ -1,683 +0,0 @@
/*
* ASTerisk MANager
* Copyright (C) 2002, Linux Support Services, Inc.
*
* Distributed under the terms of the GNU General Public License
*/
#include <newt.h>
#include <stdio.h>
#include <sys/time.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <asterisk/md5.h>
#include <asterisk/manager.h>
#undef gethostbyname
#define MAX_HEADERS 80
#define MAX_LEN 256
static struct ast_mansession {
struct sockaddr_in sin;
int fd;
char inbuf[MAX_LEN];
int inlen;
} session;
static struct ast_chan {
char name[80];
char exten[20];
char context[20];
char priority[20];
char callerid[40];
char state[10];
struct ast_chan *next;
} *chans;
static struct ast_chan *find_chan(char *name)
{
struct ast_chan *prev = NULL, *chan = chans;
while(chan) {
if (!strcmp(name, chan->name))
return chan;
prev = chan;
chan = chan->next;
}
chan = malloc(sizeof(struct ast_chan));
if (chan) {
memset(chan, 0, sizeof(struct ast_chan));
strncpy(chan->name, name, sizeof(chan->name) - 1);
if (prev)
prev->next = chan;
else
chans = chan;
}
return chan;
}
static void del_chan(char *name)
{
struct ast_chan *prev = NULL, *chan = chans;
while(chan) {
if (!strcmp(name, chan->name)) {
if (prev)
prev->next = chan->next;
else
chans = chan->next;
return;
}
prev = chan;
chan = chan->next;
}
}
static void fdprintf(int fd, char *fmt, ...)
{
char stuff[4096];
va_list ap;
va_start(ap, fmt);
vsnprintf(stuff, sizeof(stuff), fmt, ap);
va_end(ap);
write(fd, stuff, strlen(stuff));
}
static char *get_header(struct message *m, char *var)
{
char cmp[80];
int x;
snprintf(cmp, sizeof(cmp), "%s: ", var);
for (x=0;x<m->hdrcount;x++)
if (!strncasecmp(cmp, m->headers[x], strlen(cmp)))
return m->headers[x] + strlen(cmp);
return "";
}
static int event_newstate(struct ast_mansession *s, struct message *m)
{
struct ast_chan *chan;
chan = find_chan(get_header(m, "Channel"));
strncpy(chan->state, get_header(m, "State"), sizeof(chan->state) - 1);
return 0;
}
static int event_newexten(struct ast_mansession *s, struct message *m)
{
struct ast_chan *chan;
chan = find_chan(get_header(m, "Channel"));
strncpy(chan->exten, get_header(m, "Extension"), sizeof(chan->exten) - 1);
strncpy(chan->context, get_header(m, "Context"), sizeof(chan->context) - 1);
strncpy(chan->priority, get_header(m, "Priority"), sizeof(chan->priority) - 1);
return 0;
}
static int event_newchannel(struct ast_mansession *s, struct message *m)
{
struct ast_chan *chan;
chan = find_chan(get_header(m, "Channel"));
strncpy(chan->state, get_header(m, "State"), sizeof(chan->state) - 1);
strncpy(chan->callerid, get_header(m, "Callerid"), sizeof(chan->callerid) - 1);
return 0;
}
static int event_status(struct ast_mansession *s, struct message *m)
{
struct ast_chan *chan;
chan = find_chan(get_header(m, "Channel"));
strncpy(chan->state, get_header(m, "State"), sizeof(chan->state) - 1);
strncpy(chan->callerid, get_header(m, "Callerid"), sizeof(chan->callerid) - 1);
strncpy(chan->exten, get_header(m, "Extension"), sizeof(chan->exten) - 1);
strncpy(chan->context, get_header(m, "Context"), sizeof(chan->context) - 1);
strncpy(chan->priority, get_header(m, "Priority"), sizeof(chan->priority) - 1);
return 0;
}
static int event_hangup(struct ast_mansession *s, struct message *m)
{
del_chan(get_header(m, "Channel"));
return 0;
}
static int event_ignore(struct ast_mansession *s, struct message *m)
{
return 0;
}
static int event_rename(struct ast_mansession *s, struct message *m)
{
struct ast_chan *chan;
chan = find_chan(get_header(m, "Oldname"));
strncpy(chan->name, get_header(m, "Newname"), sizeof(chan->name) - 1);
return 0;
}
static struct event {
char *event;
int (*func)(struct ast_mansession *s, struct message *m);
} events[] = {
{ "Newstate", event_newstate },
{ "Newchannel", event_newchannel },
{ "Newexten", event_newexten },
{ "Hangup", event_hangup },
{ "Rename", event_rename },
{ "Status", event_status },
{ "Link", event_ignore },
{ "Unlink", event_ignore },
{ "StatusComplete", event_ignore }
};
static int process_message(struct ast_mansession *s, struct message *m)
{
int x;
char event[80] = "";
strncpy(event, get_header(m, "Event"), sizeof(event) - 1);
if (!strlen(event)) {
fprintf(stderr, "Missing event in request");
return 0;
}
for (x=0;x<sizeof(events) / sizeof(events[0]);x++) {
if (!strcasecmp(event, events[x].event)) {
if (events[x].func(s, m))
return -1;
break;
}
}
if (x >= sizeof(events) / sizeof(events[0]))
fprintf(stderr, "Ignoring unknown event '%s'", event);
#if 0
for (x=0;x<m->hdrcount;x++) {
printf("Header: %s\n", m->headers[x]);
}
#endif
return 0;
}
static void rebuild_channels(newtComponent c)
{
void *prev = NULL;
struct ast_chan *chan;
char tmpn[42];
char tmp[256];
int x=0;
prev = newtListboxGetCurrent(c);
newtListboxClear(c);
chan = chans;
while(chan) {
snprintf(tmpn, sizeof(tmpn), "%s (%s)", chan->name, chan->callerid);
if (strlen(chan->exten))
snprintf(tmp, sizeof(tmp), "%-30s %8s -> %s@%s:%s",
tmpn, chan->state,
chan->exten, chan->context, chan->priority);
else
snprintf(tmp, sizeof(tmp), "%-30s %8s",
tmpn, chan->state);
newtListboxAppendEntry(c, tmp, chan);
x++;
chan = chan->next;
}
if (!x)
newtListboxAppendEntry(c, " << No Active Channels >> ", NULL);
newtListboxSetCurrentByKey(c, prev);
}
static int has_input(struct ast_mansession *s)
{
int x;
for (x=1;x<s->inlen;x++)
if ((s->inbuf[x] == '\n') && (s->inbuf[x-1] == '\r'))
return 1;
return 0;
}
static int get_input(struct ast_mansession *s, char *output)
{
/* output must have at least sizeof(s->inbuf) space */
int res;
int x;
struct timeval tv = {0, 0};
fd_set fds;
for (x=1;x<s->inlen;x++) {
if ((s->inbuf[x] == '\n') && (s->inbuf[x-1] == '\r')) {
/* Copy output data up to and including \r\n */
memcpy(output, s->inbuf, x + 1);
/* Add trailing \0 */
output[x+1] = '\0';
/* Move remaining data back to the front */
memmove(s->inbuf, s->inbuf + x + 1, s->inlen - x);
s->inlen -= (x + 1);
return 1;
}
}
if (s->inlen >= sizeof(s->inbuf) - 1) {
fprintf(stderr, "Dumping long line with no return from %s: %s\n", inet_ntoa(s->sin.sin_addr), s->inbuf);
s->inlen = 0;
}
FD_ZERO(&fds);
FD_SET(s->fd, &fds);
res = select(s->fd + 1, &fds, NULL, NULL, &tv);
if (res < 0) {
fprintf(stderr, "Select returned error: %s\n", strerror(errno));
} else if (res > 0) {
res = read(s->fd, s->inbuf + s->inlen, sizeof(s->inbuf) - 1 - s->inlen);
if (res < 1)
return -1;
s->inlen += res;
s->inbuf[s->inlen] = '\0';
} else {
return 2;
}
return 0;
}
static int input_check(struct ast_mansession *s, struct message **mout)
{
static struct message m;
int res;
if (mout)
*mout = NULL;
for(;;) {
res = get_input(s, m.headers[m.hdrcount]);
if (res == 1) {
#if 0
fprintf(stderr, "Got header: %s", m.headers[m.hdrcount]);
fgetc(stdin);
#endif
/* Strip trailing \r\n */
if (strlen(m.headers[m.hdrcount]) < 2)
continue;
m.headers[m.hdrcount][strlen(m.headers[m.hdrcount]) - 2] = '\0';
if (!strlen(m.headers[m.hdrcount])) {
if (mout && strlen(get_header(&m, "Response"))) {
*mout = &m;
return 0;
}
if (process_message(s, &m))
break;
memset(&m, 0, sizeof(&m));
} else if (m.hdrcount < MAX_HEADERS - 1)
m.hdrcount++;
} else if (res < 0) {
return -1;
} else if (res == 2)
return 0;
}
return -1;
}
static struct message *wait_for_response(int timeout)
{
struct message *m;
struct timeval tv;
int res;
fd_set fds;
for (;;) {
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) * 1000;
FD_SET(session.fd, &fds);
res = select(session.fd + 1, &fds, NULL, NULL, &tv);
if (res < 1)
break;
if (input_check(&session, &m) < 0) {
return NULL;
}
if (m)
return m;
}
return NULL;
}
static int manager_action(char *action, char *fmt, ...)
{
struct ast_mansession *s;
char tmp[4096];
va_list ap;
s = &session;
fdprintf(s->fd, "Action: %s\r\n", action);
va_start(ap, fmt);
vsnprintf(tmp, sizeof(tmp), fmt, ap);
va_end(ap);
write(s->fd, tmp, strlen(tmp));
fdprintf(s->fd, "\r\n");
return 0;
}
static int show_message(char *title, char *msg)
{
newtComponent form;
newtComponent label;
newtComponent ok;
struct newtExitStruct es;
newtCenteredWindow(60,7, title);
label = newtLabel(4,1,msg);
ok = newtButton(27, 3, "OK");
form = newtForm(NULL, NULL, 0);
newtFormAddComponents(form, label, ok, NULL);
newtFormRun(form, &es);
newtPopWindow();
newtFormDestroy(form);
return 0;
}
static newtComponent showform;
static int show_doing(char *title, char *tmp)
{
struct newtExitStruct es;
newtComponent label;
showform = newtForm(NULL, NULL, 0);
newtCenteredWindow(70,4, title);
label = newtLabel(3,1,tmp);
newtFormAddComponents(showform,label, NULL);
newtFormSetTimer(showform, 200);
newtFormRun(showform, &es);
return 0;
}
static int hide_doing(void)
{
newtPopWindow();
newtFormDestroy(showform);
return 0;
}
static void try_status(void)
{
struct message *m;
manager_action("Status", "");
m = wait_for_response(10000);
if (!m) {
show_message("Status Failed", "Timeout waiting for response");
} else if (strcasecmp(get_header(m, "Response"), "Success")) {
show_message("Status Failed Failed", get_header(m, "Message"));
}
}
static void try_hangup(newtComponent c)
{
struct ast_chan *chan;
struct message *m;
chan = newtListboxGetCurrent(c);
if (chan) {
manager_action("Hangup", "Channel: %s\r\n", chan->name);
m = wait_for_response(10000);
if (!m) {
show_message("Hangup Failed", "Timeout waiting for response");
} else if (strcasecmp(get_header(m, "Response"), "Success")) {
show_message("Hangup Failed", get_header(m, "Message"));
}
}
}
static int get_user_input(char *msg, char *buf, int buflen)
{
newtComponent form;
newtComponent ok;
newtComponent cancel;
newtComponent inpfield;
const char *input;
int res = -1;
struct newtExitStruct es;
newtCenteredWindow(60,7, msg);
inpfield = newtEntry(5, 2, "", 50, &input, 0);
ok = newtButton(22, 3, "OK");
cancel = newtButton(32, 3, "Cancel");
form = newtForm(NULL, NULL, 0);
newtFormAddComponents(form, inpfield, ok, cancel, NULL);
newtFormRun(form, &es);
strncpy(buf, input, buflen - 1);
if (es.u.co == ok)
res = 0;
else
res = -1;
newtPopWindow();
newtFormDestroy(form);
return res;
}
static void try_redirect(newtComponent c)
{
struct ast_chan *chan;
char dest[256];
struct message *m;
char channame[256];
char tmp[80];
char *context;
chan = newtListboxGetCurrent(c);
if (chan) {
strncpy(channame, chan->name, sizeof(channame) - 1);
snprintf(tmp, sizeof(tmp), "Enter new extension for %s", channame);
if (get_user_input(tmp, dest, sizeof(dest)))
return;
if ((context = strchr(dest, '@'))) {
*context = '\0';
context++;
manager_action("Redirect", "Channel: %s\r\nContext: %s\r\nExten: %s\r\nPriority: 1\r\n", chan->name,context,dest);
} else {
manager_action("Redirect", "Channel: %s\r\nExten: %s\r\nPriority: 1\r\n", chan->name, dest);
}
m = wait_for_response(10000);
if (!m) {
show_message("Hangup Failed", "Timeout waiting for response");
} else if (strcasecmp(get_header(m, "Response"), "Success")) {
show_message("Hangup Failed", get_header(m, "Message"));
}
}
}
static int manage_calls(char *host)
{
newtComponent form;
newtComponent quit;
newtComponent hangup;
newtComponent redirect;
newtComponent channels;
struct newtExitStruct es;
char tmp[80];
/* If there's one thing you learn from this code, it is this...
Never, ever fly Air France. Their customer service is absolutely
the worst. I've never heard the words "That's not my problem" as
many times as I have from their staff -- It should, without doubt
be their corporate motto if it isn't already. Don't bother giving
them business because you're just a pain in their side and they
will be sure to let you know the first time you speak to them.
If you ever want to make me happy just tell me that you, too, will
never fly Air France again either (in spite of their excellent
cuisine). */
snprintf(tmp, sizeof(tmp), "Asterisk Manager at %s", host);
newtCenteredWindow(74, 20, tmp);
form = newtForm(NULL, NULL, 0);
newtFormWatchFd(form, session.fd, NEWT_FD_READ);
newtFormSetTimer(form, 100);
quit = newtButton(62, 16, "Quit");
redirect = newtButton(35, 16, "Redirect");
hangup = newtButton(50, 16, "Hangup");
channels = newtListbox(1,1,14, NEWT_FLAG_SCROLL);
newtFormAddComponents(form, channels, redirect, hangup, quit, NULL);
newtListboxSetWidth(channels, 72);
show_doing("Getting Status", "Retrieving system status...");
try_status();
hide_doing();
for(;;) {
newtFormRun(form, &es);
if (has_input(&session) || (es.reason == NEWT_EXIT_FDREADY)) {
if (input_check(&session, NULL)) {
show_message("Disconnected", "Disconnected from remote host");
break;
}
} else if (es.reason == NEWT_EXIT_COMPONENT) {
if (es.u.co == quit)
break;
if (es.u.co == hangup) {
try_hangup(channels);
} else if (es.u.co == redirect) {
try_redirect(channels);
}
}
rebuild_channels(channels);
}
newtFormDestroy(form);
return 0;
}
static int login(char *hostname)
{
newtComponent form;
newtComponent cancel;
newtComponent login;
newtComponent username;
newtComponent password;
newtComponent label;
newtComponent ulabel;
newtComponent plabel;
const char *user;
const char *pass;
struct message *m;
struct newtExitStruct es;
char tmp[55];
struct hostent *hp;
int res = -1;
session.fd = socket(AF_INET, SOCK_STREAM, 0);
if (session.fd < 0) {
snprintf(tmp, sizeof(tmp), "socket() failed: %s\n", strerror(errno));
show_message("Socket failed", tmp);
return -1;
}
snprintf(tmp, sizeof(tmp), "Looking up %s\n", hostname);
show_doing("Connecting....", tmp);
hp = gethostbyname(hostname);
if (!hp) {
snprintf(tmp, sizeof(tmp), "No such address: %s\n", hostname);
show_message("Host lookup failed", tmp);
return -1;
}
hide_doing();
snprintf(tmp, sizeof(tmp), "Connecting to %s", hostname);
show_doing("Connecting...", tmp);
session.sin.sin_family = AF_INET;
session.sin.sin_port = htons(DEFAULT_MANAGER_PORT);
memcpy(&session.sin.sin_addr, hp->h_addr, sizeof(session.sin.sin_addr));
if (connect(session.fd,(struct sockaddr*)&session.sin, sizeof(session.sin))) {
snprintf(tmp, sizeof(tmp), "%s failed: %s\n", hostname, strerror(errno));
show_message("Connect Failed", tmp);
return -1;
}
hide_doing();
login = newtButton(5, 6, "Login");
cancel = newtButton(25, 6, "Cancel");
newtCenteredWindow(40, 10, "Asterisk Manager Login");
snprintf(tmp, sizeof(tmp), "Host: %s", hostname);
label = newtLabel(4,1, tmp);
ulabel = newtLabel(4,2,"Username:");
plabel = newtLabel(4,3,"Password:");
username = newtEntry(14, 2, "", 20, &user, 0);
password = newtEntry(14, 3, "", 20, &pass, NEWT_FLAG_HIDDEN);
form = newtForm(NULL, NULL, 0);
newtFormAddComponents(form, username, password, login, cancel, label, ulabel, plabel,NULL);
newtFormRun(form, &es);
if (es.reason == NEWT_EXIT_COMPONENT) {
if (es.u.co == login) {
snprintf(tmp, sizeof(tmp), "Logging in '%s'...", user);
show_doing("Logging in", tmp);
/* Check to see if the remote host supports MD5 Authentication */
manager_action("Challenge", "AuthType: MD5\r\n");
m = wait_for_response(10000);
if (m && !strcasecmp(get_header(m, "Response"), "Success")) {
char *challenge = get_header(m, "Challenge");
int x;
int len = 0;
char md5key[256] = "";
struct MD5Context md5;
unsigned char digest[16];
MD5Init(&md5);
MD5Update(&md5, challenge, strlen(challenge));
MD5Update(&md5, pass, strlen(pass));
MD5Final(digest, &md5);
for (x=0; x<16; x++)
len += sprintf(md5key + len, "%2.2x", digest[x]);
manager_action("Login",
"AuthType: MD5\r\n"
"Username: %s\r\n"
"Key: %s\r\n",
user, md5key);
m = wait_for_response(10000);
hide_doing();
if (!strcasecmp(get_header(m, "Response"), "Success")) {
res = 0;
} else {
show_message("Login Failed", get_header(m, "Message"));
}
} else {
memset(m, 0, sizeof(m));
manager_action("Login",
"Username: %s\r\n"
"Secret: %s\r\n",
user, pass);
m = wait_for_response(10000);
hide_doing();
if (m) {
if (!strcasecmp(get_header(m, "Response"), "Success")) {
res = 0;
} else {
show_message("Login Failed", get_header(m, "Message"));
}
}
}
}
}
newtFormDestroy(form);
return res;
}
int main(int argc, char *argv[])
{
if (argc < 2) {
fprintf(stderr, "Usage: astman <host>\n");
exit(1);
}
newtInit();
newtCls();
newtDrawRootText(0, 0, "Asterisk Manager (C)2002, Linux Support Services, Inc.");
newtPushHelpLine("Welcome to the Asterisk Manager!");
if (login(argv[1])) {
newtFinished();
exit(1);
}
manage_calls(argv[1]);
newtFinished();
return 0;
}

834
doc/app_sms.html Executable file
View File

@@ -0,0 +1,834 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
</head>
<body>
<h1>* Application SMS</h1>
The SMS module for asterisk was developed by Adrian Kennard, and is an
implementation of the ETSI specification for landline SMS, ETSI ES 201
912, which is available from www.etsi.org. Landline SMS is starting to
be available in various parts of Europe, and is available from BT in
the UK. However, asterisk would allow gateways to be created in other
locations such as the US, and use of SMS capable phones such as the
Magic Messenger. SMS works using analogue or ISDN lines.<br>
<h2>Background</h2>
Short Message Service (SMS), or <span style="font-style: italic;">texting</span>
is very popular between mobile phones. A message can be sent between
two phones, and normally contains 160 characters. There are ways in
which various types of data can be encoded in a text message such as
ring tones, and small graphic, etc. Text messaging is being used for
voting and competitions, and also SPAM...<br>
<br>
Sending a message involves the mobile phone contacting a message centre
(SMSC) and passing the message to it. The message centre then contacts
the destination mobile to deliver the message. The SMSC is responsible
for storing the message and trying to send it until the destination
mobile is available, or a timeout.<br>
<br>
Landline SMS works in basically the same way. You would normally have a
suitable text capable landline phone, or a separate texting box such as
a Magic Messenger on your phone line. This sends a message to a message
centre your telco provides by making a normal call and sending the data
using 1200 Baud FSK signaling according to the ETSI spec. To receive a
message the message centre calls the line with a specific calling
number, and the text capable phone answers the call and receives the
data using 1200 Baud FSK signaling. This works particularly well in the
UK as the calling line identity is sent before the first ring, so no
phones in the house would ring when a message arrives.<br>
<h2>Typical use with asterisk</h2>
Sending messages from an asterisk box can be used for a variety of
reasons, including notification from any monitoring systems, email
subject lines, etc.<br>
Receiving messages to an asterisk box is typically used just to email
the messages to someone appropriate - we email and texts that are
received to our direct numbers to the appropriate person. Received
messages could also be used to control applications, manage
competitions, votes, post items to IRC, anything.<br>
Using a terminal such as a magic messenger, an asterisk box could ask
as a message centre sending messages to the terminal, which will beep
and pop up the message (and remember 100 or so messages in its memory).<br>
<h2>Terminology</h2>
<table style="text-align: left;" border="1" cellpadding="2"
cellspacing="2">
<tbody>
<tr>
<td style="vertical-align: top;">SMS<br>
</td>
<td style="vertical-align: top;">Short Message Service<br>
</td>
<td style="vertical-align: top;">i.e. text messages<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">SMSC<br>
</td>
<td style="vertical-align: top;">Short Message Service Centre<br>
</td>
<td style="vertical-align: top;">The system responsible for
storing and forwarding messages<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">MO<br>
</td>
<td style="vertical-align: top;">Mobile Originated<br>
</td>
<td style="vertical-align: top;">A message on its way from a
mobile or landline device to the SMSC<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">MT<br>
</td>
<td style="vertical-align: top;">Mobile Terminated<br>
</td>
<td style="vertical-align: top;">A message on its way from the
SMSC to the mobile or landline device<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">RX<br>
</td>
<td style="vertical-align: top;">Receive<br>
</td>
<td style="vertical-align: top;">A message coming in to the
asterisk box<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">TX<br>
</td>
<td style="vertical-align: top;">Transmit<br>
</td>
<td style="vertical-align: top;">A message going out of the
asterisk box<br>
</td>
</tr>
</tbody>
</table>
<h2>Sub address</h2>
When sending a message to a landline, you simply send to the landline
number. In the UK, all of the mobile operators (bar one) understand
sending messages to landlines and pass the messages to the BTText
system for delivery to the landline.<br>
<br>
The specification for landline SMS allows for the possibility of more
than one device on a single landline. These can be configured with <span
style="font-style: italic;">Sub addresses</span> which are a single
digit. To send a message to a specific device the message is sent to
the landline number with an extra digit appended to the end. The telco
can define a default sub address (9 in the UK) which is used when the
extra digit is not appended to the end. When the call comes in, part of
the calling line ID is the sub address, so that only one device on the
line answers the call and receives the message.<br>
<br>
Sub addresses also work for outgoing messages. Part of the number
called by the device to send a message is its sub address. Sending from
the default sub address (9 in the UK) means the message is delivered
with the <span style="font-style: italic;">sender </span>being the
normal landline number. Sending from any other sub address makes the <span
style="font-style: italic;">sender</span> the landline number with an
extra digit on the end.<br>
<br>
Using asterisk, you can make use of the sub addresses for sending and
receiving messages. Using DDI (DID, i.e. multiple numbers on the line
on ISDN) you can also make use of many different numbers for SMS.<br>
<h2>Build / installation</h2>
<span style="font-weight: bold;">app_sms.c</span> is included in the
latest cvs. It lives in the asterisk source <span
style="font-weight: bold;">apps</span> directory and is included in
the object list (<span style="font-weight: bold;">app_sms.so</span>) in
<span style="font-weight: bold;">apps/Makefile</span>.<br>
<span style="font-weight: bold;">smsq.c</span> is a stand alone helper
application which is used to send SMSs from the command line. It uses
the <span style="font-weight: bold;">popt</span> library. A line for
your make file is:-<br>
<pre>smsq: smsq.c<br> cc -O -o smsq smsq.c -lpopt<br></pre>
<span style="font-family: monospace;"></span>
<h2>extensions.conf</h2>
The following contexts are recommended.<br>
<pre>; Mobile Terminated, RX. This is used when an incoming call from the SMS arrives, with the queue (called number and sub address) in ${EXTEN}<br>; Running an app after receipt of the text allows the app to find all messages in the queue and handle them, e.g. email them.<br>; The app may be something like smsq --process=somecommand --queue=${EXTEN} to run a command for each received message<br>; See below for usage<br>[smsmtrx]<br>exten = _X.,1, SMS(${EXTEN}|a)<br>exten = _X.,2,System("someapptohandleincomingsms ${EXTEN}")<br>exten = _X.,3,Hangup<br><br>; Mobile originated, RX. This is receiving a message from a device, e.g. a Magic Messenger on a sip extension<br>; Running an app after receipt of the text allows the app to find all messages in the queue and handle then, e.g. sending them to the public SMSC<br>; The app may be something like smsq --process=somecommand --queue=${EXTEN} to run a command for each received message<br>; See below for example usage<br>[smsmorx]<br>exten = _X.,1, SMS(${EXTEN}|sa)<br>exten = _X.,2,System("someapptohandlelocalsms ${EXTEN}")<br>exten = _X.,3,Hangup<span
style="font-family: sans-serif;"></span><span
style="font-family: sans-serif;"></span></pre>
<span style="font-family: sans-serif;"></span><span
style="font-weight: bold;">smsmtrx</span> is normally accessed by an
incoming call from the SMSC. In the UK this call is from a CLI of
080058752X0 where X is the sub address. As such a typical usage in the
extensions.conf at the point of handling an incoming call is:-<br>
<pre>exten = _X./8005875290,1,Goto(smsmtrx,${EXTEN},1)<br>exten = _X./_80058752[0-8]0,1,Goto(smsmtrx,${EXTEN}-${CALLERIDNUM:8:1},1)<br></pre>
Alternatively, if you have the correct national prefix on incoming CLI,
e.g. using zaphfc, you might use:-<br>
<pre>exten = _X./08005875290,1,Goto(smsmtrx,${EXTEN},1)<br>exten = _X./_080058752[0-8]0,1,Goto(smsmtrx,${EXTEN}-${CALLERIDNUM:9:1},1)</pre>
<span style="font-weight: bold;">smsmorx</span> is normally accessed by
a call from a local sip device connected to a Magic Messenger. It could
however by that you are operating asterisk as a message centre for
calls from outside. Either way, you look at the called number and goto
smsmorx. In the UK, the SMSC number that would be dialed is 1709400X
where X is the caller sub address. As such typical usage in
extension.config at the point of handling a call from a sip phone is:-<br>
<pre>exten = 17094009,1,Goto(smsmorx,${CALLERIDNUM},1)<br>exten = _1709400[0-8],1,Goto(smsmorx,${CALLERIDNUM}-{EXTEN:7:1},1)<br></pre>
<h2>Using smsq</h2>
<span style="font-weight: bold;">smsq</span> is a simple helper
application designed to make it easy to send messages from a command
line. it is intended to run on the asterisk box and have direct access
to the queue directories for SMS and for asterisk.<br>
<br>
In its simplest form you can send an SMS by a command such as <br>
<br>
smsq 0123456789 This is a test to 0123456789<br>
<br>
This would create a queue file for a mobile originated TX message in
queue 0 to send the text "This is a test to 0123456789" to 0123456789.
It would then place a file in the /var/spool/asterisk/outgoing
directory to initiate a call to 17094009 (the default message centre in
smsq) attached to application SMS with argument of the queue name (0).<br>
<br>
Normally smsq will queue a message ready to send, and will then create
a file in the asterisk outgoing directory causing asterisk to actually
connect to the message centre or device and actually send the pending
message(s).<br>
<br>
Using --process, smsq can however be used on received queues to run a
command for each file (matching the queue if specified) with various
environment variables set based on the message (see below);<br>
<br>
smsq options:-<br>
<br>
<table style="text-align: left;" border="1" cellpadding="2"
cellspacing="2">
<tbody>
<tr>
<td style="vertical-align: top;">--help</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">Show help text<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--usage<br>
</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">Show usage<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--queue<br>
</td>
<td style="vertical-align: top;">-q<br>
</td>
<td style="vertical-align: top;">Specify a specific queue<br>
In no specified, messages are queued under queue "0"<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--da<br>
</td>
<td style="vertical-align: top;">-d<br>
</td>
<td style="vertical-align: top;">Specify destination address<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--oa<br>
</td>
<td style="vertical-align: top;">-o<br>
</td>
<td style="vertical-align: top;">Specify originating address<br>
This also implies that we are generating a mobile terminated message<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--ud<br>
</td>
<td style="vertical-align: top;">-m<br>
</td>
<td style="vertical-align: top;">Specify the actual message<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--ud-file<br>
</td>
<td style="vertical-align: top;">-f<br>
</td>
<td style="vertical-align: top;">Specify a file to be read for
the context of the message<br>
A blank filename (e.g. --ud-file= on its own) means read stdin. Very
useful when using via ssh where command line parsing could mess up the
message.<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--mt<br>
</td>
<td style="vertical-align: top;">-t<br>
</td>
<td style="vertical-align: top;">Mobile terminated message to be
generated<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--mo<br>
</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">Mobile originated message to be
generated<br>
Default<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--tx<br>
</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">Transmit message<br>
Default<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--rx<br>
</td>
<td style="vertical-align: top;">-r<br>
</td>
<td style="vertical-align: top;">Generate a message in the
receive queue<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--UTF-8<br>
</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">Treat the file as UTF-8 encoded
(default) </td>
</tr>
<tr>
<td style="vertical-align: top;">--UCS-1<br>
</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">Treat the file as raw 8 bit
UCS-1 data, not UTF-8 encoded<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--UCS-2<br>
</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">Treat the file as raw 16 bit
bigendian USC-2 data<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--process<br>
</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">Specific a command to process
for each file in the queue<br>
Implies --rx and --mt if not otherwise specified.<br>
Sets environment variables for every possible variable,
and also ud, ud8 (USC-1 hex), and ud16 (USC-2 hex) for each call.
Removes files.<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--motx-channel<br>
</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">Specify the channel for motx
calls<br>
May contain X to use sub address based on queue name or may be full
number<br>
Default is Local/1709400X<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--motx-callerid<br>
</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">Specify the caller ID for motx
calls<br>
The default is the queue name without -X suffix<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--motx-wait<br>
</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">Wait time for motx call<br>
Default 10<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--motx-delay<br>
</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">Retry time for motx call<br>
Default 1<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--motx-retries<br>
</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">Retries for motx call<br>
Default 10<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--mttx-channel<br>
</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">Specify the channel for mttx
calls<br>
Default is Local/ and the queue name without -X suffix<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--mtttx-callerid<br>
</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">Specify the callerid for mttx
calls<br>
May include X to use sub address based on queue name or may be full
number<br>
Default is 080058752X0<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--mttx-wait<br>
</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">Wait time for mttx call<br>
Default 10<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--mttx-delay<br>
</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">Retry time for mttx call<br>
Default 30<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--mttx-retries<br>
</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">Retries for mttx call<br>
Default 100<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--default-sub-address<br>
</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">The default sub address assumed
(e.g. for X in CLI and dialled numbers as above) when none added (-X)
to queue<br>
Default 9<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--no-dial<br>
</td>
<td style="vertical-align: top;">-x<br>
</td>
<td style="vertical-align: top;">Create queue, but do not dial to
send message<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--no-wait<br>
</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">Do not wait if a call appears to
be in progress<br>
This could have a small window where a mesdsage is queued but not sent,
so regular calls to smsq should be done to pick up any missed messages<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--concurrent<br>
</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">How many concurrent calls to
allow (per queue), default 1<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--mr<br>
</td>
<td style="vertical-align: top;">-n<br>
</td>
<td style="vertical-align: top;">Message reference<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--pid<br>
</td>
<td style="vertical-align: top;">-p<br>
</td>
<td style="vertical-align: top;">Protocol ID<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--dcs<br>
</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">Data coding scheme<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--udh<br>
</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">Specific hex string of user data
header specified (not including the initial length byte)<br>
May be a blank string to indicate header is included in the user data
already but user data header indication to be set.<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--srr<br>
</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">Status report requested<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--rp<br>
</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">Return path requested<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--vp<br>
</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">Specify validity period (seconds)<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--scts<br>
</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">Specify timestamp
(YYYY-MM-DDTHH:MM:SS)<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">--spool-dir<br>
</td>
<td style="vertical-align: top;"><br>
</td>
<td style="vertical-align: top;">Spool dir (in which sms and
outgoing are found)<br>
Default /var/spool/asterisk<br>
</td>
</tr>
</tbody>
</table>
<p>Other arguments starting '-' or '--' are invalid and will cause an
error. Any trailing arguments are processed as follows:-<br>
</p>
<ul>
<li>If the message is mobile originating and no destination address
has been specified, then the first argument is assumed to be a
destination address</li>
<li>If the message is mobile terminating and no destination address
has been specified, then the first argument is assumed to be the queue
name</li>
<li>If there is no user data, or user data file specified, then any
following arguments are assumed to be the message, which are
concatenated.</li>
<li>If no user data is specified, then no message is sent. However,
unless --no-dial is specified, smsq checks for pending messages and
generates an outgoing anyway</li>
</ul>
Note that when smsq attempts to make a file in
/var/spool/asterisk/outgoing, it checks if there is already a call
queued for that queue. It will try several filenames, up to the
--concorrent setting. If these files
exists, then this means asterisk is already queued to send all messages
for that queue, and so asterisk should pick up the message just queued.
However, this alone could create a race condition, so if the files
exist then smsq will wait up to 3 seconds to confirm it still exists or
if the queued messages have been sent already.
The --no-wait turns off this behaviour. Basically, this means that if
you have a lot of messages to send all at
once, asterisk will not make unlimited concurrent calls to the same
message centre or device for the same queue. This is because it is
generally more efficient to make one call and send all of the messages
one after the other.<br>
<br>
smsq can be used with no arguments, or with a queue name only, and it
will check for any pending messages and cause an outgoing if there are
any. It only sets up one outgoing call at a time based on the first
queued message it finds. A outgoing call will normally send all queued
messages for that queue. One way to use smsq would be to run with no
queue name (so any queue) every minute or every few seconds to send
pending message. This is not normally necessary unless --no-dial is
selected. Note that smsq does only check motx or mttx depending on the
options selected, so it would need to be called twice as a general
check.<br>
<br>
UTF-8 is used to parse command line arguments for user data, and is the
default when reading a file. If an invalid UTF-8 sequence is found, it
is treated as UCS-1 data (i.e, as is).<br>
<br>
The --process option causes smsq to scan the specified queue (default
is mtrx) for messages (matching the queue specified, or any if queue
not specified) and run a command and delete the file. The command is
run with a number of environment variables set as follows. Note that
these are unset if not needed and not just taken from the calling
environment. This allows simple processing of incoming messages<br>
<br>
<table style="text-align: left;" border="1" cellpadding="2"
cellspacing="2">
<tbody>
<tr>
<td style="vertical-align: top;">$queue<br>
</td>
<td style="vertical-align: top;">Set if a queue specified<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">$?srr<br>
</td>
<td style="vertical-align: top;">srr is set (to blank) if srr
defined and has value 1.<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">$?rp<br>
</td>
<td style="vertical-align: top;">rp is set (to blank) if rp
defined and has value 1.<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">$ud<br>
</td>
<td style="vertical-align: top;">User data, UTF-8 encoding,
including any control characters, but with nulls stripped out<br>
Useful for the content of emails, for example, as it includes any
newlines, etc.<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">$ude<br>
</td>
<td style="vertical-align: top;">User data, escaped UTF-8,
including all characters, but control characters \n, \r, \t, \f, \xxx
and \ is escaped as \\<br>
Useful fGuaranteed one line printable text, so useful in Subject lines
of emails, etc<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">$ud8<br>
</td>
<td style="vertical-align: top;">Hex UCS-1 coding of user data (2
hex digits per character)<br>
Present only if all user data is in range U+0000 to U+00FF<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">$ud16<br>
</td>
<td style="vertical-align: top;">Hex UCS-2 coding of user data (4
hex digits per chartacter)<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;"><span style="font-style: italic;">other</span><br>
</td>
<td style="vertical-align: top;">Other fields set using their
field name, e.g. mr, pid, dcs, etc. udh is a hex byte string<br>
</td>
</tr>
</tbody>
</table>
<h2>File formats</h2>
By default all queues are held in a director /var/spool/asterisk/sms.
Within this directory are sub directories mtrx, mttx, morx, motx which
hold the received messages and the messages ready to send. Also,
/var/log/asterisk/sms is a log file of all messages handled.<br>
<br>
The file name in each queue directory starts with the queue parameter
to SMS which is normally the CLI used for an outgoing message or the
called number on an incoming message, and may have -X (X being sub
address) appended. If no queue ID is known, then 0 is used by smsq by
default. After this is a dot, and then any text. Files are scanned for
matching queue ID and a dot at the start. This means temporary files
being created can be given a different name not starting with a queue
(we recommend a . on the start of the file name for temp files).<br>
<br>
Files in these queues are in the form of a simple text file where each
line starts with a keyword and an = and then data. udh and ud have
options for hex encoding, see below.<br>
<br>
UTF-8. The user data (ud) field is treated as being UTF-8 encoded
unless the DCS is specified indicating 8 bit formart. If 8 bit format
is specified then the user data is sent as is.<br>
<br>
The keywords are as
follows:-<br>
<table style="text-align: left;" border="1" cellpadding="2"
cellspacing="2">
<tbody>
<tr>
<td style="vertical-align: top;">oa</td>
<td style="vertical-align: top;">Originating address<br>
The phone number from which the message came<br>
Present on mobile terminated messages and is the CLI for morx messages<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">da<br>
</td>
<td style="vertical-align: top;">Destination Address<br>
The phone number to which the message is sent<br>
Present on mobile originated messages<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">scts<br>
</td>
<td style="vertical-align: top;">The service centre time stamp<br>
Format YYYY-MM-DDTHH:MM:SS<br>
Present on mobile terminated messages<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">pid<br>
</td>
<td style="vertical-align: top;">One byte decimal protocol ID<br>
See GSM specs for more details<br>
Normally 0 or absent<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">dcs<br>
</td>
<td style="vertical-align: top;">One byte decimal data coding
scheme<br>
If omitted, a sensible default is used (see below)<br>
See GSM specs for more details<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">mr<br>
</td>
<td style="vertical-align: top;">One byte decimal message
reference<br>
Present on mobile originated messages, added by default if absent<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">srr<br>
</td>
<td style="vertical-align: top;">0 or 1 for status report request<br>
Does not work in UK yet, not implemented in app_sms yet<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">rp<br>
</td>
<td style="vertical-align: top;">0 or 1 return path<br>
See GSM specs for details<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">vp<br>
</td>
<td style="vertical-align: top;">Validity period in seconds<br>
Does not work in UK yet<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">udh<br>
</td>
<td style="vertical-align: top;">Hex string of user data header
prepended to the SMS contents, excluding initial length byte.<br>
Consistent with ud, this is specified as udh# rather than udh=<br>
If blank, this means that the udhi flag will be set but any user data
header must be in the ud field<br>
</td>
</tr>
<tr>
<td style="vertical-align: top;">ud<br>
</td>
<td style="vertical-align: top;">User data, may be text, or hex,
see below<br>
</td>
</tr>
</tbody>
</table>
<br>
udh is specified as as udh# followed by hex (2 hex digits per byte). If
present, then the user data header indicator bit is set, and the length
plus the user data header is added to the start of the user data, with
padding if necessary (to septet boundary in 7 bit format).<br>
<br>
User data can hold an USC character codes U+0000 to U+FFFF. Any other
characters are coded as U+FEFF<br>
ud can be specified as ud= followed by UTF-8 encoded text if it
contains no control characters, i.e. only (U+0020 to U+FFFF). Any
invalid UTF-8 sequences are treated as is (U+0080-U+00FF).<br>
ud can also be specified as ud# followed by hex (2 hex digits per byte)
containing characters U+0000 to U+00FF only.<br>
ud can also be specified as ud## followed by hex (4 hex digits per
byte) containing UCS-2 characters.<br>
When written by app_sms (e.g. incoming messages), the file is written
with ud= if it can be (no control characters). If it cannot, the a
comment line ;ud= is used to show the user data for human readability
and ud# or ud## is used.<br>
<h2>Delivery reports</h2>
The SMS specification allows for delivery reports. These are requested
using the srr bit. However, as these do not work in the UK yet they are
not fully implemented in this application. If anyone has a telco that
does implement these, please let me know. BT in the UK have a non
standard way to do this by starting the message with *0#, and so this
application may have a UK specific bodge in the near future to handle
these.<br>
<br>
The main changes that are proposed for delivery report handling are :-<br>
<ul>
<li>New queues for sent messages, one file for each destination
address and message reference.</li>
<li>New field in message format, user reference, allowing
applications to tie up their original message with a report.</li>
<li>Handling of the delivery confirmation/rejection and connecting to
the outgoing message - the received message file would then have fields
for the original outgoing message and user reference allowing
applications to handle confirmations better.<br>
</li>
</ul>
<br>
</body>
</html>

View File

@@ -1,2 +1,3 @@
astman
.depend
astman
smsq

View File

@@ -8,7 +8,9 @@ ifeq ($(findstring BSD,${OSARCH}),BSD)
CFLAGS+=-I/usr/local/include -L/usr/local/lib
endif
TARGET=$(shell if [ -f /usr/include/newt.h ]; then echo "astman"; else if [ -f /usr/local/include/newt.h ]; then echo "astman"; else echo "none" ; fi ; fi)
TARGET+=$(shell if [ -f /usr/include/popt.h ]; then echo "smsq"; else if [ -f /usr/local/include/popt.h ]; then echo "smsq"; fi ; fi)
TARGET+=$(shell if [ -f /usr/include/newt.h ]; then echo "astman"; else if [ -f /usr/local/include/newt.h ]; then echo "astman"; fi ; fi)
all: depend $(TARGET)
install:
@@ -27,6 +29,9 @@ clean:
astman: astman.o ../md5.o
$(CC) $(CFLAGS) -o astman astman.o ../md5.o -lnewt
smsq: smsq.o
$(CC) $(CFLAGS) -o smsq smsq.o -lpopt
ifneq ($(wildcard .depend),)
include .depend
endif

740
utils/smsq.c Executable file
View File

@@ -0,0 +1,740 @@
#include <stdio.h>
#include <popt.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <malloc.h>
#include <dirent.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
// SMS queuing application for use with asterisk app_sms
// by Adrian Kennard, 2004
/* reads next USC character from null terminated UTF-8 string and advanced pointer */
/* for non valid UTF-8 sequences, returns character as is */
/* Does not advance pointer for null termination */
static int utf8decode (unsigned char **pp)
{
unsigned char *p = *pp;
if (!*p)
return 0; /* null termination of string */
(*pp)++;
if (*p < 0xC0)
return *p; /* ascii or continuation character */
if (*p < 0xE0)
{
if (*p < 0xC2 || (p[1] & 0xC0) != 0x80)
return *p; /* not valid UTF-8 */
(*pp)++;
return ((*p & 0x1F) << 6) + (p[1] & 0x3F);
}
if (*p < 0xF0)
{
if ((*p == 0xE0 && p[1] < 0xA0) || (p[1] & 0xC0) != 0x80 || (p[2] & 0xC0) != 0x80)
return *p; /* not valid UTF-8 */
(*pp) += 2;
return ((*p & 0x0F) << 12) + ((p[1] & 0x3F) << 6) + (p[2] & 0x3F);
}
if (*p < 0xF8)
{
if ((*p == 0xF0 && p[1] < 0x90) || (p[1] & 0xC0) != 0x80 || (p[2] & 0xC0) != 0x80 || (p[3] & 0xC0) != 0x80)
return *p; /* not valid UTF-8 */
(*pp) += 3;
return ((*p & 0x07) << 18) + ((p[1] & 0x3F) << 12) + ((p[2] & 0x3F) << 6) + (p[3] & 0x3F);
}
if (*p < 0xFC)
{
if ((*p == 0xF8 && p[1] < 0x88) || (p[1] & 0xC0) != 0x80 || (p[2] & 0xC0) != 0x80 || (p[3] & 0xC0) != 0x80
|| (p[4] & 0xC0) != 0x80)
return *p; /* not valid UTF-8 */
(*pp) += 4;
return ((*p & 0x03) << 24) + ((p[1] & 0x3F) << 18) + ((p[2] & 0x3F) << 12) + ((p[3] & 0x3F) << 6) + (p[4] & 0x3F);
}
if (*p < 0xFE)
{
if ((*p == 0xFC && p[1] < 0x84) || (p[1] & 0xC0) != 0x80 || (p[2] & 0xC0) != 0x80 || (p[3] & 0xC0) != 0x80
|| (p[4] & 0xC0) != 0x80 || (p[5] & 0xC0) != 0x80)
return *p; /* not valid UTF-8 */
(*pp) += 5;
return ((*p & 0x01) << 30) + ((p[1] & 0x3F) << 24) + ((p[2] & 0x3F) << 18) + ((p[3] & 0x3F) << 12) + ((p[4] & 0x3F) << 6) +
(p[5] & 0x3F);
}
return *p; /* not sensible */
}
// check for any queued messages in specific queue (queue="" means any queue)
// returns 0 if nothing queued, 1 if queued and outgoing set up OK, 2 of outgoing exists
static char txqcheck (char *dir, char *queue, char subaddress, char *channel, char *callerid, int wait, int delay, int retries, int concurrent)
{
char ogname[100],
temp[100],
dirname[100],
*p=NULL;
FILE *f;
DIR *d;
int ql = strlen (queue);
struct dirent *fn;
sprintf (dirname, "sms/%s", dir);
d = opendir (dirname);
if (!d)
return 0;
while ((fn = readdir (d))
&& !(*fn->d_name != '.'
&& ((!ql && (p = strchr (fn->d_name, '.'))) || (ql && !strncmp (fn->d_name, queue, ql) && fn->d_name[ql] == '.'))));
if (!fn)
{
closedir (d);
return 0;
}
if (!ql)
{ // not searching any specific queue, so use whatr we found as the queue
queue = fn->d_name;
ql = p - queue;
}
p = strchr (queue, '-');
if (p && p < queue + ql)
{
ql = p - queue;
subaddress = p[1];
}
sprintf (temp, "sms/.smsq-%d", getpid ());
f = fopen (temp, "w");
if (!f)
{
perror (temp);
closedir (d);
return 0;
}
fprintf (f, "Channel: ");
if (!channel)
fprintf (f, "Local/%.*s\n", ql, queue);
else
{
p = strchr (channel, '/');
if (!p)
p = channel;
p = strchr (p, 'X');
if (p)
fprintf (f, "%.*s%c%s\n", p - channel, channel, subaddress, p + 1);
else
fprintf (f, "%s\n", channel);
}
fprintf (f, "Callerid: SMS <");
if (!callerid)
fprintf (f, "%.*s", ql, queue);
else
{
p = strchr (callerid, 'X');
if (p)
fprintf (f, "%.*s%c%s", p - callerid, callerid, subaddress, p + 1);
else
fprintf (f, "%s", callerid);
}
fprintf (f, ">\n");
fprintf (f, "Application: SMS\n");
fprintf (f, "Data: %.*s", ql, queue);
if (dir[1] == 't')
fprintf (f, "|s");
fprintf (f, "\nMaxRetries: %d\n", retries);
fprintf (f, "RetryTime: %d\n", delay);
fprintf (f, "WaitTime: %d\n", wait);
fclose (f);
closedir (d);
{
int try = 0;
while (try < concurrent)
{
try++;
sprintf (ogname, "outgoing/smsq.%s.%s.%d", dir, queue, try);
if (!link (temp, ogname))
{ // queued OK
unlink (temp);
return 1;
}
}
}
// failed to create call queue
unlink (temp);
return 2;
}
// Process received queue entries and run through a process, setting environment variables
static void rxqcheck (char *dir, char *queue, char *process)
{
unsigned char *p;
char dirname[100],
temp[100];
DIR *d;
int ql = strlen (queue);
struct dirent *fn;
sprintf (temp, "sms/.smsq-%d", getpid ());
sprintf (dirname, "sms/%s", dir);
d = opendir (dirname);
if (!d)
return;
while ((fn = readdir (d)))
if ((*fn->d_name != '.'
&& ((!ql && (p = strchr (fn->d_name, '.'))) || (ql && !strncmp (fn->d_name, queue, ql) && fn->d_name[ql] == '.'))))
{ /* process file */
char filename[NAME_MAX + 10];
char line[1000];
unsigned short ud[160];
unsigned char udl = 0;
FILE *f;
sprintf (filename, "sms/%s/%s", dir, fn->d_name);
if (rename (filename, temp))
continue; // cannot access file
f = fopen (temp, "r");
unlink (temp);
if (!f)
{
perror (temp);
continue;
}
unsetenv ("oa");
unsetenv ("da");
unsetenv ("scts");
unsetenv ("pid");
unsetenv ("dcs");
unsetenv ("mr");
unsetenv ("srr");
unsetenv ("rp");
unsetenv ("vp");
unsetenv ("udh");
unsetenv ("ud");
unsetenv ("ude");
unsetenv ("ud8");
unsetenv ("ud16");
unsetenv ("morx");
unsetenv ("motx");
unsetenv ("queue");
if (*queue)
setenv ("queue", queue, 1);
setenv (dir, "", 1);
while (fgets (line, sizeof (line), f))
{
for (p = line; *p && *p != '\n' && *p != '\r'; p++);
*p = 0; /* strip eoln */
p = line;
if (!*p || *p == ';')
continue; /* blank line or comment, ignore */
while (isalnum (*p))
{
*p = tolower (*p);
p++;
}
while (isspace (*p))
*p++ = 0;
if (*p == '=')
{ // =
*p++ = 0;
if (!strcmp (line, "oa") || !strcmp (line, "da") || !strcmp (line, "scts") || !strcmp (line, "pid")
|| !strcmp (line, "dcs") || !strcmp (line, "mr") || !strcmp (line, "vp"))
setenv (line, p, 1);
else if ((!strcmp (line, "srr") || !strcmp (line, "rp")) && atoi (p))
setenv (line, "", 1);
else if (!strcmp (line, "ud"))
{ // read the user data as UTF-8
long v;
udl = 0;
while ((v = utf8decode (&p)) && udl < 160)
if (v && v <= 0xFFFF)
ud[udl++] = v;
}
} else if (*p == '#')
{
*p++ = 0;
if (*p == '#')
{ // ##
p++;
if (!strcmp (line, "udh"))
setenv (line, p, 1);
else if (!strcmp (line, "ud"))
{ // read user data UCS-2
udl = 0;
while (*p && udl < 160)
{
if (isxdigit (*p) && isxdigit (p[1]) && isxdigit (p[2]) && isxdigit (p[3]))
{
ud[udl++] =
(((isalpha (*p) ? 9 : 0) + (*p & 0xF)) << 12) +
(((isalpha (p[1]) ? 9 : 0) + (p[1] & 0xF)) << 8) +
(((isalpha (p[2]) ? 9 : 0) + (p[2] & 0xF)) << 4) + ((isalpha (p[3]) ? 9 : 0) + (p[3] & 0xF));
p += 4;
} else
break;
}
}
} else
{ // #
if (!strcmp (line, "ud"))
{ // read user data UCS-1
udl = 0;
while (*p && udl < 160)
{
if (isxdigit (*p) && isxdigit (p[1]))
{
ud[udl++] = (((isalpha (*p) ? 9 : 0) + (*p & 0xF)) << 4) + ((isalpha (p[1]) ? 9 : 0) + (p[1] & 0xF));
p += 2;
} else
break;
}
}
}
}
}
fclose (f);
// set up user data variables
{
char temp[481];
int n,
p;
for (n = 0, p = 0; p < udl; p++)
{
unsigned short v = ud[p];
if (v)
{
if (v < 0x80)
temp[n++] = v;
else if (v < 0x800)
{
temp[n++] = (0xC0 + (v >> 6));
temp[n++] = (0x80 + (v & 0x3F));
} else
{
temp[n++] = (0xE0 + (v >> 12));
temp[n++] = (0x80 + ((v >> 6) & 0x3F));
temp[n++] = (0x80 + (v & 0x3F));
}
}
}
temp[n] = 0;
setenv ("ud", temp, 1);
for (n = 0, p = 0; p < udl; p++)
{
unsigned short v = ud[p];
if (v < ' ' || v == '\\')
{
temp[n++] = '\\';
if (v == '\\')
temp[n++] = '\\';
else if (v == '\n')
temp[n++] = 'n';
else if (v == '\r')
temp[n++] = 'r';
else if (v == '\t')
temp[n++] = 't';
else if (v == '\f')
temp[n++] = 'f';
else
{
temp[n++] = '0' + (v >> 6);
temp[n++] = '0' + ((v >> 3) & 7);
temp[n++] = '0' + (v & 7);
}
} else if (v < 0x80)
temp[n++] = v;
else if (v < 0x800)
{
temp[n++] = (0xC0 + (v >> 6));
temp[n++] = (0x80 + (v & 0x3F));
} else
{
temp[n++] = (0xE0 + (v >> 12));
temp[n++] = (0x80 + ((v >> 6) & 0x3F));
temp[n++] = (0x80 + (v & 0x3F));
}
}
temp[n] = 0;
setenv ("ude", temp, 1);
for (p = 0; p < udl && ud[p] < 0x100; p++);
if (p == udl)
{
for (n = 0, p = 0; p < udl; p++)
{
sprintf (temp + n, "%02X", ud[p]);
n += 2;
}
setenv ("ud8", temp, 1);
}
for (n = 0, p = 0; p < udl; p++)
{
sprintf (temp + n, "%04X", ud[p]);
n += 4;
}
setenv ("ud16", temp, 1);
}
// run the command
system (process);
}
closedir (d);
}
// Main app
int
main (int argc, const char *argv[])
{
char c;
int mt = 0,
mo = 0,
tx = 0,
rx = 0,
nodial = 0,
nowait = 0,
concurrent = 1,
motxwait = 10,
motxdelay = 1,
motxretries = 10,
mttxwait = 10,
mttxdelay = 30,
mttxretries = 100,
mr = -1,
pid = -1,
dcs = -1,
srr = 0,
rp = 0,
vp = 0,
udl = 0,
utf8 = 0,
ucs1 = 0,
ucs2 = 0;
unsigned short ud[160];
unsigned char *uds = 0,
*udh = 0;
char *da = 0,
*oa = 0,
*queue = "",
*udfile = 0,
*process = 0,
*spooldir = "/var/spool/asterisk",
*motxchannel = "Local/1709400X",
*motxcallerid = 0,
*mttxchannel = 0,
*mttxcallerid = "080058752X0",
*defaultsubaddress = "9",
subaddress = 0,
*scts = 0;
poptContext optCon; // context for parsing command-line options
const struct poptOption optionsTable[] = {
{"queue", 'q', POPT_ARG_STRING | POPT_ARGFLAG_SHOW_DEFAULT, &queue, 0, "Queue [inc sub address]", "number[-X]"},
{"da", 'd', POPT_ARG_STRING, &da, 0, "Destination address", "number"},
{"oa", 'o', POPT_ARG_STRING, &oa, 0, "Origination address", "number"},
{"ud", 'm', POPT_ARG_STRING, &uds, 0, "Message", "text"},
{"ud-file", 'f', POPT_ARG_STRING, &udfile, 0, "Message file", "filename"},
{"UTF-8", 0, POPT_ARG_NONE, &utf8, 0, "File treated as null terminated UTF-8 (default)", 0},
{"UCS-1", 0, POPT_ARG_NONE, &ucs1, 0, "File treated as UCS-1", 0},
{"UCS-2", 0, POPT_ARG_NONE, &ucs2, 0, "File treated as UCS-2", 0},
{"mt", 't', POPT_ARG_NONE, &mt, 0, "Mobile Terminated", 0},
{"mo", 0, POPT_ARG_NONE, &mo, 0, "Mobile Originated", 0},
{"tx", 0, POPT_ARG_NONE, &tx, 0, "Send message", 0},
{"rx", 'r', POPT_ARG_NONE, &rx, 0, "Queue for receipt", 0},
{"process", 'e', POPT_ARG_STRING, &process, 0, "Rx queue process command", "command"},
{"no-dial", 'x', POPT_ARG_NONE, &nodial, 0, "Do not dial", 0},
{"no-wait", 0, POPT_ARG_NONE, &nowait, 0, "Do not wait if already calling", 0},
{"concurrent", 0, POPT_ARG_INT | POPT_ARGFLAG_SHOW_DEFAULT, &concurrent, 0, "Number of concurrent calls to allow", "n"},
{"motx-channel", 0, POPT_ARG_STRING | POPT_ARGFLAG_SHOW_DEFAULT, &motxchannel, 0, "Channel for motx calls", "channel"},
{"motx-callerid", 0, POPT_ARG_STRING, &motxcallerid, 0,
"Caller ID for motx calls (default is queue name without sub address)", "number"},
{"motx-wait", 0, POPT_ARG_INT | POPT_ARGFLAG_SHOW_DEFAULT, &motxwait, 0, "Time to wait for motx call to answer",
"seconds"},
{"motx-delay", 0, POPT_ARG_INT | POPT_ARGFLAG_SHOW_DEFAULT, &motxdelay, 0, "Time between motx call retries", "seconds"},
{"motx-retries", 0, POPT_ARG_INT | POPT_ARGFLAG_SHOW_DEFAULT, &motxretries, 0, "Number of retries for motx call", "n"},
{"mttx-channel", 0, POPT_ARG_STRING, &mttxchannel, 0,
"Channel for mttx calls (default is Local/ and queue name without sub address)", "channel"},
{"mttx-callerid", 0, POPT_ARG_STRING | POPT_ARGFLAG_SHOW_DEFAULT, &mttxcallerid, 0,
"Caller ID for mttx calls (default is queue name without sub address)", "number"},
{"mttx-wait", 0, POPT_ARG_INT | POPT_ARGFLAG_SHOW_DEFAULT, &mttxwait, 0, "Time to wait for mttx call to answer",
"seconds"},
{"mttx-delay", 0, POPT_ARG_INT | POPT_ARGFLAG_SHOW_DEFAULT, &mttxdelay, 0, "Time between mttx call retries", "seconds"},
{"mttx-retries", 0, POPT_ARG_INT | POPT_ARGFLAG_SHOW_DEFAULT, &mttxretries, 0, "Number of retries for mttx call", "n"},
{"mr", 'n', POPT_ARG_INT, &mr, 0, "Message reference", "n"},
{"pid", 'p', POPT_ARG_INT, &pid, 0, "Protocol ID", "n"},
{"dcs", 'c', POPT_ARG_INT, &dcs, 0, "Data Coding Scheme", "n"},
{"udh", 0, POPT_ARG_STRING, &udh, 0, "User data header", "hex"},
{"srr", 0, POPT_ARG_NONE, &srr, 0, "Status Report Request", 0},
{"rp", 0, POPT_ARG_NONE, &rp, 0, "Return Path request", 0},
{"v", 0, POPT_ARG_INT, &vp, 0, "Validity Period", "seconds"},
{"scts", 0, POPT_ARG_STRING, &scts, 0, "Timestamp", "YYYY-MM-SSTHH:MM:SS"},
{"default-sub-address", 0, POPT_ARG_STRING | POPT_ARGFLAG_SHOW_DEFAULT, &defaultsubaddress, 0, "Default sub address", "X"},
{"spool-dir", 0, POPT_ARG_STRING | POPT_ARGFLAG_SHOW_DEFAULT, &spooldir, 0, "Asterisk spool dir", "dirname"},
POPT_AUTOHELP {NULL, 0, 0, NULL, 0}
};
optCon = poptGetContext (NULL, argc, argv, optionsTable, 0);
poptSetOtherOptionHelp (optCon, "<oa/da> <message>");
/* Now do options processing, get portname */
if ((c = poptGetNextOpt (optCon)) < -1)
{
/* an error occurred during option processing */
fprintf (stderr, "%s: %s\n", poptBadOption (optCon, POPT_BADOPTION_NOALIAS), poptStrerror (c));
return 1;
}
if (!ucs1 && !ucs2)
utf8 = 1;
if (utf8 + ucs1 + ucs2 > 1)
{
fprintf (stderr, "Pick one of UTF-8, UCS-1 or UCS-2 only\n");
return 1;
}
if (!udfile && (ucs1 || ucs2))
{
fprintf (stderr, "Command line arguments always treated as UTF-8\n");
return 1;
}
// if (!where && poptPeekArg (optCon)) where = (char *) poptGetArg (optCon);
if (!mt && !mo && process)
mt = 1;
if (!mt && !mo && oa)
mt = 1;
if (!mt)
mo = 1;
if (mt && mo)
{
fprintf (stderr, "Cannot be --mt and --mo\n");
return 1;
}
if (!rx && !tx && process)
rx = 1;
if (!rx)
tx = 1;
if (tx && rx)
{
fprintf (stderr, "Cannot be --tx and --rx\n");
return 1;
}
if (rx)
nodial = 1;
if (uds && udfile)
{
fprintf (stderr, "Cannot have --ud and --ud-file\n");
return 1;
}
if (mo && !da && poptPeekArg (optCon))
da = (char *) poptGetArg (optCon);
if (mt && !oa && poptPeekArg (optCon))
oa = (char *) poptGetArg (optCon);
if (tx && oa && mo)
{
fprintf (stderr, "--oa makes no sense with --mo as CLI is used (i.e. queue name)\n");
return 1;
}
if (tx && da && mt)
{
fprintf (stderr, "--da makes no sense with --mt as called number is used (i.e. queue name)\n");
return 1;
}
if (da && strlen (da) > 20)
{
fprintf (stderr, "--da too long\n");
return 1;
}
if (oa && strlen (oa) > 20)
{
fprintf (stderr, "--oa too long\n");
return 1;
}
if (queue && strlen (queue) > 20)
{
fprintf (stderr, "--queue name too long\n");
return 1;
}
if (mo && scts)
{
fprintf (stderr, "scts is set my service centre\n");
return 1;
}
if (uds)
{ /* simple user data command line option in \UTF-8 */
while (udl < 160 && *uds)
{
int v = utf8decode (&uds);
if (v > 0xFFFF)
{
fprintf (stderr, "Invalid character U+%X at %d\n", v, udl);
return 1;
}
ud[udl++] = v;
}
}
if (!uds && !udfile && poptPeekArg (optCon))
{ /* multiple command line arguments in UTF-8 */
while (poptPeekArg (optCon) && udl < 160)
{
unsigned char *a = (char *) poptGetArg (optCon);
if (udl && udl < 160)
ud[udl++] = ' '; /* space between arguments */
while (udl < 160 && *a)
{
int v = utf8decode (&a);
if (v > 0xFFFF)
{
fprintf (stderr, "Invalid character U+%X at %d\n", v, udl);
return 1;
}
ud[udl++] = v;
}
}
}
if (poptPeekArg (optCon))
{
fprintf (stderr, "Unknown argument %s\n", poptGetArg (optCon));
return 1;
}
if (udfile)
{ // get message from file
unsigned char dat[1204],
*p = dat,
*e;
int f,
n;
if (*udfile)
f = open (udfile, O_RDONLY);
else
f = fileno (stdin);
if (f < 0)
{
perror (udfile);
return 1;
}
n = read (f, dat, sizeof (dat));
if (n < 0)
{
perror (udfile);
return 1;
}
if (*udfile)
close (f);
e = dat + n;
if (utf8)
{ /* UTF-8 */
while (p < e && udl < 160 && *p)
ud[udl++] = utf8decode (&p);
} else if (ucs1)
{ /* UCS-1 */
while (p < e && udl < 160)
ud[udl++] = *p++;
} else
{ /* UCS-2 */
while (p + 1 < e && udl < 160)
{
ud[udl++] = (*p << 8) + p[1];
p += 2;
}
}
}
if (queue)
{
char *d = strrchr (queue, '-');
if (d && d[1])
subaddress = d[1];
else
subaddress = *defaultsubaddress;
}
if (chdir (spooldir))
{
perror (spooldir);
return 1;
}
if (oa || da)
{ // send message
char temp[100],
queuename[100],
*dir = (mo ? rx ? "sms/morx" : "sms/motx" : rx ? "sms/mtrx" : "sms/mttx");
FILE *f;
sprintf (temp, "sms/.smsq-%d", getpid ());
mkdir ("sms", 0777); // ensure directory exists
mkdir (dir, 0777); // ensure directory exists
sprintf (queuename, "%s/%s.%ld-%d", dir, *queue ? queue : "0", (long)time (0), getpid ());
f = fopen (temp, "w");
if (!f)
{
perror (temp);
return 1;
}
if (oa)
fprintf (f, "oa=%s\n", oa);
if (da)
fprintf (f, "da=%s\n", da);
if (scts)
fprintf (f, "scts=%s\n", scts);
if (pid >= 0)
fprintf (f, "pid=%d\n", pid);
if (dcs >= 0)
fprintf (f, "dcs=%d\n", dcs);
if (mr >= 0)
fprintf (f, "mr=%d\n", mr);
if (srr)
fprintf (f, "srr=1\n");
if (rp)
fprintf (f, "rp=1\n");
if (udh)
fprintf (f, "udh#%s\n", udh);
if (vp > 0)
fprintf (f, "vp=%d\n", vp);
if (udl)
{
int p;
for (p = 0; p < udl && ud[p] < 0x100; p++);
if (p == udl)
{
for (p = 0; p < udl && ud[p] < 0x80 && ud[p] >= 0x20; p++);
if (p == udl)
{ /* use text */
fprintf (f, "ud=");
for (p = 0; p < udl; p++)
fputc (ud[p], f);
} else
{ /* use one byte hex */
fprintf (f, "ud#");
for (p = 0; p < udl; p++)
fprintf (f, "%02X", ud[p]);
}
} else
{ /* use two byte hex */
fprintf (f, "ud##");
for (p = 0; p < udl; p++)
fprintf (f, "%04X", ud[p]);
}
fprintf (f, "\n");
}
fclose (f);
if (rename (temp, queuename))
{
perror (queuename);
unlink (temp);
return 1;
}
}
if (!nodial && tx && !process)
{ // dial to send messages
char ret=0,
try = 3;
if (nowait)
try = 1;
while (try--)
{
if (mo)
ret = txqcheck ("motx", queue, subaddress, motxchannel, motxcallerid, motxwait, motxdelay, motxretries, concurrent);
else
ret = txqcheck ("mttx", queue, subaddress, mttxchannel, mttxcallerid, mttxwait, mttxdelay, mttxretries, concurrent);
if (ret < 2)
break; // sent, or queued OK
if (try)
sleep (1);
}
if (ret == 2 && !nowait)
fprintf (stderr, "No call scheduled as already sending\n");
}
if (process)
rxqcheck (mo ? rx ? "morx" : "motx" : rx ? "mtrx" : "mttx", queue, process);
return 0;
}