Compare commits

..

2 commits

Author SHA1 Message Date
LDA
33edd2ceaf [MOD] Do NOT do unnecessary notifications
I should move notifying users into some sort of background task. It is
not particularly necessary to send events, and essentially slows down
the requester thread doing something that's not really sending an event.

It could also scale terribly in rooms where there exists a lot of local
users, with every one of them having possibly at least hundreds of sync
tokens.
2024-12-03 15:57:05 +01:00
LDA
4b427a4c82 [ADD/WIP] Dumb key management
This is just enough to fool FluffyChat and nheko, and still not enough
to actually do proper E2EE. Also, don't use Cinny with it, it seems to
repeteadly upload keys. Terrible.
2024-12-02 11:50:38 +01:00
15 changed files with 408 additions and 36 deletions

62
Schema/KeyUpload.json Normal file
View file

@ -0,0 +1,62 @@
{
"guard": "TELODENDRIA_KEY_UPLOAD_H",
"header": "Schema/KeyUpload.h",
"types": {
"DeviceKeys": {
"type": "struct",
"fields": {
"device_id": {
"type": "string",
"required": true
},
"user_id": {
"type": "string",
"required": true
},
"algorithms": {
"type": "[string]",
"required": true
},
"keys": {
"type": "{string}",
"required": true
},
"signatures": {
"//": "TODO: More complex j2s types.",
"//": "This is meant to be a map from user ID to ",
"//": "algo+device ID to a signature(string).",
"type": "object",
"required": true
}
}
},
"KeyResponse": {
"type": "struct",
"fields": {
"one_time_key_counts": {
"type": "{integer}",
"required": true
}
}
},
"KeyUploadRequest": {
"type": "struct",
"fields": {
"device_keys": {
"type": "DeviceKeys",
"required": false
},
"fallback_keys": {
"//": "This is a one-time key.",
"type": "object",
"required": false
},
"one_time_keys": {
"//": "This is a one-time key.",
"type": "object",
"required": false
}
}
}
}
}

View file

@ -38,6 +38,10 @@
"redacted_because": {
"type": "object",
"required": false
},
"transaction_id": {
"type": "string",
"required": false
}
}
},

View file

@ -68,6 +68,7 @@
"sender": { "type": "string", "required": true },
"state_key": { "type": "string" },
"redacts": { "type": "string" },
"_unsigned": { "type": "object" },
"type": { "type": "string", "required": true }
},
"type": "struct"
@ -105,6 +106,9 @@
},
"rooms": {
"type": "Rooms"
},
"device_one_time_keys_count": {
"type": "{integer}"
}
},
"type": "struct"

View file

