cgi.ml 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. (*
  2. * _ _ ____ _
  3. * _| || |_/ ___| ___ _ __ _ __ ___ | |
  4. * |_ .. _\___ \ / _ \ '_ \| '_ \ / _ \| |
  5. * |_ _|___) | __/ |_) | |_) | (_) |_|
  6. * |_||_| |____/ \___| .__/| .__/ \___/(_)
  7. * |_| |_|
  8. *
  9. * Personal Social Web.
  10. *
  11. * cgi.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. open Seppo_lib
  29. let ( let* ) = Result.bind
  30. let webfinger _uuid qs =
  31. match qs |> List.assoc_opt "resource" with
  32. | Some [resource] ->
  33. (match resource
  34. |> Rfc7565.of_string
  35. |> Result.get_ok
  36. |> Shell.webfinger with
  37. | Error e ->
  38. Logr.debug (fun m -> m "%s.%s %s" "cgi" "webfinger" e);
  39. Ok (`Bad_request, [Http.H.ct_plain], e |> Cgi.Response.body)
  40. | Ok q ->
  41. match
  42. q.links |> As2_vocab.Types.Webfinger.self_link,
  43. q.links |> As2_vocab.Types.Webfinger.profile_page,
  44. qs |> List.assoc_opt "redirect" with
  45. | Some j,_,Some [{|self|}] ->
  46. let r = Uri.make
  47. ~path:"actor"
  48. ~query:["id",[j |> Uri.to_string]]
  49. () in
  50. r
  51. |> Uri.to_string
  52. |> Http.s302
  53. | _,Some h,Some [{|http://webfinger.net/rel/profile-page|}] ->
  54. h
  55. |> Uri.to_string
  56. |> Http.s302
  57. | _,_,_ ->
  58. Ok (`OK, [Http.H.ct_json], fun oc ->
  59. q
  60. |> As2_vocab.Encode.Webfinger.query_result ~base:Uri.empty
  61. |> Ezjsonm.value_to_channel oc ))
  62. | _ -> Http.s400
  63. let actor _uuid qs (r : Cgi.Request.t) =
  64. match qs |> List.assoc_opt "id" with
  65. | Some [id] ->
  66. let key =
  67. (* https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12#appendix-C *)
  68. let pem = {|-----BEGIN RSA PRIVATE KEY-----
  69. MIICXgIBAAKBgQDCFENGw33yGihy92pDjZQhl0C36rPJj+CvfSC8+q28hxA161QF
  70. NUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6Z4UMR7EOcpfdUE9Hf3m/hs+F
  71. UR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJwoYi+1hqp1fIekaxsyQIDAQAB
  72. AoGBAJR8ZkCUvx5kzv+utdl7T5MnordT1TvoXXJGXK7ZZ+UuvMNUCdN2QPc4sBiA
  73. QWvLw1cSKt5DsKZ8UETpYPy8pPYnnDEz2dDYiaew9+xEpubyeW2oH4Zx71wqBtOK
  74. kqwrXa/pzdpiucRRjk6vE6YY7EBBs/g7uanVpGibOVAEsqH1AkEA7DkjVH28WDUg
  75. f1nqvfn2Kj6CT7nIcE3jGJsZZ7zlZmBmHFDONMLUrXR/Zm3pR5m0tCmBqa5RK95u
  76. 412jt1dPIwJBANJT3v8pnkth48bQo/fKel6uEYyboRtA5/uHuHkZ6FQF7OUkGogc
  77. mSJluOdc5t6hI1VsLn0QZEjQZMEOWr+wKSMCQQCC4kXJEsHAve77oP6HtG/IiEn7
  78. kpyUXRNvFsDE0czpJJBvL/aRFUJxuRK91jhjC68sA7NsKMGg5OXb5I5Jj36xAkEA
  79. gIT7aFOYBFwGgQAQkWNKLvySgKbAZRTeLBacpHMuQdl1DfdntvAyqpAZ0lY0RKmW
  80. G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI
  81. 7U1yQXnTAEFYM560yJlzUpOb1V4cScGd365tiSMvxLOvTA==
  82. -----END RSA PRIVATE KEY-----|} in
  83. let base = r |> Cgi.Request.base in
  84. let base = Uri.make ~path:(r.script_name ^ "/") () |> Http.reso ~base in
  85. let path = "actor.jsa" in
  86. let id' = Uri.make ~path () |> Http.reso ~base in
  87. let key_id = id' |> Ap.Person.my_key_id in
  88. let pk = pem
  89. |> Cstruct.of_string
  90. |> Ap.PubKeyPem.private_of_pem_data
  91. |> Result.get_ok in
  92. Some (Http.Signature.mkey key_id pk (Ptime_clock.now ()))
  93. in
  94. (match id |> Uri.of_string |> Shell.actor ~key with
  95. | Error e ->
  96. Logr.debug (fun m -> m "%s.%s %s" "cgi" "actor" e);
  97. Ok (`Bad_request, [Http.H.ct_plain], e |> Cgi.Response.body)
  98. | Ok q ->
  99. Ok (`OK, [Http.H.ct_jlda], fun oc ->
  100. let lang = As2_vocab.Constants.ActivityStreams.und in
  101. q
  102. |> As2_vocab.Encode.person ~lang ~base:Uri.empty
  103. |> Ezjsonm.value_to_channel oc ))
  104. | _ -> Http.s400
  105. (* a callback endpoint for signing pem *)
  106. let actor_jsa uuid r =
  107. let path = "actor.jsa" in
  108. let base = r |> Cgi.Request.base in
  109. let base = Uri.make ~path:(r.script_name ^ "/") () |> Http.reso ~base in
  110. let lang = Some "und"
  111. (* https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12#appendix-C *)
  112. and id = Uri.make ~path () |> Http.reso ~base in
  113. assert (id |> Uri.to_string |> St.is_suffix ~affix:"/apchk.cgi/actor.jsa");
  114. let name = Some "ApChk.cgi" in
  115. let preferred_username = name
  116. and pem = {|-----BEGIN PUBLIC KEY-----
  117. MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCFENGw33yGihy92pDjZQhl0C3
  118. 6rPJj+CvfSC8+q28hxA161QFNUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6
  119. Z4UMR7EOcpfdUE9Hf3m/hs+FUR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJw
  120. oYi+1hqp1fIekaxsyQIDAQAB
  121. -----END PUBLIC KEY-----|}
  122. and signatureAlgorithm = Some "https://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
  123. in
  124. {Ap.Person.empty with
  125. id;
  126. name;
  127. preferred_username;
  128. generator = Some {href=St.seppo_u; name; name_map=[]; rel=None};
  129. public_key =
  130. {
  131. id = id |> Ap.Person.my_key_id;
  132. owner = Some id;
  133. pem;
  134. signatureAlgorithm;
  135. };
  136. }
  137. |> As2_vocab.Encode.person ~base ~lang
  138. |> Ezjsonm.value_to_string ~minify:false
  139. |> Http.clob_send uuid Http.Mime.app_jlda
  140. let handle uuid _ic (req : Cgi.Request.t) : Cgi.Response.t =
  141. let dispatch (r : Cgi.Request.t) =
  142. let send_res ct p = match ("static" ^ p) |> Res.read with
  143. | None -> Http.s500
  144. | Some b -> Http.clob_send uuid ct b in
  145. match r.path_info, r.request_method |> Cohttp.Code.method_of_string with
  146. | ("/doap.rdf" as p, `GET) -> p |> send_res Http.Mime.text_xml
  147. | ("/LICENSE" as p, `GET) -> p |> send_res Http.Mime.text_plain
  148. | ("/doap2html.xsl" as p, `GET) -> p |> send_res Http.Mime.text_xsl
  149. | "", `GET -> Http.s302 (req.script_name ^ "/xml")
  150. | "/", `GET -> Http.s302 req.script_name
  151. | "/actor", `GET -> r |> actor uuid (r.query_string |> Uri.query_of_encoded)
  152. | "/actor.jsa", `GET -> r |> actor_jsa uuid
  153. | "/version", `GET ->
  154. Printf.sprintf
  155. "https://Seppo.Social/v/%s+%s" Version.dune_project_version Version.git_sha
  156. |> Http.s302
  157. | "/webfinger", `GET -> r.query_string |> Uri.query_of_encoded |> webfinger uuid
  158. | "/css", `GET -> "/apchk.css" |> send_res Http.Mime.text_css
  159. | "/xml", `GET -> "/apchk.xml" |> send_res Http.Mime.text_xml
  160. | "/xsl", `GET -> "/apchk.xsl" |> send_res Http.Mime.text_xsl
  161. | _, `GET -> Http.s404
  162. | _ -> Http.s405
  163. and merge = function
  164. | Ok v -> v
  165. | Error v -> v
  166. in
  167. Logr.info (fun m -> m "%s -> %s %a" req.remote_addr req.request_method Uri.pp (req |> Cgi.Request.path_and_query));
  168. req
  169. |> dispatch
  170. |> merge