chiark / gitweb /
Debianization for 1.01-2
[qmail] / qmail-remote.c
1 #include <sys/types.h>
2 #include <sys/socket.h>
3 #include <netinet/in.h>
4 #include <arpa/inet.h>
5 #include "sig.h"
6 #include "getln.h"
7 #include "stralloc.h"
8 #include "substdio.h"
9 #include "subfd.h"
10 #include "scan.h"
11 #include "case.h"
12 #include "error.h"
13 #include "auto_qmail.h"
14 #include "control.h"
15 #include "dns.h"
16 #include "alloc.h"
17 #include "quote.h"
18 #include "ip.h"
19 #include "ipalloc.h"
20 #include "ipme.h"
21 #include "gen_alloc.h"
22 #include "gen_allocdefs.h"
23 #include "str.h"
24 #include "now.h"
25 #include "exit.h"
26 #include "constmap.h"
27 #include "tcpto.h"
28 #include "timeoutconn.h"
29 #include "timeoutread.h"
30 #include "timeoutwrite.h"
31
32 #define HUGESMTPTEXT 5000
33
34 #define PORT_SMTP 25 /* silly rabbit, /etc/services is for users */
35 unsigned long port = PORT_SMTP;
36
37 GEN_ALLOC_typedef(saa,stralloc,sa,len,a)
38 GEN_ALLOC_readyplus(saa,stralloc,sa,len,a,i,n,x,10,saa_readyplus)
39 static stralloc sauninit = {0};
40
41 stralloc helohost = {0};
42 stralloc routes = {0};
43 struct constmap maproutes;
44 stralloc host = {0};
45 stralloc sender = {0};
46
47 saa reciplist = {0};
48
49 struct ip_address partner;
50
51 void out(s) char *s; { if (substdio_puts(subfdout,s) == -1) _exit(0); }
52 void zero() { if (substdio_put(subfdout,"\0",1) == -1) _exit(0); }
53 void zerodie() { zero(); substdio_flush(subfdout); _exit(0); }
54
55 void outsafe(sa) stralloc *sa; { int i; char ch;
56 for (i = 0;i < sa->len;++i) {
57 ch = sa->s[i]; if (ch < 33) ch = '?'; if (ch > 126) ch = '?';
58 if (substdio_put(subfdout,&ch,1) == -1) _exit(0); } }
59
60 void temp_nomem() { out("ZOut of memory. (#4.3.0)\n"); zerodie(); }
61 void temp_oserr() { out("Z\
62 System resources temporarily unavailable. (#4.3.0)\n"); zerodie(); }
63 void temp_noconn() { out("Z\
64 Sorry, I wasn't able to establish an SMTP connection. (#4.4.1)\n"); zerodie(); }
65 void temp_read() { out("ZUnable to read message. (#4.3.0)\n"); zerodie(); }
66 void temp_dnscanon() { out("Z\
67 CNAME lookup failed temporarily. (#4.4.3)\n"); zerodie(); }
68 void temp_dns() { out("Z\
69 Sorry, I couldn't find any host by that name. (#4.1.2)\n"); zerodie(); }
70 void temp_chdir() { out("Z\
71 Unable to switch to home directory. (#4.3.0)\n"); zerodie(); }
72 void temp_control() { out("Z\
73 Unable to read control files. (#4.3.0)\n"); zerodie(); }
74 void perm_partialline() { out("D\
75 SMTP cannot transfer messages with partial final lines. (#5.6.2)\n"); zerodie(); }
76 void perm_usage() { out("D\
77 I (qmail-remote) was invoked improperly. (#5.3.5)\n"); zerodie(); }
78 void perm_dns() { out("D\
79 Sorry, I couldn't find any host named ");
80 outsafe(&host);
81 out(". (#5.1.2)\n"); zerodie(); }
82 void perm_nomx() { out("D\
83 Sorry, I couldn't find a mail exchanger or IP address. (#5.4.4)\n");
84 zerodie(); }
85 void perm_ambigmx() { out("D\
86 Sorry. Although I'm listed as a best-preference MX or A for that host,\n\
87 it isn't in my control/locals file, so I don't treat it as local. (#5.4.6)\n");
88 zerodie(); }
89
90 int timeout = 1200;
91 int timeoutconnect = 60;
92
93 void getcontrols()
94 {
95  int r;
96  if (control_init() == -1)
97   { if (errno == error_nomem) temp_nomem(); temp_control(); }
98
99  if (control_readint(&timeout,"control/timeoutremote") == -1)
100   { if (errno == error_nomem) temp_nomem(); temp_control(); }
101  if (control_readint(&timeoutconnect,"control/timeoutconnect") == -1)
102   { if (errno == error_nomem) temp_nomem(); temp_control(); }
103
104  r = control_rldef(&helohost,"control/helohost",1,(char *) 0);
105  if (r == -1) if (errno == error_nomem) temp_nomem();
106  if (r != 1) temp_control();
107
108  switch(control_readfile(&routes,"control/smtproutes",0))
109   {
110    case -1:
111      if (errno == error_nomem) temp_nomem(); temp_control();
112    case 0:
113      if (!constmap_init(&maproutes,"",0,1)) temp_nomem(); break;
114    case 1:
115      if (!constmap_init(&maproutes,routes.s,routes.len,1)) temp_nomem(); break;
116   }
117 }
118
119 char smtptobuf[1024];
120 char smtpfrombuf[128];
121 stralloc smtpline = {0};
122 stralloc smtptext = {0};
123
124 void outsmtptext()
125 {
126  int i; 
127  if (smtptext.s) if (smtptext.len) if (smtptext.len < HUGESMTPTEXT)
128   {
129    if (substdio_puts(subfdout,"Remote host said: ") == -1) _exit(0);
130    for (i = 0;i < smtptext.len;++i)
131      if (!smtptext.s[i]) smtptext.s[i] = '?';
132    if (substdio_put(subfdout,smtptext.s,smtptext.len) == -1) _exit(0);
133    smtptext.len = 0;
134   }
135 }
136
137 unsigned long smtpcode(ss)
138 substdio *ss;
139 {
140  int match;
141  unsigned long code;
142
143  if (!stralloc_copys(&smtptext,"")) return 421;
144  do
145   {
146    if (getln(ss,&smtpline,&match,'\n') != 0) return 421;
147    if (!match) return 421;
148    if ((smtpline.len >= 2) && (smtpline.s[smtpline.len - 2] == '\r'))
149     {
150      smtpline.s[smtpline.len - 2] = '\n';
151      --smtpline.len;
152     }
153    if (!stralloc_cat(&smtptext,&smtpline)) return 421;
154    if (scan_nbblong(smtpline.s,smtpline.len,10,0,&code) != 3) return 421;
155    if (smtpline.len == 3) return code;
156   }
157  while (smtpline.s[3] == '-');
158
159  return code;
160 }
161
162 void outhost()
163 {
164  char x[IPFMT];
165
166  x[ip_fmt(x,&partner)] = 0;
167  out(x);
168 }
169
170 void writeerr()
171 {
172  out("ZConnected to "); outhost();
173  out(" but communications failed. (#4.4.2)\n");
174  zerodie();
175 }
176
177 void quit(ssto,ssfrom)
178 substdio *ssto;
179 substdio *ssfrom;
180 {
181  outsmtptext();
182  if (substdio_putsflush(ssto,"QUIT\r\n") != -1)
183    smtpcode(ssfrom); /* protocol design stupidity */
184  zerodie();
185 }
186
187 stralloc dataline = {0};
188
189 void blast(ssto,ssfrom)
190 substdio *ssto;
191 substdio *ssfrom;
192 {
193  int match;
194
195  for (;;)
196   {
197    if (getln(ssfrom,&dataline,&match,'\n') != 0) temp_read();
198    if (!match && !dataline.len) break;
199    if (!match) perm_partialline();
200    --dataline.len;
201    if (dataline.len && (dataline.s[0] == '.'))
202      if (substdio_put(ssto,".",1) == -1) writeerr();
203    if (substdio_put(ssto,dataline.s,dataline.len) == -1) writeerr();
204    if (substdio_put(ssto,"\r\n",2) == -1) writeerr();
205   }
206  if (substdio_put(ssto,".\r\n",3) == -1) writeerr();
207  if (substdio_flush(ssto) == -1) writeerr();
208 }
209
210 stralloc recip = {0};
211
212 void smtp(fd)
213 int fd;
214 {
215  substdio ssto;
216  substdio ssfrom;
217  unsigned long code;
218  int flaganyrecipok;
219  int i;
220
221  substdio_fdbuf(&ssto,timeoutwrite,TIMEOUTWRITE(timeout,fd),smtptobuf,sizeof(smtptobuf));
222  substdio_fdbuf(&ssfrom,timeoutread,TIMEOUTREAD(timeout,fd),smtpfrombuf,sizeof(smtpfrombuf));
223
224  if (smtpcode(&ssfrom) != 220)
225   {
226    out("ZConnected to "); outhost(); out(" but greeting failed.\n");
227    quit(&ssto,&ssfrom);
228   }
229
230  if (substdio_puts(&ssto,"HELO ") == -1) writeerr();
231  if (substdio_put(&ssto,helohost.s,helohost.len) == -1) writeerr();
232  if (substdio_puts(&ssto,"\r\n") == -1) writeerr();
233  if (substdio_flush(&ssto) == -1) writeerr();
234
235  if (smtpcode(&ssfrom) != 250)
236   {
237    out("ZConnected to "); outhost(); out(" but my name was rejected.\n");
238    quit(&ssto,&ssfrom);
239   }
240
241  if (substdio_puts(&ssto,"MAIL FROM:<") == -1) writeerr();
242  if (substdio_put(&ssto,sender.s,sender.len) == -1) writeerr();
243  if (substdio_puts(&ssto,">\r\n") == -1) writeerr();
244  if (substdio_flush(&ssto) == -1) writeerr();
245
246  code = smtpcode(&ssfrom);
247  if (code >= 500)
248   {
249    out("DConnected to "); outhost(); out(" but sender was rejected.\n");
250    quit(&ssto,&ssfrom);
251   }
252  if (code >= 400)
253   {
254    out("ZConnected to "); outhost(); out(" but sender was rejected.\n");
255    quit(&ssto,&ssfrom);
256   }
257
258  flaganyrecipok = 0;
259  for (i = 0;i < reciplist.len;++i)
260   {
261    if (substdio_puts(&ssto,"RCPT TO:<") == -1) writeerr();
262    if (substdio_put(&ssto,reciplist.sa[i].s,reciplist.sa[i].len) == -1) writeerr();
263    if (substdio_puts(&ssto,">\r\n") == -1) writeerr();
264    if (substdio_flush(&ssto) == -1) writeerr();
265
266    code = smtpcode(&ssfrom);
267    if (code == 421)
268     {
269      out("ZConnected to "); outhost(); out(" but connection died.\n");
270      quit(&ssto,&ssfrom);
271     }
272    if (code >= 500)
273     {
274      out("h"); outhost(); out(" does not like recipient.\n");
275      outsmtptext(); zero();
276     }
277    else if (code >= 400)
278     {
279      out("s"); outhost(); out(" does not like recipient.\n");
280      outsmtptext(); zero();
281     }
282    else
283     {
284      out("r"); zero();
285      flaganyrecipok = 1;
286     }
287   }
288
289  if (!flaganyrecipok)
290   {
291    out("DGiving up.\n");
292    quit(&ssto,&ssfrom);
293   }
294
295  if (substdio_putsflush(&ssto,"DATA\r\n") == -1) writeerr();
296
297  code = smtpcode(&ssfrom);
298  if (code == 421)
299   {
300    out("ZConnected to "); outhost(); out(" but connection died.\n");
301    quit(&ssto,&ssfrom);
302   }
303  if (code >= 500)
304   {
305    out("D"); outhost(); out(" failed on DATA command.\n");
306    quit(&ssto,&ssfrom);
307   }
308  if (code >= 400)
309   {
310    out("Z"); outhost(); out(" failed on DATA command.\n");
311    quit(&ssto,&ssfrom);
312   }
313
314  blast(&ssto,subfdin);
315
316  code = smtpcode(&ssfrom);
317  if (code == 421)
318   {
319    out("ZConnected to "); outhost(); out(" but connection died. Possible duplicate!\n");
320    quit(&ssto,&ssfrom);
321   }
322  if (code >= 500)
323   {
324    out("D"); outhost(); out(" failed after I sent the message.\n");
325    quit(&ssto,&ssfrom);
326   }
327  if (code >= 400)
328   {
329    out("Z"); outhost(); out(" failed after I sent the message.\n");
330    quit(&ssto,&ssfrom);
331   }
332
333  out("K"); outhost(); out(" accepted message.\n");
334  quit(&ssto,&ssfrom);
335 }
336
337 stralloc canonhost = {0};
338 stralloc canonbox = {0};
339
340 void addrmangle(saout,s,flagalias,flagcname)
341 stralloc *saout; /* host has to be canonical, box has to be quoted */
342 char *s;
343 int *flagalias;
344 int flagcname;
345 {
346  int j;
347
348  *flagalias = flagcname;
349
350  j = str_rchr(s,'@');
351  if (!s[j])
352   {
353    if (!stralloc_copys(saout,s)) temp_nomem();
354    return;
355   }
356  if (!stralloc_copys(&canonbox,s)) temp_nomem();
357  canonbox.len = j;
358  if (!quote(saout,&canonbox)) temp_nomem();
359  if (!stralloc_cats(saout,"@")) temp_nomem();
360
361  if (!stralloc_copys(&canonhost,s + j + 1)) temp_nomem();
362  if (flagcname)
363    switch(dns_cname(&canonhost))
364     {
365      case 0: *flagalias = 0; break;
366      case DNS_MEM: temp_nomem();
367      case DNS_SOFT: temp_dnscanon();
368      case DNS_HARD: ; /* alias loop, not our problem */
369     }
370
371  if (!stralloc_cat(saout,&canonhost)) temp_nomem();
372 }
373
374 void main(argc,argv)
375 int argc;
376 char **argv;
377 {
378  static ipalloc ip = {0};
379  int i;
380  unsigned long random;
381  char **recips;
382  unsigned long prefme;
383  int flagallaliases;
384  int flagalias;
385  char *relayhost;
386
387  sig_pipeignore();
388  if (argc < 4) perm_usage();
389  if (chdir(auto_qmail) == -1) temp_chdir();
390  getcontrols();
391
392
393  if (!stralloc_copys(&host,argv[1])) temp_nomem();
394
395  relayhost = 0;
396  for (i = 0;i <= host.len;++i)
397    if ((i == 0) || (i == host.len) || (host.s[i] == '.'))
398      if (relayhost = constmap(&maproutes,host.s + i,host.len - i))
399        break;
400  if (relayhost && !*relayhost) relayhost = 0;
401
402  if (relayhost)
403   {
404    i = str_chr(relayhost,':');
405    if (relayhost[i])
406     {
407      scan_ulong(relayhost + i + 1,&port);
408      relayhost[i] = 0;
409     }
410    if (!stralloc_copys(&host,relayhost)) temp_nomem();
411   }
412
413
414  addrmangle(&sender,argv[2],&flagalias,!relayhost);
415
416  if (!saa_readyplus(&reciplist,0)) temp_nomem();
417  if (ipme_init() != 1) temp_oserr();
418
419  flagallaliases = 1;
420  recips = argv + 3;
421  while (*recips)
422   {
423    if (!saa_readyplus(&reciplist,1)) temp_nomem();
424    reciplist.sa[reciplist.len] = sauninit;
425    addrmangle(reciplist.sa + reciplist.len,*recips,&flagalias,!relayhost);
426    if (!flagalias) flagallaliases = 0;
427    ++reciplist.len;
428    ++recips;
429   }
430
431
432  random = now() + (getpid() << 16);
433  switch (relayhost ? dns_ip(&ip,&host) : dns_mxip(&ip,&host,random))
434   {
435    case DNS_MEM: temp_nomem();
436    case DNS_SOFT: temp_dns();
437    case DNS_HARD: perm_dns();
438    case 1:
439      if (ip.len <= 0) temp_dns();
440   }
441
442  if (ip.len <= 0) perm_nomx();
443
444  prefme = 100000;
445  for (i = 0;i < ip.len;++i)
446    if (ipme_is(&ip.ix[i].ip))
447      if (ip.ix[i].pref < prefme)
448        prefme = ip.ix[i].pref;
449
450  if (relayhost) prefme = 300000;
451  if (flagallaliases) prefme = 500000;
452
453  for (i = 0;i < ip.len;++i)
454    if (ip.ix[i].pref < prefme)
455      break;
456
457  if (i >= ip.len)
458    perm_ambigmx();
459
460  for (i = 0;i < ip.len;++i) if (ip.ix[i].pref < prefme)
461   {
462    int s;
463
464    if (tcpto(&ip.ix[i].ip)) continue;
465
466    s = socket(AF_INET,SOCK_STREAM,0);
467    if (s == -1) temp_oserr();
468
469    if (timeoutconn(s,&ip.ix[i].ip,(unsigned int) port,timeoutconnect) == 0)
470     {
471      tcpto_err(&ip.ix[i].ip,0);
472      partner = ip.ix[i].ip;
473      smtp(s); /* does not return */
474     }
475    tcpto_err(&ip.ix[i].ip,errno == error_timeout);
476    close(s);
477   }
478  
479  temp_noconn();
480 }