Disables the NNTP repl command when --disables-nntp-repl is given.

This commit is contained in:
Circling Skies 2024-12-21 11:21:29 -03:00
parent a89e088212
commit 3850c72b6d
6 changed files with 162 additions and 106 deletions

View file

@ -1,9 +1,10 @@
(#S(LOOP::ACCOUNT (#S(LOOP::ACCOUNT
:USERNAME "ROOT" :USERNAME "ROOT"
:SEEN 3943637447 :SEEN 3943778307
:LAST-POST NIL :LAST-POST NIL
:FRIENDS NIL :FRIENDS NIL
:PASS NIL :PASS #(166 101 164 89 32 66 47 157 65 126 72 103 239 220 79 184 160 74 31
63 255 31 160 126 153 142 134 247 247 162 122 227)
:PASS-LOCKED NIL :PASS-LOCKED NIL
:PASS-LOCKED-WHY NIL :PASS-LOCKED-WHY NIL
:CREATION 3913066800)) :CREATION 3913066800))

View file

@ -16,7 +16,7 @@ usage()
sed -E '/^\(defun |\(defmacro /{ sed -E '/^\(defun |\(defmacro /{
i\ i\
}' $* | sed '/^[ \t]*$/{ }' "$@" | sed '/^[ \t]*$/{
N N
/^[ \t]*\n$/D /^[ \t]*\n$/D
}' }'

View file

@ -1,6 +1,6 @@
;;; -*- Mode: LISP; Syntax: COMMON-LISP; -*- ;;; -*- Mode: LISP; Syntax: COMMON-LISP; -*-
(asdf:defsystem :LOOP (asdf:defsystem :LOOP
:version "afe7d0e" :version "a89e088"
:description "An NNTP server for a circle of friends." :description "An NNTP server for a circle of friends."
:depends-on (:lisp-unit :str :uiop :cl-ppcre :local-time :clsql-sqlite3 :clingon :depends-on (:lisp-unit :str :uiop :cl-ppcre :local-time :clsql-sqlite3 :clingon
:filesystem-utils :ironclad/digest/sha256) :filesystem-utils :ironclad/digest/sha256)

View file

@ -17,19 +17,20 @@
(in-package #:loop) (in-package #:loop)
(defparameter *debug* nil)
(defstruct account username seen last-post friends pass pass-locked pass-locked-why creation) (defstruct account username seen last-post friends pass pass-locked pass-locked-why creation)
(defparameter *accounts* nil) (defparameter *accounts* nil)
(defstruct client group (article 1) (username "ANONYMOUS") (auth? 'no)) (defstruct client group (article 1) (username "ANONYMOUS") (auth? 'no))
(defparameter *client* (make-client)) (defparameter *client* (make-client))
(defstruct request verb args said) (defstruct request verb args said)
(defstruct response code data request multi-line) (defstruct response code data request multi-line)
(defvar *default-database* nil) (defparameter *default-database* nil)
(defstruct command fn verb description) (defstruct command fn verb description)
(defparameter *commands-assoc* nil) (defparameter *commands-assoc* nil)
(defstruct article headers body) (defstruct article headers body)
(defparameter *months-inactive-allowed* 3) (defparameter *months-inactive-allowed* 3)
(defparameter *months-never-logged-in* 1) (defparameter *months-never-logged-in* 1)
(defvar *debug* nil) (defparameter *enable-nntp-repl* t)
(defun table-of-commands () (defun table-of-commands ()
`(("GROUP" ,#'cmd-group "sets the current group") `(("GROUP" ,#'cmd-group "sets the current group")
@ -843,7 +844,7 @@
(defun menu (ls) (defun menu (ls)
(loop for item in ls (loop for item in ls
collect (display-fn (car ls)))) collect (display-fn item)))
(defun display-fn (cmd-pair) (defun display-fn (cmd-pair)
(let ((cmd (cdr cmd-pair))) (let ((cmd (cdr cmd-pair)))
@ -935,16 +936,18 @@
(defun cmd-post (r) (defun cmd-post (r)
(with-auth (with-auth
(send-response! (send-response!
(make-response :code 340 (make-response
:data (format nil "Okay, go ahead. Suggested message-id ~a." :code 340
:data (format nil "Okay, go ahead. Suggested message-id ~a."
(suggest-message-id)))) (suggest-message-id))))
(let* ((bs (nntp-read-article))) (let* ((bs (nntp-read-article)))
(multiple-value-bind (okay? error) (conforms? bs) (multiple-value-bind (okay? error) (conforms? bs)
(if (not okay?) (cond ((not okay?)
(make-response :code 400 :request r (make-response
:data (format nil "Sorry. Your article doesn't conform: ~a." error)) :code 400 :request r
(multiple-value-bind (code reply) (post bs) :data (format nil "Sorry. Your article doesn't conform: ~a." error)))
(make-response :code code :request r :data reply))))))) (t (multiple-value-bind (code reply) (post bs)
(make-response :code code :request r :data reply))))))))
(defun post (bs) (defun post (bs)
(let ((ngs (newsgroups-header->list (let ((ngs (newsgroups-header->list
@ -1282,8 +1285,13 @@
(make-response :code 200 :data (format nil "state: ~a" *client*) :request r)) (make-response :code 200 :data (format nil "state: ~a" *client*) :request r))
(defun cmd-repl (r) (defun cmd-repl (r)
(with-auth (if *enable-nntp-repl*
(repl r))) (with-auth
(repl r))
(make-response
:code 400
:data "The REPL has been *explicitly* disabled by the sysadmin. :("
:request r)))
(defun notify-group-created (g) (defun notify-group-created (g)
(post-notification (post-notification
@ -1338,10 +1346,15 @@
:key :list-accounts) :key :list-accounts)
(clingon:make-option (clingon:make-option
:flag :flag
:description "runs a REPL" :description "runs a REPL right now"
:short-name #\r :short-name #\r
:long-name "repl" :long-name "repl"
:key :repl) :key :repl)
(clingon:make-option
:flag
:description "disables the NNTP REPL"
:long-name "disable-nntp-repl"
:key :disable-nntp-repl)
(clingon:make-option (clingon:make-option
:flag :flag
:description "logging (on stderr)" :description "logging (on stderr)"
@ -1392,12 +1405,16 @@
(connect-index! "message-id.db") (connect-index! "message-id.db")
(create-index!) (create-index!)
(let ((args (clingon:command-arguments cmd)) (let ((args (clingon:command-arguments cmd))
(run-server t)
(repl (clingon:getopt cmd :repl)) (repl (clingon:getopt cmd :repl))
(ca (clingon:getopt cmd :create-account)) (ca (clingon:getopt cmd :create-account))
(pa (clingon:getopt cmd :change-passwd)) (pa (clingon:getopt cmd :change-passwd))
(la (clingon:getopt cmd :list-accounts)) (la (clingon:getopt cmd :list-accounts))
(logging (clingon:getopt cmd :logging))) (logging (clingon:getopt cmd :logging))
(disable-nntp-repl (clingon:getopt cmd :disable-nntp-repl)))
(setf *debug* logging) (setf *debug* logging)
(when (or ca pa la)
(setf run-server nil))
(when la (when la
(cli/list-accounts)) (cli/list-accounts))
(when ca (when ca
@ -1406,14 +1423,16 @@
(cli/change-passwd pa args)) (cli/change-passwd pa args))
(when repl (when repl
(repl (make-request :verb "repl" :args '(command-line)))) (repl (make-request :verb "repl" :args '(command-line))))
(when (and (not la) (not ca) (not pa) (not repl)) (when disable-nntp-repl
(setq *enable-nntp-repl* nil))
(when run-server
(server-start)))) (server-start))))
(defun cli/command () (defun cli/command ()
(clingon:make-command (clingon:make-command
:name "loop" :name "loop"
:description "An NNTP server for a circle of friends." :description "An NNTP server for a circle of friends."
:version "afe7d0e" :version "a89e088"
:license "GPL v3" :license "GPL v3"
:options (cli/options) :options (cli/options)
:handler #'cli/main-with-handlers)) :handler #'cli/main-with-handlers))
@ -1443,7 +1462,7 @@
(send-response! (send-response!
(make-response (make-response
:code 200 :code 200
:data "Welcome! I am LOOP afe7d0e. Say ``help'' for a menu."))) :data "Welcome! I am LOOP a89e088. Say ``help'' for a menu.")))
(setq lisp-unit:*print-failures* t) (setq lisp-unit:*print-failures* t)
(define-test dispatching (define-test dispatching

200
loop.nw
View file

@ -47,6 +47,7 @@
{a circle out of fashion}} {a circle out of fashion}}
\date{January 2024} \date{January 2024}
\begin{document} \begin{document}
\pdfbookmark[1]{Introduction}{intro}
\fontfamily{cmr}\selectfont \fontfamily{cmr}\selectfont
\maketitle \maketitle
%\setlength{\parskip}{3pt} %\setlength{\parskip}{3pt}
@ -54,9 +55,9 @@
\Lp\ is an out-of-fashion program, used as medium of communication by \Lp\ is an out-of-fashion program, used as medium of communication by
antiquated people. \Lp\ members insist that technical communication antiquated people. \Lp\ members insist that technical communication
be made in writing and not in a hurry. That's how backwards they are. be made in writing and not in a hurry. To give you an idea, they
To give you an idea, they write \Lp\ in Lisp---jurassic technology. write \Lp\ in jurassic technology. You wouldn't pay them any
We surely wouldn't pay them any attention. attention.
% %
\begin{verbatim} \begin{verbatim}
Drunk and dressed in their best brown baggies and their platform soles Drunk and dressed in their best brown baggies and their platform soles
@ -74,6 +75,24 @@ the list of destinaries. So long as everyone replies to everyone,
John, too, will start getting all the messages. If anyone violates John, too, will start getting all the messages. If anyone violates
this rule of replying to everyone involved, the loop is broken. this rule of replying to everyone involved, the loop is broken.
%% \begin{figure}[!htb]
%% \centering \includegraphics[width=0.8\linewidth]{images/gnus-summary.png}
%% \caption{Gnus, a news reader embedded in the GNU EMACS text editor.}
%% \label{fg:gnus}
%% \end{figure}
%%
%% \begin{figure}[!htb]
%% \centering \includegraphics[width=0.8\linewidth]{images/tbird-summary.png}
%% \caption{Thunderbird, a news reader produced by the Mozilla Foundation.}
%% \label{fg:bird}
%% \end{figure}
%%
%% \begin{figure}[!htb]
%% \centering \includegraphics[width=0.8\linewidth]{images/sylpheed-summary.png}
%% \caption{Sylpheed, a news reader produced by Hiroyuki Yamamoto.}
%% \label{fg:sylpheed}
%% \end{figure}
There are surely inconveniences in using e-mail as conference medium. There are surely inconveniences in using e-mail as conference medium.
For example, after John has been added to the loop, he is not able to For example, after John has been added to the loop, he is not able to
leave by his own account. He needs to ask everyone involved to stop leave by his own account. He needs to ask everyone involved to stop
@ -118,41 +137,23 @@ NNTP-aware programs. You could write your own. Figures
\ref{fg:gnus}--\ref{fg:sylpheed} show a few programs for reading \ref{fg:gnus}--\ref{fg:sylpheed} show a few programs for reading
network news via NNTP. network news via NNTP.
\begin{figure}[!htb] \pdfbookmark[1]{Principles for a discussion group}{principles}
\centering \includegraphics[width=0.8\linewidth]{images/gnus-summary.png} \label{principles}
\caption{Gnus, a news reader embedded in the GNU EMACS text editor.} {\bf Principles for a discussion group}. We believe a discussion group
\label{fg:gnus}
\end{figure}
\begin{figure}[!htb]
\centering \includegraphics[width=0.8\linewidth]{images/tbird-summary.png}
\caption{Thunderbird, a news reader produced by the Mozilla Foundation.}
\label{fg:bird}
\end{figure}
\begin{figure}[!htb]
\centering \includegraphics[width=0.8\linewidth]{images/sylpheed-summary.png}
\caption{Sylpheed, a news reader produced by Hiroyuki Yamamoto.}
\label{fg:sylpheed}
\end{figure}
\noindent{\bf Principles for a discussion group}. We believe a discussion group
should be small and grow slowly. By ``slowly'', we mean that each should be small and grow slowly. By ``slowly'', we mean that each
member comes in through an invitation. This way, the group being member comes in through an invitation. This way, the group being
closed by definition, we keep spam out and give members a certain closed by definition, we keep spam out and give members a certain
sense of privilege. sense of privilege. A discussion group should be formed by interested
people. If a participant doesn't log-in for a certain period of time,
A discussion group should be formed by interested people. If a \lp locks the participant's account---see Section
participant doesn't log-in for a certain period of time, \lp locks the \ref{sec:inactive-users}. The account can be reactivated, but it will
participant's account---see Section \ref{sec:inactive-users}. The take asking another participant (with an active account) to do so. In
account can be reactivated, but it will take asking another other words, there's an encouragement for an uninterested member not
participant (with an active account) to do so. In other words, to come back to the \lp. The idea is to keep a certain cohesion in
there's an encouragement for an uninterested member not to come back the discussion groups. When an account is locked or unlocked, an
to the \lp. The idea is to keep a certain cohesion in the discussion article is posted to the group {\tt local.control.news}, so everyone
groups. When an account is locked or unlocked, an article is posted knows who is leaving and arriving. This way, participants get to have
to the group {\tt local.control.news}, so everyone knows who is an idea of who is reading them.
leaving and arriving. This way, participants get to have an idea of
who is reading them.
Each invitation comes with a certain responsibility: it's possible to Each invitation comes with a certain responsibility: it's possible to
see who invited who. If {\tt BOB} misbehaves, everyone gets to see see who invited who. If {\tt BOB} misbehaves, everyone gets to see
@ -658,7 +659,7 @@ An NNTP server for a circle of friends.
@ @
<<Version>>= <<Version>>=
afe7d0e a89e088
@ @
\section{Parsing of requests} \section{Parsing of requests}
@ -709,10 +710,15 @@ we need to is to implement a new option. The library is extensible.
:key :list-accounts) :key :list-accounts)
(clingon:make-option (clingon:make-option
:flag :flag
:description "runs a REPL" :description "runs a REPL right now"
:short-name #\r :short-name #\r
:long-name "repl" :long-name "repl"
:key :repl) :key :repl)
(clingon:make-option
:flag
:description "disables the NNTP REPL"
:long-name "disable-nntp-repl"
:key :disable-nntp-repl)
(clingon:make-option (clingon:make-option
:flag :flag
:description "logging (on stderr)" :description "logging (on stderr)"
@ -776,12 +782,16 @@ service starts with [[server-start]].
(connect-index! "message-id.db") (connect-index! "message-id.db")
(create-index!) (create-index!)
(let ((args (clingon:command-arguments cmd)) (let ((args (clingon:command-arguments cmd))
(run-server t)
(repl (clingon:getopt cmd :repl)) (repl (clingon:getopt cmd :repl))
(ca (clingon:getopt cmd :create-account)) (ca (clingon:getopt cmd :create-account))
(pa (clingon:getopt cmd :change-passwd)) (pa (clingon:getopt cmd :change-passwd))
(la (clingon:getopt cmd :list-accounts)) (la (clingon:getopt cmd :list-accounts))
(logging (clingon:getopt cmd :logging))) (logging (clingon:getopt cmd :logging))
(disable-nntp-repl (clingon:getopt cmd :disable-nntp-repl)))
(setf *debug* logging) (setf *debug* logging)
(when (or ca pa la)
(setf run-server nil))
(when la (when la
(cli/list-accounts)) (cli/list-accounts))
(when ca (when ca
@ -790,7 +800,9 @@ service starts with [[server-start]].
(cli/change-passwd pa args)) (cli/change-passwd pa args))
(when repl (when repl
(repl (make-request :verb "repl" :args '(command-line)))) (repl (make-request :verb "repl" :args '(command-line))))
(when (and (not la) (not ca) (not pa) (not repl)) (when disable-nntp-repl
(setq *enable-nntp-repl* nil))
(when run-server
(server-start)))) (server-start))))
(defun cli/command () (defun cli/command ()
@ -980,7 +992,7 @@ reason to think we're doing to debug it.}
(defun menu (ls) (defun menu (ls)
(loop for item in ls (loop for item in ls
collect (display-fn (car ls)))) collect (display-fn item)))
(defun display-fn (cmd-pair) (defun display-fn (cmd-pair)
(let ((cmd (cdr cmd-pair))) (let ((cmd (cdr cmd-pair)))
@ -1555,7 +1567,15 @@ XXX: instead of only catching [[sb-posix:syscall-error]], we should
catch anything else, reporting the error. Otherwise, we will blow up catch anything else, reporting the error. Otherwise, we will blow up
in case of some unexpected error, which might not be a bad idea---as in case of some unexpected error, which might not be a bad idea---as
long as we can log these errors and get a report later on of what's long as we can log these errors and get a report later on of what's
going on so we can improve the code. going on so we can improve the code. I still don't really know what
to do here. Let's leave it as it is. The original idea is to put a
[[t]]-case in the [[handler-case]] below and just log the error
instead of crashing completely. We can simulate the catching of an
unexpected condition by signaling it from fetch-article as a test.
This type of situation should have a testing routine as well. So,
yeah, first give yourself another read of the [[lisp-unit]]
documentation, then how to handle conditions properly and then come
back to this to-do item.
<<Commands head, body, article>>= <<Commands head, body, article>>=
(defun typical-cmd-response (code r g i get-data) (defun typical-cmd-response (code r g i get-data)
@ -1775,10 +1795,11 @@ now, however, we have only these two to worry about.
(ensure-header "date" #'get-date bs)) (ensure-header "date" #'get-date bs))
@ %def ensure-mid ensure-date @ %def ensure-mid ensure-date
Now it's time to look at the header \verb|newsgroups|. (XXX: Our code Now it's time to look at the header \verb|newsgroups|. XXX: we need
here is a bit confusing, but I don't know the best to do here, so I'm to rewrite this because we have that plan of verifying everything
going ahead unpretentiously.) If we get approved by [[conforms?]], there is to verify up front in [[conforms?]]. So when we invoke
then we verify the list of newsgroups right away. [[post]], there's nothing else to verify. We're verifying in two
places at the same time.
The name of each group must conform to the expression The name of each group must conform to the expression
@ -1801,16 +1822,18 @@ of trying to stop them.
(defun cmd-post (r) (defun cmd-post (r)
(with-auth (with-auth
(send-response! (send-response!
(make-response :code 340 (make-response
:data (format nil "Okay, go ahead. Suggested message-id ~a." :code 340
:data (format nil "Okay, go ahead. Suggested message-id ~a."
(suggest-message-id)))) (suggest-message-id))))
(let* ((bs (nntp-read-article))) (let* ((bs (nntp-read-article)))
(multiple-value-bind (okay? error) (conforms? bs) (multiple-value-bind (okay? error) (conforms? bs)
(if (not okay?) (cond ((not okay?)
(make-response :code 400 :request r (make-response
:data (format nil "Sorry. Your article doesn't conform: ~a." error)) :code 400 :request r
(multiple-value-bind (code reply) (post bs) :data (format nil "Sorry. Your article doesn't conform: ~a." error)))
(make-response :code code :request r :data reply))))))) (t (multiple-value-bind (code reply) (post bs)
(make-response :code code :request r :data reply))))))))
@ @
It's time to write the action of posting. One thing to keep in mind It's time to write the action of posting. One thing to keep in mind
@ -1820,14 +1843,14 @@ our index. More to follow.) More importantly, we cannot let the user
post to any group if one of the groups is incorrectly named---for post to any group if one of the groups is incorrectly named---for
example, when the group doesn't exist. Why don't we post to the ones example, when the group doesn't exist. Why don't we post to the ones
that are correct and warn the user of the ones that are incorrect? that are correct and warn the user of the ones that are incorrect?
Because that is not prudent. The user could be trying to divulge news Because that is not prudent. The user could be trying to publish news
to be received at the same time by various groups. We would make such to be received at the same time by various groups. We would make such
plans all go down the drain. So we don't. plans all go down the drain.
We collect a list of newsgroups that don't exist (or whose names do We collect a list of newsgroups that don't exist (or whose names do
not conform for any reason). If we find any such group, then we not conform for any reason). If we find any such group, then we
refuse posting and return a 400 code with a message describing which refuse posting and return a 400 code with a message describing which
group names failed. Otherwise we [[save-article-insist]]. group names failed. Otherwise we save the article.
<<Command post>>= <<Command post>>=
(defun post (bs) (defun post (bs)
@ -2096,16 +2119,30 @@ all or it has been discussed with the community beforehand.
\subsection{{\tt REPL}} \subsection{{\tt REPL}}
\lp\ is totally {\em hackable}. Users can say {\tt repl} to have \lp\ is totally {\em hackable}. Users can say {\tt repl} to have
complete control over their \lxxp\ process. XXX: we should implement complete control over their \lxxp\ process.
an option [[--disable-repl]] so that REPL hacking is turned off.
(This would mean your users are not true hackers.)
<<Command repl>>= <<Command repl>>=
(defun cmd-repl (r) (defun cmd-repl (r)
(with-auth (if *enable-nntp-repl*
(repl r))) (with-auth
(repl r))
(make-response
:code 400
:data "The REPL has been *explicitly* disabled by the sysadmin. :("
:request r)))
@ @
If your users are not the hacker-type, you can disable the NNTP REPL
with the command-line option [[--disable-nntp-repl]]. We decide not
to hide the command in the list of commands given by saying {\tt HELP}
to \lp\ because this way users are advertised about the commands that
exist---they could be having fun, but their sysadmin doesn't think
they're skilled enough.
<<Global variable that decides whether to enable the NNTP REPL>>=
(defparameter *enable-nntp-repl* t)
@
\section{Publication of news} \section{Publication of news}
If you're interested in being notified about what's going on in the If you're interested in being notified about what's going on in the
@ -2215,7 +2252,7 @@ working. Since we work with only one, we pretty much never need to
specify anything. specify anything.
<<Reference to the database>>= <<Reference to the database>>=
(defvar *default-database* nil) (defparameter *default-database* nil)
@ %def *default-database* @ %def *default-database*
<<How to create and connect to the index>>= <<How to create and connect to the index>>=
@ -2387,16 +2424,15 @@ Index built.
\section{Deletion and locking of inactive accounts}\label{sec:inactive-users} \section{Deletion and locking of inactive accounts}\label{sec:inactive-users}
XXX: remove this paragraph from here; present the program first and We now implement some of the principles exposed earlier on
then talk about it. In [[remove-friend]], note that [[username]] is page~\pageref{principles}. The program
the account name and [[friend]] is the name of the account being @<<cron-remove-inactive-users.lisp@>> would be run by {\tt cron} every
removed. Notice as well that we only know who invited the person day (at midnight, say). It checks all accounts that are inactive and
after we can get a hold of the account in [[accounts.lisp]]. This either locks them (to be deleted later) or deletes them {\em for
means we must scan each account to delete an account---we can't delete good}. If you want to keep accounts forever, just don't run the
an account and still leave the account as someone's friend. program. XXX: our idea is to also delete {\em for good} all accounts
that are locked (by the same period of time), but we have not yet done
The program [[cron-remove-inactive-users.lisp]] can be executed every that.
day at midnight, say.
<<cron-remove-inactive-users.lisp>>= <<cron-remove-inactive-users.lisp>>=
<<Quicklisp loading preamble>> <<Quicklisp loading preamble>>
@ -2408,15 +2444,12 @@ day at midnight, say.
(write-accounts!) (write-accounts!)
@ %def cron-remove-inactive-users.lisp @ %def cron-remove-inactive-users.lisp
In [[remove-account]], we probably should use [[delete-if]] as well on The entire program is really [[remove-inactive-users!]].
the list of friends since it is effectively what we are doing there
with [[setf]]. %% TODO
<<How to remove inactive users>>= <<How to remove inactive users>>=
(defun remove-inactive-users! () (defun remove-inactive-users! ()
(loop for u in *accounts* do (loop for u in *accounts* do
(let ((username (account-username u))) (let ((username (account-username u)))
(format t "Username: ~a~%" username)
(cond ((and (not (locked? username)) (cond ((and (not (locked? username))
(inactive-from-never-logged-in? username)) (inactive-from-never-logged-in? username))
(post-notification (post-notification
@ -2442,10 +2475,10 @@ with [[setf]]. %% TODO
To remove an account, we need to first remove the username (to be To remove an account, we need to first remove the username (to be
removed) from anyone's list of friends. So, this involves scanning removed) from anyone's list of friends. So, this involves scanning
the entire list of accounts. Also, notice that delete ``may modify the entire list of accounts. Also, notice that delete ``may modify
{\em sequence}''. More importantly is to understand tha we really {\em sequence}''. More important is to understand that we really must
must {\tt setf} the return, otherwise we might find the deletion did {\tt setf} the return, otherwise we might find the deletion did not
not take effect---for example, when deleting the first element of a take effect---for example, when deleting the first element of a list.
list. (This deserves a better explanation, but if you know how linked (XXX: this deserves a better explanation, but if you know how linked
lists are implemented in C, say, then you're likely well aware of how lists are implemented in C, say, then you're likely well aware of how
it works.) it works.)
@ -2469,7 +2502,9 @@ Accounts that do not have a creation date up until today---Tue Sep 17
21:37:18 ESAST 2024---will have its creation dates migrated to the 21:37:18 ESAST 2024---will have its creation dates migrated to the
\Lp\ epoch, which is January 1st 2024, the exact month in which \Lp\ epoch, which is January 1st 2024, the exact month in which
\Lp\ was written. But notice that this migration is done only once. \Lp\ was written. But notice that this migration is done only once.
New system administrators of \Lp\ will never need to run this. New system administrators of \Lp\ will never need to run this. (We do
not remove this set of source code chunks because they serve as an
example of how to a migration like that.)
<<How to migrate accounts without a creation date>>= <<How to migrate accounts without a creation date>>=
(defun loop-epoch () (defun loop-epoch ()
@ -2916,6 +2951,7 @@ something to think about.
@ %def @ %def
<<Global variables>>= <<Global variables>>=
(defparameter *debug* nil)
<<Representation of accounts>> <<Representation of accounts>>
<<Representation of a client>> <<Representation of a client>>
<<Representation of requests and responses>> <<Representation of requests and responses>>
@ -2923,7 +2959,7 @@ something to think about.
<<Representation of commands>> <<Representation of commands>>
<<Representation of articles>> <<Representation of articles>>
<<Definition of maximum allowed inactive periods>> <<Definition of maximum allowed inactive periods>>
(defvar *debug* nil) <<Global variable that decides whether to enable the NNTP REPL>>
@ @
On which packages do we depend? On which packages do we depend?
@ -2942,7 +2978,7 @@ The \lp\ system definition:
:description "<<Description>>" :description "<<Description>>"
:depends-on (<<List of packages to be loaded>>) :depends-on (<<List of packages to be loaded>>)
:components ((:file "loop"))) :components ((:file "loop")))
@ %def :loop loop.asd @
\section*{Index of chunks} \section*{Index of chunks}
\nowebchunks \nowebchunks

View file

@ -4,11 +4,11 @@ usage()
printf 'usage: %s tag file\n' $0 printf 'usage: %s tag file\n' $0
exit 1 exit 1
} }
test $# '<' 2 && usage test $# -lt 2 && usage
tag="$1" tag="$1"
shift shift
sed "/<<Version>>=/ { sed "/<<Version>>=/ {
n; n;
c\\ c\\
$tag $tag
}" $* }" "$@"