laforge
/
openbts-osmo
Archived
1
0
Fork 0
This repository has been archived on 2022-03-30. You can view files and clone it, but cannot push or open issues or pull requests.
openbts-osmo/public-trunk/smqueue/smqueue.cpp

2320 lines
70 KiB
C++

/*
* Smqueue.cpp - In-memory queue manager for Short Messages (SMS's) for OpenBTS.
* Written by John Gilmore, July 2009.
*
* Copyright 2009 Free Software Foundation, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* See the COPYING file in the main directory for details.
*/
#include "smqueue.h"
#include "smnet.h"
#include "smsc.h"
#include <time.h>
#include <osipparser2/osip_message.h> /* from osipparser2 */
#include <iostream>
#include <fstream>
#include <cstdlib> // strtol
#include <errno.h>
#include <sys/types.h> // open
#include <sys/stat.h> // open
#include <fcntl.h> // open
#include <ctype.h> // isdigit
#include <Logger.h>
#include <Configuration.h> // DAB
using namespace std;
using namespace SMqueue;
/* The global config table. */
// DAB
ConfigurationTable gConfig("smqueue.config");
/* We try to centralize most of the timeout values into this table.
Occasionally the code might do something different where it knows
better, but most state transitions just use this table to set the
timeout that will fire if nothing happens in the new state for X
seconds. */
/* Timeouts when going from NO_STATE into each subsequent state */
#define NT 6000 /* seconds = 50 minutes - "No Timeout" - for states
where we will only time out if something is really
broken. */
#define RT 600 /* seconds = 5 minutes - "Re Try" - for state
transitions where we're starting over from
scratch due to some error. */
/* Timeout when moving from this state to new state:
NS IS RF AF WD RD AD WS RS AS WM RM AM DM WR RH AR */
int timeouts_NO_STATE[STATE_MAX_PLUS_ONE] = {
NT, 0, 0, NT, NT, 0, NT, NT, 0, NT, NT, 0, NT, 0, NT, NT, NT,};
int timeouts_INITIAL_STATE[STATE_MAX_PLUS_ONE] = {
0, 0, 0, NT, NT, NT, NT, NT, NT, NT, NT, 0, NT, 0, NT, NT, NT,};
int timeouts_REQUEST_FROM_ADDRESS_LOOKUP[STATE_MAX_PLUS_ONE] = {
0, NT, 10, 10, NT, 0, NT, NT, NT, NT, NT, NT, NT, 0, 1, 0, NT,};
int timeouts_ASKED_FOR_FROM_ADDRESS_LOOKUP[STATE_MAX_PLUS_ONE] = {
0, NT, 60, NT, NT, NT, NT, NT, NT, NT, NT, NT, NT, 0, NT, NT, NT,};
int timeouts_AWAITING_TRY_DESTINATION_IMSI[STATE_MAX_PLUS_ONE] = {
0, NT, RT, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, NT, NT, NT,};
int timeouts_REQUEST_DESTINATION_IMSI[STATE_MAX_PLUS_ONE] = {
0, NT, RT, NT, RT, NT, NT, NT, 0, NT, NT, NT, NT, 0, NT, NT, NT,};
int timeouts_ASKED_FOR_DESTINATION_IMSI[STATE_MAX_PLUS_ONE] = {
0, NT, RT, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, NT, NT, NT,};
int timeouts_AWAITING_TRY_DESTINATION_SIPURL[STATE_MAX_PLUS_ONE] = {
0, NT, RT, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, NT, NT, NT,};
int timeouts_REQUEST_DESTINATION_SIPURL[STATE_MAX_PLUS_ONE] = {
0, NT, RT, NT, RT, NT, NT, NT, NT, NT, NT, 0, NT, 0, NT, NT, NT,};
int timeouts_ASKED_FOR_DESTINATION_SIPURL[STATE_MAX_PLUS_ONE] = {
0, NT, RT, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, NT, NT, NT,};
int timeouts_AWAITING_TRY_MSG_DELIVERY[STATE_MAX_PLUS_ONE] = {
0, NT, RT, NT, RT, NT, NT, NT, NT, NT, 75, 0, NT, 0, NT, NT, NT,};
int timeouts_REQUEST_MSG_DELIVERY[STATE_MAX_PLUS_ONE] = {
// 0, NT, RT, NT, RT, NT, NT, NT, 75, NT, 75, 75, 15, 0, NT, NT, NT,};
0, NT, RT, NT, RT, NT, NT, NT, 5, NT, 75, 75, 15, 0, NT, NT, NT,};
int timeouts_ASKED_FOR_MSG_DELIVERY[STATE_MAX_PLUS_ONE] = {
0, NT, RT, NT, NT, NT, NT, NT, NT, NT, 60, 10, NT, 0, NT, NT, NT,};
int timeouts_DELETE_ME_STATE[STATE_MAX_PLUS_ONE] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,};
int timeouts_AWAITING_REGISTER_HANDSET[STATE_MAX_PLUS_ONE] = {
0, NT, 0, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, 1, 0, NT,};
int timeouts_REGISTER_HANDSET[STATE_MAX_PLUS_ONE] = {
0, NT, 0, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, 1, 1, 2,};
int timeouts_ASKED_TO_REGISTER_HANDSET[STATE_MAX_PLUS_ONE] = {
0, NT, 0, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, 1, 1, 10,};
/* Timeout when moving from this state to new state:
NS IS RF AF WD RD AD WS RS AS WM RM AM DM WR RH AR */
#undef NT /* No longer needed */
#undef RT
/* Index to all timeouts. Keep in order! */
int (*SMqueue::timeouts[STATE_MAX_PLUS_ONE])[STATE_MAX_PLUS_ONE] = {
&timeouts_NO_STATE,
&timeouts_INITIAL_STATE,
&timeouts_REQUEST_FROM_ADDRESS_LOOKUP,
&timeouts_ASKED_FOR_FROM_ADDRESS_LOOKUP,
&timeouts_AWAITING_TRY_DESTINATION_IMSI,
&timeouts_REQUEST_DESTINATION_IMSI,
&timeouts_ASKED_FOR_DESTINATION_IMSI,
&timeouts_AWAITING_TRY_DESTINATION_SIPURL,
&timeouts_REQUEST_DESTINATION_SIPURL,
&timeouts_ASKED_FOR_DESTINATION_SIPURL,
&timeouts_AWAITING_TRY_MSG_DELIVERY,
&timeouts_REQUEST_MSG_DELIVERY,
&timeouts_ASKED_FOR_MSG_DELIVERY,
&timeouts_DELETE_ME_STATE,
&timeouts_AWAITING_REGISTER_HANDSET,
&timeouts_REGISTER_HANDSET,
&timeouts_ASKED_TO_REGISTER_HANDSET,
};
string SMqueue::sm_state_strings[STATE_MAX_PLUS_ONE] = {
"No State",
"Initial State",
"Request From-Address Lookup",
"Asked for From-Address",
"Awaiting Try Destination IMSI",
"Request Destination IMSI",
"Asked for Destination IMSI",
"Awaiting Try Destination SIP URL",
"Request Destination SIP URL",
"Asked for Destination SIP URL",
"Awaiting Try Message Delivery",
"Request Message Delivery",
"Asked for Message Delivery",
"Delete Me",
"Awaiting Register Handset",
"Register Handset",
"Asked to Register Handset",
};
string sm_state_string(enum sm_state astate)
{
if (astate < STATE_MAX_PLUS_ONE)
return sm_state_strings[astate];
else return "Invalid State Number";
}
/* Global variables */
bool SMqueue::osip_initialized = false; // Have we called lib's initializer?
struct osip *SMqueue::osipptr = NULL; // Ptr to struct sorta used by library
const char *short_msg_pending::smp_my_ipaddress = NULL; // Accessible copy
const char *short_msg_pending::smp_my_2nd_ipaddress = NULL; // Accessible copy
bool print_as_we_validate = false; // Debugging
/*
* Associative map between target phone numbers (short codes) and
* function pointers that implement those numbers.
*/
short_code_map_t short_code_map;
/* Release memory from osip library */
void
osip_mem_release()
{
if (osip_initialized) {
osip_release (SMqueue::osipptr);
SMqueue::osipptr = NULL;
SMqueue::osip_initialized = false;
}
}
/* ==== FIXME KLUDGE ====
* Table of IMSIs and phone numbers, for translation.
* This is only for test-bench use. Real life uses the Home Location
* Register (../HLR), currently implemented via Asterisk.
*/
static
struct imsi_phone { char imsi[4+15+1]; char phone[1+15+1]; } imsi_phone[] = {
// {"IMSI310260550682564", "100"}, /* Siemens A52 */
{"IMSI666410186585295", "+17074700741"}, /* Nokia 8890 */
{"IMSI777100223456161", "+17074700746"}, /* Palm Treo */
{{0}, {0}}
};
/* Functions */
enum sm_state
handle_sms_message(short_msg_pending *qmsg);
bool
check_to_user(char *user);
char *
read_short_msg_from_file(char *file);
/*
* Utility functions
*/
void
SMqueue::abfuckingort() { (*(char *)0) = 127; } // where is the real one??
// C++ wants you to use <string>. Fuck that. When we want dynamically
// allocated char *'s, then we'll use them. strdup uses malloc, tho, so
// we need a "new/delete" oriented strdup. Ok, simple.
char *
SMqueue::new_strdup(const char *orig)
{
int len = strlen(orig);
char *result = new char[len+1];
strncpy(result, orig, len+1);
return result;
}
/*
* Main state machine for Short Message processing.
*/
/* Timeouts. Back up and try again, sometimes after waiting a while. */
void
SMq::process_timeout()
{
time_t now = time(NULL);
short_msg_p_list::iterator qmsg;
enum sm_state newstate;
/* When we modify a timestamp below (in the set_state function),
we re-queue the message within the queue, so we have to
restart the iterator every time around the loop. In effect,
we're always looking at the top thing on the list (thus the
earliest one in time). */
while (true) {
qmsg = time_sorted_list.begin();
if (qmsg == time_sorted_list.end())
return; /* Empty queue */
if (qmsg->next_action_time > now)
return; /* Wait til later to do more */
switch (qmsg->state) {
case INITIAL_STATE:
// This is the initial state in which a message
// enters the system. Here, the message could be
// a SIP response as well as a SIP MESSAGE -- or
// something we can't process like a SIP REGISTER
// or garbage. Well, actually, garbage was rejected
// earlier before queue insertion.
// From this point onward, we're going to assume
// that the message has valid, reasonable headers
// and contents for our purposes. Centralize all
// that checking in validate_short_msg().
if (MSG_IS_REQUEST(qmsg->parsed)) {
// It's a MESSAGE or invalid REGISTER.
// We support only MESSAGE here.
if (0 != strcmp(qmsg->parsed->sip_method, "MESSAGE")) {
LOG(WARN) << "Invalid incoming SIP message, method is "
<< qmsg->parsed->sip_method;
newstate = NO_STATE;
} else {
// Check for short-code and handle it.
// If handle_short_code() returns true, it sets newstate
// on its own
if (!handle_short_code(short_code_map, qmsg, newstate)) {
// For non-special messages, look up who they're from.
newstate = REQUEST_FROM_ADDRESS_LOOKUP;
}
}
set_state(qmsg, newstate);
break;
} else { // It's a RESPONSE.
handle_response(qmsg);
// The RESPONSE has been deleted in handle_response().
// We go back to the top of the loop.
break;
}
break;
case NO_STATE:
// Messages in NO_STATE have errors in them.
// Dump it to the log, and delete it, so the queue
// won't build up.
qmsg->make_text_valid();
LOG(NOTICE) << "== This message had an error and is being deleted:"
<< endl << "MSG = " << qmsg->text;
// Fall thru into DELETE_ME_STATE!
case DELETE_ME_STATE:
// This message should quietly go away.
{
short_msg_p_list temp;
// Extract the current sm from the time_sorted_list
temp.splice(temp.begin(), time_sorted_list, qmsg);
// When we remove it from the new "temp" list,
// this entry will be deallocated. qmsg still
// points to its (dead) storage, so be careful
// not to reference qmsg (I don't know a C++ way
// to set it to NULL or delete it or something).
temp.pop_front();
}
break;
default:
LOG(ALARM) << "Message timed out with bad state "
<< qmsg->state << " and message: " << qmsg->text;
set_state(qmsg, INITIAL_STATE);
// WTF? Shouldn't we proceed to NO_STATE aka "error state"?
/* NO BREAK */
case REQUEST_FROM_ADDRESS_LOOKUP:
/* Ask to translate the IMSI in the From field
into the phone number. */
newstate = lookup_from_address (&*qmsg);
set_state(qmsg, newstate);
break;
case REQUEST_DESTINATION_IMSI:
/* Ask to translate the destination phone
number in the Request URI into an IMSI. */
newstate = lookup_uri_imsi(&*qmsg);
set_state(qmsg, newstate);
break;
case REQUEST_DESTINATION_SIPURL:
/* Ask to translate the IMSI in the Request URI
into the host/port combo to send it to. */
newstate = lookup_uri_hostport(&*qmsg);
set_state(qmsg, newstate);
break;
case AWAITING_TRY_MSG_DELIVERY:
/* We have waited awhile and now want to try
delivering the message again. */
set_state(qmsg, REQUEST_MSG_DELIVERY);
/* No Break */
case REQUEST_MSG_DELIVERY:
/* We are trying to deliver to the handset now (or
again after congestion). */
// Check for short-code and handle it.
// If handle_short_code() returns true, it sets newstate
// on its own
if (!pack_sms_for_delivery(qmsg))
{
// Error...
set_state(qmsg, NO_STATE);
break;
}
// debug_dump(); // FIXME, remove
// Only print delivering msg if delivering to non-
// localhost.
if (0 != strcmp("127.0.0.1",
qmsg->parsed->req_uri->host)) {
LOG(INFO) << "Delivering '"
<< qmsg->qtag << "' from "
<< qmsg->parsed->from->url->username
// << " for "
// << qmsg->parsed->req_uri->username
<< " at "
<< qmsg->parsed->req_uri->host
<< ":" << qmsg->parsed->req_uri->port
<< ".";
}
// FIXME, if we can't deliver the datagram we
// just do the same thing regardless of the result.
if (my_network.deliver_msg_datagram(&*qmsg))
set_state(qmsg, REQUEST_DESTINATION_SIPURL);
else
set_state(qmsg, REQUEST_DESTINATION_SIPURL);
break;
case ASKED_FOR_MSG_DELIVERY:
/* We sent the message to the handset, but never
got back an ack. Must wait awhile to avoid
flooding the network or the user with dups. */
set_state(qmsg, AWAITING_TRY_MSG_DELIVERY);
break;
case AWAITING_REGISTER_HANDSET:
/* We got a shortcode SMS which succeeded in
associating a phone number with this IMSI.
Now we have to wait for that to take effect
in the HLR, before the next steps. */
// See if the IMSI maps to a caller ID yet.
if (!ready_to_register(qmsg)) {
// Re-up our timeout
set_state(qmsg, AWAITING_REGISTER_HANDSET);
break;
}
set_state(qmsg, REGISTER_HANDSET);
/* No Break */
case REGISTER_HANDSET:
/* We got a shortcode SMS which succeeded in
associating a phone number with this IMSI.
Now we have to associate the IMSI with the
IP addr:port of its cell site. */
newstate = register_handset(qmsg);
set_state(qmsg, newstate);
break;
case ASKED_TO_REGISTER_HANDSET:
/* We got a shortcode SMS which succeeded in
associating a phone number with this IMSI.
We asked to assoc the IMSI with the addr:port
of its cell, but the HLR hasn't answered. */
// we asked to register; if we time out, go back
// and try again.
set_state(qmsg, AWAITING_REGISTER_HANDSET);
break;
}
} /* Repeat from the top. */
}
/*
* We have received a SIP response message (with a status code like 200,
* a message like "Okay", and various other fields). Find the outgoing
* message that matches this response, and do the right thing. The response
* message has already been parsed and validated.
*
* If the response says the message is delivered, delete both the response
* and the message.
*
* In general, delete the response so it won't stay at the front of the
* queue.
*/
void
SMq::handle_response(short_msg_p_list::iterator qmsgit)
{
short_msg_pending *qmsg = &*qmsgit;
short_msg_p_list::iterator sent_msg;
short_msg_p_list resplist;
// First, remove this response message from the queue. That way,
// when we search the queue, we won't find OURSELF. We also don't
// want the response hanging around in the queue anyway.
resplist.splice(resplist.begin(), time_sorted_list, qmsgit);
// We'll delete the list element on our way out of this function as
// resplist goes out of scope.
// figure out what message we're responding to.
if (!find_queued_msg_by_tag(sent_msg, qmsg->qtag, qmsg->qtaghash)) {
// No message in queue.
LOG(NOTICE) << "Couldn't find message for response tag '"
<< qmsg->qtag << "'; response is:" << endl
<< qmsg->text;
// no big problem, just ignore it.
return;
}
// Check what kind of response we got, based on its status code.
// FIXME, logfile output would be useful here.
LOG(NOTICE) << "Got " << qmsg->parsed->status_code
<< " response for sent msg '" << sent_msg->qtag << "' in state "
<< sent_msg->state;
if ( sent_msg->state != ASKED_FOR_MSG_DELIVERY
&& sent_msg->state != REQUEST_MSG_DELIVERY
&& sent_msg->state != REQUEST_DESTINATION_SIPURL
&& sent_msg->state != AWAITING_TRY_MSG_DELIVERY) {
LOG(ERROR) << "*** That's not a pleasant state. ***";
// Don't abort here -- if a msg gets forked, one fork
// gets a redirect/reject, this puts us back into a lookup
// state, then we get a response from another fork, don't die.
// Just quietly keep going.
}
switch (qmsg->parsed->status_code / 100) {
case 1: // 1xx -- interim response
// Just ignore it.
break;
case 2: // 2xx -- success.
// Done! Extract original msg from queue, and toss it.
sent_msg->parse();
if (sent_msg->parsed &&
sent_msg->parsed->sip_method &&
0 == strcmp("REGISTER", sent_msg->parsed->sip_method)) {
// This was a SIP REGISTER message, so we need
// to free up the original SMS Shortcode message that
// started the registration process.
short_msg_p_list::iterator oldsms;
if (!get_link(oldsms, sent_msg)) {
LOG(NOTICE) << "Can't find SMS message for newly "
"registered handset, linktag '"
<< qmsg->linktag << "'.";
// Assume this was a dup after a retry of
// the REGISTER message -- thus this is a
// second REGISTER response, after we already
// handled the original reg SMS. Ignore it.
} else if (
oldsms->state == ASKED_TO_REGISTER_HANDSET
|| oldsms->state == REGISTER_HANDSET
|| oldsms->state == AWAITING_REGISTER_HANDSET) {
// Go back in and rerun the SMS message,
// now that we think we can reply to it.
// Special code in registration processing
// will notice it's a re-reg and just reply
// with a welcome message.
oldsms->set_state(INITIAL_STATE);
} else {
// Orig SMS exists, but not in a normal state.
// Assume that the original SMS is in a
// retry loop somewhere. Ignore it.
// Eventually it'll notice...? FIXME.
}
}
// Whether a response to a REGISTER or a MESSAGE, delete
// the datagram that we sent, which has been responded to.
LOG(INFO) << "Deleting sent message.";
resplist.splice(resplist.begin(),
time_sorted_list, sent_msg);
resplist.pop_front(); // pop and delete the sent_msg.
// FIXME, consider breaking loose any other messages for
// the same destination now.
break;
case 4: // 4xx -- failure by client
// This means the original message was bad. Bounce it.
{
ostringstream errmsg;
errmsg << qmsg->parsed->status_code << " "
<< qmsg->parsed->reason_phrase;
sent_msg->set_state(
bounce_message((&*sent_msg), errmsg.str().c_str()));
}
break;
case 5: // 5xx -- failure by server (poss. congestion)
// FIXME, perhaps we should change its timeout value?? Shorter
// or longer???
LOG(WARN) << "CONGESTION at OpenBTS\?\?!";
break;
case 3: // 3xx -- message needs redirection
case 6: // 6xx -- message rejected (by this destination).
// Try going back through looking up the destination again.
sent_msg->set_state(REQUEST_DESTINATION_IMSI);
break;
default:
LOG(WARN) << "Unknown status code in SIP response.";
break;
}
// On exit, we delete the response message we've been examining
// when resplist goes out of scope.
}
/*
* Find a queued message, based on its tag value. Return an iterator
* that can be used to remove it from the list if desired.
*/
bool
SMq::find_queued_msg_by_tag(short_msg_p_list::iterator &mymsg,
const char *tag, int taghash)
{
short_msg_p_list::iterator x;
for (x = time_sorted_list.begin(); x != time_sorted_list.end(); x++) {
if (taghash == x->qtaghash && !strcmp (tag, x->qtag)) {
mymsg = x;
return true;
}
}
return false;
}
// Same, but figure out the taghash manually.
bool
SMq::find_queued_msg_by_tag(short_msg_p_list::iterator &mymsg,
const char *tag)
{
return find_queued_msg_by_tag(mymsg, tag, mymsg->taghash_of(tag));
}
/*
* Validate a short_msg by parsing it and then checking the parse
* to make sure it has *everything* we need to process and forward it.
* **ALL** validity checks on incoming short_msg's are centralized here.
* Thus the rest of the code doesn't need to muck around checking
* every possible thing -- it can just look at the parts of the message
* that it needs to.
*
* Also, if the qtag hasn't been set yet, set it, based on the message's
* fields.
*
* If we reject a message, return the SIP response code that we should
* return to the sender, diagnosing the problem. If the message is OK,
* result is zero.
*/
int
short_msg_pending::validate_short_msg()
{
osip_message_t *p;
__node_t *plist;
osip_generic_param_t *param;
char *user;
size_t clen;
char *fromtag;
char *endptr;
if (print_as_we_validate) { // debugging
make_text_valid();
LOG(DEBUG) << "MSG = " << this->text;
}
if (!parsed_is_valid) {
if (!parse()) {
return 400;
}
}
/* The message has been parsed. Now check that we like its
structure and contents. */
p = parsed;
if (!p) // this should really be an abort.
return 400;
if (!p->sip_version || 0 != strcmp("SIP/2.0", p->sip_version))
return 400;
// If it's an ack, check some things. If it's a message,
// check others.
if (MSG_IS_RESPONSE(p)) {
// It's an ack.
if (p->status_code < 0
|| !p->reason_phrase)
return 400;
clen = 0;
if (p->content_length && p->content_length->value) {
errno = 0;
clen = strtol(p->content_length->value, &endptr, 10);
if (*endptr != '\0' || errno != 0)
return 400;
}
if (clen != 0)
return 400; // Acks have no content!
if (p->bodies.nb_elt != 0)
return 400; // Acks have no bodies!
} else {
// It's a message.
if (!p->req_uri || !p->req_uri->scheme)
return 400;
if (0 != strcmp("sip", p->req_uri->scheme))
return 416;
if (!check_host_port(p->req_uri->host,p->req_uri->port))
return 484;
if (!p->sip_method)
return 405;
if ( 0 == strcmp("MESSAGE", p->sip_method)) {
user = p->req_uri->username;
if (!user)
return 484;
if (!check_to_user(user))
return 484;
// FIXME, URL versus To: might have different username
// requirements?
// FIXME, add support for more Content-Type's.
if (!p->content_type || !p->content_type->type
|| !p->content_type->subtype
|| !( (0 == strcmp("text", p->content_type->type)
&& 0 == strcmp("plain", p->content_type->subtype))
||(0 == strcmp("application", p->content_type->type)
&& 0 == strcmp("vnd.3gpp.sms", p->content_type->subtype))
)
)
return 415;
if (p->bodies.nb_elt != 1 || !p->bodies.node
|| 0 != p->bodies.node->next
|| !p->bodies.node->element
|| false) // ...
return 413; // "Request entity-body too large"
if (!p->cseq || !p->cseq->method
|| 0 != strcmp("MESSAGE", p->cseq->method)
|| !p->cseq->number) // FIXME, validate number??
return 400;
// contacts -- cannot occur in SIP MESSAGE's
// or most response acks.
if (p->contacts.nb_elt)
return 400;
} else if (0 == strcmp("REGISTER", p->sip_method)) {
// Null username is OK in register message.
// Null content-type is OK.
// Null message body also OK.
if (!p->cseq || !p->cseq->method
|| 0 != strcmp("REGISTER", p->cseq->method)
|| !p->cseq->number) // FIXME, validate number??
return 400;
} else {
return 405; // Unknown SIP datagram type
}
}
// accepts - no restrictions
// accept_encodings - no restrictions?
// accept_langauges - no restrictions?
// alert_infos - no restrictions?
// allows - no restrictions?
// authentication_infos - no restrictions?
// authorizations - no restrictions
if (!p->call_id)
return 400;
// call_infos - no restrictions
// content-encodings - FIXME - no restrictions?
clen = 0;
if (p->content_length && p->content_length->value) {
errno = 0;
clen = strtol(p->content_length->value, &endptr, 10);
if (*endptr != '\0' || errno != 0) // Errs or trailing crud?
return 413;
}
// clen is now the numeric content_length.
// FIXME where is the message body??
// FIXME check the content_length for < 140 chars too */
// But we may have to do that AFTER the encoding, etc is decoded
// error_infos - no restrictions
// From: needs an extra element which is a message tag? FIXME if wrong
if (!p->from || !p->from->url
|| p->from->gen_params.nb_elt < 1
|| !p->from->gen_params.node)
return 400;
plist = (__node_t *) p->from->gen_params.node;
fromtag = NULL;
do {
param = (osip_generic_param_t *) plist->element;
LOG(DEBUG) << "Param " << param->gname << "=" << param->gvalue;
if (!strcmp("tag", param->gname))
fromtag = param->gvalue;
plist = (__node_t *)plist->next;
} while (plist);
if (!fromtag)
return 400;
if (!p->mime_version) {
; // No mime version, OK
} else if (!p->mime_version->value // Version 1.0 is OK.
|| 0 != strcmp("1.0", p->mime_version->value))
return 415; // Any other is NOPE.
// proxy_authenticates - no restrictions
// proxy_authentication_infos -- no restriction
// record_routes -- no restriction
// require -- RFC 3261 sec 20.32: If received, reject this msg!
// Unfortunately, the parser doesn't even seem to recognize it!
// routes -- no restrictions
if (!p->to || !p->to->url || !p->to->url->scheme
|| 0 != strcmp("sip", p->to->url->scheme)
|| !check_host_port(p->to->url->host, p->to->url->port)
|| !p->to->url->username
#if 0
// Asterisk returns a tag on the To: line of its SIP REGISTER
// responses. This violates the RFC but we'd better allow it.
// (We don't really care -- we just ignore it anyway.)
|| p->to->gen_params.nb_elt != 0
#endif
) // no tag field allowed; see
// RFC 3261 sec 8.1.1.2
return 400;
user = p->to->url->username;
if (!check_to_user(user))
return 400;
// The spec wants Via: line even on acks, but do we care? FIXME.
// if (p->vias.nb_elt < 1 || false) // ... FIXME )
// return 400;
// www_authenticates - no restrictions
// headers - ???
// message_property ??? FIXME
// message ??? FIXME
// message_length ??? FIXME
// application_data - no restrictions
// Set the qtag from the parsed fields, if it hasn't been set yet.
return (set_qtag());
}
// Set the qtag from the parsed fields, if it hasn't been set yet.
// Whenever we change any of these fields, we have to recalculate
// the qtag.
// FIXME, we assume that the CSEQ, Call-ID, and From tag all are
// components that, in combination, identify the message uniquely.
// FIXME! The spec is unpleasantly unclear about this.
// Result is 0 for success, or 3-digit integer error code if error.
// FIXME! The Call-ID changes each time we re-send an SMS.
// When we get a 200 "Delivered" message for ANY of those
// different Call-ID's, we should accept that the msg was
// delivered and delete it. Therefore, remove the Call-ID
// from the qtag.
int
short_msg_pending::set_qtag()
{
osip_message_t *p;
__node_t *plist;
osip_generic_param_t *param;
char *fromtag;
if (!parsed_is_valid) {
if (!parse()) {
return 400;
}
}
p = parsed;
if (!p->from)
return 400;
plist = (__node_t *) p->from->gen_params.node;
if (!plist)
return 400;
fromtag = NULL;
do {
param = (osip_generic_param_t *) plist->element;
LOG(DEBUG) << "Param " << param->gname << "=" << param->gvalue;
if (!strcmp("tag", param->gname))
fromtag = param->gvalue;
plist = (__node_t *)plist->next;
} while (plist);
if (!fromtag)
return 400;
if (!p->call_id)
return 401;
if (!p->cseq)
return 402;
delete [] qtag; // slag the old one, if any.
int len = strlen(p->cseq->number) + 2 // crlf or --
#ifdef USE_CALL_ID_TAG
+ strlen(p->call_id->number) + 1 // @
+ strlen(p->call_id->host) + 2 // crlf or --
#endif
+ strlen(fromtag) + 1; // null at end
qtag = new char[len];
// There's probably some fancy C++ way to do this. FIXME.
strcpy(qtag, p->cseq->number);
strcat(qtag, "--");
#ifdef USE_CALL_ID_TAG
strcat(qtag, p->call_id->number);
strcat(qtag, "@");
strcat(qtag, p->call_id->host);
strcat(qtag, "--");
#endif
strcat(qtag, fromtag);
// Check the length calculation, abort if bad.
if (qtag[len-1] != '\0'
|| qtag[len-2] == '\0')
abfuckingort();
// Set the taghash too.
// FIXME, if we set this with a good hash function,
// our linear searches will run much faster, avoiding
// almost all strcmp's. For now, punt easy.
qtaghash = taghash_of(qtag);
return 0;
}
/*
* Hash a tag value for fast searches.
*/
// FIXME, if we set this with a good hash function,
// our linear searches will run much faster, avoiding
// almost all strcmp's. For now, punt easy.
int
short_msg_pending::taghash_of (const char *fromtag)
{
return fromtag[0];
}
/* Check the host and port number specified.
* Currently, be conservative and only take localhost refs.
* We know FIXME that this will have to be expanded...
*/
bool
short_msg_pending::check_host_port (char *host, char *port)
{
static int warn_once;
if (!host)
return false;
if (!strcmp ("localhost", host) ||
!strcmp ("127.0.0.1", host) ||
(smp_my_ipaddress && !strcmp (smp_my_ipaddress, host)) ||
(smp_my_2nd_ipaddress && !strcmp (smp_my_2nd_ipaddress, host))) {
;
} else {
if (0 == warn_once++) {
LOG(NOTICE) << "Accepting SIP Message from " << host <<
" for SMS delivery, even though it's not "
"from localhost.";
}
// In theory we should be *routing* it to where it
// specifies -- but the address is *probably* us, and
// we have no way to tell what our own address is,
// since (in a real configuration) we're behind a NAT
// firewall at an address that never appears in ifconfig,
// for example. Must assume it's OK -- for now. FIXME.
return true;
}
// port can either be specified or not.
// we could check whether it's all digits... FIXME
return true;
}
/*
* Check the username in the To: field, perhaps in the From: fiend,
* perhaps in the URI in the MESSAGE line at the top...
*/
bool
check_to_user (char *user)
{
// For now, don't check -- but port some checks up from the
// code below in lookup_from_address. FIXME.
return true;
}
/*
* Make it possible for one message to link to another (by name).
* We use names rather than pointers because it's independent of the
* memory management. Cost = a search of the queue.
*/
// Set the linktag of "newmsg" to point to oldmsg.
void
SMq::set_linktag(short_msg_p_list::iterator newmsg,
short_msg_p_list::iterator oldmsg)
{
if (!oldmsg->qtag) {
oldmsg->set_qtag();
}
size_t len = strlen(oldmsg->qtag);
newmsg->linktag = new char[1+len];
strncpy(newmsg->linktag, oldmsg->qtag, len);
newmsg->linktag[len] = '\0';
}
// Get the other message that this message links to.
// Result is true if found, false if not.
bool
SMq::get_link(short_msg_p_list::iterator &oldmsg,
short_msg_p_list::iterator qmsg)
{
char *alink;
alink = qmsg->linktag;
if (!alink)
return false;
return find_queued_msg_by_tag(oldmsg, alink);
}
/*
* Originate half of a short message
* Put it in the queue and start handling it, but don't actually
* finish it or send it; return it to the caller for further mucking with.
*
* In particular, the caller must set the:
* uri
* From:
* To:
* Content-Type and message body, if any.
*
* Method is which type of SIP packet (MESSAGE, RESPONSE, etc).
* Result is a short_msg_p_list containing one short_msg_pending, with
* the half-initialized message in it.
*/
short_msg_p_list *
SMq::originate_half_sm(string method)
{
short_msg_p_list *smpl;
short_msg_pending *response;
osip_via_t *via;
char *temp, *p, *mycallnum;
const char *myhost;
smpl = new short_msg_p_list (1);
response = &*smpl->begin(); // Here's our short_msg_pending!
response->initialize (0, NULL, true);
osip_message_init(&response->parsed);
response->parsed_is_valid = true;
if (!have_register_call_id || method != "REGISTER") {
// If it's a MESSAGE, or if it's the first REGISTER,
// it needs a new Call-ID.
myhost = my_ipaddress.c_str();
mycallnum = my_network.new_call_number();
osip_call_id_init(&response->parsed->call_id);
p = (char *)osip_malloc (strlen(myhost)+1);
strcpy(p, myhost);
osip_call_id_set_host (response->parsed->call_id, p);
p = (char *)osip_malloc (strlen(mycallnum)+1);
strcpy(p, mycallnum);
osip_call_id_set_number (response->parsed->call_id, p);
if (method == "REGISTER") {
// Save the new call-ID for all subsequent registers
char *my_callid;
if (osip_call_id_to_str (response->parsed->call_id,
&my_callid)) {
abfuckingort();
}
register_call_id = string(my_callid);
osip_free (my_callid);
register_call_seq = 0;
have_register_call_id = true;
}
} else if (method == "REGISTER") {
// Copy the saved call-ID
osip_call_id_init(&response->parsed->call_id);
osip_call_id_parse(response->parsed->call_id,
register_call_id.c_str());
}
ostringstream cseqline;
unsigned int cseq;
if (method == "REGISTER") {
cseq = ++register_call_seq;
} else {
cseq = my_network.new_random_number();
cseq &= 0xFFFF; // for short readable numbers
}
cseqline << cseq << " " << method;
osip_message_set_cseq(response->parsed, cseqline.str().c_str());
osip_message_set_method (response->parsed, osip_strdup(method.c_str()));
osip_message_set_via(response->parsed, "SIP/2.0/UDP x 1234;branch=123");
// FIXME, don't assume UDP, allow TCP here too.
osip_message_get_via (response->parsed, 0, &via);
temp = via_get_host(via);
osip_free(temp);
via_set_host(via, osip_strdup(my_ipaddress.c_str()));
temp = via_get_port(via);
osip_free(temp);
via_set_port(via, osip_strdup(my_udp_port.c_str()));
// We've altered the text, and the parsed version controls.
response->parsed_was_changed();
// Return our half-baked message (in a list for easy std::list mgmt).
return smpl;
}
/*
* Originate a short message
* Put it in the queue and start handling it.
* From is a shortcode or phone number or something.
* To is an IMSI or phone number,
* msgtext is plain ASCII text.
* firststate is REQUEST_DESTINATION_IMSI if to is a phone number.
* or REQUEST_DESTINATION_SIPURL if to is an IMSI already.
* Result is 0 for success, negative for error.
*/
int
SMq::originate_sm(const char *from, const char *to, const char *msgtext,
enum sm_state firststate)
{
short_msg_p_list *smpl;
short_msg_pending *response;
int errcode;
smpl = originate_half_sm("MESSAGE");
response = &*smpl->begin(); // Here's our short_msg_pending!
// Plain text SIP MESSAGE should be repacked before delivery
response->need_repack = true;
// For the tag, we cheat and reuse the cseq number.
// I don't see any reason not to...why do we have three different
// tag fields scattered around?
ostringstream fromline;
fromline << from << "<sip:" << from << "@" << my_ipaddress << ">;tag="
<< response->parsed->cseq->number;
osip_message_set_from(response->parsed, fromline.str().c_str());
ostringstream toline;
toline << "<sip:" << to << "@" << my_ipaddress << ">";
osip_message_set_to(response->parsed, toline.str().c_str());
ostringstream uriline;
uriline << "sip:" << to << "@" << my_ipaddress << ":5602";
osip_uri_init(&response->parsed->req_uri);
osip_uri_parse(response->parsed->req_uri, uriline.str().c_str());
osip_message_set_content_type(response->parsed, "text/plain");
response->content_type = short_msg::TEXT_PLAIN;
size_t len = strlen(msgtext);
if (len > SMS_MESSAGE_MAX_LENGTH)
len = SMS_MESSAGE_MAX_LENGTH;
osip_message_set_body(response->parsed, msgtext, len);
// We've altered the text and the parsed version controls.
response->parsed_was_changed();
// Now that we set the From tag, we have to create the queue tag.
response->set_qtag();
// Now turn it into a text and then parse it for validity
response->make_text_valid();
response->unparse();
errcode = response->validate_short_msg();
if (!errcode) {
insert_new_message (*smpl, firststate);
}
delete smpl;
return errcode? -1: 0;
}
/*
* Send a bounce message, based on an existing queued message.
* Return the state to set the original bouncing message to.
*/
enum sm_state
SMq::bounce_message(short_msg_pending *sent_msg, const char *errstr)
{
ostringstream errmsg;
char *username;
std::string thetext;
int status;
username = sent_msg->parsed->to->url->username;
thetext = sent_msg->get_text();
LOG(NOTICE) << "Bouncing " << sent_msg->qtag << " from "
<< sent_msg->parsed->from->url->username // his phonenum
<< " to " << username << ": " << errstr;
errmsg << "Can't send your SMS to " << username << ": ";
if (errstr)
errmsg << errstr << ": " << thetext;
else
errmsg << "can't send: " << thetext;
// Don't bounce a message from us - it makes endless loops.
status = 1;
if (0 != strcmp(gConfig.getStr("Bounce.Code"), sent_msg->parsed->from->url->username))
{
// But do bounce anything else.
char *bounceto = sent_msg->parsed->from->url->username;
bool bounce_to_imsi = 0 == strncmp("IMSI", bounceto, 4)
|| 0 == strncmp("imsi", bounceto, 4);
status = originate_sm(gConfig.getStr("Bounce.Code"), // Read from a config
bounceto, // to his phonenum or IMSI
errmsg.str().c_str(), // error msg
bounce_to_imsi? REQUEST_DESTINATION_SIPURL: // dest is IMSI
REQUEST_DESTINATION_IMSI); // dest is phonenum
}
if (status == 0) {
return DELETE_ME_STATE;
} else {
return NO_STATE; // Punt to debug.
}
}
/*
* See if the handset's imsi and phone number are in the HLR database yet,
* since if it isn't, we can't register the imsi at its cell's host:port yet.
*/
bool
SMq::ready_to_register (short_msg_p_list::iterator qmsg)
{
char *callerid, *imsi;
qmsg->parse();
if (!qmsg->parsed ||
!qmsg->parsed->from ||
!qmsg->parsed->from->url)
return false;
imsi = qmsg->parsed->from->url->username;
callerid = my_hlr.getCLIDLocal(imsi);
return (callerid != NULL);
}
/*
* Register a handset's IMSI with its cell, by sending Asterisk
* a SIP REGISTER message that we "relay" from the cell.
* (We actually originate it, but we pretend that the cell sent it to us.
* Actually the cell sent us an incoming shortcode SMS from an
* unregistered phone, which is in the queue in REGISTER_HANDSET state.)
*
* Argument qmsg is the SMS message, with its From line still an IMSI.
* We register "IMSI@HLRhost:HLRport" at the sip uri:
* "IMSI@cellhost:cellport".
*/
enum sm_state
SMq::register_handset (short_msg_p_list::iterator qmsg)
{
short_msg_p_list *smpl;
short_msg_pending *response;
int errcode;
char *imsi;
smpl = originate_half_sm("REGISTER");
response = &*smpl->begin(); // Here's our short_msg_pending!
// SIP REGISTER should not be repacked before delivery
response->need_repack = false;
imsi = qmsg->parsed->from->url->username;
// The To: line is the long-term name being registered.
ostringstream toline;
toline << imsi << "<sip:" << imsi << "@" << my_register_hostport << ">";
osip_message_set_to(response->parsed, toline.str().c_str());
// The From: line is the same, plus a tag.
// However, we steal the tag from our cseq, since we don't care
// about it much.
ostringstream fromline;
fromline << toline.str() << ";tag=" << response->parsed->cseq->number;
osip_message_set_from(response->parsed, fromline.str().c_str());
// URI in the first line: insert SIP HLR's host/port.
ostringstream uriline;
uriline << "sip:" << my_register_hostport;
osip_uri_init(&response->parsed->req_uri);
osip_uri_parse(response->parsed->req_uri, uriline.str().c_str());
// Contact: field specifies where we're registering from.
ostringstream contactline;
contactline << "<sip:" << imsi << "@";
contactline << my_network.string_addr((struct sockaddr *)qmsg->srcaddr,
qmsg->srcaddrlen, true);
contactline << ">;expires=3600";
osip_message_set_contact(response->parsed, contactline.str().c_str());
// We've altered the fields, and the parsed version controls.
response->parsed_was_changed();
// Now that we set the From tag, we have to create the queue tag.
response->set_qtag();
// Set the linktag of our REGISTER message to point to qmsg (SMS msg)
set_linktag(smpl->begin(), qmsg);
// Now turn it into a text and then parse it for validity
response->make_text_valid();
response->unparse();
errcode = response->validate_short_msg();
if (errcode) {
abfuckingort(); // Our msg should be valid
}
// Pop new SIP REGISTER out of the smpl queue-of-one and
// into the real queue, where it will very soon be delivered.
insert_new_message (*smpl, REQUEST_MSG_DELIVERY);
// We can't reference response, or *smpl, any more...
delete smpl;
// The next state of the original (SMS shortcode) message is...
return ASKED_TO_REGISTER_HANDSET;
}
bool SMq::handle_short_code(const short_code_map_t &short_code_map,
short_msg_p_list::iterator qmsg,
sm_state &next_state)
{
osip_body_t *bod1;
std::string bods;
short_func_t shortfn;
short_code_map_t::const_iterator shortit;
enum short_code_action sca;
short_code_params params;
int status;
string short_code;
short_code = qmsg->parsed->req_uri->username;
shortit = short_code_map.find (short_code);
if (shortit == short_code_map.end()) {
return false;
}
/* Messages to certain addresses are special commands */
shortfn = shortit->second;
bods = qmsg->get_text();
// Set up arguments and access pointers, then call
// the short-code function to process it.
params.scp_retries = qmsg->retries;
params.scp_smq = this;
params.scp_qmsg_it = qmsg;
LOG(INFO) << "Short-code SMS "
<< qmsg->parsed->req_uri->username
<< " with text \"" << bods << "\"";
sca = (*shortfn) (qmsg->parsed->from->url->username, // imsi
bods.data(), // msg text
&params);
// The short-code function asks us to do something when
// it's done. Do it.
switch (sca) {
case SCA_REPLY:
LOG(INFO) << "Short-code replies: "
<< params.scp_reply;
status = originate_sm(
qmsg->parsed->req_uri->username, // from shortcode
qmsg->parsed->from->url->username,// to his IMSI
params.scp_reply, REQUEST_DESTINATION_SIPURL);
if (!status) {
next_state = DELETE_ME_STATE; // Done!
return true;
}
LOG(NOTICE) << "Reply failed " << status << "!";
// NO BREAK
default:
case SCA_INTERNAL_ERROR:
LOG(ERROR) << "Error in short-code function "
<< qmsg->parsed->req_uri->username
<< "(" << bods << "): " << params.scp_reply;
next_state = NO_STATE;
return true;
case SCA_EXEC_SMQUEUE:
reexec_smqueue = true;
stop_main_loop = true;
next_state = DELETE_ME_STATE;
return true;
case SCA_QUIT_SMQUEUE:
stop_main_loop = true;
next_state = DELETE_ME_STATE;
return true;
case SCA_DONE:
next_state = DELETE_ME_STATE;
return true;
case SCA_RETRY_AFTER_DELAY:
// FIXME, timeout is implicit in set_state table,
// rather than taken from params.scp_delay.
qmsg->retries++;
next_state = REQUEST_FROM_ADDRESS_LOOKUP;
return true;
case SCA_AWAIT_REGISTER:
// We just linked the phone# to the IMSI, but we
// have to wait til the HLR updates, before
// we can link the IMSI to the originating IP address
// and port number of its cell.
next_state = AWAITING_REGISTER_HANDSET;
return true;
case SCA_REGISTER:
next_state = register_handset(qmsg);
return true;
case SCA_TREAT_AS_ORDINARY:
break; // fall through into non-special case.
case SCA_RESTART_PROCESSING:
next_state = INITIAL_STATE;
return true;
}
return false;
}
/* Change the From address to a valid phone number in + countrycode phonenum
format. Also add a Via: line about us. */
enum sm_state
SMq::lookup_from_address (short_msg_pending *qmsg)
{
char *host = qmsg->parsed->from->url->host;
bool got_phone = false;
char *scheme = qmsg->parsed->from->url->scheme;
if (!scheme) return NO_STATE;
if (0 != strcmp("sip", scheme)) return NO_STATE;
char *username = qmsg->parsed->from->url->username;
if (!username) return NO_STATE;
if (!host) return NO_STATE;
if (0 != strcmp("127.0.0.1", host)
&& 0 != strcmp("localhost", host)
&& 0 != strcmp(my_ipaddress.c_str(), host)
&& 0 != strcmp(my_2nd_ipaddress.c_str(), host)) {
// This isn't a phone number.
//
// There's a bizarre convention to move the email address
// into the message text, followed by a space... GSM 03.40 sec 3.8
ostringstream newtext;
osip_body_t *bod1;
if (qmsg->parsed->bodies.nb_elt == 1) {
bod1 = (osip_body_t *)qmsg->parsed->bodies.node->element;
} else {
return NO_STATE; // Punt on msg w/no text
}
newtext << qmsg->parsed->from->url->username << "@"
<< host << " " << qmsg->get_text();
osip_free(bod1->body);
bod1->body = osip_strdup(newtext.str().c_str());
bod1->length = strlen(bod1->body);
// Change From address to a local shortcode
osip_free(qmsg->parsed->from->url->username);
osip_free(qmsg->parsed->from->url->host);
qmsg->parsed->from->url->username = osip_strdup ("211");
qmsg->parsed->from->url->host = osip_strdup (my_ipaddress.c_str());
got_phone = true;
}
// Insert a Via: line describing us, this makes us easier to trace,
// and also allows a remote SIP agent to reply to us. (Maybe?)
osip_via_t *via;
char *temp;
osip_message_append_via(qmsg->parsed, "SIP/2.0/UDP x:1234;branch=123");
// FIXME, don't assume UDP, allow TCP here too.
osip_message_get_via (qmsg->parsed, 0, &via);
temp = via_get_host(via);
osip_free(temp);
via_set_host(via, osip_strdup(my_ipaddress.c_str()));
temp = via_get_port(via);
osip_free(temp);
via_set_port(via, osip_strdup(my_udp_port.c_str()));
/* Username can be in various formats. Check for formats that
we know about. Anything else we punt. */
if (got_phone || username[0] == '+' || isdigit(username[0])) {
/* We have a phone number. This is what we want.
So we're done, and can move on to the next part
of processing the short_msg. */
return REQUEST_DESTINATION_IMSI;
}
/* If we have "imsi" on the front, strip it. */
char *tryuser = username;
if ((username[0] == 'i'||username[0]=='I')
&& (username[1] == 'm'||username[1]=='M')
&& (username[2] == 's'||username[2]=='S')
&& (username[3] == 'i'||username[3]=='I')) {
tryuser += 4;
}
/* http://en.wikipedia.org/wiki/International_Mobile_Subscriber_Identity */
size_t len = strlen (tryuser);
if (len != 15 && len != 14) {
/* This is not an IMSI. Punt. */
return NO_STATE;
}
/* Look up the IMSI in the Home Location Register. */
char *newfrom;
newfrom = my_hlr.getCLIDLocal(username);
if (!newfrom) {
/* ==================FIXME KLUDGE====================
* Here is our fake table of IMSIs and phone numbers
* ==================FIXME KLUDGE==================== */
for (int i = 0; imsi_phone[i].imsi[0]; i++) {
if (0 == strcmp(imsi_phone[i].imsi, username)) {
newfrom = strdup(imsi_phone[i].phone);
break;
}
}
}
if (!newfrom) {
LOG(NOTICE) << "Lookup IMSI <" << username
<< "> to phonenum failed.";
LOG(DEBUG) << qmsg->text;
return bounce_message (qmsg,
gConfig.getStr("Bounce.Message.IMSILookupFailed")
);
// return NO_STATE; // Put it into limbo for debug.
}
/* We found it! Translation done!
Now the dance of freeing the old name and
inserting new name. */
char *p;
osip_free (qmsg->parsed->from->url->username);
p = (char *)osip_malloc (strlen(newfrom)+1);
strcpy(p, newfrom);
qmsg->parsed->from->url->username = p;
qmsg->parsed_was_changed();
free(newfrom); // C interface uses free() not delete.
return REQUEST_DESTINATION_IMSI;
}
/* Change the Request-URI's address to a valid IMSI. */
enum sm_state
SMq::lookup_uri_imsi (short_msg_pending *qmsg)
{
qmsg->parse();
char *scheme = qmsg->parsed->req_uri->scheme;
if (!scheme) return NO_STATE;
if (0 != strcmp("sip", scheme)) return NO_STATE;
char *host = qmsg->parsed->req_uri->host;
if (!host) return NO_STATE;
if (0 != strcmp("127.0.0.1", host)
&& 0 != strcmp("localhost", host)
&& 0 != strcmp(my_ipaddress.c_str(), host)
&& 0 != strcmp(my_2nd_ipaddress.c_str(), host))
return NO_STATE;
char *username = qmsg->parsed->req_uri->username;
if (!username) return NO_STATE;
/* Username can be in various formats. Check for formats that
we know about. Anything else we punt. */
if (username[0] == '+' || ( 0 != strncmp("imsi", username, 4)
&& 0 != strncmp("IMSI", username, 4))) {
// We have a phone number. It needs translation.
char *newdest = my_hlr.getIMSI(username);
if (!newdest) {
/* ==================FIXME KLUDGE====================
* Here is our fake table of IMSIs and phone numbers
* ==================FIXME KLUDGE==================== */
for (int i = 0; imsi_phone[i].phone[0]; i++) {
if (0 == strcmp(imsi_phone[i].phone, username)) {
newdest = strdup(imsi_phone[i].imsi);
break;
}
}
}
// It had better say "imsi" if it's an IMSI, else
// lookup_uri_hostport will get confused.
if (newdest
&& 0 != strncmp("imsi", newdest, 4)
&& 0 != strncmp("IMSI", newdest, 4)) {
free(newdest);
newdest = NULL;
}
if (!newdest) {
/* Didn't find it in HLR or fake table. Bitch. */
// We have to return an error to the originator.
LOG(NOTICE) << "Lookup phonenum '" << username << "' to IMSI failed.";
LOG(DEBUG) << "MSG = " << qmsg->text;
if (global_relay.c_str()[0] == '\0'
|| !my_hlr.useGateway(username)) {
// There's no global relay -- or the HLR says not to
// use the global relay for it -- so send a bounce.
return bounce_message (qmsg, gConfig.getStr("Bounce.Message.NotRegistered"));
} else {
// Send the message to our global relay.
// We leave the username as a phone number, and
// let it pass on to look up the destination
// SIP URL (which is the global relay).
//
// However, the From address is at this point the
// sender's local ph#. Map it to the global ph#.
char *newfrom;
newfrom = my_hlr.mapCLIDGlobal(
qmsg->parsed->from->url->username);
if (newfrom) {
osip_free(qmsg->parsed->from->url->username);
qmsg->parsed->from->url->username =
osip_strdup (newfrom);
}
return REQUEST_DESTINATION_SIPURL;
}
}
/* We found it! Translation done!
Now the dance of freeing the old name and
inserting new name. */
char *p;
osip_free (qmsg->parsed->req_uri->username);
p = (char *)osip_malloc (strlen(newdest)+1);
strcpy(p, newdest);
qmsg->parsed->req_uri->username = p;
qmsg->parsed_was_changed();
free(newdest); // C interface uses free() not delete
return REQUEST_DESTINATION_SIPURL;
}
/* If we have "imsi" on the front, scan past it. */
if (username[0] == 'i' && username[1] == 'm'
&& username[2] == 's' && username[3] == 'i') {
username += 4;
}
/* http://en.wikipedia.org/wiki/International_Mobile_Subscriber_Identity */
size_t len = strlen (username);
if (len != 15 && len != 14) {
/* This is not an IMSI. Punt. */
return NO_STATE;
}
/* We have an IMSI already. Now figure out how to route it. */
return REQUEST_DESTINATION_SIPURL;
}
/*
* Look up the hostname and port number where we should send Short Messages
* for the IMSI (or phone number, if we're using a global relay)
* in the To address.
*
* This is also where we assign a new Call-ID to the message, so that
* re-sends will use the same Call-ID, but re-locate's (looking up the
* recipient's location again) will use a new one.
*/
enum sm_state
SMq::lookup_uri_hostport (short_msg_pending *qmsg)
{
qmsg->parse();
char *imsi = qmsg->parsed->req_uri->username;
char *p, *mycallnum;
char *newhost, *newport;
const char *myhost;
if (!imsi) return NO_STATE;
/* Username can be in various formats. Check for formats that
we know about. Anything else we punt. */
if (imsi[0] == '+' || (0 != strncmp("imsi", imsi, 4)
&& 0 != strncmp("IMSI", imsi, 4))) {
// We have a phone number. It needs translation.
newport = NULL;
newhost = strdup(global_relay.c_str());
} else {
/* imsi is an IMSI at this point. */
newport = NULL;
newhost = my_hlr.getRegistrationIP (imsi);
}
if (newhost) {
// Break up returned "host:port" string.
char *colon = strchr(newhost,':');
if (!colon) {
free(newhost);
newhost = NULL;
newport = NULL;
} else {
newport = strdup(colon+1);
*colon = '\0';
}
}
// KLUDGE! KLUDGE! KLUDGE! for testing only
if (!newhost) {
newhost = strdup((char *)"127.0.0.1");
}
if (!newport) {
newport = strdup((char *)"5062");
}
/* We found it! Translation done!
Now the dance of freeing the old ones and
inserting new ones. */
if (0 != strcmp (newhost, qmsg->parsed->req_uri->host))
{
osip_free (qmsg->parsed->req_uri->host);
p = (char *)osip_malloc (strlen(newhost)+1);
strcpy(p, newhost);
qmsg->parsed->req_uri->host = p;
qmsg->parsed_was_changed();
}
if (qmsg->parsed->req_uri->port != newport)
{
osip_free (qmsg->parsed->req_uri->port);
p = newport;
if (newport) {
p = (char *)osip_malloc (strlen(newport)+1);
strcpy(p, newport);
}
qmsg->parsed->req_uri->port = p;
qmsg->parsed_was_changed();
}
// We've altered the message, it's a new message, and it needs
// a new Call-ID so it won't be confused with the old message.
myhost = my_ipaddress.c_str();
mycallnum = my_network.new_call_number();
if (!qmsg->parsed->call_id) {
osip_call_id_init(&qmsg->parsed->call_id);
}
if (0 != strcmp(myhost,
osip_call_id_get_host (qmsg->parsed->call_id))) {
osip_free (osip_call_id_get_host (qmsg->parsed->call_id));
p = (char *)osip_malloc (strlen(myhost)+1);
strcpy(p, myhost);
osip_call_id_set_host (qmsg->parsed->call_id, p);
qmsg->parsed_was_changed();
}
if (0 != strcmp(mycallnum,
osip_call_id_get_number (qmsg->parsed->call_id))) {
osip_free (osip_call_id_get_number (qmsg->parsed->call_id));
p = (char *)osip_malloc (strlen(mycallnum)+1);
strcpy(p, mycallnum);
osip_call_id_set_number (qmsg->parsed->call_id, p);
qmsg->parsed_was_changed();
}
// Now that we changed the Call-ID, we have to update the queue tag.
qmsg->set_qtag();
// Both of these were dynamic storage; don't leak them.
// (They were allocated by malloc() so we free with free().
// Note that osip_malloc isn't necessarily malloc -- and
// neither of them is necessarily new or delete .)
free(newhost);
free(newport);
// OK, we're done; next step is to deliver it to that host & port!
return REQUEST_MSG_DELIVERY;
}
/*
* Helper function because C++ is fucked about types.
* and the osip library doesn't keep its types straight.
*/
int
osip_via_clone2 (void *via, void **dest)
{
return osip_via_clone ((const osip_via_t *)via,
(osip_via_t **)dest);
}
/*
* After we received a datagram, send a SIP response message
* telling the sender what we did with it. (Unless the datagram we
* received was already a SIP response message...)
*/
void
SMq::respond_sip_ack(int errcode, short_msg_pending *smp,
char *netaddr, size_t netaddrlen)
{
string phrase;
short_msg response;
bool okay;
if (!smp->parse())
return; // Don't ack invalid SIP messages.
if (MSG_IS_RESPONSE(smp->parsed))
return; // Don't ack a response message, or we loop!
osip_message_init(&response.parsed);
response.parsed_is_valid = true;
// Copy over the CSeq, From, To, Call-ID, Via, etc.
osip_to_clone(smp->parsed->to, &response.parsed->to);
osip_from_clone(smp->parsed->from, &response.parsed->from);
osip_cseq_clone(smp->parsed->cseq, &response.parsed->cseq);
osip_call_id_clone(smp->parsed->call_id, &response.parsed->call_id);
osip_list_clone(&smp->parsed->vias, &response.parsed->vias,
&osip_via_clone2);
// Start a new Via: line.
ostringstream newvia;
// FIXME, don't assume UDP, allow TCP here too.
newvia << "SIP/2.0/UDP " << my_ipaddress << ":" << my_udp_port
<< ";branch=1;received="
<< my_network.string_addr((struct sockaddr *)netaddr, netaddrlen,
false);
osip_message_append_via(response.parsed, newvia.str().c_str());
// Make a nice message.
switch (errcode) {
case 100: phrase="Trying..."; break;
case 200: phrase="Okay!"; break;
case 202: phrase="Queued"; break;
case 400: phrase="Bad Request"; break;
case 401: phrase="Un Author Ized"; break;
case 403: phrase="Forbidden - first register, by texting your 10-digit phone number to 101."; break;
case 404: phrase="Phone Number Not Registered"; break; // Not Found
case 405: phrase="Method Not Allowed";
osip_message_set_allow(response.parsed, "MESSAGE");
break;
case 413: phrase="Message Body Size Error"; break;
case 415: phrase="Unsupported Content Type";
osip_message_set_accept(response.parsed, "text/plain, application/vnd.3gpp.sms");
break;
case 416: phrase="Unsupported URI scheme (not SIP)"; break;
case 480: phrase="Recipient Temporarily Unavailable"; break;
case 484: phrase="Address Incomplete"; break;
default: phrase="Error Message Table Needs Updating"; break;
}
osip_message_set_status_code (response.parsed, errcode);
osip_message_set_reason_phrase (response.parsed,
osip_strdup((char *)phrase.c_str()));
// We've altered the text and the parsed version controls.
response.parsed_was_changed();
// Now turn it into a datagram and hustle it home.
response.make_text_valid();
LOG(INFO) << "Responding with \"" << errcode << " " << phrase << "\".";
okay = my_network.send_dgram(response.text, strlen(response.text),
netaddr, netaddrlen);
if (!okay)
LOG(ERROR) << "send_dgram had trouble sending the response.";
}
//
// The main loop that listens for incoming datagrams, handles them
// through the queue, and moves them toward transmission.
//
void
SMq::main_loop()
{
int len; // MUST be signed -- not size_t!
// else we can't see -1 for errors...
int timeout, mstimeout;
short_msg_p_list *smpl;
short_msg_pending *smp;
char buffer[5000];
short_msg_p_list::iterator qmsg;
time_t now;
int errcode;
stop_main_loop = false;
while (!stop_main_loop) {
now = time(NULL);
qmsg = time_sorted_list.begin();
if (qmsg == time_sorted_list.end()) {
timeout = -1; // Infinite timeout
} else {
timeout = qmsg->next_action_time - now;
if (timeout < 0) timeout = 0; // Check for incoming anyway
}
mstimeout = 1000 * timeout;
#undef DEBUG_Q
#ifdef DEBUG_Q
LOG(DEBUG) << "=== Top of main_loop: queue:";
debug_dump();
LOG(DEBUG) << "============== End of queue. timeout = " << timeout;
#else
char timebuf[26+/*slop*/4]; //
ctime_r(&now, timebuf);
timebuf[19] = '\0'; // Leave out space, year and newline
if (timeout < 0) {
LOG(INFO) << "=== " << timebuf+4 << " "
<< time_sorted_list.size() << " queued; "
<< "waiting.";
} else {
LOG(INFO) << "=== " << timebuf+4 << " "
<< time_sorted_list.size() << " queued; "
<< timeout << " seconds til "
<< sm_state_string(qmsg->state)
<< " for " << qmsg->qtag;
}
#endif
len = my_network.get_next_dgram(buffer, sizeof(buffer), mstimeout);
if (len < 0) {
// Error.
LOG(ERROR) << "Error from get_next_dgram: " << strerror(errno);
// Just continue...
} else if (len == 0) {
// Timeout. Just push things along.
LOG(DEBUG) << "Timeout...";
} else {
// We got a datagram. Dump it into the queue, copying it.
//
// Here we do a bit of tricky memory allocation. Rather
// than make a short_msg_pending and then have to COPY it
// into a short_msg_p_list (including malloc-ing all the
// possible pointed-to stuff and then freeing all the original
// strings and things), we make a short_msg_p_list
// and create in it a single default element. Then we fill
// in that element as our new short_msg_pending. This lets
// us (soon) link it into the main message queue list,
// without ever copying it.
//
// HOWEVER! The implementation of std::list in GNU C++
// (ver. 4.3.3) has a bug: it does not PERMIT a class to be
// made into
// a list UNLESS it allows copy-construction of its instances.
// You get an extremely inscrutable error message deep
// in the templated bowels of stl_list.h , referencing
// the next non-comment line of this file.
// THUS, we can't check at compile time to prevent the
// making of copies of short_msg_pending's -- instead, we
// have to do that check at runtime (allowing the default
// newly-initialized one to be copied, but aborting with
// any depth of stuff in it).
smpl = new short_msg_p_list (1);
smp = &*smpl->begin(); // Here's our short_msg_pending!
smp->initialize (len, buffer, false);
smp->ms_to_sc = true;
if (my_network.recvaddrlen <= sizeof (smp->srcaddr)) {
smp->srcaddrlen = my_network.recvaddrlen;
memcpy(smp->srcaddr, my_network.src_addr,
my_network.recvaddrlen);
}
errcode = smp->validate_short_msg();
if (errcode == 0) {
if (MSG_IS_REQUEST(smp->parsed)) {
LOG(NOTICE) << "Got SMS '"
<< smp->qtag << "' from "
<< smp->parsed->from->url->username
<< " for "
<< smp->parsed->req_uri->username
<< ".";
} else {
LOG(INFO) << "Got SMS "
<< smp->parsed->status_code
<< " Response '"
<< smp->qtag << "'.";
}
insert_new_message (*smpl);
errcode = 202; // Accepted and queued.
} else {
LOG(WARN) << "Received bad " << errcode
<< " datagram:" << endl
<< "BADMSG = " << smp->text;
}
// It's OK to reference "smp" here, whether it's in the
// smpl list, or has been moved into the main time_sorted_list.
respond_sip_ack (errcode, smp, smp->srcaddr, smp->srcaddrlen);
// We won't leak memory if we didn't queue it up, since
// the delete of smpl will delete anything still
// in ITS list.
delete smpl;
}
process_timeout();
} /* while (!stop_main_loop) */
}
/* Debug dump of SMq and mainly the queue. */
void SMq::debug_dump() {
short_msg_p_list::iterator x = time_sorted_list.begin();
time_t now = time(NULL);
for (; x != time_sorted_list.end(); ++x) {
x->make_text_valid();
LOG(DEBUG) << "== State: " << sm_state_string (x->state) << "\t"
<< (x->next_action_time - now) << endl << "MSG = "
<< x->text;
}
}
/* Print net addr in hex. Returns a static buffer. */
char *
netaddr_fmt(char *srcaddr, unsigned len)
{
static char buffer[999];
char *bufp = buffer;
buffer[0] = 0;
*bufp++ = '=';
while (len > 0) {
snprintf(bufp, 3, "%02x", *(unsigned char *)srcaddr);
bufp+= 2;
len--;
}
return buffer;
}
/* Parse a string of hex into a net address and length */
bool
netaddr_parse(const char *str, char *addr, unsigned int *len)
{
if (str[0] != '=')
return false;
str++;
int xlen = strlen(str);
int retlen = xlen/2;
if (xlen != retlen*2)
return false;
*len = retlen;
char *myaddr = addr;
const char *strp = str;
while (retlen > 0) {
char blah[3];
unsigned int mybyte;
blah[0] = strp[0]; blah[1] = strp[1]; blah[2] = '\0';
sscanf(blah, "%x", &mybyte);
*myaddr++ = mybyte;
strp+= 2;
retlen--;
}
return true;
}
/*
* Save queue to file.
*
* We save in reverse timestamp order, to make it very fast to insert when
* re-read.
*/
bool
SMq::save_queue_to_file(std::string qfile)
{
short_msg_p_list::reverse_iterator x = time_sorted_list.rbegin();
ofstream ofile;
unsigned howmany = 0;
ofile.open(qfile.c_str(), ios::out | ios::binary | ios::trunc);
if (!ofile.is_open())
return false;
for (; x != time_sorted_list.rend(); ++x) {
x->make_text_valid();
ofile << "=== " << (int) x->state << " "
<< x->next_action_time << " "
<< my_network.string_addr((struct sockaddr *)x->srcaddr, x->srcaddrlen, true) << " "
<< strlen(x->text)
<< x->ms_to_sc << x->need_repack << endl
<< x->text << endl << endl;
howmany++;
}
bool result = !ofile.fail();
ofile.close();
if (result) {
LOG(INFO) << "Saved " << howmany << " queued messages to " << qfile;
} else {
LOG(ERROR) << "FAILED to save " << howmany << " queued messages to " << qfile;
}
return result;
}
/*
* Read a new queue from file.
*/
bool
SMq::read_queue_from_file(std::string qfile)
{
ifstream ifile;
std::string equals;
unsigned astate, atime, alength;
unsigned ms_to_sc, need_repack;
std::string short_code;
std::string netaddrstr;
sm_state mystate;
time_t mytime;
char *msgtext;
unsigned howmany = 0, howmanyerrs = 0;
char ignoreme;
short_msg_p_list *smpl;
short_msg_pending *smp;
int errcode;
ifile.open (qfile.c_str(), ios::in | ios::binary);
if (!ifile.is_open())
return false;
while (!ifile.eof()) {
ifile >> equals >> astate >> atime;
if (equals != "===") {
if (ifile.eof())
break;
abfuckingort();
}
ifile >> netaddrstr;
ifile >> alength;
ifile >> ms_to_sc;
ifile >> need_repack;
while (ifile.peek() == '\n')
ignoreme = ifile.get();
msgtext = new char[alength+2];
// Get alength chars (or until null char, hope there are none)
ifile.get(msgtext, alength+1, '\0');
while (ifile.peek() == '\n')
ignoreme = ifile.get();
howmany++;
mystate = (SMqueue::sm_state)astate;
mytime = atime;
smpl = new short_msg_p_list (1);
smp = &*smpl->begin(); // Here's our short_msg_pending!
smp->initialize (alength, msgtext, true);
// We use the just-allocated msgtext; it gets freed after
// delivery of message.
// Restore saved state
smp->ms_to_sc = ms_to_sc;
smp->need_repack = need_repack;
smp->srcaddrlen = 0;
if (!my_network.parse_addr(netaddrstr.c_str(), smp->srcaddr, sizeof(smp->srcaddr), &smp->srcaddrlen))
abfuckingort();
errcode = smp->validate_short_msg();
if (errcode == 0) {
if (MSG_IS_REQUEST(smp->parsed)) {
LOG(INFO) << "Read SMS '"
<< smp->qtag << "' from "
<< smp->parsed->from->url->username
<< " for "
<< smp->parsed->req_uri->username
<< " direction=" << (smp->ms_to_sc?"MS->SC":"SC->MS")
<< " need_repack=" << (smp->need_repack?"true":"false");
} else {
LOG(WARN) << "Read bad SMS "
<< smp->parsed->status_code
<< " Response '"
<< smp->qtag << "':" << msgtext;
}
insert_new_message (*smpl, mystate, mytime);
} else {
LOG(ERROR) << "Read bad " << errcode
<< " message:" << endl
<< "BADMSG = " << smp->text;
howmanyerrs++;
}
delete smpl;
}
LOG(INFO) << "=== Read " << howmany << " messages total, " << howmanyerrs
<< " bad ones.";
ifile.close();
return true;
}
#if 0
/*
* Read in a message from a file. Return malloc'd char block of the whole
* thing.
*/
char *
read_msg_text_from_file(char *fname, size_t *lptr)
{
ifstream ifs;
size_t length, got;
char *mess;
int i;
ifs.open (fname, ios::in | ios::binary | ios::ate);
if (!ifs.is_open())
return NULL;
length = ifs.tellg(); // we opened it at the end
mess = new char[length+1];
if (!mess) {
ifs.close();
*lptr = length; // may as well return the length
return NULL; // even though we fail.
}
ifs.seekg (0, ios::beg); // Now go back to the beginning.
got = ifs.readsome (mess, length+1);
mess[length] = '\0'; // terminate the string
i = ifs.eof(); // We should be at EOF.
// but due to bugs in iostreams, we aren't. FIXME.
i = ifs.bad(); // But we shouldn't have bad().
ifs.close();
if (i || got != length)
return NULL;
*lptr = length;
return mess;
}
// FIXME - this needs work to adjust to the new insert_new_message
// memory allocation paradigm.
short_msg_pending *
read_short_msg_pending_from_file(char *fname)
{
short_msg_pending *smp;
size_t length;
char *sip_text;
sip_text = read_msg_text_from_file (fname, &length);
if (!sip_text)
return NULL;
// FIXME! We should also be able to read the state, timeout, etc.
// FIXME! This'll be needed for writing/restoring the queue.
smp = new short_msg_pending (length, sip_text, true);
return smp;
}
#endif
/* Really simple first try */
int
main(int argc, char **argv)
{
bool please_re_exec = false;
// short_msg_p_list aq;
// short_msg *sm;
// short_msg_pending *smp;
std::string savefile;
// Set up short-code commands users can type
init_smcommands(&short_code_map);
// This scope lets us delete the smq (and the network sockets)
// before we re-exec ourself.
while (true) {
SMq smq; /* Our big state machine & msg queue */
// Configure the logger.
if (gConfig.defines("Log.FileName")) {
gSetLogFile(gConfig.getStr("Log.FileName"));
}
// IP address:port of the Home Location Register that we send SIP
// REGISTER messages to.
smq.set_register_hostport(gConfig.getStr("Asterisk.address"));
// IP address:port of the global relay where we sent SIP messages
// if we don't know where else to send them.
smq.set_global_relay(gConfig.getStr("SIP.global_relay"));
// IP address of our own smqueue, as seen from outside.
smq.set_my_ipaddress(gConfig.getStr("SIP.myIP" /* "127.0.0.1" */));
smq.set_my_2nd_ipaddress(gConfig.getStr("SIP.myIP2" /* "NAT crap" */));
// Port number that we (smqueue) listen on.
smq.init_listener(gConfig.getStr("SIP.myPort")); // Port number to listen on.
// Debug -- print all msgs in log
// print_as_we_validate = gConfig.getBool("Debug.print_as_we_validate");
print_as_we_validate = gConfig.defines("Debug.print_as_we_validate");
// system() calls in backgrounded jobs hang if stdin is still open on tty.
// So, close it.
close(0); // Shut off stdin in case we're in background
open("/dev/null", O_RDONLY); // fill it with nullity
LOG(INFO) << "My own IP address is configured as " << smq.my_ipaddress;
LOG(INFO) << "The HLR registry is at " << smq.my_register_hostport;
savefile = gConfig.getStr("savefile");
if (!smq.read_queue_from_file (savefile)) {
LOG(WARN) << "Failed to read queue from file " << savefile;
}
LOG(INFO) << "Queue contains " << smq.time_sorted_list.size() << " msgs.";
// smq.debug_dump();
smq.stop_main_loop = false;
smq.reexec_smqueue = false;
smq.main_loop();
// The rest of this code never gets run (unless main_loop exits
// based upon getting a "reboot" sms or signal or something).
if (smq.reexec_smqueue) {
LOG(WARN) << "====== Re-Execing! ======";
if (!smq.save_queue_to_file(savefile)) {
LOG(ERROR) << "OUCH! Could not save queue to file " << savefile;
}
please_re_exec = true;
break; // Get out of scope that contains smq, closing file descrs.
} else {
LOG(NOTICE) << "====== Quitting! ======";
if (!smq.save_queue_to_file(savefile)) {
LOG(ERROR) << "OUCH! Could not save queue to file " << savefile;
}
// smq.debug_dump();
break; // don't re-exec the main loop.
}
}
// Free up any OSIP stuff, to make valgrind squeaky clean.
osip_mem_release();
if (please_re_exec)
execvp(argv[0], argv);
return 0;
}