cfg.ml 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. (*
  2. * _ _ ____ _
  3. * _| || |_/ ___| ___ _ __ _ __ ___ | |
  4. * |_ .. _\___ \ / _ \ '_ \| '_ \ / _ \| |
  5. * |_ _|___) | __/ |_) | |_) | (_) |_|
  6. * |_||_| |____/ \___| .__/| .__/ \___/(_)
  7. * |_| |_|
  8. *
  9. * Personal Social Web.
  10. *
  11. * Copyright (C) The #Seppo contributors. All rights reserved.
  12. *
  13. * This program is free software: you can redistribute it and/or modify
  14. * it under the terms of the GNU General Public License as published by
  15. * the Free Software Foundation, either version 3 of the License, or
  16. * (at your option) any later version.
  17. *
  18. * This program is distributed in the hope that it will be useful,
  19. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. * GNU General Public License for more details.
  22. *
  23. * You should have received a copy of the GNU General Public License
  24. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  25. *)
  26. let seppo_cgi = "seppo.cgi"
  27. let random_pwd () =
  28. (* 12*8 bits of entropy packed into 16 legible characters *)
  29. Random0.random_buffer 12
  30. |> Cstruct.to_string
  31. |> Base64.encode_string ~alphabet:Base64.uri_safe_alphabet
  32. module Base = struct
  33. let fn = "app/etc/baseurl.s"
  34. let to_file fn u : (string,string) result =
  35. fn |> File.out_channel_replace (fun oc ->
  36. assert (u |> Uri.path |> St.is_prefix ~affix:"/");
  37. assert (u |> Uri.path |> St.is_suffix ~affix:"/");
  38. assert (not (u |> Uri.path |> St.is_suffix ~affix:"//"));
  39. Csexp.Atom (u
  40. |> Uri.to_string)
  41. |> Csexp.to_channel oc;
  42. Ok fn)
  43. let from_file =
  44. File.in_channel (fun ic ->
  45. match ic |> Csexp.input with
  46. | Error _ as e -> e
  47. | Ok Csexp.Atom b ->
  48. let b = b |> Uri.of_string in
  49. assert (match b |> Uri.scheme with
  50. | Some "http"
  51. | Some "https" -> true
  52. | _ -> false);
  53. assert (b |> Uri.host |> Option.is_some);
  54. assert (b |> Uri.to_string |> St.is_suffix ~affix:"/");
  55. assert (not (b |> Uri.path |> St.is_suffix ~affix:"//"));
  56. assert (b |> Uri.fragment |> Option.is_none);
  57. assert (b |> Uri.query |> List.length = 0);
  58. Ok b
  59. | _ -> Error __LOC__ )
  60. end
  61. module ServerSession = struct
  62. let timeout tnow =
  63. 30 * 60
  64. |> Ptime.Span.of_int_s
  65. |> Ptime.add_span tnow
  66. |> Option.value ~default:Ptime.min
  67. let fn = "app/var/run/session.s"
  68. let l32 = 32
  69. type t = Ptime.t * Cstruct.t
  70. let from_file fn =
  71. (* Logr.debug (fun m -> m "CookieSecret.from_file: %s" fn); *)
  72. let ( let* ) = Result.bind in
  73. try
  74. fn |> File.in_channel (fun ic ->
  75. let* t,l = match Csexp.input ic with
  76. | Error _ as e -> e
  77. | Ok Csexp.(List [Atom t; Atom s]) ->
  78. (match t |> Ptime.of_rfc3339 with
  79. | Ok (t,_,_) -> Ok (t,s |> Cstruct.of_string)
  80. | Error _ -> Error "expected rfc3339"
  81. )
  82. | _ -> Error "expected cookie secret" in
  83. assert (l32 = (l |> Cstruct.length));
  84. Ok (t,l))
  85. with _ -> Error "not found"
  86. let create ?(fn = fn) ?(sec = Random0.random_buffer l32) tnow =
  87. assert (sec |> Cstruct.length = l32);
  88. Logr.debug (fun m -> m "%s.%s" "Cfg.CookieSecret" "create_session");
  89. let te = tnow |> timeout in
  90. let r = te,sec in
  91. fn |> File.out_channel_replace (fun oc ->
  92. Csexp.(List [
  93. Atom (te |> Ptime.to_rfc3339);
  94. Atom (sec |> Cstruct.to_string);
  95. ])
  96. |> Csexp.to_channel oc);
  97. Some r
  98. let delete_session ?(fn = fn) () =
  99. Unix.unlink fn
  100. let valid_secret tnow ((to_,sec) : t) =
  101. if Ptime.is_later to_ ~than:tnow
  102. then Some sec
  103. else None
  104. end
  105. module Profile = struct
  106. type t = {
  107. title : string; (* similar atom:subtitle *)
  108. bio : string; (* similar atom:description *)
  109. language : Rfc4287.rfc4646;
  110. timezone : Timedesc.Time_zone.t;
  111. posts_per_page : int;
  112. }
  113. let validate p : (t, 'a) result =
  114. Ok p
  115. let encode p =
  116. let Rfc4287.Rfc4646 language = p.language in
  117. let tz : string = p.timezone |> Timedesc.Time_zone.name in
  118. let ppp : string = p.posts_per_page |> string_of_int in
  119. Csexp.(List [
  120. List [ Atom "title"; Atom p.title ] ;
  121. List [ Atom "bio"; Atom p.bio ] ;
  122. List [ Atom "language"; Atom language ] ;
  123. List [ Atom "timezone"; Atom tz ] ;
  124. List [ Atom "posts-per-page"; Atom ppp ] ;
  125. ])
  126. let decode = function
  127. | Ok Csexp.(List [
  128. List [ Atom "title"; Atom title ] ;
  129. List [ Atom "bio"; Atom bio ] ;
  130. List [ Atom "language"; Atom language ] ;
  131. List [ Atom "timezone"; Atom timezone ] ;
  132. List [ Atom "posts-per-page"; Atom posts_per_page ] ;
  133. ]) ->
  134. {
  135. title;
  136. bio;
  137. language = Rfc4287.Rfc4646 language;
  138. timezone = Timedesc.Time_zone.(timezone |> make |> Option.value ~default:Rfc3339.fallback);
  139. posts_per_page = posts_per_page |> int_of_string;
  140. }
  141. |> validate
  142. | Ok _ -> Error "profile field expectation failure"
  143. | Error _ as e -> e
  144. let from_file fn =
  145. try fn |> File.in_channel Csexp.input
  146. |> decode
  147. with
  148. | e ->
  149. Logr.err (fun m -> m "%s %a" __LOC__ St.pp_exc e);
  150. Error "failed to load profile from file"
  151. let to_file fn (p : t) =
  152. Logr.debug (fun m -> m "to_file '%s' ('%s')" fn p.title);
  153. fn |> File.out_channel_replace (fun oc ->
  154. p
  155. |> encode
  156. |> Csexp.to_channel oc;
  157. Ok fn )
  158. let fn = "app/etc/profile.s"
  159. let load
  160. ?(tz = Rfc3339.fallback)
  161. fn : t =
  162. let defa posts_per_page timezone : t =
  163. let language = Rfc4287.Rfc4646 "en"
  164. and title = "Yet Another #Seppo! 🌻"
  165. and bio = {|#Seppo — Personal Social Web. For you!
  166. Hooray! You successfully put the file seppo.cgi from https://Seppo.Social/en/support/#installation on your webspace, visited it and are now enjoying networking in the fediverse!
  167. #Seppo is an https://W3.org/TR/ActivityPub fediverse server software of unsurpassed sustainability and respects the https://permacomputing.net/Principles/. It has a minimal resource and carbon footprint and is built to work for decades without maintenance. Shared webspace is sufficient, no privileged access ('root') required. The seppo.cgi is active only in the moments you are sending and receiving posts. Your casual visitors won't ever use it. They get static files from your webspace. By renting that, your provider cares for security and you may sleep untroubled!
  168. CGIs entered the stage 1997 and drove the dotcom boom. Later on they got a bad name, mostly because they don't scale well to big numbers and can't serve millions of users at a time. Many younger developers are unaware of them. However, you are not a million users, you are just one! A CGI can very well serve one, it even has favourable security properties in this case. And remember, your visitors won't use the CGI.|}
  169. in {title;bio;language;timezone;posts_per_page}
  170. in
  171. match from_file fn with
  172. | Ok p -> p
  173. | Error e ->
  174. Logr.warn (fun m -> m "%s.%s: %s" "Cfg.Profile" "load" e);
  175. defa
  176. 50
  177. tz
  178. end
  179. module Urlcleaner = struct
  180. let fn = "app/etc/url-cleaner.s"
  181. type t = {
  182. rex : string;
  183. rep : string;
  184. }
  185. let is_valid v : (t, 'a) result = Ok v
  186. let of_file _fn =
  187. Error "not implemented yet"
  188. let apply' _c _s =
  189. Error "not implemented yet"
  190. let apply _l _s =
  191. Error "not implemented yet"
  192. end