File indexing completed on 2024-05-12 05:35:39

0001 /*
0002     A helper that's run using KAuth and does the system modifications.
0003     SPDX-FileCopyrightText: 1998 Luca Montecchiani <m.luca@usa.net>
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 
0006 */
0007 
0008 #include "helper.h"
0009 
0010 #include <config-workspace.h>
0011 
0012 #include <sys/time.h>
0013 #include <time.h>
0014 #include <unistd.h>
0015 
0016 #include <KConfig>
0017 #include <KConfigGroup>
0018 #include <KProcess>
0019 #include <QDebug>
0020 #include <QFile>
0021 #include <QRegularExpression>
0022 
0023 #if defined(USE_SOLARIS)
0024 #include <KTemporaryFile>
0025 #include <sys/param.h>
0026 #include <sys/stat.h>
0027 #include <sys/types.h>
0028 #endif
0029 
0030 // We cannot rely on the $PATH environment variable, because D-Bus activation
0031 // clears it. So we have to use a reasonable default.
0032 static const QString exePath = QStringLiteral("/usr/sbin:/usr/bin:/sbin:/bin");
0033 
0034 int ClockHelper::ntp(const QStringList &ntpServers, bool ntpEnabled)
0035 {
0036     int ret = 0;
0037 
0038     // write to the system config file
0039     QFile config_file(KDE_CONFDIR "/kcmclockrc");
0040     if (!config_file.exists()) {
0041         config_file.open(QIODevice::WriteOnly);
0042         config_file.close();
0043         config_file.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadGroup | QFile::ReadOther);
0044     }
0045     KConfig _config(config_file.fileName(), KConfig::SimpleConfig);
0046     KConfigGroup config(&_config, QStringLiteral("NTP"));
0047     config.writeEntry("servers", ntpServers);
0048     config.writeEntry("enabled", ntpEnabled);
0049 
0050     QString ntpUtility = QStandardPaths::findExecutable(QStringLiteral("ntpdate"));
0051     if (ntpUtility.isEmpty()) {
0052         ntpUtility = QStandardPaths::findExecutable(QStringLiteral("rdate"));
0053     }
0054 
0055     if (ntpEnabled && !ntpUtility.isEmpty()) {
0056         // NTP Time setting
0057         QString timeServer = ntpServers.first();
0058         if (timeServer.indexOf(QRegularExpression(QStringLiteral(".*\\(.*\\)$"))) != -1) {
0059             timeServer.remove(QRegularExpression(QStringLiteral(".*\\(")));
0060             timeServer.remove(QRegularExpression(QStringLiteral("\\).*")));
0061             // Would this be better?: s/^.*\(([^)]*)\).*$/\1/
0062         }
0063 
0064         KProcess proc;
0065         proc << ntpUtility << timeServer;
0066         if (proc.execute() != 0) {
0067             ret |= NTPError;
0068         } else {
0069             toHwclock();
0070         }
0071     } else if (ntpEnabled) {
0072         ret |= NTPError;
0073     }
0074 
0075     return ret;
0076 }
0077 
0078 int ClockHelper::date(const QString &newdate, const QString &olddate)
0079 {
0080     struct timeval tv;
0081 
0082     tv.tv_sec = newdate.toULong() - olddate.toULong() + time(nullptr);
0083     tv.tv_usec = 0;
0084     if (settimeofday(&tv, nullptr)) {
0085         return DateError;
0086     }
0087 
0088     toHwclock();
0089     return 0;
0090 }
0091 
0092 // on non-Solaris systems which do not use /etc/timezone?
0093 int ClockHelper::tz(const QString &selectedzone)
0094 {
0095     int ret = 0;
0096 
0097     // only allow letters, numbers hyphen underscore plus and forward slash
0098     // allowed pattern taken from time-util.c in systemd
0099     const auto rx = QRegularExpression(QRegularExpression::anchoredPattern(QStringLiteral("[a-zA-Z0-9-_+/]*")));
0100     if (!rx.match(selectedzone).hasMatch()) {
0101         return ret;
0102     }
0103 
0104     QString val;
0105 #if defined(USE_SOLARIS) // MARCO
0106     KTemporaryFile tf;
0107     tf.setPrefix("kde-tzone");
0108     tf.open();
0109     QTextStream ts(&tf);
0110 
0111     QFile fTimezoneFile(INITFILE);
0112     bool updatedFile = false;
0113 
0114     if (fTimezoneFile.open(QIODevice::ReadOnly)) {
0115         bool found = false;
0116 
0117         QTextStream is(&fTimezoneFile);
0118 
0119         for (QString line = is.readLine(); !line.isNull(); line = is.readLine()) {
0120             if (line.find("TZ=") == 0) {
0121                 ts << "TZ=" << selectedzone << endl;
0122                 found = true;
0123             } else {
0124                 ts << line << endl;
0125             }
0126         }
0127 
0128         if (!found) {
0129             ts << "TZ=" << selectedzone << endl;
0130         }
0131 
0132         updatedFile = true;
0133         fTimezoneFile.close();
0134     }
0135 
0136     if (updatedFile) {
0137         ts.device()->reset();
0138         fTimezoneFile.remove();
0139 
0140         if (fTimezoneFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
0141             QTextStream os(&fTimezoneFile);
0142 
0143             for (QString line = ts->readLine(); !line.isNull(); line = ts->readLine()) {
0144                 os << line << endl;
0145             }
0146 
0147             fchmod(fTimezoneFile.handle(), S_IXUSR | S_IRUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
0148             fTimezoneFile.close();
0149         }
0150     }
0151 
0152     val = selectedzone;
0153 #else
0154     QString tz = "/usr/share/zoneinfo/" + selectedzone;
0155 
0156     if (QFile::exists(tz)) { // make sure the new TZ really exists
0157         QFile::remove(QStringLiteral("/etc/localtime"));
0158     } else {
0159         return TimezoneError;
0160     }
0161 
0162     if (!QFile::link(tz, QStringLiteral("/etc/localtime"))) { // fail if we can't setup the new timezone
0163         return TimezoneError;
0164     }
0165 
0166     QFile fTimezoneFile(QStringLiteral("/etc/timezone"));
0167 
0168     if (fTimezoneFile.exists() && fTimezoneFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
0169         QTextStream t(&fTimezoneFile);
0170         t << selectedzone;
0171         fTimezoneFile.close();
0172     }
0173 #endif // !USE_SOLARIS
0174     val = ':' + selectedzone;
0175 
0176     setenv("TZ", val.toLocal8Bit().constData(), 1);
0177     tzset();
0178 
0179     return ret;
0180 }
0181 
0182 int ClockHelper::tzreset()
0183 {
0184 #if !defined(USE_SOLARIS) // Do not update the System!
0185     unlink("/etc/timezone");
0186     unlink("/etc/localtime");
0187 
0188     setenv("TZ", "", 1);
0189     tzset();
0190 #endif // !USE SOLARIS
0191     return 0;
0192 }
0193 
0194 void ClockHelper::toHwclock()
0195 {
0196     QString hwclock = QStandardPaths::findExecutable(QStringLiteral("hwclock"), exePath.split(QLatin1Char(':')));
0197     if (!hwclock.isEmpty()) {
0198         KProcess::execute(hwclock, QStringList() << QStringLiteral("--systohc"));
0199     }
0200 }
0201 
0202 ActionReply ClockHelper::save(const QVariantMap &args)
0203 {
0204     bool _ntp = args.value(QStringLiteral("ntp")).toBool();
0205     bool _date = args.value(QStringLiteral("date")).toBool();
0206     bool _tz = args.value(QStringLiteral("tz")).toBool();
0207     bool _tzreset = args.value(QStringLiteral("tzreset")).toBool();
0208 
0209     int ret = 0; // error code
0210     //  The order here is important
0211     if (_ntp)
0212         ret |= ntp(args.value(QStringLiteral("ntpServers")).toStringList(), args.value(QStringLiteral("ntpEnabled")).toBool());
0213     if (_date)
0214         ret |= date(args.value(QStringLiteral("newdate")).toString(), args.value(QStringLiteral("olddate")).toString());
0215     if (_tz)
0216         ret |= tz(args.value(QStringLiteral("tzone")).toString());
0217     if (_tzreset)
0218         ret |= tzreset();
0219 
0220     if (ret == 0) {
0221         return ActionReply::SuccessReply();
0222     } else {
0223         ActionReply reply(ActionReply::HelperErrorReply());
0224         reply.setErrorCode(static_cast<ActionReply::Error>(ret));
0225         return reply;
0226     }
0227 }
0228 
0229 KAUTH_HELPER_MAIN("org.kde.kcontrol.kcmclock", ClockHelper)