2021-04-27 Fred Gleason <fredg@paravelsystems.com>

* Changed the 'RDAIRPLAY_EXIT_PASSWORD' field from 'varchar(41)'
	to 'varchar(48)'.
	* Incremented the database version to 349.
	* Renamed the 'RDSha1Hash()' function to 'RDSha1HashFile()'.
	* Added 'RDSha1HashPassword()' function in 'lib/rdhash.[cpp|h]'.
	* Added 'RDSha1HashCheckPassword()' function in 'lib/rdhash.[cpp|h]'.
	* Changed the hashing algorithm used for the Exit Password for
	rdairplay(1) to salted SHA1.

Signed-off-by: Fred Gleason <fredg@paravelsystems.com>
This commit is contained in:
Fred Gleason 2021-04-27 16:52:26 -04:00
parent 05c35a208c
commit 9a65658267
17 changed files with 164 additions and 46 deletions

View File

@ -21631,3 +21631,12 @@
* Fixed a regression in 'RDLog::create()' that threw SQL errors. * Fixed a regression in 'RDLog::create()' that threw SQL errors.
2021-04-27 Fred Gleason <fredg@paravelsystems.com> 2021-04-27 Fred Gleason <fredg@paravelsystems.com>
* Fixed a regression in 'RDLogModel' that threw SQL errors. * Fixed a regression in 'RDLogModel' that threw SQL errors.
2021-04-27 Fred Gleason <fredg@paravelsystems.com>
* Changed the 'RDAIRPLAY_EXIT_PASSWORD' field from 'varchar(41)'
to 'varchar(48)'.
* Incremented the database version to 349.
* Renamed the 'RDSha1Hash()' function to 'RDSha1HashFile()'.
* Added 'RDSha1HashPassword()' function in 'lib/rdhash.[cpp|h]'.
* Added 'RDSha1HashCheckPassword()' function in 'lib/rdhash.[cpp|h]'.
* Changed the hashing algorithm used for the Exit Password for
rdairplay(1) to salted SHA1.

View File

@ -26,7 +26,7 @@ DEFAULT_SERVICE varchar(10) From SERVICES.NAME
HOUR_SELECTOR_ENABLED enum('N','Y') HOUR_SELECTOR_ENABLED enum('N','Y')
EXIT_CODE int(11) 0=clean, 1=dirty EXIT_CODE int(11) 0=clean, 1=dirty
VIRTUAL_EXIT_CODE int(11) 0=clean, 1=dirty VIRTUAL_EXIT_CODE int(11) 0=clean, 1=dirty
EXIT_PASSWORD varchar(41) EXIT_PASSWORD varchar(48)
SKIN_PATH varchar(191) SKIN_PATH varchar(191)
SHOW_COUNTERS enum('N','Y') SHOW_COUNTERS enum('N','Y')
AUDITION_PREROLL int(11) AUDITION_PREROLL int(11)

View File

@ -24,7 +24,7 @@
/* /*
* Current Database Version * Current Database Version
*/ */
#define RD_VERSION_DATABASE 348 #define RD_VERSION_DATABASE 349
#endif // DBVERSION_H #endif // DBVERSION_H

View File

