Byl vydán Mozilla Firefox 146.0. Přehled novinek v poznámkách k vydání a poznámkách k vydání pro vývojáře. Řešeny jsou rovněž bezpečnostní chyby. Nový Firefox 146 bude brzy k dispozici také na Flathubu a Snapcraftu.
Před rokem převzala Digitální a informační agentura (DIA) vlastnictví a provoz jednotné státní domény gov.cz. Nyní spustila samoobslužný portál, který umožňuje orgánům veřejné moci snadno registrovat nové domény státní správy pod doménu gov.cz nebo spravovat ty stávající. Proces nové registrace, který dříve trval 30 dní, se nyní zkrátil na několik minut.
IBM kupuje za 11 miliard USD (229,1 miliardy Kč) firmu Confluent zabývající se datovou infrastrukturou. Posílí tak svoji nabídku cloudových služeb a využije růstu poptávky po těchto službách, který je poháněný umělou inteligencí.
Nejvyšší správní soud (NSS) podruhé zrušil pokutu za únik zákaznických údajů z e-shopu Mall.cz. Incidentem se musí znovu zabývat Úřad pro ochranu osobních údajů (ÚOOÚ). Samotný únik ještě neznamená, že správce dat porušil svou povinnost zajistit jejich bezpečnost, plyne z rozsudku dočasně zpřístupněného na úřední desce. Úřad musí vždy posoudit, zda byla přijatá opatření přiměřená povaze rizik, stavu techniky a nákladům.
Organizace Free Software Foundation Europe (FSFE) zrušila svůj účet na 𝕏 (Twitter) s odůvodněním: "To, co mělo být původně místem pro dialog a výměnu informací, se proměnilo v centralizovanou arénu nepřátelství, dezinformací a ziskem motivovaného řízení, což je daleko od ideálů svobody, za nimiž stojíme". FSFE je aktivní na Mastodonu.
Paramount nabízí za celý Warner Bros. Discovery 30 USD na akcii, tj. celkově o 18 miliard USD více než nabízí Netflix. V hotovosti.
Nájemný botnet Aisuru prolomil další "rekord". DDoS útok na Cloudflare dosáhl 29,7 Tbps. Aisuru je tvořený až čtyřmi miliony kompromitovaných zařízení.
Iced, tj. multiplatformní GUI knihovna pro Rust, byla vydána ve verzi 0.14.0.
FEX, tj. open source emulátor umožňující spouštět aplikace pro x86 a x86_64 na architektuře ARM64, byl vydán ve verzi 2512. Před pár dny FEX oslavil sedmé narozeniny. Hlavní vývojář FEXu Ryan Houdek v oznámení poděkoval společnosti Valve za podporu. Pierre-Loup Griffais z Valve, jeden z architektů stojících za SteamOS a Steam Deckem, v rozhovoru pro The Verge potvrdil, že FEX je od svého vzniku sponzorován společností Valve.
Byla vydána nová verze 2.24 svobodného video editoru Flowblade (GitHub, Wikipedie). Přehled novinek v poznámkách k vydání. Videoukázky funkcí Flowblade na Vimeu. Instalovat lze také z Flathubu.
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:
Ale jinak skvely pocteni Davide
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
To pak musí opravit někdo z vyšších adminů.
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.