mirror of
				https://github.com/ElvishArtisan/rivendell.git
				synced 2025-11-03 23:53:59 +01:00 
			
		
		
		
	* Added an 'Engine=' directive to the [MySQL] section of rd.conf(5). * Refactored the schema update code to use standard form in rdadmin(1).
		
			
				
	
	
		
			436 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			436 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
// opendb.cpp
 | 
						|
//
 | 
						|
// Open a Rivendell Database
 | 
						|
//
 | 
						|
//   (C) Copyright 2002-2004,2016 Fred Gleason <fredg@paravelsystems.com>
 | 
						|
//
 | 
						|
//   This program is free software; you can redistribute it and/or modify
 | 
						|
//   it under the terms of the GNU General Public License version 2 as
 | 
						|
//   published by the Free Software Foundation.
 | 
						|
//
 | 
						|
//   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 General Public License for more details.
 | 
						|
//
 | 
						|
//   You should have received a copy of the GNU General Public
 | 
						|
//   License along with this program; if not, write to the Free Software
 | 
						|
//   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 | 
						|
//
 | 
						|
 | 
						|
#include <stdlib.h>
 | 
						|
#include <unistd.h>
 | 
						|
#include <qsqldriver.h>
 | 
						|
#include <qmessagebox.h>
 | 
						|
#include <opendb.h>
 | 
						|
#include <createdb.h>
 | 
						|
#include <rd.h>
 | 
						|
#include <rddb.h>
 | 
						|
#include <dbversion.h>
 | 
						|
#include <rdcheck_version.h>
 | 
						|
#include <rdcheck_daemons.h>
 | 
						|
#include <mysql_login.h>
 | 
						|
#include <globals.h>
 | 
						|
 | 
						|
// Includes used for netmask and remote server detection.
 | 
						|
#include <net/if.h>
 | 
						|
#include <sys/ioctl.h>
 | 
						|
#include <netdb.h>
 | 
						|
#include <arpa/inet.h>
 | 
						|
 | 
						|
#include <qobject.h>
 | 
						|
 | 
						|
/**
 | 
						|
 * Get the netmask of an interface and return it via an in_addr struct pointer.
 | 
						|
 *
 | 
						|
 * Note: uses linux IOCTL call SIOCGIFNETMASK to retrieve the netmask from a
 | 
						|
 * temporary socket.
 | 
						|
 *
 | 
						|
 * @param interface String with the interface to query.
 | 
						|
 * @param netmask Pointer to struct in_addr that will be populated with the netmask.
 | 
						|
 * @return true on success
 | 
						|
 **/
 | 
						|
