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