@ -20,10 +20,11 @@
#include <QObject> #include <QObject>
#include "rdairplay_conf.h"
#include "rddb.h" #include "rddb.h"
#include "rdconf.h" #include "rdconf.h"
#include "rdairplay_conf.h"
#include "rdescape_string.h" #include "rdescape_string.h"
#include "rdhash.h"
RDAirPlayConf::RDAirPlayConf(const QString &station,const QString &tablename) RDAirPlayConf::RDAirPlayConf(const QString &station,const QString &tablename)
{ {
@ -662,34 +663,40 @@ bool RDAirPlayConf::exitPasswordValid(const QString &passwd) const
{ {
QString sql; QString sql;
RDSqlQuery *q; RDSqlQuery *q;
bool ret=false;
sql=QString("select `EXIT_PASSWORD` from `")+air_tablename+"` where "+
"STATION='"+RDEscapeString(air_station)+"' && "+ sql=QString("select ")+
"((`EXIT_PASSWORD`=PASSWORD('"+RDEscapeString(passwd)+"'))"; "`EXIT_PASSWORD` "+ // 00
if(passwd.isEmpty()) { "from `"+air_tablename+"` where "+
sql+="||(`EXIT_PASSWORD` is null)"; "`STATION`='"+RDEscapeString(air_station)+"'";
}
sql+=")";
q=new RDSqlQuery(sql); q=new RDSqlQuery(sql);
if(q->size()>0) { if(q->first()) {
delete q; if(passwd.isEmpty()) {
return true; ret=q->value(0).isNull();
}
else {
ret=RDSha1HashCheckPassword(passwd,q->value(0).toString());
}
} }
delete q; return ret;
return false;
} }
void RDAirPlayConf::setExitPassword(const QString &passwd) const void RDAirPlayConf::setExitPassword(const QString &passwd) const
{ {
QString sql; QString sql;
RDSqlQuery *q;
sql=QString("update `")+air_tablename+"` set "+ if(passwd.isEmpty()) {
"`EXIT_PASSWORD`=PASSWORD('"+RDEscapeString(passwd)+"') where "+ sql=QString("update `")+air_tablename+"` set "+
"`STATION`='"+RDEscapeString(air_station)+"'"; "`EXIT_PASSWORD`=null where "+
q=new RDSqlQuery(sql); "`STATION`='"+RDEscapeString(air_station)+"'";
delete q; }
else {
sql=QString("update `")+air_tablename+"` set "+
"`EXIT_PASSWORD`='"+RDEscapeString(RDSha1HashPassword(passwd))+"' where "+
"`STATION`='"+RDEscapeString(air_station)+"'";
}
RDSqlQuery::apply(sql);
} }

View File

@ -27,9 +27,28 @@
#include <openssl/sha.h> #include <openssl/sha.h>
#include <QDateTime>
#include "rdhash.h" #include "rdhash.h"
QString RDSha1Hash(const QString &filename,bool throttle) QString __RDSha1Hash_MakePasswordHash(const QString &secret,const QString &salt)
{
SHA_CTX ctx;
unsigned char md[SHA_DIGEST_LENGTH];
SHA1_Init(&ctx);
SHA1_Update(&ctx,salt.toUtf8(),salt.toUtf8().length());
SHA1_Update(&ctx,secret.toUtf8(),secret.toUtf8().length());
SHA1_Final(md,&ctx);
QString ret=salt;
for(int i=0;i<SHA_DIGEST_LENGTH;i++) {
ret+=QString().sprintf("%02x",0xff&md[i]);
}
return ret;
}
QString RDSha1HashFile(const QString &filename,bool throttle)
{ {
QString ret; QString ret;
SHA_CTX ctx; SHA_CTX ctx;
@ -57,3 +76,27 @@ QString RDSha1Hash(const QString &filename,bool throttle)
return ret; return ret;
} }
QString RDSha1HashPassword(const QString &secret)
{
//
// Create a salt value
//
srand(QDateTime::currentDateTime().toMSecsSinceEpoch());
QString salt=QString().sprintf("%08x",rand());
//
// Generate the hash
//
return __RDSha1Hash_MakePasswordHash(secret,salt);
}
bool RDSha1HashCheckPassword(const QString &secret,const QString &hash)
{
QString salt=secret.left(8);
return __RDSha1Hash_MakePasswordHash(secret,hash.left(8))==hash;
}

View File

@ -23,7 +23,9 @@
#include <qstring.h> #include <qstring.h>
QString RDSha1Hash(const QString &filename,bool throttle=false); QString RDSha1HashFile(const QString &filename,bool throttle=false);
QString RDSha1HashPassword(const QString &secret);
bool RDSha1HashCheckPassword(const QString &secret,const QString &hash);
#endif // RD_H #endif // RDHASH_H

View File

@ -2334,7 +2334,7 @@ void MainObject::CheckInRecording(QString cutname,CatchEvent *evt,
s->setChannels(evt->channels()); s->setChannels(evt->channels());
cut->checkInRecording(rda->config()->stationName(),"", cut->checkInRecording(rda->config()->stationName(),"",
rda->config()->stationName(),s,msecs); rda->config()->stationName(),s,msecs);
cut->setSha1Hash(RDSha1Hash(RDCut::pathName(cut->cutName()))); cut->setSha1Hash(RDSha1HashFile(RDCut::pathName(cut->cutName())));
delete s; delete s;
cut->autoTrim(RDCut::AudioBoth,-threshold); cut->autoTrim(RDCut::AudioBoth,-threshold);
RDCart *cart=new RDCart(cut->cartNumber()); RDCart *cart=new RDCart(cut->cartNumber());

View File

@ -133,6 +133,7 @@ MainWidget::MainWidget(RDConfig *c,QWidget *parent)
login_button=new QPushButton(this); login_button=new QPushButton(this);
login_button->setFont(buttonFont()); login_button->setFont(buttonFont());
login_button->setText(tr("Set User")); login_button->setText(tr("Set User"));
login_button->setDefault(true);
connect(login_button,SIGNAL(clicked()),this,SLOT(loginData())); connect(login_button,SIGNAL(clicked()),this,SLOT(loginData()));
// //

View File

@ -32,7 +32,9 @@ MainObject::MainObject(QObject *parent)
:QObject(parent) :QObject(parent)
{ {
QString filename=""; QString filename="";
QString password="";
QString hash="";
// //
// Read Command Options // Read Command Options
// //
@ -42,27 +44,58 @@ MainObject::MainObject(QObject *parent)
filename=cmd->value(i); filename=cmd->value(i);
cmd->setProcessed(i,true); cmd->setProcessed(i,true);
} }
if(cmd->key(i)=="--hash") {
hash=cmd->value(i);
cmd->setProcessed(i,true);
}
if(cmd->key(i)=="--password") {
password=cmd->value(i);
cmd->setProcessed(i,true);
}
} }
if(filename.isEmpty()) { if(filename.isEmpty()&&password.isEmpty()) {
fprintf(stderr,"test_hash: missing --filename\n"); fprintf(stderr,"test_hash: missing --filename or --password\n");
exit(256); exit(256);
} }
if((!filename.isEmpty())&&(!password.isEmpty())) {
QString hash=RDSha1Hash(filename); fprintf(stderr,"test_hash: --filename and --password are mutually exclusive\n");
if(hash.isEmpty()) {
fprintf(stderr,"test_hash: unable to open \"%s\"\n",
filename.toUtf8().constData());
exit(256); exit(256);
} }
printf("%s\n",hash.toUtf8().constData());
exit(0); if(!filename.isEmpty()) { // Hash the specified file
hash=RDSha1HashFile(filename);
if(hash.isEmpty()) {
fprintf(stderr,"test_hash: unable to open \"%s\"\n",
filename.toUtf8().constData());
exit(256);
}
printf("%s\n",hash.toUtf8().constData());
exit(0);
}
if((!hash.isEmpty())&&(!password.isEmpty())) { // Check the specified hash
if( RDSha1HashCheckPassword(password,hash)) {
printf("Match!\n");
}
else {
printf("No Match!\n");
}
exit(0);
}
if(!password.isEmpty()) { // Generate password hash
printf("%s\n",RDSha1HashPassword(password).toUtf8().constData());
exit(0);
}
fprintf(stderr,"test_hash: inconsistent arguments given!\n");
exit(256);
} }
int main(int argc,char *argv[]) int main(int argc,char *argv[])
{ {
QApplication a(argc,argv,false); QCoreApplication a(argc,argv);
new MainObject(); new MainObject();
return a.exec(); return a.exec();
} }