bool get_netmask(const char * interface, struct in_addr * netmask)
 | 
						|
{
 | 
						|
  int fd;
 | 
						|
  struct ifreq ifr;
 | 
						|
  struct sockaddr_in *nmask;
 | 
						|
 | 
						|
  fd=socket(AF_INET, SOCK_DGRAM, 0);
 | 
						|
  ifr.ifr_addr.sa_family=AF_INET;
 | 
						|
  strncpy(ifr.ifr_name, interface, IFNAMSIZ-1);
 | 
						|
  ioctl(fd, SIOCGIFNETMASK, &ifr);
 | 
						|
  close(fd);
 | 
						|
  nmask=(struct sockaddr_in *)&ifr.ifr_netmask;
 | 
						|
  netmask->s_addr=nmask->sin_addr.s_addr;
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * Check if creating the database on the local machine or on a remote server in
 | 
						|
 * the same subnet.
 | 
						|
 *
 | 
						|
 * @param host QString with the hostname of machine to create the database.
 | 
						|
 * @return true if database is to be created on a remote server.
 | 
						|
 **/
 | 
						|
bool check_remote_server(QString host) {
 | 
						|
  char local_hostname[256];
 | 
						|
  int rc;
 | 
						|
  struct hostent *temp_hostent;
 | 
						|
  struct hostent local_hostent;
 | 
						|
  struct hostent host_hostent;
 | 
						|
  struct in_addr local_ip;
 | 
						|
  struct in_addr host_ip;
 | 
						|
  struct in_addr local_netmask;
 | 
						|
 | 
						|
  // check if host is 'localhost'
 | 
						|
  if (0==strncasecmp("localhost", (const char *)host, 255))
 | 
						|
    return false;
 | 
						|
 | 
						|
  rc=gethostname(local_hostname, 255);
 | 
						|
  // compare hostnames
 | 
						|
  if ((0==rc) && (0!=strncasecmp(local_hostname, (const char *)host, 255))) {
 | 
						|
    if ((temp_hostent=gethostbyname(local_hostname)))
 | 
						|
      local_hostent=*temp_hostent;
 | 
						|
    else
 | 
						|
      return false;
 | 
						|
    local_ip=*(struct in_addr *)temp_hostent->h_addr;
 | 
						|
 | 
						|
    if ((temp_hostent=gethostbyname((const char *)host)))
 | 
						|
      host_hostent=*temp_hostent;
 | 
						|
    else
 | 
						|
      return false;
 | 
						|
    host_ip=*(struct in_addr *)temp_hostent->h_addr;
 | 
						|
 | 
						|
    // compare IPs
 | 
						|
    if ((local_hostent.h_addrtype == AF_INET) &&
 | 
						|
        (host_hostent.h_addrtype == AF_INET) &&
 | 
						|
        (local_ip.s_addr != host_ip.s_addr)) {
 | 
						|
      // FIXME: ideally do something smarter than just testing eth0 (note use
 | 
						|
      // below in format_remote_host() also)
 | 
						|
      //   get list of interfaces   if_nameindex()
 | 
						|
      //   loop through list of interfaces, get IP, see if it matches local IP
 | 
						|
      //   once have good interface, get netmask
 | 
						|
      rc=get_netmask("eth0", &local_netmask);
 | 
						|
 | 
						|
      // compare if IPs are on same subnet.  note: use of bitwise sum
 | 
						|
      if ( (local_ip.s_addr & local_netmask.s_addr) == 
 | 
						|
                (host_ip.s_addr & local_netmask.s_addr) )
 | 
						|
        return true;
 | 
						|
    } // endif compare IPs
 | 
						|
  } // endif compare hostnames
 | 
						|
  return false;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * Format a QString with the MySQL host portion for a remote host.  The
 | 
						|
 * resulting string will use the local subnet and mask to generate something
 | 
						|
 * like "192.168.1.0/255.255.255.0" .
 | 
						|
 *
 | 
						|
 * @param host string with the hostname to use for the subnet and mask.
 | 
						|
 * @return QString with formated result.
 | 
						|
 **/
 | 
						|
QString format_remote_host(const char *hostname) {
 | 
						|
  struct in_addr local_netmask;
 | 
						|
  struct hostent *temp_hostent;
 | 
						|
  struct in_addr local_ip;
 | 
						|
  struct in_addr local_subnet;
 | 
						|
 | 
						|
  temp_hostent=gethostbyname(hostname);
 | 
						|
  local_ip=*(struct in_addr *)temp_hostent->h_addr;
 | 
						|
  // FIXME: ideally do something smarter than just testing eth0 (see above in check_remote_server() also)
 | 
						|
  get_netmask("eth0", &local_netmask);
 | 
						|
  local_subnet.s_addr=(local_ip.s_addr & local_netmask.s_addr);
 | 
						|
  return QString().sprintf("%s/%s", strdupa(inet_ntoa(local_subnet)), strdupa(inet_ntoa(local_netmask)) );
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
bool OpenDb(QString dbname,QString login,QString pwd,
 | 
						|
	    QString host,QString stationname,bool interactive,RDConfig *config)
 | 
						|
{
 | 
						|
  // 
 | 
						|
  // Yeesh, this whole method really needs a rewrite!
 | 
						|
  // They shoot horses, don't they??
 | 
						|
  //
 | 
						|
 | 
						|
  int db_ver;
 | 
						|
  QString admin_name;
 | 
						|
  QString admin_pwd;
 | 
						|
  QString sql;
 | 
						|
  QSqlQuery *q;
 | 
						|
  QString msg;
 | 
						|
  MySqlLogin *mysql_login;
 | 
						|
  QString str;
 | 
						|
  int err=0;
 | 
						|
  QString err_str="";
 | 
						|
 | 
						|
  //
 | 
						|
  // Open Database
 | 
						|
  //
 | 
						|
  QSqlDatabase *db=QSqlDatabase::addDatabase(admin_config->mysqlDriver());
 | 
						|
  if(!db) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
  db->setDatabaseName(dbname);
 | 
						|
  db->setUserName(login);
 | 
						|
  db->setPassword(pwd);
 | 
						|
  db->setHostName(host);
 | 
						|
  if(!db->open()) {
 | 
						|
    /*
 | 
						|
    if(!interactive) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
    */
 | 
						|
    RDKillDaemons();
 | 
						|
    if(interactive) {
 | 
						|
      msg=QObject::tr("Unable to access the Rivendell Database!\n\
 | 
						|
Please enter a login for an account with\n\
 | 
						|
administrative rights on the mySQL server,\n\
 | 
						|
and we will try to get this straightened out.");
 | 
						|
      mysql_login=new MySqlLogin(msg,&admin_name,&admin_pwd);
 | 
						|
      if(mysql_login->exec()!=0) {
 | 
						|
	delete mysql_login;
 | 
						|
	db->removeDatabase(dbname);
 | 
						|
	return false;
 | 
						|
      }
 | 
						|
      delete mysql_login;
 | 
						|
    }
 | 
						|
    else {
 | 
						|
      admin_name=admin_admin_username;
 | 
						|
      admin_pwd=admin_admin_password;
 | 
						|
      if(!admin_admin_hostname.isEmpty()) {
 | 
						|
	db->setHostName(admin_admin_hostname);
 | 
						|
      }
 | 
						|
    }
 | 
						|
    db->setUserName(admin_name);
 | 
						|
    db->setPassword(admin_pwd);
 | 
						|
    if(db->open()) {      // Fixup DB Access Permsissions
 | 
						|
      PrintError(QObject::tr("Wrong access permissions for accessing mySQL!"),
 | 
						|
		 interactive);
 | 
						|
      db->removeDatabase("mysql");
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
    else {
 | 
						|
      db->setDatabaseName("mysql");
 | 
						|
      if(!db->open()) {   // mySQL is hosed -- scream and die.
 | 
						|
	PrintError(QObject::tr("Unable to connect to mySQL!"),interactive);
 | 
						|
	db->removeDatabase("mysql");
 | 
						|
	return false;
 | 
						|
      }
 | 
						|
      else {              // Create a new Rivendell Database
 | 
						|
	sql=QString().sprintf("create database %s",(const char *)dbname);
 | 
						|
	q=new QSqlQuery(sql);
 | 
						|
	if(!q->isActive()) {   // Can't create DB.
 | 
						|
	  delete q;
 | 
						|
	  PrintError(QObject::tr("Unable to create a Rivendell Database!"),
 | 
						|
		     interactive);
 | 
						|
	  db->removeDatabase("mysql");
 | 
						|
	  return false;
 | 
						|
	}
 | 
						|
	delete q;
 | 
						|
 | 
						|
        // Check if creating the database on the local machine or on a remote
 | 
						|
        // server in the same subnet.  If creating on a remote server, set the
 | 
						|
        // host portion of the MySQL user to the subnet that is common between
 | 
						|
        // the local workstation and the server.
 | 
						|
        if (check_remote_server(host)) {
 | 
						|
          host=format_remote_host(host);
 | 
						|
        }
 | 
						|
	sql=QString().sprintf("create user %s@'%s' identified by '%s'", 
 | 
						|
	          (const char *)login, (const char *)host, (const char *)pwd);
 | 
						|
	q=new QSqlQuery(sql);
 | 
						|
	delete q;
 | 
						|
	sql=QString().sprintf("grant SELECT, INSERT, UPDATE, DELETE, CREATE, DROP,\
 | 
						|
	          INDEX, ALTER, LOCK TABLES on %s.* to %s@'%s'", 
 | 
						|
	          (const char *)dbname, (const char *)login, (const char *)host);
 | 
						|
	q=new QSqlQuery(sql);
 | 
						|
	delete q;
 | 
						|
	q=new QSqlQuery("flush privileges");
 | 
						|
	delete q;
 | 
						|
	db->close();   // Relinquish admin perms
 | 
						|
	if(!admin_admin_dbname.isEmpty()) {
 | 
						|
	  dbname=admin_admin_dbname;
 | 
						|
	}
 | 
						|
	db->setDatabaseName(dbname);
 | 
						|
	db->setUserName(login);
 | 
						|
	db->setPassword(pwd);
 | 
						|
	db->setHostName(host);
 | 
						|
	if(!db->open()) {   // Can't open new database
 | 
						|
	  PrintError(QObject::tr("Unable to connect to new Rivendell Database!"),
 | 
						|
		     interactive);
 | 
						|
	  db->removeDatabase(dbname);
 | 
						|
	  return false;
 | 
						|
	}
 | 
						|
	if(!CreateDb(login,pwd,config)) {   // Can't create tables.
 | 
						|
	  PrintError(QObject::tr("Unable to create Rivendell Database!"),
 | 
						|
		     interactive);
 | 
						|
	  db->removeDatabase(dbname);
 | 
						|
	  return false;
 | 
						|
	}
 | 
						|
	db->close();
 | 
						|
	db->setDatabaseName(dbname);
 | 
						|
	db->setUserName(login);
 | 
						|
	db->setPassword(pwd);
 | 
						|
	if(!db->open()) {
 | 
						|
	  PrintError(QObject::tr("Unable to connect to Rivendell Database!"),
 | 
						|
		     interactive);
 | 
						|
	  db->removeDatabase(dbname);
 | 
						|
	  return false;
 | 
						|
	}	  
 | 
						|
	if(!InitDb(login,pwd,stationname,config)) {  // Can't initialize tables.
 | 
						|
	  PrintError(QObject::tr("Unable to initialize Rivendell Database!"),
 | 
						|
		     interactive);
 | 
						|
	  db->removeDatabase(dbname);
 | 
						|
	  return false;
 | 
						|
	}
 | 
						|
	if(interactive) {
 | 
						|
	  QMessageBox::information(NULL,QObject::tr("RDAdmin"),
 | 
						|
			      QObject::tr("New Rivendell Database Created!"));
 | 
						|
	}
 | 
						|
	return true;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
  if((db_ver=RDCheckVersion())<RD_VERSION_DATABASE) {
 | 
						|
    if(db_ver==0) {    // Pre-historic database version!
 | 
						|
      if(!interactive) {
 | 
						|
	PrintError(QObject::tr("Unable to upgrade database"),false);
 | 
						|
	return false;
 | 
						|
      }
 | 
						|
      msg=QObject::tr("The Rivendell Database is too old to be upgraded,\n\
 | 
						|
and so must be replaced.  This will DESTROY any\n\
 | 
						|
existing audio and data!  If you want to do this,\n\
 | 
						|
enter a username and password for a mySQL account\n\
 | 
						|
with administrative privledges, otherwise hit cancel.");
 | 
						|
      if(interactive) {
 | 
						|
	mysql_login=new MySqlLogin(msg,&admin_name,&admin_pwd);
 | 
						|
	if(mysql_login->exec()!=0) {
 | 
						|
	  delete mysql_login;
 | 
						|
	  db->removeDatabase(dbname);
 | 
						|
	  return false;
 | 
						|
	}
 | 
						|
	delete mysql_login;
 | 
						|
      }
 | 
						|
      else {
 | 
						|
	admin_name=admin_admin_username;
 | 
						|
	admin_pwd=admin_admin_password;
 | 
						|
      }
 | 
						|
      RDKillDaemons();
 | 
						|
      db->close();
 | 
						|
      db->setDatabaseName("mysql");
 | 
						|
      db->setUserName(admin_name);
 | 
						|
      db->setPassword(admin_pwd);
 | 
						|
      if(!db->open()) {
 | 
						|
	PrintError(QObject::tr("Unable to log into Administrator account!"),
 | 
						|
		   interactive);
 | 
						|
	db->removeDatabase(dbname);
 | 
						|
	return false;
 | 
						|
      }	  
 | 
						|
      q=new QSqlQuery(QString().sprintf ("drop database %s",(const char *)dbname));
 | 
						|
      delete q;
 | 
						|
      q=new QSqlQuery(QString().sprintf("create database %s",(const char *)dbname));
 | 
						|
      if(!q->isActive()) {   // Can't create DB.
 | 
						|
	delete q;
 | 
						|
	PrintError(QObject::tr("Unable to create a Rivendell Database!"),
 | 
						|
		   interactive);
 | 
						|
	db->removeDatabase("mysql");
 | 
						|
	return false;
 | 
						|
      }
 | 
						|
      delete q;
 | 
						|
      sql=QString().
 | 
						|
	sprintf("grant all on %s to %s identified by \"%s\"",
 | 
						|
		(const char *)dbname,(const char *)login,(const char *)pwd);
 | 
						|
      q=new QSqlQuery(sql);
 | 
						|
      if(!q->isActive()) {  // Can't authorize DB.
 | 
						|
	PrintError(QObject::tr("Unable to authorize a Rivendell Database!"),
 | 
						|
		   interactive);
 | 
						|
	db->removeDatabase("mysql");
 | 
						|
	return false;
 | 
						|
      }
 | 
						|
      db->close();   // Relinquish admin perms
 | 
						|
      db->setDatabaseName(dbname);
 | 
						|
      db->setUserName(login);
 | 
						|
      db->setPassword(pwd);
 | 
						|
      if(!db->open()) {   // Can't open new database
 | 
						|
	PrintError(QObject::tr("Unable to connect to new Rivendell Database!"),
 | 
						|
		   interactive);
 | 
						|
	db->removeDatabase(dbname);
 | 
						|
	return false;
 | 
						|
      }
 | 
						|
      if(!CreateDb(login,pwd,config)) {   // Can't create tables.
 | 
						|
	PrintError(QObject::tr("Unable to create Rivendell Database!"),
 | 
						|
		   interactive);
 | 
						|
	db->removeDatabase(dbname);
 | 
						|
	return false;
 | 
						|
      }
 | 
						|
      db->close();
 | 
						|
      db->setDatabaseName(dbname);
 | 
						|
      db->setUserName(login);
 | 
						|
      db->setPassword(pwd);
 | 
						|
      if(!db->open()) {
 | 
						|
	PrintError(QObject::tr("Unable to connect to Rivendell Database!"),
 | 
						|
		   interactive);
 | 
						|
	db->removeDatabase(dbname);
 | 
						|
	return false;
 | 
						|
      }	  
 | 
						|
      if(!InitDb(login,pwd,stationname,config)) {   // Can't initialize tables.
 | 
						|
	PrintError(QObject::tr("Unable to initialize Rivendell Database!"),
 | 
						|
		   interactive);
 | 
						|
	db->removeDatabase(dbname);
 | 
						|
	return false;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    else {             // Out-of-date version
 | 
						|
      if(interactive) {
 | 
						|
	if(QMessageBox::warning(NULL,QObject::tr("Update Needed"),
 | 
						|
		  QObject::tr("The Rivendell Database needs to be updated.\n\
 | 
						|
All audio and settings will be preserved, but\n\
 | 
						|
this will STOP any audio playout or recording\n\
 | 
						|
on this machine for a few seconds.  Continue?"),
 | 
						|
				QMessageBox::Yes,QMessageBox::No)!=
 | 
						|
	   QMessageBox::Yes) {
 | 
						|
	  db->removeDatabase(dbname);
 | 
						|
	  return false;
 | 
						|
	}      
 | 
						|
      }
 | 
						|
      RDKillDaemons();
 | 
						|
      if((err=UpdateDb(db_ver,config))!=UPDATEDB_SUCCESS) {
 | 
						|
	err_str=QObject::tr("Unable to update Rivendell Database:");
 | 
						|
	switch(err) {
 | 
						|
	case UPDATEDB_BACKUP_FAILED:
 | 
						|
	  err_str+=QObject::tr("\nDatabase backup failed!");
 | 
						|
	  break;
 | 
						|
 | 
						|
	case UPDATEDB_QUERY_FAILED:
 | 
						|
	  err_str+=QObject::tr("\nSchema modification failed!");
 | 
						|
	  break;
 | 
						|
 | 
						|
	default:
 | 
						|
	  err_str+=QObject::tr("\nUnknown/unspecified error!");
 | 
						|
	  break;
 | 
						|
	}
 | 
						|
	PrintError(err_str,interactive);
 | 
						|
	db->removeDatabase(dbname);
 | 
						|
	return false;
 | 
						|
      }
 | 
						|
      str=QString(
 | 
						|
        QObject::tr("The Rivendell Database has been updated to version"));
 | 
						|
      msg=QString().
 | 
						|
	sprintf("%s %d",(const char *)str,RD_VERSION_DATABASE);
 | 
						|
      if(!admin_skip_backup) {
 | 
						|
	msg+=QObject::tr("\nand a backup of the original database saved in ");
 | 
						|
	msg+=admin_backup_filename;
 | 
						|
      }
 | 
						|
      msg+=".";
 | 
						|
      if(interactive) {
 | 
						|
	QMessageBox::information(NULL,QObject::tr("Database Updated"),msg);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 |