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/smcommands.cpp

388 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 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 <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;
if (gConfig.defines("SC.SMSC.Code")) {
(*scm)[gConfig.getStr("SC.SMSC.Code")] = shortcode_smsc;
}
// (*scm)["666"] = shortcode_text_access;
}