1/*
2 * Copyright (c) 1995-1999 Hannah Schroeter <hannah@mamba.pond.sub.org>
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *  1. Redistributions of source code must retain the above copyright
8 *     notice, this list of conditions and the following disclaimer,
9 *     either in a separate file included with the distribution, or
10 *     copied into the source files.
11 *  2. Redistributions in binary form must reproduce the above copyright
12 *     notice, this list of conditions and the following disclaimer in the
13 *     documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND HIS CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR HIS CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28#include <sys/types.h>
29
30#include <sys/file.h>
31#include <sys/time.h>
32#include <sys/resource.h>
33#include <sys/stat.h>
34#include <sys/wait.h>
35
36#include <errno.h>
37#include <fcntl.h>
38#include <signal.h>
39#include <stdio.h>
40#include <stdlib.h>
41#include <string.h>
42#include <time.h>
43#include <unistd.h>
44
45#ifdef RLIMIT_NOFILE
46struct rlimit fdlimit;
47#define getdtablesize() (getrlimit(RLIMIT_NOFILE,&fdlimit)?\
48		(perror("getrlimit RLIMIT_NOFILE"),exit(EX_OSERR),0):\
49		(int)fdlimit.rlim_cur)
50#endif
51
52#define MAXHOSTNAME 255
53#define TRANSFERBUFSIZE 2048
54/* safety */
55
56/* From .../sendmail/src/sysexits.h */
57#define EX_OK 0
58#define EX_USAGE 64
59#define EX_NOINPUT 66
60#define EX_NOHOST 68
61#define EX_OSERR 71
62#define EX_IOERR 74
63#define EX_TEMPFAIL 75
64
65#ifdef USE_FLOCK
66#undef USE_FLOCK
67#define USE_FLOCK 1
68#undef USE_LOCKFILES
69#else
70#undef USE_LOCKFILES
71#define USE_LOCKFILES 1
72#endif
73
74int maxfd;
75extern int errno;
76
77#if USE_LOCKFILES
78int lock (char *filename)
79{
80  int fd;
81  fd = open (filename, O_CREAT | O_EXCL, 0000);
82  if (fd >= 0) {
83    close (fd);
84    return 0;
85  }
86  if (errno != EEXIST) {
87    perror("open (lockfile)");
88    exit(EX_OSERR);
89  }
90  return -1;
91}
92
93void lockwait (char *filename)
94{
95  while (lock (filename)) sleep (2);
96}
97
98void unlock (char *filename)
99{
100  unlink (filename);
101}
102#endif
103
104void usage (char *av0)
105{
106  fprintf (stderr, "usage: %s method hostname\n", av0);
107  exit (EX_USAGE);
108}
109
110int main (int argc, char **argv)
111{
112  char *method, *host;
113#if USE_LOCKFILES
114  char lockfilename [MAXHOSTNAME+1+5];
115#endif
116  char hostbuf[MAXHOSTNAME+6];
117  int pid;
118  int pipefd[2];
119  char transmitter_path[FILENAME_MAX+1];
120  char transmitter_argv0 [FILENAME_MAX+1];
121  int i;
122  char transferbuf[TRANSFERBUFSIZE];
123  int hostfd;
124  int childstat;
125
126  maxfd = getdtablesize();
127
128  argv[argc]=0; /* XXX what for? */
129
130  if (argc != 3)
131    usage (argv[0]);
132
133  umask (007);
134  if (chdir ("@QUEUEDIR@")) {
135    perror ("chdir @QUEUEDIR@");
136    exit (EX_IOERR);
137  }
138  method = argv[1];
139  host = argv[2];
140  if (strlen (host) > MAXHOSTNAME) {
141    fprintf (stderr, "%s: Hostname too long\n", host);
142    exit (EX_NOHOST);
143  }
144  {
145    struct stat s;
146    if (stat (host, &s)) {
147      if (errno == ENOENT) {
148        fprintf (stderr, "%s: Host unknown (or no batch present)\n", host);
149        exit (EX_NOHOST);
150      } else {
151        fprintf (stderr, "stat @QUEUEDIR@/%s", host); perror ("");
152        exit (EX_IOERR);
153      }
154    }
155    if (!s.st_size) {
156      /* nothing to batch */
157      exit (0);
158    }
159  }
160  hostfd = open (host, O_RDWR, 0);
161  if (hostfd < 0) {
162    perror (host); exit (EX_IOERR);
163  }
164#if USE_LOCKFILES
165  sprintf (lockfilename, "%s.lock", host);
166  lockwait (lockfilename);
167#endif
168#if USE_FLOCK
169 lockredo:
170  if (flock(hostfd, LOCK_EX) < 0) {
171    switch (errno) {
172    case EBADF:
173      fprintf (stderr, "Panic: flock returns EBADF\n");
174      exit (EX_OSERR);
175      /* NOTREACHED */
176    case EINVAL:
177      fprintf (stderr, "Panic: flock returns EINVAL\n");
178      fprintf (stderr, "Was @QUEUEDIR@/%s not a plain file?\n", host);
179      exit (EX_OSERR);
180      /* NOTREACHED */
181    case EINTR:
182      goto lockredo;
183    default:
184      perror("Panic: flock");
185      exit(EX_OSERR);
186    }
187  }
188#endif
189  if (! strncmp("@LOCALHOSTNAME@", "`hostname", 9)) {
190    if (gethostname (hostbuf, MAXHOSTNAME)) {
191#if USE_LOCKFILES
192      unlock (lockfilename);
193#endif
194      perror ("gethostname"); exit (EX_OSERR);
195        /* flock() lock implicitly cleared */
196    }
197  } else {
198    strcpy(hostbuf, "@LOCALHOSTNAME@");
199  }
200  hostbuf[sizeof(hostbuf)-1] = 0;
201  if (! strchr (hostbuf, '.') && strcmp("@DOMAINSUFFIX@", "none"))
202    strcat (hostbuf, "@DOMAINSUFFIX@");
203  if (pipe (pipefd)) {
204#if USE_LOCKFILES
205    unlock (lockfilename);
206#endif
207    perror ("pipe"); exit (EX_OSERR);
208      /* flock() locks are implicitly cleared */
209  }
210  strcpy (transmitter_path, "@PRIVBINDIR@/transmitter.");
211  if (strlen (transmitter_path) + strlen (method) > FILENAME_MAX) {
212    fprintf (stderr, "%s: Method name too long\n", method);
213#if USE_LOCKFILES
214    unlock (lockfilename);
215#endif
216    exit (EX_USAGE);
217      /* flock() locks are implicitly cleared */
218  }
219  strcat (transmitter_path, method);
220  strcpy (transmitter_argv0, "transmitter.");
221  strcat (transmitter_argv0, method);
222  pid = fork ();
223  if (pid < 0) {
224    perror ("fork");
225#if USE_LOCKFILES
226    unlock (lockfilename);
227#endif
228    exit (EX_OSERR);
229      /* flock() lock is implicitly cleared */
230  }
231  if (pid == 0) {
232    /* child */
233    close (pipefd[1]);
234    dup2 (pipefd[0], 0); /* fd 0 -> pipe */
235      /* keep fd 1 and fd 2 */
236    for (i = 3; i < maxfd; i++) close (i);
237      /* note: this will also close hostfd. Thus, the flock() variant
238       * of the batcher relies on the fact, that flock() has
239       * the BSD last close semantics (and renders broken flock()
240       * emulations basing on POSIX locks useless in such situations!)
241       */
242    if (setpgid (0, getpid())) {
243      perror ("setpgid");
244      exit (EX_OSERR);
245    }
246    execl (transmitter_path, transmitter_argv0, host, NULL);
247    perror ("execl transmitter");
248    exit (EX_OSERR);
249  }
250  signal (SIGPIPE, SIG_IGN);
251  /* Now write the pipe */
252  if (write (pipefd[1], "HELO ", 5) != 5)
253    goto bad;
254  { int l = strlen (hostbuf);
255    if (write (pipefd[1], hostbuf, l) != l)
256      goto bad;
257  }
258  if (write (pipefd[1], "\n", 1) != 1)
259    goto bad;
260
261  while (1) {
262    int rd;
263    rd = read (hostfd, transferbuf, TRANSFERBUFSIZE);
264    if (rd < 0) goto bad;
265    if (rd == 0) break;
266    if (write (pipefd[1], transferbuf, rd) != rd) goto bad;
267  }
268  if (write (pipefd[1], "QUIT\n", 5) != 5) goto bad;
269  close (pipefd[1]);
270    /* send EOF */
271
272  while (wait (&childstat) != pid)
273    ;
274  if (WIFSIGNALED(childstat) || WEXITSTATUS(childstat)) goto bad2;
275  /* child exited ok */
276  if (ftruncate (hostfd, 0)) {
277#if USE_LOCKFILES
278    unlock (lockfilename);
279#endif
280    perror ("ftruncate"); exit (EX_OSERR);
281  }
282#if USE_LOCKFILES
283  unlock (lockfilename);
284#endif
285  exit (EX_OK);
286    /* flock() locks are cleared implicitly */
287bad:
288  /* parent error */
289  killpg (pid, SIGTERM);
290  sleep (2);
291  killpg (pid, SIGKILL);
292  while (wait (&childstat) != pid)
293    ;
294bad2:
295#if USE_LOCKFILES
296  unlock (lockfilename);
297#endif
298  if (WIFSIGNALED(childstat)) {
299    signal (WTERMSIG(childstat), SIG_DFL);
300    kill (getpid(), WTERMSIG(childstat));
301    sleep (1);
302    exit (WTERMSIG(childstat));
303  }
304  return (WEXITSTATUS(childstat));
305}
306