File indexing completed on 2024-05-05 05:48:31
0001 /* 0002 CT Cron Implementation 0003 -------------------------------------------------------------------- 0004 SPDX-FileCopyrightText: 1999 Gary Meyer <gary@meyer.net> 0005 -------------------------------------------------------------------- 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "ctcron.h" 0010 0011 #include <QDateTime> 0012 #include <QFile> 0013 #include <QProcess> 0014 #include <QRegularExpression> 0015 #include <QTemporaryFile> 0016 #include <QTextStream> 0017 0018 #include <KLocalizedString> 0019 #include <KShell> 0020 0021 #include "ctInitializationError.h" 0022 #include "cttask.h" 0023 #include "ctvariable.h" 0024 0025 // For root permissions 0026 #include <KAuth/Action> 0027 #include <KAuth/ExecuteJob> 0028 #include <kauth_version.h> 0029 0030 #include <pwd.h> // pwd, getpwnam(), getpwuid() 0031 #include <unistd.h> // getuid(), unlink() 0032 0033 #include "kcm_cron_debug.h" 0034 0035 CommandLineStatus CommandLine::execute() 0036 { 0037 QProcess process; 0038 0039 int exitCode; 0040 process.start(commandLine, parameters); 0041 if (!process.waitForStarted()) { 0042 exitCode = 127; 0043 } else { 0044 process.waitForFinished(-1); 0045 exitCode = process.exitCode(); 0046 } 0047 0048 CommandLineStatus commandLineStatus; 0049 0050 commandLineStatus.commandLine = commandLine + QLatin1String(" ") + parameters.join(QLatin1String(" ")); 0051 0052 commandLineStatus.standardOutput = QLatin1String(process.readAllStandardOutput()); 0053 commandLineStatus.standardError = QLatin1String(process.readAllStandardError()); 0054 commandLineStatus.exitCode = exitCode; 0055 0056 return commandLineStatus; 0057 } 0058 0059 CTCron::CTCron(const QString &crontabBinary, const struct passwd *userInfos, bool currentUserCron, CTInitializationError &ctInitializationError) 0060 : d(new CTCronPrivate()) 0061 { 0062 Q_ASSERT(userInfos); 0063 0064 d->multiUserCron = false; 0065 d->systemCron = false; 0066 d->currentUserCron = currentUserCron; 0067 0068 d->crontabBinary = crontabBinary; 0069 0070 CommandLine readCommandLine; 0071 0072 // regular user, so provide user's own crontab 0073 if (currentUserCron) { 0074 readCommandLine.commandLine = d->crontabBinary; 0075 readCommandLine.parameters << QStringLiteral("-l"); 0076 } else { 0077 readCommandLine.commandLine = d->crontabBinary; 0078 readCommandLine.parameters << QStringLiteral("-u") << QLatin1String(userInfos->pw_name) << QStringLiteral("-l"); 0079 } 0080 0081 d->initialTaskCount = 0; 0082 d->initialVariableCount = 0; 0083 0084 if (!initializeFromUserInfos(userInfos)) { 0085 ctInitializationError.setErrorMessage(i18n("No password entry found for uid '%1'", getuid())); 0086 qCDebug(KCM_CRON_LOG) << "Error in crontab creation of" << userInfos->pw_name; 0087 return; 0088 } 0089 0090 // Don't set error if it can't be read, it means the user doesn't have a crontab. 0091 CommandLineStatus commandLineStatus = readCommandLine.execute(); 0092 if (commandLineStatus.exitCode == 0) { 0093 QTextStream stream(&commandLineStatus.standardOutput); 0094 parseTextStream(&stream); 0095 } else { 0096 qCDebug(KCM_CRON_LOG) << "Error when executing command" << commandLineStatus.commandLine; 0097 qCDebug(KCM_CRON_LOG) << "Standard output :" << commandLineStatus.standardOutput; 0098 qCDebug(KCM_CRON_LOG) << "Standard error :" << commandLineStatus.standardError; 0099 } 0100 0101 d->initialTaskCount = d->task.size(); 0102 d->initialVariableCount = d->variable.size(); 0103 } 0104 0105 CTCron::CTCron() 0106 : d(new CTCronPrivate()) 0107 { 0108 // Empty constructor, new CTCronPrivate created. 0109 } 0110 0111 bool CTCron::initializeFromUserInfos(const struct passwd *userInfos) 0112 { 0113 if (userInfos == nullptr) { 0114 return false; 0115 } else { 0116 d->userLogin = QLatin1String(userInfos->pw_name); 0117 d->userRealName = QLatin1String(userInfos->pw_gecos); 0118 return true; 0119 } 0120 } 0121 0122 CTCron &CTCron::operator=(const CTCron &source) 0123 { 0124 if (this == &source) { 0125 return *this; 0126 } 0127 0128 // Not sure when this would tigger. 0129 if (source.isSystemCron()) { 0130 qCDebug(KCM_CRON_LOG) << "Affect the system cron"; 0131 } 0132 0133 d->variable.clear(); 0134 const auto variables = source.variables(); 0135 for (CTVariable *ctVariable : variables) { 0136 auto tmp = new CTVariable(*ctVariable); 0137 d->variable.append(tmp); 0138 } 0139 0140 d->task.clear(); 0141 const auto tasks = source.tasks(); 0142 for (CTTask *ctTask : tasks) { 0143 auto tmp = new CTTask(*ctTask); 0144 d->task.append(tmp); 0145 } 0146 0147 return *this; 0148 } 0149 0150 void CTCron::parseFile(const QString &fileName) 0151 { 0152 QFile file(fileName); 0153 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { 0154 return; 0155 } 0156 0157 QTextStream in(&file); 0158 parseTextStream(&in); 0159 } 0160 0161 void CTCron::parseTextStream(QTextStream *stream) 0162 { 0163 QString comment; 0164 0165 while (!stream->atEnd()) { 0166 QString line = stream->readLine(); 0167 // skip empty lines, empty lines are space between commented cron expressions 0168 if (line.isEmpty()) { 0169 comment.clear(); 0170 continue; 0171 } 0172 // search for comments "#" but not disabled tasks "#\" 0173 // It's always loading comments user added by crontab -e command 0174 // or user added by Kcron 0175 if (line.startsWith(QLatin1Char('#')) && line.indexOf(QLatin1Char('\\')) != 1) { 0176 line = line.mid(1, line.length() - 1); 0177 if (comment.isEmpty()) { 0178 comment = line.trimmed(); 0179 } else { 0180 comment += QLatin1Char('\n') + line.trimmed(); 0181 } 0182 continue; 0183 } 0184 0185 // either a task or a variable 0186 int firstWhiteSpace(line.indexOf(QRegularExpression(QLatin1String("[ \t]")))); 0187 int firstEquals(line.indexOf(QLatin1String("="))); 0188 0189 // if there is an equals sign and either there is no 0190 // whitespace or the first whitespace is after the equals 0191 // sign, it must be a variable 0192 if ((firstEquals > 0) && ((firstWhiteSpace == -1) || firstWhiteSpace > firstEquals)) { 0193 // create variable 0194 auto tmp = new CTVariable(line, comment, d->userLogin); 0195 d->variable.append(tmp); 0196 comment.clear(); 0197 } 0198 // must be a task, either enabled or disabled 0199 else { 0200 if (firstWhiteSpace > 0) { 0201 auto tmp = new CTTask(line, comment, d->userLogin, d->multiUserCron); 0202 d->task.append(tmp); 0203 comment.clear(); 0204 } 0205 } 0206 } 0207 } 0208 0209 QString CTCron::exportCron() const 0210 { 0211 QString exportCron; 0212 0213 for (CTVariable *ctVariable : std::as_const(d->variable)) { 0214 exportCron += ctVariable->exportVariable(); 0215 exportCron += QLatin1String("\n"); 0216 } 0217 for (CTTask *ctTask : std::as_const(d->task)) { 0218 exportCron += ctTask->exportTask(); 0219 exportCron += QLatin1String("\n"); 0220 } 0221 0222 exportCron += QLatin1String("\n"); 0223 QString exportInfo = 0224 i18nc("Generation Message + current date", "File generated by KCron the %1.", QDateTime::currentDateTime().toString(QLocale().dateTimeFormat())); 0225 exportCron += QLatin1String("# ") + exportInfo + QLatin1String("\n"); 0226 0227 return exportCron; 0228 } 0229 0230 CTCron::~CTCron() 0231 { 0232 qDeleteAll(d->task); 0233 0234 qDeleteAll(d->variable); 0235 delete d; 0236 } 0237 0238 CTSaveStatus CTCron::prepareSaveStatusError(const CommandLineStatus &commandLineStatus) 0239 { 0240 QString standardOutput; 0241 if (commandLineStatus.standardOutput.isEmpty()) { 0242 standardOutput = i18n("<em>No output.</em>"); 0243 } else { 0244 standardOutput = commandLineStatus.standardOutput; 0245 } 0246 0247 QString standardError; 0248 if (commandLineStatus.standardError.isEmpty()) { 0249 standardError = i18n("<em>No error.</em>"); 0250 } else { 0251 standardError = commandLineStatus.standardError; 0252 } 0253 0254 QString detailError; 0255 if (commandLineStatus.exitCode == 127) { 0256 detailError = i18n("<p><strong>Command:</strong> %1</p><strong>Command could not be started</strong>", commandLineStatus.commandLine); 0257 } else { 0258 detailError = i18n("<p><strong>Command:</strong> %1</p><strong>Standard Output :</strong><pre>%2</pre><strong>Error Output :</strong><pre>%3</pre>", 0259 commandLineStatus.commandLine, 0260 standardOutput, 0261 standardError); 0262 } 0263 0264 return CTSaveStatus(i18n("An error occurred while updating crontab."), detailError); 0265 } 0266 0267 CTSaveStatus CTCron::save() 0268 { 0269 // write to temp file 0270 QTemporaryFile tmp; 0271 if (!tmp.open()) { 0272 return CTSaveStatus(i18n("Unable to open crontab file for writing"), i18n("The file %1 could not be opened.", tmp.fileName())); 0273 } 0274 0275 { 0276 QTextStream out(&tmp); 0277 out << exportCron(); 0278 out.flush(); 0279 } 0280 tmp.close(); 0281 0282 // For root permissions. 0283 if (d->systemCron) { 0284 qCDebug(KCM_CRON_LOG) << "Attempting to save system cron"; 0285 QVariantMap args; 0286 args.insert(QStringLiteral("source"), tmp.fileName()); 0287 0288 KAuth::Action saveAction(QStringLiteral("local.kcron.crontab.save")); 0289 saveAction.setHelperId(QStringLiteral("local.kcron.crontab")); 0290 saveAction.setArguments(args); 0291 KAuth::ExecuteJob *job = saveAction.execute(); 0292 if (!job->exec()) 0293 qCDebug(KCM_CRON_LOG) << "KAuth returned an error: " << job->error() << job->errorText(); 0294 if (job->error() > 0) { 0295 return CTSaveStatus(i18n("KAuth::ExecuteJob Error"), job->errorText()); 0296 } 0297 } 0298 // End root permissions. 0299 else { 0300 qCDebug(KCM_CRON_LOG) << "Attempting to save user cron"; 0301 // Save without root permissions. 0302 CommandLine writeCommandLine; 0303 writeCommandLine.commandLine = d->crontabBinary; 0304 if (d->currentUserCron) { 0305 writeCommandLine.parameters << tmp.fileName(); 0306 } else { 0307 writeCommandLine.parameters << QStringLiteral("-u") << d->userLogin << tmp.fileName(); 0308 } 0309 0310 const CommandLineStatus commandLineStatus = writeCommandLine.execute(); 0311 if (commandLineStatus.exitCode != 0) { 0312 return prepareSaveStatusError(commandLineStatus); 0313 } 0314 } 0315 0316 // Mark as applied 0317 for (CTTask *ctTask : std::as_const(d->task)) { 0318 ctTask->apply(); 0319 } 0320 0321 for (CTVariable *ctVariable : std::as_const(d->variable)) { 0322 ctVariable->apply(); 0323 } 0324 0325 d->initialTaskCount = d->task.size(); 0326 d->initialVariableCount = d->variable.size(); 0327 qCDebug(KCM_CRON_LOG) << "All saved"; 0328 return CTSaveStatus(); 0329 } 0330 0331 void CTCron::cancel() 0332 { 0333 const auto tasks = d->task; 0334 for (CTTask *ctTask : tasks) { 0335 ctTask->cancel(); 0336 } 0337 0338 const auto variables = d->variable; 0339 for (CTVariable *ctVariable : variables) { 0340 ctVariable->cancel(); 0341 } 0342 } 0343 0344 bool CTCron::isDirty() const 0345 { 0346 if (d->initialTaskCount != d->task.count()) { 0347 return true; 0348 } 0349 0350 if (d->initialVariableCount != d->variable.count()) { 0351 return true; 0352 } 0353 0354 for (CTTask *ctTask : std::as_const(d->task)) { 0355 if (ctTask->dirty()) { 0356 return true; 0357 } 0358 } 0359 0360 for (CTVariable *ctVariable : std::as_const(d->variable)) { 0361 if (ctVariable->dirty()) { 0362 return true; 0363 } 0364 } 0365 0366 return false; 0367 } 0368 0369 QString CTCron::path() const 0370 { 0371 QString path; 0372 0373 for (CTVariable *ctVariable : std::as_const(d->variable)) { 0374 if (ctVariable->variable == QLatin1String("PATH")) { 0375 path = ctVariable->value; 0376 } 0377 } 0378 0379 return path; 0380 } 0381 0382 QList<CTTask *> CTCron::tasks() const 0383 { 0384 return d->task; 0385 } 0386 0387 QList<CTVariable *> CTCron::variables() const 0388 { 0389 return d->variable; 0390 } 0391 0392 void CTCron::addTask(CTTask *task) 0393 { 0394 if (isSystemCron()) { 0395 task->setSystemCrontab(true); 0396 } else { 0397 task->userLogin = d->userLogin; 0398 task->setSystemCrontab(false); 0399 } 0400 0401 qCDebug(KCM_CRON_LOG) << "Adding task" << task->comment << " user : " << task->userLogin; 0402 0403 d->task.append(task); 0404 } 0405 0406 void CTCron::addVariable(CTVariable *variable) 0407 { 0408 if (isSystemCron()) { 0409 variable->userLogin = QStringLiteral("root"); 0410 } else { 0411 variable->userLogin = d->userLogin; 0412 } 0413 0414 qCDebug(KCM_CRON_LOG) << "Adding variable" << variable->variable << " user : " << variable->userLogin; 0415 0416 d->variable.append(variable); 0417 } 0418 0419 void CTCron::modifyTask(CTTask * /*task*/) 0420 { 0421 // Nothing to do specifically when modifying a task 0422 } 0423 0424 void CTCron::modifyVariable(CTVariable * /*variable*/) 0425 { 0426 // Nothing to do specifically when modifying a variable 0427 } 0428 0429 void CTCron::removeTask(CTTask *task) 0430 { 0431 d->task.removeAll(task); 0432 } 0433 0434 void CTCron::removeVariable(CTVariable *variable) 0435 { 0436 d->variable.removeAll(variable); 0437 } 0438 0439 bool CTCron::isMultiUserCron() const 0440 { 0441 return d->multiUserCron; 0442 } 0443 0444 bool CTCron::isCurrentUserCron() const 0445 { 0446 return d->currentUserCron; 0447 } 0448 0449 bool CTCron::isSystemCron() const 0450 { 0451 return d->systemCron; 0452 } 0453 0454 QString CTCron::userLogin() const 0455 { 0456 return d->userLogin; 0457 } 0458 0459 QString CTCron::userRealName() const 0460 { 0461 return d->userRealName; 0462 }