1 /* $Id: sm.c 10405 2020-11-21 07:02:43Z iulius $
2 **
3 ** Provide a command line interface to the storage manager.
4 */
5
6 #include "config.h"
7 #include "clibrary.h"
8 #include <sys/uio.h>
9
10 #include "inn/buffer.h"
11 #include "inn/innconf.h"
12 #include "inn/messages.h"
13 #include "inn/qio.h"
14 #include "inn/wire.h"
15 #include "inn/libinn.h"
16 #include "inn/storage.h"
17
18 #define WIRE_CHUNK_SIZE 0x10000
19
20 static const char usage[] = "\
21 Usage: sm [-cdHiqrRSs] [token ...]\n\
22 \n\
23 Command-line interface to the INN storage manager. The default action is\n\
24 to display the complete article associated with each token given. If no\n\
25 tokens are specified on the command line, they're read from stdin, one per\n\
26 line.\n\
27 \n\
28 -c Show clear information about a token\n\
29 -d, -r Delete the articles associated with the given tokens\n\
30 -H Display the headers of articles only\n\
31 -i Translate tokens into newsgroup names and article numbers\n\
32 -q Suppress all error messages except usage\n\
33 -R Display the raw article rather than undoing wire format\n\
34 or consume wire format articles when storing\n\
35 -S Output articles in rnews batch file format\n\
36 -s Store the article provided on stdin\n";
37
38 /* The options that can be set on the command line, used to determine what to
39 do with each token. */
40 struct options {
41 bool artinfo; /* Show newsgroup and article number. */
42 bool clearinfo; /* Show clear information about a token. */
43 bool delete; /* Delete articles instead of showing them. */
44 bool header; /* Display article headers only. */
45 bool raw; /* Display or consume wire-format articles. */
46 bool rnews; /* Output articles as rnews batch files. */
47 };
48
49
50 /*
51 ** This basically duplicates what INN does, except that INN has to
52 ** do more complex things to add the Path header.
53 **
54 ** Note that we make no attempt to add history or overview information, at
55 ** least right now.
56 */
57 static bool
store_article_common(char * text,size_t size)58 store_article_common(char *text, size_t size)
59 {
60 char *start, *end;
61 ARTHANDLE handle = ARTHANDLE_INITIALIZER;
62 TOKEN token;
63
64 /* Build the basic article handle. */
65 handle.type = TOKEN_EMPTY;
66 handle.data = text;
67 handle.iov = xmalloc(sizeof(struct iovec));
68 handle.iov->iov_base = text;
69 handle.iov->iov_len = size;
70 handle.iovcnt = 1;
71 handle.len = size;
72 handle.arrived = 0;
73 handle.expires = 0;
74
75 /* Find the expiration time, if any. */
76 start = wire_findheader(text, size, "Expires", true);
77 if (start != NULL) {
78 char *expires;
79
80 end = wire_endheader(start, text + size - 1);
81 if (end == NULL)
82 die("cannot find end of Expires header");
83 expires = xstrndup(start, end - start);
84 handle.expires = parsedate_rfc5322_lax(expires);
85 free(expires);
86 if (handle.expires == (time_t) -1)
87 handle.expires = 0;
88 }
89
90 /* Find the appropriate newsgroups header. */
91 if (innconf->storeonxref) {
92 start = wire_findheader(text, size, "Xref", true);
93 if (start == NULL)
94 die("no Xref header found in message");
95 end = wire_endheader(start, text + size - 1);
96 if (end == NULL)
97 die("cannot find end of Xref header");
98 for (; *start != ' ' && start < end; start++)
99 ;
100 if (start >= end)
101 die("malformed Xref header");
102 start++;
103 } else {
104 start = wire_findheader(text, size, "Newsgroups", true);
105 if (start == NULL)
106 die("no Newsgroups header found in message");
107 end = wire_endheader(start, text + size - 1);
108 if (end == NULL)
109 die("cannot find end of Newsgroups header");
110 }
111 handle.groups = start;
112 handle.groupslen = end - start;
113
114 /* Store the article. */
115 token = SMstore(handle);
116 free(handle.iov);
117
118 if (token.type == TOKEN_EMPTY) {
119 warn("failed to store article: %s", SMerrorstr);
120 return false;
121 } else {
122 printf("%s\n", TokenToText(token));
123 return true;
124 }
125 }
126
127
128 /*
129 ** Given a file descriptor, read a post from that file descriptor and then
130 ** store it.
131 */
132 static bool
store_article(int fd)133 store_article(int fd)
134 {
135 struct buffer *article;
136 size_t size;
137 char *text;
138 bool result;
139
140 article = buffer_new();
141 if (!buffer_read_file(article, fd)) {
142 sysdie("cannot read article");
143 }
144 text = wire_from_native(article->data, article->left, &size);
145 buffer_free(article);
146 result = store_article_common(text, size);
147 free(text);
148 return result;
149 }
150
151
152 /*
153 ** Given a file descriptor, read any number of posts in wire format
154 ** from that file descriptor and store them.
155 */
156 static bool
store_wire_articles(int fd)157 store_wire_articles(int fd)
158 {
159 bool result = true;
160 bool skipping = false;
161 struct buffer *input;
162 size_t offset;
163 ssize_t got;
164
165 input = buffer_new();
166 buffer_resize(input, WIRE_CHUNK_SIZE);
167 offset = 0;
168 for (;;) {
169 got = buffer_read(input, fd);
170 if (got < 0) {
171 result = false;
172 syswarn("article read failed");
173 break;
174 }
175 if (got == 0)
176 break;
177
178 while (buffer_find_string(input, "\r\n.\r\n", offset, &offset)) {
179 size_t size;
180
181 size = offset + 5;
182 if (skipping) {
183 skipping = false;
184 } else {
185 char *text;
186
187 text = input->data + input->used;
188 if (!store_article_common(text, size))
189 result = false;
190 }
191 input->used += size;
192 input->left -= size;
193 offset = 0;
194 }
195
196 if (!skipping && input->used <= 0 && input->left >= input->size) {
197 if (input->size >= innconf->maxartsize) {
198 skipping = true;
199 result = false;
200 warn("article too large, skipping");
201 } else {
202 buffer_resize(input, input->size + WIRE_CHUNK_SIZE);
203 }
204 }
205 if (skipping && input->left > 4) {
206 input->used += input->left - 4;
207 input->left = 4;
208 }
209 if (input->left >= 4) {
210 offset = input->left - 4;
211 } else {
212 offset = 0;
213 }
214 if (input->used > 0)
215 buffer_compact(input);
216 }
217
218 if (input->left > 0)
219 warn("trailing garbage at end of input");
220
221 buffer_free(input);
222 return result;
223 }
224
225
226 /*
227 ** Process a single token, performing the operations specified in the given
228 ** options struct. Calls warn and die to display error messages; -q is
229 ** implemented by removing all the warn and die error handlers.
230 */
231 static bool
process_token(const char * id,const struct options * options)232 process_token(const char *id, const struct options *options)
233 {
234 TOKEN token;
235 struct artngnum artinfo;
236 ARTHANDLE *article;
237 size_t length;
238 char *text;
239
240 if (!IsToken(id)) {
241 warn("%s is not a storage token", id);
242 return false;
243 }
244 token = TextToToken(id);
245
246 if (options->artinfo) {
247 if (!SMprobe(SMARTNGNUM, &token, &artinfo)) {
248 warn("could not get article information for %s", id);
249 return false;
250 } else {
251 printf("%s: %lu\n", artinfo.groupname, artinfo.artnum);
252 free(artinfo.groupname);
253 }
254 } else if (options->clearinfo) {
255 text = SMexplaintoken(token);
256 printf("%s %s\n", id, text);
257 free(text);
258 } else if (options->delete) {
259 if (!SMcancel(token)) {
260 warn("could not remove %s: %s", id, SMerrorstr);
261 return false;
262 }
263 } else {
264 article = SMretrieve(token, options->header ? RETR_HEAD : RETR_ALL);
265 if (article == NULL) {
266 warn("could not retrieve %s", id);
267 return false;
268 }
269 if (options->raw) {
270 if (fwrite(article->data, article->len, 1, stdout) != 1)
271 die("output failed");
272 } else {
273 text = wire_to_native(article->data, article->len, &length);
274 if (options->rnews)
275 printf("#! rnews %lu\n", (unsigned long) length);
276 if (fwrite(text, length, 1, stdout) != 1)
277 die("output failed");
278 free(text);
279 }
280 SMfreearticle(article);
281 }
282 return true;
283 }
284
285
286 int
main(int argc,char * argv[])287 main(int argc, char *argv[])
288 {
289 int option;
290 bool okay, status;
291 struct options options = { false, false, false, false, false, false };
292 bool store = false;
293
294 /* Suppress notice messages like tradspool rebuilding its map. */
295 message_handlers_notice(0);
296
297 message_program_name = "sm";
298
299 if (!innconf_read(NULL))
300 exit(1);
301
302 while ((option = getopt(argc, argv, "cdHiqrRSs")) != EOF) {
303 switch (option) {
304 case 'c':
305 options.clearinfo = true;
306 break;
307 case 'd':
308 case 'r':
309 options.delete = true;
310 break;
311 case 'H':
312 options.header = true;
313 break;
314 case 'i':
315 options.artinfo = true;
316 break;
317 case 'q':
318 message_handlers_warn(0);
319 message_handlers_die(0);
320 break;
321 case 'R':
322 options.raw = true;
323 break;
324 case 'S':
325 options.rnews = true;
326 break;
327 case 's':
328 store = true;
329 break;
330 default:
331 fprintf(stderr, usage);
332 exit(1);
333 }
334 }
335
336 /* Check options for consistency. */
337 if (options.artinfo && options.delete)
338 die("-i cannot be used with -r or -d");
339 if (options.artinfo && (options.header || options.raw || options.rnews))
340 die("-i cannot be used with -H, -R, or -S");
341 if (options.delete && (options.header || options.rnews))
342 die("-r or -d cannot be used with -H or -S");
343 if (options.raw && options.rnews)
344 die("-R cannot be used with -S");
345 if (options.header && options.rnews)
346 die("-H cannot be used with -S");
347 if (store && (options.artinfo || options.delete
348 || options.header || options.rnews))
349 die("-s cannot be used with -i, -r, -d, -H, or -S");
350 if (options.clearinfo && (options.artinfo || options.delete
351 || options.header || options.raw
352 || options.rnews || store))
353 die("-c cannot be used with -i, -r, -d, -H, -R, -S, or -s");
354
355 /* Initialize the storage manager. If we're doing article deletions, we
356 need to open it read/write. */
357 if (store || options.delete) {
358 bool value = true;
359
360 if (!SMsetup(SM_RDWR, &value))
361 die("cannot set up storage manager");
362 }
363 if (!SMinit())
364 die("cannot initialize storage manager: %s", SMerrorstr);
365
366 /* If we're storing articles, do that and then exit. */
367 if (store) {
368 if (options.raw) {
369 status = store_wire_articles(fileno(stdin));
370 } else {
371 status = store_article(fileno(stdin));
372 }
373 exit(status ? 0 : 1);
374 }
375
376 /* Process tokens. If no arguments were given on the command line,
377 process tokens from stdin. Otherwise, walk through the remaining
378 command line arguments. */
379 okay = true;
380 if (optind == argc) {
381 QIOSTATE *qp;
382 char *line;
383
384 qp = QIOfdopen(fileno(stdin));
385 for (line = QIOread(qp); line != NULL; line = QIOread(qp)) {
386 status = process_token(line, &options);
387 okay = okay && status;
388 }
389 if (QIOerror(qp)) {
390 if (QIOtoolong(qp))
391 die("input line too long");
392 sysdie("error reading stdin");
393 }
394 QIOclose(qp);
395 } else {
396 int i;
397
398 for (i = optind; i < argc; i++) {
399 status = process_token(argv[i], &options);
400 okay = okay && status;
401 }
402 }
403
404 SMshutdown();
405 exit(okay ? 0 : 1);
406 }
407