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