384 lines
12 KiB
C++
384 lines
12 KiB
C++
|
/*
|
||
|
* Smcommands.cpp - Short Message (SMS) commands ("shortcodes") for OpenBTS.
|
||
|
* Written by John Gilmore, August 2009.
|
||
|
*
|
||
|
* Configuration table added by David A. Burgess, January 2010.
|
||
|
*
|
||
|
* Copyright 2009, 2010 Free Software Foundation, Inc.
|
||
|
*
|
||
|
* This program is free software: you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License as published by
|
||
|
* the Free Software Foundation, either version 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 General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU 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 <iostream>
|
||
|
#include <fstream>
|
||
|
#include <string>
|
||
|
#include <cstdlib> // strtol
|
||
|
#include <time.h> // ctime_r
|
||
|
|
||
|
#include <Configuration.h>
|
||
|
|
||
|
using namespace std;
|
||
|
using namespace SMqueue;
|
||
|
|
||
|
extern ConfigurationTable gConfig;
|
||
|
|
||
|
|
||
|
/* Seekrit SMS message to make the server quit for valgrind leak checking. */
|
||
|
enum short_code_action
|
||
|
whiplash_quit (const char *imsi, const char *msgtext, short_code_params *scp)
|
||
|
{
|
||
|
const char* psswd = gConfig.getStr("SC.WhiplashQuit.Password");
|
||
|
unsigned len = strlen(psswd);
|
||
|
|
||
|
/* Messages to this code are special commands, perhaps */
|
||
|
if (!strncmp(psswd, msgtext, len)) {
|
||
|
if (!strncmp("quit", msgtext+len+1, 4)) {
|
||
|
return SCA_QUIT_SMQUEUE;
|
||
|
}
|
||
|
if (!strncmp("exec", msgtext+len+1, 4)) {
|
||
|
return SCA_EXEC_SMQUEUE;
|
||
|
}
|
||
|
if (!strncmp("testsave", msgtext+len+1, 4)) {
|
||
|
scp->scp_smq->save_queue_to_file(gConfig.getStr("SC.WhiplashQuit.SaveFile"));
|
||
|
scp->scp_reply = new_strdup("Done.");
|
||
|
return SCA_REPLY;
|
||
|
}
|
||
|
scp->scp_reply = new_strdup("Unknown Command");
|
||
|
return SCA_REPLY;
|
||
|
}
|
||
|
return SCA_TREAT_AS_ORDINARY;
|
||
|
}
|
||
|
|
||
|
enum short_code_action
|
||
|
shortcode_debug_dump (const char *imsi, const char *msgtext,
|
||
|
short_code_params *scp)
|
||
|
{
|
||
|
scp->scp_smq->debug_dump();
|
||
|
return SCA_DONE;
|
||
|
}
|
||
|
|
||
|
enum short_code_action
|
||
|
shortcode_quick_chk (const char *imsi, const char *msgtext,
|
||
|
short_code_params *scp)
|
||
|
{
|
||
|
ostringstream answer;
|
||
|
|
||
|
answer << scp->scp_smq->time_sorted_list.size() << " queued.";
|
||
|
scp->scp_reply = new_strdup(answer.str().c_str());
|
||
|
return SCA_REPLY;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* 411 -- information.
|
||
|
* Start by telling people their phone number.
|
||
|
*/
|
||
|
enum short_code_action
|
||
|
shortcode_four_one_one (const char *imsi, const char *msgtext,
|
||
|
short_code_params *scp)
|
||
|
{
|
||
|
ostringstream answer;
|
||
|
|
||
|
short_msg_p_list::iterator x;
|
||
|
int n = 0, missing = 0, registering = 0, bouncing = 0;
|
||
|
|
||
|
for (x = scp->scp_smq->time_sorted_list.begin();
|
||
|
x != scp->scp_smq->time_sorted_list.end(); x++) {
|
||
|
n++;
|
||
|
switch (x->state) {
|
||
|
case REQUEST_DESTINATION_SIPURL:
|
||
|
case REQUEST_MSG_DELIVERY:
|
||
|
case ASKED_FOR_MSG_DELIVERY:
|
||
|
case AWAITING_TRY_MSG_DELIVERY:
|
||
|
x->make_text_valid();
|
||
|
if (0 == strcmp ("127.0.0.1", x->parsed->req_uri->host))
|
||
|
missing++;
|
||
|
if (0 == strcmp (gConfig.getStr("SC.Register.Code"),x->parsed->from->url->username))
|
||
|
registering++;
|
||
|
if (0 == strcmp (gConfig.getStr("SC.Register.Code"), x->parsed->req_uri->username))
|
||
|
registering++;
|
||
|
if (0 == strcmp (gConfig.getStr("SC.Info.Code"), x->parsed->from->url->username))
|
||
|
bouncing++;
|
||
|
break;
|
||
|
|
||
|
case AWAITING_REGISTER_HANDSET:
|
||
|
case REGISTER_HANDSET:
|
||
|
case ASKED_TO_REGISTER_HANDSET:
|
||
|
registering++;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
;
|
||
|
}
|
||
|
}
|
||
|
answer << n << " queued";
|
||
|
if (missing)
|
||
|
answer << ", " << missing << " unlocatable";
|
||
|
if (registering)
|
||
|
answer << ", " << registering << " registering";
|
||
|
if (bouncing)
|
||
|
answer << ", " << bouncing << " bouncing";
|
||
|
|
||
|
answer << ", ";
|
||
|
const char *mycell = scp->scp_smq->my_network.string_addr (
|
||
|
(sockaddr *)scp->scp_qmsg_it->srcaddr,
|
||
|
scp->scp_qmsg_it->srcaddrlen, false).c_str();
|
||
|
mycell += strlen(mycell) - 3; // Hack to only show last 3 digits
|
||
|
answer << "cell " << mycell;
|
||
|
|
||
|
answer << ", ";
|
||
|
char *username = scp->scp_qmsg_it->parsed->from->url->username;
|
||
|
answer << username;
|
||
|
|
||
|
answer << ", ";
|
||
|
char *newfrom = scp->scp_smq->my_hlr.getCLIDLocal(username);
|
||
|
answer << "phonenum " << newfrom;
|
||
|
|
||
|
time_t now = time(NULL);
|
||
|
char timebuf[26+/*slop*/4]; //
|
||
|
answer << ", ";
|
||
|
ctime_r(&now, timebuf);
|
||
|
timebuf[19] = '\0'; // Leave out space, year and newline
|
||
|
answer << "at " << timebuf+4; // Leave out day of week
|
||
|
|
||
|
answer << ", ";
|
||
|
answer << "'" << msgtext << "'";
|
||
|
|
||
|
scp->scp_reply = new_strdup(answer.str().c_str());
|
||
|
return SCA_REPLY;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Remove a message from the queue, by its tag.
|
||
|
* If first char is "-", don't reply, just do it.
|
||
|
* (this keeps the reply out of the queue, while debugging.)
|
||
|
* If argument is "6000", then delete any queued message with
|
||
|
* timeout greater than 5000 seconds.
|
||
|
*/
|
||
|
enum short_code_action
|
||
|
shortcode_zap_queued (const char *imsi, const char *msgtext,
|
||
|
short_code_params *scp)
|
||
|
{
|
||
|
ostringstream answer;
|
||
|
short_msg_p_list::iterator sent_msg;
|
||
|
short_msg_p_list resplist;
|
||
|
bool noreply = false;
|
||
|
|
||
|
if (msgtext[0] == '-') {
|
||
|
noreply = true;
|
||
|
msgtext++;
|
||
|
}
|
||
|
|
||
|
if (!strcmp(gConfig.getStr("SC.ZapQueued.Password"), msgtext)) {
|
||
|
// Delete all messages in queue in NO_STATE state or with
|
||
|
// huge timeouts.
|
||
|
short_msg_p_list::iterator x;
|
||
|
time_t toolate = 5000
|
||
|
+ scp->scp_smq->time_sorted_list.begin()->gettime();
|
||
|
int n = 0;
|
||
|
|
||
|
for (x = scp->scp_smq->time_sorted_list.begin();
|
||
|
x != scp->scp_smq->time_sorted_list.end(); x++) {
|
||
|
if (x->state == NO_STATE || toolate <= x->next_action_time) {
|
||
|
n++;
|
||
|
resplist.splice(resplist.begin(),
|
||
|
scp->scp_smq->time_sorted_list, x);
|
||
|
resplist.pop_front(); // pop and delete the sent_msg.
|
||
|
}
|
||
|
}
|
||
|
answer << "Removed " << n << " messages.";
|
||
|
} else {
|
||
|
// figure out what message we're deleting, by tag
|
||
|
if (!scp->scp_smq->find_queued_msg_by_tag(sent_msg, msgtext)) {
|
||
|
// No message in queue.
|
||
|
answer << "No message queued with tag '" << msgtext
|
||
|
<< "'.";
|
||
|
} else {
|
||
|
answer << "Deleting queued msg '" << msgtext
|
||
|
<< " in state " << sent_msg->state
|
||
|
<< " and timeout "
|
||
|
<< sent_msg->next_action_time - sent_msg->gettime();
|
||
|
resplist.splice(resplist.begin(),
|
||
|
scp->scp_smq->time_sorted_list, sent_msg);
|
||
|
resplist.pop_front(); // pop and delete the sent_msg.
|
||
|
}
|
||
|
}
|
||
|
|
||
|
scp->scp_reply = new_strdup(answer.str().c_str());
|
||
|
return noreply? SCA_DONE: SCA_REPLY;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Register the user's phone with the HLR.
|
||
|
*
|
||
|
* This is how new phones get into the system!
|
||
|
* The message includes the user's phone number, and possible arguments
|
||
|
* afterward.
|
||
|
*
|
||
|
* This shortcode message can be processed several times, if we need
|
||
|
* to do retries because of locked files, for example.
|
||
|
*/
|
||
|
enum short_code_action
|
||
|
shortcode_register (const char *imsi, const char *msgtext,
|
||
|
short_code_params *scp)
|
||
|
{
|
||
|
SMq *smq = scp->scp_smq;
|
||
|
ostringstream answer;
|
||
|
char *existing, *phonenum = NULL, *q;
|
||
|
const char *p;
|
||
|
int seendig;
|
||
|
bool seenplus;
|
||
|
bool badnum;
|
||
|
int exclaim;
|
||
|
HLR::Status did;
|
||
|
|
||
|
phonenum = new char[strlen(msgtext)+1];
|
||
|
// FIXME, error-check this sucker very well!
|
||
|
// Simple parse.
|
||
|
q = phonenum;
|
||
|
exclaim = 0;
|
||
|
seendig = 0;
|
||
|
seenplus = false;
|
||
|
badnum = false;
|
||
|
for (p = msgtext; *p != 0 && !badnum; p++) {
|
||
|
switch (*p) {
|
||
|
case '0': case '1': case '2': case '3': case '4':
|
||
|
case '5': case '6': case '7': case '8': case '9':
|
||
|
*q++ = *p;
|
||
|
seendig++;
|
||
|
break;
|
||
|
case '+':
|
||
|
if (!seendig && !seenplus) {
|
||
|
*q++ = *p;
|
||
|
seenplus++;
|
||
|
} else {
|
||
|
answer << "Error: + can only be the first character in a phone number";
|
||
|
badnum++;
|
||
|
}
|
||
|
break;
|
||
|
case ' ': case '(': case ')': case '\r': case '\n':
|
||
|
break;
|
||
|
case '!':
|
||
|
exclaim++;
|
||
|
break;
|
||
|
default:
|
||
|
answer << "Error: invalid character '" << *p << "' in requested number";
|
||
|
badnum++;
|
||
|
}
|
||
|
}
|
||
|
*q++ = '\0'; // Null-terminate it.
|
||
|
if (!badnum) {
|
||
|
bool override = gConfig.defines("SC.Register.Digits.Override") && (exclaim==3);
|
||
|
if (!override) {
|
||
|
if ((seendig > gConfig.getNum("SC.Register.Digits.Max")) ||
|
||
|
(seendig < gConfig.getNum("SC.Register.Digits.Min"))) {
|
||
|
answer << phonenum << " not valid with " << seendig << " digits. "
|
||
|
<< "Must be " << gConfig.getStr("SC.Register.Digits.Min")
|
||
|
<< " to " << gConfig.getStr("SC.Register.Digits.Max") << " digits.";
|
||
|
badnum++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!badnum) {
|
||
|
|
||
|
existing = smq->my_hlr.getCLIDLocal(imsi);
|
||
|
if (existing) {
|
||
|
// There are two ways to get here. One is to send a
|
||
|
// registration shortcode when you've already registered.
|
||
|
// The other is that we were re-called after final
|
||
|
// registration of the handset. Figure which.
|
||
|
if (0 == strcmp(existing, phonenum)) {
|
||
|
answer << gConfig.getStr("SC.Register.Msg.WelcomeA")
|
||
|
<< ' ' << phonenum << ' '
|
||
|
<< gConfig.getStr("SC.Register.Msg.WelcomeB");
|
||
|
} else {
|
||
|
answer << gConfig.getStr("SC.Register.Msg.AlreadyA")
|
||
|
<< ' ' << existing << ' '
|
||
|
<< gConfig.getStr("SC.Register.Msg.AlreadyB");
|
||
|
}
|
||
|
} else {
|
||
|
existing = smq->my_hlr.getIMSI(phonenum);
|
||
|
if (existing) {
|
||
|
LOG(DEBUG) << phonenum << " is already in the HLR";
|
||
|
answer << gConfig.getStr("SC.Register.Msg.TakenA")
|
||
|
<< ' ' << phonenum << ' '
|
||
|
<< gConfig.getStr("SC.Register.Msg.TAkenB");
|
||
|
} else {
|
||
|
LOG(DEBUG) << phonenum << " is not in the HLR";
|
||
|
// Neither the IMSI nor the phonenum is in use.
|
||
|
// Book 'em, danno!
|
||
|
did = smq->my_hlr.addUser(imsi, phonenum);
|
||
|
switch (did) {
|
||
|
case HLR::SUCCESS:
|
||
|
// Phone#<->IMSI is set up; now register
|
||
|
// the IMSI<->cell IP:port mapping.
|
||
|
delete [] phonenum;
|
||
|
return SCA_REGISTER;
|
||
|
|
||
|
case HLR::DELAYED: // FIXME, force reload later.
|
||
|
delete [] phonenum;
|
||
|
return SCA_AWAIT_REGISTER;
|
||
|
|
||
|
case HLR::FAILURE:
|
||
|
default:
|
||
|
answer << gConfig.getStr("SC.Register.Msg.ErrorA")
|
||
|
<< ' ' << phonenum << ' '
|
||
|
<< gConfig.getStr("SC.Register.Msg.ErrorB")
|
||
|
<< imsi << '.';
|
||
|
break;
|
||
|
|
||
|
case HLR::TRYAGAIN:
|
||
|
// Locked database, have to retry soon.
|
||
|
scp->scp_delay = 10; // seconds
|
||
|
delete [] phonenum;
|
||
|
return SCA_RETRY_AFTER_DELAY;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
delete [] phonenum;
|
||
|
LOG(DEBUG) << "answering \"" << answer.str() << "\"";
|
||
|
scp->scp_reply = new_strdup(answer.str().c_str());
|
||
|
return SCA_REPLY;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Here is where we list all the functions that we care to make
|
||
|
* available -- along with their phone numbers.
|
||
|
*/
|
||
|
void
|
||
|
SMqueue::init_smcommands (short_code_map_t *scm)
|
||
|
{
|
||
|
if (gConfig.defines("SC.Register.Code"))
|
||
|
(*scm)[gConfig.getStr("SC.Register.Code")] = shortcode_register;
|
||
|
if (gConfig.defines("SC.Info.Code"))
|
||
|
(*scm)[gConfig.getStr("SC.Info.Code")] = shortcode_four_one_one;
|
||
|
if (gConfig.defines("SC.DebugDump.Code"))
|
||
|
(*scm)[gConfig.getStr("SC.DebugDump.Code")] = shortcode_debug_dump;
|
||
|
if (gConfig.defines("SC.QuickChk.Code"))
|
||
|
(*scm)[gConfig.getStr("SC.QuickChk.Code")] = shortcode_quick_chk;
|
||
|
if (gConfig.defines("SC.ZapQueued.Code"))
|
||
|
(*scm)[gConfig.getStr("SC.ZapQueued.Code")] = shortcode_zap_queued;
|
||
|
if (gConfig.defines("SC.WhiplashQuit.Code"))
|
||
|
(*scm)[gConfig.getStr("SC.WhiplashQuit.Code")] = whiplash_quit;
|
||
|
// (*scm)["666"] = shortcode_text_access;
|
||
|
}
|