diff --git a/README.md b/README.md index d6e04d5..939871d 100644 --- a/README.md +++ b/README.md @@ -857,6 +857,38 @@ The content of the request is used by the Recorder as though it had arrived as a If the Recorder is compiled without specifying `WITH_MQTT` at build time, support for MQTT is disabled completely. +### Friends in HTTP mode + +When a device posts a location request in HTTP mode, the endpoint may return a JSON array of OwnTracks objects of which `_type`s `cmd`, `location` and `card` may be supported by the device. This allows the device to see, say, friends. The Recorder has built-in support for this with the named "friends" lmdb database. +Assuming the following content of the friends database + +``` +$ ocat -S JP --dump=friends +jane-phone ["john/android"] +``` + +when user `jane` and device `phone` POST a new location via HTTP, the Recorder will present the following payload to the device: + +```json +[ + { + "_type": "card", + "tid": "JA", + "face": "/9j/4AAQSkZJR...", + "name": "John Doe" + }, + { + "_type": "location", + "tid": "JA", + "lat": 48.95833, + "lon": 2.39523, + "tst": 1456212791 + } +] +``` + + + ### Authentication In HTTP mode, the Recorder provides no form of authentication; anybody who "stumbles" over the correct endpoint will be able to post location data to your Recorder! You do not want this to happen. @@ -902,6 +934,17 @@ echo "jjolie-iphone s3cr1t" | ocat --load=keys Beware: these secret keys are stored in plain text so the database must be protected! +#### `friends` + +For http mode, the `friends` named LMDB database contains lists of "friends" on a per user-device key. The key's value must be a valid JSON array of strings, each in the form `"user:device"` or `"user/device"` which indicate which locations a particular user may see. For example, when a user called `jane` on device `phone` publishes in http mode and she should be permitted to see where `john` / `android` is, we add the following key/value to the friends named database: + +```bash +ocat --load=friends <) internally. + * If this user/device combo has no friends, return an empty array. + */ + +JsonNode *populate_friends(struct mg_connection *conn, char *u, char *d) +{ + struct udata *ud = (struct udata *)conn->server_param; + JsonNode *results = json_mkarray(), *lastuserlist; + JsonNode *friends, *obj, *jud, *newob, *jtid; + int np; + char *pairs[3]; + static UT_string *userdevice = NULL; + + + utstring_renew(userdevice); + utstring_printf(userdevice, "%s-%s", u, d); + + friends = gcache_json_get(ud->httpfriends, UB(userdevice)); + + if (ud->debug) { + char *js = NULL; + + if (friends) + js = json_stringify(friends, NULL); + debug(ud, "Friends of %s: %s", UB(userdevice), js ? js : ""); + if (js) + free(js); + } + + + /* assume the following are friends of jane/3s */ + // json_append_element(friends, json_mkstring("foo/bar")); + // json_append_element(friends, json_mkstring("e:1")); + // json_append_element(friends, json_mkstring("jog/fok")); + // json_append_element(friends, json_mkstring("iss-iss")); + // json_append_element(friends, json_mkstring("db/station")); + + /* + * Run through the array of friends of this user. Get LAST object, + * which contains CARD and LOCATION data. Create an array of + * separate location and card objects to return in HTTP mode. + */ + + json_foreach(jud, friends) { + if ((np = splitter(jud->string_, "/:-", pairs)) != 2) { + continue; + } + if ((lastuserlist = last_users(pairs[0], pairs[1], NULL)) == NULL) { + splitterfree(pairs); + continue; + } + splitterfree(pairs); + + if ((obj = json_find_element(lastuserlist, 0)) == NULL) { + json_delete(lastuserlist); + continue; + } + + // printf("OBJ --->%s<---\n", json_stringify(obj, " ")); + + /* TID is mandatory; if we don't have that, skip */ + if ((jtid = json_find_member(obj, "tid")) == NULL) { + json_delete(lastuserlist); + continue; + } + + /* CARD */ + if (json_find_member(obj, "face") && json_find_member(obj, "name")) { + newob = json_mkobject(); + json_append_member(newob, "_type", json_mkstring("card")); + json_copy_element_to_object(newob, "tid", jtid); + json_copy_element_to_object(newob, "face", json_find_member(obj, "face")); + json_copy_element_to_object(newob, "name", json_find_member(obj, "name")); + json_append_element(results, newob); + } + + /* LOCATION */ + newob = json_mkobject(); + json_append_member(newob, "_type", json_mkstring("location")); + json_copy_element_to_object(newob, "tid", jtid); + json_copy_element_to_object(newob, "lat", json_find_member(obj, "lat")); + json_copy_element_to_object(newob, "lon", json_find_member(obj, "lon")); + json_copy_element_to_object(newob, "tst", json_find_member(obj, "tst")); + json_append_element(results, newob); + + json_delete(lastuserlist); + } + + json_delete(friends); + + return (results); +} + /* * Invoked from an HTTP POST to /pub?u=username&d=devicename * We need u and d in order to contruct a topic name. Obtain @@ -385,6 +482,7 @@ static int dopublish(struct mg_connection *conn, const char *uri) struct udata *ud = (struct udata *)conn->server_param; char *payload, *u, *d; static UT_string *topic = NULL; + JsonNode *jarray; if ((u = field(conn, "u")) == NULL) { u = strdup("owntracks"); @@ -396,8 +494,6 @@ static int dopublish(struct mg_connection *conn, const char *uri) utstring_renew(topic); utstring_printf(topic, "owntracks/%s/%s", u, d); - free(u); - free(d); /* We need a nul-terminated payload in handle_message() */ payload = calloc(sizeof(char), conn->content_len + 1); @@ -409,7 +505,11 @@ static int dopublish(struct mg_connection *conn, const char *uri) free(payload); - return json_response(conn, NULL); + jarray = populate_friends(conn, u, d); + free(u); + free(d); + + return json_response(conn, jarray); } /* diff --git a/recorder.c b/recorder.c index feacf76..a25b323 100644 --- a/recorder.c +++ b/recorder.c @@ -1490,6 +1490,11 @@ int main(int argc, char **argv) } gcache_close(gt); #endif /* !ENCRYPT */ + if ((gt = gcache_open(path, "friends", FALSE)) == NULL) { + fprintf(stderr, "Cannot lmdb-open `friends'\n"); + exit(2); + } + gcache_close(gt); exit(0); } @@ -1547,6 +1552,7 @@ int main(int argc, char **argv) # ifdef WITH_ENCRYPT ud->keydb = gcache_open(err, "keys", TRUE); # endif + ud->httpfriends = gcache_open(err, "friends", TRUE); #if WITH_LUA /* diff --git a/udata.h b/udata.h index 8097e91..ba6664f 100644 --- a/udata.h +++ b/udata.h @@ -38,6 +38,7 @@ struct udata { char *label; /* Server label */ char *geokey; /* Google reverse-geo API key */ int debug; /* enable for debugging */ + struct gcache *httpfriends; /* lmdb named database 'friends' */ }; #endif