mirror of
https://github.com/RangeNetworks/openbts.git
synced 2025-10-27 10:03:46 +00:00
1019 lines
38 KiB
C++
1019 lines
38 KiB
C++
/*
|
|
* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
|
|
* Copyright 2010 Kestrel Signal Processing, Inc.
|
|
* Copyright 2014 Range Networks, Inc.
|
|
*
|
|
* This software is distributed under multiple licenses;
|
|
* see the COPYING file in the main directory for licensing
|
|
* information for this specific distribution.
|
|
*
|
|
* This use of this software may be subject to additional restrictions.
|
|
* See the LEGAL file in the main directory for details.
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
#define LOG_GROUP LogGroup::Control
|
|
#include <Logger.h>
|
|
#include <Globals.h>
|
|
#include <sqlite3.h>
|
|
#include <sqlite3util.h>
|
|
|
|
#include <GSML3MMMessages.h>
|
|
#include <Reporting.h>
|
|
#include <Globals.h>
|
|
#include <GSML3CommonElements.h>
|
|
|
|
#include <string>
|
|
#include <iostream>
|
|
#include <iomanip>
|
|
|
|
#include <sys/stat.h>
|
|
#include "TMSITable.h"
|
|
#include "ControlTransfer.h"
|
|
#include "L3MobilityManagement.h"
|
|
|
|
using namespace std;
|
|
|
|
// (pat) For l3rewrite the TMSI will be assigned by the VLR, which will be either a new SubscriberRegistry program or Yate.
|
|
// As a result, everything here has to change.
|
|
// (pat) The access to any database needs to be message oriented if there is any chance of stalling, which could goof up the invoking state machine.
|
|
|
|
Control::TMSITable gTMSITable;
|
|
|
|
|
|
namespace Control {
|
|
static Mutex sTmsiMutex; // We have to serialize tmsi creation.
|
|
|
|
static unsigned sHighestFakeTmsi; // The highest fake tmsi value in the table to create an entry with no tmsi.
|
|
#define MAX_VALID_TMSI 0x3fffffff // 30 bits. We dont make TMSIs this high, but the invalid TMSIs start one higher than this.
|
|
|
|
// Note: we are using the TMSI as the primary key for efficiency, which means we must have a unique tmsi value in the
|
|
// TMSITable for every entry whether it is assigned to the MS or not.
|
|
// We keep valid TMSIs that we are not sure the MS received, and also TMSIs that may have authorized in the past and subsequently
|
|
// become unauthorized.
|
|
// For unauthorized phones, we used to go ahead and create valid TMSIs anyway, but I dont want to do that now that TMSIs are
|
|
// genuine random numbers and somewhat expensive to allocate, plus there could be many of them and I dont want them
|
|
// using up TMSI address space, because eventually we may receive TMSIs from neighbors as well.
|
|
// Another alternative was to have a separate TMSI->IMSI mapping table, but I dont think that is any better.
|
|
// If you do not specify a primary key, sqlite assigns a new key one higher than the highest existing in the table.
|
|
// So I will do is let it go ahead and assign TMSIs 1,2,3,4 for the case where the TMSI is not used,
|
|
// Fine, so we will use values up to 64K as valid TMSIs, and values above that for invalid TMSIs.
|
|
// This seems to do a good job and sqlite does not seem to increase the database size much when you
|
|
// assign primary keys that are not tightly clustered.
|
|
// The TMSI_ASSIGNED field indicates if the MS has confirmed receipt of the TMSI.
|
|
// Eventually we may receive TMSIs from neighbors as well.
|
|
namespace TmsiTableDefinition {
|
|
|
|
static const char *tmsiTableVersion = "7"; // Change this version whenever you change anything in the TMSI table.
|
|
// The fields from the tmsi table that are printed by the CLI 'tmsis' command.
|
|
// TMSITabDump assumes the names of some of these header fields to decide the appropriate formatting.
|
|
static string shortHeader =
|
|
"IMSI TMSI IMEI AUTH CREATED ACCESSED TMSI_ASSIGNED";
|
|
static string longHeader = shortHeader + " PTMSI_ASSIGNED AUTH_EXPIRY REJECT_CODE ASSOCIATED_URI ASSERTED_IDENTITY WELCOME_SENT";
|
|
//"IMSI TMSI IMEI AUTH CREATED ACCESSED TMSI_ASSIGNED PTMSI_ASSIGNED AUTH_EXPIRY REJECT_CODE ASSOCIATED_URI ASSERTED_IDENTITY WELCOME_SENT";
|
|
static string longerHeader = longHeader + " A5_SUPPORT POWER_CLASS RRLP_STATUS OLD_TMSI OLD_MCC OLD_MNC OLD_LAC";
|
|
//"IMSI TMSI IMEI AUTH CREATED ACCESSED TMSI_ASSIGNED PTMSI_ASSIGNED AUTH_EXPIRY REJECT_CODE ASSOCIATED_URI ASSERTED_IDENTITY WELCOME_SENT A5_SUPPORT POWER_CLASS RRLP_STATUS OLD_TMSI OLD_MCC OLD_MNC OLD_LAC";
|
|
static const char* create =
|
|
"CREATE TABLE IF NOT EXISTS TMSI_TABLE ("
|
|
// Valid TMSIs use negative values. Small positive auto-incremented numbers are dummy values
|
|
// automatically assigned by sqlite used for entries where the TMSI is unspecified.
|
|
"TMSI INTEGER PRIMARY KEY, " // AUTOINCREMENT This will also be used as the P-TMSI
|
|
// IMSI is just the digits without "IMSI" in front.
|
|
// (pat) the UNIQUE tells sqlite to create an index on this field to optimize queries.
|
|
"IMSI TEXT UNIQUE NOT NULL, " // IMSI
|
|
"IMEI TEXT, " // IMEI
|
|
"CREATED INTEGER NOT NULL, " // Unix time (seconds) of record creation
|
|
"ACCESSED INTEGER NOT NULL, " // Unix time (seconds) of last encounter
|
|
// "APP_FLAGS INTEGER DEFAULT 0, " // Application-specific flags (pat) No one is using this so I took it out.
|
|
"A5_SUPPORT INTEGER, " // encryption support
|
|
"POWER_CLASS INTEGER, " // power class
|
|
"OLD_TMSI INTEGER, " // previous TMSI in old network
|
|
"OLD_MCC INTEGER, " // previous network MCC
|
|
"OLD_MNC INTEGER, " // previous network MNC
|
|
"OLD_LAC INTEGER, " // previous network LAC
|
|
#if CACHE_AUTH // This code is eroded and no longer functional.
|
|
// (pat 9-2013): We are not storing the authentication info in the TMSI table.
|
|
// We need cached authorization for the case where we are using open registration and TMSIs are being sent,
|
|
// to verify the TMSI to avoid TMSI collisions, however, this should never have been the in the BTS -
|
|
// needs to be in sipauthserve if anywhere, so we always call the Registrar to get fresh challenges
|
|
// For UMTS this is a requirement, so there is no point putting in a special case for 2G GSM to cache a single RAND here.
|
|
// Cached authentication could also be used in the case where the Registrar is unreachable; but in that case we
|
|
// should fall back to using IMSIs and fail open. We dont handle that well at the moment.
|
|
//"RANDUPPER INTEGER, " // authentication token
|
|
//"RANDLOWER INTEGER, " // authentication token
|
|
//"SRES INTEGER, " // authentication token
|
|
#endif
|
|
"kc varchar(33) default '', " // returned by the Registrar, needed for ciphering.
|
|
"RRLP_STATUS INTEGER DEFAULT 0, " // Does MS support RRLP? If so, where are we in the RRLP state machine?
|
|
"DEG_LAT FLOAT, " // RRLP result
|
|
"DEG_LONG FLOAT, " // RRLP result
|
|
"ASSOCIATED_URI text default '', " // Saved from the SIP REGISTER message and inserted into MOC SIP INVITE.
|
|
"ASSERTED_IDENTITY text default '', " // Saved from the SIP REGISTER message and inserted into MOC SIP INVITE.
|
|
"WELCOME_SENT INTEGER DEFAULT 0," // 0 == welcome message not sent yet; 1 == sent by us; 2 == sent by someone else.
|
|
// (pat) We keep the unauthorized entries for several reasons:
|
|
// 1. so we can cache the unauthorized status to reduce loading on the Registrar by using cached unauthorization
|
|
// 2. to prevent sending the (un)welcome message multiple times.
|
|
// 3. if a previously authorized MS becomes unauthorized we want to keep the entry here to reserve the TMSI permanently.
|
|
"AUTH INTEGER DEFAULT 0," // Authorization result, 0 == unauthorized. See enum Authorization for other values.
|
|
"AUTH_EXPIRY INTEGER DEFAULT 0," // Absolute time in seconds when authorization expires or 0 for single-use.
|
|
"REJECT_CODE INTEGER DEFAULT 0," // Reject code, or 0 if authorized.
|
|
"TMSI_ASSIGNED INTEGER DEFAULT 0," // Set when the TMSI has been successfully assigned to the MS, ie, the MS knows it.
|
|
"PTMSI_ASSIGNED INTEGER DEFAULT 0" // Set when the P-TMSI has been successfully assigned to the MS by the SGSN, ie, the MS knows it.
|
|
|
|
// TODO: A bunch of stuff for GPRS. GPRS could use the TMSI as the P-TMSI if it is limited to 30 bits.
|
|
")";
|
|
};
|
|
|
|
// These are the mappings of allocated tmsis to/from the TMSI_TABLE.
|
|
static int tmsi2table(uint32_t tmsi)
|
|
{
|
|
return (int) tmsi;
|
|
}
|
|
static uint32_t table2tmsi(int storedValue)
|
|
{
|
|
return storedValue;
|
|
}
|
|
static bool tmsiIsValid(int rawTmsi)
|
|
{
|
|
return rawTmsi != 0 && rawTmsi <= MAX_VALID_TMSI;
|
|
}
|
|
|
|
|
|
bool configTmsiTestMode()
|
|
{
|
|
return gConfig.getNum("Control.LUR.TestMode");
|
|
}
|
|
|
|
bool configSendTmsis()
|
|
{
|
|
return gConfig.getBool("Control.LUR.SendTMSIs");
|
|
}
|
|
|
|
|
|
// return true on success
|
|
bool TMSITable::runQuery(const char *query, int checkChanges) const
|
|
{
|
|
int resultCode, changes=0;
|
|
LOG(DEBUG)<<LOGVAR(query)<<LOGVAR(checkChanges);
|
|
// (pat) It appears that sqlite3_changes returns 0 if the value being replaced matched what was already there.
|
|
if (!sqlite_command(mTmsiDB,query,&resultCode) || (checkChanges && 0 == (changes = sqlite3_changes(mTmsiDB)))) {
|
|
// The changes is only useful if checkChanges is set.
|
|
LOG(ERR) << "TMSI table query failed:" <<LOGVAR(query) <<LOGVAR(resultCode) <<LOGVAR(changes) <<" error:"<<sqlite3_errmsg(mTmsiDB);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// pat 9-2013: I am adding an extra table to hold attributes including a version number of the TMSI table file.
|
|
// (pat) If the TMSI_TABLE version does not match expected, drop the TMSI_TABLE before returning, and the caller will recreate it.
|
|
bool TMSITable::tmsiTabCheckVersion()
|
|
{
|
|
string version = sqlite_get_attr(mTmsiDB,"VERSION");
|
|
if (version == TmsiTableDefinition::tmsiTableVersion) {
|
|
// Success! The VERSION property matches the expected value.
|
|
return true;
|
|
}
|
|
|
|
// Delete the existing tmsi table from the database, caller will recreate it.
|
|
runQuery("DROP TABLE IF EXISTS TMSI_TABLE");
|
|
|
|
// Set the version attribute.
|
|
sqlite_set_attr(mTmsiDB,"VERSION",TmsiTableDefinition::tmsiTableVersion);
|
|
return false;
|
|
}
|
|
|
|
// Delete expired TMSITable entries.
|
|
void TMSITable::tmsiTabCleanup()
|
|
{
|
|
// Delete old TMSIs.
|
|
unsigned maxage = gConfig.getNum("Control.TMSITable.MaxAge"); // In hours.
|
|
if (maxage == 0 || maxage >= 99999) {return;} // punt if disabled or ridiculous.
|
|
unsigned oldest_allowed = time(NULL) - (maxage * 60*60);
|
|
char query[102];
|
|
snprintf(query,100,"DELETE FROM TMSI_TABLE WHERE ACCESSED <= %u",oldest_allowed);
|
|
runQuery(query,false);
|
|
int changes = sqlite3_changes(mTmsiDB);
|
|
if (changes) {
|
|
LOG(INFO) << "Deleted "<<changes<<" expired entries from TMSITable with age < Control.TMSITable.Maxage="<<maxage<<" hours";
|
|
}
|
|
}
|
|
|
|
void TMSITable::tmsiTabClearAuthCache()
|
|
{
|
|
runQuery("UPDATE TMSI_TABLE SET AUTH_EXPIRY=0");
|
|
}
|
|
|
|
|
|
void TMSITable::tmsiTabClear()
|
|
{
|
|
runQuery("DELETE FROM TMSI_TABLE WHERE 1");
|
|
//clearAuthFailures();
|
|
//authFailures.clear();
|
|
}
|
|
|
|
void TMSITable::tmsiTabInit()
|
|
{
|
|
tmsiTabCleanup();
|
|
|
|
// Run through the tmsi table to find the highest number tmsi.
|
|
// We could let sqlite just automatically create them except for the initial case
|
|
// where we need to create the first fake tmsi.
|
|
sHighestFakeTmsi = MAX_VALID_TMSI;
|
|
if (1) {
|
|
// Not that it matters, since this is done only once, but because TMSI is a primary key this query is probably optimized.
|
|
sqlQuery qHighestTmsi(mTmsiDB,"TMSI_TABLE","max(TMSI)","");
|
|
if (qHighestTmsi.sqlSuccess()) {
|
|
unsigned highest_tmsi = qHighestTmsi.getResultInt(0);
|
|
LOG(DEBUG) << "Highest TMSI="<<highest_tmsi;
|
|
if (highest_tmsi > sHighestFakeTmsi) { sHighestFakeTmsi = highest_tmsi; }
|
|
} else {
|
|
// Possibly empty table.
|
|
}
|
|
} else {
|
|
// old way, the brute-force approach.
|
|
sqlQuery qinit(mTmsiDB,"TMSI_TABLE","TMSI","");
|
|
while (qinit.sqlResultSize()) {
|
|
int rawtmsi = qinit.getResultInt(0);
|
|
if (rawtmsi > (int)sHighestFakeTmsi) { sHighestFakeTmsi = rawtmsi; }
|
|
if (! qinit.sqlStep()) break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int TMSITable::tmsiTabOpen(const char* wPath)
|
|
{
|
|
// FIXME -- We can't call the logger here because it has not been initialized yet.
|
|
// (pat) I think this has been fixed - the TMSITable initialization is now in main().
|
|
//printf("TMSITable::open(%s)\n",wPath); fflush(stdout);
|
|
LOG(INFO) << "TMSITable::open "<<wPath;
|
|
mTablePath = string(wPath);
|
|
|
|
int rc = sqlite3_open(wPath,&mTmsiDB);
|
|
if (rc) {
|
|
// (pat) Lets just create the directory if it does not exist.
|
|
char dirpath[strlen(wPath)+100];
|
|
strcpy(dirpath,wPath);
|
|
char *sp = strrchr(dirpath,'/');
|
|
if (sp) {
|
|
*sp = 0;
|
|
mkdir(dirpath,0777);
|
|
rc = sqlite3_open(wPath,&mTmsiDB); // try try again.
|
|
}
|
|
}
|
|
if (rc) {
|
|
LOG(EMERG) << "Cannot open TMSITable database at " << wPath << ": " << sqlite3_errmsg(mTmsiDB);
|
|
sqlite3_close(mTmsiDB);
|
|
mTmsiDB = NULL;
|
|
exit(1);
|
|
//return 1;
|
|
}
|
|
|
|
tmsiTabCheckVersion();
|
|
|
|
if (!sqlite_command(mTmsiDB,TmsiTableDefinition::create)) {
|
|
LOG(EMERG) << "Cannot create TMSI table in file:" <<mTablePath <<" using:" <<TmsiTableDefinition::create ;
|
|
exit(1);
|
|
//return 1;
|
|
}
|
|
// Set high-concurrency WAL mode.
|
|
if (!sqlite_command(mTmsiDB,enableWAL)) {
|
|
LOG(EMERG) << "Cannot enable WAL mode on database at " << wPath << ", error message: " << sqlite3_errmsg(mTmsiDB);
|
|
}
|
|
LOG(INFO) << "Opened TMSI table version "<<TmsiTableDefinition::tmsiTableVersion<< ":"<<wPath;
|
|
// (mike) 2014-06: to free ourselves of the in-tree sqlite3 code, the system library must be used.
|
|
// However, sqlite3_db_filename is not available in Ubuntu 12.04. Removing dependence for now.
|
|
LOG(DEBUG) << "TEST sqlite3_db_filename:"<< wPath;//sqlite3_db_filename(mTmsiDB,"main");
|
|
tmsiTabInit();
|
|
return 0;
|
|
}
|
|
|
|
TMSITable::~TMSITable()
|
|
{
|
|
if (mTmsiDB) sqlite3_close(mTmsiDB);
|
|
}
|
|
|
|
bool TMSITable::dropTmsi(uint32_t tmsi)
|
|
{
|
|
char query[100];
|
|
LOG(DEBUG) << "Removing TMSITable entry for"<<LOGVAR(tmsi);
|
|
snprintf(query,100,"DELETE FROM TMSI_TABLE WHERE TMSI == %d",tmsi2table(tmsi));
|
|
return runQuery(query,1);
|
|
}
|
|
|
|
bool TMSITable::dropImsi(const char *imsi)
|
|
{
|
|
char query[100];
|
|
LOG(DEBUG) << "Removing TMSITable entry for"<<LOGVAR(imsi);
|
|
snprintf(query,100,"DELETE FROM TMSI_TABLE WHERE IMSI == '%s'",imsi);
|
|
return runQuery(query,1);
|
|
}
|
|
|
|
|
|
#if UNUSED
|
|
void TMSITable::tmsiTabSetAuthAndAssign(string imsi,int auth, int assigned)
|
|
{
|
|
char query[100];
|
|
snprintf(query,100,"UPDATE TMSI_TABLE SET AUTH=%u, ASSIGNED=%u WHERE IMSI == '%s'",auth,assigned,imsi.c_str());
|
|
runQuery(query,1);
|
|
}
|
|
#endif
|
|
|
|
// This does nothing if the IMSI is not found in the table.
|
|
void TMSITable::tmsiTabSetRejected(string imsi,int rejectCode)
|
|
{
|
|
char query[100];
|
|
snprintf(query,100,"UPDATE TMSI_TABLE SET AUTH=0,REJECT_CODE=%d WHERE IMSI == '%s'",rejectCode,imsi.c_str());
|
|
runQuery(query,1);
|
|
}
|
|
|
|
struct TSqlString : public string {
|
|
char buf[100];
|
|
void appendf(const char *fmt,int val) { snprintf(buf,100,fmt,val); append(buf); }
|
|
void appendf(const char *fmt,const char *val) { snprintf(buf,100,fmt,val); append(buf); }
|
|
void appendf(const char *fmt,string val) { snprintf(buf,100,fmt,val.c_str()); append(buf); }
|
|
void appendf(const char *fmt,const char *a,string b) { snprintf(buf,100,fmt,a,b.c_str()); append(buf); }
|
|
void appendf(const char *fmt,const char *a,int b) { snprintf(buf,100,fmt,a,b); append(buf); }
|
|
//void addc(const char *name) { if (cnt++) append(","); append(name); }
|
|
//void addci(int ival) { if (cnt++) append(","); appendf("%d",ival); }
|
|
//void addcs(string sval) { if (cnt++) append(","); appendf("'%s'",sval.c_str()); }
|
|
TSqlString() { reserve(150); }
|
|
};
|
|
|
|
struct TSqlQuery : public TSqlString {
|
|
virtual void addc(const char *name,unsigned ival) = 0;
|
|
virtual void addc(const char *name,string sval) = 0;
|
|
virtual void finish() = 0;
|
|
void addStore(TmsiTableStore *store);
|
|
};
|
|
|
|
struct TSqlUpdate : public TSqlQuery {
|
|
Int_z cnt; // Count of commas.
|
|
void addc(const char *name,string sval) {
|
|
if (cnt++) this->append(",");
|
|
this->appendf("%s='%s'",name,sval);
|
|
}
|
|
void addc(const char *name,unsigned ival) {
|
|
if (cnt++) this->append(",");
|
|
this->appendf("%s=%u",name,ival);
|
|
}
|
|
void finish() {}
|
|
};
|
|
|
|
struct TSqlInsert : public TSqlQuery {
|
|
TSqlString values;
|
|
Int_z cnt; // Count of commas.
|
|
void addc(const char *name, string sval) {
|
|
if (cnt++) { this->append(","); values.append(","); }
|
|
this->append(name);
|
|
values.appendf("'%s'",sval);
|
|
}
|
|
void addc(const char *name, unsigned ival) {
|
|
if (cnt++) { this->append(","); values.append(","); }
|
|
this->append(name);
|
|
values.appendf("%u",ival);
|
|
}
|
|
void finish() {
|
|
append(") VALUES (");
|
|
append(this->values);
|
|
append(")");
|
|
}
|
|
};
|
|
|
|
// Flush anything that has changed out to the TMSI table in this SQL query.
|
|
void TSqlQuery::addStore(TmsiTableStore *store)
|
|
{
|
|
if (store->imei_changed) { addc("IMEI",store->imei); store->imei_changed = false; }
|
|
if (store->auth_changed) { addc("AUTH",store->auth); store->auth_changed = false; }
|
|
if (store->authExpiry_changed) { addc("AUTH_EXPIRY",store->authExpiry); store->authExpiry_changed = false; }
|
|
if (store->rejectCode_changed) { addc("REJECT_CODE",store->rejectCode); store->rejectCode_changed = false; }
|
|
if (store->assigned_changed) { addc("TMSI_ASSIGNED",store->assigned); store->assigned_changed = false; }
|
|
if (store->a5support_changed) { addc("A5_SUPPORT",store->a5support); store->a5support_changed = false; }
|
|
if (store->powerClass_changed) { addc("POWER_CLASS",store->powerClass); store->powerClass_changed = false; }
|
|
if (store->kc_changed) { addc("kc",store->kc); store->kc_changed = false; }
|
|
if (store->associatedUri_changed) { addc("ASSOCIATED_URI",store->associatedUri); store->associatedUri_changed = false; }
|
|
if (store->assertedIdentity_changed) { addc("ASSERTED_IDENTITY",store->assertedIdentity); store->assertedIdentity_changed = false; }
|
|
if (store->welcomeSent_changed) { addc("WELCOME_SENT",store->welcomeSent); store->welcomeSent_changed = false; }
|
|
}
|
|
|
|
|
|
|
|
unsigned TMSITable::allocateTmsi()
|
|
{
|
|
int tmsi;
|
|
|
|
// For testing, we will deliberately screw something up.
|
|
if (configTmsiTestMode()) {
|
|
// Deliberately over-write an existing entry to create a TMSI collision.
|
|
// Must use at least two phones - the second one will be assigned the same tmsi as the first.
|
|
sqlQuery q8(mTmsiDB,"TMSI_TABLE","TMSI",""); // Returns all the table rows one by one.
|
|
if (q8.sqlSuccess() == false) {
|
|
WATCH("TMSI table is empty");
|
|
} else {
|
|
int rawtmsi = q8.getResultInt();
|
|
if (tmsiIsValid(rawtmsi)) {
|
|
unsigned tmsi = table2tmsi(rawtmsi);
|
|
WATCH("TMSI table test mode: created deliberate tmsi collision for"<<LOGVAR(tmsi));
|
|
// We must delete the existing entry or we will not be able to reassign it.
|
|
dropTmsi(tmsi);
|
|
return tmsi;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Make a new random tmsi that is not in the TMSI table.
|
|
// Since the source code is public, there is no point using a random number generator unless we use a truly random seed.
|
|
// The result may be used as a P-TMSI so it must be less than 30 bits.
|
|
const unsigned mask = 0xfffff; // We will use a 20 bit tmsi. That's plenty.
|
|
struct timeval now;
|
|
gettimeofday(&now,NULL);
|
|
unsigned int seed = now.tv_sec + now.tv_usec;
|
|
while (1) {
|
|
tmsi = rand_r(&seed) & mask;
|
|
// (pat) Skip the first 1000 tmsis. Those low numbers were used by OpenBTS 3.x and we'll just be safe
|
|
// and skip them entirely.
|
|
if (tmsi < 1000) { tmsi += 1000; }
|
|
WATCH("testing"<<LOGVAR(tmsi)<<LOGVAR(seed));
|
|
if (tmsi == 0) continue;
|
|
sqlQuery q7(mTmsiDB,"TMSI_TABLE","TMSI","TMSI",tmsi2table(tmsi));
|
|
if (0 == q7.sqlSuccess()) { break; }
|
|
}
|
|
return tmsi;
|
|
}
|
|
|
|
void TMSITable::tmsiTabUpdate(string imsi, TmsiTableStore *store)
|
|
{
|
|
LOG(INFO) << "update entry for"<<LOGVAR(imsi) <<LOGVAR(store->auth_changed)<<LOGVAR(store->auth)<<LOGVAR(store->assigned_changed)<<LOGVAR(store->assigned);
|
|
|
|
ScopedLock lock(sTmsiMutex,__FILE__,__LINE__); // This lock should be redundant - sql serializes access, but it may prevent sql retry failures.
|
|
TSqlUpdate q1;
|
|
q1.append("UPDATE TMSI_TABLE SET ");
|
|
q1.addStore(store);
|
|
if (! q1.cnt) { return; } // Nothing changed.
|
|
q1.addc("ACCESSED",(unsigned)time(NULL));
|
|
q1.appendf(" WHERE IMSI='%s'",imsi); // You must include the quotes around IMSI
|
|
runQuery(q1.c_str(),true);
|
|
}
|
|
|
|
// Update or create a new entry in the TMSI table.
|
|
// If we are assigning TMSIs to the MS, return the new TMSI, else 0.
|
|
// This routine checks to see if the record for this IMSI already exists; the caller actually knows this and could inform us,
|
|
// however there is a great deal of logic with many options between the original TMSITable lookup and updating
|
|
// the TMSITable entry here, so I deemed it more robust and resistant to future changes if this routine ignores that
|
|
// and does another lookup of the IMSI to see if the record already exists.
|
|
uint32_t TMSITable::tmsiTabCreateOrUpdate(
|
|
const string imsi,
|
|
TmsiTableStore *store,
|
|
const GSM::L3LocationAreaIdentity * lai,
|
|
uint32_t oldTmsi)
|
|
{
|
|
// Create or find an entry based on IMSI.
|
|
// Return assigned TMSI.
|
|
assert(mTmsiDB);
|
|
bool sendTmsis = configSendTmsis();
|
|
unsigned now = (unsigned)time(NULL);
|
|
|
|
ScopedLock lock(sTmsiMutex,__FILE__,__LINE__); // This lock should be redundant - sql serializes access, but it may prevent sql retry failures.
|
|
|
|
unsigned oldRawTmsi = tmsiTabGetTMSI(imsi,false);
|
|
bool isNewRecord = (oldRawTmsi == 0);
|
|
|
|
TSqlQuery *queryp; // dufus language
|
|
TSqlInsert foo;
|
|
TSqlUpdate bar;
|
|
if (isNewRecord) {
|
|
// Create a new record.
|
|
queryp = &foo; queryp->reserve(200);
|
|
queryp->append("INSERT INTO TMSI_TABLE (");
|
|
queryp->addc("IMSI",imsi);
|
|
queryp->addc("CREATED",now);
|
|
queryp->addc("ACCESSED",now);
|
|
} else {
|
|
queryp = &bar; queryp->reserve(200);
|
|
// Update existing record.
|
|
queryp->append("UPDATE TMSI_TABLE SET ");
|
|
}
|
|
uint32_t tmsi = 0;
|
|
|
|
if (sendTmsis) {
|
|
// We are handling several cases here. If it is a new record we are allocating a new tmsi for the first time,
|
|
// or if it is an existing record that was created without an assigned tmsi, ie, it has a fake tmsi,
|
|
// then we are updating it to a real tmsi.
|
|
// We never go backwards, ie, once we allocate a real tmsi, we never change it back to a fake tmsi,
|
|
// because we want to reserve the tmsi for this handset effectively forever.
|
|
if (! tmsiIsValid(oldRawTmsi)) {
|
|
gReports.incr("OpenBTS.GSM.MM.TMSI.Assigned");
|
|
tmsi = allocateTmsi();
|
|
queryp->addc("TMSI",tmsi2table(tmsi));
|
|
} else {
|
|
tmsi = oldRawTmsi;
|
|
}
|
|
} else {
|
|
if (isNewRecord) {
|
|
// We are creating a new record.
|
|
// sqlite3 would auto-assign a unique TMSI higher than any other in
|
|
// the table, but we need to handle the initial case for the first fake tmsi.
|
|
tmsi = ++sHighestFakeTmsi;
|
|
queryp->addc("TMSI",tmsi2table(tmsi));
|
|
}
|
|
}
|
|
queryp->addStore(store);
|
|
|
|
if (isNewRecord && lai) {
|
|
queryp->addc("OLD_MCC",lai->MCC());
|
|
queryp->addc("OLD_MNC",lai->MNC());
|
|
queryp->addc("OLD_LAC",lai->LAC());
|
|
queryp->addc("OLD_TMSI",oldTmsi);
|
|
queryp->finish();
|
|
} else {
|
|
queryp->append(format(" WHERE IMSI=='%s'",imsi));
|
|
}
|
|
|
|
if (!runQuery(queryp->c_str(),1)) {
|
|
LOG(ALERT) << "TMSI creation failed for"<<LOGVAR(imsi)<<" query:"<<*queryp;
|
|
return 0;
|
|
}
|
|
LOG(INFO) << (isNewRecord ? "new" : "updated") <<" entry for"<<LOGVAR(imsi)<<LOGHEX(tmsi);
|
|
|
|
if (sendTmsis) { // double check to make sure the database entry made it.
|
|
unsigned tmsicheck;
|
|
if (!sqlite3_single_lookup(mTmsiDB,"TMSI_TABLE","IMSI",imsi.c_str(),"TMSI",tmsicheck) || table2tmsi(tmsicheck) != tmsi) {
|
|
LOG(ERR) << "TMSI database inconsistancy"<<LOGVAR(imsi)<<LOGVAR(tmsi)<<LOGVAR(tmsicheck);
|
|
return 0;
|
|
}
|
|
}
|
|
return tmsi;
|
|
}
|
|
|
|
#if 0 // not so old version
|
|
uint32_t TMSITable::tmsiTabAssign(const string imsi, const GSM::L3LocationAreaIdentity * lai, uint32_t oldTmsi,TmsiTableStore *store)
|
|
{
|
|
// Create or find an entry based on IMSI.
|
|
// Return assigned TMSI.
|
|
assert(mTmsiDB);
|
|
|
|
gReports.incr("OpenBTS.GSM.MM.TMSI.Assigned");
|
|
|
|
ScopedLock lock(sTmsiMutex,__FILE__,__LINE__); // This lock should be redundant - sql serializes access, but it may prevent sql retry failures.
|
|
|
|
uint32_t tmsi = 0;
|
|
|
|
// Create a new record.
|
|
LOG(INFO) << "new entry for"<<LOGVAR(imsi)<<LOGVAR(tmsi);
|
|
unsigned now = (unsigned)time(NULL);
|
|
TSqlInsert query; query.reserve(150);
|
|
bool sendTmsis = configSendTmsis();
|
|
query.append("INSERT INTO TMSI_TABLE (");
|
|
query.addc("IMSI",imsi);
|
|
query.addc("CREATED",now);
|
|
query.addc("ACCESSED",now);
|
|
if (sendTmsis) {
|
|
tmsi = allocateTmsi();
|
|
query.addc("TMSI",tmsi2table(tmsi));
|
|
} else {
|
|
// Nothing needed. sqlite3 woult auto-assign a unique TMSI higher than any other in
|
|
// the table, but we need to handle the initial case for the first fake tmsi.
|
|
tmsi = ++sHighestFakeTmsi;
|
|
query.addc("TMSI",tmsi2table(tmsi));
|
|
}
|
|
query.addStore(store);
|
|
|
|
if (lai) {
|
|
query.addc("OLD_MCC",lai->MCC());
|
|
query.addc("OLD_MNC",lai->MNC());
|
|
query.addc("OLD_LAC",lai->LAC());
|
|
query.addc("OLD_TMSI",oldTmsi);
|
|
}
|
|
query.finish();
|
|
if (!runQuery(query.c_str(),1)) {
|
|
LOG(ALERT) << "TMSI creation failed, query:"<<query;
|
|
return 0;
|
|
}
|
|
|
|
if (sendTmsis) { // double check to make sure the database entry made it.
|
|
unsigned tmsicheck;
|
|
if (!sqlite3_single_lookup(mTmsiDB,"TMSI_TABLE","IMSI",imsi.c_str(),"TMSI",tmsicheck) || table2tmsi(tmsicheck) != tmsi) {
|
|
LOG(ERR) << "TMSI database inconsistancy"<<LOGVAR(imsi)<<LOGVAR(tmsi)<<LOGVAR(tmsicheck);
|
|
return 0;
|
|
}
|
|
}
|
|
return tmsi;
|
|
}
|
|
#endif
|
|
|
|
#if old_version
|
|
// Create a new entry in the TMSI table.
|
|
// This also set the AUTH status to authorized; it is only called on registration success.
|
|
// If we are assigning TMSIs to the MS, return the new TMSI, else 0.
|
|
uint32_t TMSITable::assign(const string imsi, const GSM::L3LocationAreaIdentity * lai, uint32_t oldTmsi, const string imei)
|
|
{
|
|
// Create or find an entry based on IMSI.
|
|
// Return assigned TMSI.
|
|
assert(mTmsiDB);
|
|
|
|
gReports.incr("OpenBTS.GSM.MM.TMSI.Assigned");
|
|
|
|
ScopedLock lock(sTmsiMutex,__FILE__,__LINE__); // This lock should be redundant - sql serializes access, but it may prevent sql retry failures.
|
|
|
|
uint32_t tmsi = 0;
|
|
|
|
// Create a new record.
|
|
LOG(INFO) << "new entry for"<<LOGVAR(imsi)<<LOGVAR(tmsi);
|
|
string query;
|
|
query.append("INSERT INTO TMSI_TABLE (");
|
|
bool sendTmsis = configSendTmsis();
|
|
if (sendTmsis) {
|
|
tmsi = allocateTmsi();
|
|
query.add("TMSI",tmsi);
|
|
}
|
|
query.add("IMSI",imsi);
|
|
unsigned now = (unsigned)time(NULL);
|
|
query.add("CREATED",now);
|
|
query.add("ACCESSED",now);
|
|
if (lai) {
|
|
query.add("OLD_MCC",lai->MCC());
|
|
query.add("OLD_MNC",lai->MNC());
|
|
query.add("OLD_LAC",lai->LAC());
|
|
query.add("OLD_TMSI",oldTmsi);
|
|
}
|
|
query.append(") VALUES (");
|
|
query.append(values);
|
|
query.append(")");
|
|
if (!runQuery(query.c_str(),1)) {
|
|
LOG(ALERT) << "TMSI creation failed, query:"<<query;
|
|
return 0;
|
|
}
|
|
|
|
if (sendTmsis) {
|
|
unsigned tmsicheck;
|
|
if (!sqlite3_single_lookup(mTmsiDB,"TMSI_TABLE","IMSI",imsi.c_str(),"TMSI",tmsicheck) || tmsicheck != tmsi) {
|
|
LOG(ERR) << "TMSI database inconsistancy"<<LOGVAR(imsi)<<LOGVAR(tmsi)<<LOGVAR(tmsicheck);
|
|
return 0;
|
|
}
|
|
}
|
|
return tmsi;
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
// Update timestamp by TMSI.
|
|
void TMSITable::tmsiTabTouchTmsi(unsigned TMSI) const
|
|
{
|
|
char query[100];
|
|
snprintf(query,100,"UPDATE TMSI_TABLE SET ACCESSED = %u WHERE TMSI == %d", (unsigned)time(NULL),tmsi2table(TMSI));
|
|
runQuery(query);
|
|
}
|
|
|
|
// Update timestamp by IMSI.
|
|
void TMSITable::tmsiTabTouchImsi(string IMSI) const
|
|
{
|
|
char query[100];
|
|
snprintf(query,100,"UPDATE TMSI_TABLE SET ACCESSED = %u WHERE IMSI == '%s'", (unsigned)time(NULL),IMSI.c_str());
|
|
runQuery(query);
|
|
}
|
|
|
|
void TMSITable::tmsiTabReallocationComplete(unsigned TMSI) const
|
|
{
|
|
ScopedLock lock(sTmsiMutex,__FILE__,__LINE__); // This lock should be redundant - sql serializes access, but it may prevent sql retry failures.
|
|
char query[100];
|
|
snprintf(query,100,"UPDATE TMSI_TABLE SET TMSI_ASSIGNED = 1 WHERE TMSI == %d",tmsi2table(TMSI));
|
|
runQuery(query);
|
|
}
|
|
|
|
|
|
// Pop all the fields we use during mobility management authorization out of the TMSI table.
|
|
bool TMSITable::tmsiTabGetStore(string imsi, TmsiTableStore *store) const
|
|
{
|
|
store->store_valid = true; // We have either updated the store or confirmed the imsi does not exist in the TMSI_TABLE.
|
|
sqlQuery q11(mTmsiDB,"TMSI_TABLE","AUTH,AUTH_EXPIRY,TMSI_ASSIGNED,REJECT_CODE,WELCOME_SENT", "IMSI",imsi.c_str());
|
|
if (!q11.sqlSuccess()) {
|
|
LOG(INFO) << "No TMSI_TABLE table entry for"<<LOGVAR(imsi) <<LOGVAR2("query",q11.mQueryString);
|
|
return false;
|
|
}
|
|
store->auth = (Authorization) q11.getResultInt(0); //store->auth_valid = true;
|
|
store->authExpiry = q11.getResultInt(1); //store->authExpiry_valid = true;
|
|
store->assigned = q11.getResultInt(2); //store->assigned_valid = true;
|
|
store->rejectCode = q11.getResultInt(3); //store->rejectCode_valid = true;
|
|
store->welcomeSent = q11.getResultInt(4); //store->welcomeSent_valid = true;
|
|
LOG(DEBUG) <<LOGVAR2("auth",store->auth)<<LOGVAR2("authExpiry",store->authExpiry)
|
|
<<LOGVAR2("assigned",store->assigned)<<LOGVAR2("rejectCode",store->rejectCode)<<LOGVAR2("welcomSent",store->welcomeSent);
|
|
return true;
|
|
}
|
|
|
|
|
|
string TMSITable::tmsiTabGetIMSI(unsigned tmsi, unsigned *pAuthorizationResult) const
|
|
{
|
|
string imsi;
|
|
if (pAuthorizationResult) tmsiTabTouchTmsi(tmsi);
|
|
sqlQuery q4(mTmsiDB,"TMSI_TABLE","IMSI,AUTH", "TMSI",tmsi2table(tmsi));
|
|
if (!q4.sqlSuccess()) {
|
|
if (pAuthorizationResult) { *pAuthorizationResult = 0; }
|
|
} else {
|
|
imsi = q4.getResultText(0);
|
|
int auth = q4.getResultInt(1);
|
|
if (pAuthorizationResult) { *pAuthorizationResult = auth; }
|
|
LOG(DEBUG) <<LOGVAR(tmsi) <<LOGVAR(imsi) <<LOGVAR(auth);
|
|
}
|
|
return imsi;
|
|
}
|
|
|
|
unsigned TMSITable::tmsiTabCheckAuthorization(string imsi) const
|
|
{
|
|
tmsiTabTouchImsi(imsi);
|
|
unsigned auth = 0;
|
|
if (! sqlite3_single_lookup(mTmsiDB,"TMSI_TABLE","IMSI",imsi.c_str(),"AUTH",auth)) {
|
|
return 0; // If IMSI not found, unauthorized.
|
|
}
|
|
return auth;
|
|
}
|
|
|
|
// If onlyIfKnown only return the TMSI if the handset has received and acknowleged it.
|
|
// Note that if onlyIfKnown is false, this may return invalid TMSIs; the caller must check validity.
|
|
unsigned TMSITable::tmsiTabGetTMSI(const string imsi, bool onlyIfKnown) const
|
|
{
|
|
ScopedLock lock(sTmsiMutex,__FILE__,__LINE__); // This lock should be redundant - sql serializes access, but it may prevent sql retry failures.
|
|
sqlQuery q3(mTmsiDB,"TMSI_TABLE","TMSI,TMSI_ASSIGNED", "IMSI",imsi.c_str());
|
|
if (! q3.sqlSuccess()) {
|
|
LOG(DEBUG) << "not found"<<LOGVAR(imsi);
|
|
return 0;
|
|
} else {
|
|
LOG(DEBUG) << "found"<<LOGVAR(imsi)<<LOGVAR(onlyIfKnown)<<LOGVAR(q3.getResultInt(0))<<LOGVAR(q3.getResultInt(1));
|
|
int rawtmsi = q3.getResultInt(0); // the returned tmsi.
|
|
int tmsiAssigned = q3.getResultInt(1); // true if the tmsi has been sent to the handset in a tmsi assignment.
|
|
if (onlyIfKnown && tmsiAssigned == 0) { return 0; }
|
|
return table2tmsi(rawtmsi); // the returned tmsi.
|
|
}
|
|
}
|
|
|
|
|
|
void printAge(unsigned seconds, ostream& os)
|
|
{
|
|
static const unsigned k=5;
|
|
os << setw(4);
|
|
if (seconds<k*60) {
|
|
os << seconds << 's';
|
|
return;
|
|
}
|
|
unsigned minutes = (seconds+30) / 60;
|
|
if (minutes<k*60) {
|
|
os << minutes << 'm';
|
|
return;
|
|
}
|
|
unsigned hours = (minutes+30) / 60;
|
|
if (hours<k*24) {
|
|
os << hours << 'h';
|
|
return;
|
|
}
|
|
os << (hours+12)/24 << 'd';
|
|
}
|
|
|
|
// Print the seconds as a low-precision age in seconds, minutes, hours, or days.
|
|
string prettyAge(unsigned seconds)
|
|
{
|
|
static const unsigned k=5;
|
|
if (seconds<k*60) { return format("%ds",seconds); }
|
|
unsigned minutes = (seconds+30) / 60;
|
|
if (minutes<k*60) { return format("%dm",minutes); }
|
|
unsigned hours = (minutes+30) / 60;
|
|
if (hours<k*24) { return format("%dh",hours); }
|
|
return format("%dd", (hours+12)/24);
|
|
}
|
|
|
|
|
|
vector< vector<string> > TMSITable::tmsiTabView(int verbosity, bool rawFlag, unsigned maxrows) const
|
|
{
|
|
vector< vector<string> > view;
|
|
vector<string> vh1;
|
|
|
|
string header1 = verbosity >= 2 ? TmsiTableDefinition::longerHeader :
|
|
verbosity == 1 ? TmsiTableDefinition::longHeader : TmsiTableDefinition::shortHeader;
|
|
vector<string> headers = stringSplit(vh1,header1.c_str());
|
|
view.push_back(headers);
|
|
|
|
string columns = replaceAll(header1," ",",");
|
|
sqlQuery query(mTmsiDB,"TMSI_TABLE",columns.c_str(),"ORDER BY ACCESSED DESC"); // descending
|
|
unsigned nrows = 1;
|
|
time_t now = time(NULL);
|
|
// If the table is completely empty this returns a row of all empty values.
|
|
while (query.sqlResultSize() && (nrows++ < maxrows)) {
|
|
// Print out the columns.
|
|
vector<string> row; row.clear();
|
|
unsigned col = 0;
|
|
for (col = 0; col < vh1.size(); col++) {
|
|
string header = headers[col];
|
|
//if (header.find("TMSI") != string::npos) // Needed if we add PTMSI
|
|
if (header == "TMSI") {
|
|
// TMSI. Use hex and fix negative numbers.
|
|
int rawtmsi = query.getResultInt(col);
|
|
if (rawFlag || tmsiIsValid(rawtmsi)) {
|
|
unsigned tmsi = table2tmsi(rawtmsi);
|
|
row.push_back(format("0x%x",(unsigned) tmsi));
|
|
} else {
|
|
// This is a dummy tmsi created by sqlite.
|
|
row.push_back("-");
|
|
}
|
|
} else if (header == "CREATED" || header == "ACCESSED") {
|
|
// Print seconds as a time value.
|
|
row.push_back(prettyAge(now - query.getResultInt(col)));
|
|
} else if (header == "AUTH_EXPIRY") {
|
|
// The expiry is the absolute time when it expires, or 0 if unknown.
|
|
int expiry = query.getResultInt(col);
|
|
int remaining = expiry - now;
|
|
if (remaining < 0) remaining = 0;
|
|
row.push_back(expiry ? prettyAge(remaining) : "-");
|
|
} else {
|
|
// All other column types. If they are integer they are converted to "" if NULL else decimal.
|
|
row.push_back(query.getResultText(col));
|
|
}
|
|
}
|
|
view.push_back(row);
|
|
if (! query.sqlStep()) break;
|
|
}
|
|
return view;
|
|
}
|
|
|
|
|
|
void TMSITable::tmsiTabDump(int verbosity,bool rawFlag, ostream& os, bool showAll, bool taboption) const
|
|
{
|
|
// Dump the TMSI table.
|
|
unsigned maxrows = showAll ? 10000000 : 100;
|
|
vector< vector<string> > view = tmsiTabView(verbosity, rawFlag, maxrows);
|
|
|
|
#if unused
|
|
// Add the IMSI authorization failures. They dont have TMSIs, or any other information.
|
|
vector<string> failList;
|
|
getAuthFailures(failList);
|
|
for (vector<string>::iterator it = failList.begin(); it != failList.end(); it++) {
|
|
if (view.size() >= maxrows) break;
|
|
vector<string> failureRow;
|
|
failureRow.push_back(*it); // The IMSI;
|
|
failureRow.push_back(string("-")); // TMSI
|
|
failureRow.push_back(string("0")); // AUTH
|
|
view.push_back(failureRow);
|
|
}
|
|
#endif
|
|
|
|
if (view.size() >= maxrows) {
|
|
vector<string> tmp;
|
|
tmp.push_back(string("..."));
|
|
view.push_back(tmp);
|
|
}
|
|
|
|
printPrettyTable(view,os,taboption);
|
|
|
|
#if 0 // previous tmsi table dumping code.
|
|
sqlite3_stmt *stmt;
|
|
if (sqlite3_prepare_statement(mTmsiDB,&stmt,"SELECT TMSI,IMSI,CREATED,ACCESSED FROM TMSI_TABLE ORDER BY ACCESSED DESC")) {
|
|
LOG(ERR) << "sqlite3_prepare_statement failed";
|
|
return;
|
|
}
|
|
time_t now = time(NULL);
|
|
while (sqlite3_run_query(mTmsiDB,stmt)==SQLITE_ROW) {
|
|
os << hex << setw(8) << sqlite3_column_int64(stmt,0) << ' ' << dec;
|
|
os << sqlite3_column_text(stmt,1) << ' ';
|
|
printAge(now-sqlite3_column_int(stmt,2),os); os << ' ';
|
|
printAge(now-sqlite3_column_int(stmt,3),os); os << ' ';
|
|
os << endl;
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
#if UNUSED
|
|
void TMSITable::setIMEI(string IMSI, string IMEI)
|
|
{
|
|
// If the IMEI has changed, update it and also reset RRLP_STATUS.
|
|
// The A5_SUPPORT and POWER_CLASS have to change too, but that is done by classmark().
|
|
if (IMEI.size()) {
|
|
sqlQuery qu(mTmsiDB,"TMSI_TABLE","IMEI","IMSI",IMSI.c_str());
|
|
string oldIMEI = qu.getResultText(0);
|
|
if (oldIMEI != IMEI) {
|
|
LOG(INFO) << "Updating" <<LOGVAR(IMSI) << " from" <<LOGVAR(oldIMEI) <<" to" <<LOGVAR(IMEI);
|
|
char query[100];
|
|
snprintf(query,100,"UPDATE TMSI_TABLE SET IMEI='%s',RRLP_STATUS=0 WHERE IMSI == '%s'",IMEI.c_str(),IMSI.c_str());
|
|
runQuery(query,1);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
bool TMSITable::classmark(const char* IMSI, const GSM::L3MobileStationClassmark2& classmark)
|
|
{
|
|
int A5Bits = classmark.getA5Bits();
|
|
char query[100];
|
|
snprintf(query,100,
|
|
"UPDATE TMSI_TABLE SET A5_SUPPORT=%u,ACCESSED=%u,POWER_CLASS=%u "
|
|
" WHERE IMSI=\"%s\"",
|
|
A5Bits,(unsigned)time(NULL),classmark.powerClass(),IMSI);
|
|
runQuery(query,1);
|
|
return true;
|
|
}
|
|
|
|
|
|
// Return 0 for no encryption, or the algorithm number, ie, 1 means A5_1, 2 means A5_2, etc.
|
|
unsigned getPreferredA5Algorithm(unsigned A5Bits)
|
|
{
|
|
if (A5Bits & GSM::EncryptionAlgorithm::Bit5_3) return 3;
|
|
// if (A5Bits & GSM::EncryptionAlgorithm::Bit5_2) return 2; // not supported
|
|
if (A5Bits & GSM::EncryptionAlgorithm::Bit5_1) return 1;
|
|
return 0;
|
|
}
|
|
|
|
int TMSITable::tmsiTabGetPreferredA5Algorithm(const char* IMSI)
|
|
{
|
|
sqlQuery query(mTmsiDB,"TMSI_TABLE","A5_SUPPORT", "IMSI",IMSI);
|
|
if (!query.sqlSuccess()) return 0;
|
|
int cm = query.getResultInt(0);
|
|
#if 0
|
|
char query[200];
|
|
snprintf(query,200, "SELECT A5_SUPPORT from TMSI_TABLE WHERE IMSI=\"%s\"", IMSI);
|
|
sqlite3_stmt *stmt;
|
|
if (sqlite3_prepare_statement(mTmsiDB,&stmt,query)) {
|
|
LOG(ERR) << "sqlite3_prepare_statement failed for " << query;
|
|
return 0;
|
|
}
|
|
if (sqlite3_run_query(mTmsiDB,stmt)!=SQLITE_ROW) {
|
|
// Returning false here just means the IMSI is not there yet.
|
|
sqlite3_finalize(stmt);
|
|
return 0;
|
|
}
|
|
int cm = sqlite3_column_int(stmt,0);
|
|
sqlite3_finalize(stmt);
|
|
#endif
|
|
return getPreferredA5Algorithm(cm);
|
|
}
|
|
|
|
|
|
#if CACHE_AUTH
|
|
void TMSITable::putAuthTokens(const char* IMSI, uint64_t upperRAND, uint64_t lowerRAND, uint32_t SRES)
|
|
{
|
|
char query[300];
|
|
snprintf(query,300,"UPDATE TMSI_TABLE SET RANDUPPER=%llu,RANDLOWER=%llu,SRES=%u,ACCESSED=%u WHERE IMSI=\"%s\"",
|
|
upperRAND,lowerRAND,SRES,(unsigned)time(NULL),IMSI);
|
|
if (! (sqlite_command(mTmsiDB,query) && 1 == sqlite3_changes(mTmsiDB))) {
|
|
LOG(ALERT) << "cannot write to TMSI table";
|
|
}
|
|
}
|
|
|
|
bool TMSITable::getAuthTokens(const char* IMSI, uint64_t& upperRAND, uint64_t& lowerRAND, uint32_t& SRES) const
|
|
{
|
|
TSqlQuery query(mTmsiDB, "TMSI_TABLE", "RANDUPPER,RANDLOWER,SRES", "IMSI", IMSI);
|
|
if (query.sqlResultSize() == 3) {
|
|
upperRAND = query.getResultInt(0);
|
|
lowerRAND = query.getResultInt(1);
|
|
SRES = query.getResultInt(2);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
#if UNUSED
|
|
// Note that we overwrite the P-Associated-URI and P-Asserted-Identity, even if the incoming are empty. If we dont know them, we dont know them.
|
|
void TMSITable::putKc(const char* imsi, string Kc, string pAssociatedUri, string pAssertedIdentity)
|
|
{
|
|
char query[200];
|
|
snprintf(query,200,"UPDATE TMSI_TABLE SET kc='%s',ASSOCIATED_URI='%s',ASSERTED_IDENTITY='%s' WHERE IMSI='%s'", Kc.c_str(), pAssociatedUri.c_str(),pAssertedIdentity.c_str(),imsi);
|
|
// And I quote sqlite.org documentation for "UPDATE": "It is not an error if the WHERE clause does not evaluate true for any row in the table".
|
|
// So if if the IMSI is not found this does not return an error. We have to check sqlite3_changes() to see if a row changed.
|
|
if (! runQuery(query,1)) {
|
|
// We dont write the query because it has Kc in it.
|
|
LOG(ALERT) << "cannot write Kc or asserted identities to TMSI table for"<<LOGVAR(imsi);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
string TMSITable::getKc(const char* IMSI) const
|
|
{
|
|
string Kcs;
|
|
if (!sqlite_single_lookup( mTmsiDB, "TMSI_TABLE", "IMSI", IMSI, "kc", Kcs)) {
|
|
LOG(ERR) << "sqlite3_single_lookup failed to find kc for " << IMSI;
|
|
return "";
|
|
}
|
|
return Kcs;
|
|
}
|
|
|
|
void TMSITable::getSipIdentities(string imsi, string &pAssociatedUri,string &pAssertedIdentity) const
|
|
{
|
|
sqlQuery query(mTmsiDB, "TMSI_TABLE", "ASSOCIATED_URI,ASSERTED_IDENTITY", "IMSI", imsi.c_str());
|
|
if (query.sqlResultSize() == 2) {
|
|
pAssociatedUri = query.getResultText(0);
|
|
pAssertedIdentity = query.getResultText(1);
|
|
}
|
|
LOG(DEBUG) <<LOGVAR(imsi) <<LOGVAR(pAssociatedUri) <<LOGVAR(pAssertedIdentity);
|
|
}
|
|
|
|
}; // namespace
|
|
|
|
|
|
// vim: ts=4 sw=4
|