Dánské ministerstvo pro digitální záležitosti má v plánu přejít na Linux a LibreOffice [It's FOSS News].
V úterý Google vydal Android 16. Zdrojové kódy jsou k dispozici na AOSP (Android Open Source Project). Chybí (zatím?) ale zdrojové kódy specifické pro telefony Pixel od Googlu. Projekty jako CalyxOS a GrapheneOS řeší, jak tyto telefony nadále podporovat. Nejistá je podpora budoucích Pixelů. Souvisí to s hrozícím rozdělením Googlu (Google, Chrome, Android)?
Byla vydána (𝕏) květnová aktualizace aneb nová verze 1.101 editoru zdrojových kódů Visual Studio Code (Wikipedie). Přehled novinek i s náhledy a videi v poznámkách k vydání. Ve verzi 1.101 vyjde také VSCodium, tj. komunitní sestavení Visual Studia Code bez telemetrie a licenčních podmínek Microsoftu.
V Brně na FIT VUT probíhá třídenní open source komunitní konference DevConf.CZ 2025. Vstup je zdarma, nutná je ale registrace. Na programu je celá řada zajímavých přednášek, lightning talků, meetupů a workshopů. Přednášky lze sledovat i online na YouTube kanálu konference. Aktuální dění lze sledovat na Matrixu, 𝕏 nebo Mastodonu.
Vyloučení technologií, které by mohly představovat bezpečnostní riziko pro stát, má umožnit zákon o kybernetické bezpečnosti, který včera Senát schválil spolu s novelami navazujících právních předpisů. Norma, kterou nyní dostane k podpisu prezident, počítá rovněž s prověřováním dodavatelů technologií pro stát. Normy mají nabýt účinnosti od třetího měsíce po jejich vyhlášení ve Sbírce zákonů.
Open source platforma Home Assistant (Demo, GitHub, Wikipedie) pro monitorování a řízení inteligentní domácnosti byla vydána v nové verzi 2025.6.
Po Red Hat Enterprise Linuxu a AlmaLinuxu byl v nové stabilní verzi 10.0 vydán také Rocky Linux. Přehled novinek v poznámkách k vydání.
Bylo vydáno Eclipse IDE 2025-06 aneb Eclipse 4.36. Představení novinek tohoto integrovaného vývojového prostředí také na YouTube.
Americká filmová studia Walt Disney a Universal Pictures podala žalobu na provozovatele populárního generátoru obrázků pomocí umělé inteligence (AI) Midjourney. Zdůvodňují to údajným porušováním autorských práv. V žalobě podané u federálního soudu v Los Angeles označují firmu za „bezednou jámu plagiátorství“, neboť podle nich bez povolení bezostyšně kopíruje a šíří postavy z filmů jako Star Wars, Ledové království nebo Já, padouch, aniž by do nich investovala jediný cent.
Ultra Ethernet Consortium (UEC), jehož cílem je optimalizace a další vývoj Ethernetu s důrazem na rostoucí síťové požadavky AI a HPC, vydalo specifikaci Ultra Ethernet 1.0 (pdf, YouTube).
Tento díl seriálu úzce souvisí s článkem Grafické programy v Qt 4 – 8 (TCP klient), jelikož je o server, jehož klienta jsem napsal s grafickým rozhraním. Základní informace o TCP síťování, socketech v Qt a ukázkovém programu se dočtete tam, zatímco tento článek popisuje čistě serverovou část, takže doporučuji začít čtení odkazovaným článkem.
Teď, když chápeme komunikaci přes QTcpSocket
, můžeme se vrhnout na serverovou část. Základem je třída QTcpServer
. Hned po vytvoření instance je vhodné nastavit nějaké základní parametry serveru, například takto:
QTcpServer* server = new QTcpServer(this); // explicitně zakážeme použití proxy server->setProxy(QNetworkProxy::NoProxy); // nastavíme max. počet spojení server->setMaxPendingConnections(50);
Ve většině případů je dobré zpracovat signál newConnection()
, kterým nás server informuje o novém příchozím spojení. Slot, který na tento signál napojíme, bude chtít nejspíš získat socket tohoto nového spojení. K tomu využijeme metodu nextPendingConnection()
, a to následovně:
// získáme socket QTcpSocket* socket = server->nextPendingConnection(); // dobré je hned ověřit, zda ještě existuje (tj. zda metoda nevrátila 0) if(!socket) return;
Existují dvě alternativy k tomuto přístupu zpracování nových příchozích spojení a jejich použití záleží na tom, čeho přesně chcete dosáhnout. Pokud potřebujete provádět něco velmi specifického a vyžadujete vysokou flexibilitu, potom si celou událost můžete zpracovat sami, a to tak, že si vytvoříte vlastní třídu založenou na QTcpServer
a reimplementujete její chráněnou virtuální metodu incomingConnection(int socketDescriptor)
. Integer, který dostanete jako argument, je popisovač (descriptor) socketu a pokud nepoužíváte QNetworkProxy
, lze s ním pracovat pomocí nativních socketových funkcí.
Druhý alternativní způsob je podstatně jednodušší. Nejdřív je třeba spustit server (viz níže) a potom lze použít blokující metodu waitForNewConnection()
, které lze zadat čas v milisekundách (jak dlouho má čekat na příchozí připojení) a ukazatel na bool
, do kterého se uloží informace o tom, zda došlo k vypršení času (tedy false
znamená, že máte nové spojení).
Když máme připravené zpracování nových připojení, můžeme spustit server, tedy naslouchání na určité adrese (nebo více adresách) a portu. Třeba takto:
QString ip("127.0.0.1"); QHostAddress addr; if(!addr.setAddress(ip)) { // zadaná IP adresa není ve správném formátu return 1; } // port=0 zajistí automatický výběr portu server->listen(addr, 0);
Ještě než server spustíte, je třeba vytvořit mu konfigurační soubor, kam zapíšete platná uživatelská jména (bez mezer) a přiřadíte jim hesla (resp. jejich SHA1 hash). Chcete-li vytvořit uživatele user s heslem pass, tak si nejdřív zjistěte SHA1 hash řetězce „pass“:
$ echo -n "pass" | sha1sum 9d4e1e23bd5b727046a9e3b4b7db57bd8d6ee684
Když máte hash, vytvořte si konfigurační soubor v textovém editoru (na UNIXech ~/.config/watzke.cz/todoserver.conf
) a přidejte do něj sekci [logins]
a vaším uživatelem.
[logins] user=9d4e1e23bd5b727046a9e3b4b7db57bd8d6ee684
Server komunikuje jednoduchým protokolem, který je zdokumentovaný ve výše odkazovaném článku. Obsahuje jednoduchou obranu před bruteforce přihlašováním – povolí maximálně 10 chybných přihlášení za minutu. Jde spíše jen o ukázku implementace takové věci, spíš než cokoliv jiného, nicméně crackera by to mělo zpomalit :-)
Samozřejmě teď lze namítnout, že zmiňovat crackery je výsměch, když nepoužívám SSL sockety (nebo jiný způsob šifrování), ale o to mi v této ukázce nejde. Nicméně, když najdete bezpečnostní chybu, která s tímto nesouvisí, určitě se ozvěte v diskusi. Ještě dodám, že o chybějícím limitu délky jednotlivých záznamů vím, může tam být (teoreticky) libovolně dlouhý řetězec, i když můj klient umožní max. 32767 (2^15-1) znaků. Sám jsem několik bezpečnostních chyb odhalil a opravil při testování. Zvlášť při psaní síťového serveru, kde se řeší přihlašování, je třeba korektně ošetřovat nejrůznější výjimky. Nikdy nespoléhejte pouze na kontroly ve vašem klientovi.
tcpserver.h
: API.
#ifndef TCPSERVER_H #define TCPSERVER_H #include <QObject> #include <QTextStream> #include <QList> #include <QPair> #include <QHostAddress> #include <QHash> #include <QSettings> class QTcpServer; class QTcpSocket; class TcpServer : public QObject { Q_OBJECT public: TcpServer(QHostAddress addr = QHostAddress(QHostAddress::Any), int port = 2266); private: QTcpServer* server; QTextStream cerr; QHash<QTcpSocket*, QString> logins; QHash<QString, int> log; QSettings settings; void login(QTcpSocket* socket, const QString& user, const QString& pass, bool force = false); QString getKey(QString user, int index); void sortKeys(QString user); void listItems(QTcpSocket* socket); void addItem(QTcpSocket* socket, QString item); void removeItems(QTcpSocket* socket, QList<QByteArray> keys); void socketWrite(QTcpSocket* socket, QByteArray data); private slots: void purgeLog(); void handleNewConnection(); void reply(); void gotDisconnected(); }; #endif // TCPSERVER_H
tcpserver.cpp
:
#include "tcpserver.h" #include <QtNetwork> #include <cstdio> TcpServer::TcpServer(QHostAddress addr, int port) : cerr(stderr, QIODevice::WriteOnly) // poslouží pro výpis na std. chybový výstup, // podobně jako std::cerr v C++ { // vytvoříme TCP server server = new QTcpServer(this); // zajistíme zpracování požadavků vlastním slotem connect(server, SIGNAL(newConnection()), this, SLOT(handleNewConnection())); // každou minutu pročistíme záznam přihlašování QTimer::singleShot(60*1000, this, SLOT(purgeLog())); // explicitně zakážeme použití proxy server->setProxy(QNetworkProxy::NoProxy); // nastavíme max. počet spojení server->setMaxPendingConnections(50); // nastavíme IP a port pro naslouchání bool listening = server->listen(addr, port); // uložíme si IP adresu s portem do řetězce ve tvaru IP:port (jen pro výpis) QString ipport = addr.toString() + ":" + QString::number(port); // pokud se TCP server nepodařilo spustit na dané IP a portu if(not listening) { // vypíšeme chybovou hlášku cerr << tr("couldn't bind to %1, quitting…").arg(ipport) + "\n"; // zajistíme okamžitý zápis na stderr cerr.flush(); // tímto zajistíme ukončení programu po provedení konstruktoru QTimer::singleShot(0, qApp, SLOT(quit())); } else { cerr << tr("listening on %1").arg(ipport) + "\n"; cerr.flush(); } } // (ihned) zapíše data do socketu void TcpServer::socketWrite(QTcpSocket* socket, QByteArray data) { // zapíše data socket->write(data); // vynutí okamžitý zápis čekajících dat socket->flush(); } // vyprázdní záznam o přihlašování void TcpServer::purgeLog() { log.clear(); } // zpracuje nové příchozí spojení void TcpServer::handleNewConnection() { // získáme socket QTcpSocket* socket = server->nextPendingConnection(); if(!socket) return; cerr << tr("incoming connection from ") << socket->peerAddress().toString() << "\n"; cerr.flush(); // komunikace s klientem connect(socket, SIGNAL(readyRead()), SLOT(reply())); // šetříme paměť a zajistíme smazání socketu po jeho odpojení connect(socket, SIGNAL(disconnected()), SLOT(gotDisconnected())); // požádáme klienta, aby se přihlásil this->socketWrite(socket, "LOGIN"); } // vyřizuje odpověď na příchozí požadavky void TcpServer::reply() { // získáme socket (skrz objekt, který vyslal signál) QTcpSocket* socket = qobject_cast<QTcpSocket*>(this->sender()); if(log.value(socket->peerAddress().toString(), 0) >= 10) this->socketWrite(socket, "TOOMANYWRONGLOGINS"); // požadavek QByteArray rawdata = socket->readAll(); // argumenty požadavku QList<QByteArray> args = rawdata.split(' '); // samotný příkaz QString command = args.takeFirst(); // přihlašovací/uživatelské jméno QString user = logins.value(socket); qDebug() << rawdata; if(command == "LOGIN" || command == "FORCELOGIN") { // klient se chce přihlásit // klient je již přihlášen (na tomto socketu) if(!user.isEmpty()) { this->socketWrite(socket, "ALREADYLOGGED"); return; } // vynucené přihlášení? bool force = (command == "FORCELOGIN"); // heslo QString pass; // špatný počet argumentů nebudeme tolerovat if(args.size() == 2) { user = args.at(0); pass = args.at(1); } // zahájíme přihlašování this->login(socket, user, pass, force); return; } if(user.isEmpty()) { socket->disconnectFromHost(); QString ip(socket->peerAddress().toString()); cerr << tr("a client (IP=%1) has requested something without being logged in,").arg(ip) << "\n" << tr("which is suspicious – closing the connection.") << "\n"; cerr.flush(); } if(command == "LIST") { // klient žádá o seznam svých TODO položek this->listItems(socket); } else if(command == "ADD") { // klient si přeje přidat novou TODO položku this->addItem(socket, rawdata.right( rawdata.size()-command.size()-1 )); } else if(command == "REMOVE") { // klient si přeje odstranit položky this->removeItems(socket, rawdata.right( rawdata.size()-command.size()-1 ) .split(' ')); } else { // klient poslal neplatný příkaz this->socketWrite(socket, "INVALIDCMD"); cerr << "invalid command: " << command << "\n"; cerr.flush(); return; } } // přihlásí uživatele void TcpServer::login(QTcpSocket* socket, const QString& user, const QString& pass, bool force) { //qDebug() << "user" << user << "is trying to log in with pass" << pass; // načteme správné heslo QString realpass = settings.value(QString("logins/%1").arg(user)).toString(); // a porovnáme jej se zadaným (navíc se ujistíme, zda uživatel vůbec existuje, // abychom ošetřili přihlášení bez hesla) if(!user.isEmpty() && !realpass.isEmpty() && pass == realpass) { // ok // zjistíme, zda je uživatel již přihlášen odjinud bool alreadyLogged = logins.values().contains(user); // pokud ano a nejde o vynucený login… if(alreadyLogged and not force) { // informujeme o tom klienta this->socketWrite(socket, "ALREADYLOGGED"); return; } // pokud ano a jde o vynucený login… if(alreadyLogged) { // získáme seznam všech socketů QList<QTcpSocket*> keys = logins.keys(); // a odpojíme ten, který je přihlášený pod daným jménem for(int i=0; i<keys.size(); i++) if(logins.value(keys.at(i)) == user) { QTcpSocket* oldSocket = keys.at(i); oldSocket->disconnectFromHost(); logins.remove(oldSocket); break; } } // pošleme odpověď this->socketWrite(socket, "LOGGED"); // přiřadíme si socket k uživatelskému jménu do seznamu přihlášení logins.insert(socket, user); cerr << tr("user %1 successfully logged in").arg(user) << "\n"; cerr.flush(); } else { // špatný login this->socketWrite(socket, "WRONGLOGIN"); // odpojíme klienta socket->disconnectFromHost(); // zaznamenáme chybný login QString ip(socket->peerAddress().toString()); log.insert(ip, log.value(ip, 0)+1); cerr << tr("user %1 tried to log in with a wrong password, " "closing the connection").arg(user) << "\n"; cerr.flush(); } } // opraví klíče (indexy) položek v nastavení tak, aby šly po sobě (000 až 999) void TcpServer::sortKeys(QString user) { // získáme seznam klíčů QStringList keys = settings.allKeys(); // seznam je skutečně plný, nedá se nic dělat if(keys.size() > 999) return; cerr << tr("re-sorting %1's todo list").arg(user) + "\n"; cerr.flush(); // získáme seznam položek QStringList items; foreach(QString key, keys) items << settings.value(key).toString(); //qDebug() << "items:" << items; // odstraníme všechny položky settings.remove(""); // a přidáme je zpátky se správnými klíči for(int key = 0; key < keys.size(); key++) settings.setValue(this->getKey(user, key), items.at(key)); settings.sync(); } // vrátí klíč (index), pod kterým se položka uloží QString TcpServer::getKey(QString user, int index) { QString key = QString::number(index); if(index < 10) // pokud je index menší než 10 key.prepend("00"); // přidáme dvě nuly (5 -> 005), atd. else if(index < 100) key.prepend("0"); else if(index < 1000) Q_UNUSED(key) else { // index je vyšší než 999, zkusíme to ještě zachránit // zkusíme opravit indexy this->sortKeys(user); // načteme poslední index a přičteme k němu 1 int nIndex = settings.allKeys().last().toInt() + 1; // pokud je menší než 1000, povedlo se… if(nIndex < 1000) key = this->getKey(user, nIndex); else // prázdný klíč = plný seznam key.clear(); } return key; } // odešle klientovi seznam jeho položek void TcpServer::listItems(QTcpSocket* socket) { // načteme si uživatelské jméno QString user = logins.value(socket); // odpověď QByteArray reply; settings.beginGroup("todolist_" + user); QStringList keys = settings.allKeys(); // přidáme do odpovědi seznam všech položek foreach(QString key, keys) reply += settings.value(key).toString() + '\n'; settings.endGroup(); // ořízneme poslední \n reply.chop(1); // přidáme odpovídající příkaz if(reply.isEmpty()) reply = "LISTEMPTY"; else reply.prepend("LISTING "); //qDebug() << reply; // a odpovíme this->socketWrite(socket, reply); } // přidá položku do seznamu uživatele void TcpServer::addItem(QTcpSocket* socket, QString item) { QString user = logins.value(socket); settings.beginGroup("todolist_" + user); // zjistíme, pod kterým klíčem/indexem položku uložit int index; if(settings.allKeys().size() > 0) // k poslednímu uloženému indexu přičteme 1 index = settings.allKeys().last().toInt() + 1; else // první index bude 0 index = 0; //qDebug() << "index:" << index; // získáme klíč ve správném formátu QString key = this->getKey(user, index); // prázdný klíč = plný seznam if(key.isEmpty()) { // informujeme klienta, že má plný seznam TODO this->socketWrite(socket, "LISTFULL"); // nezapomeneme ukončit skupinu! settings.endGroup(); cerr << tr("user %1 has got a full todo list").arg(user) << "\n"; cerr.flush(); return; } // uložíme položku settings.setValue(key, item); // sestrojíme odpověď QByteArray reply = "ADDED "; reply += settings.value(key).toString(); settings.endGroup(); // odešleme odpověď this->socketWrite(socket, reply); // zapíšeme změny v TODO settings.sync(); } // odstraní vybrané položky ze seznamu void TcpServer::removeItems(QTcpSocket* socket, QList<QByteArray> keys) { QString user = logins.value(socket); QByteArray reply = "REMOVED"; settings.beginGroup("todolist_" + user); QStringList savedKeys = settings.allKeys(); foreach(QByteArray key, keys) { int index = key.toInt(); settings.remove(savedKeys.at(index)); reply += " " + key; } settings.endGroup(); settings.sync(); this->socketWrite(socket, reply); } // klient se odpojil void TcpServer::gotDisconnected() { // získáme ukazatel na objekt (socket), který vyslal signál QTcpSocket* socket = qobject_cast<QTcpSocket*>( this->sender() ); // získáme přihlašovací jméno QString user = logins.value(socket); if(user.isEmpty()) user = "*not logged*"; else // odstraníme ze seznamu aktivních připojení logins.remove(socket); // smažeme socket socket->deleteLater(); cerr << tr("a client (%1) has disconnected").arg(user) << "\n"; cerr.flush(); }
main.cpp
:
#include <QCoreApplication> #include <QDebug> #include <QHostAddress> #include <QStringList> #include <QTextCodec> #include "tcpserver.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); // výchozí port int port = 2266; // výchozí adresa pro naslouchání QHostAddress addr(QHostAddress::Any); // je vše v pořádku pro spuštění programu? bool ok = true; // kontrola argumentů na příkazovém řádku if(argc > 3) { ok = false; } else { if(argc == 3) port = a.arguments().at(2).toInt(&ok); if(argc > 1) ok = addr.setAddress(a.arguments().at(1)); } if(not ok) { qDebug() << "usage:" << argv[0] << "[IP(0.0.0.0)] [port(2266)]"; return 1; } a.setApplicationName("todoserver"); a.setOrganizationDomain("watzke.cz"); // nastavíme kódování C řetězců QTextCodec::setCodecForCStrings( QTextCodec::codecForName("UTF-8") ); TcpServer s(addr, port); return a.exec(); }
Zdrojáky si můžete stáhnout v archívu tcpserver.tar.bz2.
Nástroje: Tisk bez diskuse
Tiskni
Sdílej:
Odkaz v uvodu clanku odkazuje asi jinam nez by melJo, díky. To je tím, že ten článek měl původně č. 9, ale nakonec předběhl původní osmičku
Ale jinak skvely pocteni DavideTo jsem rád, díky.
a potom lze použít blokovací metodu waitForNewConnection()spíš bych napsal blokující. Blokovací vyznívá tak, že účelem té metody je blokovat.