/* * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation files * (the "Software"), to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, * publish, distribute, sublicense, and/or sell copies of the Software, * and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include #include #include #include #include #include #include #include int RegTokenValid(RegTokenInfo * token) { HashMap *tokenJson; Int64 uses, used; UInt64 expiration; if (!token || !RegTokenExists(token->db, token->name)) { return 0; } tokenJson = DbJson(token->ref); uses = JsonValueAsInteger(HashMapGet(tokenJson, "uses")); used = JsonValueAsInteger(HashMapGet(tokenJson, "used")); expiration = JsonValueAsInteger(HashMapGet(tokenJson, "expires_on")); return (UInt64Eq(expiration, UInt64Create(0, 0)) || UInt64Geq(UtilServerTs(), expiration)) && (Int64Eq(uses, Int64Neg(Int64Create(0, 1))) || Int64Lt(used, uses)); } void RegTokenUse(RegTokenInfo * token) { HashMap *tokenJson; if (!token || !RegTokenExists(token->db, token->name)) { return; } if (Int64Geq(token->uses, Int64Create(0, 0)) && Int64Geq(token->used, token->uses)) { return; } token->used = Int64Add(token->used, Int64Create(0, 1)); /* Write the information to the hashmap */ tokenJson = DbJson(token->ref); JsonValueFree(HashMapSet(tokenJson, "used", JsonValueInteger(token->used))); } int RegTokenExists(Db * db, char *token) { if (!token || !db) { return 0; } return DbExists(db, 3, "tokens", "registration", token); } int RegTokenDelete(RegTokenInfo * token) { if (!token || !RegTokenClose(token)) { return 0; } if (!DbDelete(token->db, 3, "tokens", "registration", token->name)) { return 0; } Free(token->name); Free(token->owner); Free(token); return 1; } RegTokenInfo * RegTokenGetInfo(Db * db, char *token) { RegTokenInfo *ret; DbRef *tokenRef; HashMap *tokenJson; if (!RegTokenExists(db, token)) { return NULL; } tokenRef = DbLock(db, 3, "tokens", "registration", token); if (!tokenRef) { return NULL; } tokenJson = DbJson(tokenRef); ret = Malloc(sizeof(RegTokenInfo)); ret->db = db; ret->ref = tokenRef; ret->owner = StrDuplicate(JsonValueAsString(HashMapGet(tokenJson, "created_by"))); ret->name = StrDuplicate(token); ret->expires = JsonValueAsInteger(HashMapGet(tokenJson, "expires_on")); ret->created = JsonValueAsInteger(HashMapGet(tokenJson, "created_on")); ret->uses = JsonValueAsInteger(HashMapGet(tokenJson, "uses")); ret->used = JsonValueAsInteger(HashMapGet(tokenJson, "used")); ret->grants = UserDecodePrivileges(HashMapGet(tokenJson, "grants")); return ret; } void RegTokenFree(RegTokenInfo * tokeninfo) { if (tokeninfo) { Free(tokeninfo->name); Free(tokeninfo->owner); Free(tokeninfo); } } int RegTokenClose(RegTokenInfo * tokeninfo) { if (!tokeninfo) { return 0; } return DbUnlock(tokeninfo->db, tokeninfo->ref); } static int RegTokenVerify(char *token) { size_t i, size; char c; if (!token) { return 0; } /* The spec says the following: "The token required for this * authentication [...] is an opaque string with maximum length of * 64 characters in the range [A-Za-z0-9._~-]." */ if ((size = strlen(token)) > 64) { return 0; } for (i = 0; i < size; i++) { c = token[i]; if (!(isalnum(c) || c == '0' || c == '_' || c == '~' || c == '-')) { return 0; } } return 1; } HashMap * RegTokenJSON(RegTokenInfo * info) { char *creator; char *tokenname; UInt64 created; UInt64 expires; Int64 used; Int64 uses; Int64 remaining; HashMap *jsoninfo = HashMapCreate(); tokenname = info->name; created = info->created; expires = info->expires; uses = info->uses; used = info->used; remaining = Int64Sub(uses, used); if (Int64Eq(uses, Int64Neg(Int64Create(0, 1)))) { /* If uses == -1(infinite uses), just set it too * to -1 */ remaining = uses; } if (!(creator = info->owner)) { /* The owner can be null if Telodendria created it. * Since users can't contain a space, it is in this case set to * "Telodendria Server". */ creator = "Telodendria Server"; } HashMapSet(jsoninfo, "name", JsonValueString(tokenname)); HashMapSet(jsoninfo, "created_by", JsonValueString(creator)); HashMapSet(jsoninfo, "created_on", JsonValueInteger(created)); HashMapSet(jsoninfo, "expires_on", JsonValueInteger(expires)); HashMapSet(jsoninfo, "used", JsonValueInteger(used)); /* #26 says the following: * "The number of uses *remaining* for the token [...]" * You therefore can't easily set the uses value here, hence why we * are using `remaining' */ HashMapSet(jsoninfo, "uses", JsonValueInteger(remaining)); return jsoninfo; } RegTokenInfo * RegTokenCreate(Db * db, char *name, char *owner, UInt64 expires, Int64 uses, int privileges) { RegTokenInfo *ret; HashMap *tokenJson; UInt64 timestamp = UtilServerTs(); if (!db || !name) { return NULL; } /* -1 indicates infinite uses; zero and all positive values are a * valid number of uses; althought zero would be rather useless. * Anything less than -1 doesn't make sense. */ if (Int64Lt(uses, Int64Neg(Int64Create(0, 1)))) { return NULL; } /* Verify the token */ if (!RegTokenVerify(name) || (UInt64Gt(expires, UInt64Create(0, 0)) && UInt64Lt(expires, timestamp))) { return NULL; } ret = Malloc(sizeof(RegTokenInfo)); /* Set the token's properties */ ret->db = db; ret->ref = DbCreate(db, 3, "tokens", "registration", name); if (!ret->ref) { /* RegToken already exists or some weird fs error */ Free(ret); return NULL; } ret->name = StrDuplicate(name); ret->owner = StrDuplicate(owner); ret->used = Int64Create(0, 0); ret->uses = uses; ret->created = timestamp; ret->expires = expires; ret->grants = privileges; /* Write user info to database. */ tokenJson = DbJson(ret->ref); HashMapSet(tokenJson, "created_by", JsonValueString(ret->owner)); HashMapSet(tokenJson, "created_on", JsonValueInteger(ret->created)); HashMapSet(tokenJson, "expires_on", JsonValueInteger(ret->expires)); HashMapSet(tokenJson, "used", JsonValueInteger(ret->used)); HashMapSet(tokenJson, "uses", JsonValueInteger(ret->uses)); HashMapSet(tokenJson, "grants", UserEncodePrivileges(privileges)); return ret; }