1 /*  $Id: tinyleaf.c 9767 2014-12-07 21:13:43Z iulius $
2 **
3 **  An extremely lightweight receive-only NNTP server.
4 **
5 **  Copyright 2003, 2004 Russ Allbery <eagle@eyrie.org>
6 **
7 **  Permission is hereby granted, free of charge, to any person obtaining a
8 **  copy of this software and associated documentation files (the "Software"),
9 **  to deal in the Software without restriction, including without limitation
10 **  the rights to use, copy, modify, merge, publish, distribute, sublicense,
11 **  and/or sell copies of the Software, and to permit persons to whom the
12 **  Software is furnished to do so, subject to the following conditions:
13 **
14 **  The above copyright notice and this permission notice shall be included in
15 **  all copies or substantial portions of the Software.
16 **
17 **  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 **  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 **  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
20 **  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 **  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 **  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 **  DEALINGS IN THE SOFTWARE.
24 */
25 
26 #include "config.h"
27 #include "clibrary.h"
28 #include <dirent.h>
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <sys/wait.h>
32 
33 #include "inn/dispatch.h"
34 #include "inn/messages.h"
35 #include "inn/md5.h"
36 #include "inn/nntp.h"
37 #include "inn/utility.h"
38 #include "inn/vector.h"
39 #include "inn/version.h"
40 #include "inn/libinn.h"
41 
42 /* Prototypes for command callbacks. */
43 static void command_help(struct cvector *, void *);
44 static void command_ihave(struct cvector *, void *);
45 static void command_quit(struct cvector *, void *);
46 
47 /* The actual command dispatch table for TinyNNTP.  This table MUST be
48    sorted. */
49 const struct dispatch commands[] = {
50     { "help",  command_help,  0, 0, NULL },
51     { "ihave", command_ihave, 1, 1, NULL },
52     { "quit",  command_quit,  0, 0, NULL }
53 };
54 
55 /* Global state for the daemon. */
56 struct state {
57     struct nntp *nntp;
58     unsigned long count;
59     unsigned long duplicates;
60     FILE *processor;
61 };
62 
63 
64 /*
65 **  Do a clean shutdown, which mostly just involves closing the open processor
66 **  pipe, if present, and reporting on any abnormal exit status.
67 */
68 static void
shutdown(struct state * state)69 shutdown(struct state *state)
70 {
71     int status;
72 
73     if (state->processor != NULL) {
74         status = pclose(state->processor);
75         if (status == -1)
76             syswarn("unable to wait for processor");
77         else if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
78             warn("processor exited with status %d", WEXITSTATUS(status));
79         else if (WIFSIGNALED(status))
80             warn("processor killed by signal %d", WTERMSIG(status));
81     }
82     notice("processed %lu articles, rejected %lu duplicates", state->count,
83            state->duplicates);
84     nntp_free(state->nntp);
85     exit(0);
86 }
87 
88 
89 /*
90 **  Handle an IHAVE command.  For the time being, we just ignore the message
91 **  ID argument.
92 */
93 static void
command_ihave(struct cvector * command,void * cookie)94 command_ihave(struct cvector *command, void *cookie)
95 {
96     struct state *state = cookie;
97     enum nntp_status status;
98     char filename[33], *article, *msgid;
99     unsigned char hash[16];
100     size_t length;
101     int fd, oerrno;
102 
103     msgid = xstrdup(command->strings[1]);
104     if (!nntp_respond(state->nntp, NNTP_CONT_IHAVE, "Send article"))
105         sysdie("cannot flush output");
106     status = nntp_read_multiline(state->nntp, &article, &length);
107     switch (status) {
108     case NNTP_READ_OK:
109         break;
110     case NNTP_READ_EOF:
111         die("connection closed while receiving article");
112         shutdown(state);
113         break;
114     case NNTP_READ_ERROR:
115         sysdie("network error while receiving article");
116         break;
117     case NNTP_READ_TIMEOUT:
118         die("network timeout while receiving article");
119         break;
120     case NNTP_READ_LONG:
121         warn("article %s exceeds maximum size", msgid);
122         free(msgid);
123         return;
124     default:
125         sysdie("internal: unknown NNTP library status");
126         break;
127     }
128     md5_hash((unsigned char *) msgid, strlen(msgid), hash);
129     inn_encode_hex(hash, sizeof(hash), filename, sizeof(filename));
130     fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
131     if (fd < 0) {
132         if (errno == EEXIST) {
133             if (!nntp_respond(state->nntp, NNTP_FAIL_IHAVE_REFUSE,
134                               "Duplicate"))
135                 sysdie("cannot flush output");
136             state->duplicates++;
137             free(msgid);
138             return;
139         }
140         sysdie("unable to create article file %s", filename);
141     }
142     if (xwrite(fd, article, length) < 0) {
143         oerrno = errno;
144         if (unlink(filename) < 0)
145             syswarn("cannot clean up failed write to %s, remove by hand",
146                 filename);
147         errno = oerrno;
148         sysdie("unable to write article %s to file %s", msgid,
149                filename);
150     }
151     close(fd);
152     if (state->processor != NULL) {
153         fprintf(state->processor, "%s %s\n", filename, msgid);
154         if (fflush(state->processor) == EOF || ferror(state->processor))
155             sysdie("unable to flush %s to processor", filename);
156     }
157     state->count++;
158     if (!nntp_respond(state->nntp, NNTP_OK_IHAVE, "Article received"))
159         sysdie("cannot flush output");
160     free(msgid);
161 }
162 
163 
164 /*
165 **  Process a HELP command, sending some useful (?) information.
166 */
167 static void
command_help(struct cvector * command UNUSED,void * cookie)168 command_help(struct cvector *command UNUSED, void *cookie)
169 {
170     struct state *state = cookie;
171 
172     nntp_respond_noflush(state->nntp, NNTP_INFO_HELP, "tinyfeed from %s",
173                          INN_VERSION_STRING);
174     nntp_send_line_noflush(state->nntp, "Supported commands:");
175     nntp_send_line_noflush(state->nntp, " ");
176     nntp_send_line_noflush(state->nntp, "  IHAVE <message-id>");
177     nntp_send_line_noflush(state->nntp, "  HELP");
178     nntp_send_line_noflush(state->nntp, "  QUIT");
179     nntp_send_line_noflush(state->nntp, ".");
180     if (!nntp_flush(state->nntp))
181         sysdie("cannot flush output");
182 }
183 
184 
185 /*
186 **  Process a QUIT command, closing down the server.
187 */
188 static void
command_quit(struct cvector * command UNUSED,void * cookie)189 command_quit(struct cvector *command UNUSED, void *cookie)
190 {
191     struct state *state = cookie;
192 
193     if (!nntp_respond(state->nntp, NNTP_OK_QUIT, "Goodbye"))
194         syswarn("cannot flush output during QUIT");
195     shutdown(state);
196 }
197 
198 
199 /*
200 **  Process a syntax error.
201 */
202 static void
command_syntax(struct cvector * command UNUSED,void * cookie)203 command_syntax(struct cvector *command UNUSED, void *cookie)
204 {
205     struct state *state = cookie;
206 
207     if (!nntp_respond(state->nntp, NNTP_ERR_SYNTAX, "Syntax error"))
208         sysdie("cannot flush output");
209 }
210 
211 
212 /*
213 **  Process an unknown command.
214 */
215 static void
command_unknown(struct cvector * command UNUSED,void * cookie)216 command_unknown(struct cvector *command UNUSED, void *cookie)
217 {
218     struct state *state = cookie;
219 
220     if (!nntp_respond(state->nntp, NNTP_ERR_COMMAND, "Unsupported command"))
221         sysdie("cannot flush output");
222 }
223 
224 
225 int
main(int argc,char * argv[])226 main(int argc, char *argv[])
227 {
228     struct state state = { NULL, 0, 0, NULL };
229     struct cvector *command;
230     enum nntp_status status;
231 
232     message_program_name = "tinyfeed";
233     message_handlers_notice(1, message_log_syslog_info);
234     message_handlers_warn(1, message_log_syslog_warning);
235     message_handlers_die(1, message_log_syslog_warning);
236 
237     /* Change to the spool directory where all articles will be written. */
238     if (argc < 2)
239         die("no spool directory specified");
240     if (chdir(argv[1]) < 0)
241         sysdie("cannot change directory to %s", argv[1]);
242 
243     /* If a processing command was specified, open a pipe to it. */
244     if (argc == 3) {
245         state.processor = popen(argv[2], "w");
246         if (state.processor == NULL)
247             sysdie("cannot open a pipe to %s", argv[2]);
248         setvbuf(state.processor, NULL, _IONBF, BUFSIZ);
249     }
250 
251     /* Go into the main input loop.  The only commands we support, for now,
252        are HELP, IHAVE and QUIT. */
253     notice("starting");
254     state.nntp = nntp_new(STDIN_FILENO, STDOUT_FILENO, 1024 * 1024, 10 * 60);
255     nntp_respond(state.nntp, NNTP_OK_BANNER_NOPOST, "tinyfeed ready");
256     command = cvector_new();
257     while (1) {
258         status = nntp_read_command(state.nntp, command);
259         switch (status) {
260         case NNTP_READ_OK:
261             dispatch(command, commands, ARRAY_SIZE(commands), command_unknown,
262                      command_syntax, &state);
263             break;
264         case NNTP_READ_EOF:
265             notice("connection closed");
266             shutdown(&state);
267             break;
268         case NNTP_READ_ERROR:
269             sysdie("network error");
270             break;
271         case NNTP_READ_TIMEOUT:
272             notice("network timeout");
273             shutdown(&state);
274             break;
275         case NNTP_READ_LONG:
276             warn("long line received");
277             break;
278         default:
279             sysdie("internal: unknown NNTP library status");
280             break;
281         }
282     }
283 }
284