Merge branch 'sms-split'
* sms-split: smqueue: Add SMSC short code to smqueue.config.example Checking in forgotten smsc.cpp and smsc.h files. Ignore User-Data-Header in SMS TPDU when decoding text in TLUserData::encode7bit(). Transparently pass TP-UHDI (User-Data-Header-Indicator) from SMS-SUBMIT to SMS-DELIVER. Slightly cleaner output in TLUserData::write(). Log contents of a message instead of its memory addresss in SMS::parseTPDU(). Use decoded text from original message in bounce and e-mail messages. More readable bounce message. Maximum SMS length is 160 symbols in default alphabet. Avoid duplication of "IMSI" in SC.Register.Msg. Set Log.Alarms.* values in smqueue config. Make "from" address for bounce messages configurable. Move most of the SMS processing to smqueue (initial check-in). Better documentation and error reporting for ThreadSemaphore. Implementation of Semaphore. Implemented "trans" CLI command to list all active transacions.
This commit is contained in:
commit
3ea07de75c
|
@ -290,6 +290,19 @@ int tmsis(int argc, char** argv, ostream& os, istream& is)
|
|||
}
|
||||
|
||||
|
||||
/** Print the transactions table. */
|
||||
int trans(int argc, char** argv, ostream& os, istream& is)
|
||||
{
|
||||
if (argc!=1) return BAD_NUM_ARGS;
|
||||
|
||||
// os << "TMSI IMSI IMEI age used" << endl;
|
||||
gTransactionTable.dump(os);
|
||||
os << endl << gTransactionTable.size() << " transactions in table" << endl;
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
int findimsi(int argc, char** argv, ostream& os, istream& is)
|
||||
{
|
||||
|
@ -759,6 +772,7 @@ Parser::Parser()
|
|||
addCommand("help", showHelp, "[command] -- list available commands or gets help on a specific command.");
|
||||
addCommand("exit", exit_function, "[wait] -- exit the application, either immediately, or waiting for existing calls to clear with a timeout in seconds");
|
||||
addCommand("tmsis", tmsis, "[\"clear\"] or [\"dump\" filename] -- print/clear the TMSI table or dump it to a file.");
|
||||
addCommand("trans", trans, "-- print the transactions table.");
|
||||
addCommand("findimsi", findimsi, "[IMSIPrefix] -- prints all imsi's that are prefixed by IMSIPrefix");
|
||||
addCommand("sendsms", sendsms, "<IMSI> <src> -- send SMS to <IMSI>, addressed from <src>, after prompting.");
|
||||
addCommand("sendrrlp", sendrrlp, "<IMSI> <hexstring> -- send RRLP message <hexstring> to <IMSI>.");
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include "Threads.h"
|
||||
#include "Timeval.h"
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -96,6 +97,67 @@ void Signal::wait(Mutex& wMutex, unsigned timeout) const
|
|||
pthread_cond_timedwait(&mSignal,&wMutex.mMutex,&waitTime);
|
||||
}
|
||||
|
||||
ThreadSemaphore::Result ThreadSemaphore::wait(unsigned timeoutMs)
|
||||
{
|
||||
Timeval then(timeoutMs);
|
||||
struct timespec waitTime = then.timespec();
|
||||
int s;
|
||||
while ((s = sem_timedwait(&mSem,&waitTime)) == -1 && errno == EINTR)
|
||||
continue;
|
||||
|
||||
if (s == -1)
|
||||
{
|
||||
if (errno == ETIMEDOUT)
|
||||
{
|
||||
return TSEM_TIMEOUT;
|
||||
}
|
||||
return TSEM_ERROR;
|
||||
}
|
||||
return TSEM_OK;
|
||||
}
|
||||
|
||||
ThreadSemaphore::Result ThreadSemaphore::wait()
|
||||
{
|
||||
int s;
|
||||
while ((s = sem_wait(&mSem)) == -1 && errno == EINTR)
|
||||
continue;
|
||||
|
||||
if (s == -1)
|
||||
{
|
||||
return TSEM_ERROR;
|
||||
}
|
||||
return TSEM_OK;
|
||||
}
|
||||
|
||||
ThreadSemaphore::Result ThreadSemaphore::trywait()
|
||||
{
|
||||
int s;
|
||||
while ((s = sem_trywait(&mSem)) == -1 && errno == EINTR)
|
||||
continue;
|
||||
|
||||
if (s == -1)
|
||||
{
|
||||
if (errno == EAGAIN)
|
||||
{
|
||||
return TSEM_TIMEOUT;
|
||||
}
|
||||
return TSEM_ERROR;
|
||||
}
|
||||
return TSEM_OK;
|
||||
}
|
||||
|
||||
ThreadSemaphore::Result ThreadSemaphore::post()
|
||||
{
|
||||
if (sem_post(&mSem) != 0)
|
||||
{
|
||||
if (errno == EOVERFLOW)
|
||||
{
|
||||
return TSEM_OVERFLOW;
|
||||
}
|
||||
return TSEM_ERROR;
|
||||
}
|
||||
return TSEM_OK;
|
||||
}
|
||||
|
||||
void Thread::start(void *(*task)(void*), void *arg)
|
||||
{
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include <pthread.h>
|
||||
#include <iostream>
|
||||
#include <assert.h>
|
||||
#include <semaphore.h>
|
||||
|
||||
class Mutex;
|
||||
|
||||
|
@ -120,7 +121,62 @@ class Signal {
|
|||
|
||||
};
|
||||
|
||||
/** Semaphore */
|
||||
class ThreadSemaphore {
|
||||
|
||||
private:
|
||||
|
||||
sem_t mSem;
|
||||
|
||||
public:
|
||||
|
||||
enum Result {
|
||||
TSEM_OK, ///< Success.
|
||||
TSEM_TIMEOUT, ///< wait() or trywait() timed out.
|
||||
TSEM_OVERFLOW, ///< post() overflows a semaphore
|
||||
TSEM_ERROR ///< Generic error.
|
||||
};
|
||||
|
||||
/** Create and initialize semaphore.
|
||||
* @param[in] value - initial semaphore value.
|
||||
*/
|
||||
ThreadSemaphore(unsigned value = 0)
|
||||
{
|
||||
int s = sem_init(&mSem,0,value);
|
||||
assert(s == 0);
|
||||
}
|
||||
|
||||
~ThreadSemaphore() { sem_destroy(&mSem); }
|
||||
|
||||
/** Wait for semaphore to be signaled with timeout.
|
||||
* @param[in] timeoutMs - timeout in milliseconds
|
||||
*
|
||||
* @retval TSEM_OK on success.
|
||||
* @retval TSEM_TIMEOUT on timeout.
|
||||
* @retval TSEM_ERROR on error.
|
||||
*/
|
||||
Result wait(unsigned timeoutMs);
|
||||
|
||||
/** Wait for semaphore to be signaled infinitely.
|
||||
* @retval TSEM_OK on success.
|
||||
* @retval TSEM_ERROR on error.
|
||||
*/
|
||||
Result wait();
|
||||
|
||||
/** Check if semaphore has been signaled and disarm it.
|
||||
* @retval TSEM_OK is semaphore is signaled.
|
||||
* @retval TSEM_TIMEOUT if semaphore is not signaled.
|
||||
* @retval TSEM_ERROR on error.
|
||||
*/
|
||||
Result trywait();
|
||||
|
||||
/** Signal semaphore.
|
||||
* @retval TSEM_OK on success.
|
||||
* @retval TSEM_ERROR on error.
|
||||
*/
|
||||
Result post();
|
||||
|
||||
};
|
||||
|
||||
#define START_THREAD(thread,function,argument) \
|
||||
thread.start((void *(*)(void*))function, (void*)argument);
|
||||
|
|
|
@ -343,6 +343,18 @@ size_t TransactionTable::size()
|
|||
}
|
||||
|
||||
|
||||
void TransactionTable::dump(ostream& os) const
|
||||
{
|
||||
mLock.lock();
|
||||
TransactionMap::const_iterator tp = mTable.begin();
|
||||
while (tp != mTable.end()) {
|
||||
os << hex << "0x" << tp->first << " " << dec << tp->second << endl;
|
||||
++tp;
|
||||
}
|
||||
mLock.unlock();
|
||||
}
|
||||
|
||||
|
||||
void Control::clearTransactionHistory( TransactionEntry& transaction )
|
||||
{
|
||||
SIP::SIPEngine& engine = transaction.SIP();
|
||||
|
|
|
@ -611,6 +611,9 @@ class TransactionTable {
|
|||
//@}
|
||||
|
||||
size_t size();
|
||||
|
||||
/** Write entries as text to a stream. */
|
||||
void dump(std::ostream&) const;
|
||||
};
|
||||
|
||||
//@} // Transaction Table
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sstream>
|
||||
#include <GSMLogicalChannel.h>
|
||||
#include <GSML3MMMessages.h>
|
||||
#include "ControlCommon.h"
|
||||
|
@ -86,99 +87,8 @@ L3Frame* getFrameSMS(LogicalChannel *LCH, GSM::Primitive primitive=DATA)
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**@name Functions for transmitting through various gateways. */
|
||||
//@{
|
||||
|
||||
|
||||
/** Substitute spaces with +'s for compatibility with some protocols. */
|
||||
void convertText(char *dest, const char *src)
|
||||
bool sendSIP(const L3MobileIdentity &mobileID, const char* address, const char* body)
|
||||
{
|
||||
// FIXME -- We should just do full "percent encoding" here.
|
||||
while (*src != '\0') {
|
||||
if (*src == ' ') *dest++ = '+';
|
||||
else *dest++ = *src;
|
||||
src++;
|
||||
}
|
||||
*dest = '\0';
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Send SMS via and HTTP interface. */
|
||||
bool sendHTTP(const char* destination, const char* message)
|
||||
{
|
||||
char convMessage[strlen(message)+2];
|
||||
convertText(convMessage,message);
|
||||
char command[2048];
|
||||
// FIXME -- Check specs for a good timeout value here.
|
||||
sprintf(command,"wget -T 5 -C -q -O - \"http://%s/http/%s&to=%s&text=%s\" >& /dev/null",
|
||||
gConfig.getStr("SMS.HTTP.Gateway"),
|
||||
gConfig.getStr("SMS.HTTP.AccessString"),
|
||||
destination, convMessage);
|
||||
LOG(DEBUG) << "MOSMS: sendHTTP sending with " << command;
|
||||
|
||||
// HTTP "GET" method with wget.
|
||||
// FIXME -- Look at the output of wget to check success.
|
||||
FILE* wget = popen(command,"r");
|
||||
if (!wget) {
|
||||
LOG(ALARM) << "cannot open wget with " << command;
|
||||
return false;
|
||||
}
|
||||
pclose(wget);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/** Send e-mail with local sendmail program. */
|
||||
bool sendEMail(const char* address, const char* body, const char* subject=NULL)
|
||||
{
|
||||
// We're not checking for overflow because the TPDU can't be more than a few hundred bytes.
|
||||
|
||||
// Build the command line.
|
||||
// FIXME -- Use sendmail to have better header control.
|
||||
char command[1024];
|
||||
if (subject) sprintf(command,"mail -s \"%s\" %s",subject,address);
|
||||
else sprintf(command,"mail %s",address);
|
||||
LOG(INFO) << "sending SMTP: \"" << body << "\" via \"" << command << "\"";
|
||||
|
||||
// Send the mail.
|
||||
FILE* mail = popen(command,"w");
|
||||
if (!mail) {
|
||||
LOG(ALARM) << "cannot send mail with \"" << command << "\"";
|
||||
return false;
|
||||
}
|
||||
// FIXME -- We should be sure body is 7-bit clean.
|
||||
fprintf(mail,"%s",body);
|
||||
if (pclose(mail) == -1) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//@}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Send a TDPU to a numeric address.
|
||||
@param mobileID The sender's IMSI.
|
||||
@param submit A TL-SUBMIT PDU.
|
||||
@return true on success
|
||||
*/
|
||||
bool sendToNumericAddress(const L3MobileIdentity &mobileID, const TLSubmit& submit)
|
||||
{
|
||||
LOG(INFO) << "from " << mobileID << " message: " << submit;
|
||||
const TLAddress& address = submit.DA();
|
||||
const char* body = submit.UD().data();
|
||||
|
||||
// If there is an external HTTP gateway, use it.
|
||||
if (gConfig.defines("SMS.HTTP.Gateway")) return sendHTTP(address.digits(), body);
|
||||
|
||||
// Otherwise, we are looking for a SIP interface to smsd.
|
||||
|
||||
// Steps:
|
||||
// 1 -- Create a transaction record.
|
||||
// 2 -- Send it to the server.
|
||||
|
@ -186,20 +96,19 @@ bool sendToNumericAddress(const L3MobileIdentity &mobileID, const TLSubmit& subm
|
|||
// 4 -- Return true for OK or ACCEPTED, false otherwise.
|
||||
|
||||
// Form the TLAddress into a CalledPartyNumber for the transaction.
|
||||
L3CalledPartyBCDNumber calledParty(address.digits());
|
||||
L3CalledPartyBCDNumber calledParty(address);
|
||||
// Step 1 -- Create a transaction record.
|
||||
TransactionEntry transaction(
|
||||
mobileID,
|
||||
L3CMServiceType::ShortMessage,
|
||||
0, // doesn't matter
|
||||
calledParty);
|
||||
TransactionEntry transaction(mobileID,
|
||||
L3CMServiceType::ShortMessage,
|
||||
0, // doesn't matter
|
||||
calledParty);
|
||||
transaction.SIP().User(mobileID.digits());
|
||||
transaction.Q931State(TransactionEntry::SMSSubmitting);
|
||||
gTransactionTable.add(transaction);
|
||||
LOG(DEBUG) << "MOSMS: transaction: " << transaction;
|
||||
|
||||
// Step 2 -- Send the message to the server.
|
||||
transaction.SIP().MOSMSSendMESSAGE(address.digits(),gConfig.getStr("SIP.IP"),body);
|
||||
transaction.SIP().MOSMSSendMESSAGE(address, gConfig.getStr("SIP.IP"), body, false);
|
||||
|
||||
// Step 3 -- Wait for OK or ACCEPTED.
|
||||
SIPState state = transaction.SIP().MOSMSWaitForSubmit();
|
||||
|
@ -209,67 +118,6 @@ bool sendToNumericAddress(const L3MobileIdentity &mobileID, const TLSubmit& subm
|
|||
return state==SIP::Cleared;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/** Send a TPDU through whatever gateway is available. */
|
||||
bool submitSMS(const L3MobileIdentity& mobileID, const TLSubmit& submit)
|
||||
{
|
||||
LOG(INFO) << "from " << mobileID;
|
||||
//const TLAddress& address = submit.DA();
|
||||
const char* body = submit.UD().data();
|
||||
|
||||
// Check for direct e-mail address at start of message body.
|
||||
// FIXME -- This doesn't really follow the spec. See GSM 03.40 3.8.
|
||||
static const Regexp emailAddress("^[[:graph:]]+@[[:graph:]]+ ");
|
||||
if (emailAddress.match(body)) {
|
||||
// FIXME -- Get the sender's E.164 to put in the subject line.
|
||||
char bodyCopy[strlen(body)+2];
|
||||
strcpy(bodyCopy,body);
|
||||
char* SMTPAddress = bodyCopy;
|
||||
char* term = strchr(bodyCopy,' ');
|
||||
// If term's NULL, the regexp is broken.
|
||||
assert(term);
|
||||
*term = '\0';
|
||||
char* SMTPPayload = term+1;
|
||||
LOG(INFO) << "sending SMTP to " << SMTPAddress << ": " << SMTPPayload;
|
||||
if (SMTPPayload) return sendEMail(SMTPAddress,SMTPPayload,"from OpenBTS gateway");
|
||||
else return sendEMail(SMTPAddress,"(empty)","from OpenBTS gateway");
|
||||
}
|
||||
|
||||
// Send to smsd or HTTP gateway, depending on what's defined in the conig.
|
||||
return sendToNumericAddress(mobileID,submit);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Process the incoming TPDU.
|
||||
@param mobileID Sender's IMSI.
|
||||
@param TPDU The TPDU (duh).
|
||||
@return true if successful
|
||||
*/
|
||||
bool handleTPDU(const L3MobileIdentity& mobileID, const TLFrame& TPDU)
|
||||
{
|
||||
LOG(DEBUG) << "SMS: handleTPDU MTI=" << TPDU.MTI();
|
||||
// Handle just the uplink cases.
|
||||
switch ((TLMessage::MessageType)TPDU.MTI()) {
|
||||
case TLMessage::DELIVER_REPORT:
|
||||
case TLMessage::STATUS_REPORT:
|
||||
return false;
|
||||
case TLMessage::SUBMIT: {
|
||||
TLSubmit submit;
|
||||
submit.parse(TPDU);
|
||||
LOG(INFO) << "SMS SMS-SUBMIT " << submit;
|
||||
return submitSMS(mobileID,submit);
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Process the RPDU.
|
||||
@param mobileID The sender's IMSI.
|
||||
|
@ -281,10 +129,9 @@ bool handleRPDU(const L3MobileIdentity& mobileID, const RLFrame& RPDU)
|
|||
LOG(DEBUG) << "SMS: handleRPDU MTI=" << RPDU.MTI();
|
||||
switch ((RPMessage::MessageType)RPDU.MTI()) {
|
||||
case RPMessage::Data: {
|
||||
RPData data;
|
||||
data.parse(RPDU);
|
||||
LOG(INFO) << "SMS RP-DATA " << data;
|
||||
return handleTPDU(mobileID,data.TPDU());
|
||||
ostringstream body;
|
||||
RPDU.hex(body);
|
||||
return sendSIP(mobileID, gConfig.getStr("SIP.SMSC"), body.str().data());
|
||||
}
|
||||
case RPMessage::Ack:
|
||||
case RPMessage::SMMA:
|
||||
|
@ -427,13 +274,7 @@ bool Control::deliverSMSToMS(const char *callingPartyDigits, const char* message
|
|||
// This function is used to deliver messages that originate INSIDE the BTS.
|
||||
// For the normal SMS delivery, see MTSMSController.
|
||||
|
||||
// Start ABM in SAP3.
|
||||
LCH->send(ESTABLISH,3);
|
||||
// Wait for SAP3 ABM to connect.
|
||||
// The next read on SAP3 should the ESTABLISH primitive.
|
||||
// This won't return NULL. It will throw an exception if it fails.
|
||||
delete getFrameSMS(LCH,ESTABLISH);
|
||||
|
||||
#if 0
|
||||
// HACK
|
||||
// Check for "Easter Eggs"
|
||||
// TL-PID
|
||||
|
@ -449,6 +290,44 @@ bool Control::deliverSMSToMS(const char *callingPartyDigits, const char* message
|
|||
RPData(reference,
|
||||
RPAddress(gConfig.getStr("SMS.FakeSrcSMSC")),
|
||||
TLDeliver(callingPartyDigits,message,TLPID)));
|
||||
#else
|
||||
unsigned reference = random() % 255;
|
||||
BitVector RPDUbits(strlen(message)*4);
|
||||
if (!RPDUbits.unhex(message)) {
|
||||
LOG(WARN) << "Hex string parsing failed (in incoming SIP MESSAGE)";
|
||||
throw UnexpectedMessage();
|
||||
}
|
||||
|
||||
RPData rp_data;
|
||||
try {
|
||||
RLFrame RPDU(RPDUbits);
|
||||
LOG(DEBUG) << "SMS RPDU: " << RPDU;
|
||||
|
||||
rp_data.parse(RPDU);
|
||||
LOG(DEBUG) << "SMS RP-DATA " << rp_data;
|
||||
}
|
||||
catch (SMSReadError) {
|
||||
LOG(WARN) << "SMS parsing failed (above L3)";
|
||||
// Cause 95, "semantically incorrect message".
|
||||
LCH->send(CPData(1,TI,RPError(95,reference)),3);
|
||||
throw UnexpectedMessage();
|
||||
}
|
||||
catch (L3ReadError) {
|
||||
LOG(WARN) << "SMS parsing failed (in L3)";
|
||||
// TODO:: send error back to the phone
|
||||
throw UnsupportedMessage();
|
||||
}
|
||||
CPData deliver(0,TI,rp_data);
|
||||
|
||||
#endif
|
||||
|
||||
// Start ABM in SAP3.
|
||||
LCH->send(ESTABLISH,3);
|
||||
// Wait for SAP3 ABM to connect.
|
||||
// The next read on SAP3 should the ESTABLISH primitive.
|
||||
// This won't return NULL. It will throw an exception if it fails.
|
||||
delete getFrameSMS(LCH,ESTABLISH);
|
||||
|
||||
LOG(INFO) << "sending " << deliver;
|
||||
LCH->send(deliver,3);
|
||||
|
||||
|
@ -543,7 +422,9 @@ void Control::MTSMSController(TransactionEntry& transaction,
|
|||
// but instead to an RRLP transaction over the already allocated LogicalChannel.
|
||||
const char* m = transaction.message(); // NOTE - not very nice, my way of checking.
|
||||
if ((strlen(m) > 4) && (std::string("RRLP") == std::string(m, m+4))) {
|
||||
BitVector rrlp_position_request = hex2bitvector(transaction.message() + 4);
|
||||
const char *transaction_hex = transaction.message() + 4;
|
||||
BitVector rrlp_position_request(strlen(transaction_hex)*4);
|
||||
rrlp_position_request.unhex(transaction_hex);
|
||||
LOG(INFO) << "MTSMS: Sending RRLP";
|
||||
// TODO - how to get mobID here?
|
||||
L3MobileIdentity mobID = L3MobileIdentity("000000000000000");
|
||||
|
@ -579,15 +460,29 @@ void Control::MTSMSController(TransactionEntry& transaction,
|
|||
transaction.Q931State(TransactionEntry::SMSDelivering);
|
||||
gTransactionTable.update(transaction);
|
||||
|
||||
bool success = deliverSMSToMS(transaction.calling().digits(),transaction.message(),random()%7,LCH);
|
||||
try {
|
||||
bool success = deliverSMSToMS(transaction.calling().digits(),transaction.message(),random()%7,LCH);
|
||||
|
||||
// Close the Dm channel.
|
||||
LOG(INFO) << "MTSMS: closing";
|
||||
LCH->send(L3ChannelRelease());
|
||||
// Close the Dm channel.
|
||||
LOG(INFO) << "MTSMS: closing";
|
||||
LCH->send(L3ChannelRelease());
|
||||
|
||||
// Ack in SIP domain and update transaction state.
|
||||
if (success) {
|
||||
// Ack in SIP domain and update transaction state.
|
||||
if (success) {
|
||||
engine.MTSMSSendOK();
|
||||
clearTransactionHistory(transaction);
|
||||
}
|
||||
}
|
||||
catch (UnexpectedMessage) {
|
||||
// TODO -- MUST SEND PERMANENT ERROR HERE!!!!!!!!!
|
||||
engine.MTSMSSendOK();
|
||||
LCH->send(L3ChannelRelease());
|
||||
clearTransactionHistory(transaction);
|
||||
}
|
||||
catch (UnsupportedMessage) {
|
||||
// TODO -- MUST SEND PERMANENT ERROR HERE!!!!!!!!!
|
||||
engine.MTSMSSendOK();
|
||||
LCH->send(L3ChannelRelease());
|
||||
clearTransactionHistory(transaction);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -654,7 +654,7 @@ int SIPEngine::RxFrame(unsigned char * rx_frame){
|
|||
|
||||
|
||||
SIPState SIPEngine::MOSMSSendMESSAGE(const char * wCalledUsername,
|
||||
const char * wCalledDomain , const char *messageText)
|
||||
const char * wCalledDomain , const char *messageText, bool plainText)
|
||||
{
|
||||
LOG(DEBUG) << "mState=" << mState;
|
||||
LOG(INFO) << "SIP send to " << wCalledUsername << "@" << wCalledDomain << " MESSAGE " << messageText;
|
||||
|
@ -673,11 +673,18 @@ SIPState SIPEngine::MOSMSSendMESSAGE(const char * wCalledUsername,
|
|||
mRemoteUsername = wCalledUsername;
|
||||
mRemoteDomain = wCalledDomain;
|
||||
|
||||
const char *content_type;
|
||||
if (plainText) {
|
||||
content_type = "text/plain";
|
||||
} else {
|
||||
content_type = "application/vnd.3gpp.sms";
|
||||
}
|
||||
|
||||
osip_message_t * message = sip_message(
|
||||
mRemoteUsername.c_str(), mSIPUsername.c_str(),
|
||||
mSIPPort, gConfig.getStr("SIP.IP"), mMessengerIP,
|
||||
mFromTag.c_str(), mViaBranch.c_str(), mCallID.c_str(), mCSeq,
|
||||
messageText);
|
||||
messageText, content_type);
|
||||
|
||||
// Send Invite to Asterisk.
|
||||
gSIPInterface.writeMessenger(message);
|
||||
|
|
|
@ -200,10 +200,12 @@ public:
|
|||
@param called_username SIP userid or E.164 address.
|
||||
@param called_domain SIP user's domain.
|
||||
@param message_text MESSAGE payload as a C string.
|
||||
@param plainText True if message is text/plain, otherwise it's application/vnd.3gpp.sms
|
||||
@return New SIP call state.
|
||||
*/
|
||||
SIPState MOSMSSendMESSAGE(const char * called_username,
|
||||
const char * called_domain, const char *message_text);
|
||||
const char * called_domain, const char *message_text,
|
||||
bool plainText);
|
||||
|
||||
SIPState MOSMSWaitForSubmit();
|
||||
|
||||
|
|
|
@ -214,7 +214,7 @@ osip_message_t * SIP::sip_unregister( const char * sip_username, short wlocal_po
|
|||
}
|
||||
|
||||
|
||||
osip_message_t * SIP::sip_message( const char * dialed_number, const char * sip_username, short wlocal_port, const char * local_ip, const char * proxy_ip, const char * from_tag, const char * via_branch, const char * call_id, int cseq, const char* message) {
|
||||
osip_message_t * SIP::sip_message( const char * dialed_number, const char * sip_username, short wlocal_port, const char * local_ip, const char * proxy_ip, const char * from_tag, const char * via_branch, const char * call_id, int cseq, const char* message, const char* content_type) {
|
||||
|
||||
char local_port[10];
|
||||
sprintf(local_port, "%i", wlocal_port);
|
||||
|
@ -276,7 +276,17 @@ osip_message_t * SIP::sip_message( const char * dialed_number, const char * sip_
|
|||
sprintf(temp_buf,"%i",cseq);
|
||||
osip_cseq_set_number(request->cseq, strdup(temp_buf));
|
||||
|
||||
osip_message_set_content_type(request, strdup("text/plain"));
|
||||
// Content-Type
|
||||
if (content_type)
|
||||
{
|
||||
// Explicit value provided
|
||||
osip_message_set_content_type(request, strdup(content_type));
|
||||
} else {
|
||||
// Default to text/plain
|
||||
osip_message_set_content_type(request, strdup("text/plain"));
|
||||
}
|
||||
|
||||
// Content-Length
|
||||
sprintf(temp_buf,"%lu",strlen(message));
|
||||
osip_message_set_content_length(request, strdup(temp_buf));
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ osip_message_t * sip_unregister( const char * sip_username, short local_port, co
|
|||
const char * proxy_ip, const char * from_tag, const char * via_branch, const char * call_id, int cseq);
|
||||
|
||||
|
||||
osip_message_t * sip_message( const char * dialed_number, const char * sip_username, short local_port, const char * local_ip, const char * proxy_ip, const char * from_tag, const char * via_branch, const char * call_id, int cseq, const char* message);
|
||||
osip_message_t * sip_message( const char * dialed_number, const char * sip_username, short local_port, const char * local_ip, const char * proxy_ip, const char * from_tag, const char * via_branch, const char * call_id, int cseq, const char* message, const char* content_type=NULL);
|
||||
|
||||
osip_message_t * sip_invite( const char * dialed_number, short rtp_port,const char * sip_username, short local_port, const char * local_ip, const char * proxy_ip, const char * from_tag, const char * via_branch, const char * call_id, int cseq, unsigned codec);
|
||||
|
||||
|
|
|
@ -76,6 +76,61 @@ CPMessage * SMS::parseSMS( const GSM::L3Frame& frame )
|
|||
}
|
||||
|
||||
|
||||
RPData *SMS::hex2rpdata(const char *hexstring)
|
||||
{
|
||||
RPData *rp_data = NULL;
|
||||
|
||||
BitVector RPDUbits(strlen(hexstring)*4);
|
||||
if (!RPDUbits.unhex(hexstring)) {
|
||||
return false;
|
||||
}
|
||||
LOG(DEBUG) << "SMS RPDU bits: " << RPDUbits;
|
||||
|
||||
try {
|
||||
RLFrame RPDU(RPDUbits);
|
||||
LOG(DEBUG) << "SMS RPDU: " << RPDU;
|
||||
|
||||
rp_data = new RPData();
|
||||
rp_data->parse(RPDU);
|
||||
LOG(DEBUG) << "SMS RP-DATA " << *rp_data;
|
||||
}
|
||||
catch (SMSReadError) {
|
||||
LOG(WARN) << "SMS parsing failed (above L3)";
|
||||
// TODO:: send error back to the phone
|
||||
delete rp_data;
|
||||
rp_data = NULL;
|
||||
}
|
||||
catch (L3ReadError) {
|
||||
LOG(WARN) << "SMS parsing failed (in L3)";
|
||||
// TODO:: send error back to the phone
|
||||
delete rp_data;
|
||||
rp_data = NULL;
|
||||
}
|
||||
|
||||
return rp_data;
|
||||
}
|
||||
|
||||
TLMessage *SMS::parseTPDU(const TLFrame& TPDU)
|
||||
{
|
||||
LOG(DEBUG) << "SMS: parseTPDU MTI=" << TPDU.MTI();
|
||||
// Handle just the uplink cases.
|
||||
switch ((TLMessage::MessageType)TPDU.MTI()) {
|
||||
case TLMessage::DELIVER_REPORT:
|
||||
case TLMessage::STATUS_REPORT:
|
||||
// FIXME -- Not implemented yet.
|
||||
LOG(WARN) << "Unsupported TPDU type: " << (TLMessage::MessageType)TPDU.MTI();
|
||||
return NULL;
|
||||
case TLMessage::SUBMIT: {
|
||||
TLSubmit *submit = new TLSubmit;
|
||||
submit->parse(TPDU);
|
||||
LOG(INFO) << "SMS SMS-SUBMIT " << *submit;
|
||||
return submit;
|
||||
}
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void CPMessage::text(ostream& os) const
|
||||
{
|
||||
os << (CPMessage::MessageType)MTI();
|
||||
|
@ -163,8 +218,13 @@ ostream& SMS::operator<<(ostream& os, const RPMessage& msg)
|
|||
|
||||
void RPUserData::parseV(const L3Frame& src, size_t &rp, size_t expectedLength)
|
||||
{
|
||||
LOG(DEBUG) << "src=" << src << " (length=" << src.length() << ") rp=" << rp << " expectedLength=" << expectedLength;
|
||||
unsigned numBits = expectedLength*8;
|
||||
if (rp+numBits > src.size()) {
|
||||
SMS_READ_ERROR;
|
||||
}
|
||||
mTPDU.resize(numBits);
|
||||
LOG(DEBUG) << "mTPDU length=" << mTPDU.length() << "data=" << mTPDU;
|
||||
src.segmentCopyTo(mTPDU,rp,numBits);
|
||||
rp += numBits;
|
||||
}
|
||||
|
@ -410,16 +470,96 @@ void TLValidityPeriod::text(ostream& os) const
|
|||
os << "expiration=(" << str << ")";
|
||||
}
|
||||
|
||||
void TLUserData::encode7bit(const char *text)
|
||||
{
|
||||
size_t wp = 0;
|
||||
|
||||
// 1. Prepare.
|
||||
// Default alphabet (7-bit)
|
||||
mDCS = 0;
|
||||
// With 7-bit encoding TP-User-Data-Length count septets, i.e. just number
|
||||
// of characters.
|
||||
mLength = strlen(text);
|
||||
int bytes = (mLength*7+7)/8;
|
||||
int filler_bits = bytes*8-mLength*7;
|
||||
mRawData.resize(bytes*8);
|
||||
|
||||
// 2. Write TP-UD
|
||||
// This tail() works because UD is always the last field in the PDU.
|
||||
BitVector chars = mRawData.tail(wp);
|
||||
for (unsigned i=0; i<mLength; i++) {
|
||||
char gsm = encodeGSMChar(text[i]);
|
||||
mRawData.writeFieldReversed(wp,gsm,7);
|
||||
}
|
||||
mRawData.writeField(wp,0,filler_bits);
|
||||
}
|
||||
|
||||
std::string TLUserData::decode() const
|
||||
{
|
||||
std::string text;
|
||||
|
||||
switch (mDCS) {
|
||||
case 0:
|
||||
case 244:
|
||||
case 245:
|
||||
case 246:
|
||||
case 247:
|
||||
{
|
||||
// GSM 7-bit encoding, GSM 03.38 6.
|
||||
// Check bounds.
|
||||
if (mLength*7 > (mRawData.size())) {
|
||||
LOG(NOTICE) << "badly formatted TL-UD";
|
||||
SMS_READ_ERROR;
|
||||
}
|
||||
|
||||
size_t crp = 0;
|
||||
unsigned text_length = mLength;
|
||||
|
||||
// Skip User-Data-Header. We don't decode it here.
|
||||
// User-Data-Header handling is described in GSM 03.40 9.2.3.24
|
||||
// and is pictured in GSM 03.40 Figure 9.2.3.24 (a)
|
||||
if (mUDHI) {
|
||||
// Length-of-User-Data-Header
|
||||
unsigned udhl = mRawData.peekFieldReversed(crp,8);
|
||||
// Calculate UDH length in septets, including fill bits.
|
||||
unsigned udh_septets = (udhl*8 + 8 + 6) / 7;
|
||||
// Adjust actual text position and length.
|
||||
crp += udh_septets * 7;
|
||||
text_length -= udh_septets;
|
||||
LOG(DEBUG) << "UDHL(octets)=" << udhl
|
||||
<< " UDHL(septets)=" << udh_septets
|
||||
<< " pointer(bits)=" << crp
|
||||
<< " text_length(septets)=" << text_length;
|
||||
}
|
||||
|
||||
// Do decoding
|
||||
text.resize(text_length);
|
||||
for (unsigned i=0; i<text_length; i++) {
|
||||
char gsm = mRawData.readFieldReversed(crp,7);
|
||||
text[i] = decodeGSMChar(gsm);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
LOG(NOTICE) << "unsupported DCS 0x" << mDCS;
|
||||
SMS_READ_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
size_t TLUserData::length() const
|
||||
{
|
||||
// The reported value includes the length byte itself.
|
||||
// The length() method only needs to work for formats supported
|
||||
// by the write() method.
|
||||
assert(!mUDHI); // We don't support user headers.
|
||||
assert(mDCS<0x100); // Someone forgot to initialize the DCS.
|
||||
size_t sum = 1; // Start by counting the length byte.
|
||||
size_t sum = 1; // Start by counting the TP-User-Data-Length byte.
|
||||
#if 1
|
||||
sum += (mRawData.size()+7)/8;
|
||||
#else
|
||||
// The DCS is defined in GSM 03.38 4.
|
||||
if (mDCS==0) {
|
||||
// Default 7-bit alphabet
|
||||
|
@ -428,23 +568,31 @@ size_t TLUserData::length() const
|
|||
unsigned octets = bits/8;
|
||||
if (bits%8) octets += 1;
|
||||
sum += octets;
|
||||
return sum;
|
||||
} else {
|
||||
LOG(ERROR) << "unsupported SMS DCS 0x" << hex << mDCS;
|
||||
// It's OK to abort here. This method is only used for encoding.
|
||||
// So we should never end up here.
|
||||
assert(0); // We don't support this DCS.
|
||||
}
|
||||
#endif
|
||||
return sum;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void TLUserData::parse(const TLFrame& src, size_t& rp)
|
||||
{
|
||||
assert(!mUDHI); // We don't support user headers.
|
||||
// The DCS is defined in GSM 03.38 4.
|
||||
assert(mDCS<0x100); // Someone forgot to initialize the DCS.
|
||||
unsigned numChar = src.readField(rp,8);
|
||||
// TP-User-Data-Length
|
||||
mLength = src.readField(rp,8);
|
||||
#if 1
|
||||
// This tail() works because UD is always the last field in the PDU.
|
||||
mRawData.clone(src.tail(rp));
|
||||
// Should we do this here?
|
||||
mRawData.LSB8MSB();
|
||||
#else
|
||||
assert(!mUDHI); // We don't support user headers.
|
||||
switch (mDCS) {
|
||||
case 0:
|
||||
case 244:
|
||||
|
@ -478,11 +626,22 @@ void TLUserData::parse(const TLFrame& src, size_t& rp)
|
|||
SMS_READ_ERROR;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void TLUserData::write(TLFrame& dest, size_t& wp) const
|
||||
{
|
||||
#if 1
|
||||
// First write TP-User-Data-Length
|
||||
dest.writeField(wp,mLength,8);
|
||||
|
||||
// Then write TP-User-Data
|
||||
// This tail() works because UD is always the last field in the PDU.
|
||||
BitVector ud_dest = dest.tail(wp);
|
||||
mRawData.copyTo(ud_dest);
|
||||
ud_dest.LSB8MSB();
|
||||
#else
|
||||
// Stuff we don't support...
|
||||
assert(!mUDHI);
|
||||
assert(mDCS==0);
|
||||
|
@ -496,14 +655,17 @@ void TLUserData::write(TLFrame& dest, size_t& wp) const
|
|||
dest.writeFieldReversed(wp,gsm,7);
|
||||
}
|
||||
chars.LSB8MSB();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
void TLUserData::text(ostream& os) const
|
||||
{
|
||||
if (mDCS==0) os << mData;
|
||||
else os << "unknown encoding";
|
||||
os << "DCS=" << mDCS;
|
||||
os << " UDHI=" << mUDHI;
|
||||
os << " UDLength=" << mLength;
|
||||
os << " UD=("; mRawData.hex(os); os << ")";
|
||||
}
|
||||
|
||||
|
||||
|
@ -533,10 +695,12 @@ size_t TLSubmit::bodyLength() const
|
|||
|
||||
void TLSubmit::parseBody(const TLFrame& src, size_t& rp)
|
||||
{
|
||||
bool udhi;
|
||||
|
||||
parseRD(src);
|
||||
parseVPF(src);
|
||||
parseRP(src);
|
||||
parseUDHI(src);
|
||||
udhi = parseUDHI(src);
|
||||
parseSRR(src);
|
||||
mMR = src.readField(rp,8);
|
||||
mDA.parse(src,rp);
|
||||
|
@ -545,6 +709,7 @@ void TLSubmit::parseBody(const TLFrame& src, size_t& rp)
|
|||
mVP.VPF(mVPF);
|
||||
mVP.parse(src,rp);
|
||||
mUD.DCS(mDCS);
|
||||
mUD.UDHI(udhi);
|
||||
mUD.parse(src,rp);
|
||||
}
|
||||
|
||||
|
@ -555,7 +720,7 @@ void TLSubmit::text(ostream& os) const
|
|||
os << " RD=" << mRD;
|
||||
os << " VPF=" << mVPF;
|
||||
os << " RP=" << mRP;
|
||||
os << " UDHI=" << mUDHI;
|
||||
os << " UDHI=" << mUD.UDHI();
|
||||
os << " SRR=" << mSRR;
|
||||
os << " MR=" << mMR;
|
||||
os << " DA=(" << mDA << ")";
|
||||
|
@ -568,7 +733,7 @@ void TLSubmit::text(ostream& os) const
|
|||
|
||||
size_t TLDeliver::bodyLength() const
|
||||
{
|
||||
LOG(DEBUG) << "TLDEliver::bodyLength OA " << mOA.length() << " SCTS " << mSCTS.length() << " UD " << mUD.length();
|
||||
LOG(DEBUG) << "TLDeliver::bodyLength OA " << mOA.length() << " SCTS " << mSCTS.length() << " UD " << mUD.length();
|
||||
return mOA.length() + 1 + 1 + mSCTS.length() + mUD.length();
|
||||
}
|
||||
|
||||
|
@ -577,11 +742,11 @@ void TLDeliver::writeBody(TLFrame& dest, size_t& wp) const
|
|||
{
|
||||
writeMMS(dest);
|
||||
writeRP(dest);
|
||||
writeUDHI(dest);
|
||||
writeUDHI(dest, mUD.UDHI());
|
||||
writeSRI(dest);
|
||||
mOA.write(dest,wp);
|
||||
dest.writeField(wp,mPID,8);
|
||||
dest.writeField(wp,0,8); // hardcode DCS
|
||||
dest.writeField(wp,mUD.DCS(),8);
|
||||
mSCTS.write(dest,wp);
|
||||
writeUnused(dest);
|
||||
mUD.write(dest,wp);
|
||||
|
|
|
@ -38,7 +38,9 @@
|
|||
#include <GSML3Message.h>
|
||||
#include <GSML3CCElements.h>
|
||||
#include <GSML3MMElements.h>
|
||||
#include <Logger.h>
|
||||
|
||||
using namespace GSM;
|
||||
|
||||
namespace SMS {
|
||||
|
||||
|
@ -164,7 +166,9 @@ class TLUserData : public TLElement {
|
|||
|
||||
unsigned mDCS; ///< data coding scheme
|
||||
bool mUDHI; ///< header indicator
|
||||
char mData[161]; ///< actual data, as a C string
|
||||
unsigned mLength; ///< TP-User-Data-Length, see GSM 03.40 Fig. 9.2.3.24(a),
|
||||
///< GSM 03.40 Fig. 9.2.3.24(b) and GSM 03.40 9.2.3.16.
|
||||
BitVector mRawData; ///< raw packed data
|
||||
|
||||
public:
|
||||
|
||||
|
@ -172,19 +176,49 @@ class TLUserData : public TLElement {
|
|||
TLUserData(unsigned wDCS=0x100, bool wUDHI=false)
|
||||
:TLElement(),
|
||||
mDCS(wDCS),
|
||||
mUDHI(wUDHI)
|
||||
{ mData[0]='\0'; }
|
||||
mUDHI(wUDHI),
|
||||
mLength(0)
|
||||
{
|
||||
}
|
||||
|
||||
/** Initialze from a simple C string. */
|
||||
TLUserData(const char* text, bool wUDHI=false)
|
||||
/** Initialize from a raw encoded data. */
|
||||
TLUserData(unsigned wDCS, const BitVector wRawData, unsigned wLength,
|
||||
bool wUDHI=false)
|
||||
:TLElement(),
|
||||
mDCS(wDCS),
|
||||
mUDHI(wUDHI),
|
||||
mLength(wLength)
|
||||
{
|
||||
mRawData.clone(wRawData);
|
||||
}
|
||||
|
||||
/** Initialize from a simple C string. */
|
||||
TLUserData(const char* text, GSMAlphabet alphabet=ALPHABET_7BIT, bool wUDHI=false)
|
||||
:TLElement(),
|
||||
mDCS(0),
|
||||
mUDHI(wUDHI)
|
||||
{ strncpy(mData,text,sizeof(mData)-1); mData[sizeof(mData)-1]='\0'; }
|
||||
mUDHI(wUDHI),
|
||||
mLength(0)
|
||||
{
|
||||
switch(alphabet) {
|
||||
case ALPHABET_7BIT:
|
||||
encode7bit(text);
|
||||
break;
|
||||
case ALPHABET_8BIT:
|
||||
case ALPHABET_UCS2:
|
||||
default:
|
||||
LOG(WARN) << "Unsupported alphabet: " << alphabet;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DCS(unsigned wDCS) { mDCS=wDCS; }
|
||||
unsigned DCS() const { return mDCS; }
|
||||
void UDHI(unsigned wUDHI) { mUDHI=wUDHI; }
|
||||
const char* data() const { return mData; }
|
||||
unsigned UDHI() const { return mUDHI; }
|
||||
/** Encode text into this element, using 7-bit alphabet */
|
||||
void encode7bit(const char *text);
|
||||
/** Decode text from this element, using 7-bit alphabet */
|
||||
std::string decode() const;
|
||||
|
||||
/** This length includes a byte for the length field. */
|
||||
size_t length() const;
|
||||
|
@ -225,7 +259,7 @@ class TLMessage {
|
|||
bool mSRR; ///< status report request
|
||||
bool mSRI; ///< status report indication
|
||||
bool mSRQ; ///< status report qualifier
|
||||
bool mUDHI; ///< user-data header-indicator
|
||||
// bool mUDHI; ///< user-data header-indicator. Stored in TLUserData.
|
||||
bool mRP; ///< reply path
|
||||
//@}
|
||||
|
||||
|
@ -236,16 +270,16 @@ class TLMessage {
|
|||
|
||||
/** GSM 03.40 9.2.3.1 */
|
||||
enum MessageType {
|
||||
DELIVER = 0x0,
|
||||
DELIVER_REPORT = 0x0,
|
||||
STATUS_REPORT = 0x2,
|
||||
COMMAND = 0x02,
|
||||
SUBMIT = 0x1,
|
||||
SUBMIT_REPORT = 0x1
|
||||
DELIVER = 0x0, // SC -> MS
|
||||
DELIVER_REPORT = 0x0, // MS -> SC
|
||||
STATUS_REPORT = 0x2, // SC -> MS
|
||||
COMMAND = 0x02, // MS -> SC
|
||||
SUBMIT = 0x1, // MS -> SC
|
||||
SUBMIT_REPORT = 0x1 // SC -> MS
|
||||
};
|
||||
|
||||
TLMessage()
|
||||
:mMMS(false),mSRI(false),mUDHI(false),mRP(true)
|
||||
:mMMS(false),mSRI(false),mRP(true)
|
||||
{}
|
||||
|
||||
virtual ~TLMessage(){}
|
||||
|
@ -286,8 +320,8 @@ class TLMessage {
|
|||
void parseSRI(const TLFrame& fm) { mSRI=fm[2]; }
|
||||
void writeSRQ(TLFrame& fm) const { fm[2]=mSRQ; }
|
||||
void parseSRQ(const TLFrame& fm) { mSRQ=fm[2]; }
|
||||
void writeUDHI(TLFrame& fm) const { fm[1]=mUDHI; }
|
||||
void parseUDHI(const TLFrame& fm) { mUDHI=fm[1]; }
|
||||
void writeUDHI(TLFrame& fm, bool udhi) const { fm[1]=udhi; }
|
||||
bool parseUDHI(const TLFrame& fm) { return fm[1]; }
|
||||
void writeRP(TLFrame& fm) const { fm[0]=mRP; }
|
||||
void parseRP(const TLFrame& fm) { mRP=fm[0]; }
|
||||
void writeUnused(TLFrame& fm) const { fm.fill(0,3,2); } ///< Fill unused bits with 0s
|
||||
|
@ -352,7 +386,7 @@ class TLDeliver : public TLMessage {
|
|||
|
||||
TLAddress mOA; ///< origination address, GSM 03.40 9.3.2.7
|
||||
unsigned mPID; ///< TL-PID, GSM 03.40 9.2.3.9
|
||||
// Hardcode DCS.
|
||||
// DCS is taken from mUD.
|
||||
TLTimestamp mSCTS; ///< service center timestamp, GSM 03.40 9.2.3.11
|
||||
TLUserData mUD; ///< user data
|
||||
|
||||
|
@ -774,6 +808,21 @@ std::ostream& operator<<(std::ostream& os, CPMessage::MessageType MTI);
|
|||
*/
|
||||
CPMessage * parseSMS( const GSM::L3Frame& frame );
|
||||
|
||||
/**
|
||||
Parse msgtext from a hex string to RPData struct.
|
||||
@param hexstring RPData encoded into hex-string.
|
||||
@return Pointer to parsed RPData or NULL on error.
|
||||
*/
|
||||
RPData *hex2rpdata(const char *hexstring);
|
||||
|
||||
/**
|
||||
Parse a TPDU.
|
||||
Currently only SMS-SUBMIT is supported.
|
||||
@param TPDU The TPDU.
|
||||
@return Pointer to parsed TLMessage or NULL on error.
|
||||
*/
|
||||
TLMessage *parseTPDU(const TLFrame& TPDU);
|
||||
|
||||
/** A factory method for SMS L3 (CM) messages. */
|
||||
CPMessage * CPFactory( CPMessage::MessageType MTI );
|
||||
|
||||
|
|
|
@ -163,18 +163,11 @@ SIP.Timer.A 2000
|
|||
#
|
||||
# SMS parameters
|
||||
#
|
||||
# ISDN address of source SMSC when we fake out a source SMSC.
|
||||
SMS.FakeSrcSMSC 0000
|
||||
# ISDN address of destination SMSC when a fake value is needed.
|
||||
SMS.DefaultDestSMSC 0000
|
||||
|
||||
|
||||
# The SMS HTTP gateway.
|
||||
# Comment out if you don't have one or if you want to use smqueue.
|
||||
#SMS.HTTP.Gateway api.clickatell.com
|
||||
|
||||
# IF SMS.HTTP.Gateway IS DEFINED, SMS.HTTP.AccessString MUST ALSO BE DEFINED.
|
||||
#SMS.HTTP.AccessString sendmsg?user=xxxx&password=xxxx&api_id=xxxx
|
||||
# SMSes will be sent to SIP URI sip:<SIP.SMSC>@<SIP.IP>
|
||||
SIP.SMSC smsc
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -20,15 +20,34 @@
|
|||
|
||||
include $(top_srcdir)/Makefile.common
|
||||
|
||||
AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES)
|
||||
AM_CXXFLAGS = -O3 -g -lpthread
|
||||
|
||||
EXTRA_DIST = \
|
||||
Makefile.standalone \
|
||||
README.smqueue \
|
||||
poll.c \
|
||||
smcommands.cpp \
|
||||
smnet.cpp \
|
||||
smqueue.config.example \
|
||||
smqueue.cpp \
|
||||
runloop.sh
|
||||
|
||||
noinst_PROGRAMS = \
|
||||
smqueue
|
||||
|
||||
noinst_HEADERS = \
|
||||
poll.h \
|
||||
smnet.h \
|
||||
smqueue.h \
|
||||
runloop.sh
|
||||
smsc.h
|
||||
|
||||
smqueue_SOURCES = \
|
||||
poll.c \
|
||||
smcommands.cpp \
|
||||
smnet.cpp \
|
||||
smqueue.cpp \
|
||||
smsc.cpp
|
||||
smqueue_LDADD = \
|
||||
$(GLOBALS_LA) \
|
||||
$(SMS_LA) \
|
||||
$(GSM_LA) \
|
||||
$(COMMON_LA) \
|
||||
$(HLR_LA) \
|
||||
$(SIP_LA) \
|
||||
$(OSIP_LIBS)
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
LOCALLIBS=../CommonLibs/Logger.cpp ../CommonLibs/Timeval.cpp ../CommonLibs/Threads.cpp ../CommonLibs/Sockets.cpp ../CommonLibs/Configuration.cpp
|
||||
LIBS=$(LOCALLIBS) -losipparser2 -losip2 -lc -lpthread
|
||||
INCLUDES=-I../CommonLibs -I../HLR
|
||||
CPPFLAGS=-g -Wall
|
||||
#CPPFLAGS=-Weffc++ -g -Wall
|
||||
|
||||
smqueue: smqueue.cpp smqueue.h smnet.cpp smnet.h smcommands.cpp ../HLR/HLR.cpp ../HLR/HLR.h
|
||||
g++ -o smqueue $(CPPFLAGS) $(INCLUDES) smqueue.cpp smnet.cpp smcommands.cpp ../HLR/HLR.cpp $(LIBS)
|
||||
|
||||
smqueue.shar:
|
||||
shar >smqueue.shar smqueue.cpp smqueue.h smnet.cpp smnet.h smcommands.cpp smqueue.config Makefile testmsg*
|
||||
|
||||
clean:
|
||||
rm smqueue
|
|
@ -1,7 +1 @@
|
|||
The Smqueue RFC-3428 Store and Forward Server
|
||||
|
||||
Smqueue has different build dependencies than OpenBTS and so it has
|
||||
its own make file outside of the standard openbts autobuild system.
|
||||
This allows smqueue to be built without installing GNU Radio.
|
||||
|
||||
make -f Makefile.standalone
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
#include "smqueue.h"
|
||||
#include "smnet.h"
|
||||
#include "smsc.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
@ -379,5 +380,8 @@ SMqueue::init_smcommands (short_code_map_t *scm)
|
|||
(*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;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,13 @@ Log.Level.smcommands.cpp DEBUG
|
|||
# Logging file. Logs to stdout if this is not defined.
|
||||
#Log.FileName smqueue.log
|
||||
|
||||
# LOG(ALARM) is printed and also sent as udp to this address.
|
||||
Log.Alarms.TargetIP 192.168.10.200
|
||||
$optional Log.Alarms.TargetIP
|
||||
Log.Alarms.TargetPort 10101
|
||||
# Number of alarms saved in internal queue
|
||||
Log.Alarms.Max 10
|
||||
|
||||
|
||||
savefile savedqueue.txt
|
||||
|
||||
|
@ -37,10 +44,26 @@ $optional Debug.print_as_we_validate
|
|||
#SIP.global_relay 81.201.82.50:5060
|
||||
SIP.global_relay
|
||||
|
||||
#
|
||||
# SMS parameters
|
||||
#
|
||||
# ISDN address of source SMSC when we fake out a source SMSC.
|
||||
SMS.FakeSrcSMSC 0000
|
||||
|
||||
# The SMS HTTP gateway.
|
||||
# Comment out if you don't have one or if you want to use smqueue.
|
||||
#SMS.HTTP.Gateway api.clickatell.com
|
||||
|
||||
# IF SMS.HTTP.Gateway IS DEFINED, SMS.HTTP.AccessString MUST ALSO BE DEFINED.
|
||||
#SMS.HTTP.AccessString sendmsg?user=xxxx&password=xxxx&api_id=xxxx
|
||||
|
||||
|
||||
# return SMS messages
|
||||
BounceMessage.IMSILookupFailed Cannot determine return address; bouncing message. Text your phone number to 101 to register and try again.
|
||||
BounceMessage.NotRegistered Phone not registered here.
|
||||
Bounce.Message.IMSILookupFailed Cannot determine return address; bouncing message. Text your phone number to 101 to register and try again.
|
||||
Bounce.Message.NotRegistered Phone not registered here. Message
|
||||
|
||||
# Bounce from address
|
||||
Bounce.Code 411
|
||||
|
||||
|
||||
|
||||
|
@ -62,7 +85,7 @@ SC.Register.Msg.TakenA The phone number
|
|||
SC.Register.Msg.TakenB is already in use. Try another, then call that one to talk to whoever took yours.
|
||||
# Message for internal errors, sent as <A> << phonenumber << <B> << imsi
|
||||
SC.Register.Msg.ErrorA Error in assigning
|
||||
SC.Register.Msg.ErrorB to IMSI
|
||||
SC.Register.Msg.ErrorB to
|
||||
# Bounds for valid number length
|
||||
SC.Register.Digits.Max 10
|
||||
SC.Register.Digits.Min 7
|
||||
|
@ -87,4 +110,6 @@ SC.WhiplashQuit.Code 314158
|
|||
SC.WhiplashQuit.Password Snidely
|
||||
SC.WhiplashQuit.SaveFile testsave.txt
|
||||
|
||||
|
||||
# SMS-Center code. OpenBTS will send messages from mobile phones
|
||||
# to this short code and will work as usual SMS-Center.
|
||||
SC.SMSC.Code smsc
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#include "smqueue.h"
|
||||
#include "smnet.h"
|
||||
#include "smsc.h"
|
||||
#include <time.h>
|
||||
#include <osipparser2/osip_message.h> /* from osipparser2 */
|
||||
#include <iostream>
|
||||
|
@ -57,41 +58,44 @@ ConfigurationTable gConfig("smqueue.config");
|
|||
transitions where we're starting over from
|
||||
scratch due to some error. */
|
||||
/* Timeout when moving from this state to new state:
|
||||
NS RF AF WD RD AD WS RS AS WM RM AM DM WR RH AR */
|
||||
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, NT, NT, 0, NT, NT, 0, NT, NT, 0, NT, 0, NT, NT, NT,};
|
||||
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, 10, 10, NT, 0, NT, NT, NT, NT, NT, NT, NT, 0, 1, 0, NT,};
|
||||
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, 60, NT, NT, NT, NT, NT, NT, NT, NT, NT, NT, 0, NT, NT, NT,};
|
||||
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, RT, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, NT, NT, NT,};
|
||||
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, RT, NT, RT, NT, NT, NT, 0, NT, NT, NT, NT, 0, NT, NT, NT,};
|
||||
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, RT, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, NT, NT, NT,};
|
||||
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, RT, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, NT, NT, NT,};
|
||||
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, RT, NT, RT, NT, NT, NT, NT, NT, NT, 0, NT, 0, NT, NT, NT,};
|
||||
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, RT, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, NT, NT, NT,};
|
||||
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, RT, NT, RT, NT, NT, NT, NT, NT, 75, 0, NT, 0, NT, NT, NT,};
|
||||
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, RT, NT, RT, NT, NT, NT, 75, NT, 75, 75, 15, 0, NT, NT, NT,};
|
||||
// 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, RT, NT, NT, NT, NT, NT, NT, NT, 60, 10, NT, 0, NT, NT, NT,};
|
||||
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, 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, 0, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, 1, 0, NT,};
|
||||
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, 0, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, 1, 1, 2,};
|
||||
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, 0, NT, RT, NT, NT, NT, NT, NT, NT, NT, NT, 0, 1, 1, 10,};
|
||||
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 RF AF WD RD AD WS RS AS WM RM AM DM WR RH AR */
|
||||
NS IS RF AF WD RD AD WS RS AS WM RM AM DM WR RH AR */
|
||||
|
||||
#undef NT /* No longer needed */
|
||||
#undef RT
|
||||
|
@ -99,6 +103,7 @@ int timeouts_ASKED_TO_REGISTER_HANDSET[STATE_MAX_PLUS_ONE] = {
|
|||
/* 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,
|
||||
|
||||
|
@ -123,6 +128,7 @@ int (*SMqueue::timeouts[STATE_MAX_PLUS_ONE])[STATE_MAX_PLUS_ONE] = {
|
|||
|
||||
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",
|
||||
|
@ -180,7 +186,8 @@ osip_mem_release()
|
|||
*/
|
||||
static
|
||||
struct imsi_phone { char imsi[4+15+1]; char phone[1+15+1]; } imsi_phone[] = {
|
||||
{"IMSI666410186585295", "+17074700741"}, /* Nokia 8890 */
|
||||
// {"IMSI310260550682564", "100"}, /* Siemens A52 */
|
||||
{"IMSI666410186585295", "+17074700741"}, /* Nokia 8890 */
|
||||
{"IMSI777100223456161", "+17074700746"}, /* Palm Treo */
|
||||
{{0}, {0}}
|
||||
};
|
||||
|
@ -244,6 +251,46 @@ SMq::process_timeout()
|
|||
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
|
||||
|
@ -268,36 +315,16 @@ SMq::process_timeout()
|
|||
break;
|
||||
|
||||
default:
|
||||
LOG(NOTICE) << "Message timed out with bad state "
|
||||
LOG(ALARM) << "Message timed out with bad state "
|
||||
<< qmsg->state << " and message: " << qmsg->text;
|
||||
set_state(qmsg, REQUEST_FROM_ADDRESS_LOOKUP);
|
||||
set_state(qmsg, INITIAL_STATE);
|
||||
// WTF? Shouldn't we proceed to NO_STATE aka "error state"?
|
||||
/* NO BREAK */
|
||||
case REQUEST_FROM_ADDRESS_LOOKUP:
|
||||
// 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
|
||||
// Deal with it!
|
||||
newstate = handle_sms_message(qmsg);
|
||||
set_state(qmsg, newstate);
|
||||
break;
|
||||
} else {
|
||||
// It's a RESPONSE.
|
||||
handle_response(qmsg);
|
||||
// Probably the RESPONSE has been deleted.
|
||||
// We go back to top of loop.
|
||||
break;
|
||||
}
|
||||
/* 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:
|
||||
|
@ -322,6 +349,18 @@ SMq::process_timeout()
|
|||
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.
|
||||
|
@ -476,7 +515,7 @@ SMq::handle_response(short_msg_p_list::iterator qmsgit)
|
|||
// Special code in registration processing
|
||||
// will notice it's a re-reg and just reply
|
||||
// with a welcome message.
|
||||
oldsms->set_state(REQUEST_FROM_ADDRESS_LOOKUP);
|
||||
oldsms->set_state(INITIAL_STATE);
|
||||
} else {
|
||||
// Orig SMS exists, but not in a normal state.
|
||||
// Assume that the original SMS is in a
|
||||
|
@ -644,8 +683,12 @@ short_msg_pending::validate_short_msg()
|
|||
// 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("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
|
||||
|
@ -1052,6 +1095,9 @@ SMq::originate_sm(const char *from, const char *to, const char *msgtext,
|
|||
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?
|
||||
|
@ -1070,6 +1116,7 @@ SMq::originate_sm(const char *from, const char *to, const char *msgtext,
|
|||
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;
|
||||
|
@ -1102,19 +1149,12 @@ enum sm_state
|
|||
SMq::bounce_message(short_msg_pending *sent_msg, const char *errstr)
|
||||
{
|
||||
ostringstream errmsg;
|
||||
osip_body_t *bod1;
|
||||
char *username;
|
||||
char *thetext;
|
||||
std::string thetext;
|
||||
int status;
|
||||
|
||||
username = sent_msg->parsed->to->url->username;
|
||||
if (sent_msg->parsed->bodies.nb_elt > 0) {
|
||||
// bod1->body is the original SMS text
|
||||
bod1 = (osip_body_t *)sent_msg->parsed->bodies.node->element;
|
||||
thetext = bod1->body;
|
||||
} else {
|
||||
thetext = "";
|
||||
}
|
||||
thetext = sent_msg->get_text();
|
||||
|
||||
LOG(NOTICE) << "Bouncing " << sent_msg->qtag << " from "
|
||||
<< sent_msg->parsed->from->url->username // his phonenum
|
||||
|
@ -1126,19 +1166,19 @@ SMq::bounce_message(short_msg_pending *sent_msg, const char *errstr)
|
|||
else
|
||||
errmsg << "can't send: " << thetext;
|
||||
|
||||
// Don't bounce a message from 411, makes endless loops.
|
||||
// Don't bounce a message from us - it makes endless loops.
|
||||
status = 1;
|
||||
if (0 != strcmp("411", sent_msg->parsed->from->url->username)) {
|
||||
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;
|
||||
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("411", // from "411"? FIXME?
|
||||
|| 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
|
||||
bounce_to_imsi? REQUEST_DESTINATION_SIPURL: // dest is IMSI
|
||||
REQUEST_DESTINATION_IMSI); // dest is phonenum
|
||||
}
|
||||
if (status == 0) {
|
||||
return DELETE_ME_STATE;
|
||||
|
@ -1189,6 +1229,9 @@ SMq::register_handset (short_msg_p_list::iterator qmsg)
|
|||
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.
|
||||
|
@ -1246,109 +1289,111 @@ SMq::register_handset (short_msg_p_list::iterator qmsg)
|
|||
return ASKED_TO_REGISTER_HANDSET;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Initial handling of SMS messages found in the queue
|
||||
*/
|
||||
enum sm_state
|
||||
SMq::handle_sms_message (short_msg_p_list::iterator qmsg)
|
||||
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;
|
||||
char *bods;
|
||||
std::string bods;
|
||||
short_func_t shortfn;
|
||||
short_code_map_t::iterator shortit;
|
||||
short_code_map_t::const_iterator shortit;
|
||||
enum short_code_action sca;
|
||||
short_code_params params;
|
||||
int status;
|
||||
string short_code;
|
||||
|
||||
if (0 != strcmp(qmsg->parsed->sip_method, "MESSAGE")) {
|
||||
LOG(WARN) << "Invalid incoming SIP message, method is "
|
||||
<< qmsg->parsed->sip_method;
|
||||
return NO_STATE;
|
||||
short_code = qmsg->parsed->req_uri->username;
|
||||
shortit = short_code_map.find (short_code);
|
||||
|
||||
if (shortit == short_code_map.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
shortit = short_code_map.find (string(qmsg->parsed->req_uri->username));
|
||||
/* Messages to certain addresses are special commands */
|
||||
shortfn = shortit->second;
|
||||
bods = qmsg->get_text();
|
||||
|
||||
if (shortit != short_code_map.end()) {
|
||||
/* Messages to certain addresses are special commands */
|
||||
shortfn = shortit->second;
|
||||
bods = (char *)"";
|
||||
if (qmsg->parsed->bodies.nb_elt == 1) {
|
||||
bod1 = (osip_body_t *)qmsg->parsed->bodies.node->element;
|
||||
bods = bod1->body;
|
||||
// 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
|
||||
¶ms);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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 "
|
||||
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 << ").";
|
||||
<< "(" << bods << "): " << params.scp_reply;
|
||||
next_state = NO_STATE;
|
||||
return true;
|
||||
|
||||
sca = (*shortfn) (qmsg->parsed->from->url->username, // imsi
|
||||
bods, // msg text
|
||||
¶ms);
|
||||
case SCA_EXEC_SMQUEUE:
|
||||
reexec_smqueue = true;
|
||||
stop_main_loop = true;
|
||||
next_state = DELETE_ME_STATE;
|
||||
return true;
|
||||
|
||||
// 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) {
|
||||
return DELETE_ME_STATE; // Done!
|
||||
}
|
||||
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;
|
||||
return NO_STATE;
|
||||
case SCA_QUIT_SMQUEUE:
|
||||
stop_main_loop = true;
|
||||
next_state = DELETE_ME_STATE;
|
||||
return true;
|
||||
|
||||
case SCA_EXEC_SMQUEUE:
|
||||
reexec_smqueue = true;
|
||||
stop_main_loop = true;
|
||||
return DELETE_ME_STATE;
|
||||
case SCA_DONE:
|
||||
next_state = DELETE_ME_STATE;
|
||||
return true;
|
||||
|
||||
case SCA_QUIT_SMQUEUE:
|
||||
stop_main_loop = true;
|
||||
return DELETE_ME_STATE;
|
||||
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_DONE:
|
||||
return DELETE_ME_STATE;
|
||||
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_RETRY_AFTER_DELAY:
|
||||
// FIXME, timeout is implicit in set_state table,
|
||||
// rather than taken from params.scp_delay.
|
||||
qmsg->retries++;
|
||||
return REQUEST_FROM_ADDRESS_LOOKUP;
|
||||
case SCA_REGISTER:
|
||||
next_state = register_handset(qmsg);
|
||||
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.
|
||||
return AWAITING_REGISTER_HANDSET;
|
||||
case SCA_TREAT_AS_ORDINARY:
|
||||
break; // fall through into non-special case.
|
||||
|
||||
case SCA_REGISTER:
|
||||
return register_handset(qmsg);
|
||||
|
||||
case SCA_TREAT_AS_ORDINARY:
|
||||
break; // fall through into non-special case.
|
||||
}
|
||||
case SCA_RESTART_PROCESSING:
|
||||
next_state = INITIAL_STATE;
|
||||
return true;
|
||||
}
|
||||
|
||||
// For non-special messages, look up who they're from.
|
||||
return lookup_from_address (&*qmsg);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1378,18 +1423,15 @@ SMq::lookup_from_address (short_msg_pending *qmsg)
|
|||
// into the message text, followed by a space... GSM 03.40 sec 3.8
|
||||
ostringstream newtext;
|
||||
osip_body_t *bod1;
|
||||
char *bods;
|
||||
|
||||
bods = (char *)"";
|
||||
if (qmsg->parsed->bodies.nb_elt == 1) {
|
||||
if (qmsg->parsed->bodies.nb_elt == 1) {
|
||||
bod1 = (osip_body_t *)qmsg->parsed->bodies.node->element;
|
||||
bods = bod1->body;
|
||||
} else {
|
||||
return NO_STATE; // Punt on msg w/no text
|
||||
}
|
||||
newtext << qmsg->parsed->from->url->username << "@"
|
||||
<< host << " " << bods;
|
||||
osip_free(bods);
|
||||
<< host << " " << qmsg->get_text();
|
||||
osip_free(bod1->body);
|
||||
bod1->body = osip_strdup(newtext.str().c_str());
|
||||
bod1->length = strlen(bod1->body);
|
||||
|
||||
|
@ -1461,7 +1503,7 @@ SMq::lookup_from_address (short_msg_pending *qmsg)
|
|||
<< "> to phonenum failed.";
|
||||
LOG(DEBUG) << qmsg->text;
|
||||
return bounce_message (qmsg,
|
||||
gConfig.getStr("BounceMessage.IMSILookupFailed")
|
||||
gConfig.getStr("Bounce.Message.IMSILookupFailed")
|
||||
);
|
||||
// return NO_STATE; // Put it into limbo for debug.
|
||||
}
|
||||
|
@ -1540,7 +1582,7 @@ SMq::lookup_uri_imsi (short_msg_pending *qmsg)
|
|||
|| !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("BounceMessage.NotRegistered"));
|
||||
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
|
||||
|
@ -1782,7 +1824,7 @@ SMq::respond_sip_ack(int errcode, short_msg_pending *smp,
|
|||
break;
|
||||
case 413: phrase="Message Body Size Error"; break;
|
||||
case 415: phrase="Unsupported Content Type";
|
||||
osip_message_set_accept(response.parsed, "text/plain");
|
||||
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;
|
||||
|
@ -1897,6 +1939,7 @@ SMq::main_loop()
|
|||
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;
|
||||
|
@ -2017,7 +2060,9 @@ SMq::save_queue_to_file(std::string qfile)
|
|||
ofile << "=== " << (int) x->state << " "
|
||||
<< x->next_action_time << " "
|
||||
<< my_network.string_addr((struct sockaddr *)x->srcaddr, x->srcaddrlen, true) << " "
|
||||
<< strlen(x->text) << endl << x->text << endl << endl;
|
||||
<< strlen(x->text)
|
||||
<< x->ms_to_sc << x->need_repack << endl
|
||||
<< x->text << endl << endl;
|
||||
howmany++;
|
||||
}
|
||||
|
||||
|
@ -2040,6 +2085,8 @@ 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;
|
||||
|
@ -2062,6 +2109,8 @@ SMq::read_queue_from_file(std::string qfile)
|
|||
}
|
||||
ifile >> netaddrstr;
|
||||
ifile >> alength;
|
||||
ifile >> ms_to_sc;
|
||||
ifile >> need_repack;
|
||||
while (ifile.peek() == '\n')
|
||||
ignoreme = ifile.get();
|
||||
msgtext = new char[alength+2];
|
||||
|
@ -2079,6 +2128,10 @@ SMq::read_queue_from_file(std::string qfile)
|
|||
// 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();
|
||||
|
@ -2091,7 +2144,8 @@ SMq::read_queue_from_file(std::string qfile)
|
|||
<< 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
|
||||
|
@ -2113,7 +2167,7 @@ SMq::read_queue_from_file(std::string qfile)
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
#if 0
|
||||
/*
|
||||
* Read in a message from a file. Return malloc'd char block of the whole
|
||||
* thing.
|
||||
|
@ -2169,7 +2223,7 @@ read_short_msg_pending_from_file(char *fname)
|
|||
smp = new short_msg_pending (length, sip_text, true);
|
||||
return smp;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/* Really simple first try */
|
||||
|
@ -2183,7 +2237,8 @@ main(int argc, char **argv)
|
|||
// short_msg_pending *smp;
|
||||
std::string savefile;
|
||||
|
||||
init_smcommands(&short_code_map); // Set up short-code commands users can type
|
||||
// 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.
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
#define SM_QUEUE_H
|
||||
|
||||
#include <time.h>
|
||||
#include <osipparser2/osip_message.h> /* from osipparser2 */
|
||||
//#include <osipparser2/osip_message.h> /* from osipparser2 */
|
||||
#include <stdlib.h> /* for osipparser2 */
|
||||
#include <sys/time.h> /* for osip_init */
|
||||
#include <osip2/osip.h> /* for osip_init */
|
||||
|
@ -35,10 +35,18 @@
|
|||
#include "smnet.h" // My network support
|
||||
#include "HLR.h" // My home location register
|
||||
|
||||
// That's awful OSIP has a CR define.
|
||||
// It clashes with our innocent L2Address::CR().
|
||||
// Don't create 2-letter #defines, ever!
|
||||
#undef CR
|
||||
#include "SMSMessages.h"
|
||||
using namespace SMS;
|
||||
|
||||
|
||||
namespace SMqueue {
|
||||
|
||||
/* Maximum text size of an SMS message. */
|
||||
#define SMS_MESSAGE_MAX_LENGTH 140
|
||||
#define SMS_MESSAGE_MAX_LENGTH 160
|
||||
|
||||
/* std::abort isn't always there. Neither is the C library version.
|
||||
Idiots? You tell me. */
|
||||
|
@ -60,33 +68,33 @@ char *new_strdup(const char *orig);
|
|||
*/
|
||||
|
||||
enum sm_state { // timeout, next-state-if-timeout
|
||||
NO_STATE = 0,
|
||||
REQUEST_FROM_ADDRESS_LOOKUP = 1,
|
||||
ASKED_FOR_FROM_ADDRESS_LOOKUP = 2,
|
||||
NO_STATE,
|
||||
INITIAL_STATE,
|
||||
REQUEST_FROM_ADDRESS_LOOKUP,
|
||||
ASKED_FOR_FROM_ADDRESS_LOOKUP,
|
||||
|
||||
AWAITING_TRY_DESTINATION_IMSI = 3,
|
||||
REQUEST_DESTINATION_IMSI = 4,
|
||||
ASKED_FOR_DESTINATION_IMSI = 5,
|
||||
AWAITING_TRY_DESTINATION_IMSI,
|
||||
REQUEST_DESTINATION_IMSI,
|
||||
ASKED_FOR_DESTINATION_IMSI,
|
||||
|
||||
AWAITING_TRY_DESTINATION_SIPURL = 6,
|
||||
REQUEST_DESTINATION_SIPURL = 7,
|
||||
ASKED_FOR_DESTINATION_SIPURL = 8,
|
||||
AWAITING_TRY_DESTINATION_SIPURL,
|
||||
REQUEST_DESTINATION_SIPURL,
|
||||
ASKED_FOR_DESTINATION_SIPURL,
|
||||
|
||||
AWAITING_TRY_MSG_DELIVERY = 9,
|
||||
REQUEST_MSG_DELIVERY = 10,
|
||||
ASKED_FOR_MSG_DELIVERY = 11,
|
||||
AWAITING_TRY_MSG_DELIVERY,
|
||||
REQUEST_MSG_DELIVERY,
|
||||
ASKED_FOR_MSG_DELIVERY,
|
||||
|
||||
DELETE_ME_STATE = 12,
|
||||
DELETE_ME_STATE,
|
||||
|
||||
AWAITING_REGISTER_HANDSET = 13,
|
||||
REGISTER_HANDSET = 14,
|
||||
ASKED_TO_REGISTER_HANDSET = 15,
|
||||
AWAITING_REGISTER_HANDSET,
|
||||
REGISTER_HANDSET,
|
||||
ASKED_TO_REGISTER_HANDSET,
|
||||
|
||||
STATE_MAX_PLUS_ONE, /* Keep this one last! */
|
||||
};
|
||||
|
||||
#define STATE_MAX (STATE_MAX_PLUS_ONE - 1)
|
||||
#define INITIAL_STATE REQUEST_FROM_ADDRESS_LOOKUP
|
||||
|
||||
// How to print a state
|
||||
extern std::string sm_state_strings[STATE_MAX_PLUS_ONE];
|
||||
|
@ -102,6 +110,13 @@ extern struct osip *osipptr;
|
|||
term storage in the queue. */
|
||||
class short_msg {
|
||||
public:
|
||||
|
||||
enum ContentType {
|
||||
UNSUPPORTED_CONTENT,
|
||||
TEXT_PLAIN,
|
||||
VND_3GPP_SMS
|
||||
};
|
||||
|
||||
/* First just the text string. A SIP message including body. */
|
||||
unsigned short text_length;
|
||||
char *text /* [text_length] */; // C++ doesn't make it simple
|
||||
|
@ -116,13 +131,32 @@ class short_msg {
|
|||
// to;
|
||||
// time_t date;
|
||||
// expiration;
|
||||
ContentType content_type; // Content-Type of the message
|
||||
|
||||
RPData *rp_data; // Parsed RP-DATA of an SMS. It's read from MESSAGE body if
|
||||
// it has application/vnd.3gpp.sms MIME-type. Note, that
|
||||
// it's parsed on request and may be NULL at any point.
|
||||
TLMessage *tl_message; // Parsed RPDU of an SMS. It's read from rp_data. Note,
|
||||
// that it's parsed on request and may be NULL at
|
||||
// any point.
|
||||
bool ms_to_sc; // Direction of the message. True is this is MS->SC SMS, false
|
||||
// otherwise.
|
||||
bool need_repack; // Message should be packed into TPDU for delivery. E.g.
|
||||
// SIP MESSAGE sent to MS should be packed, while SIP
|
||||
// REGISTER should not.
|
||||
|
||||
short_msg () :
|
||||
text_length (0),
|
||||
text (NULL),
|
||||
parsed_is_valid (false),
|
||||
parsed_is_better (false),
|
||||
parsed (NULL) {
|
||||
parsed (NULL),
|
||||
content_type(UNSUPPORTED_CONTENT),
|
||||
rp_data(NULL),
|
||||
tl_message(NULL),
|
||||
ms_to_sc(false),
|
||||
need_repack(true)
|
||||
{
|
||||
}
|
||||
// Make a short message, perhaps taking responsibility for deleting
|
||||
// the "new"-allocated memory passed in.
|
||||
|
@ -131,7 +165,12 @@ class short_msg {
|
|||
text (cstr),
|
||||
parsed_is_valid (false),
|
||||
parsed_is_better (false),
|
||||
parsed (NULL)
|
||||
parsed (NULL),
|
||||
content_type(UNSUPPORTED_CONTENT),
|
||||
rp_data(NULL),
|
||||
tl_message(NULL),
|
||||
ms_to_sc(false),
|
||||
need_repack(true)
|
||||
{
|
||||
if (!use_my_memory) {
|
||||
text = new char [text_length+1];
|
||||
|
@ -151,7 +190,12 @@ class short_msg {
|
|||
text (0),
|
||||
parsed_is_valid (false),
|
||||
parsed_is_better (false),
|
||||
parsed (NULL)
|
||||
parsed (NULL),
|
||||
content_type(UNSUPPORTED_CONTENT),
|
||||
rp_data(NULL),
|
||||
tl_message(NULL),
|
||||
ms_to_sc(false),
|
||||
need_repack(true)
|
||||
{
|
||||
if (text_length) {
|
||||
text = new char [text_length+1];
|
||||
|
@ -159,14 +203,19 @@ class short_msg {
|
|||
text[text_length] = '\0';
|
||||
}
|
||||
};
|
||||
public:
|
||||
|
||||
#if 0
|
||||
short_msg (std::string str) :
|
||||
text_length (str.length()),
|
||||
text (0),
|
||||
parsed_is_valid (false),
|
||||
parsed_is_better (false),
|
||||
parsed (NULL)
|
||||
parsed (NULL),
|
||||
content_type(UNSUPPORTED_CONTENT),
|
||||
rp_data(NULL),
|
||||
tl_message(NULL),
|
||||
ms_to_sc(false),
|
||||
need_repack(false)
|
||||
{
|
||||
text = new char [text_length+1];
|
||||
strncpy(text, str.data(), text_length);
|
||||
|
@ -174,35 +223,26 @@ class short_msg {
|
|||
};
|
||||
#endif
|
||||
|
||||
/* Override operator= to avoid pointer-sharing problems */
|
||||
/* Disable operator= to avoid pointer-sharing problems */
|
||||
private:
|
||||
short_msg & operator= (const short_msg &rvalue);
|
||||
public:
|
||||
/*
|
||||
{
|
||||
this->text_length = rvalue.text_length;
|
||||
this->text = new char [rvalue.text_length+1];
|
||||
strncpy(this->text, rvalue.text, rvalue.text_length);
|
||||
this->text[rvalue.text_length] = '\0';
|
||||
this->parsed_is_valid = rvalue.parsed_is_valid;
|
||||
this->parsed_is_better = rvalue.parsed_is_better;
|
||||
osip_message_clone (rvalue.parsed, &this->parsed);
|
||||
return *this;
|
||||
} */
|
||||
|
||||
/* Destructor */
|
||||
virtual ~short_msg () { unparse(); delete [] text; };
|
||||
virtual ~short_msg ()
|
||||
{
|
||||
if (parsed)
|
||||
osip_message_free(parsed);
|
||||
delete [] text;
|
||||
delete rp_data;
|
||||
delete tl_message;
|
||||
};
|
||||
|
||||
// Pseudo-constructor due to inability to run constructors on
|
||||
// members of lists.
|
||||
// Initialize a newly-default-constructed short message,
|
||||
// perhaps taking responsibility for deleting
|
||||
// the "new"-allocated memory passed in.
|
||||
void
|
||||
initialize ()
|
||||
{ // Nothing to do: default constructor + default initializer
|
||||
}
|
||||
|
||||
void
|
||||
initialize (int len, char * const cstr, bool use_my_memory)
|
||||
{
|
||||
|
@ -233,6 +273,7 @@ class short_msg {
|
|||
|
||||
unparse(); // Free any previous one.
|
||||
|
||||
// Parse SIP message
|
||||
i = osip_message_init(&sip);
|
||||
if (i != 0) abfuckingort(); /* throw out-of-memory */
|
||||
i = osip_message_parse(sip, text, text_length);
|
||||
|
@ -240,11 +281,45 @@ class short_msg {
|
|||
parsed = sip;
|
||||
parsed_is_valid = true;
|
||||
parsed_is_better = false;
|
||||
|
||||
// Now parse SMS if needed
|
||||
if (parsed->content_type == NULL)
|
||||
{
|
||||
// Most likely this is SIP response.
|
||||
content_type = UNSUPPORTED_CONTENT;
|
||||
} else if ( strcmp(parsed->content_type->type, "text") == 0
|
||||
&& strcmp(parsed->content_type->subtype, "plain") == 0)
|
||||
{
|
||||
// If Content-Type is text/plain, then no decoding is needed.
|
||||
content_type = TEXT_PLAIN;
|
||||
} else if ( strcmp(parsed->content_type->type, "application") == 0
|
||||
&& strcmp(parsed->content_type->subtype, "vnd.3gpp.sms") == 0)
|
||||
{
|
||||
// This is an encoded SMS' TPDU.
|
||||
content_type = VND_3GPP_SMS;
|
||||
|
||||
// Decode it RP-DATA
|
||||
osip_body_t *bod1 = (osip_body_t *)parsed->bodies.node->element;
|
||||
const char *bods = bod1->body;
|
||||
rp_data = hex2rpdata(bods);
|
||||
if (rp_data == NULL) {
|
||||
LOG(INFO) << "RP-DATA unpacking failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decode RPDU
|
||||
tl_message = parseTPDU(rp_data->TPDU());
|
||||
if (rp_data == NULL) {
|
||||
LOG(INFO) << "RPDU parsing failed";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Anytime a caller CHANGES the values in the parsed tree of the
|
||||
message, they MUST call this, to let the cacheing system
|
||||
message, they MUST call this, to let the caching system
|
||||
know that the cached copy of the text-string message is no
|
||||
longer valid. Actually, we have TWO such cached copies! FIXME
|
||||
so we have to invalidate both of them. */
|
||||
|
@ -256,7 +331,7 @@ class short_msg {
|
|||
|
||||
/* Make the text string valid, if the parsed copy is better.
|
||||
(It gets "better" by being modified, and parsed_was_changed()
|
||||
got called, but we deferred fixing up the text string til now.) */
|
||||
got called, but we deferred fixing up the text string till now.) */
|
||||
void
|
||||
make_text_valid() {
|
||||
if (parsed_is_better) {
|
||||
|
@ -288,6 +363,15 @@ class short_msg {
|
|||
|
||||
/* Free up all memory used by parsed version of message. */
|
||||
void unparse() {
|
||||
// Free parsed TDPU.
|
||||
// FIXME -- We should check if it has been changed and update MESSAGE body.
|
||||
delete rp_data;
|
||||
rp_data = NULL;
|
||||
delete tl_message;
|
||||
tl_message = NULL;
|
||||
content_type = UNSUPPORTED_CONTENT;
|
||||
|
||||
// Now unparse SIP
|
||||
if (parsed_is_better)
|
||||
make_text_valid();
|
||||
if (parsed)
|
||||
|
@ -297,6 +381,39 @@ class short_msg {
|
|||
parsed_is_better = false;
|
||||
}
|
||||
|
||||
std::string get_text() const
|
||||
{
|
||||
switch (content_type) {
|
||||
case TEXT_PLAIN: {
|
||||
osip_body_t *bod1 = (osip_body_t *)parsed->bodies.node->element;
|
||||
return bod1->body;
|
||||
}
|
||||
break;
|
||||
|
||||
case VND_3GPP_SMS: {
|
||||
const TLSubmit *submit = (TLSubmit*)tl_message;
|
||||
if (submit == NULL) {
|
||||
return "";
|
||||
}
|
||||
|
||||
try {
|
||||
return submit->UD().decode();
|
||||
}
|
||||
catch (SMSReadError) {
|
||||
LOG(WARN) << "SMS parsing failed (above L3)";
|
||||
// TODO:: Should we send error back to the phone?
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
case UNSUPPORTED_CONTENT:
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// I couldn't figure out how to make these static members of the class...
|
||||
|
@ -372,19 +489,6 @@ class short_msg_pending: public short_msg {
|
|||
linktag (NULL)
|
||||
{
|
||||
}
|
||||
|
||||
short_msg_pending (const short_msg &sm) :
|
||||
short_msg (sm),
|
||||
state (NO_STATE),
|
||||
next_action_time (0),
|
||||
retries (0),
|
||||
// srcaddr({0}), // can't seem to initialize an array?
|
||||
srcaddrlen(0),
|
||||
qtag (NULL),
|
||||
qtaghash (0),
|
||||
linktag (NULL)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
//
|
||||
|
@ -425,37 +529,10 @@ class short_msg_pending: public short_msg {
|
|||
this->linktag[len] = '\0';
|
||||
}
|
||||
}
|
||||
public:
|
||||
|
||||
/* Override operator= to avoid pointer-sharing problems */
|
||||
private:
|
||||
short_msg_pending & operator= (const short_msg_pending &rvalue);
|
||||
/* FIXME, remove if not needed?
|
||||
{
|
||||
this->state = rvalue.state;
|
||||
this->next_action_time = rvalue.next_action_time;
|
||||
this->retries = rvalue.retries;
|
||||
this->qtag = rvalue.qtag;
|
||||
if (rvalue.srcaddrlen) {
|
||||
if (rvalue.srcaddrlen > sizeof (srcaddr))
|
||||
abfuckingort();
|
||||
memcpy(this->srcaddr, rvalue.srcaddr, rvalue.srcaddrlen);
|
||||
}
|
||||
|
||||
if (rvalue.qtag) {
|
||||
int len = strlen(rvalue.qtag);
|
||||
this->qtag = new char[len+1];
|
||||
strncpy(this->qtag, rvalue.qtag, len);
|
||||
this->qtag[len] = '\0';
|
||||
}
|
||||
this->qtaghash = rvalue.qtaghash;
|
||||
if (rvalue.linktag) {
|
||||
int len = strlen(rvalue.linktag);
|
||||
this->linktag = new char[len+1];
|
||||
strncpy(this->linktag, rvalue.linktag, len);
|
||||
this->linktag[len] = '\0';
|
||||
}
|
||||
} */
|
||||
public:
|
||||
|
||||
/* Destructor */
|
||||
|
@ -477,28 +554,7 @@ class short_msg_pending: public short_msg {
|
|||
* This 'constructs' the new short_msg_pending in a temporary list,
|
||||
* and we can then trivially move it into the real message queue,
|
||||
* removing it from the temporary list in the process.
|
||||
*
|
||||
* The wriggling guts under C++ are a little too visible for my taste.
|
||||
*/
|
||||
void
|
||||
initguts ()
|
||||
{
|
||||
state = NO_STATE;
|
||||
next_action_time = 0;
|
||||
retries = 0;
|
||||
memset (srcaddr, 0, sizeof(srcaddr));
|
||||
srcaddrlen = 0;
|
||||
qtag = NULL;
|
||||
qtaghash = 0;
|
||||
linktag = NULL;
|
||||
}
|
||||
void
|
||||
initialize ()
|
||||
{
|
||||
short_msg::initialize ();
|
||||
// initguts();
|
||||
}
|
||||
|
||||
// Make a pending short message, perhaps taking responsibility for
|
||||
// deleting the "new"-allocated memory passed in.
|
||||
void
|
||||
|
@ -521,19 +577,6 @@ class short_msg_pending: public short_msg {
|
|||
linktag (NULL)
|
||||
{
|
||||
}
|
||||
|
||||
short_msg_pending (const short_msg &sm) :
|
||||
short_msg (sm),
|
||||
state (NO_STATE),
|
||||
next_action_time (0),
|
||||
retries (0),
|
||||
// srcaddr({0}), // can't seem to initialize an array?
|
||||
srcaddrlen(0),
|
||||
qtag (NULL),
|
||||
qtaghash (0),
|
||||
linktag (NULL)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Optimize this later so we don't make so many kernel calls. */
|
||||
|
@ -577,6 +620,7 @@ class short_msg_pending: public short_msg {
|
|||
/* Check host and port for validity. */
|
||||
bool
|
||||
check_host_port(char *host, char *port);
|
||||
|
||||
};
|
||||
|
||||
typedef std::list<short_msg_pending> short_msg_p_list;
|
||||
|
@ -589,16 +633,20 @@ typedef std::list<short_msg_pending> short_msg_p_list;
|
|||
class SMq;
|
||||
|
||||
enum short_code_action {
|
||||
SCA_DONE = 0,
|
||||
SCA_INTERNAL_ERROR = 1,
|
||||
SCA_REPLY = 2,
|
||||
SCA_RETRY_AFTER_DELAY = 3,
|
||||
SCA_REPLY_AND_RETRY = 4,
|
||||
SCA_QUIT_SMQUEUE = 5,
|
||||
SCA_AWAIT_REGISTER = 6,
|
||||
SCA_REGISTER = 7,
|
||||
SCA_TREAT_AS_ORDINARY = 8,
|
||||
SCA_EXEC_SMQUEUE = 9
|
||||
SCA_DONE = 0, ///< No further processing is needed. Free message.
|
||||
SCA_INTERNAL_ERROR = 1, //< Just report error and bail out.
|
||||
SCA_REPLY = 2, ///< Free this message and send replay back to the msg sender
|
||||
///< with a text from params.scp_reply
|
||||
SCA_RETRY_AFTER_DELAY = 3, ///< HLR is busy. Retry query later.
|
||||
SCA_REPLY_AND_RETRY = 4, ///< UNUSED.
|
||||
SCA_QUIT_SMQUEUE = 5, ///< Self-explanatory. Exit smqueue.
|
||||
SCA_AWAIT_REGISTER = 6, ///< HLR response is delayed. Wait.
|
||||
SCA_REGISTER = 7, ///< HLR record for this phone has been retrieved.
|
||||
///< Proceed to registration with Asterisk.
|
||||
SCA_TREAT_AS_ORDINARY = 8, ///< Continue msg processing as if it were non-shortcode msg.
|
||||
SCA_EXEC_SMQUEUE = 9, ///< Fork new smqueue instance and exit this one.
|
||||
SCA_RESTART_PROCESSING = 10 ///< Return from this short code processing
|
||||
///< and run another short code.
|
||||
};
|
||||
|
||||
class short_code_params {
|
||||
|
@ -812,9 +860,13 @@ class SMq {
|
|||
enum sm_state
|
||||
register_handset (short_msg_p_list::iterator qmsg);
|
||||
|
||||
/* Initial handling of an incoming SMS message in the queue */
|
||||
enum sm_state
|
||||
handle_sms_message(short_msg_p_list::iterator qmsg);
|
||||
/* Check if this is a short-code message and handle it.
|
||||
* Return true if message has been handled, false if you should continue
|
||||
* message handling as usual. In latter case \p next_state is untouched.
|
||||
*/
|
||||
bool
|
||||
handle_short_code(const short_code_map_t &short_code_map,
|
||||
short_msg_p_list::iterator qmsg, enum sm_state &next_state);
|
||||
|
||||
/* When a SIP response arrives, search the queue for its matching
|
||||
MESSAGE and handle both. */
|
||||
|
|
|
@ -0,0 +1,394 @@
|
|||
/*
|
||||
* SMSC.h - SMS Center implementation for OpenBTS.
|
||||
* Written by Alexander Chemeris, 2010.
|
||||
*
|
||||
* Copyright 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 <stdio.h>
|
||||
//#include <GSMLogicalChannel.h>
|
||||
//#include <GSML3MMMessages.h>
|
||||
#include <GSML3Message.h>
|
||||
//#include <GSML3CommonElements.h>
|
||||
//#include "ControlCommon.h"
|
||||
#include <Regexp.h>
|
||||
#include <Logger.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace GSM;
|
||||
//using namespace Control;
|
||||
|
||||
//#include "SIPInterface.h"
|
||||
//#include "SIPUtility.h"
|
||||
//#include "SIPMessage.h"
|
||||
//#include "SIPEngine.h"
|
||||
//using namespace SIP;
|
||||
|
||||
//#include "CollectMSInfo.h"
|
||||
|
||||
#include "smsc.h"
|
||||
|
||||
// FORWARD DECLARATIONS
|
||||
void set_to_for_smsc(const char *address, short_msg_p_list::iterator &smsg);
|
||||
|
||||
/**@name Functions for transmitting through various gateways. */
|
||||
//@{
|
||||
|
||||
|
||||
/** Substitute spaces with +'s for compatibility with some protocols. */
|
||||
void convertText(char *dest, const char *src)
|
||||
{
|
||||
// FIXME -- We should just do full "percent encoding" here.
|
||||
while (*src != '\0') {
|
||||
if (*src == ' ') *dest++ = '+';
|
||||
else *dest++ = *src;
|
||||
src++;
|
||||
}
|
||||
*dest = '\0';
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Send SMS via and HTTP interface. */
|
||||
short_code_action sendHTTP(const char* destination, const std::string &message)
|
||||
{
|
||||
char convMessage[message.length()+2];
|
||||
convertText(convMessage,message.data());
|
||||
char command[2048];
|
||||
// FIXME -- Check specs for a good timeout value here.
|
||||
sprintf(command,"wget -T 5 -C -q -O - \"http://%s/http/%s&to=%s&text=%s\" >& /dev/null",
|
||||
gConfig.getStr("SMS.HTTP.Gateway"),
|
||||
gConfig.getStr("SMS.HTTP.AccessString"),
|
||||
destination, convMessage);
|
||||
LOG(DEBUG) << "MOSMS: sendHTTP sending with " << command;
|
||||
|
||||
// HTTP "GET" method with wget.
|
||||
// FIXME -- Look at the output of wget to check success.
|
||||
FILE* wget = popen(command,"r");
|
||||
if (!wget) {
|
||||
LOG(ALARM) << "cannot open wget with " << command;
|
||||
return SCA_INTERNAL_ERROR;
|
||||
}
|
||||
pclose(wget);
|
||||
return SCA_DONE;
|
||||
}
|
||||
|
||||
|
||||
/** Send e-mail with local sendmail program. */
|
||||
short_code_action sendEMail(const char* address, const char* body, const char* subject=NULL)
|
||||
{
|
||||
// We're not checking for overflow because the TPDU can't be more than a few hundred bytes.
|
||||
|
||||
// Build the command line.
|
||||
// FIXME -- Use sendmail to have better header control.
|
||||
char command[1024];
|
||||
if (subject) sprintf(command,"mail -s \"%s\" %s",subject,address);
|
||||
else sprintf(command,"mail %s",address);
|
||||
LOG(INFO) << "sending SMTP: \"" << body << "\" via \"" << command << "\"";
|
||||
|
||||
// Send the mail.
|
||||
FILE* mail = popen(command,"w");
|
||||
if (!mail) {
|
||||
LOG(ALARM) << "cannot send mail with \"" << command << "\"";
|
||||
return SCA_INTERNAL_ERROR;
|
||||
}
|
||||
// FIXME -- We should be sure body is 7-bit clean.
|
||||
fprintf(mail,"%s",body);
|
||||
if (pclose(mail) == -1) return SCA_INTERNAL_ERROR;
|
||||
return SCA_DONE;
|
||||
}
|
||||
|
||||
short_code_action sendSIP_init(const char *imsi, const TLSubmit& submit,
|
||||
const std::string &body, short_code_params *scp)
|
||||
{
|
||||
const char *address = submit.DA().digits();
|
||||
const TLUserData& tl_ud = submit.UD();
|
||||
const char *from = scp->scp_qmsg_it->parsed->from->url->username;
|
||||
LOG(INFO) << "from " << imsi << " to " << address;
|
||||
|
||||
if (scp == NULL)
|
||||
{
|
||||
LOG(WARN) << "short_code_params is NULL. Error.";
|
||||
return SCA_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
// START OF THE SIP PROCESSING
|
||||
osip_message_t *omsg = scp->scp_qmsg_it->parsed;
|
||||
|
||||
// Req.URI
|
||||
osip_free(omsg->req_uri->username);
|
||||
omsg->req_uri->username = (char *)osip_malloc (strlen(address)+1);
|
||||
strcpy(omsg->req_uri->username, address);
|
||||
|
||||
// To:
|
||||
set_to_for_smsc(address, scp->scp_qmsg_it);
|
||||
|
||||
// Let them know that parsed part has been changed.
|
||||
scp->scp_qmsg_it->parsed_was_changed();
|
||||
|
||||
if (ISLOGGING(DEBUG)) {
|
||||
// Call make_text_valid() is needed for debug only.
|
||||
scp->scp_qmsg_it->make_text_valid();
|
||||
LOG(DEBUG) << "Updated SMS message: " << scp->scp_qmsg_it->text;
|
||||
}
|
||||
return SCA_RESTART_PROCESSING;
|
||||
}
|
||||
|
||||
void create_sms_delivery(const std::string &body,
|
||||
const TLUserData &UD,
|
||||
short_msg_p_list::iterator &smsg)
|
||||
{
|
||||
RPData *rp_data_new = NULL;
|
||||
TLDeliver *deliver = NULL;
|
||||
const char *from = smsg->parsed->from->url->username;
|
||||
|
||||
// HACK
|
||||
// Check for "Easter Eggs"
|
||||
// TL-PID
|
||||
// See 03.40 9.2.3.9.
|
||||
unsigned TLPID=0;
|
||||
if (strncmp(body.data(),"#!TLPID",7)==0) sscanf(body.data(),"#!TLPID%d",&TLPID);
|
||||
|
||||
// Generate RP-DATA with SMS-DELIVER
|
||||
{
|
||||
unsigned reference = random() % 255;
|
||||
deliver = new TLDeliver(from,UD,TLPID);
|
||||
rp_data_new = new RPData(reference, RPAddress(gConfig.getStr("SMS.FakeSrcSMSC")),
|
||||
*deliver);
|
||||
LOG(DEBUG) << "New RPData: " << *rp_data_new;
|
||||
}
|
||||
|
||||
// Replace RP-DATA in the message
|
||||
delete smsg->rp_data;
|
||||
delete smsg->tl_message;
|
||||
smsg->rp_data = rp_data_new;
|
||||
smsg->tl_message = deliver;
|
||||
}
|
||||
|
||||
void pack_tpdu(short_msg_p_list::iterator &smsg)
|
||||
{
|
||||
// Pack RP-DATA to bitstream
|
||||
RLFrame RPDU_new(smsg->rp_data->bitsNeeded());
|
||||
smsg->rp_data->write(RPDU_new);
|
||||
LOG(DEBUG) << "New RLFrame: " << RPDU_new;
|
||||
|
||||
// START OF THE SIP PROCESSING
|
||||
osip_message_t *omsg = smsg->parsed;
|
||||
|
||||
// Message body
|
||||
osip_body_t *bod1 = (osip_body_t *)omsg->bodies.node->element;
|
||||
osip_free(bod1->body);
|
||||
ostringstream body_stream;
|
||||
RPDU_new.hex(body_stream);
|
||||
bod1->length = body_stream.str().length();
|
||||
bod1->body = (char *)osip_malloc (bod1->length+1);
|
||||
strcpy(bod1->body, body_stream.str().data());
|
||||
|
||||
// Let them know that parsed part has been changed.
|
||||
smsg->parsed_was_changed();
|
||||
|
||||
// It's SC->MS now
|
||||
smsg->ms_to_sc = false;
|
||||
}
|
||||
|
||||
void set_to_for_smsc(const char *address, short_msg_p_list::iterator &smsg)
|
||||
{
|
||||
osip_message_t *omsg = smsg->parsed;
|
||||
|
||||
// Set To field
|
||||
osip_free(omsg->to->url->username);
|
||||
osip_free(omsg->to->displayname);
|
||||
omsg->to->url->username = (char *)osip_malloc (strlen(address)+1);
|
||||
omsg->to->displayname = (char *)osip_malloc (strlen(omsg->req_uri->username)+1);
|
||||
strcpy(omsg->to->url->username, address);
|
||||
strcpy(omsg->to->displayname, omsg->req_uri->username);
|
||||
|
||||
// Let them know that parsed part has been changed.
|
||||
smsg->parsed_was_changed();
|
||||
}
|
||||
|
||||
//@}
|
||||
|
||||
|
||||
/** Send a TPDU through whatever gateway is available. */
|
||||
short_code_action submitSMS(const char *imsi, const TLSubmit& submit,
|
||||
const std::string &body, short_code_params *scp)
|
||||
{
|
||||
LOG(INFO) << "from " << imsi << " message: " << submit;
|
||||
const TLAddress& address = submit.DA();
|
||||
|
||||
// Check for direct e-mail address at start of message body.
|
||||
// FIXME -- This doesn't really follow the spec. See GSM 03.40 3.8.
|
||||
static const Regexp emailAddress("^[[:graph:]]+@[[:graph:]]+ ");
|
||||
if (emailAddress.match(body.data())) {
|
||||
// FIXME -- Get the sender's E.164 to put in the subject line.
|
||||
char bodyCopy[body.length()+2];
|
||||
strcpy(bodyCopy,body.data());
|
||||
char* SMTPAddress = bodyCopy;
|
||||
char* term = strchr(bodyCopy,' ');
|
||||
// If term's NULL, the regexp is broken.
|
||||
assert(term);
|
||||
*term = '\0';
|
||||
char* SMTPPayload = term+1;
|
||||
LOG(INFO) << "sending SMTP to " << SMTPAddress << ": " << SMTPPayload;
|
||||
if (SMTPPayload) return sendEMail(SMTPAddress,SMTPPayload,"from OpenBTS gateway");
|
||||
else return sendEMail(SMTPAddress,"(empty)","from OpenBTS gateway");
|
||||
}
|
||||
|
||||
// Send to smqueue or HTTP gateway, depending on what's defined in the config.
|
||||
if (gConfig.defines("SMS.HTTP.Gateway"))
|
||||
// If there is an external HTTP gateway, use it.
|
||||
return sendHTTP(address.digits(), body);
|
||||
else
|
||||
// Otherwise, we are looking for a SIP interface to smqueue.
|
||||
return sendSIP_init(imsi, submit, body, scp);
|
||||
}
|
||||
|
||||
short_code_action shortcode_smsc(const char *imsi, const char *msgtext,
|
||||
short_code_params *scp)
|
||||
{
|
||||
short_code_action return_action = SCA_TREAT_AS_ORDINARY;
|
||||
short_msg_p_list::iterator smsg = scp->scp_qmsg_it;
|
||||
|
||||
if (smsg->content_type != short_msg::VND_3GPP_SMS)
|
||||
{
|
||||
LOG(WARN) << "We support only TPDU-coded SMS messages. Plain text ones are to be supported later.";
|
||||
return SCA_INTERNAL_ERROR;
|
||||
}
|
||||
if (smsg->tl_message == NULL)
|
||||
{
|
||||
LOG(WARN) << "TLMessage is not available. Error during SMS decoding occurred?";
|
||||
return SCA_INTERNAL_ERROR;
|
||||
}
|
||||
if (((TLMessage::MessageType)smsg->tl_message->MTI()) != TLMessage::SUBMIT)
|
||||
{
|
||||
LOG(WARN) << "TPDU must be of SMS-SUBMIT type, we have MTI="
|
||||
<< (TLMessage::MessageType)smsg->tl_message->MTI();
|
||||
return SCA_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
return_action = submitSMS(imsi, *((TLSubmit*)smsg->tl_message), msgtext, scp);
|
||||
|
||||
return return_action;
|
||||
}
|
||||
|
||||
bool pack_text_to_tpdu(const std::string &body,
|
||||
short_msg_p_list::iterator &smsg)
|
||||
{
|
||||
create_sms_delivery(body, TLUserData(body.data()), smsg);
|
||||
pack_tpdu(smsg);
|
||||
|
||||
// Set Content-Type field
|
||||
const char *type = "application";
|
||||
const char *subtype = "vnd.3gpp.sms";
|
||||
osip_free(smsg->parsed->content_type->type);
|
||||
osip_free(smsg->parsed->content_type->subtype);
|
||||
smsg->parsed->content_type->type = (char *)osip_malloc (strlen(type)+1);
|
||||
smsg->parsed->content_type->subtype = (char *)osip_malloc (strlen(subtype)+1);
|
||||
strcpy(smsg->parsed->content_type->type, type);
|
||||
strcpy(smsg->parsed->content_type->subtype, subtype);
|
||||
|
||||
// Let them know that parsed part has been changed.
|
||||
smsg->parsed_was_changed();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool recode_tpdu(const std::string &body,
|
||||
short_msg_p_list::iterator &smsg)
|
||||
{
|
||||
bool return_action = true;
|
||||
|
||||
// Safety check
|
||||
if (smsg->tl_message == NULL)
|
||||
{
|
||||
LOG(WARN) << "TLMessage is not available. Error during SMS decoding occurred?";
|
||||
return false;
|
||||
}
|
||||
|
||||
switch ((TLMessage::MessageType)smsg->tl_message->MTI()) {
|
||||
case TLMessage::SUBMIT: {
|
||||
TLSubmit *submit = (TLSubmit*)smsg->tl_message;
|
||||
const TLUserData& tl_ud = submit->UD();
|
||||
create_sms_delivery(body, tl_ud, smsg);
|
||||
pack_tpdu(smsg);
|
||||
return_action = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case TLMessage::DELIVER_REPORT:
|
||||
case TLMessage::STATUS_REPORT:
|
||||
// TODO
|
||||
LOG(WARN) << "TPDU must be of SMS-SUBMIT type, we have MTI="
|
||||
<< (TLMessage::MessageType)smsg->tl_message->MTI();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now we have MS->SC message
|
||||
smsg->ms_to_sc = false;
|
||||
|
||||
return return_action;
|
||||
}
|
||||
|
||||
bool pack_sms_for_delivery(short_msg_p_list::iterator &smsg)
|
||||
{
|
||||
bool return_action = true;
|
||||
std::string msgtext;
|
||||
|
||||
if (!smsg->need_repack) {
|
||||
// Nothing to do.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parse message if not parsed yet.
|
||||
smsg->parse();
|
||||
|
||||
// Get message text in plain text form
|
||||
msgtext = smsg->get_text();
|
||||
|
||||
switch (smsg->content_type) {
|
||||
case short_msg::TEXT_PLAIN:
|
||||
return_action = pack_text_to_tpdu(msgtext, smsg);
|
||||
break;
|
||||
|
||||
case short_msg::VND_3GPP_SMS:
|
||||
// No need to recode if it's SC->MS already
|
||||
if (smsg->ms_to_sc)
|
||||
{
|
||||
return_action = recode_tpdu(msgtext, smsg);
|
||||
}
|
||||
break;
|
||||
|
||||
case short_msg::UNSUPPORTED_CONTENT:
|
||||
default:
|
||||
LOG(WARN) << "Unsupported SIP-messages content.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Successful repack
|
||||
if (return_action) {
|
||||
smsg->need_repack = false;
|
||||
}
|
||||
|
||||
if (ISLOGGING(DEBUG)) {
|
||||
// Call make_text_valid() is needed for debug only.
|
||||
smsg->make_text_valid();
|
||||
LOG(DEBUG) << "Updated SMS message: " << smsg->text;
|
||||
}
|
||||
|
||||
return return_action;
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* SMSC.h - SMS Center implementation for OpenBTS.
|
||||
* Written by Alexander Chemeris, 2010.
|
||||
*
|
||||
* Copyright 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.
|
||||
*/
|
||||
|
||||
#ifndef SMSC_H
|
||||
#define SMSC_H
|
||||
|
||||
#include "smqueue.h"
|
||||
|
||||
using namespace SMqueue;
|
||||
|
||||
/** Unhex and parse msgtext to RPData struct.
|
||||
@param msgttext RPData encoded into hex-string.
|
||||
@return Pointer to parsed RPData or NULL on failure.
|
||||
*/
|
||||
RPData *hex2rpdata(const char *msgtext);
|
||||
|
||||
enum short_code_action shortcode_smsc(const char *imsi, const char *msgtext,
|
||||
short_code_params *scp);
|
||||
bool pack_sms_for_delivery(short_msg_p_list::iterator &smsg);
|
||||
|
||||
#endif
|
Reference in New Issue