You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
k3b/k3bsetup/k3bsetup2.cpp

561 lines
16 KiB

/*
*
* $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 <kconfig.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;
KConfig* config;
};
K3bSetup2::K3bSetup2( TQWidget *parent, const char *, const TQStringList& )
: KCModule( parent, "k3bsetup" )
{
d = new Private();
d->config = new KConfig( "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( KCModule::Apply|KCModule::Cancel|KCModule::Ok|KCModule::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"