@ -348,6 +348,11 @@ RoomEventFetch(Room *room, char *id, bool prev)
"pdu_status",
JsonValueDuplicate(HashMapGet(DbJson(event_ref), "status"))
));
JsonValueFree(HashMapSet(
unsign,
"transaction_id",
JsonValueDuplicate(HashMapGet(DbJson(event_ref), "transaction"))
));
ts = JsonValueAsInteger(HashMapGet(ret, "origin_server_ts"));
JsonValueFree(HashMapSet(
unsign,
@ -391,7 +396,7 @@ finish:
}
HashMap *
RoomEventCreate(char *sender, char *type, char *key, HashMap *c)
RoomEventCreate(char *sender, char *type, char *key, HashMap *c, char *txn)
{
HashMap *event;
if (!sender || !type || !c)
@ -403,6 +408,7 @@ RoomEventCreate(char *sender, char *type, char *key, HashMap *c)
JsonSet(event, JsonValueObject(c), 1, "content");
JsonSet(event, JsonValueString(sender), 1, "sender");
JsonSet(event, JsonValueString(type), 1, "type");
JsonSet(event, JsonValueString(txn), 1, "transaction");
if (key)
{
JsonSet(event, JsonValueString(key), 1, "state_key");
@ -557,7 +563,7 @@ RoomSendInvite(User *sender, bool direct, char *user, Room *room)
content = HashMapCreate();
JsonSet(content, JsonValueBoolean(direct), 1, "is_direct");
JsonSet(content, JsonValueString("invite"), 1, "membership");
event = RoomEventCreate(senderStr, "m.room.member", user, content);
event = RoomEventCreate(senderStr, "m.room.member", user, content, NULL);
JsonFree(RoomEventSend(room, event, NULL));
JsonFree(event);
@ -692,7 +698,7 @@ RoomLeave(Room *room, User *user, char **errp)
content = HashMapCreate();
JsonSet(content, JsonValueString("leave"), 1, "membership");
event = RoomEventCreate(userString, "m.room.member", userString, content);
event = RoomEventCreate(userString, "m.room.member", userString, content, NULL);
pdu = RoomEventSend(room, event, errp);
/* TODO: One ought to be extremely careful with managing users in those
@ -756,7 +762,7 @@ RoomRedact(Room *room, User *user, char *eventID, char *reason, char **errp)
HashMapSet(content, "reason", JsonValueString(reason));
event = RoomEventCreate(userString,
"m.room.redaction", NULL,
content
content, NULL
);
HashMapSet(event, "redacts", JsonValueString(eventID));
pdu = RoomEventSend(room, event, errp);
@ -811,7 +817,7 @@ RoomJoin(Room *room, User *user, char **errp)
content = HashMapCreate();
JsonSet(content, JsonValueString("join"), 1, "membership");
event = RoomEventCreate(userString, "m.room.member", userString, content);
event = RoomEventCreate(userString, "m.room.member", userString, content, NULL);
pdu = RoomEventSend(room, event, errp);
/* TODO: One ought to be extremely careful with managing users in those

View file

@ -97,7 +97,7 @@ RoomPopulate(Room *room, User *user, RoomCreateRequest *req, ServerPart s)
}
JsonValueFree(HashMapSet(content, key, JsonValueDuplicate(val)));
}
event = RoomEventCreate(sender_str, "m.room.create", "", content);
event = RoomEventCreate(sender_str, "m.room.create", "", content, NULL);
JsonFree(RoomEventSend(room, event, NULL));
JsonFree(event);
UserAddJoin(user, room->id);
@ -105,7 +105,7 @@ RoomPopulate(Room *room, User *user, RoomCreateRequest *req, ServerPart s)
/* m.room.member */
content = HashMapCreate();
JsonSet(content, JsonValueString("join"), 1, "membership");
event = RoomEventCreate(sender_str, "m.room.member", sender_str, content);
event = RoomEventCreate(sender_str, "m.room.member", sender_str, content, NULL);
JsonFree(RoomEventSend(room, event, NULL));
JsonFree(event);
@ -126,7 +126,7 @@ RoomPopulate(Room *room, User *user, RoomCreateRequest *req, ServerPart s)
}
HashMapSet(content, key, JsonValueDuplicate(val));
}
event = RoomEventCreate(sender_str, "m.room.power_levels", "", content);
event = RoomEventCreate(sender_str, "m.room.power_levels", "", content, NULL);
JsonFree(RoomEventSend(room, event, NULL));
JsonFree(event);
@ -174,7 +174,7 @@ RoomPopulate(Room *room, User *user, RoomCreateRequest *req, ServerPart s)
, 1, a); \
event = RoomEventCreate( \
sender_str, \
"m.room." #p, "", content); \
"m.room." #p, "", content, NULL); \
JsonFree(RoomEventSend(room, event, NULL)); \
JsonFree(event); \
} \
@ -208,7 +208,7 @@ RoomPopulate(Room *room, User *user, RoomCreateRequest *req, ServerPart s)
{
content = HashMapCreate();
JsonSet(content, JsonValueString(req->name), 1, "name");
event = RoomEventCreate(sender_str, "m.room.name", "", content);
event = RoomEventCreate(sender_str, "m.room.name", "", content, NULL);
JsonFree(RoomEventSend(room, event, NULL));
JsonFree(event);
}
@ -216,7 +216,7 @@ RoomPopulate(Room *room, User *user, RoomCreateRequest *req, ServerPart s)
{
content = HashMapCreate();
JsonSet(content, JsonValueString(req->topic), 1, "topic");
event = RoomEventCreate(sender_str, "m.room.topic", "", content);
event = RoomEventCreate(sender_str, "m.room.topic", "", content, NULL);
JsonFree(RoomEventSend(room, event, NULL));
JsonFree(event);
}
@ -240,7 +240,7 @@ RoomPopulate(Room *room, User *user, RoomCreateRequest *req, ServerPart s)
JsonSet(content, JsonValueString(fullStr), 1, "alias");
event = RoomEventCreate(
sender_str,
"m.room.canonical_alias", "", content);
"m.room.canonical_alias", "", content, NULL);
JsonFree(RoomEventSend(room, event, NULL));
JsonFree(event);
@ -270,7 +270,7 @@ RoomPopulate(Room *room, User *user, RoomCreateRequest *req, ServerPart s)
}
event = RoomEventCreate(sender_str, "m.room.power_levels", "", pl_content);
event = RoomEventCreate(sender_str, "m.room.power_levels", "", pl_content, NULL);
JsonFree(RoomEventSend(room, event, NULL));
JsonFree(event);

