Update DTLS stuff, fixing certificate pinning bugs.

We should now support dtls1.2 under win10 (otherwise win7+8 is still stuck with 1.0).
SSQC can now query client certificate info via infokey - *cert_sha1 or *cert_dn
Server addresses can be postfixed with eg ip:port?fp=BASE64 to provide a fingerprint to verify the server without depending on cert authorities.
This commit is contained in:
Shpoike 2023-02-20 08:35:56 +00:00
parent 4d06516fb2
commit f2d54f30d8
17 changed files with 1338 additions and 405 deletions

View file

@ -289,15 +289,6 @@ static struct
int numadr; int numadr;
int nextadr; int nextadr;
netadr_t adr[8]; //addresses that we're trying to transfer to, one entry per dns result, eg both ::1 AND 127.0.0.1 netadr_t adr[8]; //addresses that we're trying to transfer to, one entry per dns result, eg both ::1 AND 127.0.0.1
#ifdef HAVE_DTLS
enum
{ //not relevant when given a direct dtls address.
DTLS_DISABLE,
DTLS_TRY,
DTLS_REQUIRE,
DTLS_ACTIVE,
} dtlsupgrade;
#endif
int protocol; //nq/qw/q2/q3. guessed based upon server replies int protocol; //nq/qw/q2/q3. guessed based upon server replies
int subprotocol; //the monkeys are trying to eat me. int subprotocol; //the monkeys are trying to eat me.
struct struct
@ -320,13 +311,20 @@ static struct
enum coninfomode_e enum coninfomode_e
{ {
CIM_DEFAULT, //sends both a qw getchallenge and nq connect (also with postfixed getchallenge so modified servers can force getchallenge) CIM_DEFAULT, //sends both a qw getchallenge and nq connect (also with postfixed getchallenge so modified servers can force getchallenge)
CIM_NQONLY, //disables getchallenge (so fte servers treat us as an nq server). should not be used for dpp7 servers. CIM_NQONLY, //disables getchallenge (so fte servers treat us as an nq client). should not be used for dpp7 servers.
CIM_QEONLY, //forces dtls and uses a different nq netchan version CIM_QEONLY, //forces dtls and uses a different nq netchan version
} mode; } mode;
enum coninfospec_e
{
CIS_DEFAULT, //default
CIS_JOIN, //force join
CIS_OBSERVE, //force observe
} spec;
int defaultport; int defaultport;
int tries; //increased each try, every fourth trys nq connect packets. int tries; //increased each try, every fourth trys nq connect packets.
unsigned char guid[64]; //client->server guid (so doesn't change with transfers) unsigned char guid[64]; //client->server guid (so doesn't change with transfers)
// qbyte fingerprint[5*4]; //sha1 hash of accepted dtls certs
struct dtlspeercred_s peercred;
} connectinfo; } connectinfo;
qboolean nomaster; qboolean nomaster;
@ -595,6 +593,29 @@ char *CL_GUIDString(netadr_t *adr)
return connectinfo.guid; return connectinfo.guid;
} }
static void CL_ConnectAbort(const char *format, ...)
{ //stops trying to connect, doesn't affect the _current_ connection, so usable for transfers.
va_list argptr;
char reason[1024];
if (format)
{
va_start (argptr, format);
Q_vsnprintfz (reason, sizeof(reason), format,argptr);
va_end (argptr);
Cvar_Set(&cl_disconnectreason, reason);
Con_Printf (CON_ERROR"%s\n", reason);
}
#ifdef HAVE_DTLS
while (connectinfo.numadr)
NET_DTLS_Disconnect(cls.sockets, &connectinfo.adr[--connectinfo.numadr]);
#endif
connectinfo.numadr = 0;
SCR_EndLoadingPlaque();
connectinfo.trying = false;
}
/* /*
======================= =======================
CL_SendConnectPacket CL_SendConnectPacket
@ -658,13 +679,27 @@ static void CL_SendConnectPacket (netadr_t *to)
t1 = Sys_DoubleTime (); t1 = Sys_DoubleTime ();
if (connectinfo.peercred.hash && net_enable_dtls.ival>0)
{
char cert[8192];
char digest[DIGEST_MAXSIZE];
int sz = NET_GetConnectionCertificate(cls.sockets, to, QCERT_PEERCERTIFICATE, cert, sizeof(cert));
if (sz <= 0 || memcmp(connectinfo.peercred.digest, digest, CalcHash(connectinfo.peercred.hash, digest, sizeof(digest), cert, sz)))
{ //FIXME: we may have already pinned the bad cert, which may cause issues when reconnecting without FP info later.
if (NET_GetConnectionCertificate(cls.sockets, to, QCERT_ISENCRYPTED, NULL, 0)<0)
CL_ConnectAbort ("Fingerprint specified, but server did not report any certificate\n");
else
CL_ConnectAbort ("Server certificate does not match specified fingerprint\n");
return;
}
}
if (!to) if (!to)
{ {
to = &addr; to = &addr;
if (!NET_StringToAdr (cls.servername, PORT_DEFAULTSERVER, to)) if (!NET_StringToAdr (cls.servername, PORT_DEFAULTSERVER, to))
{ {
Con_TPrintf ("CL_SendConnectPacket: Bad server address \"%s\"\n", cls.servername); CL_ConnectAbort ("CL_SendConnectPacket: Bad server address \"%s\"\n", cls.servername);
connectinfo.trying = false;
return; return;
} }
} }
@ -675,8 +710,7 @@ static void CL_SendConnectPacket (netadr_t *to)
if (!NET_IsClientLegal(to)) if (!NET_IsClientLegal(to))
{ {
Con_TPrintf ("Illegal server address\n"); CL_ConnectAbort("Illegal server address\n");
connectinfo.trying = false;
return; return;
} }
@ -715,10 +749,12 @@ static void CL_SendConnectPacket (netadr_t *to)
Q_strncatz(data, va("\\prx\\%s", cls.servername), sizeof(data)); Q_strncatz(data, va("\\prx\\%s", cls.servername), sizeof(data));
*a = '@'; *a = '@';
} }
if (connectinfo.spec==CIS_OBSERVE)
Q_strncatz(data, "\\spectator\\1", sizeof(data));
//the info itself //the info itself
{ {
static const char *prioritykeys[] = {"name", "password", "spectator", "lang", "rate", "team", "topcolor", "bottomcolor", "skin", "_", "*", NULL}; static const char *prioritykeys[] = {"name", "password", "spectator", "lang", "rate", "team", "topcolor", "bottomcolor", "skin", "_", "*", NULL};
static const char *ignorekeys[] = {"prx", "*z_ext", NULL}; const char *ignorekeys[] = {"prx", "*z_ext", (connectinfo.spec!=CIS_DEFAULT)?"spectator":NULL, NULL};
InfoBuf_ToString(&cls.userinfo[0], data+strlen(data), sizeof(data)-strlen(data), prioritykeys, ignorekeys, NULL, &cls.userinfosync, &cls.userinfo[0]); InfoBuf_ToString(&cls.userinfo[0], data+strlen(data), sizeof(data)-strlen(data), prioritykeys, ignorekeys, NULL, &cls.userinfosync, &cls.userinfo[0]);
} }
if (connectinfo.protocol == CP_QUAKEWORLD) //zquake extension info. if (connectinfo.protocol == CP_QUAKEWORLD) //zquake extension info.
@ -808,32 +844,18 @@ static void CL_ResolvedServer(void *vctx, void *data, size_t a, size_t b)
if (!ctx->found) if (!ctx->found)
{ {
Cvar_Set(&cl_disconnectreason, va("Bad server address \"%s\"", ctx->servername)); CL_ConnectAbort("Bad server address \"%s\"\n", ctx->servername);
Con_TPrintf ("Bad server address \"%s\"\n", ctx->servername);
connectinfo.trying = false;
SCR_EndLoadingPlaque();
return; return;
} }
#ifdef HAVE_DTLS #ifdef HAVE_DTLS
for (i = 0; i < ctx->found; i++) for (i = 0; i < ctx->found; i++)
{ {
if (connectinfo.dtlsupgrade == DTLS_ACTIVE || connectinfo.mode==CIM_QEONLY) if (net_enable_dtls.ival>=4 || connectinfo.mode==CIM_QEONLY)// || (connectinfo.peercred.hash && net_enable_dtls.ival >= 1))
{ //if we've already established a dtls connection, stick with it { //if we've already established a dtls connection, stick with it
if (ctx->adr[i].prot == NP_DGRAM) if (ctx->adr[i].prot == NP_DGRAM)
ctx->adr[i].prot = NP_DTLS; ctx->adr[i].prot = NP_DTLS;
} }
else if (connectinfo.adr[i].prot == NP_DTLS)
{ //dtls connections start out with regular udp, and upgrade to dtls once its established that the server supports it.
//FIXME: remove this block once our new netcode is better established.
connectinfo.dtlsupgrade = DTLS_REQUIRE;
ctx->adr[i].prot = NP_DGRAM;
}
else
{
//hostname didn't specify dtls. upgrade if we're allowed, but don't mandate it.
//connectinfo.dtlsupgrade = DTLS_TRY;
}
} }
#endif #endif
@ -846,11 +868,11 @@ static void CL_ResolvedServer(void *vctx, void *data, size_t a, size_t b)
static void CL_ResolveServer(void *vctx, void *data, size_t a, size_t b) static void CL_ResolveServer(void *vctx, void *data, size_t a, size_t b)
{ {
struct resolvectx_s *ctx = vctx; struct resolvectx_s *ctx = vctx;
const char *host = strrchr(cls.servername+1, '@'); const char *host = strrchr(ctx->servername+1, '@');
if (host) if (host)
host++; host++;
else else
host = cls.servername; host = ctx->servername;
ctx->found = NET_StringToAdr2 (host, connectinfo.defaultport, ctx->adr, countof(ctx->adr), NULL); ctx->found = NET_StringToAdr2 (host, connectinfo.defaultport, ctx->adr, countof(ctx->adr), NULL);
@ -1109,9 +1131,7 @@ void CL_CheckForResend (void)
connectinfo.nextadr = 0; connectinfo.nextadr = 0;
if (!connectinfo.numadr) if (!connectinfo.numadr)
{ {
Con_TPrintf ("CL_CheckForResend: Bad server address \"%s\"\n", cls.servername); CL_ConnectAbort("CL_CheckForResend: Bad server address \"%s\"\n", cls.servername);
connectinfo.trying = false;
SCR_EndLoadingPlaque();
return; return;
} }
NET_AdrToString(data, sizeof(data), &connectinfo.adr[connectinfo.nextadr]); NET_AdrToString(data, sizeof(data), &connectinfo.adr[connectinfo.nextadr]);
@ -1200,6 +1220,9 @@ void CL_CheckForResend (void)
else else
connectinfo.clogged = false; //do the prints and everything. connectinfo.clogged = false; //do the prints and everything.
if (!cls.sockets) //only if its needed... we don't want to keep using a new port unless we have to
NET_InitClient(false);
#ifdef HAVE_DTLS #ifdef HAVE_DTLS
if (connectinfo.numadr>0 && connectinfo.adr[0].prot == NP_DTLS) if (connectinfo.numadr>0 && connectinfo.adr[0].prot == NP_DTLS)
{ //get through the handshake first, instead of waiting for a 5-sec timeout between polls. { //get through the handshake first, instead of waiting for a 5-sec timeout between polls.
@ -1208,17 +1231,16 @@ void CL_CheckForResend (void)
case NETERR_CLOGGED: //temporary failure case NETERR_CLOGGED: //temporary failure
connectinfo.clogged = true; connectinfo.clogged = true;
return; return;
case NETERR_DISCONNECTED:
CL_ConnectAbort("DTLS Certificate Verification Failure\n");
break;
case NETERR_NOROUTE: //not an error here, just means we need to send a new handshake.
break;
default: default:
break; break;
} }
} }
if (connectinfo.dtlsupgrade != DTLS_ACTIVE)
#endif #endif
{
if (!cls.sockets) //only if its needed... we don't want to keep using a new port unless we have to
NET_InitClient(false);
}
t1 = Sys_DoubleTime (); t1 = Sys_DoubleTime ();
if (!connectinfo.istransfer) if (!connectinfo.istransfer)
@ -1246,10 +1268,7 @@ void CL_CheckForResend (void)
to = &connectinfo.adr[connectinfo.nextadr%connectinfo.numadr]; to = &connectinfo.adr[connectinfo.nextadr%connectinfo.numadr];
if (!NET_IsClientLegal(to)) if (!NET_IsClientLegal(to))
{ {
Cvar_Set(&cl_disconnectreason, va("Illegal server address")); CL_ConnectAbort ("Illegal server address\n");
Con_TPrintf ("Illegal server address\n");
SCR_EndLoadingPlaque();
connectinfo.trying = false;
return; return;
} }
@ -1274,12 +1293,9 @@ void CL_CheckForResend (void)
connectinfo.clogged = false; connectinfo.clogged = false;
if (connectinfo.tries == 0 && connectinfo.nextadr < connectinfo.numadr) if (connectinfo.tries == 0 && connectinfo.nextadr < connectinfo.numadr)
if (!NET_EnsureRoute(cls.sockets, "conn", cls.servername, to)) if (!NET_EnsureRoute(cls.sockets, "conn", &connectinfo.peercred, to))
{ {
Cvar_Set(&cl_disconnectreason, va("Unable to establish connection to %s\n", cls.servername)); CL_ConnectAbort ("Unable to establish connection to %s\n", cls.servername);
Con_Printf ("Unable to establish connection to %s\n", cls.servername);
connectinfo.trying = false;
SCR_EndLoadingPlaque();
return; return;
} }
@ -1377,32 +1393,92 @@ void CL_CheckForResend (void)
} }
else else
{ {
Cvar_Set(&cl_disconnectreason, va("No route to \"%s\", giving up\n", cls.servername)); CL_ConnectAbort ("No route to host, giving up\n");
Con_TPrintf ("No route to host, giving up\n");
connectinfo.trying = false;
SCR_EndLoadingPlaque();
NET_CloseClient(); NET_CloseClient();
} }
} }
} }
static void CL_BeginServerConnect(const char *host, int port, qboolean noproxy, enum coninfomode_e mode) static void CL_BeginServerConnect(char *host, int port, qboolean noproxy, enum coninfomode_e mode, enum coninfospec_e spec)
{ {
if (!strncmp(host, "localhost", 9)) const char *schemeend = strstr(host, "://");
noproxy = true; //FIXME: resolve the address here or something so that we don't end up using a proxy for lan addresses. char *arglist;
if (strstr(host, "://") || !*cl_proxyaddr.string || noproxy) Q_strncpyz(cls.serverurl, host, sizeof(cls.serverurl));
Q_strncpyz (cls.servername, host, sizeof(cls.servername));
if (schemeend)
{
const char *schemestart = strchr(host, ':');
int schemelen;
//if its one of our explicit protocols then use the url as-is
const char *netschemes[] = {"udp", "udp4", "udp6", "ipx", "tcp", "tcp4", "tcp6", /*ipx*/"spx", "ws", "wss", "tls", "dtls", "ice", "rtc", "ices", "rtcs", "irc", "udg", "unix"};
int i;
size_t slen;
if (!schemestart || schemestart==schemeend)
schemestart = host;
else
schemestart++;
schemelen = schemeend-schemestart;
Q_strncpyz (cls.servername, "", sizeof(cls.servername));
for (i = 0; i < countof(netschemes); i++)
{
slen = strlen(netschemes[i]);
if (schemelen == slen && !strncmp(schemestart, netschemes[i], slen))
{
Q_strncpyz (cls.servername, host, sizeof(cls.servername)); //oh. will probably be okay then
break;
}
}
if (!*cls.servername)
{ //not some '/foo' name, not rtc:// either...
char *sl = strchr(schemeend+3, '/');
if (sl)
{
if (!strncmp(sl, "/observe", 8))
{
if (spec == CIS_DEFAULT)
spec = CIS_OBSERVE;
else if (spec != CIS_OBSERVE)
Con_Printf("Ignoring 'observe'\n");
memmove(sl, sl+8, strlen(sl+8)+1);
}
else if (!strncmp(sl, "/join", 5))
{
if (spec == CIS_DEFAULT)
spec = CIS_JOIN;
else if (spec != CIS_OBSERVE)
Con_Printf("Ignoring 'join'\n");
memmove(sl, sl+5, strlen(sl+5)+1);
}
else if (!strncmp(sl, "/", 1) && (sl[1] == 0 || sl[1]=='?'))
{
//current spectator mode
memmove(sl, sl+1, strlen(sl+1)+1);
}
}
Q_strncpyz (cls.servername, schemeend+3, sizeof(cls.servername)); //probably some game-specific mess that we don't know
}
}
else else
Q_snprintfz(cls.servername, sizeof(cls.servername), "%s@%s", host, cl_proxyaddr.string); {
if (!strncmp(host, "localhost", 9))
noproxy = true; //FIXME: resolve the address here or something so that we don't end up using a proxy for lan addresses.
if (strstr(host, "://") || !*cl_proxyaddr.string || noproxy)
Q_strncpyz (cls.servername, host, sizeof(cls.servername));
else
Q_snprintfz(cls.servername, sizeof(cls.servername), "%s@%s", host, cl_proxyaddr.string);
}
arglist = strchr(cls.servername, '?');
if (!port) if (!port)
port = cl_defaultport.value; port = cl_defaultport.value;
#ifdef HAVE_DTLS
while (connectinfo.numadr) CL_ConnectAbort(NULL);
NET_DTLS_Disconnect(cls.sockets, &connectinfo.adr[--connectinfo.numadr]);
#endif
memset(&connectinfo, 0, sizeof(connectinfo)); memset(&connectinfo, 0, sizeof(connectinfo));
if (*cl_disconnectreason.string) if (*cl_disconnectreason.string)
Cvar_Set(&cl_disconnectreason, ""); Cvar_Set(&cl_disconnectreason, "");
@ -1410,15 +1486,31 @@ static void CL_BeginServerConnect(const char *host, int port, qboolean noproxy,
connectinfo.defaultport = port; connectinfo.defaultport = port;
connectinfo.protocol = CP_UNKNOWN; connectinfo.protocol = CP_UNKNOWN;
connectinfo.mode = mode; connectinfo.mode = mode;
connectinfo.spec = spec;
#ifdef HAVE_DTLS connectinfo.peercred.name = cls.servername;
if (net_enable_dtls.ival >= 3) if (arglist)
connectinfo.dtlsupgrade = DTLS_REQUIRE; {
else if (net_enable_dtls.ival >= 2) *arglist++ = 0;
connectinfo.dtlsupgrade = DTLS_TRY; while (*arglist)
else {
connectinfo.dtlsupgrade = DTLS_DISABLE; char *e = strchr(arglist, '&');
#endif if (e)
*e=0;
if (!strncasecmp(arglist, "fp=", 3))
{
Base64_DecodeBlock(arglist+3, arglist+strlen(arglist), connectinfo.peercred.digest, sizeof(connectinfo.peercred.digest));
connectinfo.peercred.hash = &hash_sha1;
}
else
Con_Printf(CON_WARNING"uri arg not known: \"%s\"\n", arglist);
if (e)
arglist=e+1;
else
break;
}
}
SCR_SetLoadingStage(LS_CONNECTION); SCR_SetLoadingStage(LS_CONNECTION);
CL_CheckForResend(); CL_CheckForResend();
@ -1434,9 +1526,11 @@ void CL_BeginServerReconnect(void)
} }
#endif #endif
#ifdef HAVE_DTLS #ifdef HAVE_DTLS
if (connectinfo.numadr>0) {
NET_DTLS_Disconnect(cls.sockets, &connectinfo.adr[0]); int i;
connectinfo.dtlsupgrade = 0; for (i = 0; i < connectinfo.numadr; i++)
NET_DTLS_Disconnect(cls.sockets, &connectinfo.adr[i]);
}
#endif #endif
#ifdef SUPPORT_ICE #ifdef SUPPORT_ICE
while (connectinfo.numadr) //remove any ICE addresses. probably we'll end up with no addresses left leaving us free to re-resolve giving us the original(ish) rtc connection. while (connectinfo.numadr) //remove any ICE addresses. probably we'll end up with no addresses left leaving us free to re-resolve giving us the original(ish) rtc connection.
@ -1474,11 +1568,11 @@ void CL_Transfer_f(void)
return; return;
} }
CL_ConnectAbort(NULL);
server = Cmd_Argv (1); server = Cmd_Argv (1);
if (!*server) if (!*server)
{ {
//if they didn't specify a server, abort any active transfer/connection. //if they didn't specify a server, abort any active transfer/connection.
connectinfo.trying = false;
return; return;
} }
@ -1529,7 +1623,7 @@ void CL_Connect_f (void)
#endif #endif
CL_Disconnect_f (); CL_Disconnect_f ();
CL_BeginServerConnect(server, 0, false, CIM_DEFAULT); CL_BeginServerConnect(server, 0, false, CIM_DEFAULT, CIS_DEFAULT);
} }
#if defined(CL_MASTER) && defined(HAVE_PACKET) #if defined(CL_MASTER) && defined(HAVE_PACKET)
static void CL_ConnectBestRoute_f (void) static void CL_ConnectBestRoute_f (void)
@ -1564,7 +1658,7 @@ static void CL_ConnectBestRoute_f (void)
else else
#endif #endif
CL_Disconnect_f (); CL_Disconnect_f ();
CL_BeginServerConnect(server, 0, true, CIM_DEFAULT); CL_BeginServerConnect(server, 0, true, CIM_DEFAULT, CIS_DEFAULT);
} }
#endif #endif
@ -1591,9 +1685,7 @@ static void CL_Join_f (void)
CL_Disconnect_f (); CL_Disconnect_f ();
Cvar_Set(&spectator, "0"); CL_BeginServerConnect(server, 0, false, CIM_DEFAULT, CIS_JOIN);
CL_BeginServerConnect(server, 0, false, CIM_DEFAULT);
} }
void CL_Observe_f (void) void CL_Observe_f (void)
@ -1621,7 +1713,7 @@ void CL_Observe_f (void)
Cvar_Set(&spectator, "1"); Cvar_Set(&spectator, "1");
CL_BeginServerConnect(server, 0, false, CIM_DEFAULT); CL_BeginServerConnect(server, 0, false, CIM_DEFAULT, CIS_OBSERVE);
} }
#ifdef NQPROT #ifdef NQPROT
@ -1646,7 +1738,7 @@ void CLNQ_Connect_f (void)
CL_Disconnect_f (); CL_Disconnect_f ();
CL_BeginServerConnect(server, 26000, true, mode); CL_BeginServerConnect(server, 26000, true, mode, CIS_DEFAULT/*doesn't really do spec/join stuff, but if the server asks for our info later...*/);
} }
#endif #endif
@ -2242,9 +2334,7 @@ void CL_Disconnect_f (void)
#endif #endif
CL_Disconnect (NULL); CL_Disconnect (NULL);
CL_ConnectAbort(NULL);
connectinfo.trying = false;
NET_CloseClient(); NET_CloseClient();
(void)CSQC_UnconnectedInit(); (void)CSQC_UnconnectedInit();
@ -2930,6 +3020,7 @@ void CL_Packet_f (void)
int i, l; int i, l;
char *in, *out; char *in, *out;
netadr_t adr; netadr_t adr;
struct dtlspeercred_s cred = {Cmd_Argv(1)};
if (Cmd_Argc() != 3) if (Cmd_Argc() != 3)
{ {
@ -3019,7 +3110,7 @@ void CL_Packet_f (void)
if (!cls.sockets) if (!cls.sockets)
NET_InitClient(false); NET_InitClient(false);
if (!NET_EnsureRoute(cls.sockets, "packet", Cmd_Argv(1), &adr)) if (!NET_EnsureRoute(cls.sockets, "packet", &cred, &adr))
return; return;
NET_SendPacket (cls.sockets, out-send, send, &adr); NET_SendPacket (cls.sockets, out-send, send, &adr);
@ -3340,7 +3431,8 @@ void CL_ConnectionlessPacket (void)
{ {
if (CL_IsPendingServerAddress(&net_from)) if (CL_IsPendingServerAddress(&net_from))
{ {
if (!NET_EnsureRoute(cls.sockets, "redir", cls.servername, &adr)) struct dtlspeercred_s cred = {cls.servername}; //FIXME
if (!NET_EnsureRoute(cls.sockets, "redir", &cred, &adr))
Con_Printf (CON_ERROR"Unable to redirect to %s\n", data); Con_Printf (CON_ERROR"Unable to redirect to %s\n", data);
else else
{ {
@ -3358,29 +3450,20 @@ void CL_ConnectionlessPacket (void)
else if (!strcmp(s, "reject")) else if (!strcmp(s, "reject"))
{ //generic rejection. stop trying. { //generic rejection. stop trying.
char *data = MSG_ReadStringLine(); char *data = MSG_ReadStringLine();
Con_Printf ("reject\n%s\n", data); Con_Printf ("reject\n");
if (CL_IsPendingServerAddress(&net_from)) if (CL_IsPendingServerAddress(&net_from))
{ CL_ConnectAbort("%s\n", data);
Cvar_Set(&cl_disconnectreason, va("%s\n", data));
connectinfo.trying = false;
}
return; return;
} }
else if (!strcmp(s, "badname")) else if (!strcmp(s, "badname"))
{ //rejected purely because of player name { //rejected purely because of player name
if (CL_IsPendingServerAddress(&net_from)) if (CL_IsPendingServerAddress(&net_from))
{ CL_ConnectAbort("bad player name\n");
Cvar_Set(&cl_disconnectreason, va("bad player name\n"));
connectinfo.trying = false;
}
} }
else if (!strcmp(s, "badaccount")) else if (!strcmp(s, "badaccount"))
{ //rejected because username or password is wrong { //rejected because username or password is wrong
if (CL_IsPendingServerAddress(&net_from)) if (CL_IsPendingServerAddress(&net_from))
{ CL_ConnectAbort("invalid username or password\n");
Cvar_Set(&cl_disconnectreason, va("invalid username or password\n"));
connectinfo.trying = false;
}
} }
Con_Printf ("f%s\n", s); Con_Printf ("f%s\n", s);
@ -3606,40 +3689,50 @@ void CL_ConnectionlessPacket (void)
} }
#ifdef HAVE_DTLS #ifdef HAVE_DTLS
if (candtls && net_from.prot == NP_DGRAM && (connectinfo.dtlsupgrade || candtls > 1) && !NET_IsEncrypted(&net_from)) if ((candtls && net_enable_dtls.ival) && net_from.prot == NP_DGRAM && (net_enable_dtls.ival>1 || candtls > 1) && !NET_IsEncrypted(&net_from))
{ {
//c2s getchallenge //c2s getchallenge <no client details
//s2c c%u\0DTLS=$candtls //s2c c%u\0DTLS=$candtls <may leak server details>
//<<YOU ARE HERE>> //<<YOU ARE HERE>>
//c2s dtlsconnect %u //c2s dtlsconnect %u [REALTARGET] <FIXME: target server is plain text, not entirely unlike tls1.2, but still worse than a vpn and could be improved>
//s2c dtlsopened //s2c dtlsopened <no details at all, other than that the server is now willing to accept dtls handshakes etc>
//c2s DTLS(getchallenge) //c2s DTLS(getchallenge)
//DTLS(etc) //DTLS(etc)
//NOTE: the dtlsconnect/dtlsopened parts are redundant and the non-dtls parts are entirely optional (and should be skipped the client requries/knows the server supports dtls) //NOTE: the dtlsconnect/dtlsopened parts are redundant and the non-dtls parts are now entirely optional (and should be skipped if the client requries/knows the server supports dtls)
//the challenge response includes server capabilities, so we still need the getchallenge/response part of the handshake despite dtls making the actual challenge part redundant. //the challenge response includes server capabilities, so we still need the getchallenge/response part of the handshake despite dtls making the actual challenge part redundant.
//getchallenge has to be done twice, with the outer one only reporting whether dtls can/should be used. //getchallenge has to be done twice, with the outer one only reporting whether dtls can/should be used.
//this means the actual connect packet is already over dtls, which protects the user's userinfo. //this means the actual connect packet is already over dtls, which protects the user's userinfo.
//FIXME: do rcon via dtls too, but requires tracking pending rcon packets until the handshake completes. //FIXME: do rcon via dtls too, but requires tracking pending rcon packets until the handshake completes.
//server says it can do dtls, but will still need to ask it to allocate extra resources for us. //server says it can do dtls, but will still need to ask it to allocate extra resources for us (I hadn't gotten dtls cookies working properly at that point).
char *pkt; if (net_enable_dtls.ival>0)
//qwfwd proxy routing
char *at;
if ((at = strrchr(cls.servername, '@')))
{ {
*at = 0; char *pkt;
pkt = va("%c%c%c%c""dtlsconnect %i %s", 255, 255, 255, 255, connectinfo.challenge, cls.servername); //qwfwd proxy routing. it doesn't support it yet, but hey, if its willing to forward the dtls packets its all good.
*at = '@'; char *at;
if ((at = strrchr(cls.servername, '@')))
{
*at = 0;
pkt = va("%c%c%c%c""dtlsconnect %i %s", 255, 255, 255, 255, connectinfo.challenge, cls.servername);
*at = '@';
}
else
pkt = va("%c%c%c%c""dtlsconnect %i", 255, 255, 255, 255, connectinfo.challenge);
NET_SendPacket (cls.sockets, strlen(pkt), pkt, &net_from);
return;
}
else if (candtls >= 3)
{
Cvar_Set(&cl_disconnectreason, va("DTLS is disabled, but server requires it. not connecting\n"));
connectinfo.trying = false;
Con_Printf("DTLS is disabled, but server requires it. Set ^[/net_enable_dtls 1^] before connecting again.\n");
return;
} }
else
pkt = va("%c%c%c%c""dtlsconnect %i", 255, 255, 255, 255, connectinfo.challenge);
NET_SendPacket (cls.sockets, strlen(pkt), pkt, &net_from);
return;
} }
if (connectinfo.dtlsupgrade == DTLS_REQUIRE && !NET_IsEncrypted(&net_from)) if (net_enable_dtls.ival>=3 && !NET_IsEncrypted(&net_from))
{ {
Cvar_Set(&cl_disconnectreason, va("Server does not support/allow dtls. not connecting\n")); Cvar_Set(&cl_disconnectreason, va("Server does not support/allow dtls. not connecting\n"));
connectinfo.trying = false; connectinfo.trying = false;
@ -3802,10 +3895,9 @@ void CL_ConnectionlessPacket (void)
return; return;
memset(&cred, 0, sizeof(cred)); memset(&cred, 0, sizeof(cred));
cred.peer.name = cls.servername; cred.peer = connectinfo.peercred;
if (NET_DTLS_Create(cls.sockets, &net_from, &cred)) if (NET_DTLS_Create(cls.sockets, &net_from, &cred))
{ {
connectinfo.dtlsupgrade = DTLS_ACTIVE;
connectinfo.numadr = 1; //fixate on this resolved address. connectinfo.numadr = 1; //fixate on this resolved address.
connectinfo.adr[0] = net_from; connectinfo.adr[0] = net_from;
connectinfo.adr[0].prot = NP_DTLS; connectinfo.adr[0].prot = NP_DTLS;
@ -3813,11 +3905,7 @@ void CL_ConnectionlessPacket (void)
connectinfo.time = 0; //send a new challenge NOW. connectinfo.time = 0; //send a new challenge NOW.
} }
else else
{ CL_ConnectAbort("Unable to initialise dtls driver. You may need to adjust tls_provider or disable dtls with ^[/net_enable_dtls 0^]\n"); //this is a local issue, and not a result on remote packets.
if (connectinfo.dtlsupgrade == DTLS_TRY)
connectinfo.dtlsupgrade = DTLS_DISABLE;
Con_Printf ("unable to establish dtls route\n");
}
#else #else
Con_Printf ("dtlsopened (unsupported)\n"); Con_Printf ("dtlsopened (unsupported)\n");
#endif #endif
@ -4112,7 +4200,7 @@ void CLNQ_ConnectionlessPacket(void)
else else
{ {
//send a dummy packet. //send a dummy packet.
//this makes our local nat think we initialised the conversation, so that we can receive the. //this makes our local firewall think we initialised the conversation, so that we can receive their packets. however this only works if our nat uses the same public port for private ports.
Netchan_Transmit(&cls.netchan, 1, "\x01", 2500); Netchan_Transmit(&cls.netchan, 1, "\x01", 2500);
} }
return; return;
@ -5422,9 +5510,9 @@ NORETURN void VARGS Host_EndGame (const char *message, ...)
SCR_EndLoadingPlaque(); SCR_EndLoadingPlaque();
CL_Disconnect (string); CL_Disconnect (string);
CL_ConnectAbort(NULL);
SV_UnspawnServer(); SV_UnspawnServer();
connectinfo.trying = false;
Cvar_Set(&cl_shownet, "0"); Cvar_Set(&cl_shownet, "0");
@ -6215,7 +6303,7 @@ qboolean Host_RunFile(const char *fname, int nlen, vfsfile_t *file)
// "quake2:rtc://broker:port/game" // "quake2:rtc://broker:port/game"
// "qw://[stream@]host[:port]/COMMAND" join, spectate, qtvplay // "qw://[stream@]host[:port]/COMMAND" join, spectate, qtvplay
//we'll chop off any non-auth prefix, its just so we can handle multiple protocols via a single uri scheme. //we'll chop off any non-auth prefix, its just so we can handle multiple protocols via a single uri scheme.
char *t, *cmd; char *t, *cmd, *args;
const char *url; const char *url;
char buffer[8192]; char buffer[8192];
const char *schemestart = strchr(fname, ':'); const char *schemestart = strchr(fname, ':');
@ -6261,6 +6349,15 @@ qboolean Host_RunFile(const char *fname, int nlen, vfsfile_t *file)
t[urilen] = 0; t[urilen] = 0;
url = t+schemelen; url = t+schemelen;
*buffer = 0;
for (args = t+schemelen; *args; args++)
{
if (*args == '?')
{
*args++ = 0;
break;
}
}
for (cmd = t+schemelen; *cmd; cmd++) for (cmd = t+schemelen; *cmd; cmd++)
{ {
if (*cmd == '/') if (*cmd == '/')

View file

@ -492,7 +492,8 @@ typedef struct
infobuf_t userinfo[MAX_SPLITS]; infobuf_t userinfo[MAX_SPLITS];
infosync_t userinfosync; infosync_t userinfosync;
char servername[MAX_OSPATH]; // name of server from original connect char serverurl[MAX_OSPATH*4]; // eg qw://foo:27500/join?fp=blah
char servername[MAX_OSPATH]; // internal parsing, eg dtls://foo:27500
struct ftenet_connections_s *sockets; struct ftenet_connections_s *sockets;

View file

@ -774,6 +774,20 @@ void M_Menu_GameOptions_f (void)
y+=4; y+=4;
info->hostnameedit = MC_AddEdit (menu, 64, 160, y, "Hostname", name.string);y+=info->hostnameedit->common.height; info->hostnameedit = MC_AddEdit (menu, 64, 160, y, "Hostname", name.string);y+=info->hostnameedit->common.height;
info->publicgame = MC_AddCombo (menu, 64, 160, y, "Public", publicoptions, bound(0, sv_public.ival+1, 4));y+=8; info->publicgame = MC_AddCombo (menu, 64, 160, y, "Public", publicoptions, bound(0, sv_public.ival+1, 4));y+=8;
#if !defined(FTE_TARGET_WEB) && defined(HAVE_DTLS)
{
extern cvar_t net_enable_dtls;
static const char *encoptions[] =
{
"None",
"Accept",
"Request",
"Require",
NULL
};
MC_AddCvarCombo (menu, 64, 160, y, "DTLS Encryption", &net_enable_dtls, encoptions, NULL);y+=8;
}
#endif
y+=4; y+=4;
for (players = 0; players < sizeof(numplayeroptions)/ sizeof(numplayeroptions[0]); players++) for (players = 0; players < sizeof(numplayeroptions)/ sizeof(numplayeroptions[0]); players++)
@ -1110,6 +1124,16 @@ void M_Menu_Network_f (void)
"Smooth Demos Only", "Smooth Demos Only",
NULL NULL
}; };
#ifdef HAVE_DTLS
extern cvar_t net_enable_dtls;
static const char *dtlsopts[] = {
"Disabled",
"Accept",
"Request",
"Require",
NULL
};
#endif
static const char *smoothingvalues[] = {"0", "1", "2", NULL}; static const char *smoothingvalues[] = {"0", "1", "2", NULL};
extern cvar_t cl_download_csprogs, cl_download_redirection, requiredownloads, cl_solid_players; extern cvar_t cl_download_csprogs, cl_download_redirection, requiredownloads, cl_solid_players;
extern cvar_t cl_predict_players, cl_lerp_smooth, cl_predict_extrapolate; extern cvar_t cl_predict_players, cl_lerp_smooth, cl_predict_extrapolate;
@ -1122,6 +1146,9 @@ void M_Menu_Network_f (void)
MB_EDITCVARSLIM("Network FPS", "cl_netfps", "Sets ammount of FPS used to communicate with server (sent and received)"), MB_EDITCVARSLIM("Network FPS", "cl_netfps", "Sets ammount of FPS used to communicate with server (sent and received)"),
MB_EDITCVARSLIM("Rate", "rate", "Maximum bytes per second that the server should send to the client"), MB_EDITCVARSLIM("Rate", "rate", "Maximum bytes per second that the server should send to the client"),
MB_EDITCVARSLIM("Download Rate", "drate", "Maximum bytes per second that the server should send maps and demos to the client"), MB_EDITCVARSLIM("Download Rate", "drate", "Maximum bytes per second that the server should send maps and demos to the client"),
#ifdef HAVE_DTLS
MB_COMBOCVAR("DTLS Encryption", net_enable_dtls, dtlsopts, NULL, "Use this to avoid snooping. Certificates will be pinned."),
#endif
MB_SPACING(4), MB_SPACING(4),
MB_CHECKBOXCVARTIP("Require Download", requiredownloads, 0, "Ignore downloaded content sent to the client and connect immediately"), MB_CHECKBOXCVARTIP("Require Download", requiredownloads, 0, "Ignore downloaded content sent to the client and connect immediately"),
MB_CHECKBOXCVARTIP("Redirect Download", cl_download_redirection, 0, "Whether the client will ignore download redirection from servers"), MB_CHECKBOXCVARTIP("Redirect Download", cl_download_redirection, 0, "Whether the client will ignore download redirection from servers"),

View file

@ -5,6 +5,7 @@ clientside master queries and server ping/polls
#include "quakedef.h" #include "quakedef.h"
#include "cl_master.h" #include "cl_master.h"
#include "netinc.h"
#define FAVOURITESFILE "favourites.txt" #define FAVOURITESFILE "favourites.txt"
@ -496,7 +497,10 @@ static void SV_Master_Worker_Resolved(void *ctx, void *data, size_t a, size_t b)
{ {
//tcp masters require a route //tcp masters require a route
if (NET_AddrIsReliable(na)) if (NET_AddrIsReliable(na))
NET_EnsureRoute(svs.sockets, master->cv.name, master->cv.string, na); {
struct dtlspeercred_s cred = {master->cv.string};
NET_EnsureRoute(svs.sockets, master->cv.name, &cred, na);
}
//q2+qw masters are given a ping to verify that they're still up //q2+qw masters are given a ping to verify that they're still up
switch (master->protocol) switch (master->protocol)

View file

@ -6891,9 +6891,9 @@ static int Base64_Decode(char inp)
return (inp-'a') + 26; return (inp-'a') + 26;
if (inp >= '0' && inp <= '9') if (inp >= '0' && inp <= '9')
return (inp-'0') + 52; return (inp-'0') + 52;
if (inp == '+') if (inp == '+' || inp == '-')
return 62; return 62;
if (inp == '/') if (inp == '/' || inp == '_')
return 63; return 63;
//if (inp == '=') //padding char //if (inp == '=') //padding char
return 0; //invalid return 0; //invalid
@ -6929,6 +6929,23 @@ size_t Base64_EncodeBlock(const qbyte *in, size_t length, char *out, size_t outs
*out = 0; *out = 0;
return out-start; return out-start;
} }
size_t Base64_EncodeBlockURI(const qbyte *in, size_t length, char *out, size_t outsize)
{ //special uri-safe version (also trims)
outsize = Base64_EncodeBlock(in, length, out, outsize);
for (length = 0; length < outsize; length++)
{
if (out[length] == '+')
out[length] = '-';
else if (out[length] == '/')
out[length] = '_';
else if (out[length] == '=')
{ //truncate it here.
out[length] = 0;
return length;
}
}
return outsize;
}
size_t Base64_DecodeBlock(const char *in, const char *in_end, qbyte *out, size_t outsize) size_t Base64_DecodeBlock(const char *in, const char *in_end, qbyte *out, size_t outsize)
{ {
qbyte *start = out; qbyte *start = out;

View file

@ -695,7 +695,10 @@ static void CertLog_Write(void)
VFS_PUTS(f, certhex); VFS_PUTS(f, certhex);
VFS_PRINTF(f, "\" %i\n", l->trusted?true:false); VFS_PRINTF(f, "\" %i\n", l->trusted?true:false);
} }
VFS_CLOSE(f);
} }
else
Con_Printf(CON_ERROR"Unable to write %s\n", CERTLOG_FILENAME);
} }
static void CertLog_Purge(void) static void CertLog_Purge(void)
{ {
@ -753,6 +756,7 @@ static void CertLog_Import(const char *filename)
} }
CertLog_Update(addressstring, certdata, certsize, atoi(trusted)); CertLog_Update(addressstring, certdata, certsize, atoi(trusted));
} }
VFS_CLOSE(f);
} }
static void CertLog_UntrustAll_f(void) static void CertLog_UntrustAll_f(void)
{ {
@ -795,6 +799,8 @@ qboolean CertLog_ConnectOkay(const char *hostname, void *cert, size_t certsize,
extern cvar_t net_enable_dtls; extern cvar_t net_enable_dtls;
struct certlog_s *l; struct certlog_s *l;
qboolean trusted = (net_enable_dtls.ival >= 2); qboolean trusted = (net_enable_dtls.ival >= 2);
char digest[DIGEST_MAXSIZE];
char fp[DIGEST_MAXSIZE*2+1];
if (certlog_curprompt) if (certlog_curprompt)
return false; return false;
@ -808,11 +814,20 @@ qboolean CertLog_ConnectOkay(const char *hostname, void *cert, size_t certsize,
{ //cert is new, but we don't care about full trust. don't bother to prompt when the user doesn't much care. { //cert is new, but we don't care about full trust. don't bother to prompt when the user doesn't much care.
//(but do pin so we at least know when its MITMed after the fact) //(but do pin so we at least know when its MITMed after the fact)
Con_Printf(CON_WARNING"Auto-Pinning certificate for %s."CON_DEFAULT" ^[/seta %s 2^]+ for actual security.\n", hostname, net_enable_dtls.name); Con_Printf(CON_WARNING"Auto-Pinning certificate for %s."CON_DEFAULT" ^[/seta %s 2^]+ for actual security.\n", hostname, net_enable_dtls.name);
if (certsize)
Base64_EncodeBlockURI(digest, CalcHash(&hash_sha1, digest, sizeof(digest), cert, certsize), fp, sizeof(fp));
else
strcpy(fp, "<No Certificate>");
Con_Printf(S_COLOR_GRAY" fp: %s\n", fp);
CertLog_Update(hostname, cert, certsize, false); CertLog_Update(hostname, cert, certsize, false);
CertLog_Write(); CertLog_Write();
} }
else if (!l || l->certsize != certsize || memcmp(l->cert, cert, certsize) || (trusted && !l->trusted)) else if (!l || l->certsize != certsize || memcmp(l->cert, cert, certsize) || (trusted && !l->trusted))
{ //new or different { //new or different
if (certsize)
Base64_EncodeBlockURI(digest, CalcHash(&hash_sha1, digest, sizeof(digest), cert, certsize), fp, sizeof(fp));
else
strcpy(fp, "<No Certificate>");
if (qrenderer) if (qrenderer)
{ {
unsigned int i; unsigned int i;
@ -820,7 +835,7 @@ qboolean CertLog_ConnectOkay(const char *hostname, void *cert, size_t certsize,
char *text; char *text;
const char *accepttext; const char *accepttext;
const char *lines[] = { const char *lines[] = {
va(localtext("Certificate for %s\n"), hostname), va(localtext("Certificate for %s\n(fp:"S_COLOR_GRAY"%s"S_COLOR_WHITE")\n"), hostname, fp),
(certlogproblems&CERTLOG_WRONGHOST)?localtext("^1Certificate does not match host\n"):"", (certlogproblems&CERTLOG_WRONGHOST)?localtext("^1Certificate does not match host\n"):"",
((certlogproblems&(CERTLOG_MISSINGCA|CERTLOG_WRONGHOST))==CERTLOG_MISSINGCA)?localtext("^1Certificate authority is untrusted.\n"):"", ((certlogproblems&(CERTLOG_MISSINGCA|CERTLOG_WRONGHOST))==CERTLOG_MISSINGCA)?localtext("^1Certificate authority is untrusted.\n"):"",
(certlogproblems&CERTLOG_EXPIRED)?localtext("^1Expired Certificate\n"):"", (certlogproblems&CERTLOG_EXPIRED)?localtext("^1Expired Certificate\n"):"",

View file

@ -151,7 +151,8 @@ neterr_t NET_SendPacket (struct ftenet_connections_s *col, int length, const voi
int NET_LocalAddressForRemote(struct ftenet_connections_s *collection, netadr_t *remote, netadr_t *local, int idx); int NET_LocalAddressForRemote(struct ftenet_connections_s *collection, netadr_t *remote, netadr_t *local, int idx);
void NET_PrintAddresses(struct ftenet_connections_s *collection); void NET_PrintAddresses(struct ftenet_connections_s *collection);
qboolean NET_AddressSmellsFunny(netadr_t *a); qboolean NET_AddressSmellsFunny(netadr_t *a);
qboolean NET_EnsureRoute(struct ftenet_connections_s *collection, char *routename, char *host, netadr_t *adr); struct dtlspeercred_s;
qboolean NET_EnsureRoute(struct ftenet_connections_s *collection, char *routename, const struct dtlspeercred_s *peerinfo, netadr_t *adr);
void NET_TerminateRoute(struct ftenet_connections_s *collection, netadr_t *adr); void NET_TerminateRoute(struct ftenet_connections_s *collection, netadr_t *adr);
void NET_PrintConnectionsStatus(struct ftenet_connections_s *collection); void NET_PrintConnectionsStatus(struct ftenet_connections_s *collection);
@ -191,9 +192,13 @@ qboolean FTENET_AddToCollection(struct ftenet_connections_s *col, const char *na
enum certprops_e enum certprops_e
{ {
QCERT_PEERFINGERPRINT QCERT_ISENCRYPTED, //0 or error
QCERT_PEERSUBJECT, //null terminated. should be a hash of the primary cert, ignoring chain.
QCERT_PEERCERTIFICATE, //should be the primary cert, ignoring chain. no fixed maximum size required, mostly 2k but probably best to allow at leasy 5k.. or 8k.
QCERT_LOCALCERTIFICATE, //the cert we're using/advertising. may have no context. to tell people what fp to expect.
}; };
size_t NET_GetConnectionCertificate(struct ftenet_connections_s *col, netadr_t *a, enum certprops_e prop, char *out, size_t outsize); int NET_GetConnectionCertificate(struct ftenet_connections_s *col, netadr_t *a, enum certprops_e prop, char *out, size_t outsize);
#ifdef HAVE_DTLS #ifdef HAVE_DTLS
struct dtlscred_s; struct dtlscred_s;
@ -207,7 +212,7 @@ extern cvar_t dtls_psk_hint, dtls_psk_user, dtls_psk_key;
#ifdef SUPPORT_ICE #ifdef SUPPORT_ICE
neterr_t ICE_SendPacket(size_t length, const void *data, netadr_t *to); neterr_t ICE_SendPacket(size_t length, const void *data, netadr_t *to);
void ICE_Terminate(netadr_t *to); //if we kicked the client/etc, kill their ICE too. void ICE_Terminate(netadr_t *to); //if we kicked the client/etc, kill their ICE too.
qboolean ICE_IsEncrypted(netadr_t *to); int ICE_GetPeerCertificate(netadr_t *to, enum certprops_e prop, char *out, size_t outsize);
void ICE_Init(void); void ICE_Init(void);
#endif #endif
extern cvar_t timeout; extern cvar_t timeout;

View file

@ -2103,9 +2103,12 @@ static qboolean QDECL ICE_Set(struct icestate_s *con, const char *prop, const ch
} }
else if (!strcmp(value, STRINGIFY(ICE_FAILED))) else if (!strcmp(value, STRINGIFY(ICE_FAILED)))
{ {
con->state = ICE_FAILED; if (con->state != ICE_FAILED)
if (net_ice_debug.ival >= 1) {
Con_Printf(S_COLOR_GRAY"[%s]: ice state failed\n", con->friendlyname); con->state = ICE_FAILED;
if (net_ice_debug.ival >= 1)
Con_Printf(S_COLOR_GRAY"[%s]: ice state failed\n", con->friendlyname);
}
} }
else if (!strcmp(value, STRINGIFY(ICE_CONNECTED))) else if (!strcmp(value, STRINGIFY(ICE_CONNECTED)))
{ {
@ -4805,7 +4808,7 @@ qboolean ICE_WasStun(ftenet_connections_t *col)
return false; return false;
} }
#ifdef SUPPORT_ICE #ifdef SUPPORT_ICE
qboolean ICE_IsEncrypted(netadr_t *to) int ICE_GetPeerCertificate(netadr_t *to, enum certprops_e prop, char *out, size_t outsize)
{ {
#ifdef HAVE_DTLS #ifdef HAVE_DTLS
struct icestate_s *con; struct icestate_s *con;
@ -4813,12 +4816,14 @@ qboolean ICE_IsEncrypted(netadr_t *to)
{ {
if (NET_CompareAdr(to, &con->qadr)) if (NET_CompareAdr(to, &con->qadr))
{ {
if (con->dtlsstate) if (con->dtlsstate && con->dtlsfuncs->GetPeerCertificate)
return true; return con->dtlsfuncs->GetPeerCertificate(con->dtlsstate, prop, out, outsize);
else if (prop==QCERT_ISENCRYPTED && con->dtlsstate)
return 0;
} }
} }
#endif #endif
return false; return -1;
} }
void ICE_Terminate(netadr_t *to) void ICE_Terminate(netadr_t *to)
{ {
@ -5243,14 +5248,16 @@ handleerror:
if (cl == -1) if (cl == -1)
{ {
b->error = true; b->error = true;
// Con_Printf("Broker closed connection: %s\n", data); if (net_ice_debug.ival)
Con_Printf(S_COLOR_GRAY"[%s]: Broker lost connection: %s\n", b->ice?b->ice->friendlyname:"?", *data?data:"<NO REASON>");
} }
else if (cl >= 0 && cl < b->numclients) else if (cl >= 0 && cl < b->numclients)
{ {
if (net_ice_debug.ival)
Con_Printf(S_COLOR_GRAY"[%s]: Broker lost connection: %s\n", b->clients[cl].ice?b->clients[cl].ice->friendlyname:"?", *data?data:"<NO REASON>");
if (b->clients[cl].ice) if (b->clients[cl].ice)
iceapi.Close(b->clients[cl].ice, false); iceapi.Close(b->clients[cl].ice, false);
b->clients[cl].ice = NULL; b->clients[cl].ice = NULL;
// Con_Printf("Broker closing connection: %s\n", data);
} }
break; break;
case ICEMSG_NAMEINUSE: case ICEMSG_NAMEINUSE:
@ -5273,13 +5280,23 @@ handleerror:
Z_ReallocElements((void**)&b->clients, &b->numclients, cl+1, sizeof(b->clients[0])); Z_ReallocElements((void**)&b->clients, &b->numclients, cl+1, sizeof(b->clients[0]));
} }
if (cl >= 0 && cl < b->numclients) if (cl >= 0 && cl < b->numclients)
{
FTENET_ICE_Establish(b, cl, &b->clients[cl].ice); FTENET_ICE_Establish(b, cl, &b->clients[cl].ice);
if (net_ice_debug.ival)
Con_Printf(S_COLOR_GRAY"[%s]: New client spotted...\n", b->clients[cl].ice?b->clients[cl].ice->friendlyname:"?");
}
else if (net_ice_debug.ival)
Con_Printf(S_COLOR_GRAY"[%s]: New client spotted, but index is unusable\n", "?");
} }
else else
{ {
// Con_DPrintf("Server found: %s\n", data); // Con_DPrintf("Server found: %s\n", data);
FTENET_ICE_Establish(b, cl, &b->ice); FTENET_ICE_Establish(b, cl, &b->ice);
b->serverid = cl; b->serverid = cl;
if (net_ice_debug.ival)
Con_Printf(S_COLOR_GRAY"[%s]: Relay to server now open\n", b->ice?b->ice->friendlyname:"?");
} }
break; break;
case ICEMSG_OFFER: //we received an offer from a client case ICEMSG_OFFER: //we received an offer from a client
@ -5299,19 +5316,31 @@ handleerror:
{ {
if (cl >= 0 && cl < b->numclients && b->clients[cl].ice) if (cl >= 0 && cl < b->numclients && b->clients[cl].ice)
{ {
if (net_ice_debug.ival)
Con_Printf(S_COLOR_GRAY"[%s]: Got offer:\n%s\n", b->clients[cl].ice?b->clients[cl].ice->friendlyname:"?", data);
iceapi.Set(b->clients[cl].ice, "sdpoffer", data); iceapi.Set(b->clients[cl].ice, "sdpoffer", data);
iceapi.Set(b->clients[cl].ice, "state", STRINGIFY(ICE_CONNECTING)); iceapi.Set(b->clients[cl].ice, "state", STRINGIFY(ICE_CONNECTING));
FTENET_ICE_SendOffer(b, cl, b->clients[cl].ice, "sdpanswer"); FTENET_ICE_SendOffer(b, cl, b->clients[cl].ice, "sdpanswer");
break;
} }
if (net_ice_debug.ival)
Con_Printf(S_COLOR_GRAY"[%s]: Got bad offer/answer:\n%s\n", b->clients[cl].ice?b->clients[cl].ice->friendlyname:"?", data);
} }
else else
{ {
if (b->ice) if (b->ice)
{ {
if (net_ice_debug.ival)
Con_Printf(S_COLOR_GRAY"[%s]: Got answer:\n%s\n", b->ice?b->ice->friendlyname:"?", data);
iceapi.Set(b->ice, "sdpanswer", data); iceapi.Set(b->ice, "sdpanswer", data);
iceapi.Set(b->ice, "state", STRINGIFY(ICE_CONNECTING)); iceapi.Set(b->ice, "state", STRINGIFY(ICE_CONNECTING));
break;
} }
if (net_ice_debug.ival)
Con_Printf(S_COLOR_GRAY"[%s]: Got bad offer/answer:\n%s\n", b->ice?b->ice->friendlyname:"?", data);
} }
break; break;
case ICEMSG_CANDIDATE: case ICEMSG_CANDIDATE:
@ -5327,12 +5356,20 @@ handleerror:
if (b->generic.islisten) if (b->generic.islisten)
{ {
if (cl >= 0 && cl < b->numclients && b->clients[cl].ice) if (cl >= 0 && cl < b->numclients && b->clients[cl].ice)
{
if (net_ice_debug.ival)
Con_Printf(S_COLOR_GRAY"[%s]: Got candidate:\n%s\n", b->clients[cl].ice->friendlyname, data);
iceapi.Set(b->clients[cl].ice, "sdp", data); iceapi.Set(b->clients[cl].ice, "sdp", data);
}
} }
else else
{ {
if (b->ice) if (b->ice)
{
if (net_ice_debug.ival)
Con_Printf(S_COLOR_GRAY"[%s]: Got candidate:\n%s\n", b->ice->friendlyname, data);
iceapi.Set(b->ice, "sdp", data); iceapi.Set(b->ice, "sdp", data);
}
} }
break; break;
} }
@ -5479,4 +5516,4 @@ void ICE_Init(void)
Cvar_Register(&net_ice_debug, "networking"); Cvar_Register(&net_ice_debug, "networking");
Cmd_AddCommand("net_ice_show", ICE_Show_f); Cmd_AddCommand("net_ice_show", ICE_Show_f);
} }
#endif #endif

View file

@ -99,6 +99,7 @@
#define GNUTLS_X509_STUFF \ #define GNUTLS_X509_STUFF \
GNUTLS_FUNC(gnutls_certificate_server_set_request,void,(gnutls_session_t session, gnutls_certificate_request_t req)) \
GNUTLS_FUNC(gnutls_sec_param_to_pk_bits,unsigned int,(gnutls_pk_algorithm_t algo, gnutls_sec_param_t param)) \ GNUTLS_FUNC(gnutls_sec_param_to_pk_bits,unsigned int,(gnutls_pk_algorithm_t algo, gnutls_sec_param_t param)) \
GNUTLS_FUNC(gnutls_x509_crt_init,int,(gnutls_x509_crt_t * cert)) \ GNUTLS_FUNC(gnutls_x509_crt_init,int,(gnutls_x509_crt_t * cert)) \
GNUTLS_FUNC(gnutls_x509_crt_deinit,void,(gnutls_x509_crt_t cert)) \ GNUTLS_FUNC(gnutls_x509_crt_deinit,void,(gnutls_x509_crt_t cert)) \
@ -108,6 +109,7 @@
GNUTLS_FUNC(gnutls_x509_crt_set_expiration_time,int,(gnutls_x509_crt_t cert, time_t exp_time)) \ GNUTLS_FUNC(gnutls_x509_crt_set_expiration_time,int,(gnutls_x509_crt_t cert, time_t exp_time)) \
GNUTLS_FUNC(gnutls_x509_crt_set_serial,int,(gnutls_x509_crt_t cert, const void *serial, size_t serial_size)) \ GNUTLS_FUNC(gnutls_x509_crt_set_serial,int,(gnutls_x509_crt_t cert, const void *serial, size_t serial_size)) \
GNUTLS_FUNC(gnutls_x509_crt_set_dn,int,(gnutls_x509_crt_t crt, const char *dn, const char **err)) \ GNUTLS_FUNC(gnutls_x509_crt_set_dn,int,(gnutls_x509_crt_t crt, const char *dn, const char **err)) \
GNUTLS_FUNC(gnutls_x509_crt_get_dn3,int,(gnutls_x509_crt_t crt, gnutls_datum_t * dn, unsigned flags)) \
GNUTLS_FUNC(gnutls_x509_crt_set_issuer_dn,int,(gnutls_x509_crt_t crt,const char *dn, const char **err)) \ GNUTLS_FUNC(gnutls_x509_crt_set_issuer_dn,int,(gnutls_x509_crt_t crt,const char *dn, const char **err)) \
GNUTLS_FUNC(gnutls_x509_crt_set_key,int,(gnutls_x509_crt_t crt, gnutls_x509_privkey_t key)) \ GNUTLS_FUNC(gnutls_x509_crt_set_key,int,(gnutls_x509_crt_t crt, gnutls_x509_privkey_t key)) \
GNUTLS_FUNC(gnutls_x509_crt_export2,int,(gnutls_x509_crt_t cert, gnutls_x509_crt_fmt_t format, gnutls_datum_t * out)) \ GNUTLS_FUNC(gnutls_x509_crt_export2,int,(gnutls_x509_crt_t cert, gnutls_x509_crt_fmt_t format, gnutls_datum_t * out)) \
@ -123,7 +125,9 @@
GNUTLS_FUNC(gnutls_pubkey_init,int,(gnutls_pubkey_t * key)) \ GNUTLS_FUNC(gnutls_pubkey_init,int,(gnutls_pubkey_t * key)) \
GNUTLS_FUNC(gnutls_pubkey_deinit,void,(gnutls_pubkey_t key)) \ GNUTLS_FUNC(gnutls_pubkey_deinit,void,(gnutls_pubkey_t key)) \
GNUTLS_FUNC(gnutls_pubkey_import_x509,int,(gnutls_pubkey_t key, gnutls_x509_crt_t crt, unsigned int flags)) \ GNUTLS_FUNC(gnutls_pubkey_import_x509,int,(gnutls_pubkey_t key, gnutls_x509_crt_t crt, unsigned int flags)) \
GNUTLS_FUNC(gnutls_pubkey_verify_hash2,int,(gnutls_pubkey_t key, gnutls_sign_algorithm_t algo, unsigned int flags, const gnutls_datum_t * hash, const gnutls_datum_t * signature)) GNUTLS_FUNC(gnutls_pubkey_verify_hash2,int,(gnutls_pubkey_t key, gnutls_sign_algorithm_t algo, unsigned int flags, const gnutls_datum_t * hash, const gnutls_datum_t * signature)) \
GNUTLS_FUNC(gnutls_certificate_get_ours,const gnutls_datum_t*,(gnutls_session_t session)) \
GNUTLS_FUNC(gnutls_certificate_get_crt_raw,int,(gnutls_certificate_credentials_t sc, unsigned idx1, unsigned idx2, gnutls_datum_t * cert))
#define GNUTLS_FUNCS \ #define GNUTLS_FUNCS \
@ -548,7 +552,7 @@ static int QDECL SSL_CheckFingerprint(gnutls_session_t session)
if (!memcmp(digest, file->peerdigest, file->peerhashfunc->digestsize)) if (!memcmp(digest, file->peerdigest, file->peerhashfunc->digestsize))
return 0; return 0;
} }
Con_DPrintf(CON_ERROR "%s: rejecting certificate\n", file->certname); Con_Printf(CON_ERROR "%s: rejecting certificate\n", file->certname);
return GNUTLS_E_CERTIFICATE_ERROR; return GNUTLS_E_CERTIFICATE_ERROR;
} }
#endif #endif
@ -1084,7 +1088,10 @@ qboolean SSL_InitGlobal(qboolean isserver)
#endif #endif
} }
else else
{
qgnutls_certificate_set_verify_function (xcred[isserver], SSL_CheckCert); qgnutls_certificate_set_verify_function (xcred[isserver], SSL_CheckCert);
// qgnutls_certificate_set_retrieve_function (xcred[isserver], SSL_FindClientCert);
}
#endif #endif
} }
else else
@ -1151,7 +1158,7 @@ static int GetPSKForServer(gnutls_session_t sess, char **username, gnutls_datum_
if (strcmp(svhint, dtls_psk_user.string) || CalcHashInt(&hash_sha1, dtls_psk_key.string, strlen(dtls_psk_key.string)) != 0x3dd348e4) if (strcmp(svhint, dtls_psk_user.string) || CalcHashInt(&hash_sha1, dtls_psk_key.string, strlen(dtls_psk_key.string)) != 0x3dd348e4)
{ {
Con_Printf(CON_WARNING "Possible QEx Server, please set your ^[%s\\type\\%s^] and ^[%s\\type\\%s^] cvars correctly, their current values are likely to crash the server.\n", dtls_psk_user.name,dtls_psk_user.name, dtls_psk_key.name,dtls_psk_key.name); Con_Printf(CON_WARNING "Possible QEx Server, please set your ^[%s\\type\\%s^] and ^[%s\\type\\%s^] cvars correctly, their current values are likely to crash the server.\n", dtls_psk_user.name,dtls_psk_user.name, dtls_psk_key.name,dtls_psk_key.name);
return 0; //don't report anything. return -1; //don't report anything.
} }
} }
} }
@ -1174,7 +1181,10 @@ static int GetPSKForServer(gnutls_session_t sess, char **username, gnutls_datum_
static qboolean SSL_InitConnection(gnutlsfile_t *newf, qboolean isserver, qboolean datagram) static qboolean SSL_InitConnection(gnutlsfile_t *newf, qboolean isserver, qboolean datagram)
{ {
// Initialize TLS session // Initialize TLS session
qgnutls_init (&newf->session, GNUTLS_NONBLOCK|(isserver?GNUTLS_SERVER:GNUTLS_CLIENT)|(datagram?GNUTLS_DATAGRAM:0)); qgnutls_init (&newf->session, ((newf->certcred)?GNUTLS_FORCE_CLIENT_CERT:0)
|GNUTLS_NONBLOCK
|(isserver?GNUTLS_SERVER:GNUTLS_CLIENT)
|(datagram?GNUTLS_DATAGRAM:0));
if (!isserver) if (!isserver)
qgnutls_server_name_set(newf->session, GNUTLS_NAME_DNS, newf->certname, strlen(newf->certname)); qgnutls_server_name_set(newf->session, GNUTLS_NAME_DNS, newf->certname, strlen(newf->certname));
@ -1182,6 +1192,7 @@ static qboolean SSL_InitConnection(gnutlsfile_t *newf, qboolean isserver, qboole
if (newf->certcred) if (newf->certcred)
{ {
qgnutls_certificate_server_set_request(newf->session, GNUTLS_CERT_REQUIRE); //we will need to validate their fingerprint.
qgnutls_credentials_set (newf->session, GNUTLS_CRD_CERTIFICATE, newf->certcred); qgnutls_credentials_set (newf->session, GNUTLS_CRD_CERTIFICATE, newf->certcred);
qgnutls_set_default_priority (newf->session); qgnutls_set_default_priority (newf->session);
} }
@ -1192,6 +1203,8 @@ static qboolean SSL_InitConnection(gnutlsfile_t *newf, qboolean isserver, qboole
qgnutls_credentials_set (newf->session, GNUTLS_CRD_ANON, anoncred[isserver]); qgnutls_credentials_set (newf->session, GNUTLS_CRD_ANON, anoncred[isserver]);
#else #else
#ifdef HAVE_DTLS #ifdef HAVE_DTLS
qgnutls_certificate_server_set_request(newf->session, GNUTLS_CERT_REQUEST); //request a cert, we'll use it for fingerprints.
if (datagram && !isserver) if (datagram && !isserver)
{ //do psk as needed. we can still do the cert stuff if the server isn't doing psk. { //do psk as needed. we can still do the cert stuff if the server isn't doing psk.
gnutls_psk_client_credentials_t pskcred; gnutls_psk_client_credentials_t pskcred;
@ -1402,16 +1415,22 @@ static void *GNUDTLS_CreateContext(const dtlscred_t *credinfo, void *cbctx, nete
// Sys_Printf("DTLS_CreateContext: server=%i\n", isserver); // Sys_Printf("DTLS_CreateContext: server=%i\n", isserver);
if (credinfo && credinfo->local.cert && credinfo->local.key && credinfo->peer.hash) if (credinfo && ((credinfo->local.cert && credinfo->local.key) || credinfo->peer.hash))
{ {
gnutls_datum_t pub = {credinfo->local.cert, credinfo->local.certsize},
priv = {credinfo->local.key, credinfo->local.keysize};
qgnutls_certificate_allocate_credentials (&newf->certcred); qgnutls_certificate_allocate_credentials (&newf->certcred);
qgnutls_certificate_set_x509_key_mem(newf->certcred, &pub, &priv, GNUTLS_X509_FMT_DER); if (credinfo->local.cert && credinfo->local.key)
{
gnutls_datum_t pub = {credinfo->local.cert, credinfo->local.certsize},
priv = {credinfo->local.key, credinfo->local.keysize};
qgnutls_certificate_set_x509_key_mem(newf->certcred, &pub, &priv, GNUTLS_X509_FMT_DER);
}
newf->peerhashfunc = credinfo->peer.hash; if (credinfo->peer.hash)
memcpy(newf->peerdigest, credinfo->peer.digest, newf->peerhashfunc->digestsize); {
qgnutls_certificate_set_verify_function (newf->certcred, SSL_CheckFingerprint); newf->peerhashfunc = credinfo->peer.hash;
memcpy(newf->peerdigest, credinfo->peer.digest, newf->peerhashfunc->digestsize);
qgnutls_certificate_set_verify_function (newf->certcred, SSL_CheckFingerprint);
}
} }
SSL_SetCertificateName(newf, credinfo?credinfo->peer.name:NULL); SSL_SetCertificateName(newf, credinfo?credinfo->peer.name:NULL);
@ -1442,6 +1461,9 @@ static neterr_t GNUDTLS_Transmit(void *ctx, const qbyte *data, size_t datasize)
return NETERR_DISCONNECTED; return NETERR_DISCONNECTED;
} }
if (!datasize)
return NETERR_SENT;
ret = qgnutls_record_send(f->session, data, datasize); ret = qgnutls_record_send(f->session, data, datasize);
if (ret < 0) if (ret < 0)
{ {
@ -1462,17 +1484,39 @@ static neterr_t GNUDTLS_Received(void *ctx, sizebuf_t *message)
if (f->challenging) if (f->challenging)
{ {
int cli_addr = 0xdeadbeef; //FIXME: replace with client's IP:port. size_t asize;
safeswitch (net_from.type)
{
case NA_LOOPBACK: asize = 0; break;
case NA_IP: asize = sizeof(net_from.address.ip); break;
case NA_IPV6: asize = sizeof(net_from.address.ip6); break;
case NA_IPX: asize = sizeof(net_from.address.ipx); break;
#ifdef UNIXSOCKETS
case NA_UNIX: asize = (qbyte*)&net_from.address.un.path[net_from.address.un.len]-(qbyte*)&net_from.address; break; //unlikely to be spoofed...
#endif
#ifdef IRCCONNECT
//case NA_IRC:
#endif
#ifdef HAVE_WEBSOCKCL
//case NA_WEBSOCKET: //basically web browser.
#endif
#ifdef SUPPORT_ICE
case NA_ICE: asize = strlen(net_from.address.icename); break;
#endif
case NA_INVALID:
safedefault: return NETERR_NOROUTE;
}
memset(&f->prestate, 0, sizeof(f->prestate)); memset(&f->prestate, 0, sizeof(f->prestate));
ret = qgnutls_dtls_cookie_verify(&cookie_key, ret = qgnutls_dtls_cookie_verify(&cookie_key,
&cli_addr, sizeof(cli_addr), &net_from.address, asize,
message->data, message->cursize, message->data, message->cursize,
&f->prestate); &f->prestate);
if (ret == GNUTLS_E_BAD_COOKIE) if (ret == GNUTLS_E_BAD_COOKIE)
{ {
qgnutls_dtls_cookie_send(&cookie_key, qgnutls_dtls_cookie_send(&cookie_key,
&cli_addr, sizeof(cli_addr), &net_from.address, asize,
&f->prestate, &f->prestate,
(gnutls_transport_ptr_t)f, DTLS_Push); (gnutls_transport_ptr_t)f, DTLS_Push);
return NETERR_CLOGGED; return NETERR_CLOGGED;
@ -1599,6 +1643,74 @@ static neterr_t GNUDTLS_Timeouts(void *ctx)
return NETERR_SENT; return NETERR_SENT;
} }
static int GNUDTLS_GetPeerCertificate(void *ctx, enum certprops_e prop, char *out, size_t outsize)
{
gnutlsfile_t *f = (gnutlsfile_t *)ctx;
if (f && (f->challenging || f->handshaking))
return -1; //no cert locked down yet...
safeswitch(prop)
{
case QCERT_ISENCRYPTED:
return 0; //well, should be...
case QCERT_PEERSUBJECT:
{
unsigned int certcount;
const gnutls_datum_t *const certlist = qgnutls_certificate_get_peers(f->session, &certcount);
if (certlist)
{
gnutls_x509_crt_t cert = NULL;
gnutls_datum_t dn={NULL};
qgnutls_x509_crt_init(&cert);
qgnutls_x509_crt_import(cert, certlist, GNUTLS_X509_FMT_DER);
qgnutls_x509_crt_get_dn3(cert, &dn, 0);
if (dn.size >= outsize)
dn.size = -1; //too big...
else
{
memcpy(out, dn.data, dn.size);
out[dn.size] = 0;
}
(*qgnutls_free)(dn.data);
qgnutls_x509_crt_deinit(cert);
return (int)dn.size;
}
}
return -1;
case QCERT_PEERCERTIFICATE:
{
unsigned int certcount;
const gnutls_datum_t *const certlist = qgnutls_certificate_get_peers(f->session, &certcount);
if (certlist && certlist->size <= outsize)
{
memcpy(out, certlist->data, certlist->size);
return certlist->size;
}
}
return -1;
case QCERT_LOCALCERTIFICATE:
{
const gnutls_datum_t *cert;
gnutls_datum_t d;
if (f)
cert = qgnutls_certificate_get_ours(f->session);
else //no actual context? get our default dtls server cert.
{
qgnutls_certificate_get_crt_raw (xcred[true], 0/*first chain*/, 0/*primary one*/, &d);
cert = &d;
}
if (cert->size <= outsize)
{
memcpy(out, cert->data, cert->size);
return cert->size;
}
}
return -1;
safedefault:
return -1; //dunno what you want from me.
}
}
static qboolean GNUDTLS_GenTempCertificate(const char *subject, struct dtlslocalcred_s *qcred) static qboolean GNUDTLS_GenTempCertificate(const char *subject, struct dtlslocalcred_s *qcred)
{ {
gnutls_datum_t priv = {NULL}, pub = {NULL}; gnutls_datum_t priv = {NULL}, pub = {NULL};
@ -1681,7 +1793,7 @@ static const dtlsfuncs_t dtlsfuncs_gnutls =
GNUDTLS_Transmit, GNUDTLS_Transmit,
GNUDTLS_Received, GNUDTLS_Received,
GNUDTLS_Timeouts, GNUDTLS_Timeouts,
NULL, GNUDTLS_GetPeerCertificate,
GNUDTLS_GenTempCertificate GNUDTLS_GenTempCertificate
}; };
static const dtlsfuncs_t *GNUDTLS_InitServer(void) static const dtlsfuncs_t *GNUDTLS_InitServer(void)

File diff suppressed because it is too large Load diff

View file

@ -155,6 +155,9 @@ cvar_t net_enable_websockets = CVARD("net_enable_websockets", "1", "If enabled,
#if defined(HAVE_SERVER) #if defined(HAVE_SERVER)
static void QDECL NET_Enable_DTLS_Changed(struct cvar_s *var, char *oldvalue) static void QDECL NET_Enable_DTLS_Changed(struct cvar_s *var, char *oldvalue)
{ {
char cert[8192];
int certsize;
char digest[DIGEST_MAXSIZE];
var->ival = var->value; var->ival = var->value;
//set up the default value //set up the default value
if (!*var->string) if (!*var->string)
@ -165,6 +168,12 @@ static void QDECL NET_Enable_DTLS_Changed(struct cvar_s *var, char *oldvalue)
svs.sockets->dtlsfuncs = (var->ival)?DTLS_InitServer():NULL; svs.sockets->dtlsfuncs = (var->ival)?DTLS_InitServer():NULL;
if (!svs.sockets->dtlsfuncs && var->ival >= 2) if (!svs.sockets->dtlsfuncs && var->ival >= 2)
Con_Printf("%sUnable to set %s to \"%s\", no DTLS provider available.\n", (var->ival >= 2)?CON_ERROR:CON_WARNING, var->name, var->string); Con_Printf("%sUnable to set %s to \"%s\", no DTLS provider available.\n", (var->ival >= 2)?CON_ERROR:CON_WARNING, var->name, var->string);
certsize = svs.sockets->dtlsfuncs?svs.sockets->dtlsfuncs->GetPeerCertificate(NULL, QCERT_LOCALCERTIFICATE, cert, sizeof(cert)):-1;
if (certsize > 0)
InfoBuf_SetStarBlobKey(&svs.info, "*fp", cert, Base64_EncodeBlockURI(digest, CalcHash(&hash_sha1, digest, sizeof(digest), cert, certsize), cert, sizeof(cert)));
else
InfoBuf_SetStarKey(&svs.info, "*fp", "");
} }
} }
cvar_t net_enable_dtls = CVARAFCD("net_enable_dtls", "", "sv_listen_dtls", 0, NET_Enable_DTLS_Changed, "Controls serverside dtls support.\n0: dtls blocked, not advertised.\n1: clientside choice.\n2: used where possible (recommended setting).\n3: disallow non-dtls clients (sv_port_tcp should be eg tls://[::]:27500 to also disallow unencrypted tcp connections)."); cvar_t net_enable_dtls = CVARAFCD("net_enable_dtls", "", "sv_listen_dtls", 0, NET_Enable_DTLS_Changed, "Controls serverside dtls support.\n0: dtls blocked, not advertised.\n1: clientside choice.\n2: used where possible (recommended setting).\n3: disallow non-dtls clients (sv_port_tcp should be eg tls://[::]:27500 to also disallow unencrypted tcp connections).");
@ -1565,6 +1574,7 @@ size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t num
netproto_t prot; netproto_t prot;
netadrtype_t afhint; netadrtype_t afhint;
char *path; char *path;
char *args;
struct struct
{ {
@ -1793,6 +1803,10 @@ size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t num
} }
} }
args = strchr(s, '?');
if (args)
*args=0;
path = strchr(s, '/'); path = strchr(s, '/');
#if !defined(HAVE_WEBSOCKCL) && defined(SUPPORT_ICE) #if !defined(HAVE_WEBSOCKCL) && defined(SUPPORT_ICE)
if (path == s && fs_manifest->rtcbroker && *fs_manifest->rtcbroker) if (path == s && fs_manifest->rtcbroker && *fs_manifest->rtcbroker)
@ -1829,6 +1843,9 @@ size_t NET_StringToAdr2 (const char *s, int defaultport, netadr_t *a, size_t num
a[i].prot = prot; a[i].prot = prot;
} }
if (args)
*args='?';
return result; return result;
} }
@ -2132,7 +2149,7 @@ qboolean NET_IsEncrypted(netadr_t *adr)
if (adr->type == NA_LOOPBACK) if (adr->type == NA_LOOPBACK)
return true; //might as well claim it, others can't snoop on it so... return true; //might as well claim it, others can't snoop on it so...
#ifdef SUPPORT_ICE #ifdef SUPPORT_ICE
if (adr->type == NA_ICE && ICE_IsEncrypted(adr)) if (adr->type == NA_ICE && ICE_GetPeerCertificate(adr, QCERT_ISENCRYPTED, NULL, 0)==0)
return true; return true;
#endif #endif
#if defined(FTE_TARGET_WEB) #if defined(FTE_TARGET_WEB)
@ -3230,7 +3247,8 @@ qboolean NET_DTLS_Decode(ftenet_connections_t *col)
switch(peer->funcs->Received(peer->dtlsstate, &net_message)) switch(peer->funcs->Received(peer->dtlsstate, &net_message))
{ {
case NETERR_DISCONNECTED: case NETERR_DISCONNECTED:
NET_DTLS_DisconnectPeer(col, peer); if (col->islisten)
NET_DTLS_DisconnectPeer(col, peer);
net_message.cursize = 0; net_message.cursize = 0;
break; break;
case NETERR_NOROUTE: case NETERR_NOROUTE:
@ -3254,36 +3272,33 @@ qboolean NET_DTLS_Decode(ftenet_connections_t *col)
#endif #endif
size_t NET_GetConnectionCertificate(struct ftenet_connections_s *col, netadr_t *a, enum certprops_e prop, char *out, size_t outsize) int NET_GetConnectionCertificate(struct ftenet_connections_s *col, netadr_t *a, enum certprops_e prop, char *out, size_t outsize)
{ {
if (!col) if (!col)
return 0; return -1;
switch(prop) #ifdef SUPPORT_ICE
{ if (a->type == NA_ICE)
default: return ICE_GetPeerCertificate(a, prop, out, outsize);
break;
case QCERT_PEERFINGERPRINT:
#if 0//def HAVE_DTLS
if (a->prot == NP_DTLS)
{
struct dtlspeer_s *peer;
{
a->prot = NP_DGRAM;
for (peer = col->dtls; peer; peer = peer->next)
{
if (NET_CompareAdr(&peer->addr, a))
break;
}
a->prot = NP_DTLS;
}
if (peer)
return peer->funcs->GetPeerCertificate(peer->dtlsstate, data, length);
}
#endif #endif
return 0; #ifdef HAVE_DTLS
if (a->prot == NP_DTLS)
{
struct dtlspeer_s *peer;
{
a->prot = NP_DGRAM;
for (peer = col->dtls; peer; peer = peer->next)
{
if (NET_CompareAdr(&peer->addr, a))
break;
}
a->prot = NP_DTLS;
}
if (peer && peer->funcs->GetPeerCertificate)
return peer->funcs->GetPeerCertificate(peer->dtlsstate, prop, out, outsize);
} }
return 0; #endif
return -1;
} }
@ -8212,7 +8227,7 @@ neterr_t NET_SendPacket (ftenet_connections_t *collection, int length, const voi
return NET_SendPacketCol (collection, length, data, to); return NET_SendPacketCol (collection, length, data, to);
} }
qboolean NET_EnsureRoute(ftenet_connections_t *collection, char *routename, char *host, netadr_t *adr) qboolean NET_EnsureRoute(ftenet_connections_t *collection, char *routename, const struct dtlspeercred_s *peerinfo, netadr_t *adr)
{ {
switch(adr->prot) switch(adr->prot)
{ {
@ -8225,11 +8240,11 @@ qboolean NET_EnsureRoute(ftenet_connections_t *collection, char *routename, char
case NP_DTLS: case NP_DTLS:
#ifdef HAVE_DTLS #ifdef HAVE_DTLS
adr->prot = NP_DGRAM; adr->prot = NP_DGRAM;
if (NET_EnsureRoute(collection, routename, host, adr)) if (NET_EnsureRoute(collection, routename, peerinfo, adr))
{ {
dtlscred_t cred; dtlscred_t cred;
memset(&cred, 0, sizeof(cred)); memset(&cred, 0, sizeof(cred));
cred.peer.name = host; cred.peer = *peerinfo;
if (NET_DTLS_Create(collection, adr, &cred)) if (NET_DTLS_Create(collection, adr, &cred))
{ {
adr->prot = NP_DTLS; adr->prot = NP_DTLS;
@ -8243,14 +8258,14 @@ qboolean NET_EnsureRoute(ftenet_connections_t *collection, char *routename, char
case NP_WSS: case NP_WSS:
case NP_TLS: case NP_TLS:
case NP_STREAM: case NP_STREAM:
if (!FTENET_AddToCollection(collection, routename, host, adr->type, adr->prot)) if (!FTENET_AddToCollection(collection, routename, peerinfo->name, adr->type, adr->prot))
return false; return false;
Con_Printf("Establishing connection to %s\n", host); Con_Printf("Establishing connection to %s\n", peerinfo->name);
break; break;
#if defined(SUPPORT_ICE) || defined(FTE_TARGET_WEB) #if defined(SUPPORT_ICE) || defined(FTE_TARGET_WEB)
case NP_RTC_TCP: case NP_RTC_TCP:
case NP_RTC_TLS: case NP_RTC_TLS:
if (!FTENET_AddToCollection(collection, routename, host, adr->type, adr->prot)) if (!FTENET_AddToCollection(collection, routename, peerinfo->name, adr->type, adr->prot))
return false; return false;
break; break;
#endif #endif
@ -9478,7 +9493,10 @@ int QDECL VFSTCP_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestorea
{ {
trying = VFSTCP_IsStillConnecting(tf->sock); trying = VFSTCP_IsStillConnecting(tf->sock);
if (trying < 0) if (trying < 0)
{
tf->readaborted = trying; tf->readaborted = trying;
tf->writeaborted = true;
}
else if (trying) else if (trying)
return 0; return 0;
tf->conpending = false; tf->conpending = false;
@ -9509,18 +9527,22 @@ int QDECL VFSTCP_ReadBytes (struct vfsfile_s *file, void *buffer, int bytestorea
case NET_ENOTCONN: case NET_ENOTCONN:
Con_Printf("connection to \"%s\" failed\n", tf->peer); Con_Printf("connection to \"%s\" failed\n", tf->peer);
tf->readaborted = VFS_ERROR_NORESPONSE; tf->readaborted = VFS_ERROR_NORESPONSE;
tf->writeaborted = true;
break; break;
case NET_ECONNABORTED: case NET_ECONNABORTED:
Con_DPrintf("connection to \"%s\" aborted\n", tf->peer); Con_DPrintf("connection to \"%s\" aborted\n", tf->peer);
tf->readaborted = VFS_ERROR_NORESPONSE; tf->readaborted = VFS_ERROR_NORESPONSE;
tf->writeaborted = true;
break; break;
case NET_ETIMEDOUT: case NET_ETIMEDOUT:
Con_Printf("connection to \"%s\" timed out\n", tf->peer); Con_Printf("connection to \"%s\" timed out\n", tf->peer);
tf->readaborted = VFS_ERROR_NORESPONSE; tf->readaborted = VFS_ERROR_NORESPONSE;
tf->writeaborted = true;
break; break;
case NET_ECONNREFUSED: case NET_ECONNREFUSED:
Con_DPrintf("connection to \"%s\" refused\n", tf->peer); Con_DPrintf("connection to \"%s\" refused\n", tf->peer);
tf->readaborted = VFS_ERROR_REFUSED; tf->readaborted = VFS_ERROR_REFUSED;
tf->writeaborted = true;
break; break;
case NET_ECONNRESET: case NET_ECONNRESET:
Con_DPrintf("connection to \"%s\" reset\n", tf->peer); Con_DPrintf("connection to \"%s\" reset\n", tf->peer);
@ -9579,7 +9601,7 @@ int QDECL VFSTCP_WriteBytes (struct vfsfile_s *file, const void *buffer, int byt
tf->conpending = false; tf->conpending = false;
} }
len = send(tf->sock, buffer, bytestoread, 0); len = send(tf->sock, buffer, bytestoread, MSG_NOSIGNAL);
if (len == -1 || len == 0) if (len == -1 || len == 0)
{ {
int reason = VFS_ERROR_UNSPECIFIED; int reason = VFS_ERROR_UNSPECIFIED;
@ -9591,9 +9613,13 @@ int QDECL VFSTCP_WriteBytes (struct vfsfile_s *file, const void *buffer, int byt
return 0; //nothing available yet. return 0; //nothing available yet.
case NET_ETIMEDOUT: case NET_ETIMEDOUT:
Con_Printf("connection to \"%s\" timed out\n", tf->peer); Con_Printf("connection to \"%s\" timed out\n", tf->peer);
tf->writeaborted = true;
tf->conpending = false;
return VFS_ERROR_NORESPONSE; //don't bother trying to read if we never connected. return VFS_ERROR_NORESPONSE; //don't bother trying to read if we never connected.
case NET_ECONNREFUSED: //peer sent a reset instead of accepting a new connection case NET_ECONNREFUSED: //peer sent a reset instead of accepting a new connection
Con_DPrintf("connection to \"%s\" refused\n", tf->peer); Con_DPrintf("connection to \"%s\" refused\n", tf->peer);
tf->writeaborted = true;
tf->conpending = false;
return VFS_ERROR_REFUSED; //don't bother trying to read if we never connected. return VFS_ERROR_REFUSED; //don't bother trying to read if we never connected.
case NET_ECONNABORTED: //peer closed its socket case NET_ECONNABORTED: //peer closed its socket
Con_Printf("connection to \"%s\" aborted\n", tf->peer); Con_Printf("connection to \"%s\" aborted\n", tf->peer);
@ -9608,6 +9634,8 @@ int QDECL VFSTCP_WriteBytes (struct vfsfile_s *file, const void *buffer, int byt
case EPIPE: case EPIPE:
#endif #endif
Con_Printf("connection to \"%s\" failed\n", tf->peer); Con_Printf("connection to \"%s\" failed\n", tf->peer);
tf->writeaborted = true;
tf->conpending = false;
return VFS_ERROR_NORESPONSE; //don't bother trying to read if we never connected. return VFS_ERROR_NORESPONSE; //don't bother trying to read if we never connected.
default: default:
Sys_Printf("tcp socket error %i (%s)\n", e, tf->peer); Sys_Printf("tcp socket error %i (%s)\n", e, tf->peer);

View file

@ -223,6 +223,9 @@
#ifndef INVALID_SOCKET #ifndef INVALID_SOCKET
#define INVALID_SOCKET -1 #define INVALID_SOCKET -1
#endif #endif
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0 //available on linux, no idea about other unixes. don't bug out too much... (d)tls needs this to not get constant SIGPIPE errors
#endif
#ifndef INADDR_LOOPBACK #ifndef INADDR_LOOPBACK
#define INADDR_LOOPBACK 0x7f000001 #define INADDR_LOOPBACK 0x7f000001
@ -372,7 +375,7 @@ typedef struct dtlsfuncs_s
neterr_t (*Transmit)(void *ctx, const qbyte *data, size_t datasize); neterr_t (*Transmit)(void *ctx, const qbyte *data, size_t datasize);
neterr_t (*Received)(void *ctx, sizebuf_t *message); //operates in-place... neterr_t (*Received)(void *ctx, sizebuf_t *message); //operates in-place...
neterr_t (*Timeouts)(void *ctx); neterr_t (*Timeouts)(void *ctx);
void (*GetPeerCertificate)(void *ctx); int (*GetPeerCertificate)(void *ctx, enum certprops_e prop, char *out, size_t outsize);
qboolean (*GenTempCertificate)(const char *subject, struct dtlslocalcred_s *cred); qboolean (*GenTempCertificate)(const char *subject, struct dtlslocalcred_s *cred);
} dtlsfuncs_t; } dtlsfuncs_t;
#ifdef HAVE_DTLS #ifdef HAVE_DTLS

View file

@ -6462,6 +6462,18 @@ char *PF_infokey_Internal (int entnum, const char *key)
sprintf(ov, "%d", SV_CalcPing (&svs.clients[entnum-1], true)); sprintf(ov, "%d", SV_CalcPing (&svs.clients[entnum-1], true));
else if (!strcmp(key, "guid")) else if (!strcmp(key, "guid"))
sprintf(ov, "%s", pl->guid); sprintf(ov, "%s", pl->guid);
else if (!strcmp(key, "*cert_sha1"))
{
char buf[8192];
char digest[DIGEST_MAXSIZE];
int certsize = NET_GetConnectionCertificate(svs.sockets, &controller->netchan.remote_address, QCERT_PEERCERTIFICATE, buf, sizeof(buf));
if (certsize <= 0)
value = "";
else
Base64_EncodeBlockURI(digest,CalcHash(&hash_sha1, digest, sizeof(digest), buf, certsize), ov, sizeof(ov));
}
else if (!strcmp(key, "*cert_dn"))
NET_GetConnectionCertificate(svs.sockets, &controller->netchan.remote_address, QCERT_PEERSUBJECT, ov, sizeof(ov));
else if (!strcmp(key, "challenge")) else if (!strcmp(key, "challenge"))
sprintf(ov, "%u", pl->challenge); sprintf(ov, "%u", pl->challenge);
else if (!strcmp(key, "*userid")) else if (!strcmp(key, "*userid"))
@ -12567,7 +12579,7 @@ void PR_ResetBuiltins(progstype_t type) //fix all nulls to PF_FIXME and add any
} }
} }
if (!BuiltinList[i].name) if (!BuiltinList[i].name)
Con_Printf("Failed to map builtin %s to %i specified in fte_bimap.dat\n", com_token, binum); Con_Printf("Failed to map builtin %s to %i specified in fte_bimap.txt\n", com_token, binum);
} }
} }
} }
@ -13688,6 +13700,8 @@ void PR_DumpPlatform_f(void)
{"INFOKEY_P_CSQCACTIVE","const string", QW|NQ, D("Client has csqc enabled. CSQC ents etc will be sent to this player."), 0, "\"csqcactive\""}, {"INFOKEY_P_CSQCACTIVE","const string", QW|NQ, D("Client has csqc enabled. CSQC ents etc will be sent to this player."), 0, "\"csqcactive\""},
{"INFOKEY_P_SVPING", "const string", QW|NQ, NULL, 0, "\"svping\""}, {"INFOKEY_P_SVPING", "const string", QW|NQ, NULL, 0, "\"svping\""},
{"INFOKEY_P_GUID", "const string", QW|NQ, D("Some hash string which should be reasonably unique to this player's quake installation."), 0, "\"guid\""}, {"INFOKEY_P_GUID", "const string", QW|NQ, D("Some hash string which should be reasonably unique to this player's quake installation."), 0, "\"guid\""},
{"INFOKEY_P_CERT_SHA1", "const string", QW|NQ, D("Obtains the client's (d)tls certificate's fingerprint."), 0, "\"*cert_sha1\""},
{"INFOKEY_P_CERT_DN", "const string", QW|NQ, D("Obtains the client's (d)tls certificate's Distinguished Name string."), 0, "\"*cert_dn\""},
{"INFOKEY_P_CHALLENGE", "const string", QW|NQ, NULL, 0, "\"challenge\""}, {"INFOKEY_P_CHALLENGE", "const string", QW|NQ, NULL, 0, "\"challenge\""},
{"INFOKEY_P_USERID", "const string", QW|NQ, NULL, 0, "\"*userid\""}, {"INFOKEY_P_USERID", "const string", QW|NQ, NULL, 0, "\"*userid\""},
{"INFOKEY_P_DOWNLOADPCT","const string",QW|NQ, D("The client's download percentage for the current file. Additional files are not known."), 0, "\"download\""}, {"INFOKEY_P_DOWNLOADPCT","const string",QW|NQ, D("The client's download percentage for the current file. Additional files are not known."), 0, "\"download\""},

View file

@ -1649,7 +1649,7 @@ typedef struct
{ {
qboolean hasauthed; qboolean hasauthed;
qboolean isreverse; qboolean isreverse;
char challenge[64]; char challenge[64]; //aka nonce
} qtvpendingstate_t; } qtvpendingstate_t;
int SV_MVD_GotQTVRequest(vfsfile_t *clientstream, char *headerstart, char *headerend, qtvpendingstate_t *p); int SV_MVD_GotQTVRequest(vfsfile_t *clientstream, char *headerstart, char *headerend, qtvpendingstate_t *p);
#endif #endif

View file

@ -2139,7 +2139,7 @@ static void SV_Status_f (void)
int i; int i;
client_t *cl; client_t *cl;
float cpu; float cpu;
char *s, *p; char *s, *p, *sec;
char adr[MAX_ADR_SIZE]; char adr[MAX_ADR_SIZE];
float pi, po, bi, bo; float pi, po, bi, bo;
@ -2209,6 +2209,10 @@ static void SV_Status_f (void)
else else
s = "private"; s = "private";
Con_TPrintf("public : %s\n", s); Con_TPrintf("public : %s\n", s);
#ifdef HAVE_DTLS
Con_TPrintf("fingerprint : "S_COLOR_GRAY"%s\n", InfoBuf_ValueForKey(&svs.info, "*fp"));
#endif
switch(svs.gametype) switch(svs.gametype)
{ {
#ifdef Q3SERVER #ifdef Q3SERVER
@ -2223,38 +2227,38 @@ static void SV_Status_f (void)
#endif #endif
default: default:
Con_TPrintf("client types :%s", sv_listen_qw.ival?" QW":""); Con_TPrintf("client types :%s", sv_listen_qw.ival?" ^[QW\\tip\\This is "FULLENGINENAME"'s standard protocol.^]":"");
#ifdef NQPROT #ifdef NQPROT
Con_TPrintf("%s%s", (sv_listen_nq.ival==2)?" -NQ":(sv_listen_nq.ival?" NQ":""), sv_listen_dp.ival?" DP":""); Con_TPrintf("%s%s", (sv_listen_nq.ival==2)?" ^[-NQ\\tip\\Allows 'Net'/'Normal' Quake clients to connect, with cookies and extensions that might confuse some old clients^]":(sv_listen_nq.ival?" ^[NQ\\tip\\Vanilla/Normal Quake protocol with maximum compatibility^]":""), sv_listen_dp.ival?" ^[DP\\tip\\Explicitly recognise connection requests from DP clients.^]":"");
#endif #endif
#ifdef QWOVERQ3 #ifdef QWOVERQ3
if (sv_listen_q3.ival) Con_Printf(" Q3"); if (sv_listen_q3.ival) Con_Printf(" Q3");
#endif #endif
#ifdef HAVE_DTLS #ifdef HAVE_DTLS
if (net_enable_dtls.ival >= 3) if (net_enable_dtls.ival >= 3)
Con_Printf(" DTLS-only"); Con_Printf(" ^[DTLS-only\\tip\\Insecure clients (those without support for DTLS) will be barred from connecting.^]");
else if (net_enable_dtls.ival) else if (net_enable_dtls.ival)
Con_Printf(" DTLS"); Con_Printf(" ^[DTLS\\tip\\Clients may optionally connect via DTLS for added security^]");
#endif #endif
Con_Printf("\n"); Con_Printf("\n");
#if defined(TCPCONNECT) && !defined(CLIENTONLY) #if defined(TCPCONNECT) && !defined(CLIENTONLY)
Con_TPrintf("tcp services :"); Con_TPrintf("tcp services :");
#if defined(HAVE_SSL) #if defined(HAVE_SSL)
if (net_enable_tls.ival) if (net_enable_tls.ival)
Con_Printf(" TLS"); Con_Printf(" ^[TLS\\tip\\Clients are able to connect with Transport Layer Security for the other services, allowing for the use of tls://, wss:// or https:// schemes when their underlaying protocol is enabled.^]");
#endif #endif
#ifdef HAVE_HTTPSV #ifdef HAVE_HTTPSV
if (net_enable_http.ival) if (net_enable_http.ival)
Con_Printf(" HTTP"); Con_Printf(" ^[HTTP\\tip\\This server also acts as a web server. This might be useful to allow hosting demos or stats.^]");
if (net_enable_rtcbroker.ival) if (net_enable_rtcbroker.ival)
Con_Printf(" RTC"); Con_Printf(" ^[RTC\\tip\\This server is set up to act as a webrtc broker, allowing clients+servers to locate each other instead of playing on this server.^]");
if (net_enable_websockets.ival) if (net_enable_websockets.ival)
Con_Printf(" WebSocket"); Con_Printf(" ^[WebSocket\\tip\\Clients can use the ws:// or possibly wss:// schemes to connect to this server, potentially from browser ports. This may be laggy.^]");
#endif #endif
if (net_enable_qizmo.ival) if (net_enable_qizmo.ival)
Con_Printf(" Qizmo"); Con_Printf(" ^[Qizmo\\tip\\Compatible with the tcp connection feature of qizmo, equivelent to 'connect tcp://ip:port' in FTE.^]");
if (net_enable_qtv.ival) if (net_enable_qtv.ival)
Con_Printf(" QTV"); Con_Printf(" ^[QTV\\tip\\Allows receiving streamed mvd data from this server.^]");
Con_Printf("\n"); Con_Printf("\n");
#endif #endif
break; break;
@ -2269,7 +2273,7 @@ static void SV_Status_f (void)
Con_TPrintf("map uptime : %s\n", ShowTime(sv.world.physicstime)); Con_TPrintf("map uptime : %s\n", ShowTime(sv.world.physicstime));
//show the current map+name (but hide name if its too long or would be ugly) //show the current map+name (but hide name if its too long or would be ugly)
if (columns >= 80 && *sv.mapname && strlen(sv.mapname) < 45 && !strchr(sv.mapname, '\n')) if (columns >= 80 && *sv.mapname && strlen(sv.mapname) < 45 && !strchr(sv.mapname, '\n'))
Con_TPrintf ("current map : %s (%s)\n", svs.name, sv.mapname); Con_TPrintf ("current map : %s "S_COLOR_GRAY"(%s)\n", svs.name, sv.mapname);
else else
Con_TPrintf ("current map : %s\n", svs.name); Con_TPrintf ("current map : %s\n", svs.name);
@ -2367,7 +2371,7 @@ static void SV_Status_f (void)
#define COLUMNS C_FRAGS C_USERID C_ADDRESS C_NAME C_RATE C_PING C_DROP C_DLP C_DLS C_PROT C_ADDRESS2 #define COLUMNS C_FRAGS C_USERID C_ADDRESS C_NAME C_RATE C_PING C_DROP C_DLP C_DLS C_PROT C_ADDRESS2
#define C_FRAGS COLUMN(0, "frags", if (cl->spectator==1)Con_Printf("%-5s ", "spec"); else Con_Printf("%5i ", (int)cl->old_frags)) #define C_FRAGS COLUMN(0, "frags", if (cl->spectator==1)Con_Printf("%-5s ", "spec"); else Con_Printf("%5i ", (int)cl->old_frags))
#define C_USERID COLUMN(1, "userid", Con_Printf("%6i ", (int)cl->userid)) #define C_USERID COLUMN(1, "userid", Con_Printf("%6i ", (int)cl->userid))
#define C_ADDRESS COLUMN(2, "address ", Con_Printf("%-16.16s", s)) #define C_ADDRESS COLUMN(2, "address ", Con_Printf("%s%-16.16s", sec, s))
#define C_NAME COLUMN(3, "name ", Con_Printf("%-16.16s", cl->name)) #define C_NAME COLUMN(3, "name ", Con_Printf("%-16.16s", cl->name))
#define C_RATE COLUMN(4, " hz", Con_Printf("%4i ", (cl->frameunion.frames&&cl->netchan.frame_rate>0)?(int)(0.5f+1/cl->netchan.frame_rate):0)) #define C_RATE COLUMN(4, " hz", Con_Printf("%4i ", (cl->frameunion.frames&&cl->netchan.frame_rate>0)?(int)(0.5f+1/cl->netchan.frame_rate):0))
#define C_PING COLUMN(5, "ping", Con_Printf("%4i ", (int)SV_CalcPing (cl, false))) #define C_PING COLUMN(5, "ping", Con_Printf("%4i ", (int)SV_CalcPing (cl, false)))
@ -2437,6 +2441,13 @@ static void SV_Status_f (void)
else else
s = NET_BaseAdrToString (adr, sizeof(adr), &cl->netchan.remote_address); s = NET_BaseAdrToString (adr, sizeof(adr), &cl->netchan.remote_address);
if (NET_IsLoopBackAddress(&cl->netchan.remote_address))
sec = "";
else if (NET_IsEncrypted(&cl->netchan.remote_address))
sec = S_COLOR_GREEN;
else
sec = S_COLOR_RED;
safeswitch(cl->protocol) safeswitch(cl->protocol)
{ {
case SCP_BAD: p = "-----"; break; case SCP_BAD: p = "-----"; break;
@ -2728,7 +2739,9 @@ void SV_User_f (void)
client_t *cl; client_t *cl;
int clnum=-1; int clnum=-1;
unsigned int u; unsigned int u;
char buf[256]; char buf[8192];
qbyte digest[DIGEST_MAXSIZE];
int certsize;
extern cvar_t sv_userinfo_bytelimit, sv_userinfo_keylimit; extern cvar_t sv_userinfo_bytelimit, sv_userinfo_keylimit;
static const char *pext1names[32] = { "setview", "scale", "lightstylecol", "trans", "view2", "builletens", "accuratetimings", "sounddbl", static const char *pext1names[32] = { "setview", "scale", "lightstylecol", "trans", "view2", "builletens", "accuratetimings", "sounddbl",
"fatness", "hlbsp", "bullet", "hullsize", "modeldbl", "entitydbl", "entitydbl2", "floatcoords", "fatness", "hlbsp", "bullet", "hullsize", "modeldbl", "entitydbl", "entitydbl2", "floatcoords",
@ -2813,7 +2826,16 @@ void SV_User_f (void)
Con_Printf("\n"); Con_Printf("\n");
} }
Con_Printf("ip: %s\n", NET_AdrToString(buf, sizeof(buf), &cl->netchan.remote_address)); Con_Printf("ip: %s%s\n", NET_IsEncrypted(&cl->netchan.remote_address)?S_COLOR_GREEN:S_COLOR_RED, NET_AdrToString(buf, sizeof(buf), &cl->netchan.remote_address));
certsize = NET_GetConnectionCertificate(svs.sockets, &cl->netchan.remote_address, QCERT_PEERCERTIFICATE, buf, sizeof(buf));
if (certsize <= 0)
strcpy(buf, "<no certificate>");
else
Base64_EncodeBlockURI(digest,CalcHash(&hash_sha1, digest, sizeof(digest), buf, certsize), buf, sizeof(buf));
Con_Printf("fp: %s\n", buf);
if (NET_GetConnectionCertificate(svs.sockets, &cl->netchan.remote_address, QCERT_PEERSUBJECT, buf, sizeof(buf)) < 0)
strcpy(buf, "<unavailable>");
Con_Printf("dn: %s\n", buf);
switch(cl->realip_status) switch(cl->realip_status)
{ {
case 1: case 1:

View file

@ -1127,19 +1127,67 @@ CONNECTIONLESS COMMANDS
============================================================================== ==============================================================================
*/ */
const char *SV_ProtocolNameForClient(client_t *cl)
{
//okay, that failed...
safeswitch (cl->protocol)
{
case SCP_QUAKEWORLD:
if (cl->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)
return "fteqw"; //changes enough to be significant. assumed to include csqc.
return "quakeworld";
case SCP_BAD:
return "bot";
case SCP_QUAKE2:
return "quake2";
case SCP_QUAKE3:
return "quake3";
case SCP_NETQUAKE:
if (cl->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)
return "ftenq"; //changes enough to be significant. assumed to include csqc.
if (cl->qex)
return "qex";
if (cl->proquake_angles_hack)
return "proquake";
return "vanilla";
case SCP_BJP3:
return "bjp3";
case SCP_FITZ666:
//this gets messy... probably we should distinguish more
if (cl->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)
return "ftenq"; //changes enough to be significant. assumed to include csqc.
if (cl->qex)
return "qex";
if (cl->netchan.netprim.coordtype != COORDTYPE_FIXED_13_3 || cl->netchan.netprim.anglesize != 1)
return "rmq"; //while fte tends not to care, most people consider them separate.
return "fitz";
case SCP_DARKPLACES6:
return "dp6";
case SCP_DARKPLACES7:
return "dp7";
safedefault:
return "unknown";
}
}
char *SV_PlayerPublicAddress(client_t *cl) char *SV_PlayerPublicAddress(client_t *cl)
{ //returns a string containing the client's IP address, as permitted for viewing by other clients. { //returns a string containing the client's IP address, as permitted for viewing by other clients.
//if something useful is actually returned, it should be masked. //if something useful is actually returned, it should be masked.
return "private"; //we hide it entirely out of private info caution. most nq clients expect a #.#.#.INVALID type address.
//it should be fine to put other stuff here though, we put client version instead, if we know it.
const char *ver = InfoBuf_ValueForKey(&cl->userinfo, "*ver");
const char *prot = SV_ProtocolNameForClient(cl);
return va("prot %s %s", prot, ver); //something so they can't confuse ip parsing so easily nor pass them off as some other protocol.
} }
#define STATUS_OLDSTYLE 0 #define STATUS_OLDSTYLE 0 //equivelent to STATUS_SERVERINFO|STATUS_PLAYERS
#define STATUS_SERVERINFO 1 #define STATUS_SERVERINFO 1
#define STATUS_PLAYERS 2 #define STATUS_PLAYERS 2
#define STATUS_SPECTATORS 4 #define STATUS_SPECTATORS 4
#define STATUS_SPECTATORS_AS_PLAYERS 8 //for ASE - change only frags: show as "S" #define STATUS_SPECTATORS_AS_PLAYERS 8 //for ASE - change only frags: show as "S"
#define STATUS_SHOWTEAMS 16 #define STATUS_SHOWTEAMS 16
#define STATUS_QTVLIST 32 //qtv destid "name" "streamid@host:port" numviewers #define STATUS_QTVLIST 32 //qtv destid "name" "streamid@host:port" numviewers
/* /*
================ ================
@ -1768,14 +1816,16 @@ qboolean SVC_GetChallenge (qboolean respond_dp)
#endif #endif
#ifdef HAVE_DTLS #ifdef HAVE_DTLS
if (net_enable_dtls.ival/* || !*net_enable_dtls.string*/) if (net_enable_dtls.ival>0/* || !*net_enable_dtls.string*/ && svs.sockets->dtlsfuncs)
{ {
lng = LittleLong(PROTOCOL_VERSION_DTLSUPGRADE); lng = LittleLong(PROTOCOL_VERSION_DTLSUPGRADE);
memcpy(over, &lng, sizeof(lng)); memcpy(over, &lng, sizeof(lng));
over+=sizeof(lng); over+=sizeof(lng);
if (net_enable_dtls.ival >= 2) if (net_enable_dtls.ival >= 3)
lng = LittleLong(2); //required lng = LittleLong(3); //required
else if (net_enable_dtls.ival >= 2)
lng = LittleLong(2); //encouraged
else else
lng = LittleLong(1); //supported lng = LittleLong(1); //supported
memcpy(over, &lng, sizeof(lng)); memcpy(over, &lng, sizeof(lng));
@ -2606,7 +2656,8 @@ void SV_DoDirectConnect(svconnectinfo_t *fte_restrict info)
{ {
if (spectator_password.string[0] && if (spectator_password.string[0] &&
stricmp(spectator_password.string, "none") && stricmp(spectator_password.string, "none") &&
strcmp(spectator_password.string, s) ) strcmp(spectator_password.string, s) &&
!NET_IsLoopBackAddress(&info->adr))
{ // failed { // failed
Con_TPrintf ("%s:spectator password failed\n", NET_AdrToString (adrbuf, sizeof(adrbuf), &info->adr)); Con_TPrintf ("%s:spectator password failed\n", NET_AdrToString (adrbuf, sizeof(adrbuf), &info->adr));
SV_RejectMessage (info->protocol, "requires a spectator password\n\n"); SV_RejectMessage (info->protocol, "requires a spectator password\n\n");
@ -2621,7 +2672,8 @@ void SV_DoDirectConnect(svconnectinfo_t *fte_restrict info)
s = Info_ValueForKey (info->userinfo, "password"); s = Info_ValueForKey (info->userinfo, "password");
if (password.string[0] && if (password.string[0] &&
stricmp(password.string, "none") && stricmp(password.string, "none") &&
strcmp(password.string, s) ) strcmp(password.string, s) &&
!NET_IsLoopBackAddress(&info->adr))
{ {
Con_TPrintf ("%s:password failed\n", NET_AdrToString (adrbuf, sizeof(adrbuf), &info->adr)); Con_TPrintf ("%s:password failed\n", NET_AdrToString (adrbuf, sizeof(adrbuf), &info->adr));
SV_RejectMessage (info->protocol, "server requires a password\n\n"); SV_RejectMessage (info->protocol, "server requires a password\n\n");
@ -3056,10 +3108,15 @@ void SV_DoDirectConnect(svconnectinfo_t *fte_restrict info)
//this is the upper bound of the mtu, if its too high we'll get EMSGSIZE and we'll reduce it. //this is the upper bound of the mtu, if its too high we'll get EMSGSIZE and we'll reduce it.
//however, if it drops below newcl->netchan.message.maxsize then we'll start to see undeliverable reliables, which means dropped clients. //however, if it drops below newcl->netchan.message.maxsize then we'll start to see undeliverable reliables, which means dropped clients.
newcl->netchan.mtu = MAX_DATAGRAM; //vanilla qw clients are assumed to have an mtu of this size. newcl->netchan.mtu = MAX_DATAGRAM; //vanilla qw clients are assumed to have an mtu of this size.
if (info->mtu >= 64) if (info->mtu >= 300) //anything smaller is someone being intentionally malicious.
{ //if we support application fragmenting, then we can send massive reliables without too much issue { //if we support application fragmenting, then we can send massive reliables without too much issue
newcl->netchan.mtu = info->mtu; newcl->netchan.mtu = info->mtu;
newcl->netchan.message.maxsize = sizeof(newcl->netchan.message_buf); newcl->netchan.message.maxsize = sizeof(newcl->netchan.message_buf);
if (info->adr.type == NA_ICE)
newcl->netchan.mtu -= 48+12; //dtls+sctp overhead
else if (info->adr.prot == NP_DTLS || info->adr.prot == NP_TLS)
newcl->netchan.mtu -= 48; //dtls overhead
} }
else else
{ //otherwise we can't fragment the packets, and the only way to honour the mtu is to send less data. yay for more round-trips. { //otherwise we can't fragment the packets, and the only way to honour the mtu is to send less data. yay for more round-trips.
@ -3665,9 +3722,6 @@ void SVC_DirectConnect(int expectedreliablesequence)
} }
msg_badread=false; msg_badread=false;
if (!*info.guid)
NET_GetConnectionCertificate(svs.sockets, &net_from, QCERT_PEERFINGERPRINT, info.guid, sizeof(info.guid));
info.adr = net_from; info.adr = net_from;
if (MSV_ClusterLogin(&info)) if (MSV_ClusterLogin(&info))
return; return;
@ -4500,16 +4554,18 @@ qboolean SVNQ_ConnectionlessPacket(void)
/*dual-stack client, supporting either DP or QW protocols*/ /*dual-stack client, supporting either DP or QW protocols*/
SVC_GetChallenge (false); SVC_GetChallenge (false);
} }
#ifdef HAVE_DTLS
else if (net_enable_dtls.ival > 2 && (net_from.prot == NP_DGRAM || net_from.prot == NP_STREAM || net_from.prot == NP_WS) && net_from.type != NA_LOOPBACK && !NET_IsEncrypted(&net_from))
{
SV_RejectMessage (SCP_NETQUAKE, "This server requires the use of DTLS/TLS/WSS.\n");
return true;
}
#endif
else else
{ //legacy pure-nq (though often DP). { //legacy pure-nq (though often DP).
if (progstype == PROG_H2) if (progstype == PROG_H2)
{ {
SZ_Clear(&sb); SV_RejectMessage (SCP_NETQUAKE, "NQ clients are not supported with hexen2 gamecode\n");
MSG_WriteLong(&sb, 0);
MSG_WriteByte(&sb, CCREP_REJECT);
MSG_WriteString(&sb, "NQ clients are not supported with hexen2 gamecode\n");
*(int*)sb.data = BigLong(NETFLAG_CTL+sb.cursize);
NET_SendPacket(svs.sockets, sb.cursize, sb.data, &net_from);
return true; //not our version... return true; //not our version...
} }
if (NET_WasSpecialPacket(svs.sockets)) if (NET_WasSpecialPacket(svs.sockets))

View file

@ -21,6 +21,7 @@ struct fte_certctx_s
{ {
const char *peername; const char *peername;
qboolean dtls; qboolean dtls;
qboolean failure;
hashfunc_t *hash; //if set peer's cert MUST match the specified digest (with this hash function) hashfunc_t *hash; //if set peer's cert MUST match the specified digest (with this hash function)
qbyte digest[DIGEST_MAXSIZE]; qbyte digest[DIGEST_MAXSIZE];
@ -44,6 +45,8 @@ static int OSSL_Bio_FWrite(BIO *h, const char *buf, int size)
BIO_set_retry_write(h); BIO_set_retry_write(h);
r = -1; //paranoia r = -1; //paranoia
} }
// else if (r < 0)
// Con_DPrintf("ossl Error: %i\n", r);
return r; return r;
} }
static int OSSL_Bio_FRead(BIO *h, char *buf, int size) static int OSSL_Bio_FRead(BIO *h, char *buf, int size)
@ -242,7 +245,12 @@ static int OSSL_Verify_Peer(int preverify_ok, X509_STORE_CTX *x509_ctx)
uctx->hash->terminate(digest, hctx); uctx->hash->terminate(digest, hctx);
//return 1 for success //return 1 for success
return !memcmp(digest, uctx->digest, uctx->hash->digestsize); if (memcmp(digest, uctx->digest, uctx->hash->digestsize))
{
uctx->failure = true;
return 0;
}
return 1;
} }
if(preverify_ok == 0) if(preverify_ok == 0)
@ -914,7 +922,22 @@ static void OSSL_DestroyContext(void *ctx)
static neterr_t OSSL_Transmit(void *ctx, const qbyte *data, size_t datasize) static neterr_t OSSL_Transmit(void *ctx, const qbyte *data, size_t datasize)
{ //we're sending data { //we're sending data
ossldtls_t *o = (ossldtls_t*)ctx; ossldtls_t *o = (ossldtls_t*)ctx;
int r = BIO_write(o->bio, data, datasize); int r;
if (datasize == 0) //liveness test. return clogged while handshaking and sent when finished. openssl doesn't like 0-byte writes.
{
if (o->cert.failure)
return NETERR_DISCONNECTED; //actual security happens elsewhere.
else if (SSL_is_init_finished(o->ssl))
return NETERR_SENT; //go on, send stuff.
else if (BIO_should_retry(o->bio))
{
if (BIO_do_handshake(o->bio) == 1)
return NETERR_SENT;
}
return NETERR_CLOGGED; //can't send yet.
}
else
r = BIO_write(o->bio, data, datasize);
if (r <= 0) if (r <= 0)
{ {
if (BIO_should_io_special(o->bio)) if (BIO_should_io_special(o->bio))
@ -941,7 +964,7 @@ static neterr_t OSSL_Received(void *ctx, sizebuf_t *message)
int r; int r;
if (!message) if (!message)
r = 0; r = BIO_read(o->bio, NULL, 0);
else else
{ {
o->pending = message->data; o->pending = message->data;
@ -981,7 +1004,52 @@ static neterr_t OSSL_Timeouts(void *ctx)
return OSSL_Received(ctx, NULL); return OSSL_Received(ctx, NULL);
} }
qboolean OSSL_GenTempCertificate(const char *subject, struct dtlslocalcred_s *cred) static int OSSL_GetPeerCertificate(void *ctx, enum certprops_e prop, char *out, size_t outsize)
{
ossldtls_t *o = (ossldtls_t*)ctx;
X509 *cert;
safeswitch(prop)
{
case QCERT_ISENCRYPTED:
return 0; //not an error.
case QCERT_PEERCERTIFICATE:
cert = SSL_get_peer_certificate(o->ssl);
goto returncert;
case QCERT_LOCALCERTIFICATE:
cert = vhost.servercert;
goto returncert;
returncert:
if (cert)
{
size_t blobsize = i2d_X509(cert, NULL);
qbyte *end = out;
if (blobsize <= outsize)
i2d_X509(cert, &end);
return end-(qbyte*)out;
}
return -1;
case QCERT_PEERSUBJECT:
{
int r;
X509 *cert = SSL_get_peer_certificate(o->ssl);
if (cert)
{
X509_NAME *iname = X509_get_subject_name(cert);
BIO *bio = BIO_new(BIO_s_mem());
X509_NAME_print_ex(bio, iname, 0, XN_FLAG_RFC2253);
r = BIO_read(bio, out, outsize-1);
out[(r<0)?0:r] = 0;
BIO_free(bio);
return r;
}
}
return -1;
safedefault:
return -1;
}
}
static qboolean OSSL_GenTempCertificate(const char *subject, struct dtlslocalcred_s *cred)
{ {
EVP_PKEY*pkey = EVP_PKEY_new(); EVP_PKEY*pkey = EVP_PKEY_new();
RSA *rsa = RSA_new(); RSA *rsa = RSA_new();
@ -1033,7 +1101,7 @@ static dtlsfuncs_t ossl_dtlsfuncs =
OSSL_Transmit, OSSL_Transmit,
OSSL_Received, OSSL_Received,
OSSL_Timeouts, OSSL_Timeouts,
NULL, OSSL_GetPeerCertificate,
OSSL_GenTempCertificate, OSSL_GenTempCertificate,
}; };
static const dtlsfuncs_t *OSSL_InitClient(void) static const dtlsfuncs_t *OSSL_InitClient(void)
@ -1172,6 +1240,8 @@ static void OSSL_PluginShutdown(void)
EVP_PKEY_free(vhost.privatekey); EVP_PKEY_free(vhost.privatekey);
BIO_meth_free(biometh_vfs); BIO_meth_free(biometh_vfs);
BIO_meth_free(biometh_dtls); BIO_meth_free(biometh_dtls);
memset(&vhost, 0, sizeof(vhost));
} }
static qboolean OSSL_PluginMayShutdown(void) static qboolean OSSL_PluginMayShutdown(void)
{ {