File indexing completed on 2025-01-05 05:09:30
0001 /* 0002 SPDX-FileCopyrightText: 2010-2018 Daniel Nicoletti <dantti12@gmail.com> 0003 SPDX-FileCopyrightText: 2012 Harald Sitter <sitter@kde.org> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "KCupsConnection.h" 0009 0010 #include "KCupsPasswordDialog.h" 0011 #include "KIppRequest.h" 0012 #include "kcupslib_log.h" 0013 0014 #include <config.h> 0015 0016 #include <QByteArray> 0017 #include <QCoreApplication> 0018 #include <QDBusConnection> 0019 #include <QMutexLocker> 0020 0021 #include <KLocalizedString> 0022 0023 #define RENEW_INTERVAL 3500 0024 #define SUBSCRIPTION_DURATION 3600 0025 0026 #define DBUS_SERVER_RESTARTED "server-restarted" // ServerRestarted 0027 #define DBUS_SERVER_STARTED "server-started" // ServerStarted 0028 #define DBUS_SERVER_STOPPED "server-stopped" // ServerStopped 0029 #define DBUS_SERVER_AUDIT "server-audit" // ServerAudit 0030 0031 #define DBUS_PRINTER_RESTARTED "printer-restarted" // PrinterRestarted 0032 #define DBUS_PRINTER_SHUTDOWN "printer-shutdown" // PrinterShutdown 0033 #define DBUS_PRINTER_STOPPED "printer-stopped" // PrinterStopped 0034 #define DBUS_PRINTER_STATE_CHANGED "printer-state-changed" // PrinterStateChanged 0035 #define DBUS_PRINTER_FINISHINGS_CHANGED "printer-finishings-changed" // PrinterFinishingsChanged 0036 #define DBUS_PRINTER_MEDIA_CHANGED "printer-media-changed" // PrinterMediaChanged 0037 #define DBUS_PRINTER_ADDED "printer-added" // PrinterAdded 0038 #define DBUS_PRINTER_DELETED "printer-deleted" // PrinterDeleted 0039 #define DBUS_PRINTER_MODIFIED "printer-modified" // PrinterModified 0040 0041 #define DBUS_JOB_STATE_CHANGED "job-state-changed" // JobState 0042 #define DBUS_JOB_CREATED "job-created" // JobCreated 0043 #define DBUS_JOB_COMPLETED "job-completed" // JobCompleted 0044 #define DBUS_JOB_STOPPED "job-stopped" // JobStopped 0045 #define DBUS_JOB_CONFIG_CHANGED "job-config-changed" // JobConfigChanged 0046 #define DBUS_JOB_PROGRESS "job-progress" // JobProgress 0047 0048 Q_DECLARE_METATYPE(QList<int>) 0049 Q_DECLARE_METATYPE(QList<bool>) 0050 0051 KCupsConnection *KCupsConnection::m_instance = nullptr; 0052 static int password_retries = 0; 0053 static int total_retries = 0; 0054 static int internalErrorCount = 0; 0055 const char *password_cb(const char *prompt, http_t *http, const char *method, const char *resource, void *user_data); 0056 0057 KCupsConnection *KCupsConnection::global() 0058 { 0059 if (!m_instance) { 0060 m_instance = new KCupsConnection(qApp); 0061 } 0062 0063 return m_instance; 0064 } 0065 0066 KCupsConnection::KCupsConnection(QObject *parent) 0067 : QThread(parent) 0068 { 0069 init(); 0070 } 0071 0072 KCupsConnection::KCupsConnection(const QUrl &server, QObject *parent) 0073 : QThread(parent) 0074 , m_serverUrl(server) 0075 { 0076 qRegisterMetaType<KIppRequest>("KIppRequest"); 0077 init(); 0078 } 0079 0080 KCupsConnection::~KCupsConnection() 0081 { 0082 if (m_instance == this) { 0083 m_instance = nullptr; 0084 } 0085 m_passwordDialog->deleteLater(); 0086 0087 quit(); 0088 wait(); 0089 0090 delete m_renewTimer; 0091 delete m_subscriptionTimer; 0092 } 0093 0094 void KCupsConnection::setPasswordMainWindow(WId mainwindow) 0095 { 0096 m_passwordDialog->setMainWindow(mainwindow); 0097 } 0098 0099 void KCupsConnection::init() 0100 { 0101 // Creating the dialog before start() will make it run on the gui thread 0102 m_passwordDialog = new KCupsPasswordDialog; 0103 0104 // setup the DBus subscriptions 0105 0106 // Server related signals 0107 // ServerStarted 0108 notifierConnect(QLatin1String("ServerStarted"), this, SIGNAL(serverStarted(QString))); 0109 0110 // ServerStopped 0111 notifierConnect(QLatin1String("ServerStopped"), this, SIGNAL(serverStopped(QString))); 0112 0113 // ServerRestarted 0114 notifierConnect(QLatin1String("ServerRestarted"), this, SIGNAL(serverRestarted(QString))); 0115 0116 // ServerAudit 0117 notifierConnect(QLatin1String("ServerAudit"), this, SIGNAL(serverAudit(QString))); 0118 0119 // Printer related signals 0120 // PrinterAdded 0121 notifierConnect(QLatin1String("PrinterAdded"), this, SIGNAL(printerAdded(QString, QString, QString, uint, QString, bool))); 0122 0123 // PrinterModified 0124 notifierConnect(QLatin1String("PrinterModified"), this, SIGNAL(printerModified(QString, QString, QString, uint, QString, bool))); 0125 0126 // PrinterDeleted 0127 notifierConnect(QLatin1String("PrinterDeleted"), this, SIGNAL(printerDeleted(QString, QString, QString, uint, QString, bool))); 0128 0129 // PrinterStateChanged 0130 notifierConnect(QLatin1String("PrinterStateChanged"), this, SIGNAL(printerStateChanged(QString, QString, QString, uint, QString, bool))); 0131 0132 // PrinterStopped 0133 notifierConnect(QLatin1String("PrinterStopped"), this, SIGNAL(printerStopped(QString, QString, QString, uint, QString, bool))); 0134 0135 // PrinterShutdown 0136 notifierConnect(QLatin1String("PrinterShutdown"), this, SIGNAL(printerShutdown(QString, QString, QString, uint, QString, bool))); 0137 0138 // PrinterRestarted 0139 notifierConnect(QLatin1String("PrinterRestarted"), this, SIGNAL(printerRestarted(QString, QString, QString, uint, QString, bool))); 0140 0141 // PrinterMediaChanged 0142 notifierConnect(QLatin1String("PrinterMediaChanged"), this, SIGNAL(printerMediaChanged(QString, QString, QString, uint, QString, bool))); 0143 0144 // PrinterFinishingsChanged 0145 notifierConnect(QLatin1String("PrinterFinishingsChanged"), this, SIGNAL(PrinterFinishingsChanged(QString, QString, QString, uint, QString, bool))); 0146 0147 // Job related signals 0148 // JobState 0149 notifierConnect(QLatin1String("JobState"), this, SIGNAL(jobState(QString, QString, QString, uint, QString, bool, uint, uint, QString, QString, uint))); 0150 0151 // JobCreated 0152 notifierConnect(QLatin1String("JobCreated"), this, SIGNAL(jobCreated(QString, QString, QString, uint, QString, bool, uint, uint, QString, QString, uint))); 0153 0154 // JobStopped 0155 notifierConnect(QLatin1String("JobStopped"), this, SIGNAL(jobStopped(QString, QString, QString, uint, QString, bool, uint, uint, QString, QString, uint))); 0156 0157 // JobConfigChanged 0158 notifierConnect(QLatin1String("JobConfigChanged"), 0159 this, 0160 SIGNAL(jobConfigChanged(QString, QString, QString, uint, QString, bool, uint, uint, QString, QString, uint))); 0161 0162 // JobProgress 0163 notifierConnect(QLatin1String("JobProgress"), 0164 this, 0165 SIGNAL(jobProgress(QString, QString, QString, uint, QString, bool, uint, uint, QString, QString, uint))); 0166 0167 // JobCompleted 0168 notifierConnect(QLatin1String("JobCompleted"), 0169 this, 0170 SIGNAL(jobCompleted(QString, QString, QString, uint, QString, bool, uint, uint, QString, QString, uint))); 0171 0172 // This signal is needed since the cups registration thing 0173 // doesn't Q_EMIT printerAdded when we add a printer class 0174 // This is emitted when a printer/queue is changed 0175 QDBusConnection::systemBus().connect(QLatin1String(""), 0176 QLatin1String("/com/redhat/PrinterSpooler"), 0177 QLatin1String("com.redhat.PrinterSpooler"), 0178 QLatin1String("PrinterAdded"), 0179 this, 0180 SIGNAL(rhPrinterAdded(QString))); 0181 0182 // This signal is needed since the cups registration thing 0183 // sometimes simple stops working... don't ask me why 0184 // This is emitted when a printer/queue is changed 0185 QDBusConnection::systemBus().connect(QLatin1String(""), 0186 QLatin1String("/com/redhat/PrinterSpooler"), 0187 QLatin1String("com.redhat.PrinterSpooler"), 0188 QLatin1String("QueueChanged"), 0189 this, 0190 SIGNAL(rhQueueChanged(QString))); 0191 0192 // This signal is needed since the cups registration thing 0193 // doesn't Q_EMIT printerRemoved when we add a printer class 0194 // This is emitted when a printer/queue is changed 0195 QDBusConnection::systemBus().connect(QLatin1String(""), 0196 QLatin1String("/com/redhat/PrinterSpooler"), 0197 QLatin1String("com.redhat.PrinterSpooler"), 0198 QLatin1String("PrinterRemoved"), 0199 this, 0200 SIGNAL(rhPrinterRemoved(QString))); 0201 0202 QDBusConnection::systemBus().connect(QLatin1String(""), 0203 QLatin1String("/com/redhat/PrinterSpooler"), 0204 QLatin1String("com.redhat.PrinterSpooler"), 0205 QLatin1String("JobQueuedLocal"), 0206 this, 0207 SIGNAL(rhJobQueuedLocal(QString, uint, QString))); 0208 0209 QDBusConnection::systemBus().connect(QLatin1String(""), 0210 QLatin1String("/com/redhat/PrinterSpooler"), 0211 QLatin1String("com.redhat.PrinterSpooler"), 0212 QLatin1String("JobStartedLocal"), 0213 this, 0214 SIGNAL(rhJobStartedLocal(QString, uint, QString))); 0215 0216 // Creates the timer that will renew the DBus subscription 0217 m_renewTimer = new QTimer; 0218 m_renewTimer->setInterval(RENEW_INTERVAL * 1000); 0219 m_renewTimer->moveToThread(this); 0220 connect(m_renewTimer, &QTimer::timeout, this, static_cast<void (KCupsConnection::*)()>(&KCupsConnection::renewDBusSubscription), Qt::DirectConnection); 0221 0222 // Creates the timer to merge updates on the DBus subscription 0223 m_subscriptionTimer = new QTimer; 0224 m_subscriptionTimer->setInterval(0); 0225 m_subscriptionTimer->setSingleShot(true); 0226 m_subscriptionTimer->moveToThread(this); 0227 connect(m_subscriptionTimer, &QTimer::timeout, this, &KCupsConnection::updateSubscription, Qt::DirectConnection); 0228 0229 // Starts this thread 0230 start(); 0231 } 0232 0233 void KCupsConnection::run() 0234 { 0235 // Check if we need an special connection 0236 if (!m_serverUrl.isEmpty()) { 0237 if (m_serverUrl.port() < 0) { 0238 // TODO find out if there's a better way of hardcoding 0239 // the CUPS port 0240 m_serverUrl.setPort(631); 0241 } 0242 0243 cupsSetServer(qUtf8Printable(m_serverUrl.authority())); 0244 } 0245 0246 // This is dead cool, cups will call the thread_password_cb() 0247 // function when a password set is needed, as we passed the 0248 // password dialog pointer the functions just need to call 0249 // it on a blocking mode. 0250 cupsSetPasswordCB2(password_cb, m_passwordDialog); 0251 0252 m_inited = true; 0253 exec(); 0254 0255 // Event loop quit so cancelDBusSubscription() 0256 if (m_subscriptionId != -1) { 0257 cancelDBusSubscription(); 0258 } 0259 } 0260 0261 bool KCupsConnection::readyToStart() 0262 { 0263 if (QThread::currentThread() == this) { 0264 password_retries = 0; 0265 total_retries = 0; 0266 internalErrorCount = 0; 0267 return true; 0268 } 0269 return false; 0270 } 0271 0272 ReturnArguments KCupsConnection::request(const KIppRequest &request, ipp_tag_t groupTag) const 0273 { 0274 ReturnArguments ret; 0275 ipp_t *response = nullptr; 0276 do { 0277 ippDelete(response); 0278 response = nullptr; 0279 0280 response = request.sendIppRequest(); 0281 } while (retry(qUtf8Printable(request.resource()), request.operation())); 0282 0283 if (response && groupTag != IPP_TAG_ZERO) { 0284 ret = parseIPPVars(response, groupTag); 0285 } 0286 ippDelete(response); 0287 0288 return ret; 0289 } 0290 0291 int KCupsConnection::renewDBusSubscription(int subscriptionId, int leaseDuration, const QStringList &events) 0292 { 0293 int ret = -1; 0294 0295 ipp_op_t operation; 0296 0297 // check if we have a valid subscription ID 0298 if (subscriptionId >= 0) { 0299 // Add the "notify-events" values to the request 0300 operation = IPP_RENEW_SUBSCRIPTION; 0301 } else { 0302 operation = IPP_CREATE_PRINTER_SUBSCRIPTION; 0303 } 0304 0305 KIppRequest request(operation, QLatin1String("/")); 0306 request.addString(IPP_TAG_OPERATION, IPP_TAG_URI, KCUPS_PRINTER_URI, QLatin1String("/")); 0307 request.addInteger(IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER, KCUPS_NOTIFY_LEASE_DURATION, leaseDuration); 0308 0309 if (operation == IPP_CREATE_PRINTER_SUBSCRIPTION) { 0310 // Add the "notify-events" values to the request 0311 request.addStringList(IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD, KCUPS_NOTIFY_EVENTS, events); 0312 request.addString(IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD, KCUPS_NOTIFY_PULL_METHOD, QLatin1String("ippget")); 0313 request.addString(IPP_TAG_SUBSCRIPTION, IPP_TAG_URI, KCUPS_NOTIFY_RECIPIENT_URI, QLatin1String("dbus://")); 0314 } else { 0315 request.addInteger(IPP_TAG_OPERATION, IPP_TAG_INTEGER, KCUPS_NOTIFY_SUBSCRIPTION_ID, subscriptionId); 0316 } 0317 0318 ipp_t *response = nullptr; 0319 do { 0320 // Do the request 0321 response = request.sendIppRequest(); 0322 } while (retry("/", operation)); 0323 0324 #if !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6) 0325 if (response && ippGetStatusCode(response) == IPP_OK) { 0326 #else 0327 if (response && response->request.status.status_code == IPP_OK) { 0328 #endif // !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6) 0329 ipp_attribute_t *attr; 0330 if (subscriptionId >= 0) { 0331 // Request was ok, just return the current subscription 0332 ret = subscriptionId; 0333 } else if ((attr = ippFindAttribute(response, "notify-subscription-id", IPP_TAG_INTEGER)) == nullptr) { 0334 qCWarning(LIBKCUPS) << "No notify-subscription-id in response!"; 0335 ret = -1; 0336 } else { 0337 #if !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6) 0338 ret = ippGetInteger(attr, 0); 0339 } 0340 } else if (subscriptionId >= 0 && response && ippGetStatusCode(response) == IPP_NOT_FOUND) { 0341 qCDebug(LIBKCUPS) << "Subscription not found"; 0342 // When the subscription is not found try to get a new one 0343 return renewDBusSubscription(-1, leaseDuration, events); 0344 #else 0345 ret = attr->values[0].integer; 0346 } 0347 } else if (subscriptionId >= 0 && response && response->request.status.status_code == IPP_NOT_FOUND) { 0348 qCDebug(LIBKCUPS) << "Subscription not found"; 0349 // When the subscription is not found try to get a new one 0350 return renewDBusSubscription(-1, leaseDuration, events); 0351 #endif // !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6) 0352 } else { 0353 qCDebug(LIBKCUPS) << "Request failed" << cupsLastError() << httpGetStatus(CUPS_HTTP_DEFAULT); 0354 // When the server stops/restarts we will have some error so ignore it 0355 ret = subscriptionId; 0356 } 0357 0358 ippDelete(response); 0359 0360 return ret; 0361 } 0362 0363 void KCupsConnection::notifierConnect(const QString &signal, QObject *receiver, const char *slot) 0364 { 0365 QDBusConnection systemBus = QDBusConnection::systemBus(); 0366 systemBus.connect(QString(), QStringLiteral("/org/cups/cupsd/Notifier"), QStringLiteral("org.cups.cupsd.Notifier"), signal, receiver, slot); 0367 } 0368 0369 void KCupsConnection::connectNotify(const QMetaMethod &signal) 0370 { 0371 QMutexLocker locker(&m_mutex); 0372 QString event = eventForSignal(signal); 0373 if (!event.isNull()) { 0374 m_connectedEvents << event; 0375 QMetaObject::invokeMethod(m_subscriptionTimer, "start", Qt::QueuedConnection); 0376 } 0377 } 0378 0379 void KCupsConnection::disconnectNotify(const QMetaMethod &signal) 0380 { 0381 QMutexLocker locker(&m_mutex); 0382 QString event = eventForSignal(signal); 0383 if (!event.isNull()) { 0384 m_connectedEvents.removeOne(event); 0385 QMetaObject::invokeMethod(m_subscriptionTimer, "start", Qt::QueuedConnection); 0386 } 0387 } 0388 0389 QString KCupsConnection::eventForSignal(const QMetaMethod &signal) const 0390 { 0391 // Server signals 0392 if (signal == QMetaMethod::fromSignal(&KCupsConnection::serverAudit)) { 0393 return QStringLiteral(DBUS_SERVER_AUDIT); 0394 } 0395 if (signal == QMetaMethod::fromSignal(&KCupsConnection::serverStarted)) { 0396 return QStringLiteral(DBUS_SERVER_STARTED); 0397 } 0398 if (signal == QMetaMethod::fromSignal(&KCupsConnection::serverStopped)) { 0399 return QStringLiteral(DBUS_SERVER_STOPPED); 0400 } 0401 if (signal == QMetaMethod::fromSignal(&KCupsConnection::serverRestarted)) { 0402 return QStringLiteral(DBUS_SERVER_RESTARTED); 0403 } 0404 0405 // Printer signals 0406 if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerAdded)) { 0407 return QStringLiteral(DBUS_PRINTER_ADDED); 0408 } 0409 if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerDeleted)) { 0410 return QStringLiteral(DBUS_PRINTER_DELETED); 0411 } 0412 if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerFinishingsChanged)) { 0413 return QStringLiteral(DBUS_PRINTER_FINISHINGS_CHANGED); 0414 } 0415 if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerMediaChanged)) { 0416 return QStringLiteral(DBUS_PRINTER_MEDIA_CHANGED); 0417 } 0418 if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerModified)) { 0419 return QStringLiteral(DBUS_PRINTER_MODIFIED); 0420 } 0421 if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerRestarted)) { 0422 return QStringLiteral(DBUS_PRINTER_RESTARTED); 0423 } 0424 if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerShutdown)) { 0425 return QStringLiteral(DBUS_PRINTER_SHUTDOWN); 0426 } 0427 if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerStateChanged)) { 0428 return QStringLiteral(DBUS_PRINTER_STATE_CHANGED); 0429 } 0430 if (signal == QMetaMethod::fromSignal(&KCupsConnection::printerStopped)) { 0431 return QStringLiteral(DBUS_PRINTER_STOPPED); 0432 } 0433 0434 // job signals 0435 if (signal == QMetaMethod::fromSignal(&KCupsConnection::jobCompleted)) { 0436 return QStringLiteral(DBUS_JOB_COMPLETED); 0437 } 0438 if (signal == QMetaMethod::fromSignal(&KCupsConnection::jobConfigChanged)) { 0439 return QStringLiteral(DBUS_JOB_CONFIG_CHANGED); 0440 } 0441 if (signal == QMetaMethod::fromSignal(&KCupsConnection::jobCreated)) { 0442 return QStringLiteral(DBUS_JOB_CREATED); 0443 } 0444 if (signal == QMetaMethod::fromSignal(&KCupsConnection::jobProgress)) { 0445 return QStringLiteral(DBUS_JOB_PROGRESS); 0446 } 0447 if (signal == QMetaMethod::fromSignal(&KCupsConnection::jobState)) { 0448 return QStringLiteral(DBUS_JOB_STATE_CHANGED); 0449 } 0450 if (signal == QMetaMethod::fromSignal(&KCupsConnection::jobStopped)) { 0451 return QStringLiteral(DBUS_JOB_STOPPED); 0452 } 0453 0454 // No registered event signal matched 0455 return QString(); 0456 } 0457 0458 void KCupsConnection::updateSubscription() 0459 { 0460 QMutexLocker locker(&m_mutex); 0461 // Build the current list 0462 QStringList currentEvents = m_connectedEvents; 0463 currentEvents.sort(); 0464 currentEvents.removeDuplicates(); 0465 0466 // Check if the requested events are already being asked 0467 if (m_requestedDBusEvents != currentEvents) { 0468 m_requestedDBusEvents = currentEvents; 0469 0470 // If we already have a subscription lets cancel 0471 // and create a new one 0472 if (m_subscriptionId >= 0) { 0473 cancelDBusSubscription(); 0474 } 0475 0476 // Calculates the new events 0477 renewDBusSubscription(); 0478 } 0479 } 0480 0481 void KCupsConnection::renewDBusSubscription() 0482 { 0483 // check if we have a valid subscription ID 0484 if (m_subscriptionId >= 0) { 0485 m_subscriptionId = renewDBusSubscription(m_subscriptionId, SUBSCRIPTION_DURATION); 0486 } 0487 0488 // The above request might fail if the subscription was cancelled 0489 if (m_subscriptionId < 0) { 0490 if (m_requestedDBusEvents.isEmpty()) { 0491 m_renewTimer->stop(); 0492 } else { 0493 m_subscriptionId = renewDBusSubscription(m_subscriptionId, SUBSCRIPTION_DURATION, m_requestedDBusEvents); 0494 m_renewTimer->start(); 0495 } 0496 } 0497 } 0498 0499 void KCupsConnection::cancelDBusSubscription() 0500 { 0501 KIppRequest request(IPP_CANCEL_SUBSCRIPTION, QLatin1String("/")); 0502 request.addString(IPP_TAG_OPERATION, IPP_TAG_URI, KCUPS_PRINTER_URI, QLatin1String("/")); 0503 request.addInteger(IPP_TAG_OPERATION, IPP_TAG_INTEGER, KCUPS_NOTIFY_SUBSCRIPTION_ID, m_subscriptionId); 0504 0505 do { 0506 // Do the request 0507 ippDelete(request.sendIppRequest()); 0508 } while (retry(qUtf8Printable(request.resource()), request.operation())); 0509 0510 // Reset the subscription id 0511 m_subscriptionId = -1; 0512 } 0513 0514 ReturnArguments KCupsConnection::parseIPPVars(ipp_t *response, ipp_tag_t group_tag) 0515 { 0516 ipp_attribute_t *attr; 0517 ReturnArguments ret; 0518 0519 #if !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6) 0520 QVariantMap destAttributes; 0521 for (attr = ippFirstAttribute(response); attr != nullptr; attr = ippNextAttribute(response)) { 0522 // We hit an attribute separator 0523 if (ippGetName(attr) == nullptr) { 0524 ret << destAttributes; 0525 destAttributes.clear(); 0526 continue; 0527 } 0528 0529 // Skip leading attributes until we hit a group which can be a printer, job... 0530 if (ippGetGroupTag(attr) != group_tag 0531 || (ippGetValueTag(attr) != IPP_TAG_INTEGER && ippGetValueTag(attr) != IPP_TAG_ENUM && ippGetValueTag(attr) != IPP_TAG_BOOLEAN 0532 && ippGetValueTag(attr) != IPP_TAG_TEXT && ippGetValueTag(attr) != IPP_TAG_TEXTLANG && ippGetValueTag(attr) != IPP_TAG_LANGUAGE 0533 && ippGetValueTag(attr) != IPP_TAG_NAME && ippGetValueTag(attr) != IPP_TAG_NAMELANG && ippGetValueTag(attr) != IPP_TAG_KEYWORD 0534 && ippGetValueTag(attr) != IPP_TAG_RANGE && ippGetValueTag(attr) != IPP_TAG_URI)) { 0535 continue; 0536 } 0537 0538 // Add a printer description attribute... 0539 destAttributes[QString::fromUtf8(ippGetName(attr))] = ippAttrToVariant(attr); 0540 } 0541 0542 if (!destAttributes.isEmpty()) { 0543 ret << destAttributes; 0544 } 0545 #else 0546 for (attr = response->attrs; attr != nullptr; attr = attr->next) { 0547 /* 0548 * Skip leading attributes until we hit a group which can be a printer, job... 0549 */ 0550 while (attr && attr->group_tag != group_tag) { 0551 attr = attr->next; 0552 } 0553 0554 if (attr == nullptr) { 0555 break; 0556 } 0557 0558 /* 0559 * Pull the needed attributes from this printer... 0560 */ 0561 QVariantMap destAttributes; 0562 for (; attr && attr->group_tag == group_tag; attr = attr->next) { 0563 if (attr->value_tag != IPP_TAG_INTEGER && attr->value_tag != IPP_TAG_ENUM && attr->value_tag != IPP_TAG_BOOLEAN && attr->value_tag != IPP_TAG_TEXT 0564 && attr->value_tag != IPP_TAG_TEXTLANG && attr->value_tag != IPP_TAG_LANGUAGE && attr->value_tag != IPP_TAG_NAME 0565 && attr->value_tag != IPP_TAG_NAMELANG && attr->value_tag != IPP_TAG_KEYWORD && attr->value_tag != IPP_TAG_RANGE 0566 && attr->value_tag != IPP_TAG_URI) { 0567 continue; 0568 } 0569 0570 /* 0571 * Add a printer description attribute... 0572 */ 0573 destAttributes[QString::fromUtf8(attr->name)] = ippAttrToVariant(attr); 0574 } 0575 0576 ret << destAttributes; 0577 0578 if (attr == nullptr) { 0579 break; 0580 } 0581 } 0582 #endif // !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6) 0583 0584 return ret; 0585 } 0586 0587 QVariant KCupsConnection::ippAttrToVariant(ipp_attribute_t *attr) 0588 { 0589 QVariant ret; 0590 #if !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6) 0591 switch (ippGetValueTag(attr)) { 0592 case IPP_TAG_INTEGER: 0593 case IPP_TAG_ENUM: 0594 if (ippGetCount(attr) == 1) { 0595 ret = ippGetInteger(attr, 0); 0596 } else { 0597 QList<int> values; 0598 for (int i = 0; i < ippGetCount(attr); ++i) { 0599 values << ippGetInteger(attr, i); 0600 } 0601 ret = QVariant::fromValue(values); 0602 } 0603 break; 0604 case IPP_TAG_BOOLEAN: 0605 if (ippGetCount(attr) == 1) { 0606 ret = ippGetBoolean(attr, 0); 0607 } else { 0608 QList<bool> values; 0609 for (int i = 0; i < ippGetCount(attr); ++i) { 0610 values << ippGetBoolean(attr, i); 0611 } 0612 ret = QVariant::fromValue(values); 0613 } 0614 break; 0615 case IPP_TAG_RANGE: { 0616 QVariantList values; 0617 for (int i = 0; i < ippGetCount(attr); ++i) { 0618 int rangeUpper; 0619 values << ippGetRange(attr, i, &rangeUpper); 0620 values << rangeUpper; 0621 } 0622 ret = values; 0623 } break; 0624 default: 0625 if (ippGetCount(attr) == 1) { 0626 ret = QString::fromUtf8(ippGetString(attr, 0, nullptr)); 0627 } else { 0628 QStringList values; 0629 for (int i = 0; i < ippGetCount(attr); ++i) { 0630 values << QString::fromUtf8(ippGetString(attr, i, nullptr)); 0631 } 0632 ret = values; 0633 } 0634 } 0635 #else 0636 switch (attr->value_tag) { 0637 case IPP_TAG_INTEGER: 0638 case IPP_TAG_ENUM: 0639 if (attr->num_values == 1) { 0640 ret = attr->values[0].integer; 0641 } else { 0642 QList<int> values; 0643 for (int i = 0; i < attr->num_values; ++i) { 0644 values << attr->values[i].integer; 0645 } 0646 ret = QVariant::fromValue(values); 0647 } 0648 break; 0649 case IPP_TAG_BOOLEAN: 0650 if (attr->num_values == 1) { 0651 ret = static_cast<bool>(attr->values[0].integer); 0652 } else { 0653 QList<bool> values; 0654 for (int i = 0; i < attr->num_values; ++i) { 0655 values << static_cast<bool>(attr->values[i].integer); 0656 } 0657 ret = QVariant::fromValue(values); 0658 } 0659 break; 0660 case IPP_TAG_RANGE: { 0661 QVariantList values; 0662 for (int i = 0; i < attr->num_values; ++i) { 0663 values << attr->values[i].range.lower; 0664 values << attr->values[i].range.upper; 0665 } 0666 ret = values; 0667 } break; 0668 default: 0669 if (attr->num_values == 1) { 0670 ret = QString::fromUtf8(attr->values[0].string.text); 0671 } else { 0672 QStringList values; 0673 for (int i = 0; i < attr->num_values; ++i) { 0674 values << QString::fromUtf8(attr->values[i].string.text); 0675 } 0676 ret = values; 0677 } 0678 } 0679 #endif // !(CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6) 0680 return ret; 0681 } 0682 0683 bool KCupsConnection::retry(const char *resource, int operation) const 0684 { 0685 ipp_status_t status = cupsLastError(); 0686 0687 if (operation != -1) { 0688 qCDebug(LIBKCUPS) << ippOpString(static_cast<ipp_op_t>(operation)) << "last error:" << status << cupsLastErrorString(); 0689 } else { 0690 qCDebug(LIBKCUPS) << operation << "last error:" << status << cupsLastErrorString(); 0691 } 0692 0693 // When CUPS process stops our connection 0694 // with it fails and has to be re-established 0695 if (status == IPP_INTERNAL_ERROR) { 0696 // Deleting this connection thread forces it 0697 // to create a new CUPS connection 0698 qCWarning(LIBKCUPS) << "IPP_INTERNAL_ERROR: clearing cookies and reconnecting"; 0699 0700 // Reconnect to CUPS 0701 int cancel = 0; 0702 if (httpReconnect2(CUPS_HTTP_DEFAULT, 10000, &cancel)) { 0703 qCWarning(LIBKCUPS) << "Failed to reconnect" << cupsLastErrorString(); 0704 } 0705 0706 // Try the request again 0707 return ++internalErrorCount < 3; 0708 } 0709 0710 total_retries++; 0711 0712 if (total_retries > (password_retries + 3)) { 0713 // Something is wrong. 0714 // This will happen if the password_cb function is not called, 0715 // which will for example be the case if the server has 0716 // an IP blacklist and thus always return 403. 0717 // In this case, there is nothing we can do. 0718 return false; 0719 } 0720 0721 bool forceAuth = false; 0722 // If our user is forbidden to perform the 0723 // task we try again using the root user 0724 // ONLY if it was the first time 0725 if (status == IPP_FORBIDDEN && password_retries == 0) { 0726 // Pretend to be the root user 0727 // Sometimes setting this just works 0728 cupsSetUser("root"); 0729 0730 // force authentication 0731 forceAuth = true; 0732 } 0733 0734 if (status == IPP_NOT_AUTHORIZED || status == IPP_NOT_AUTHENTICATED) { 0735 if (password_retries > 3 || password_retries == -1) { 0736 // the authentication failed 3 times 0737 // OR the dialog was canceled (-1) 0738 // reset to 0 and quit the do-while loop 0739 password_retries = 0; 0740 total_retries = 0; 0741 return false; 0742 } 0743 0744 // force authentication 0745 forceAuth = true; 0746 } 0747 0748 if (forceAuth) { 0749 // force authentication 0750 int ret = cupsDoAuthentication(CUPS_HTTP_DEFAULT, "POST", resource); 0751 qCWarning(LIBKCUPS) << "cupsDoAuthentication, return:" << ret << "password_retries:" << password_retries; 0752 0753 // If the authentication was successful 0754 // sometimes just trying to be root works 0755 return ret != 0; 0756 } 0757 0758 // the action was not forbidden 0759 return false; 0760 } 0761 0762 const char *password_cb(const char *prompt, http_t *http, const char *method, const char *resource, void *user_data) 0763 { 0764 Q_UNUSED(http) 0765 Q_UNUSED(method) 0766 Q_UNUSED(resource) 0767 0768 if (++password_retries > 3) { 0769 // cancel the authentication 0770 cupsSetUser(nullptr); 0771 return nullptr; 0772 } 0773 0774 auto passwordDialog = static_cast<KCupsPasswordDialog *>(user_data); 0775 bool wrongPassword = password_retries > 1; 0776 0777 // use prompt text from CUPS callback for dialog 0778 passwordDialog->setPromptText(i18n("A CUPS connection requires authentication: \"%1\"", QString::fromUtf8(prompt))); 0779 0780 // This will block this thread until exec is not finished 0781 qCDebug(LIBKCUPS) << password_retries; 0782 QMetaObject::invokeMethod(passwordDialog, "exec", Qt::BlockingQueuedConnection, Q_ARG(QString, QString::fromUtf8(cupsUser())), Q_ARG(bool, wrongPassword)); 0783 qCDebug(LIBKCUPS) << passwordDialog->accepted(); 0784 0785 // The password dialog has just returned check the result 0786 // method that returns QDialog enums 0787 if (passwordDialog->accepted()) { 0788 cupsSetUser(qUtf8Printable(passwordDialog->username())); 0789 return qUtf8Printable(passwordDialog->password()); 0790 } else { 0791 // the dialog was canceled 0792 password_retries = -1; 0793 cupsSetUser(nullptr); 0794 return nullptr; 0795 } 0796 } 0797 0798 #include "moc_KCupsConnection.cpp"