View file

@ -29,6 +29,8 @@ PopulateEventV1(Room * room, HashMap * event, PduV1 * pdu, ServerPart serv)
StrDuplicate(JsonValueAsString(JsonGet(event, 1, "type")));
pdu->redacts =
StrDuplicate(JsonValueAsString(JsonGet(event, 1, "redacts")));
pdu->_unsigned.transaction_id =
StrDuplicate(JsonValueAsString(JsonGet(event, 1, "transaction")));
if (JsonGet(event, 1, "state_key"))
{
pdu->state_key =

View file

@ -242,6 +242,11 @@ RoomAddEventV1(Room *room, PduV1 pdu, PduV1Status status)
JsonValueArray(ArrayCreate()),
1, "next_events"
);
JsonSet(
DbJson(event_ref),
JsonValueString(pdu._unsigned.transaction_id),
1, "transaction"
);
DbUnlock(room->db, event_ref);
Free(safe_id);
@ -333,13 +338,6 @@ RoomAddEventV1(Room *room, PduV1 pdu, PduV1Status status)
/* If we have a membership change, then add it to the
* proper table. */
{
CommonID *id = UserIdParse(pdu.sender, NULL);
User *user = UserLockID(room->db, id);
UserPushEvent(user, pdu_json);
UserUnlock(user);
UserIdFree(id);
}
if (relates_to && RelationFromJson(relates_to, &rel, &errp))
{
DbRef *relate = DbLock(

View file

@ -298,7 +298,7 @@ ROUTE_IMPL(RouteKickRoom, path, argp)
membership = RoomEventCreate(
sender,
"m.room.member", kicked,
content
content, NULL
);
HashMapSet(content, "membership", JsonValueString(membershipState));

View file

@ -34,8 +34,105 @@
#include <Room.h>
#include <string.h>
#include <Schema/Filter.h>
#include <Schema/KeyUpload.h>
HashMap *
UploadKey(RouteArgs *args, User *user, KeyUploadRequest *req, char *sender)
{
char *deviceId = UserGetDeviceId(user);
KeyResponse response = { 0 };
HashMap *json;
char *fbKey;
JsonValue *fbValue;
size_t i;
if (!user || !req || !sender)
{
return NULL;
}
Log(LOG_ERR, "did=%s", deviceId);
if (req->device_keys.user_id)
{
HashMap *publicKeys;
char *pkTag, *pk;
/* We have device key information */
if (!StrEquals(req->device_keys.user_id, sender))
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
return MatrixErrorCreate(
M_UNAUTHORIZED, "Device key update has an invalid user ID"
);
}
if (!StrEquals(req->device_keys.device_id, deviceId))
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
return MatrixErrorCreate(
M_UNAUTHORIZED, "Device key update has an invalid device ID"
);
}
/* Check the public key list */
publicKeys = req->device_keys.keys;
i = 0;
while (HashMapIterateReentrant(publicKeys, &pkTag, (void **) &pk, &i))
{
char *pktDID = strchr(pkTag, ':');
/* Maybe C does need NULL saturation */
pktDID = pktDID ? pktDID + 1 : NULL;
if (!StrEquals(pktDID, deviceId))
{
/* As far as I know, we're not meant to handle other devices'
* public keys */
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
Log(LOG_ERR, "%s!=%s 1", pktDID, deviceId);
return MatrixErrorCreate(
M_UNAUTHORIZED, "Device key update has an invalid device ID"
);
}
}
UserSetDeviceKeys(user, &req->device_keys);
}
UserClearFallbackKeys(user);
i = 0;
while (HashMapIterateReentrant(req->fallback_keys, &fbKey, (void **) &fbValue, &i))
{
char *fbKID = strchr(fbKey, ':');
size_t len = fbKID ? fbKID - fbKey : 0;
char algo[len + 1];
memcpy(algo, fbKey, len);
algo[len] = '\0';
/* Maybe C does need NULL saturation */
fbKID = fbKID ? fbKID + 1 : NULL;
UserAddKey(user, fbKey, fbValue, true);
(void) fbKID;
}
i = 0;
while (HashMapIterateReentrant(req->one_time_keys, &fbKey, (void **) &fbValue, &i))
{
char *fbKID = strchr(fbKey, ':');
size_t len = fbKID ? fbKID - fbKey : 0;
char algo[len + 1];
memcpy(algo, fbKey, len);
algo[len] = '\0';
/* Maybe C does need NULL saturation */
fbKID = fbKID ? fbKID + 1 : NULL;
UserAddKey(user, fbKey, fbValue, false);
(void) fbKID;
}
response.one_time_key_counts = UserGetOnetimeCounts(user);
UserNotifyUser(UserGetName(user));
json = KeyResponseToJson(&response);
KeyResponseFree(&response);
return json;
}
ROUTE_IMPL(RouteKeyQuery, path, argp)
{
@ -45,9 +142,14 @@ ROUTE_IMPL(RouteKeyQuery, path, argp)
HashMap *request = NULL;
HashMap *response = NULL;
User *user = NULL;
CommonID *id = NULL;
char *token = NULL;
User *user = NULL;
char *serverName = NULL;
char *sender = NULL;
char *method = ArrayGet(path, 0);
char *err;
if (HttpRequestMethodGet(args->context) != HTTP_POST)
@ -71,6 +173,11 @@ ROUTE_IMPL(RouteKeyQuery, path, argp)
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
goto finish;
}
serverName = ConfigGetServerName(db);
id = UserIdParse(UserGetName(user), serverName);
id->sigil = '@';
sender = ParserRecomposeCommonID(*id);
request = JsonDecode(HttpServerStream(args->context));
if (!request)
{
@ -79,10 +186,37 @@ ROUTE_IMPL(RouteKeyQuery, path, argp)
goto finish;
}
if (StrEquals(method, "upload"))
{
KeyUploadRequest upload = { 0 };
if (!KeyUploadRequestFromJson(request, &upload, &err))
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_BAD_JSON, err);
goto finish;
}
if ((response = UploadKey(args, user, &upload, sender)))
{
KeyUploadRequestFree(&upload);
goto finish;
}
KeyUploadRequestFree(&upload);
}
else if (StrEquals(method, "query"))
{
/* TODO: Fetch a user's key information */
}
response = HashMapCreate();
(void) path;
finish:
JsonFree(request);
UserUnlock(user);
Free(serverName);
UserIdFree(id);
Free(sender);
return response;
}

