cgi.ml 7.1 KB

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