/* * * $Id: k3bsetup2.cpp 623771 2007-01-15 13:47:39Z trueg $ * Copyright (C) 2003-2007 Sebastian Trueg <trueg@k3b.org> * * This file is part of the K3b project. * Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.org> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * See the file "COPYING" for the exact licensing terms. */ #include <config.h> #include <tqlayout.h> #include <tqmap.h> #include <tqfile.h> #include <tqfileinfo.h> #include <tqcheckbox.h> #include <tqlineedit.h> #include <tqlabel.h> #include <tqpushbutton.h> #include <tqscrollview.h> #include <tqtimer.h> #include <klocale.h> #include <kglobal.h> #include <kgenericfactory.h> #include <klistview.h> #include <keditlistbox.h> #include <kmessagebox.h> #include <kinputdialog.h> #include <kstandarddirs.h> #include <tdeconfig.h> #include <tdeversion.h> #include <ktextedit.h> #include "k3bsetup2.h" #include "base_k3bsetup2.h" #include <k3bdevicemanager.h> #include <k3bdevice.h> #include <k3bexternalbinmanager.h> #include <k3bdefaultexternalprograms.h> #include <k3bglobals.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <grp.h> static bool shouldRunSuidRoot( K3bExternalBin* bin ) { // // Since kernel 2.6.8 older cdrecord versions are not able to use the SCSI subsystem when running suid root anymore // So for we ignore the suid root issue with kernel >= 2.6.8 and cdrecord < 2.01.01a02 // // Some kernel version 2.6.16.something again introduced a problem here. Since I do not know the exact version // and a workaround was introduced in cdrecord 2.01.01a05 just use that version as the first for suid root. // // Seems as if cdrdao never had problems with suid root... // if( bin->name() == "cdrecord" ) { return ( K3b::simpleKernelVersion() < K3bVersion( 2, 6, 8 ) || bin->version >= K3bVersion( 2, 1, 1, "a05" ) || bin->hasFeature( "wodim" ) ); } else if( bin->name() == "cdrdao" ) { return true; } else if( bin->name() == "growisofs" ) { // // starting with 6.0 growiofs raises it's priority using nice(-20) // BUT: newer kernels have ridiculously low default memorylocked resource limit, which prevents privileged // users from starting growisofs 6.0 with "unable to anonymously mmap 33554432: Resource temporarily unavailable" // error message. Until Andy releases a version including a workaround we simply never configure growisofs suid root return false; // bin->version >= K3bVersion( 6, 0 ); } else return false; } class K3bSetup2::Private { public: K3bDevice::DeviceManager* deviceManager; K3bExternalBinManager* externalBinManager; bool changesNeeded; TQMap<TQCheckListItem*, TQString> listDeviceMap; TQMap<TQString, TQCheckListItem*> deviceListMap; TQMap<TQCheckListItem*, K3bExternalBin*> listBinMap; TQMap<K3bExternalBin*, TQCheckListItem*> binListMap; TDEConfig* config; }; K3bSetup2::K3bSetup2( TQWidget *parent, const char *, const TQStringList& ) : TDECModule( parent, "k3bsetup" ) { d = new Private(); d->config = new TDEConfig( "k3bsetup2rc" ); m_aboutData = new TDEAboutData("k3bsetup2", "K3bSetup 2", 0, 0, TDEAboutData::License_GPL, "(C) 2003-2007 Sebastian Trueg"); m_aboutData->addAuthor("Sebastian Trueg", 0, "trueg@k3b.org"); setButtons( TDECModule::Apply|TDECModule::Cancel|TDECModule::Ok|TDECModule::Default ); TQHBoxLayout* box = new TQHBoxLayout( this ); box->setAutoAdd(true); box->setMargin(0); box->setSpacing( KDialog::spacingHint() ); KTextEdit* label = new KTextEdit( this ); label->setText( "<h2>K3bSetup</h2>" + i18n("<p>This simple setup assistant is able to set the permissions needed by K3b in order to " "burn CDs and DVDs. " "<p>It does not take things like devfs or resmgr into account. In most cases this is not a " "problem but on some systems the permissions may be altered the next time you login or restart " "your computer. In those cases it is best to consult the distribution documentation." "<p><b>Caution:</b> Although K3bSetup 2 should not be able " "to mess up your system no guarantee can be given.") ); label->setReadOnly( true ); label->setFixedWidth( 200 ); w = new base_K3bSetup2( this ); // TODO: enable this and let root specify users w->m_editUsers->hide(); w->textLabel2->hide(); connect( w->m_checkUseBurningGroup, TQT_SIGNAL(toggled(bool)), this, TQT_SLOT(updateViews()) ); connect( w->m_editBurningGroup, TQT_SIGNAL(textChanged(const TQString&)), this, TQT_SLOT(updateViews()) ); connect( w->m_editSearchPath, TQT_SIGNAL(changed()), this, TQT_SLOT(slotSearchPrograms()) ); connect( w->m_buttonAddDevice, TQT_SIGNAL(clicked()), this, TQT_SLOT(slotAddDevice()) ); d->externalBinManager = new K3bExternalBinManager( this ); d->deviceManager = new K3bDevice::DeviceManager( this ); // these are the only programs that need special permissions d->externalBinManager->addProgram( new K3bCdrdaoProgram() ); d->externalBinManager->addProgram( new K3bCdrecordProgram(false) ); d->externalBinManager->addProgram( new K3bGrowisofsProgram() ); d->externalBinManager->search(); d->deviceManager->scanBus(); load(); // // This is a hack to work around a kcm bug which makes the faulty assumption that // every module starts without anything to apply // TQTimer::singleShot( 0, this, TQT_SLOT(updateViews()) ); if( getuid() != 0 || !d->config->checkConfigFilesWritable( true ) ) makeReadOnly(); } K3bSetup2::~K3bSetup2() { delete d->config; delete d; delete m_aboutData; } void K3bSetup2::updateViews() { d->changesNeeded = false; updatePrograms(); updateDevices(); emit changed( ( getuid() != 0 ) ? false : d->changesNeeded ); } void K3bSetup2::updatePrograms() { // first save which were checked TQMap<K3bExternalBin*, bool> checkMap; TQListViewItemIterator listIt( w->m_viewPrograms ); for( ; listIt.current(); ++listIt ) checkMap.insert( d->listBinMap[(TQCheckListItem*)*listIt], ((TQCheckListItem*)*listIt)->isOn() ); w->m_viewPrograms->clear(); d->binListMap.clear(); d->listBinMap.clear(); // load programs const TQMap<TQString, K3bExternalProgram*>& map = d->externalBinManager->programs(); for( TQMap<TQString, K3bExternalProgram*>::const_iterator it = map.begin(); it != map.end(); ++it ) { K3bExternalProgram* p = it.data(); TQPtrListIterator<K3bExternalBin> binIt( p->bins() ); for( ; binIt.current(); ++binIt ) { K3bExternalBin* b = *binIt; TQFileInfo fi( b->path ); // we need the uid bit which is not supported by TQFileInfo struct stat s; if( ::stat( TQFile::encodeName(b->path), &s ) ) { kdDebug() << "(K3bSetup2) unable to stat " << b->path << endl; } else { // create a checkviewitem TQCheckListItem* bi = new TQCheckListItem( w->m_viewPrograms, b->name(), TQCheckListItem::CheckBox ); bi->setText( 1, b->version ); bi->setText( 2, b->path ); d->listBinMap.insert( bi, b ); d->binListMap.insert( b, bi ); // check the item on first insertion or if it was checked before bi->setOn( checkMap.contains(b) ? checkMap[b] : true ); int perm = s.st_mode & 0007777; TQString wantedGroup("root"); if( w->m_checkUseBurningGroup->isChecked() ) wantedGroup = burningGroup(); int wantedPerm = 0; if( shouldRunSuidRoot( b ) ) { if( w->m_checkUseBurningGroup->isChecked() ) wantedPerm = 0004710; else wantedPerm = 0004711; } else { if( w->m_checkUseBurningGroup->isChecked() ) wantedPerm = 0000750; else wantedPerm = 0000755; } bi->setText( 3, TQString::number( perm, 8 ).rightJustify( 4, '0' ) + " " + fi.owner() + "." + fi.group() ); if( perm != wantedPerm || fi.owner() != "root" || fi.group() != wantedGroup ) { bi->setText( 4, TQString("%1 root.%2").arg(wantedPerm,0,8).arg(wantedGroup) ); if( bi->isOn() ) d->changesNeeded = true; } else bi->setText( 4, i18n("no change") ); } } } } void K3bSetup2::updateDevices() { // first save which were checked TQMap<TQString, bool> checkMap; TQListViewItemIterator listIt( w->m_viewDevices ); for( ; listIt.current(); ++listIt ) checkMap.insert( d->listDeviceMap[(TQCheckListItem*)*listIt], ((TQCheckListItem*)*listIt)->isOn() ); w->m_viewDevices->clear(); d->listDeviceMap.clear(); d->deviceListMap.clear(); TQPtrListIterator<K3bDevice::Device> it( d->deviceManager->allDevices() ); for( ; it.current(); ++it ) { K3bDevice::Device* device = *it; // check the item on first insertion or if it was checked before TQCheckListItem* item = createDeviceItem( device->blockDeviceName() ); item->setOn( checkMap.contains(device->blockDeviceName()) ? checkMap[device->blockDeviceName()] : true ); item->setText( 0, device->vendor() + " " + device->description() ); if( !device->genericDevice().isEmpty() ) { TQCheckListItem* item = createDeviceItem( device->genericDevice() ); item->setOn( checkMap.contains(device->genericDevice()) ? checkMap[device->genericDevice()] : true ); item->setText( 0, device->vendor() + " " + device->description() + " (" + i18n("Generic SCSI Device") + ")" ); } } } TQCheckListItem* K3bSetup2::createDeviceItem( const TQString& deviceNode ) { TQFileInfo fi( deviceNode ); struct stat s; if( ::stat( TQFile::encodeName(deviceNode), &s ) ) { kdDebug() << "(K3bSetup2) unable to stat " << deviceNode << endl; return 0; } else { // create a checkviewitem TQCheckListItem* bi = new TQCheckListItem( w->m_viewDevices, deviceNode, TQCheckListItem::CheckBox ); d->listDeviceMap.insert( bi, deviceNode ); d->deviceListMap.insert( deviceNode, bi ); bi->setText( 1, deviceNode ); int perm = s.st_mode & 0000777; bi->setText( 2, TQString::number( perm, 8 ).rightJustify( 3, '0' ) + " " + fi.owner() + "." + fi.group() ); if( w->m_checkUseBurningGroup->isChecked() ) { // we ignore the device's owner here if( perm != 0000660 || fi.group() != burningGroup() ) { bi->setText( 3, "660 " + fi.owner() + "." + burningGroup() ); if( bi->isOn() ) d->changesNeeded = true; } else bi->setText( 3, i18n("no change") ); } else { // we ignore the device's owner and group here if( perm != 0000666 ) { bi->setText( 3, "666 " + fi.owner() + "." + fi.group() ); if( bi->isOn() ) d->changesNeeded = true; } else bi->setText( 3, i18n("no change") ); } return bi; } } void K3bSetup2::load() { if( d->config->hasGroup("External Programs") ) { d->config->setGroup( "External Programs" ); d->externalBinManager->readConfig( d->config ); } if( d->config->hasGroup("Devices") ) { d->config->setGroup( "Devices" ); d->deviceManager->readConfig( d->config ); } d->config->setGroup( "General Settings" ); w->m_checkUseBurningGroup->setChecked( d->config->readBoolEntry( "use burning group", false ) ); w->m_editBurningGroup->setText( d->config->readEntry( "burning group", "burning" ) ); // load search path w->m_editSearchPath->clear(); w->m_editSearchPath->insertStringList( d->externalBinManager->searchPath() ); updateViews(); } void K3bSetup2::defaults() { w->m_checkUseBurningGroup->setChecked(false); w->m_editBurningGroup->setText( "burning" ); // // This is a hack to work around a kcm bug which makes the faulty assumption that // every module defaults to a state where nothing is to be applied // TQTimer::singleShot( 0, this, TQT_SLOT(updateViews()) ); } void K3bSetup2::save() { d->config->setGroup( "General Settings" ); d->config->writeEntry( "use burning group", w->m_checkUseBurningGroup->isChecked() ); d->config->writeEntry( "burning group", burningGroup() ); d->config->setGroup( "External Programs"); d->externalBinManager->saveConfig( d->config ); d->config->setGroup( "Devices"); d->deviceManager->saveConfig( d->config ); bool success = true; struct group* g = 0; if( w->m_checkUseBurningGroup->isChecked() ) { // TODO: create the group if it's not there g = getgrnam( burningGroup().local8Bit() ); if( !g ) { KMessageBox::error( this, i18n("There is no group %1.").arg(burningGroup()) ); return; } } // save the device permissions TQListViewItemIterator listIt( w->m_viewDevices ); for( ; listIt.current(); ++listIt ) { TQCheckListItem* checkItem = (TQCheckListItem*)listIt.current(); if( checkItem->isOn() ) { TQString dev = d->listDeviceMap[checkItem]; if( w->m_checkUseBurningGroup->isChecked() ) { if( ::chmod( TQFile::encodeName(dev), S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP ) ) success = false; if( ::chown( TQFile::encodeName(dev), (gid_t)-1, g->gr_gid ) ) success = false; } else { if( ::chmod( TQFile::encodeName(dev), S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH ) ) success = false; } } } // save the program permissions listIt = TQListViewItemIterator( w->m_viewPrograms ); for( ; listIt.current(); ++listIt ) { TQCheckListItem* checkItem = (TQCheckListItem*)listIt.current(); if( checkItem->isOn() ) { K3bExternalBin* bin = d->listBinMap[checkItem]; if( w->m_checkUseBurningGroup->isChecked() ) { if( ::chown( TQFile::encodeName(bin->path), (gid_t)0, g->gr_gid ) ) success = false; int perm = 0; if( shouldRunSuidRoot( bin ) ) perm = S_ISUID|S_IRWXU|S_IXGRP; else perm = S_IRWXU|S_IXGRP|S_IRGRP; if( ::chmod( TQFile::encodeName(bin->path), perm ) ) success = false; } else { if( ::chown( TQFile::encodeName(bin->path), 0, 0 ) ) success = false; int perm = 0; if( shouldRunSuidRoot( bin ) ) perm = S_ISUID|S_IRWXU|S_IXGRP|S_IXOTH; else perm = S_IRWXU|S_IXGRP|S_IRGRP|S_IXOTH|S_IROTH; if( ::chmod( TQFile::encodeName(bin->path), perm ) ) success = false; } } } if( success ) KMessageBox::information( this, i18n("Successfully updated all permissions.") ); else { if( getuid() ) KMessageBox::error( this, i18n("Could not update all permissions. You should run K3bSetup 2 as root.") ); else KMessageBox::error( this, i18n("Could not update all permissions.") ); } // WE MAY USE "newgrp -" to reinitialize the environment if we add users to a group updateViews(); } TQString K3bSetup2::quickHelp() const { return i18n("<h2>K3bSetup 2</h2>" "<p>This simple setup assistant is able to set the permissions needed by K3b in order to " "burn CDs and DVDs." "<p>It does not take into account devfs or resmgr, or similar. In most cases this is not a " "problem, but on some systems the permissions may be altered the next time you login or restart " "your computer. In these cases it is best to consult the distribution's documentation." "<p>The important task that K3bSetup 2 performs is grant write access to the CD and DVD devices." "<p><b>Caution:</b> Although K3bSetup 2 should not be able " "to damage your system, no guarantee can be given."); } TQString K3bSetup2::burningGroup() const { TQString g = w->m_editBurningGroup->text(); return g.isEmpty() ? TQString("burning") : g; } void K3bSetup2::slotSearchPrograms() { d->externalBinManager->setSearchPath( w->m_editSearchPath->items() ); d->externalBinManager->search(); updatePrograms(); emit changed( d->changesNeeded ); } void K3bSetup2::slotAddDevice() { bool ok; TQString newDevicename = KInputDialog::getText( i18n("Location of New Drive"), i18n("Please enter the device name where K3b should search\n" "for a new drive (example: /dev/mebecdrom):"), "/dev/", &ok, this ); if( ok ) { if( d->deviceManager->addDevice( newDevicename ) ) { updateDevices(); emit changed( d->changesNeeded ); } else KMessageBox::error( this, i18n("Could not find an additional device at\n%1").arg(newDevicename), i18n("Error"), false ); } } void K3bSetup2::makeReadOnly() { w->m_checkUseBurningGroup->setEnabled( false ); w->m_editBurningGroup->setEnabled( false ); w->m_editUsers->setEnabled( false ); w->m_viewDevices->setEnabled( false ); w->m_buttonAddDevice->setEnabled( false ); w->m_viewPrograms->setEnabled( false ); w->m_editSearchPath->setEnabled( false ); } typedef KGenericFactory<K3bSetup2, TQWidget> K3bSetup2Factory; K_EXPORT_COMPONENT_FACTORY( kcm_k3bsetup2, K3bSetup2Factory("k3bsetup") ) #include "k3bsetup2.moc"