is2s.ml 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. (*
  2. * _ _ ____ _
  3. * _| || |_/ ___| ___ _ __ _ __ ___ | |
  4. * |_ .. _\___ \ / _ \ '_ \| '_ \ / _ \| |
  5. * |_ _|___) | __/ |_) | |_) | (_) |_|
  6. * |_||_| |____/ \___| .__/| .__/ \___/(_)
  7. * |_| |_|
  8. *
  9. * Personal Social Web.
  10. *
  11. * is2s.ml
  12. *
  13. * Copyright (C) The #Seppo contributors. All rights reserved.
  14. *
  15. * This program is free software: you can redistribute it and/or modify
  16. * it under the terms of the GNU General Public License as published by
  17. * the Free Software Foundation, either version 3 of the License, or
  18. * (at your option) any later version.
  19. *
  20. * This program is distributed in the hope that it will be useful,
  21. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  22. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  23. * GNU General Public License for more details.
  24. *
  25. * You should have received a copy of the GNU General Public License
  26. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  27. *)
  28. (* https://www.w3.org/TR/activitypub/#inbox *)
  29. module Inbox = struct
  30. (* fetch http header values and map from lowercase plus the special name (request-target) *)
  31. let hdr (r : Cgi.Request.t) h =
  32. let toenv = String.map (function
  33. | '-' -> '_'
  34. | c -> Char.uppercase_ascii c) in
  35. let v = match toenv h with
  36. | "(REQUEST_TARGET)" -> (String.lowercase_ascii r.request_method) ^ " " ^ Cgi.Request.path_and_query r
  37. | "CONTENT_LENGTH"
  38. | "CONTENT_TYPE" as h -> h |> r.raw_string |> Option.value ~default:""
  39. | h -> ("HTTP_" ^ h) |> r.raw_string |> Option.value ~default:"" in
  40. Some v
  41. (* take a list of header names and fetch them incl. values. *)
  42. let hdrs hdr =
  43. List.fold_left
  44. (fun init k ->
  45. (match hdr k with
  46. | None -> init
  47. | Some v -> Cohttp.Header.add init k v)
  48. )
  49. (Cohttp.Header.init ())
  50. (* Receive the post request, verify te signature, parse the json and dispatch *)
  51. let post
  52. ?(blocked = Mapcdb.Cdb "app/var/db/blocked.cdb")
  53. ~base
  54. uuid
  55. now
  56. ic
  57. r : Cgi.Response.t' Lwt.t =
  58. Logr.debug (fun m -> m "%s.%s prepare my actor profile url & key" "Is2s.Inbox" "post");
  59. let (let*%) = Http.(let*%) in
  60. let run_delay = 60 in
  61. let*% si_v = "signature" |> hdr r |> Option.to_result ~none:Http.s422' in
  62. Logr.debug (fun m -> m "%s.%s %a Signature: %s" "Is2s.Inbox" "post" Uuidm.pp uuid si_v);
  63. (* Logr.debug (fun m -> m "%s.%s the signature header:\n%s" "Is2s.Inbox" "post" si_v); *)
  64. let*% si_v = si_v
  65. |> Http.Signature.decode
  66. |> Result.map_error
  67. (function
  68. | `NoMatch _
  69. | `ConverterFailure _ ->
  70. Logr.debug (fun m -> m "%s.%s Signature parsing failure" "Is2s.Inbox" "post");
  71. Http.s422') in
  72. let*% algo = si_v |> List.assoc_opt "algorithm" |> Option.to_result ~none:Http.s422' in
  73. let*% heads = si_v |> List.assoc_opt "headers" |> Option.to_result ~none:Http.s422' in
  74. let heads = heads |> String.split_on_char ' ' in
  75. let*% okeyid = si_v |> List.assoc_opt "keyId" |> Option.to_result ~none:Http.s422' in
  76. let okeyid = okeyid |> Uri.of_string in
  77. let*% sign = si_v |> List.assoc_opt "signature" |> Option.to_result ~none:Http.s422' in
  78. let sign = sign |> Base64.decode_exn |> Cstruct.of_string in
  79. let base = base () in
  80. let*% pk = Ap.PubKeyPem.(private_of_pem pk_pem) |> Result.map_error (fun s ->
  81. Logr.err (fun m -> m "%s %s.%s %s" E.e1009 "Is2s.Inbox" "post" s);
  82. Http.s500') in
  83. let me = Uri.make ~path:Ap.proj () |> Http.reso ~base in
  84. let mekeyid = me |> Ap.Person.key_id in
  85. let mekey : Http.t_sign_k = mekeyid,Ap.PubKeyPem.sign pk,now in
  86. (* don't queue it but re-try in case *)
  87. (* dereferencing tokeyid must yield an actor profile document. *)
  88. let%lwt siac = Ap.Actor.http_get ~key:(Some mekey) okeyid in
  89. let*% siac = siac
  90. |> Result.map_error (fun e ->
  91. Logr.warn (fun m -> m "%s.%s %a %s %s %a" "Is2s.Inbox" "post" Uuidm.pp uuid "get" e Uri.pp okeyid);
  92. Http.s502') in
  93. let*% _ = match blocked |> Mapcdb.find_string_opt (siac.id |> Uri.to_string) with
  94. | None -> Ok Http.s403'
  95. | Some _ -> Http.s403 in
  96. assert (not (me |> Uri.equal siac.id));
  97. let map_er0 msg =
  98. let mapr (`Msg e) =
  99. Logr.warn (fun m -> m "%s.%s %s %a %s %a" "Is2s.Inbox" "post" msg Uuidm.pp uuid e Uri.pp okeyid);
  100. Http.s422' in
  101. Result.map_error mapr in
  102. let*% siacpub = Ap.PubKeyPem.of_pem siac.public_key.pem |> map_er0 "parse key" in
  103. (* TODO? compare the key to what we knew before from this actor *)
  104. let heads = heads |> hdrs (hdr r) in
  105. let*% _ = heads
  106. |> Http.Signature.to_sign_string ~request:None
  107. |> Cstruct.of_string
  108. |> Ap.PubKeyPem.verify ~uuid ~key:okeyid ~algo siacpub sign
  109. |> map_er0 "verify signature" in
  110. (* now siac is the verified signing actor of this request *)
  111. let*% dig = Cohttp.Header.get heads "digest" |> Option.to_result ~none:Http.s422' in
  112. let*% cl = r.content_length |> Option.to_result ~none:Http.s400' in
  113. let*% cl = if cl <= Ap.content_length_max
  114. then Ok cl
  115. else Http.s413 in
  116. let bo = cl |> really_input_string ic in
  117. let dig' = Ap.PubKeyPem.digest_base64 bo in
  118. let*% _ = if dig' |> String.equal dig
  119. then Ok Http.s403'
  120. else (
  121. Logr.info (fun m -> m "%s.%s %a digest verification failed" "Is2s.Inbox" "post" Uuidm.pp uuid);
  122. Logr.debug (fun m -> m "%s.%s expected: %s" "Is2s.Inbox" "post" dig);
  123. Logr.debug (fun m -> m "%s.%s found : %s" "Is2s.Inbox" "post" dig');
  124. Logr.debug (fun m -> m "%s.%s data : %d {|%s|}" "Is2s.Inbox" "post" cl bo);
  125. Http.s422) in
  126. Logr.debug (fun m -> m "%s.%s %a verified body:\n%s" "Is2s.Inbox" "post" Uuidm.pp uuid bo);
  127. (* we could queue all further processing. *)
  128. let map_er1 msg =
  129. let mapr _ =
  130. Logr.warn (fun m -> m "%s.%s failed to %s %s" "Is2s.Inbox" "post.json" msg bo);
  131. Http.s422' in
  132. Result.map_error mapr in
  133. let*% j = bo |> Ezjsonm.from_string_result |> map_er1 "decode Json" in
  134. let*% o = j |> As2_vocab.Activitypub.Decode.obj |> map_er1 "decode ActivityPub object" in
  135. let key = mekey in
  136. let r = match o with
  137. | `Follow obj -> Ap.Followers.snd_accept ~uuid ~base ~key me siac obj
  138. | `Undo ({ obj = `Follow obj; _ } as a) -> Ap.Followers.snd_accept_undo ~uuid ~base ~key me siac {a with obj}
  139. | `Accept { obj = `Follow obj; _ } -> Ap.Following.rcv_accept ~uuid ~base me siac obj
  140. | `Create ({ obj = `Note obj; _ } as a) -> Ap.Note.rcv_create ~uuid ~base siac {a with obj}
  141. | `Update ({ obj = `Note obj; _ } as a) -> Ap.Note.rcv_update ~uuid ~base siac {a with obj}
  142. | `Reject obj -> Ap.rcv_reject ~uuid ~base siac obj
  143. | _ -> (
  144. Logr.warn (fun m -> m "%s.%s %a fallthrough\n%s" "Is2s.Inbox" "post" Uuidm.pp uuid bo);
  145. Ap.snd_reject ~uuid ~base ~key me siac j
  146. ) in
  147. let%lwt _ = Main.Queue.ping_and_forget ~base ~run_delay in
  148. r
  149. end