Compare commits
4 commits
52663d1130
...
006fa693d3
Author | SHA1 | Date | |
---|---|---|---|
006fa693d3 | |||
bff30b9443 | |||
e0c686774c | |||
643f64cb74 |
3 changed files with 137 additions and 75 deletions
64
README
64
README
|
@ -65,47 +65,17 @@ at /usr/lib/x86_64-linux-gnu.
|
|||
|
||||
First, try it out.
|
||||
|
||||
--8<-------------------------------------------------------->8---
|
||||
$ cd /path/to/loop/home
|
||||
$ ./loop --help
|
||||
NAME:
|
||||
loop - An NNTP server for a circle of friends.
|
||||
|
||||
USAGE:
|
||||
loop [options] [arguments ...]
|
||||
|
||||
OPTIONS:
|
||||
--change-passwd <VALUE> <username> <new-password> changes password
|
||||
--create-account <VALUE> <username> <invited-by> creates a new account
|
||||
--help display usage information and exit
|
||||
--logging turn on debug logging on stderr
|
||||
--version display version and exit
|
||||
-l, --list-accounts lists accounts
|
||||
-r, --repl run a REPL on port 4006
|
||||
-s, --server runs NNTP server reading from stdout
|
||||
|
||||
AUTHORS:
|
||||
Circling Skies <loop@antartida.xyz>
|
||||
|
||||
LICENSE:
|
||||
GPL v3
|
||||
--8<-------------------------------------------------------->8---
|
||||
|
||||
You can talk to the NNTP server with -s:
|
||||
|
||||
--8<-------------------------------------------------------->8---
|
||||
$ ./loop -s
|
||||
$ ./loop
|
||||
200 Welcome! Say ``help'' for a menu.
|
||||
quit
|
||||
205 Good-bye.
|
||||
--8<-------------------------------------------------------->8---
|
||||
|
||||
It's time to create an account for you. Whenever you run loop, make
|
||||
sure you're in its home directory because it will look for the file
|
||||
accounts.lisp always relatively to the current working directory of
|
||||
the process. The same applies if you set up a cron job later
|
||||
on---make sure the job, too, sets LOOP's home directory as its current
|
||||
working directory.
|
||||
It runs. Whenever you run loop, make sure you're in its home
|
||||
directory because it will look for the file accounts.lisp always
|
||||
relatively to the current working directory of the process. The same
|
||||
applies if you set up a cron job later on---make sure the job, too,
|
||||
sets LOOP's home directory as its current working directory.
|
||||
|
||||
(*) Create your account
|
||||
|
||||
|
@ -151,6 +121,28 @@ never done this, it will be better education if you learn to use
|
|||
daemontools and ucspi-tcp before going live with a LOOP community.
|
||||
It's easy and fun.
|
||||
|
||||
(*) LOOP's REPL
|
||||
|
||||
LOOP is totally hackable. Even if you're remotely connected, you can
|
||||
have total control over the loop process with the repl command:
|
||||
|
||||
$ telnet example.com 119
|
||||
Trying example.com...
|
||||
Connected to example.com.
|
||||
Escape character is '^]'.
|
||||
200 Welcome! Say ``help'' for a menu.
|
||||
login you <secret>
|
||||
200 Welcome, YOU.
|
||||
repl
|
||||
LOOP> *client*
|
||||
#S(CLIENT :GROUP NIL :ARTICLE 1 :USERNAME YOU :AUTH? YES)
|
||||
LOOP> (list-groups)
|
||||
(comp.editors.emacs comp.lisp comp.programming comp.unix
|
||||
humanities.poetry local.control.news local.havoc local.system.reports
|
||||
local.test local.users.evalu math.calculus math.havoc)
|
||||
LOOP> quit
|
||||
200 Okay, no more REPL hacking.
|
||||
|
||||
(*) Cron jobs
|
||||
|
||||
If you'd like to remove inactive accounts, we wrote
|
||||
|
|
58
loop.lisp
58
loop.lisp
|
@ -12,6 +12,7 @@
|
|||
(:import-from :lisp-unit define-test)
|
||||
(:import-from :org.shirakumo.filesystem-utils
|
||||
directory-p list-directories list-files)
|
||||
(:import-from :sb-sys interactive-interrupt)
|
||||
(:export :main))
|
||||
|
||||
(in-package #:loop)
|
||||
|
@ -190,13 +191,27 @@
|
|||
(in-package :loop)
|
||||
(loop
|
||||
(print/finish "LOOP> ")
|
||||
(let ((expr (read)))
|
||||
(if (eq 'quit expr)
|
||||
(return
|
||||
(make-response
|
||||
:code 200 :request r
|
||||
:data "Okay, no more REPL hacking."))
|
||||
(println "~a" (eval expr))))))
|
||||
(handler-case
|
||||
(let ((expr (read)))
|
||||
(if (eq 'quit expr)
|
||||
(progn
|
||||
;; At this point, there's a newline still in the stdin due
|
||||
;; to read having returned an expression, so we must wipe
|
||||
;; that out.
|
||||
(clear-input)
|
||||
(return
|
||||
(make-response
|
||||
:code 200 :request r
|
||||
:data "Okay, no more REPL hacking.")))
|
||||
(println "~a" (eval expr))))
|
||||
(end-of-file ()
|
||||
(print/finish "^D~%")
|
||||
(uiop:quit 0))
|
||||
(interactive-interrupt ()
|
||||
(print/finish "^C~%")
|
||||
(uiop:quit 0))
|
||||
(t (c)
|
||||
(print/finish "Oops: ~a~%" c)))))
|
||||
(defun empty-response () (make-response :code 400 :data "I beg your pardon?"))
|
||||
(defun prepend-response-with (message r)
|
||||
(make-response
|
||||
|
@ -1278,6 +1293,16 @@
|
|||
(if okay?
|
||||
(println "Okay, account ~a now has password ``~a''." username new-passwd)
|
||||
(println "Sorry, could not change password: ~a." problem))))))
|
||||
(defun cli/main-with-handlers (cmd)
|
||||
(handler-case
|
||||
(cli/main cmd)
|
||||
(end-of-file ()
|
||||
(print/finish "^D~%")
|
||||
(uiop:quit 0))
|
||||
(interactive-interrupt ()
|
||||
(print/finish "^C~%")
|
||||
(uiop:quit 0))))
|
||||
|
||||
(defun cli/main (cmd)
|
||||
(read-accounts!)
|
||||
(connect-index! "message-id.db")
|
||||
|
@ -1296,25 +1321,26 @@
|
|||
(when pa
|
||||
(cli/change-passwd pa args))
|
||||
(when repl
|
||||
(repl))
|
||||
(when (and (not la) (not ca) (not pa))
|
||||
(repl (make-request :verb "repl" :args '(command-line))))
|
||||
(when (and (not la) (not ca) (not pa) (not repl))
|
||||
(server-start))))
|
||||
|
||||
(defun cli/command ()
|
||||
(clingon:make-command
|
||||
:name "loop"
|
||||
:description "An NNTP server for a circle of friends."
|
||||
:version "0.1" ;; :authors '("Circling Skies <loop@antartida.xyz>")
|
||||
:version "0.1"
|
||||
:license "GPL v3"
|
||||
:options (cli/options)
|
||||
:handler #'cli/main))
|
||||
:handler #'cli/main-with-handlers))
|
||||
|
||||
(defun main-loop ()
|
||||
(let* ((bs (nntp-read-line))
|
||||
(ln (bytes->string (ucs-2->ascii bs))))
|
||||
(let ((r (send-response! (dispatch-line ln))))
|
||||
(when (not (response-quit? r))
|
||||
(main-loop)))))
|
||||
(loop
|
||||
(let* ((bs (nntp-read-line))
|
||||
(ln (bytes->string (ucs-2->ascii bs))))
|
||||
(let ((r (send-response! (dispatch-line ln))))
|
||||
(when (response-quit? r)
|
||||
(return))))))
|
||||
|
||||
(defun request-quit? (r) (and r (string= 'quit (request-verb r))))
|
||||
(defun response-quit? (r) (and r (request-quit? (response-request r))))
|
||||
|
|
90
loop.nw
90
loop.nw
|
@ -377,21 +377,54 @@ LOOP> (list-groups)
|
|||
(local.control.news local.test)
|
||||
\end{verbatim}
|
||||
|
||||
We implement it by creating a [[repl]] procedure in \lp's package with
|
||||
an option handled by [[cli/command]].
|
||||
The REPL can also be use in the middle of an NNTP conversation, which
|
||||
is useful for debugging. All authenticated users can use it, making
|
||||
passwords only a barrier to spammers, not to your circle of friends,
|
||||
since everyone can find a way to log in with anyone's account, change
|
||||
their passwords or just do whatever they want to \lxxp\ process or
|
||||
databases.
|
||||
|
||||
As the comment below explains, we need to [[clear-output]] in the
|
||||
[[*standard-input*]] to avoid the following problem:
|
||||
%
|
||||
\begin{verbatim}
|
||||
repl
|
||||
LOOP> quit
|
||||
200 Okay, no more REPL hacking.
|
||||
400 I beg your pardon?
|
||||
\end{verbatim}
|
||||
%
|
||||
What happens is that [[read]] consumes just the symbol \verb|quit|, so
|
||||
there remains a \verb|\n| or \verb|\r\n| in the [[*standard-input*]],
|
||||
which we need to wipe out, or [[nntp-read-line]] will consume an empty
|
||||
line, which is what causes that 400 response.
|
||||
|
||||
<<Loop's REPL>>=
|
||||
(defun repl (r)
|
||||
(in-package :loop)
|
||||
(loop
|
||||
(print/finish "LOOP> ")
|
||||
(let ((expr (read)))
|
||||
(if (eq 'quit expr)
|
||||
(return
|
||||
(make-response
|
||||
:code 200 :request r
|
||||
:data "Okay, no more REPL hacking."))
|
||||
(println "~a" (eval expr))))))
|
||||
(handler-case
|
||||
(let ((expr (read)))
|
||||
(if (eq 'quit expr)
|
||||
(progn
|
||||
;; At this point, there's a newline still in the stdin due
|
||||
;; to read having returned an expression, so we must wipe
|
||||
;; that out.
|
||||
(clear-input)
|
||||
(return
|
||||
(make-response
|
||||
:code 200 :request r
|
||||
:data "Okay, no more REPL hacking.")))
|
||||
(println "~a" (eval expr))))
|
||||
(end-of-file ()
|
||||
(print/finish "^D~%")
|
||||
(uiop:quit 0))
|
||||
(interactive-interrupt ()
|
||||
(print/finish "^C~%")
|
||||
(uiop:quit 0))
|
||||
(t (c)
|
||||
(print/finish "Oops: ~a~%" c)))))
|
||||
@ %def repl
|
||||
|
||||
\section{Description of the package}
|
||||
|
@ -598,11 +631,12 @@ itself---so we can cascade actions based on a user's request.
|
|||
|
||||
<<Main loop>>=
|
||||
(defun main-loop ()
|
||||
(let* ((bs (nntp-read-line))
|
||||
(ln (bytes->string (ucs-2->ascii bs))))
|
||||
(let ((r (send-response! (dispatch-line ln))))
|
||||
(when (not (response-quit? r))
|
||||
(main-loop)))))
|
||||
(loop
|
||||
(let* ((bs (nntp-read-line))
|
||||
(ln (bytes->string (ucs-2->ascii bs))))
|
||||
(let ((r (send-response! (dispatch-line ln))))
|
||||
(when (response-quit? r)
|
||||
(return))))))
|
||||
|
||||
(defun request-quit? (r) (and r (string= 'quit (request-verb r))))
|
||||
(defun response-quit? (r) (and r (request-quit? (response-request r))))
|
||||
|
@ -702,6 +736,16 @@ to do that yet.
|
|||
Now let's write the main procedure in command-line parsing.
|
||||
|
||||
<<Command-line parsing>>=
|
||||
(defun cli/main-with-handlers (cmd)
|
||||
(handler-case
|
||||
(cli/main cmd)
|
||||
(end-of-file ()
|
||||
(print/finish "^D~%")
|
||||
(uiop:quit 0))
|
||||
(interactive-interrupt ()
|
||||
(print/finish "^C~%")
|
||||
(uiop:quit 0))))
|
||||
|
||||
(defun cli/main (cmd)
|
||||
(read-accounts!)
|
||||
(connect-index! "message-id.db")
|
||||
|
@ -720,18 +764,18 @@ Now let's write the main procedure in command-line parsing.
|
|||
(when pa
|
||||
(cli/change-passwd pa args))
|
||||
(when repl
|
||||
(repl))
|
||||
(when (and (not la) (not ca) (not pa))
|
||||
(repl (make-request :verb "repl" :args '(command-line))))
|
||||
(when (and (not la) (not ca) (not pa) (not repl))
|
||||
(server-start))))
|
||||
|
||||
(defun cli/command ()
|
||||
(clingon:make-command
|
||||
:name "loop"
|
||||
:description "<<Description>>"
|
||||
:version "<<Version>>" ;; :authors '("Circling Skies <loop@antartida.xyz>")
|
||||
:version "<<Version>>"
|
||||
:license "GPL v3"
|
||||
:options (cli/options)
|
||||
:handler #'cli/main))
|
||||
:handler #'cli/main-with-handlers))
|
||||
@ %def cli/options cli/command
|
||||
|
||||
\section{Request dispatching mechanism}
|
||||
|
@ -1982,11 +2026,10 @@ all or it has been discussed with the community beforehand.
|
|||
|
||||
\subsection{{\tt REPL}}
|
||||
|
||||
Opens a REPL for the pleasure of hacking \lp. The only thing we
|
||||
require is that the user be authenticated. This means that any user
|
||||
has total control over \lp. XXX: implement an option
|
||||
[[--disable-repl]] so that REPL hacking is turned off. (This would
|
||||
mean your users are not true hackers.)
|
||||
\lp\ is totally {\em hackable}. Users can say {\tt repl} to have
|
||||
complete control over their \lxxp\ process. XXX: we should implement
|
||||
an option [[--disable-repl]] so that REPL hacking is turned off.
|
||||
(This would mean your users are not true hackers.)
|
||||
|
||||
<<Command repl>>=
|
||||
(defun cmd-repl (r)
|
||||
|
@ -2687,6 +2730,7 @@ something to think about.
|
|||
(:import-from :lisp-unit define-test)
|
||||
(:import-from :org.shirakumo.filesystem-utils
|
||||
directory-p list-directories list-files)
|
||||
(:import-from :sb-sys interactive-interrupt)
|
||||
(:export :main))
|
||||
|
||||
(in-package #:loop)
|
||||
|
|
Loading…
Reference in a new issue