From kde-core-devel Thu Apr 10 16:03:39 2008 From: Lubos Lunak Date: Thu, 10 Apr 2008 16:03:39 +0000 To: kde-core-devel Subject: Administrator mode in systemsettings Message-Id: <200804101803.39797.l.lunak () suse ! cz> X-MARC-Message: https://marc.info/?l=kde-core-devel&m=120784348110805 MIME-Version: 1 Content-Type: multipart/mixed; boundary="--Boundary-00=_bpj/HbyVDYuTLkr" --Boundary-00=_bpj/HbyVDYuTLkr Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Content-Disposition: inline Hello, systemsettings, as far as I can say, has no support at all for actions requiring root rights, and the only way to handle those seems to be running it as root directly. How about the attached patch? It makes the date/time to work normally as user, and when doing changes, it runs a small helper using kdesu that actually does those changes. Well, not that small, but it only links kdecore. I more or less copied the code from save() to the helper and the new values are passed using arguments, and since there's nothing fancy done with them, it looks quite safe to my security-not-much-trained eye. The arguments only look a bit ugly in the kdesu dialog. The patch reduces the amount of code that's run as root, doesn't make root modules possibly look different, avoids the XEmbed fun and makes it work. Comments, flames, improvements? -- Lubos Lunak KDE developer -------------------------------------------------------------- SUSE LINUX, s.r.o. e-mail: l.lunak@suse.cz , l.lunak@kde.org Lihovarska 1060/12 tel: +420 284 028 972 190 00 Prague 9 fax: +420 284 028 951 Czech Republic http://www.suse.cz --Boundary-00=_bpj/HbyVDYuTLkr Content-Type: text/x-diff; charset="us-ascii"; name="dateandtime.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="dateandtime.patch" --- dateandtime/clock.desktop.sav 2008-04-04 13:59:39.000000000 +0200 +++ dateandtime/clock.desktop 2008-04-10 17:51:14.000000000 +0200 @@ -6,8 +6,6 @@ X-KDE-ServiceTypes=KCModule X-DocPath=kcontrol/clock/index.html X-KDE-Library=kcm_clock -X-KDE-RootOnly=true -X-KDE-SubstituteUID=true X-KDE-ParentApp=kcontrol #FIXME should this be in system settings when we can get to it through the panel? --- dateandtime/dtime.cpp.sav 2008-03-18 17:58:56.000000000 +0100 +++ dateandtime/dtime.cpp 2008-04-10 17:26:33.000000000 +0200 @@ -49,6 +49,8 @@ #include "dtime.moc" +#include "helper.h" + HMSTimeWidget::HMSTimeWidget(QWidget *parent) : KIntSpinBox(parent) { @@ -202,15 +204,6 @@ Dtime::Dtime(QWidget * parent) load(); - if (getuid() != 0) - { - cal->setEnabled(false); - hour->setEnabled(false); - minute->setEnabled(false); - second->setEnabled(false); - timeServerList->setEnabled(false); - setDateTimeAuto->setEnabled(false); - } kclock->setEnabled(false); } @@ -263,7 +256,9 @@ void Dtime::configChanged(){ void Dtime::load() { - KConfig _config( "kcmclockrc", KConfig::NoGlobals ); + // The config is actually written to the system config, but the user does not have any local config, + // since there is nothing writing it. + KConfig _config( "kcmclockrc", KConfig::NoGlobals ); KConfigGroup config(&_config, "NTP"); timeServerList->addItems(config.readEntry("servers", i18n("Public Time Server (pool.ntp.org),\ @@ -284,11 +279,8 @@ oceania.pool.ntp.org")).split(',', QStri timeout(); } -void Dtime::save() +void Dtime::save( QStringList& helperargs ) { - KConfig _config("kcmclockrc", KConfig::NoGlobals); - KConfigGroup config(&_config, "NTP"); - // Save the order, but don't duplicate! QStringList list; if( timeServerList->count() != 0) @@ -301,32 +293,17 @@ void Dtime::save() if( list.count() == 10) break; } - config.writeEntry("servers", list); - config.writeEntry("enabled", setDateTimeAuto->isChecked()); + helperargs << "ntp" << QString::number( list.count()) << list + << ( setDateTimeAuto->isChecked() ? "enabled" : "disabled" ); if(setDateTimeAuto->isChecked() && !ntpUtility.isEmpty()){ - // NTP Time setting - QString timeServer = timeServerList->currentText(); - if( timeServer.indexOf( QRegExp(".*\\(.*\\)$") ) != -1 ) { - timeServer.replace( QRegExp(".*\\("), "" ); - timeServer.replace( QRegExp("\\).*"), "" ); - // Would this be better?: s/^.*\(([^)]*)\).*$/\1/ - } - KProcess proc; - proc << ntpUtility << timeServer; - if( proc.execute() != 0 ){ - KMessageBox::error( this, i18n("Unable to contact time server: %1.", timeServer) ); - setDateTimeAuto->setChecked( false ); - } - else { - // success - kDebug() << "Set date from time server " << timeServer.toLatin1() << " success!"; - } + // NTP Time setting - done in helper + timeServer = timeServerList->currentText(); + kDebug() << "Setting date from time server " << timeServer; } else { // User time setting - KProcess c_proc; - + QString BufS; // BSD systems reverse year compared to Susv3 #if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__) BufS.sprintf("%04d%02d%02d%02d%02d.%02d", @@ -342,27 +319,24 @@ void Dtime::save() kDebug() << "Set date " << BufS; - c_proc << "date" << BufS; - int result = c_proc.execute(); - if (result != 0 -#if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__) - && result != 2 // can only set local date, which is okay -#endif - ) { - KMessageBox::error( this, i18n("Can not set date.")); - return; - } - - // try to set hardware clock. We do not care if it fails - KProcess hwc_proc; - hwc_proc << "hwclock" << "--systohc"; - hwc_proc.execute(); + helperargs << "date" << BufS; } // restart time internalTimer.start( 1000 ); } +void Dtime::processHelperErrors( int code ) +{ + if( code & ERROR_DTIME_NTP ) { + KMessageBox::error( this, i18n("Unable to contact time server: %1.", timeServer) ); + setDateTimeAuto->setChecked( false ); + } + if( code & ERROR_DTIME_DATE ) { + KMessageBox::error( this, i18n("Can not set date.")); + } +} + void Dtime::timeout() { // get current time --- dateandtime/dtime.h.sav 2007-05-28 13:45:54.000000000 +0200 +++ dateandtime/dtime.h 2008-04-10 16:07:34.000000000 +0200 @@ -53,7 +53,8 @@ class Dtime : public QWidget public: Dtime( QWidget *parent=0 ); - void save(); + void save( QStringList& helperargs ); + void processHelperErrors( int code ); void load(); QString quickHelp() const; @@ -90,7 +91,7 @@ private: QDate date; QTimer internalTimer; - QString BufS; + QString timeServer; int BufI; bool refresh; bool ontimeout; --- dateandtime/main.cpp.sav 2008-01-09 13:19:10.000000000 +0100 +++ dateandtime/main.cpp 2008-04-10 17:32:16.000000000 +0200 @@ -33,11 +33,15 @@ #include #include #include +#include +#include +#include #include "main.moc" #include "tzone.h" #include "dtime.h" +#include "helper.h" K_PLUGIN_FACTORY(KlockModuleFactory, registerPlugin();) K_EXPORT_PLUGIN(KlockModuleFactory("kcmkclock")) @@ -77,17 +81,30 @@ KclockModule::KclockModule(QWidget *pare layout->addStretch(); - if(getuid() == 0) - setButtons(Help|Apply); - else - setButtons(Help); + setButtons(Help|Apply); } void KclockModule::save() { -// The order here is important - dtime->save(); - tzone->save(); + QStringList helperargs; + dtime->save( helperargs ); + tzone->save( helperargs ); + QString helper = KStandardDirs::findExe( "kcmdatetimehelper" ); + int error = 0; + if( helper.isEmpty()) + error = -1; + else { + KProcess proc; + proc << "kdesu" << "--" << helper; + proc << helperargs; + error = proc.execute(); + } + if( error < 0 || error == ERROR_CALL ) + KMessageBox::error( this, i18n( "Failed to set system date/time/timezone."), i18n( "Date/Time Error" )); + else { + dtime->processHelperErrors( error ); + tzone->processHelperErrors( error ); + } #if 0 // Tell the clock applet about the change so that it can update its timezone QDBusInterface clock("org.kde.kicker", "/Applets/Clock", "org.kde.kicker.ClockApplet"); @@ -100,4 +117,3 @@ void KclockModule::load() dtime->load(); tzone->load(); } - --- dateandtime/tzone.cpp.sav 2007-09-18 19:00:24.000000000 +0200 +++ dateandtime/tzone.cpp 2008-04-10 16:05:46.000000000 +0200 @@ -57,6 +57,8 @@ #include #endif +#include "helper.h" + Tzone::Tzone(QWidget * parent) : QGroupBox(parent) { @@ -73,8 +75,6 @@ Tzone::Tzone(QWidget * parent) lay->addWidget(tzonelist); load(); - - tzonelist->setEnabled(getuid() == 0); } void Tzone::load() @@ -99,123 +99,24 @@ void Tzone::currentZone() // FIXME: Does the logic in this routine actually work correctly? For example, // on non-Solaris systems which do not use /etc/timezone? -void Tzone::save() +void Tzone::save( QStringList& helperargs ) { QStringList selectedZones(tzonelist->selection()); if (selectedZones.count() > 0) { - // Find untranslated selected zone QString selectedzone(selectedZones[0]); - -#if defined(USE_SOLARIS) // MARCO - - KTemporaryFile tf; - tf.setPrefix("kde-tzone"); - tf.open(); - QTextStream ts(&tf); - - QFile fTimezoneFile(INITFILE); - bool updatedFile = false; - - if (fTimezoneFile.open(QIODevice::ReadOnly)) - { - bool found = false; - - QTextStream is(&fTimezoneFile); - - for (QString line = is.readLine(); !line.isNull(); - line = is.readLine()) - { - if (line.find("TZ=") == 0) - { - ts << "TZ=" << selectedzone << endl; - found = true; - } - else - { - ts << line << endl; - } - } - - if (!found) - { - ts << "TZ=" << selectedzone << endl; - } - - updatedFile = true; - fTimezoneFile.close(); - } - - if (updatedFile) - { - ts.device()->reset(); - fTimezoneFile.remove(); - - if (fTimezoneFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) - { - QTextStream os(&fTimezoneFile); - - for (QString line = ts->readLine(); !line.isNull(); - line = ts->readLine()) - { - os << line << endl; - } - - fchmod(fTimezoneFile.handle(), - S_IXUSR | S_IRUSR | S_IRGRP | S_IXGRP | - S_IROTH | S_IXOTH); - fTimezoneFile.close(); - } - } - - - QString val = selectedzone; -#else - QString tz = "/usr/share/zoneinfo/" + selectedzone; - - kDebug() << "Set time zone " << tz; - - if( !KStandardDirs::findExe( "zic" ).isEmpty()) - { - KProcess::execute("zic", QStringList() << "-l" << selectedzone); - } - else - { - QFile fTimezoneFile("/etc/timezone"); - - if (fTimezoneFile.open(QIODevice::WriteOnly | QIODevice::Truncate) ) - { - QTextStream t(&fTimezoneFile); - t << selectedzone; - fTimezoneFile.close(); - } - - if (!QFile::remove("/etc/localtime")) - { - //After the KDE 3.2 release, need to add an error message - } - else - if (!KIO::NetAccess::file_copy(KUrl(tz),KUrl("/etc/localtime"))) - KMessageBox::error( 0, i18n("Error setting new timezone."), - i18n("Timezone Error")); - } - - QString val = ':' + tz; -#endif // !USE_SOLARIS - - setenv("TZ", val.toAscii(), 1); - tzset(); - + helperargs << "tz" << selectedzone; // make the helper set the timezone } else { -#if !defined(USE_SOLARIS) // Do not update the System! - unlink( "/etc/timezone" ); - unlink( "/etc/localtime" ); - - setenv("TZ", "", 1); - tzset(); -#endif // !USE SOLARIS + helperargs << "tzreset"; // // make the helper reset the timezone } currentZone(); } + +void Tzone::processHelperErrors( int code ) +{ + if( code & ERROR_TZONE ) + KMessageBox::error( this, i18n("Error setting new timezone."), + i18n("Timezone Error")); +} --- dateandtime/tzone.h.sav 2007-10-23 11:02:58.000000000 +0200 +++ dateandtime/tzone.h 2008-04-10 16:03:48.000000000 +0200 @@ -37,7 +37,8 @@ class Tzone : public QGroupBox public: Tzone( QWidget *parent=0 ); - void save(); + void save( QStringList& helperargs ); + void processHelperErrors( int code ); void load(); Q_SIGNALS: --- dateandtime/CMakeLists.txt.sav 2008-03-18 17:58:56.000000000 +0100 +++ dateandtime/CMakeLists.txt 2008-04-10 17:55:01.000000000 +0200 @@ -13,6 +13,12 @@ target_link_libraries(kcm_clock ${KDE4_ install(TARGETS kcm_clock DESTINATION ${PLUGIN_INSTALL_DIR} ) +########### next target ############### + +set(kcmdatetimehelper_SRCS helper.cpp) +kde4_add_executable(kcmdatetimehelper ${kcmdatetimehelper_SRCS}) +target_link_libraries(kcmdatetimehelper ${KDE4_KDECORE_LIBS} ) +install(TARGETS kcmdatetimehelper DESTINATION ${LIBEXEC_INSTALL_DIR}) ########### install files ############### --- dateandtime/helper.cpp.sav 2008-04-10 14:12:38.000000000 +0200 +++ dateandtime/helper.cpp 2008-04-10 17:32:04.000000000 +0200 @@ -0,0 +1,285 @@ +/* + * tzone.cpp + * + * Copyright (C) 1998 Luca Montecchiani + * + * 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. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + + A helper that's run using kdesu and does the system modifications. + +*/ + +#include "helper.h" + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#if defined(USE_SOLARIS) +#include +#include +#include +#include +#endif + +static int Dtime_save_ntp( const QStringList& ntpServers, bool ntpEnabled ) +{ + int ret = 0; + // write to the system config file + KConfig _config( KDE_CONFDIR "kcmclockrc", KConfig::SimpleConfig); + KConfigGroup config(&_config, "NTP"); + config.writeEntry("servers", ntpServers ); + config.writeEntry("enabled", ntpEnabled ); + + QString ntpUtility; + if(!KStandardDirs::findExe("ntpdate").isEmpty()) { + ntpUtility = "ntpdate"; + } else if(!KStandardDirs::findExe("rdate").isEmpty()) { + ntpUtility = "rdate"; + } + + if(ntpEnabled && !ntpUtility.isEmpty()){ + // NTP Time setting + QString timeServer = ntpServers.first(); + if( timeServer.indexOf( QRegExp(".*\\(.*\\)$") ) != -1 ) { + timeServer.replace( QRegExp(".*\\("), "" ); + timeServer.replace( QRegExp("\\).*"), "" ); + // Would this be better?: s/^.*\(([^)]*)\).*$/\1/ + } + KProcess proc; + proc << ntpUtility << timeServer; + if( proc.execute() != 0 ){ + ret |= ERROR_DTIME_NTP; + } + } else { + ret |= ERROR_DTIME_NTP; + } + return ret; +} + +static int Dtime_save_date( const QString& date ) +{ + int ret = 0; + // User time setting + KProcess c_proc; + c_proc << "date" << date; + int result = c_proc.execute(); + if (result != 0 +#if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__) + && result != 2 // can only set local date, which is okay +#endif + ) { + ret |= ERROR_DTIME_DATE; + return ret; + } + + // try to set hardware clock. We do not care if it fails + KProcess hwc_proc; + hwc_proc << "hwclock" << "--systohc"; + hwc_proc.execute(); + return ret; +} + +// on non-Solaris systems which do not use /etc/timezone? +static int Tzone_save_set( const QString& selectedzone ) +{ + int ret = 0; +#if defined(USE_SOLARIS) // MARCO + + KTemporaryFile tf; + tf.setPrefix("kde-tzone"); + tf.open(); + QTextStream ts(&tf); + + QFile fTimezoneFile(INITFILE); + bool updatedFile = false; + + if (fTimezoneFile.open(QIODevice::ReadOnly)) + { + bool found = false; + + QTextStream is(&fTimezoneFile); + + for (QString line = is.readLine(); !line.isNull(); + line = is.readLine()) + { + if (line.find("TZ=") == 0) + { + ts << "TZ=" << selectedzone << endl; + found = true; + } + else + { + ts << line << endl; + } + } + + if (!found) + { + ts << "TZ=" << selectedzone << endl; + } + + updatedFile = true; + fTimezoneFile.close(); + } + + if (updatedFile) + { + ts.device()->reset(); + fTimezoneFile.remove(); + + if (fTimezoneFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) + { + QTextStream os(&fTimezoneFile); + + for (QString line = ts->readLine(); !line.isNull(); + line = ts->readLine()) + { + os << line << endl; + } + + fchmod(fTimezoneFile.handle(), + S_IXUSR | S_IRUSR | S_IRGRP | S_IXGRP | + S_IROTH | S_IXOTH); + fTimezoneFile.close(); + } + } + + + QString val = selectedzone; +#else + QString tz = "/usr/share/zoneinfo/" + selectedzone; + + kDebug() << "Set time zone " << tz; + + if( !KStandardDirs::findExe( "zic" ).isEmpty()) + { + KProcess::execute("zic", QStringList() << "-l" << selectedzone); + } + else + { + QFile fTimezoneFile("/etc/timezone"); + + if (fTimezoneFile.open(QIODevice::WriteOnly | QIODevice::Truncate) ) + { + QTextStream t(&fTimezoneFile); + t << selectedzone; + fTimezoneFile.close(); + } + + if (!QFile::remove("/etc/localtime")) + { + ret |= ERROR_TZONE; + } + else + if (!QFile::copy(tz,"/etc/localtime")) + ret |= ERROR_TZONE; + } + + QString val = ':' + tz; +#endif // !USE_SOLARIS + + setenv("TZ", val.toAscii(), 1); + tzset(); + + return ret; +} + +static int Tzone_save_reset() +{ +#if !defined(USE_SOLARIS) // Do not update the System! + unlink( "/etc/timezone" ); + unlink( "/etc/localtime" ); + + setenv("TZ", "", 1); + tzset(); +#endif // !USE SOLARIS + return 0; +} + + +int main( int argc, char* argv[] ) +{ + if( getuid() != 0 ) { + fprintf( stderr, "Needs to be called as root.\n" ); + return ERROR_CALL; + } + bool ntp = false; + QStringList ntpServerList; + bool ntpEnabled; + bool date = false; + QString datestr; + bool tz = false; + QString timezone; + bool tzreset = false; + QStringList args; + for( int pos = 1; + pos < argc; + ++pos ) + args.append( argv[ pos ] ); // convert to QStringList first to protect against possible overflows + while( !args.isEmpty()) { + QString arg = args.takeFirst(); + if( arg == "ntp" && !args.isEmpty()) { + int ntpCount = args.takeFirst().toInt(); + if( ntpCount >= 0 && ntpCount <= args.count()) { + for( int i = 0; + i < ntpCount; + ++i ) { + ntpServerList.append( args.takeFirst()); + } + } + if( args.isEmpty()) { + fprintf( stderr, "Wrong arguments!\n" ); + exit( ERROR_CALL ); + } + ntpEnabled = args.takeFirst() == "enabled"; + ntp = true; + } else if( arg == "date" && !args.isEmpty()) { + datestr = args.takeFirst(); + date = true; + } else if( arg == "tz" && !args.isEmpty()) { + timezone = args.takeFirst(); + tz = true; + } else if( arg == "tzreset" ) { + tzreset = true; + } else { + fprintf( stderr, "Wrong arguments!\n" ); + exit( ERROR_CALL ); + } + } + int ret = 0; // error code +// The order here is important + if( ntp ) + ret |= Dtime_save_ntp( ntpServerList, ntpEnabled ); + if( date ) + ret |= Dtime_save_date( datestr ); + if( tz ) + ret |= Tzone_save_set( timezone ); + if( tzreset ) + ret |= Tzone_save_reset(); + return ret; +} --- dateandtime/helper.h.sav 2008-04-10 16:04:32.000000000 +0200 +++ dateandtime/helper.h 2008-04-10 17:32:10.000000000 +0200 @@ -0,0 +1,40 @@ +/* + * main.h + * + * Copyright (C) 1998 Luca Montecchiani + * + * 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. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#ifndef helper_included +#define helper_included + +/* + commands: + ntp + date + tz + tzreset +*/ + +enum + { + ERROR_CALL = 1 << 0, + ERROR_TZONE = 1 << 1, + ERROR_DTIME_NTP = 1 << 2, + ERROR_DTIME_DATE = 1 << 3 + }; + +#endif // main_included --Boundary-00=_bpj/HbyVDYuTLkr--