aboutsummaryrefslogtreecommitdiff
path: root/src/UdpTasks/BeaconServer/Receive/heartbeatgamespy0.cpp
blob: 657330dd26547d21a8dceb0f653eb146a0d4c7d2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
#include "../beaconserver.h"

// heartbeat processing for different protocol types
void BeaconServer::processHeartbeatGamespy0(const QNetworkDatagram &datagram,
                                            const QString          &senderAddress,
                                            const unsigned short   &senderPort,
                                            const QString          &receiveBuffer)
{
    // parse key/value pairs and create a readable server label
    QMultiHash<QString, QString> receiveData = parseGameSpy0Buffer(receiveBuffer);
    QString senderAddressLabel = QStringLiteral("%1:%2").arg(senderAddress, QString::number(senderPort));

    /*
     * Receive a heartbeat.
     * Update or insert the server in the database. If not authenticated, send a secure/validate challenge.
     */
    if ( receiveData.contains("heartbeat") )
    {
        // store heartbeat and request authentication
        UdpData newBeacon;
        newBeacon.ip        = senderAddress;
        newBeacon.port      = receiveData.value("heartbeat", "0").toUShort();
        newBeacon.gamename  = receiveData.value("gamename", "unknown");

        // sanity check: known game(name)
        if ( _coreObject->SupportedGames.contains( newBeacon.gamename ) )
        {
            // valid port and/or default port available?
            if (newBeacon.port == 0)
            {
                // override with default port if possible
                if ( _coreObject->SupportedGames.value(newBeacon.gamename).port > 0 )
                {
                    newBeacon.port = _coreObject->SupportedGames.value( newBeacon.gamename ).port;
                }
                else // no valid port available. log and abort.
                {
                    _coreObject->Log.logEvent("heartbeat", QStringLiteral("%1 invalid port for %2")
                                   .arg(newBeacon.ip, newBeacon.gamename) );
                    return;
                }
            }
        }
        else // unknown game. log and abort.
        {
            _coreObject->Log.logEvent("unsupported", QStringLiteral("%1:%2 for unsupported game %3")
                           .arg(newBeacon.ip, QString::number(newBeacon.port), newBeacon.gamename) );
            return;
        }


        // if this server already exists, update it
        if ( updateServer(newBeacon.ip, newBeacon.port, newBeacon.gamename, true, false) )
        {
            // log update
            _coreObject->Log.logEvent("heartbeat", QStringLiteral("%1:%2 for %3")
                    .arg(newBeacon.ip, QString::number(newBeacon.port), newBeacon.gamename) );

            // no further tasks for this datagram
            return;
        }

        // else:

        // add to database
        insertServer(newBeacon.ip, newBeacon.port, newBeacon.gamename, true);

        // log type "uplink" for first heartbeat. from now on, this server is logged with type "heartbeat"
        _coreObject->Log.logEvent("new", QStringLiteral("%1:%2 for %3")
                       .arg(newBeacon.ip, QString::number(newBeacon.port),newBeacon.gamename) );

        /*
         * Set up secure/validate challenge
         */

        // some games are incompatible with secure/validate (within this protocol)
        if ( _overrideValidateBeacon.contains(newBeacon.gamename) )
            return;

        // generate new challenge
        newBeacon.secure = genChallengeString(6, false);

        // store heartbeat in temporary list
        _beaconList.remove(senderAddressLabel); // remove potential old challenges first
        _beaconList.insert(senderAddressLabel, newBeacon);

        // request authentication of remote server
        _udpSocket.writeDatagram( datagram.makeReply( QStringLiteral("\\secure\\%1").arg(newBeacon.secure).toLatin1() ) );

        // no further tasks for this datagram
        return;
    }

    /*
     * received response to authentication request
     */
    if ( receiveData.contains("validate") )
    {
        // load existing information
        UdpData valBeacon = _beaconList.value(senderAddressLabel);

        // empty heartbeat UdpData? then received validate timed out and previous UdpData was removed
        if (QHostAddress(valBeacon.ip).isNull() || valBeacon.gamename.length() <= 0)
        {
            _coreObject->Log.logEvent("secure", QStringLiteral("unexpected validate from %1").arg(senderAddress));
            return;
        }

        // get response
        AuthResult authResult = validateGamename(true, // this is a beacon
                                                 valBeacon.gamename,
                                                 receiveData.value("validate",""),
                                                 _coreObject->SupportedGames.value(valBeacon.gamename).cipher,
                                                 valBeacon.secure,
                                                 receiveData.value("enctype", "0").toInt() );

        // compare with received response
        if ( authResult.auth )
        {
            // server authenticated - log and add to database
            _coreObject->Log.logEvent("secure", QStringLiteral("successful validate from %1:%2 for %3")
                           .arg(valBeacon.ip, QString::number(valBeacon.port),valBeacon.gamename) );

            // update the existing entry that was already added in the initial heartbeat
            updateServer(valBeacon.ip, valBeacon.port, valBeacon.gamename, false, true);

            // remove from temporary/pending list
            _beaconList.remove(senderAddressLabel);
        }
        else // log failed validate
        {
            // set validate false (but update last response time)
            updateServer(valBeacon.ip, valBeacon.port, valBeacon.gamename, false, false);
            _coreObject->Log.logEvent("secure", QStringLiteral("failed validate from %1:%2 for %3")
                           .arg(valBeacon.ip, QString::number(valBeacon.port),valBeacon.gamename) );
            _coreObject->Log.logEvent("secure", QStringLiteral("secure: '%1', gamename: '%2', validate: '%3', expected: '%4'")
                                        .arg(valBeacon.secure, valBeacon.gamename, receiveData.value("validate", "null"), authResult.validate ));
        }

        return;
    }

    /*
     * status queries directed at masterserver
     */
    if (receiveData.contains("secure") or
        receiveData.contains("basic") or
        receiveData.contains("info") or
        receiveData.contains("rules") or
        receiveData.contains("status") or
        receiveData.contains("echo") )
    {
        // parse response query
        QStringList response = replyQuery(receiveData);

        // return response
        _udpSocket.writeDatagram( datagram.makeReply( response.join("").toLatin1() ) );

        // log incoming query
        _coreObject->Log.logEvent("query", QStringLiteral("%1 queried us with %2")
                       .arg(senderAddress, receiveData.keys().join(", ") ) );

        // log echo separately
        if ( receiveData.contains("echo") )
            _coreObject->Log.logEvent("echo", QStringLiteral("%1: '%2'")
                           .arg(senderAddress, receiveData.value("echo") ) );

        return;
    }

    // ignore trailing queryid+final
    if (receiveData.size() > 0)
    {
        // receive queryid and final from the query
        receiveData.remove("queryid");
        receiveData.remove("final");

        // nothing remains? then ignore. otherwise, proceed to "unknown query"
        if ( receiveData.size() <= 0)
            return;
    }

    // received another type of query?
    _coreObject->Log.logEvent("unknown", QStringLiteral("received unknown udp uplink %1 from %2")
                   .arg(receiveBuffer, senderAddress) );
}