UltraScan III
us_db2.cpp
Go to the documentation of this file.
1 #include "us_db2.h"
3 #include "us_settings.h"
4 #include "us_crypto.h"
5 #include "us_gzip.h"
6 #include "us_util.h"
7 
9 {
10 #ifndef NO_DB
11  QString certPath = US_Settings::appBaseDir() + QString( "/etc/mysql/" );
12  keyFile = certPath + QString( "server-key.pem" );
13  certFile = certPath + QString( "server-cert.pem" );
14  caFile = certPath + QString( "ca-cert.pem" );
15 
16  connected = false;
17  result = NULL;
18  db = mysql_init( NULL );
19 #endif
20 }
21 
22 #ifdef NO_DB
23 US_DB2::US_DB2( const QString& ){}
24 #else
25 US_DB2::US_DB2( const QString& masterPW )
26 {
27  QString certPath = US_Settings::appBaseDir() + QString( "/etc/mysql/" );
28  keyFile = certPath + QString( "server-key.pem" );
29  certFile = certPath + QString( "server-cert.pem" );
30  caFile = certPath + QString( "ca-cert.pem" );
31 
32  connected = false;
33  result = NULL;
34  db = mysql_init( NULL );
35 
36  QString err;
37  if ( ! connect( masterPW, err ) )
38  {
40  error = "US_DB2 error: could not connect\n" + err;
41  return;
42  }
43 
44  db_errno = OK;
45  error = "";
46  connected = true;
47 }
48 #endif
49 
51 {
52 #ifndef NO_DB
53  mysql_free_result( result );
54  mysql_close(db);
55 
56  result = NULL;
57  connected = false;
58 #endif
59 }
60 
61 #ifdef NO_DB
62 bool US_DB2::test_db_connection( const QString&, const QString&,
63  const QString&, const QString&, QString& ) { return false; }
64 #else
66  const QString& host, const QString& dbname,
67  const QString& user, const QString& password,
68  QString& error )
69 {
70  error = "";
71 
72  MYSQL* conn = mysql_init( NULL );
73  if ( ! conn )
74  {
75  error = QString( "Test connection allocation error" );
76  return false;
77  }
78 
79  QString uhost = host.section( ":", 0, 0 ).simplified();
80  int uport = host.section( ":", 1, 1 ).simplified().toInt();
81 
82  bool status = mysql_real_connect(
83  conn,
84  uhost .toAscii(),
85  user .toAscii(),
86  password.toAscii(),
87  dbname .toAscii(),
88  uport, NULL, CLIENT_MULTI_STATEMENTS );
89 
90  if ( ! status )
91  error = QString( "Test connection open error\n" ) + mysql_error( conn );
92 
93  mysql_close( conn );
94 
95  return status;
96 }
97 #endif
98 
99 #ifdef NO_DB
101  const QString&, const QString&,
102  const QString&, const QString&,
103  const QString&, const QString&,
104  QString& ) { return false; }
105 #else
107  const QString& host, const QString& dbname,
108  const QString& user, const QString& password,
109  const QString& email, const QString& pw,
110  QString& err )
111 {
112  error = "";
113 
114  if ( ! db )
115  {
116  error = QString( "Test secure connection allocation error" );
117  err = error;
118  return false;
119  }
120 
121  // Set connection to use ssl encryption
122  mysql_ssl_set( db,
123  keyFile .toAscii(),
124  certFile.toAscii(),
125  caFile .toAscii(),
126  NULL,
127  "AES128-SHA" );
128 
129  QString uhost = host.section( ":", 0, 0 ).simplified();
130  int uport = host.section( ":", 1, 1 ).simplified().toInt();
131 
132  bool status = mysql_real_connect(
133  db,
134  uhost .toAscii(),
135  user .toAscii(),
136  password.toAscii(),
137  dbname .toAscii(),
138  uport, NULL, CLIENT_MULTI_STATEMENTS );
139 
140  if ( ! status )
141  {
142  error = QString( "Test secure connection open error\n" ) +
143  mysql_error( db );
144  err = mysql_error( db );
145  return false;
146  }
147 
148  // Let's see if the user can log in
149  status = false;
150  QString q = "CALL validate_user( '', '" + email + "', '" + pw + "' )";
151 
152  this->query( q );
153 
154  if ( db_errno != OK )
155  {
156  error = mysql_error( db );
157  err = error;
158  return false;
159  }
160 
161  next(); // Setup for calling routine to just use value()
162 
163  return true;
164 }
165 #endif
166 
167 #ifdef NO_DB
168 bool US_DB2::connect( const QString&, QString& ){ return false; }
169 #else
170 bool US_DB2::connect( const QString& masterPW, QString& err )
171 {
172  if ( connected ) return true;
173 
174  QStringList defaultDB = US_Settings::defaultDB();
175  if ( defaultDB.size() < 6 )
176  {
178  error = "US_DB2 error: DB not configured";
179  err = error;
180  return false;
181  }
182 
183  QString user = defaultDB.at( 1 );
184  QString dbname = defaultDB.at( 2 );
185  QString host = defaultDB.at( 3 );
186  QString cipher = defaultDB.at( 4 );
187  QString iv = defaultDB.at( 5 ); // Initialization vector
188 
189  QString password = US_Crypto::decrypt( cipher, masterPW, iv );
190  try
191  {
192  // Set connection to use ssl encryption
193  mysql_ssl_set( db,
194  keyFile .toAscii(),
195  certFile.toAscii(),
196  caFile .toAscii(),
197  NULL,
198  "AES128-SHA" );
199 
200  // The CLIENT_MULTI_STATEMENTS flag allows for multiple queries and
201  // multiple result sets from a single stored procedure. It is required
202  // for any stored procedure that returns result sets.
203  QString uhost = host.section( ":", 0, 0 ).simplified();
204  int uport = host.section( ":", 1, 1 ).simplified().toInt();
205 //qDebug() << "DB: connect uport" << uport << "uhost" << uhost;
206 
207  connected = mysql_real_connect(
208  db,
209  uhost .toAscii(),
210  user .toAscii(),
211  password.toAscii(),
212  dbname .toAscii(),
213  uport, NULL, CLIENT_MULTI_STATEMENTS );
214 
215  }
216 
217  catch ( std::exception &e )
218  {
220  error = e.what();
221  err = "US_DB2: uncaught exception " + error;
222  return false;
223  }
224 
225  db_errno = OK;
226  error = "";
227 
228  if ( ! connected )
229  {
231  error = QString( "Connect open error: " ) + mysql_error( db );
232  err = error;
233  return false;
234  }
235 
236  email = defaultDB.at( 6 ); // Save for later
237 
238  // DB Internal PW
239  cipher = defaultDB.at( 7 );
240  iv = defaultDB.at( 8 );
241  userPW = US_Crypto::decrypt( cipher, masterPW, iv );
242  guid = defaultDB.at( 9 );
243 
244  QString q = "CALL validate_user( '" + guid + "', '', '" + userPW + "' )";
245 
246  this->query( q );
247 
248  if ( db_errno != OK )
249  {
250  err = error;
251  return false;
252  }
253 
254  next(); // Setup for calling routine to just use value()
255 
256  // See if email was changed in the database
257  if ( email != this->value( 1 ).toString() )
258  {
259  QList< QStringList > dbinfo = US_Settings::databases();
260 
261  for ( int i = 0; i < dbinfo.size(); i++ )
262  {
263  QStringList info = dbinfo.at( i );
264 
265  if ( info.at( 9 ) == guid ) // Found it
266  {
267  email = this->value( 1 ).toString();
268  info.replace( 6, email );
269  US_Settings::set_defaultDB( info ); // Update the current DB
270  dbinfo.replace( i, info );
271  break;
272  }
273  }
274 
275  US_Settings::set_databases( dbinfo ); // Update the full DB list
276  }
277 
278  return connected;
279 }
280 #endif
281 
282 #ifdef NO_DB
283 bool US_DB2::connect( const QString&, const QString&, const QString&,
284  const QString&, QString& ){ return false; }
285 #else
287  const QString& host, const QString& dbname,
288  const QString& user, const QString& password,
289  QString& error )
290 {
291  if ( connected ) return true;
292 
293  try
294  {
295  // Set connection to use ssl encryption
296  mysql_ssl_set( db,
297  keyFile .toAscii(),
298  certFile.toAscii(),
299  caFile .toAscii(),
300  NULL,
301  "AES128-SHA" );
302 
303  // The CLIENT_MULTI_STATEMENTS flag allows for multiple queries and
304  // multiple result sets from a single stored procedure. It is required
305  // for any stored procedure that returns result sets.
306  QString uhost = host.section( ":", 0, 0 ).simplified();
307  int uport = host.section( ":", 1, 1 ).simplified().toInt();
308 
309  connected = mysql_real_connect(
310  db,
311  uhost .toAscii(),
312  user .toAscii(),
313  password.toAscii(),
314  dbname .toAscii(),
315  uport, NULL, CLIENT_MULTI_STATEMENTS );
316  }
317 
318  catch ( std::exception &e )
319  {
320  error = e.what();
321  }
322 
323  db_errno = OK;
324  error = "";
325  if ( ! connected )
326  {
328  error = QString( "Connect open error: " ) + mysql_error( db );
329  }
330 
331  return connected;
332 }
333 #endif
334 
335 #ifdef NO_DB
336 void US_DB2::rawQuery( const QString& ){}
337 #else
338 void US_DB2::rawQuery( const QString& sqlQuery )
339 {
340  // Make sure that we clear out any unused
341  // result sets
342  if ( result )
343  mysql_free_result( result );
344 
345  while ( mysql_next_result( db ) == 0 )
346  {
347  result = mysql_store_result( db );
348  mysql_free_result( result );
349  }
350  result = NULL;
351 
352  if ( mysql_query( db, sqlQuery.toAscii() ) != 0 )
353  error = QString( "MySQL error: " ) + mysql_error( db );
354 
355  else
356  result = mysql_store_result( db );
357 }
358 #endif
359 
360 #ifdef NO_DB
361 int US_DB2::statusQuery( const QString& ){ return 0; }
362 #else
363 int US_DB2::statusQuery( const QString& sqlQuery )
364 {
365  db_errno = ERROR;
366 
367  this->rawQuery( sqlQuery );
368  if ( result )
369  {
370  row = mysql_fetch_row( result );
371  db_errno = atoi( row[ 0 ] );
372  mysql_free_result( result );
373  result = NULL;
374  }
375 
376  if ( db_errno != 0 )
377  {
378  this->rawQuery( "SELECT last_error()" );
379  if ( result )
380  {
381  row = mysql_fetch_row( result );
382  error = row[ 0 ];
383  mysql_free_result( result );
384  result = NULL;
385  }
386  }
387 
388  return db_errno;
389 }
390 #endif
391 
392 int US_DB2::statusQuery( const QStringList& arguments )
393 {
394  return statusQuery( buildQuery( arguments ) );
395 }
396 
397 int US_DB2::functionQuery( const QStringList& arguments )
398 {
399  return statusQuery( buildQuerySelect( arguments ) );
400 }
401 
402 #ifdef NO_DB
403 void US_DB2::query( const QString& ) {}
404 #else
405 void US_DB2::query( const QString& sqlQuery )
406 {
407  this->rawQuery( sqlQuery );
408  if ( result )
409  {
410  // This is a 2-set result: status, then data
411  row = mysql_fetch_row( result );
412  db_errno = atoi( row[ 0 ] ); // status
413  mysql_free_result( result );
414  result = NULL;
415 
416  if ( mysql_next_result( db ) == 0 ) // get the result data
417  {
418  result = mysql_store_result( db );
419  if ( ! result )
420  {
421  if ( mysql_field_count( db ) == 0 )
422  {} // We are here as the result of an INSERT, UPDATE or DELETE
423 
424  else
425  {} // Error retrieving result set
426  }
427  }
428  }
429 
430  if ( db_errno != 0 )
431  {
432  this->rawQuery( "SELECT last_error()" );
433 
434  if ( result )
435  {
436  row = mysql_fetch_row( result );
437  error = row[ 0 ];
438  mysql_free_result( result );
439  result = NULL;
440  }
441  }
442 
443  else
444  error = "";
445 }
446 #endif
447 
448 void US_DB2::query( const QStringList& arguments )
449 {
450  query( buildQuery( arguments ) );
451 }
452 
453 QString US_DB2::buildQuery( const QStringList& arguments )
454 {
455  QString newquery = "CALL " + arguments[ 0 ]
456  + "('" + guid + "', '" + userPW + "'";
457 
458  for ( int i = 1; i < arguments.size(); i++ )
459  {
460  QString arg = arguments[ i ];
461  arg.replace( "'", "\\'" );
462 
463  newquery += ", '" + arg + "'";
464  }
465 
466  newquery += ")";
467 
468 //qDebug() << "NewQuery:" << newquery;
469  return newquery;
470 }
471 
472 QString US_DB2::buildQuerySelect( const QStringList& arguments )
473 {
474  QString newquery = "SELECT " + arguments[ 0 ]
475  + "('" + guid + "', '" + userPW + "'";
476 
477  for ( int i = 1; i < arguments.size(); i++ )
478  {
479  QString arg = arguments[ i ];
480  arg.replace( "'", "\\'" );
481 
482  newquery += ", '" + arg + "'";
483  }
484 
485  newquery += ")";
486 
487 //qDebug() << "NewQuerySelect:" << newquery;
488  return newquery;
489 }
490 
491 #ifdef NO_DB
492 bool US_DB2::next( void ){ return false; }
493 #else
494 bool US_DB2::next( void )
495 {
496  row = NULL;
497  if ( result )
498  {
499  if ( ( row = mysql_fetch_row( result ) ) != NULL )
500  return true;
501  }
502 
503  return false;
504 }
505 #endif
506 
507 #ifdef NO_DB
508 QVariant US_DB2::value( unsigned ){ return QVariant::Invalid; }
509 #else
510 QVariant US_DB2::value( unsigned index )
511 {
512  if ( row && ( index < mysql_field_count( db ) ) )
513  return row[ index ];
514 
515  return QVariant::Invalid;
516 }
517 #endif
518 
519 bool US_DB2::isConnected ( void )
520 {
521  return connected;
522 }
523 
524 #ifdef NO_DB
525 int US_DB2::numRows( void ){ return 0; }
526 #else
527 int US_DB2::numRows( void )
528 {
529  return ( result )? ( (int) mysql_num_rows( result ) ) : -1;
530 }
531 #endif
532 
533 #ifdef NO_DB
534 int US_DB2::writeBlobToDB( const QString& , const QString& , const int ) { return 0; }
535 #else
536 int US_DB2::writeBlobToDB( const QString& filename,
537  const QString& procedure, const int tableID )
538 {
539  // First let's read the file
540  QFile fin( filename );
541 
542  if ( ! fin.open( QIODevice::ReadOnly ) )
543  {
544  error = QString( "writeBlob: cannot open file " ) + filename;
545  db_errno = ERROR;
546  return ERROR;
547  }
548 
549  QByteArray blobData = fin.readAll();
550  fin.close();
551 
552  if ( blobData.size() < 1 )
553  {
554  error = QString( "writeBlob: no data in file " ) + filename;
555  db_errno = ERROR;
556  return ERROR;
557  }
558 
559  if ( tableID == 0 )
560  {
561  error = QString( "writeBlob: don't know which record data belongs to in " ) + filename;
562  db_errno = ERROR;
563  return ERROR;
564  }
565 
566  // Create an escaped version of the data
567  QByteArray blobData_escaped;
568  ulong escaped_length = mysqlEscapeString( blobData_escaped, blobData, blobData.size() );
569 
570  // Calculate a checksum
571  QByteArray checksum =
572  QCryptographicHash::hash( blobData, QCryptographicHash::Md5 ).toHex();
573 
574  // Now let's start building the query
575  QString queryPart1 = "CALL " + procedure +
576  "('" + guid +
577  "', '" + userPW +
578  "', " + QString::number( tableID ) +
579  ", '" ;
580  QByteArray sqlQuery( escaped_length
581  + queryPart1.size()
582  + checksum.size() + 7, '\0' );
583  strcpy( sqlQuery.data(), queryPart1.toAscii() );
584  char* queryPtr = sqlQuery.data() + queryPart1.size();
585  memcpy( queryPtr, blobData_escaped.data(), escaped_length );
586  queryPtr += escaped_length;
587  strcpy( queryPtr, "', '" );
588  queryPtr += 4;
589  memcpy( queryPtr, checksum.data(), checksum.size() );
590  queryPtr += checksum.size();
591  strcpy( queryPtr, "')\0" );
592 
593 /*
594  QByteArray sqlQuery2( escaped_length
595  + queryPart1.size()
596  + checksum.size() + 7, '\0' );
597  sqlQuery2 = queryPart1.toAscii()
598  + blobData_escaped + "', '"
599  + checksum + "')\0";
600 
601  ulong checkSize = escaped_length + queryPart1.size() + checksum.size() + 7;
602  qDebug() << ( ( sqlQuery == sqlQuery2 ) ? "check equal" : "check not equal" );
603  qDebug() << "check2 " << memcmp( sqlQuery.data(), sqlQuery2.data(), checkSize );
604 */
605 
606  // We can't use standard methods since they use QStrings
607  // Clear out any unused result sets
608  if ( result )
609  mysql_free_result( result );
610 
611  while ( mysql_next_result( db ) == 0 )
612  {
613  result = mysql_store_result( db );
614  mysql_free_result( result );
615  }
616  result = NULL;
617 
618  if ( mysql_query( db, sqlQuery.data() ) != 0 )
619  {
620  error = QString( "MySQL error: " ) + mysql_error( db );
621 
622  db_errno = ERROR;
623  return ERROR;
624  }
625 
626  result = mysql_store_result( db );
627  row = mysql_fetch_row( result );
628  db_errno = atoi( row[ 0 ] );
629 
630 //qDebug() << "Debug " << lastDebug();
631  mysql_free_result( result );
632  result = NULL;
633 
634  if ( db_errno == BAD_CHECKSUM )
635  {
636  error = QString( "writeBlob: data transmission error (MD5 checksum)" ) ;
637 
638  return BAD_CHECKSUM;
639  }
640 
641  else if ( db_errno != OK )
642  {
643  error = QString( "MySQL error: " ) + lastError();
644 
645  return db_errno;
646  }
647 
648  return db_errno;
649 }
650 #endif
651 
652 #ifdef NO_DB
653 int US_DB2::readBlobFromDB( const QString& , const QString& , const int ) { return 0; }
654 #else
655 int US_DB2::readBlobFromDB( const QString& filename,
656  const QString& procedure, const int tableID )
657 {
658  // First let's build the query
659  QString sqlQuery = "CALL " + procedure +
660  "('" + guid +
661  "', '" + userPW +
662  "', " + QString::number( tableID ) +
663  ")" ;
664 
665  // We can't use standard methods because the
666  // binary data doesn't all transfer
667 
668  // Make sure that we clear out any unused
669  // result sets
670  if ( result )
671  mysql_free_result( result );
672 
673  while ( mysql_next_result( db ) == 0 )
674  {
675  result = mysql_store_result( db );
676  mysql_free_result( result );
677  }
678  result = NULL;
679 
680  if ( mysql_query( db, sqlQuery.toAscii() ) != OK )
681  {
682  error = QString( "MySQL error: " ) + mysql_error( db );
683  db_errno = ERROR;
684  return ERROR;
685  }
686 
687  // First result set is status
688  result = mysql_store_result( db );
689  row = mysql_fetch_row( result );
690  db_errno = atoi( row[ 0 ] );
691 
692  mysql_free_result( result );
693  result = NULL;
694 
695  // Now get the result data
696  if ( mysql_next_result( db ) == 0 )
697  {
698  result = mysql_store_result( db );
699 
700  // Make sure we get the data and the right number of bytes
701  row = mysql_fetch_row( result );
702  ulong* lengths = mysql_fetch_lengths( result );
703 
704  // Now convert
705  QByteArray aucData( row[ 0 ], lengths[ 0 ] );
706  QByteArray checksum = row[ 1 ];
707  QByteArray calculated =
708  QCryptographicHash::hash( aucData, QCryptographicHash::Md5 ).toHex();
709 
710  mysql_free_result( result );
711  result = NULL;
712 
713  // Since we got data, let's write it out
714  QFile fout( filename );
715  if ( checksum != calculated )
716  {
717  error = QString( "readBlob: data transmission error (MD5 checksum)" ) ;
718 
720  }
721 
722  else if ( ! fout.open( QIODevice::WriteOnly ) )
723  {
724  error = QString( "readBlob: could not write file " ) + filename;
725 
726  db_errno = ERROR;
727  }
728 
729  else
730  {
731  fout.write( aucData );
732  fout.close();
733  }
734 if(!error.isEmpty()) qDebug() << error;
735  }
736 
737  return db_errno;
738 }
739 #endif
740 
741 #ifdef NO_DB
742 int US_DB2::lastInsertID( void ){ return 0; }
743 #else
745 {
746  int ID = -1;
747 
748  this->rawQuery( "SELECT last_insertID()" );
749 
750  if ( result )
751  {
752  row = mysql_fetch_row( result );
753  ID = atoi( row[ 0 ] );
754  mysql_free_result( result );
755  result = NULL;
756  }
757 
758  return ID;
759 }
760 #endif
761 
762 #ifdef NO_DB
763 QString US_DB2::lastDebug( void ){ return ""; }
764 #else
765 QString US_DB2::lastDebug( void )
766 {
767  QString debug = "";
768 
769  // Let's see if there is a debug message available
770  this->rawQuery( "SELECT last_debug()" );
771  if ( result )
772  {
773  row = mysql_fetch_row( result );
774  debug = row[ 0 ];
775  mysql_free_result( result );
776  result = NULL;
777  }
778 
779  return ( debug );
780 }
781 #endif
782 
783 #ifdef NO_DB
784 unsigned long US_DB2::mysqlEscapeString( QByteArray& , QByteArray& , unsigned long ) { return 0; }
785 #else
786 unsigned long US_DB2::mysqlEscapeString( QByteArray& to, QByteArray& from, unsigned long length )
787 {
788  to.resize( length * 2 + 1 ); // Make room in advance for escaped characters
789 
790  const char* fromPtr = from.data();
791  char* toPtr = to.data();
792 
793  ulong to_length = mysql_real_escape_string( db, toPtr, fromPtr, length );
794 
795  // Add null termination to the string
796  toPtr += to_length;
797  strcpy( toPtr, "\0" );
798 
799  // Size string appropriately and return new length
800  to.resize( to_length + 1 );
801  return to_length;
802 }
803 #endif
804 
805 #ifdef NO_DB
806 int US_DB2::writeAucToDB( const QString&, int ) { return 0; }
807 #else
808 int US_DB2::writeAucToDB( const QString& filename, int tableID )
809 {
810  // First compress th file
811  US_Gzip gz;
812  int retCode = gz.gzip( filename );
813 
814  QString fn = filename;
815 
816  if ( retCode == 0 )
817  {
818  fn += ".gz"; // gzip renames the file
819  retCode = writeBlobToDB( fn, "upload_aucData", tableID );
820 
821  // Now uncompress the file
822  gz.gzip( fn );
823  }
824 
825  return retCode;
826 }
827 #endif
828 
829 #ifdef NO_DB
830 int US_DB2::readAucFromDB( const QString&, int ) { return 0; }
831 #else
832 int US_DB2::readAucFromDB( const QString& filename, int tableID )
833 {
834  QString fn = filename + ".gz";
835 
836  int retCode = readBlobFromDB( fn, "download_aucData", tableID );
837 
838  if ( retCode == OK )
839  {
840  // Check to see if it is a gzipped file
841  char buf[ 2 ];
842  QFile t( fn );
843  t.open( QIODevice::ReadOnly );
844  t.peek( buf, 2 );
845  t.close();
846 
847  // Look for gzip magic number
848  if ( buf[ 0 ] == '\037' && buf[ 1 ] == '\213' )
849  {
850  US_Gzip gz;
851  retCode = gz.gunzip( fn );
852  }
853  else // not a gz file, jsut rename it
854  {
855  QFile::rename( fn, filename );
856  }
857  }
858 
859  return retCode;
860 }
861 #endif
862