View File

@ -24,7 +24,7 @@
#include <rdcmd_switch.cpp> #include <rdcmd_switch.cpp>
#include <rdhash.h> #include <rdhash.h>
#define TEST_HASH_USAGE "[options]\n\nTest SHA1 has generation\n\n--filename=<file-name>\n The name of the file for which to generate a hash.\n\n" #define TEST_HASH_USAGE "[options]\n\nTest SHA1 hash generation\n\n--filename=<file-name>\n The name of the file for which to generate a hash.\n\n--password=<secret>\n Generate a password hash from <secret>\n\n--hash=<hash>\n When given with --secret, verify <hash>\n\n"
class MainObject : public QObject class MainObject : public QObject
{ {

View File

@ -211,7 +211,7 @@ void MainObject::CheckTableAttributes()
printf(" Database uses default charset/collation %s/%s, should be utf8mb4/%s. Fix? (y/N) ", printf(" Database uses default charset/collation %s/%s, should be utf8mb4/%s. Fix? (y/N) ",
q->value(1).toString().toUtf8().constData(), q->value(1).toString().toUtf8().constData(),
q->value(2).toString().toUtf8().constData(), q->value(2).toString().toUtf8().constData(),
db_config->mysqlCollation()); db_config->mysqlCollation().toUtf8().constData());
fflush(NULL); fflush(NULL);
if(UserResponse()) { if(UserResponse()) {
sql=QString("alter database `")+db_mysql_database+"` "+ sql=QString("alter database `")+db_mysql_database+"` "+
@ -400,7 +400,7 @@ void MainObject::RelinkAudio(const QString &srcdir) const
QStringList files=dir.entryList(QDir::Files|QDir::Readable|QDir::Hidden); QStringList files=dir.entryList(QDir::Files|QDir::Readable|QDir::Hidden);
for(int i=0;i<files.size();i++) { for(int i=0;i<files.size();i++) {
QString filename=dir.path()+"/"+files[i]; QString filename=dir.path()+"/"+files[i];
QString hash=RDSha1Hash(filename); QString hash=RDSha1HashFile(filename);
QString firstdest; QString firstdest;
bool delete_source=true; bool delete_source=true;
@ -913,7 +913,7 @@ void MainObject::RehashCart(unsigned cartnum) const
void MainObject::RehashCut(const QString &cutnum) const void MainObject::RehashCut(const QString &cutnum) const
{ {
QString hash=RDSha1Hash(RDCut::pathName(cutnum),true); QString hash=RDSha1HashFile(RDCut::pathName(cutnum),true);
if(hash.isEmpty()) { if(hash.isEmpty()) {
printf(" Unable to generate hash for \"%s\"\n", printf(" Unable to generate hash for \"%s\"\n",
RDCut::pathName(cutnum).toUtf8().constData()); RDCut::pathName(cutnum).toUtf8().constData());

View File

@ -40,6 +40,19 @@ bool MainObject::RevertSchema(int cur_schema,int set_schema,QString *err_msg)
// NEW SCHEMA REVERSIONS GO HERE... // NEW SCHEMA REVERSIONS GO HERE...
//
// Revert 349
//
if((cur_schema==349)&&(set_schema<cur_schema)) {
sql=QString("alter table `RDAIRPLAY` ")+
"modify column EXIT_PASSWORD varchar(41)";
if(!RDSqlQuery::apply(sql,err_msg)) {
return false;
}
WriteSchemaVersion(--cur_schema);
}
// //
// Revert 348 // Revert 348
// //

View File

@ -159,7 +159,7 @@ void MainObject::InitializeSchemaMap() {
global_version_map["3.3"]=314; global_version_map["3.3"]=314;
global_version_map["3.4"]=317; global_version_map["3.4"]=317;
global_version_map["3.5"]=346; global_version_map["3.5"]=346;
global_version_map["4.0"]=348; global_version_map["4.0"]=349;
} }

View File

@ -10774,6 +10774,16 @@ bool MainObject::UpdateSchema(int cur_schema,int set_schema,QString *err_msg)
WriteSchemaVersion(++cur_schema); WriteSchemaVersion(++cur_schema);
} }
if((cur_schema<349)&&(set_schema>cur_schema)) {
sql=QString("alter table `RDAIRPLAY` ")+
"modify column EXIT_PASSWORD varchar(48)";
if(!RDSqlQuery::apply(sql,err_msg)) {
return false;
}
WriteSchemaVersion(++cur_schema);
}
// NEW SCHEMA UPDATES GO HERE... // NEW SCHEMA UPDATES GO HERE...

View File

@ -256,7 +256,7 @@ void Xport::Import()
break; break;
} }
if(resp_code==200) { if(resp_code==200) {
cut->setSha1Hash(RDSha1Hash(RDCut::pathName(cut->cutName()))); cut->setSha1Hash(RDSha1HashFile(RDCut::pathName(cut->cutName())));
if(!title.isEmpty()) { if(!title.isEmpty()) {
cart->setTitle(title); cart->setTitle(title);
} }

View File

@ -107,7 +107,7 @@ void Xport::SavePodcast()
delete cast; delete cast;
XmlExit(err_msg.toUtf8(),500,"podcasts.cpp",LINE_NUMBER); XmlExit(err_msg.toUtf8(),500,"podcasts.cpp",LINE_NUMBER);
} }
cast->setSha1Hash(RDSha1Hash(destpath)); cast->setSha1Hash(RDSha1HashFile(destpath));
printf("Content-type: text/html; charset: UTF-8\n"); printf("Content-type: text/html; charset: UTF-8\n");
printf("Status: 200\n\n"); printf("Status: 200\n\n");
@ -172,7 +172,7 @@ void Xport::GetPodcast()
data=new char[st.st_blksize]; data=new char[st.st_blksize];
n=read(fd,data,st.st_blksize); n=read(fd,data,st.st_blksize);
while(n>0) { while(n>0) {
write(1,data,n); n=write(1,data,n);
n=read(fd,data,st.st_blksize); n=read(fd,data,st.st_blksize);
} }
delete data; delete data;

View File

@ -59,7 +59,7 @@ void Xport::Rehash()
delete cut; delete cut;
XmlExit("No such cut",404,"rdhash.cpp",LINE_NUMBER); XmlExit("No such cut",404,"rdhash.cpp",LINE_NUMBER);
} }
cut->setSha1Hash(RDSha1Hash(RDCut::pathName(cart_number,cut_number))); cut->setSha1Hash(RDSha1HashFile(RDCut::pathName(cart_number,cut_number)));
delete cut; delete cut;
XmlExit("OK",200,"rdhash.cpp",LINE_NUMBER); XmlExit("OK",200,"rdhash.cpp",LINE_NUMBER);
} }