1 /* $Id: innxbatch.c 6351 2003-05-19 02:00:06Z rra $
3 ** Transmit batches to remote site, using the XBATCH command
4 ** Modelled after innxmit.c and nntpbatch.c
7 ** innxbatch [options] <serverhost> <file> ...
9 ** innxbatch -i <serverhost>
11 ** will connect to serverhost's nntp port, and transfer the named files,
12 ** with an xbatch command for every file. Files that have been sent
13 ** successfully are unlink()ed. In case of any error, innxbatch terminates
14 ** and leaves any remaining files untouched, for later transmission.
16 ** -D increase debug level
17 ** -v report statistics to stdout
19 ** -i read batch file names from stdin instead from command line.
20 ** For each successfully transmitted batch, an OK is printed on
21 ** stdout, to indicate that another file name is expected.
23 ** -t Timeout for connection attempt
24 ** -T Timeout for batch transfers.
25 ** We do not use any file locking. At worst, a batch could be transmitted
26 ** twice in parallel by two independant invocations of innxbatch.
27 ** To prevent this, innxbatch should be invoked by a shell script that uses
28 ** shlock(1) to achieve mutual exclusion.
33 #include "portable/socket.h"
34 #include "portable/time.h"
43 /* Needed on AIX 4.1 to get fd_set and friends. */
44 #ifdef HAVE_SYS_SELECT_H
45 # include <sys/select.h>
48 #include "inn/innconf.h"
49 #include "inn/messages.h"
50 #include "inn/timer.h"
55 ** Syslog formats - collected together so they remain consistent
58 "%s stats offered %lu accepted %lu refused %lu rejected %lu";
59 static char STAT2[] = "%s times user %.3f system %.3f elapsed %.3f";
60 static char CANT_CONNECT[] = "%s connect failed %s";
61 static char CANT_AUTHENTICATE[] = "%s authenticate failed %s";
62 static char XBATCH_FAIL[] = "%s xbatch failed %s";
63 static char UNKNOWN_REPLY[] = "Unknown reply after sending batch -- %s";
64 static char CANNOT_UNLINK[] = "cannot unlink %s: %m";
68 static bool Debug = 0;
69 static bool STATprint;
71 static double STATbegin;
72 static double STATend;
73 static char *XBATCHname;
74 static int FromServer;
76 static sig_atomic_t GotAlarm;
77 static sig_atomic_t GotInterrupt;
78 static sig_atomic_t JMPyes;
79 static jmp_buf JMPwhere;
80 static unsigned long STATaccepted;
81 static unsigned long STAToffered;
82 static unsigned long STATrefused;
83 static unsigned long STATrejected;
86 ** Send a line to the server. \r\n will be appended
89 REMwrite(int fd, char *p)
94 static char buff[NNTP_STRLEN];
96 for (dest = buff, i = 0; p[i]; ) *dest++ = p[i++];
101 for (dest = buff, i+=2; i; dest += err, i -= err) {
102 err = write(fd, dest, i);
104 syswarn("cannot write %s to %s", dest, REMhost);
109 fprintf(stderr, "> %s\n", p);
115 ** Print transfer statistics, clean up, and exit.
120 static char QUIT[] = "quit";
124 REMwrite(ToServer, QUIT);
126 STATend = TMRnow_double();
127 if (GetResourceUsage(&usertime, &systime) < 0) {
134 REMhost, STAToffered, STATaccepted, STATrefused, STATrejected);
136 printf(STAT2, REMhost, usertime, systime, STATend - STATbegin);
140 syslog(L_NOTICE, STAT1,
141 REMhost, STAToffered, STATaccepted, STATrefused, STATrejected);
142 syslog(L_NOTICE, STAT2, REMhost, usertime, systime, STATend - STATbegin);
150 ** Clean up the NNTP escapes from a line.
157 if ((p = strchr(buff, '\r')) != NULL)
159 if ((p = strchr(buff, '\n')) != NULL)
162 /* The dot-escape is only in text, not command responses. */
168 ** Read a line of input, with timeout. We expect only answer lines, so
169 ** we ignore \r\n-->\n mapping and the dot escape.
170 ** Return true if okay, *or we got interrupted.*
173 REMread(char *start, int size)
180 for (p = start; size; ) {
182 FD_SET(FromServer, &rmask);
185 i = select(FromServer + 1, &rmask, NULL, NULL, &t);
186 if (GotInterrupt) return true;
188 if (errno == EINTR) continue;
191 if (i == 0 || !FD_ISSET(FromServer, &rmask)) return false;
192 i = read(FromServer, p, size-1);
193 if (GotInterrupt) return true;
194 if (i <= 0) return false;
198 for ( ; h < p; h++) {
199 if (h > start && '\n' == *h && '\r' == h[-1]) {
207 fprintf(stderr, "< %s\n", start);
214 ** Handle the interrupt.
225 ** Send a whole xbatch to the server. Uses the global variables
226 ** REMbuffer & friends
229 REMsendxbatch(int fd, char *buf, int size)
235 for (i = size, p = buf; i; p += err, i -= err) {
236 err = write(fd, p, i);
238 syswarn("cannot write xbatch to %s", REMhost);
242 if (GotInterrupt) Interrupted();
244 fprintf(stderr, "> [%d bytes of xbatch]\n", size);
246 /* What did the remote site say? */
247 if (!REMread(buf, size)) {
248 syswarn("no reply after sending xbatch");
251 if (GotInterrupt) Interrupted();
253 /* Parse the reply. */
256 warn("unknown reply after sending batch -- %s", buf);
257 syslog(L_ERROR, UNKNOWN_REPLY, buf);
261 case NNTP_RESENDIT_VAL:
262 case NNTP_GOODBYE_VAL:
263 syslog(L_NOTICE, XBATCH_FAIL, REMhost, buf);
268 case NNTP_OK_XBATCHED_VAL:
270 if (Debug) fprintf(stderr, "will unlink(%s)\n", XBATCHname);
271 if (unlink(XBATCHname)) {
272 /* probably another incarantion was faster, so avoid further duplicate
275 syswarn("cannot unlink %s", XBATCHname);
276 syslog(L_NOTICE, CANNOT_UNLINK, XBATCHname);
287 ** Mark that we got interrupted.
290 CATCHinterrupt(int s)
294 /* Let two interrupts kill us. */
300 ** Mark that the alarm went off.
304 CATCHalarm(int s UNUSED)
308 longjmp(JMPwhere, 1);
313 ** Print a usage message and exit.
318 warn("Usage: innxbatch [-Dv] [-t#] [-T#] host file ...");
320 warn(" innxbatch [-Dv] [-t#] [-T#] -i host");
327 main(int ac, char *av[])
333 char buff[NNTP_STRLEN];
334 RETSIGTYPE (*old)(int) = NULL;
335 unsigned int ConnectTimeout;
336 unsigned int TotalTimeout;
340 char *XBATCHbuffer = NULL;
341 int XBATCHbuffersize = 0;
344 openlog("innxbatch", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
345 message_program_name = "innxbatch";
348 if (!innconf_read(NULL))
355 while ((i = getopt(ac, av, "Dit:T:v")) != EOF)
370 ConnectTimeout = atoi(optarg);
373 TotalTimeout = atoi(optarg);
382 /* Parse arguments; host and filename. */
389 /* Open a connection to the remote server. */
390 if (ConnectTimeout) {
392 old = xsignal(SIGALRM, CATCHalarm);
394 if (setjmp(JMPwhere))
395 die("cannot connect to %s: timed out", REMhost);
396 alarm(ConnectTimeout);
398 if (NNTPconnect(REMhost, NNTP_PORT, &From, &To, buff) < 0 || GotAlarm) {
400 warn("cannot connect to %s: %s", REMhost,
401 buff[0] ? REMclean(buff): strerror(errno));
403 syslog(L_NOTICE, CANT_CONNECT, REMhost, "timeout");
405 syslog(L_NOTICE, CANT_CONNECT, REMhost,
406 buff[0] ? REMclean(buff) : strerror(i));
411 fprintf(stderr, "< %s\n", REMclean(buff));
412 if (NNTPsendpassword(REMhost, From, To) < 0 || GotAlarm) {
414 syswarn("cannot authenticate with %s", REMhost);
415 syslog(L_ERROR, CANT_AUTHENTICATE,
416 REMhost, GotAlarm ? "timeout" : strerror(i));
417 /* Don't send quit; we want the remote to print a message. */
420 if (ConnectTimeout) {
422 xsignal(SIGALRM, old);
426 /* We no longer need standard I/O. */
427 FromServer = fileno(From);
428 ToServer = fileno(To);
430 #if defined(SOL_SOCKET) && defined(SO_SNDBUF) && defined(SO_RCVBUF)
432 if (setsockopt(ToServer, SOL_SOCKET, SO_SNDBUF, (char *)&i, sizeof i) < 0)
433 perror("cant setsockopt(SNDBUF)");
434 if (setsockopt(FromServer, SOL_SOCKET, SO_RCVBUF, (char *)&i, sizeof i) < 0)
435 perror("cant setsockopt(RCVBUF)");
436 #endif /* defined(SOL_SOCKET) && defined(SO_SNDBUF) && defined(SO_RCVBUF) */
438 GotInterrupt = false;
441 /* Set up signal handlers. */
442 xsignal(SIGHUP, CATCHinterrupt);
443 xsignal(SIGINT, CATCHinterrupt);
444 xsignal(SIGTERM, CATCHinterrupt);
445 xsignal(SIGPIPE, SIG_IGN);
447 xsignal(SIGALRM, CATCHalarm);
452 STATbegin = TMRnow_double();
454 /* main loop over all specified files */
455 for (XBATCHname = *av; ac && (XBATCHname = *av); av++, ac--) {
457 if (Debug) fprintf(stderr, "will work on %s\n", XBATCHname);
463 if (GotInterrupt) Interrupted();
465 if ((fd = open(XBATCHname, O_RDONLY, 0)) < 0) {
466 syswarn("cannot open %s, skipping", XBATCHname);
470 if (fstat(fd, &statbuf)) {
471 syswarn("cannot stat %s, skipping", XBATCHname);
476 XBATCHsize = statbuf.st_size;
477 if (XBATCHsize == 0) {
478 warn("batch file %s is zero length, skipping", XBATCHname);
482 } else if (XBATCHsize > XBATCHbuffersize) {
483 XBATCHbuffersize = XBATCHsize;
484 if (XBATCHbuffer) free(XBATCHbuffer);
485 XBATCHbuffer = xmalloc(XBATCHsize);
488 err = 0; /* stupid compiler */
489 for (i = XBATCHsize, p = XBATCHbuffer; i; i -= err, p+= err) {
490 err = read(fd, p, i);
492 syswarn("error reading %s, skipping", XBATCHname);
494 } else if (0 == err) {
495 syswarn("unexpected EOF reading %s, truncated", XBATCHname);
496 XBATCHsize = p - XBATCHbuffer;
504 if (GotInterrupt) Interrupted();
506 /* Offer the xbatch. */
507 snprintf(buff, sizeof(buff), "xbatch %d", XBATCHsize);
508 if (!REMwrite(ToServer, buff)) {
509 syswarn("cannot offer xbatch to %s", REMhost);
513 if (GotInterrupt) Interrupted();
515 /* Does he want it? */
516 if (!REMread(buff, (int)sizeof buff)) {
517 syswarn("no reply to XBATCH %d from %s", XBATCHsize, REMhost);
520 if (GotInterrupt) Interrupted();
522 /* Parse the reply. */
523 switch (atoi(buff)) {
525 warn("unknown reply to %s -- %s", XBATCHname, buff);
529 case NNTP_RESENDIT_VAL:
530 case NNTP_GOODBYE_VAL:
531 /* Most likely out of space -- no point in continuing. */
532 syslog(L_NOTICE, XBATCH_FAIL, REMhost, buff);
535 case NNTP_CONT_XBATCH_VAL:
536 if (!REMsendxbatch(ToServer, XBATCHbuffer, XBATCHsize))
540 case NNTP_SYNTAX_VAL:
541 case NNTP_BAD_COMMAND_VAL:
542 warn("server %s seems not to understand XBATCH: %s", REMhost, buff);
543 syslog(L_FATAL, XBATCH_FAIL, REMhost, buff);