]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/tools/bsmtp.c
Add support for new POSIX getaddrinfo interface.
[bacula/bacula] / bacula / src / tools / bsmtp.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2001-2009 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version three of the GNU Affero General Public
10    License as published by the Free Software Foundation and included
11    in the file LICENSE.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU Affero General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22
23    Bacula® is a registered trademark of Kern Sibbald.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28 /*
29    Derived from a SMTPclient:
30
31   ======== Original copyrights ==========  
32
33        SMTPclient -- simple SMTP client
34
35        Copyright (c) 1997 Ralf S. Engelschall, All rights reserved.
36
37        This program is free software; it may be redistributed and/or modified
38        only under the terms of either the Artistic License or the GNU General
39        Public License, which may be found in the SMTP source distribution.
40        Look at the file COPYING.
41
42        This program is distributed in the hope that it will be useful, but
43        WITHOUT ANY WARRANTY; without even the implied warranty of
44        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
45        GNU General Public License for more details.
46
47        ======================================================================
48
49        smtpclient_main.c -- program source
50
51        Based on smtp.c as of August 11, 1995 from
52            W.Z. Venema,
53            Eindhoven University of Technology,
54            Department of Mathematics and Computer Science,
55            Den Dolech 2, P.O. Box 513, 5600 MB Eindhoven, The Netherlands.
56
57    =========
58
59
60    Kern Sibbald, July 2001
61
62      Note, the original W.Z. Venema smtp.c had no license and no
63      copyright.  See:
64         http://archives.neohapsis.com/archives/postfix/2000-05/1520.html
65  
66  */
67
68 #include "bacula.h"
69 #include "jcr.h"
70 #define MY_NAME "bsmtp"
71
72 #if defined(HAVE_WIN32)
73 #include <lmcons.h>
74 #endif
75
76 /*
77  * Dummy functions
78  */
79 int generate_daemon_event(JCR *jcr, const char *event) 
80 {
81    return 1;
82 }
83
84 #ifndef MAXSTRING
85 #define MAXSTRING 254
86 #endif
87
88 static FILE *sfp;
89 static FILE *rfp;
90
91 static char *from_addr = NULL;
92 static char *cc_addr = NULL;
93 static char *subject = NULL;
94 static char *err_addr = NULL;
95 static const char *mailhost = NULL;
96 static char *reply_addr = NULL;
97 static int mailport = 25;
98 static char my_hostname[MAXSTRING];
99 static bool content_utf8 = false;
100
101 /* 
102  * Take input that may have names and other stuff and strip
103  *  it down to the mail box address ... i.e. what is enclosed
104  *  in < >.  Otherwise add < >.
105  */
106 static char *cleanup_addr(char *addr, char *buf, int buf_len)
107 {
108    char *p, *q;
109
110    if ((p = strchr(addr, '<')) == NULL) {
111       snprintf(buf, buf_len, "<%s>", addr);
112    } else {
113       /* Copy <addr> */
114       for (q=buf; *p && *p!='>'; ) {
115          *q++ = *p++;
116       }
117       if (*p) {
118          *q++ = *p;
119       }
120       *q = 0;
121   }
122   Dmsg2(100, "cleanup in=%s out=%s\n", addr, buf);
123   return buf;    
124 }
125
126 /*
127  *  examine message from server
128  */
129 static void get_response(void)
130 {
131     char buf[1000];
132
133     Dmsg0(50, "Calling fgets on read socket rfp.\n");
134     buf[3] = 0;
135     while (fgets(buf, sizeof(buf), rfp)) {
136         int len = strlen(buf);
137         if (len > 0) {
138            buf[len-1] = 0;
139         }
140         if (debug_level >= 10) {
141             fprintf(stderr, "%s <-- %s\n", mailhost, buf);
142         }
143         Dmsg2(10, "%s --> %s\n", mailhost, buf);
144         if (!isdigit((int)buf[0]) || buf[0] > '3') {
145             Pmsg2(0, _("Fatal malformed reply from %s: %s\n"), mailhost, buf);
146             exit(1);
147         }
148         if (buf[3] != '-') {
149             break;
150         }
151     }
152     if (ferror(rfp)) {
153         fprintf(stderr, _("Fatal fgets error: ERR=%s\n"), strerror(errno));
154     }
155     return;
156 }
157
158 /*
159  *  say something to server and check the response
160  */
161 static void chat(const char *fmt, ...)
162 {
163     va_list ap;
164
165     va_start(ap, fmt);
166     vfprintf(sfp, fmt, ap);
167     va_end(ap);
168     if (debug_level >= 10) {
169        fprintf(stdout, "%s --> ", my_hostname);
170        va_start(ap, fmt);
171        vfprintf(stdout, fmt, ap);
172        va_end(ap);
173     }
174
175     fflush(sfp);
176     if (debug_level >= 10) {
177        fflush(stdout);
178     }
179     get_response();
180 }
181
182
183 static void usage()
184 {
185    fprintf(stderr,
186 _("\n"
187 "Usage: %s [-f from] [-h mailhost] [-s subject] [-c copy] [recipient ...]\n"
188 "       -8          set charset to UTF-8\n"
189 "       -c          set the Cc: field\n"
190 "       -d <nn>     set debug level to <nn>\n"
191 "       -dt         print a timestamp in debug output\n"
192 "       -f          set the From: field\n"
193 "       -h          use mailhost:port as the SMTP server\n"
194 "       -s          set the Subject: field\n"
195 "       -r          set the Reply-To: field\n"
196 "       -l          set the maximum number of lines to send (default: unlimited)\n"
197 "       -?          print this message.\n"
198 "\n"), MY_NAME);
199
200    exit(1);
201 }
202
203 /*
204  * Return the offset west from localtime to UTC in minutes
205  * Same as timezone.tz_minuteswest
206  *   Unix tz_offset coded by:  Attila Fülöp
207  */
208 static long tz_offset(time_t lnow, struct tm &tm)
209 {
210 #if defined(HAVE_WIN32)
211 #if defined(HAVE_MINGW)
212 __MINGW_IMPORT long     _dstbias;
213 #endif
214 #if defined(MINGW64)
215 # define _tzset tzset
216 #endif
217    /* Win32 code */
218    long offset;
219    _tzset();
220    offset = _timezone;
221    if (tm.tm_isdst) {
222       offset += _dstbias;
223    }
224    return offset /= 60;
225 #else
226
227    /* Unix/Linux code */
228    struct tm tm_utc;
229    time_t now = lnow;
230
231    (void)gmtime_r(&now, &tm_utc);
232    tm_utc.tm_isdst = tm.tm_isdst;
233    return (long)difftime(mktime(&tm_utc), now) / 60;
234 #endif
235 }
236
237 static void get_date_string(char *buf, int buf_len)
238 {
239    time_t now = time(NULL);
240    struct tm tm;
241    char tzbuf[MAXSTRING];
242    long my_timezone;
243
244    /* Add RFC822 date */
245    (void)localtime_r(&now, &tm);
246
247    my_timezone = tz_offset(now, tm);
248    strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S", &tm);
249    snprintf(tzbuf, sizeof(tzbuf), " %+2.2ld%2.2u", -my_timezone / 60, abs(my_timezone) % 60);
250    strcat(buf, tzbuf);              /* add +0100 */
251    strftime(tzbuf, sizeof(tzbuf), " (%Z)", &tm);
252    strcat(buf, tzbuf);              /* add (CEST) */
253 }
254
255 /*********************************************************************
256  *
257  *  Program to send email
258  */
259 int main (int argc, char *argv[])
260 {
261    char buf[1000];
262    int i, ch;
263    unsigned long maxlines, lines;
264 #if defined(HAVE_WIN32)
265    SOCKET s;
266 #else
267    int s, r;
268    struct passwd *pwd;
269 #endif
270    char *cp, *p;
271 #ifdef HAVE_GETADDRINFO
272    int res;
273    struct addrinfo hints;
274    struct addrinfo *ai, *rp;
275    char mail_port[10];
276 #else
277    struct hostent *hp;
278    struct sockaddr_in sin;
279 #endif
280     
281    setlocale(LC_ALL, "en_US");
282    bindtextdomain("bacula", LOCALEDIR);
283    textdomain("bacula");
284
285    my_name_is(argc, argv, "bsmtp");
286    maxlines = 0;
287
288    while ((ch = getopt(argc, argv, "8c:d:f:h:r:s:l:?")) != -1) {
289       switch (ch) {
290       case '8':
291          content_utf8 = true;
292          break;
293       case 'c':
294          Dmsg1(20, "cc=%s\n", optarg);
295          cc_addr = optarg;
296          break;
297
298       case 'd':                    /* set debug level */
299          if (*optarg == 't') {
300             dbg_timestamp = true;
301          } else {
302             debug_level = atoi(optarg);
303             if (debug_level <= 0) {
304                debug_level = 1;
305             }
306          }
307          Dmsg1(20, "Debug level = %d\n", debug_level);
308          break;
309
310       case 'f':                    /* from */
311          from_addr = optarg;
312          break;
313
314       case 'h':                    /* smtp host */
315          Dmsg1(20, "host=%s\n", optarg);
316          p = strchr(optarg, ':');
317          if (p) {
318             *p++ = 0;
319             mailport = atoi(p);
320          }
321          mailhost = optarg;
322          break;
323
324       case 's':                    /* subject */
325          Dmsg1(20, "subject=%s\n", optarg);
326          subject = optarg;
327          break;
328
329       case 'r':                    /* reply address */
330          reply_addr = optarg;
331          break;
332
333       case 'l':
334          Dmsg1(20, "maxlines=%s\n", optarg);
335          maxlines = (unsigned long) atol(optarg);
336          break;
337
338       case '?':
339       default:
340          usage();
341
342       }
343    }
344    argc -= optind;
345    argv += optind;
346
347    if (argc < 1) {
348       Pmsg0(0, _("Fatal error: no recipient given.\n"));
349       usage();
350       exit(1);
351    }
352
353    /*
354     *  Determine SMTP server
355     */
356    if (mailhost == NULL) {
357       if ((cp = getenv("SMTPSERVER")) != NULL) {
358          mailhost = cp;
359       } else {
360          mailhost = "localhost";
361       }
362    }
363
364 #if defined(HAVE_WIN32)
365    WSADATA  wsaData;
366
367    _setmode(0, _O_BINARY);
368    WSAStartup(MAKEWORD(2,2), &wsaData);
369 #endif
370
371    /*
372     *  Find out my own host name for HELO;
373     *  if possible, get the fully qualified domain name
374     */
375    if (gethostname(my_hostname, sizeof(my_hostname) - 1) < 0) {
376       Pmsg1(0, _("Fatal gethostname error: ERR=%s\n"), strerror(errno));
377       exit(1);
378    }
379 #ifdef HAVE_GETADDRINFO
380    memset(&hints, 0, sizeof(struct addrinfo));
381    hints.ai_family = AF_UNSPEC;
382    hints.ai_socktype = 0;
383    hints.ai_protocol = 0;
384    hints.ai_flags = AI_CANONNAME;
385
386    if ((res = getaddrinfo(my_hostname, NULL, &hints, &ai)) != 0) {
387       Pmsg2(0, _("Fatal getaddrinfo for myself failed \"%s\": ERR=%s\n"),
388             my_hostname, gai_strerror(res));
389       exit(1);
390    }
391    strcpy(my_hostname, ai->ai_canonname);
392    freeaddrinfo(ai);
393 #else
394    if ((hp = gethostbyname(my_hostname)) == NULL) {
395       Pmsg2(0, _("Fatal gethostbyname for myself failed \"%s\": ERR=%s\n"),
396             my_hostname, strerror(errno));
397       exit(1);
398    }
399    strcpy(my_hostname, hp->h_name);
400 #endif
401    Dmsg1(20, "My hostname is: %s\n", my_hostname);
402
403    /*
404     *  Determine from address.
405     */
406    if (from_addr == NULL) {
407 #if defined(HAVE_WIN32)
408       DWORD dwSize = UNLEN + 1;
409       LPSTR lpszBuffer = (LPSTR)alloca(dwSize);
410
411       if (GetUserName(lpszBuffer, &dwSize)) {
412          snprintf(buf, sizeof(buf), "%s@%s", lpszBuffer, my_hostname);
413       } else {
414          snprintf(buf, sizeof(buf), "unknown-user@%s", my_hostname);
415       }
416 #else
417       if ((pwd = getpwuid(getuid())) == 0) {
418          snprintf(buf, sizeof(buf), "userid-%d@%s", (int)getuid(), my_hostname);
419       } else {
420          snprintf(buf, sizeof(buf), "%s@%s", pwd->pw_name, my_hostname);
421       }
422 #endif
423       from_addr = bstrdup(buf);
424    }
425    Dmsg1(20, "From addr=%s\n", from_addr);
426
427    /*
428     *  Connect to smtp daemon on mailhost.
429     */
430 lookup_host:
431 #ifdef HAVE_GETADDRINFO
432    memset(&hints, 0, sizeof(struct addrinfo));
433    hints.ai_family = AF_UNSPEC;
434    hints.ai_socktype = SOCK_STREAM;
435    hints.ai_protocol = IPPROTO_TCP;
436    hints.ai_flags = 0;
437    snprintf(mail_port, sizeof(mail_port), "%d", mailport);
438
439    if ((res = getaddrinfo(mailhost, mail_port, &hints, &ai)) != 0) {
440       Pmsg2(0, _("Error unknown mail host \"%s\": ERR=%s\n"),
441             mailhost, gai_strerror(res));
442       if (!strcasecmp(mailhost, "localhost")) {
443          Pmsg0(0, _("Retrying connection using \"localhost\".\n"));
444          mailhost = "localhost";
445          goto lookup_host;
446       }
447       exit(1);
448    }
449
450    for (rp = ai; rp != NULL; rp = rp->ai_next) {
451 #if defined(HAVE_WIN32)
452       s = WSASocket(rp->ai_family, rp->ai_socktype, rp->ai_protocol, NULL, 0, 0);
453 #else
454       s = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
455 #endif
456       if (s < 0) {
457          continue;
458       }
459
460       if (connect(s, rp->ai_addr, rp->ai_addrlen) != -1) {
461          break;
462       }
463
464       close(s);
465    }
466
467    if (!rp) {
468       Pmsg1(0, _("Failed to connect to mailhost %s\n"), mailhost);
469       exit(1);
470    }
471
472    freeaddrinfo(ai);
473 #else
474    if ((hp = gethostbyname(mailhost)) == NULL) {
475       Pmsg2(0, _("Error unknown mail host \"%s\": ERR=%s\n"), mailhost,
476          strerror(errno));
477       if (strcasecmp(mailhost, "localhost") != 0) {
478          Pmsg0(0, _("Retrying connection using \"localhost\".\n"));
479          mailhost = "localhost";
480          goto lookup_host;
481       }
482       exit(1);
483    }
484
485    if (hp->h_addrtype != AF_INET) {
486       Pmsg1(0, _("Fatal error: Unknown address family for smtp host: %d\n"), hp->h_addrtype);
487       exit(1);
488    }
489    memset((char *)&sin, 0, sizeof(sin));
490    memcpy((char *)&sin.sin_addr, hp->h_addr, hp->h_length);
491    sin.sin_family = hp->h_addrtype;
492    sin.sin_port = htons(mailport);
493 #if defined(HAVE_WIN32)
494    if ((s = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, 0)) < 0) {
495       Pmsg1(0, _("Fatal socket error: ERR=%s\n"), strerror(errno));
496       exit(1);
497    }
498 #else
499    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
500       Pmsg1(0, _("Fatal socket error: ERR=%s\n"), strerror(errno));
501       exit(1);
502    }
503 #endif
504    if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
505       Pmsg2(0, _("Fatal connect error to %s: ERR=%s\n"), mailhost, strerror(errno));
506       exit(1);
507    }
508    Dmsg0(20, "Connected\n");
509 #endif
510
511 #if defined(HAVE_WIN32)
512    int fdSocket = _open_osfhandle(s, _O_RDWR | _O_BINARY);
513    if (fdSocket == -1) {
514       Pmsg1(0, _("Fatal _open_osfhandle error: ERR=%s\n"), strerror(errno));
515       exit(1);
516    }
517
518    int fdSocket2 = dup(fdSocket);
519
520    if ((sfp = fdopen(fdSocket, "wb")) == NULL) {
521       Pmsg1(0, _("Fatal fdopen error: ERR=%s\n"), strerror(errno));
522       exit(1);
523    }
524    if ((rfp = fdopen(fdSocket2, "rb")) == NULL) {
525       Pmsg1(0, _("Fatal fdopen error: ERR=%s\n"), strerror(errno));
526       exit(1);
527    }
528 #else
529    if ((r = dup(s)) < 0) {
530       Pmsg1(0, _("Fatal dup error: ERR=%s\n"), strerror(errno));
531       exit(1);
532    }
533    if ((sfp = fdopen(s, "w")) == 0) {
534       Pmsg1(0, _("Fatal fdopen error: ERR=%s\n"), strerror(errno));
535       exit(1);
536    }
537    if ((rfp = fdopen(r, "r")) == 0) {
538       Pmsg1(0, _("Fatal fdopen error: ERR=%s\n"), strerror(errno));
539       exit(1);
540    }
541 #endif
542
543    /*
544     *  Send SMTP headers.  Note, if any of the strings have a <
545     *   in them already, we do not enclose the string in < >, otherwise
546     *   we do.
547     */
548    get_response(); /* banner */
549    chat("HELO %s\r\n", my_hostname);
550    chat("MAIL FROM:%s\r\n", cleanup_addr(from_addr, buf, sizeof(buf)));
551    
552    for (i = 0; i < argc; i++) {
553       Dmsg1(20, "rcpt to: %s\n", argv[i]);
554       chat("RCPT TO:%s\r\n", cleanup_addr(argv[i], buf, sizeof(buf)));
555    }
556
557    if (cc_addr) {
558       chat("RCPT TO:%s\r\n", cleanup_addr(cc_addr, buf, sizeof(buf)));
559    }
560    Dmsg0(20, "Data\n");
561    chat("DATA\r\n");
562
563    /*
564     *  Send message header
565     */
566    fprintf(sfp, "From: %s\r\n", from_addr);
567    Dmsg1(10, "From: %s\r\n", from_addr);
568    if (subject) {
569       fprintf(sfp, "Subject: %s\r\n", subject);
570       Dmsg1(10, "Subject: %s\r\n", subject);
571    }
572    if (reply_addr) {
573       fprintf(sfp, "Reply-To: %s\r\n", reply_addr);
574       Dmsg1(10, "Reply-To: %s\r\n", reply_addr);
575    }
576    if (err_addr) {
577       fprintf(sfp, "Errors-To: %s\r\n", err_addr);
578       Dmsg1(10, "Errors-To: %s\r\n", err_addr);
579    }
580
581 #if defined(HAVE_WIN32)
582    DWORD dwSize = UNLEN + 1;
583    LPSTR lpszBuffer = (LPSTR)alloca(dwSize);
584
585    if (GetUserName(lpszBuffer, &dwSize)) {
586       fprintf(sfp, "Sender: %s@%s\r\n", lpszBuffer, my_hostname);
587       Dmsg2(10, "Sender: %s@%s\r\n", lpszBuffer, my_hostname);
588    } else {
589       fprintf(sfp, "Sender: unknown-user@%s\r\n", my_hostname);
590       Dmsg1(10, "Sender: unknown-user@%s\r\n", my_hostname);
591    }
592 #else
593    if ((pwd = getpwuid(getuid())) == 0) {
594       fprintf(sfp, "Sender: userid-%d@%s\r\n", (int)getuid(), my_hostname);
595       Dmsg2(10, "Sender: userid-%d@%s\r\n", (int)getuid(), my_hostname);
596    } else {
597       fprintf(sfp, "Sender: %s@%s\r\n", pwd->pw_name, my_hostname);
598       Dmsg2(10, "Sender: %s@%s\r\n", pwd->pw_name, my_hostname);
599    }
600 #endif
601
602    fprintf(sfp, "To: %s", argv[0]);
603    Dmsg1(10, "To: %s", argv[0]);
604    for (i = 1; i < argc; i++) {
605       fprintf(sfp, ",%s", argv[i]);
606       Dmsg1(10, ",%s", argv[i]);
607    }
608
609    fprintf(sfp, "\r\n");
610    Dmsg0(10, "\r\n");
611    if (cc_addr) {
612       fprintf(sfp, "Cc: %s\r\n", cc_addr);
613       Dmsg1(10, "Cc: %s\r\n", cc_addr);
614    }
615
616    if (content_utf8) {
617       fprintf(sfp, "Content-Type: text/plain; charset=UTF-8\r\n");
618       Dmsg0(10, "Content-Type: text/plain; charset=UTF-8\r\n");
619    }
620
621    get_date_string(buf, sizeof(buf));
622    fprintf(sfp, "Date: %s\r\n", buf);
623    Dmsg1(10, "Date: %s\r\n", buf);
624
625    fprintf(sfp, "\r\n");
626
627    /*
628     *  Send message body
629     */
630    lines = 0;
631    while (fgets(buf, sizeof(buf), stdin)) {
632       if (maxlines > 0 && ++lines > maxlines) {
633          Dmsg1(20, "skip line because of maxlines limit: %lu\n", maxlines);
634          while (fgets(buf, sizeof(buf), stdin)) {
635             ++lines;
636          }
637          break;
638       }
639       buf[sizeof(buf)-1] = '\0';
640       buf[strlen(buf)-1] = '\0';
641       if (buf[0] == '.') {         /* add extra . see RFC 2821 4.5.2 */
642          fputs(".", sfp);
643       }
644       fputs(buf, sfp);
645       fputs("\r\n", sfp);
646    }
647
648    if (lines > maxlines) {
649       Dmsg1(10, "hit maxlines limit: %lu\n", maxlines);
650       fprintf(sfp, "\r\n\r\n[maximum of %lu lines exceeded, skipped %lu lines of output]\r\n", maxlines, lines-maxlines);
651    }
652
653    /*
654     *  Send SMTP quit command
655     */
656    chat(".\r\n");
657    chat("QUIT\r\n");
658
659    /*
660     *  Go away gracefully ...
661     */
662    exit(0);
663 }