engine.lisp 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. (in-package :cl-user)
  2. (defpackage #:assboard.engine
  3. (:use :cl :assboard.utils)
  4. (:export #:command-error
  5. #:process-command
  6. #:format-new-posting))
  7. (in-package #:assboard.engine)
  8. (defvar *posting-hash-salt* "[TEST]" "Key for HMAC for new post to get id-hash")
  9. (defvar *posting-hash-length* 8 "id-hash length")
  10. (defvar *command-valid-for* 600 "Seconds of validity for command timestamp")
  11. (defun sync-keys-with-trustlist ()
  12. (let ((keys (assboard.pgp:list-fingerprints))
  13. (trustlist (assboard.wot:get-trustlist)))
  14. ;; Drop no-longer-trusted keys
  15. (loop for fgp in keys
  16. unless (find fgp trustlist :test #'equal :key #'car)
  17. do (assboard.pgp:delete-key fgp))
  18. ;; Download new trusted keys
  19. (loop for (fgp . username) in trustlist
  20. unless (find fgp keys :test #'equal)
  21. do (assboard.pgp:recv-key fgp))))
  22. (define-condition command-error (error) ())
  23. (define-condition not-valid-pgp-error (command-error) ())
  24. (define-condition not-valid-command-error (command-error) ())
  25. (define-condition not-valid-signature-error (command-error) ())
  26. (define-condition not-valid-timestamp-error (command-error) ())
  27. (defun get-post-hash (body author)
  28. "Get part of body+author HMAC to use id-hash."
  29. (let ((hmac (crypto:make-hmac
  30. (trivial-utf-8:string-to-utf-8-bytes *posting-hash-salt*)
  31. :sha512)))
  32. (crypto:update-hmac
  33. hmac
  34. (trivial-utf-8:string-to-utf-8-bytes (concatenate 'string body author)))
  35. (subseq (cl-base64:usb8-array-to-base64-string
  36. (crypto:hmac-digest hmac) :uri t)
  37. 0 *posting-hash-length*)))
  38. (defun cmd/new-post (cmd raw)
  39. (let* ((fgp (assboard.data::raw-fingerprint raw))
  40. (author (or (cdr (assoc fgp
  41. assboard.wot:*trustlist-cache*
  42. :test #'equal))
  43. fgp))
  44. (title-end (position #\Newline cmd))
  45. (title (subseq cmd 10 title-end))
  46. (body (subseq cmd (+ title-end 2)))
  47. (hash (get-post-hash body author))
  48. (posting
  49. (pomo:make-dao 'data::posting
  50. :author author
  51. :hash hash
  52. :title title
  53. :body body))
  54. (posting-edition
  55. (pomo:make-dao 'data::posting-edition
  56. :posting-id (data::posting-id posting)
  57. :title title
  58. :body body
  59. :raw-id (data::raw-id raw))))
  60. (values posting posting-edition)))
  61. (defun format-new-posting (title body)
  62. (format nil "NEW POST: ~A~%~%~A" title body))
  63. (defun cmd/close-post (cmd raw))
  64. (defun cmd/update-post (cmd raw))
  65. (defparameter +commands+
  66. (list (cons "NEW POST: " #'cmd/new-post)
  67. (cons "CLOSE POST: " #'cmd/close-post)
  68. (cons "UPDATE POST: " #'cmd/update-post)))
  69. (defun get-command-handler (command)
  70. (cdr (find command +commands+ :key #'car
  71. :test #'(lambda (s w) (starts-with w s)))))
  72. (defun process-command (clearsigned)
  73. ;; Remove possible CRLF
  74. (setq clearsigned (replace-all clearsigned
  75. '(#\Return #\Linefeed)
  76. (string #\Linefeed)))
  77. ;; Simple vanity check
  78. (unless (starts-with "-----BEGIN PGP SIGNED MESSAGE-----" clearsigned)
  79. (error 'not-valid-pgp-error))
  80. (handler-case
  81. (let* ((start (+ 2 (search '(#\Newline #\Newline) clearsigned)))
  82. (end (1- (search "-----BEGIN PGP SIGNATURE-----" clearsigned)))
  83. (command (subseq clearsigned start end))
  84. (handler (get-command-handler command)))
  85. ;; Command check
  86. (unless handler
  87. (error 'not-valid-command-error))
  88. ;; Signature validation
  89. (multiple-value-bind (fgp unixtime)
  90. (assboard.pgp:verify clearsigned)
  91. (unless fgp
  92. (error 'not-valid-signature-error))
  93. (let ((timestamp (local-time:unix-to-timestamp unixtime)))
  94. ;; Timestamp check
  95. (unless (< (abs (local-time:timestamp-difference
  96. (local-time:now)
  97. timestamp))
  98. *command-valid-for*)
  99. (error 'not-valid-timestamp-error))
  100. ;; All checks performed, store raw command text and process it
  101. (let ((raw (pomo:make-dao 'data::raw
  102. :clearsigned clearsigned
  103. :fingerprint fgp
  104. :timestamp timestamp)))
  105. (funcall handler command raw)))))
  106. (type-error (e)
  107. (log:error e)
  108. (error 'not-valid-command-error))))