View file

@ -122,7 +122,7 @@ ROUTE_IMPL(RouteSendEvent, path, argp)
goto finish;
}
event = RoomEventCreate(sender, eventType, NULL, JsonDuplicate(request));
event = RoomEventCreate(sender, eventType, NULL, JsonDuplicate(request), transId);
filled = RoomEventSend(room, event, &err);
JsonFree(event);
@ -266,7 +266,7 @@ ROUTE_IMPL(RouteSendState, path, argp)
event = RoomEventCreate(
sender,
eventType, stateKey ? stateKey : "",
JsonDuplicate(request)
JsonDuplicate(request), NULL
);
filled = RoomEventSend(room, event, &err);
JsonFree(event);

View file

@ -139,12 +139,17 @@ ROUTE_IMPL(RouteSync, path, argp)
/* TODO: I only am manually parsing this because j2s does not support
* a hashmap of unknown keys pointing to a known type. */
sync.rooms.invite = HashMapCreate();
sync.rooms.join = HashMapCreate();
sync.account_data.events = ArrayCreate();
sync.rooms.invite = NULL;
sync.rooms.join = NULL;
sync.account_data.events = NULL;
sync.device_one_time_keys_count = UserGetOnetimeCounts(user);
/* account data */
accountData = UserGetAccountDataSync(user, currBatch);
if (ArraySize(accountData) > 0)
{
sync.account_data.events = ArrayCreate();
}
for (i = 0; i < ArraySize(accountData); i++)
{
char *key = ArrayGet(accountData, i);
@ -158,6 +163,10 @@ ROUTE_IMPL(RouteSync, path, argp)
/* invites */
invites = UserGetInvites(user, currBatch);
if (ArraySize(invites) > 0)
{
sync.rooms.invite = HashMapCreate();
}
for (i = 0; i < ArraySize(invites); i++)
{
char *roomId = ArrayGet(invites, i);
@ -171,7 +180,7 @@ ROUTE_IMPL(RouteSync, path, argp)
invited = Malloc(sizeof(*invited));
memset(invited, 0, sizeof(*invited));
/* TODO: Populate the invitestate */
// TODO: Populate the invitestate
invited->invite_state.events = ArrayCreate();
HashMapSet(sync.rooms.invite, roomId, invited);
}
@ -179,9 +188,12 @@ ROUTE_IMPL(RouteSync, path, argp)
/* Joins */
joins = UserGetJoins(user, currBatch);
if (ArraySize(joins) > 0)
{
sync.rooms.join = HashMapCreate();
}
for (i = 0; i < ArraySize(joins); i++)
{
/* TODO: Rename these variables */
char *roomId = ArrayGet(joins, i);
JoinedRooms *joined;
char *firstEvent = NULL;
@ -230,8 +242,8 @@ ROUTE_IMPL(RouteSync, path, argp)
joined->timeline.prev_batch = UserNewMessageToken(
user, roomId, firstEvent
);
/* TODO: Don't shove the entire state.
* That's a recipe for disaster, especially on large rooms. */
// TODO: Don't shove the entire state.
// That's a recipe for disaster, especially on large rooms.
joined->state.events = ArrayCreate();
while (StateIterate(state, &type, &key, (void **) &id))
{
@ -253,12 +265,13 @@ ROUTE_IMPL(RouteSync, path, argp)
if (prevBatch)
{
/* TODO: Should we be dropping syncs? */
UserDropSync(user, prevBatch);
//UserDropSync(user, prevBatch);
nextBatch = UserInitSyncDiff(user);
}
sync.next_batch = nextBatch;
response = SyncResponseToJson(&sync);
SyncResponseFree(&sync);
(void) i;
finish:
FilterDestroy(filterData);
UserUnlock(user);

View file

@ -56,7 +56,7 @@ SendMembership(Db *db, User *user)
HashMap *content = HashMapCreate();
HashMap *membership = RoomEventCreate(
sender, "m.room.member", sender,
content
content, NULL
);
HashMapSet(content, "membership", JsonValueString("join"));

View file

@ -1387,6 +1387,8 @@ UserPushEvent(User *user, HashMap *event)
UserPushJoinSync(user, roomId);
/* TODO: In some very fun cases, this loop could be cosmically slow.
* Especially in the scale of 1.5k sync tokens. It can happen. */
entries = DbList(user->db, 3, "users", user->name, "sync");
for (i = 0; i < ArraySize(entries); i++)
{
@ -1533,6 +1535,7 @@ UserGetAccountDataSync(User *user, char *batch)
syncRef = DbLock(db, 4, "users", user->name, "sync", batch);
if (!syncRef)
{
Log(LOG_ERR, "Tried to get batch=%s (user=%s), but it's gone?", batch, user->deviceId);
return NULL;
}
@ -1858,7 +1861,7 @@ UserIsSyncOld(User *user, char *token)
dt = UtilTsMillis() - JsonValueAsInteger(HashMapGet(map, "creation"));
DbUnlock(user->db, ref);
return dt > (3 * 24 * 60 * 60 * 1000); /* Three days of timeout. */
return dt > (3 * 60 * 60 * 1000); /* Three hours of timeout. */
}
bool
UserSyncExists(User *user, char *sync)
@ -2153,3 +2156,125 @@ UserSetAccountData(User *user, char *key, HashMap *obj)
UserPushAccountData(user, key);
}
void
UserSetDeviceKeys(User *user, DeviceKeys *keys)
{
char *device;
HashMap *deviceObj;
if (!user || !keys)
{
return;
}
device = UserGetDeviceId(user);
deviceObj = JsonValueAsObject(HashMapGet(
UserGetDevices(user), device
));
if (!deviceObj)
{
return;
}
JsonValueFree(HashMapSet(deviceObj, "deviceKeys", JsonValueObject(DeviceKeysToJson(keys))));
}
void
UserClearFallbackKeys(User *user)
{
char *device;
HashMap *deviceObj;
if (!user)
{
return;
}
device = UserGetDeviceId(user);
deviceObj = JsonValueAsObject(HashMapGet(
UserGetDevices(user), device
));
if (!deviceObj)
{
return;
}
if (!HashMapGet(deviceObj, "oneTimeKeys"))
{
JsonValueFree(HashMapSet(deviceObj,
"oneTimeKeys", JsonValueObject(HashMapCreate())
));
}
JsonValueFree(HashMapSet(deviceObj,
"fallbackKeys", JsonValueObject(HashMapCreate())
));
}
void
UserAddKey(User *user, char *algo, JsonValue *key, bool fb)
{
char *device;
HashMap *deviceObj, *method;
if (!user || !algo || !key)
{
return;
}
device = UserGetDeviceId(user);
deviceObj = JsonValueAsObject(HashMapGet(
UserGetDevices(user), device
));
if (!deviceObj)
{
return;
}
method = JsonValueAsObject(HashMapGet(
deviceObj, fb ? "fallbackKeys" : "oneTimeKeys"
));
JsonValueFree(HashMapSet(method, algo, JsonValueDuplicate(key)));
}
HashMap *
UserGetOnetimeCounts(User *user)
{
char *device, *algoKey;
HashMap *deviceObj, *otk, *ret;
void *ignore;
if (!user)
{
return NULL;
}
device = UserGetDeviceId(user);
deviceObj = JsonValueAsObject(HashMapGet(
UserGetDevices(user), device
));
if (!deviceObj)
{
return NULL;
}
otk = JsonValueAsObject(HashMapGet(
deviceObj, "oneTimeKeys"
));
ret = HashMapCreate();
while (HashMapIterate(otk, &algoKey, &ignore))
{
char *algo = StrDuplicate(algoKey);
char *end = strchr(algo, ':');
int64_t *ptr;
if (end)
{
*end = '\0';
}
if (!(ptr = HashMapGet(ret, algo)))
{
ptr = Malloc(sizeof(*ptr));
*ptr = 0;
HashMapSet(ret, algo, ptr);
}
(*ptr)++;
Free(algo);
}
return ret;
}

View file

@ -195,7 +195,7 @@ extern bool RoomAddEventV1(Room *, PduV1, PduV1Status);
* Creates a barebones JSON object to be sent to
* .Fn RoomEventFetch .
*/
extern HashMap * RoomEventCreate(char *, char *, char *, HashMap *);
extern HashMap * RoomEventCreate(char *, char *, char *, HashMap *, char *);
/**
* Computes an approximation of the PDU depth by looking at

View file

@ -44,6 +44,8 @@
#include <Parser.h>
#include <Schema/KeyUpload.h>
#include <stdbool.h>
/**
@ -522,4 +524,26 @@ extern HashMap * UserGetAccountData(User *, char *);
* Replaces an account data entry.
*/
extern void UserSetAccountData(User *, char *, HashMap *);
/**
* Sets the device key list.
*/
extern void UserSetDeviceKeys(User *, DeviceKeys *);
/**
* Clears the fallback/one-time key list.
*/
extern void UserClearFallbackKeys(User *);
/**
* Adds a one-time/fallback key.
*/
extern void UserAddKey(User *, char *, JsonValue *, bool);
/**
* Generates a hashmap from algorithm to one-time key count as
* a pointer to the uint64_t.
* This is intended for /keys/upload. Please do not use this
* elsewhere */
extern HashMap * UserGetOnetimeCounts(User *);
#endif /* TELODENDRIA_USER_H */