]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/tools/bsmtp.c
Try to make bsmtp date routines more generic.
[bacula/bacula] / bacula / src / tools / bsmtp.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2001-2007 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 two of the GNU General Public
10    License as published by the Free Software Foundation plus additions
11    that are listed 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 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 John Walker.
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        SMTPclient -- simple SMTP client
32
33        Copyright (C) 1997 Ralf S. Engelschall, All Rights Reserved.
34        rse@engelschall.com
35        www.engelschall.com
36
37    Kern Sibbald, July 2001
38
39    Version $Id$
40
41  */
42
43
44 #include "bacula.h"
45 #include "jcr.h"
46 #define MY_NAME "bsmtp"
47
48 #if defined(HAVE_WIN32)
49 #include <lmcons.h>
50 #endif
51
52 /* Dummy functions */
53 int generate_daemon_event(JCR *jcr, const char *event) 
54    { return 1; }
55
56 #ifndef MAXSTRING
57 #define MAXSTRING 254
58 #endif
59
60 static FILE *sfp;
61 static FILE *rfp;
62
63 static char *from_addr = NULL;
64 static char *cc_addr = NULL;
65 static char *subject = NULL;
66 static char *err_addr = NULL;
67 static const char *mailhost = NULL;
68 static char *reply_addr = NULL;
69 static int mailport = 25;
70 static char my_hostname[MAXSTRING];
71 static bool content_utf8 = false;
72
73
74 /*
75  *  examine message from server
76  */
77 static void get_response(void)
78 {
79     char buf[MAXSTRING];
80
81     Dmsg0(50, "Calling fgets on read socket rfp.\n");
82     buf[3] = 0;
83     while (fgets(buf, sizeof(buf), rfp)) {
84         int len = strlen(buf);
85         if (len > 0) {
86            buf[len-1] = 0;
87         }
88         if (debug_level >= 10) {
89             fprintf(stderr, "%s <-- %s\n", mailhost, buf);
90         }
91         Dmsg2(10, "%s --> %s\n", mailhost, buf);
92         if (!isdigit((int)buf[0]) || buf[0] > '3') {
93             Pmsg2(0, _("Fatal malformed reply from %s: %s\n"), mailhost, buf);
94             exit(1);
95         }
96         if (buf[3] != '-') {
97             break;
98         }
99     }
100     if (ferror(rfp)) {
101         fprintf(stderr, _("Fatal fgets error: ERR=%s\n"), strerror(errno));
102     }
103     return;
104 }
105
106 /*
107  *  say something to server and check the response
108  */
109 static void chat(const char *fmt, ...)
110 {
111     va_list ap;
112
113     va_start(ap, fmt);
114     vfprintf(sfp, fmt, ap);
115     if (debug_level >= 10) {
116        fprintf(stdout, "%s --> ", my_hostname);
117        vfprintf(stdout, fmt, ap);
118     }
119     va_end(ap);
120
121     fflush(sfp);
122     if (debug_level >= 10) {
123        fflush(stdout);
124     }
125     get_response();
126 }
127
128
129 static void usage()
130 {
131    fprintf(stderr,
132 _("\n"
133 "Usage: %s [-f from] [-h mailhost] [-s subject] [-c copy] [recipient ...]\n"
134 "       -8          set charset utf-8\n"
135 "       -c          set the Cc: field\n"
136 "       -dnn        set debug level to nn\n"
137 "       -f          set the From: field\n"
138 "       -h          use mailhost:port as the SMTP server\n"
139 "       -s          set the Subject: field\n"
140 "       -r          set the Reply-To: field\n"
141 "       -l          set the maximum number of lines that should be sent (default: unlimited)\n"
142 "       -?          print this message.\n"
143 "\n"), MY_NAME);
144
145    exit(1);
146 }
147
148 static void get_date_string(char *buf, int buf_len)
149 {
150    time_t now = time(NULL);
151    struct tm tm;
152    char tzbuf[MAXSTRING];
153    long my_timezone;
154    struct timeval tv;
155    struct timezone tz;
156
157    /* Add RFC822 date */
158    (void)localtime_r(&now, &tm);
159
160 #if defined(HAVE_WIN32)
161 #if defined(HAVE_MINGW)
162 __MINGW_IMPORT long     _dstbias;
163 #endif
164    _tzset();
165    my_timezone = _timezone;
166    my_timezone += _dstbias;
167    my_timezone /= 60;
168
169 #else
170    gettimeofday(&tv, &tz);
171    my_timezone = tz.tz_minuteswest; /* timezone offset in mins */
172 #endif
173    strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S", &tm);
174    sprintf(tzbuf, " %+2.2ld%2.2u", -my_timezone / 60, abs(my_timezone) % 60);
175    strcat(buf, tzbuf);              /* add +0100 */
176    strftime(tzbuf, sizeof(tzbuf), " (%Z)", &tm);
177    strcat(buf, tzbuf);              /* add (CEST) */
178 }
179
180
181 /*********************************************************************
182  *
183  *  Program to send email
184  */
185 int main (int argc, char *argv[])
186 {
187     char buf[MAXSTRING];
188     struct sockaddr_in sin;
189     struct hostent *hp;
190     int i, ch;
191     unsigned long maxlines, lines;
192 #if defined(HAVE_WIN32)
193     SOCKET s;
194 #else
195     int s, r;
196     struct passwd *pwd;
197 #endif
198     char *cp, *p;
199     
200    setlocale(LC_ALL, "en_US");
201    bindtextdomain("bacula", LOCALEDIR);
202    textdomain("bacula");
203
204    my_name_is(argc, argv, "bsmtp");
205    maxlines = 0;
206
207    while ((ch = getopt(argc, argv, "8c:d:f:h:r:s:l:?")) != -1) {
208       switch (ch) {
209       case '8':
210          content_utf8 = true;
211          break;
212       case 'c':
213          Dmsg1(20, "cc=%s\n", optarg);
214          cc_addr = optarg;
215          break;
216
217       case 'd':                    /* set debug level */
218          debug_level = atoi(optarg);
219          if (debug_level <= 0) {
220             debug_level = 1;
221          }
222          Dmsg1(20, "Debug level = %d\n", debug_level);
223          break;
224
225       case 'f':                    /* from */
226          from_addr = optarg;
227          break;
228
229       case 'h':                    /* smtp host */
230          Dmsg1(20, "host=%s\n", optarg);
231          p = strchr(optarg, ':');
232          if (p) {
233             *p++ = 0;
234             mailport = atoi(p);
235          }
236          mailhost = optarg;
237          break;
238
239       case 's':                    /* subject */
240          Dmsg1(20, "subject=%s\n", optarg);
241          subject = optarg;
242          break;
243
244       case 'r':                    /* reply address */
245          reply_addr = optarg;
246          break;
247
248       case 'l':
249          Dmsg1(20, "maxlines=%s\n", optarg);
250          maxlines = (unsigned long) atol(optarg);
251          break;
252
253       case '?':
254       default:
255          usage();
256
257       }
258    }
259    argc -= optind;
260    argv += optind;
261
262    if (argc < 1) {
263       Pmsg0(0, _("Fatal error: no recipient given.\n"));
264       usage();
265       exit(1);
266    }
267
268 #if defined(HAVE_WIN32)
269    _setmode(0, _O_BINARY);
270 #endif
271
272    /*
273     *  Determine SMTP server
274     */
275    if (mailhost == NULL) {
276       if ((cp = getenv("SMTPSERVER")) != NULL) {
277          mailhost = cp;
278       } else {
279          mailhost = "localhost";
280       }
281    }
282
283 #if defined(HAVE_WIN32)
284    WSADATA  wsaData;
285
286    WSAStartup(MAKEWORD(2,2), &wsaData);
287 #endif
288
289    /*
290     *  Find out my own host name for HELO;
291     *  if possible, get the fully qualified domain name
292     */
293    if (gethostname(my_hostname, sizeof(my_hostname) - 1) < 0) {
294       Pmsg1(0, _("Fatal gethostname error: ERR=%s\n"), strerror(errno));
295       exit(1);
296    }
297    if ((hp = gethostbyname(my_hostname)) == NULL) {
298       Pmsg2(0, _("Fatal gethostbyname for myself failed \"%s\": ERR=%s\n"), my_hostname,
299          strerror(errno));
300       exit(1);
301    }
302    strcpy(my_hostname, hp->h_name);
303    Dmsg1(20, "My hostname is: %s\n", my_hostname);
304
305    /*
306     *  Determine from address.
307     */
308    if (from_addr == NULL) {
309 #if defined(HAVE_WIN32)
310       DWORD dwSize = UNLEN + 1;
311       LPSTR lpszBuffer = (LPSTR)alloca(dwSize);
312
313       if (GetUserName(lpszBuffer, &dwSize)) {
314          sprintf(buf, "%s@%s", lpszBuffer, my_hostname);
315       } else {
316          sprintf(buf, "unknown-user@%s", my_hostname);
317       }
318 #else
319       if ((pwd = getpwuid(getuid())) == 0) {
320          sprintf(buf, "userid-%d@%s", (int)getuid(), my_hostname);
321       } else {
322          sprintf(buf, "%s@%s", pwd->pw_name, my_hostname);
323       }
324 #endif
325       from_addr = bstrdup(buf);
326    }
327    Dmsg1(20, "From addr=%s\n", from_addr);
328
329    /*
330     *  Connect to smtp daemon on mailhost.
331     */
332 hp:
333    if ((hp = gethostbyname(mailhost)) == NULL) {
334       Pmsg2(0, _("Error unknown mail host \"%s\": ERR=%s\n"), mailhost,
335          strerror(errno));
336       if (strcasecmp(mailhost, "localhost") != 0) {
337          Pmsg0(0, _("Retrying connection using \"localhost\".\n"));
338          mailhost = "localhost";
339          goto hp;
340       }
341       exit(1);
342    }
343
344    if (hp->h_addrtype != AF_INET) {
345       Pmsg1(0, _("Fatal error: Unknown address family for smtp host: %d\n"), hp->h_addrtype);
346       exit(1);
347    }
348    memset((char *)&sin, 0, sizeof(sin));
349    memcpy((char *)&sin.sin_addr, hp->h_addr, hp->h_length);
350    sin.sin_family = hp->h_addrtype;
351    sin.sin_port = htons(mailport);
352 #if defined(HAVE_WIN32)
353    if ((s = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, 0)) < 0) {
354       Pmsg1(0, _("Fatal socket error: ERR=%s\n"), strerror(errno));
355       exit(1);
356    }
357 #else
358    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
359       Pmsg1(0, _("Fatal socket error: ERR=%s\n"), strerror(errno));
360       exit(1);
361    }
362 #endif
363    if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
364       Pmsg2(0, _("Fatal connect error to %s: ERR=%s\n"), mailhost, strerror(errno));
365       exit(1);
366    }
367    Dmsg0(20, "Connected\n");
368
369 #if defined(HAVE_WIN32)
370    int fdSocket = _open_osfhandle(s, _O_RDWR | _O_BINARY);
371    if (fdSocket == -1) {
372       Pmsg1(0, _("Fatal _open_osfhandle error: ERR=%s\n"), strerror(errno));
373       exit(1);
374    }
375
376    int fdSocket2 = dup(fdSocket);
377
378    if ((sfp = fdopen(fdSocket, "wb")) == NULL) {
379       Pmsg1(0, _("Fatal fdopen error: ERR=%s\n"), strerror(errno));
380       exit(1);
381    }
382    if ((rfp = fdopen(fdSocket2, "rb")) == NULL) {
383       Pmsg1(0, _("Fatal fdopen error: ERR=%s\n"), strerror(errno));
384       exit(1);
385    }
386 #else
387    if ((r = dup(s)) < 0) {
388       Pmsg1(0, _("Fatal dup error: ERR=%s\n"), strerror(errno));
389       exit(1);
390    }
391    if ((sfp = fdopen(s, "w")) == 0) {
392       Pmsg1(0, _("Fatal fdopen error: ERR=%s\n"), strerror(errno));
393       exit(1);
394    }
395    if ((rfp = fdopen(r, "r")) == 0) {
396       Pmsg1(0, _("Fatal fdopen error: ERR=%s\n"), strerror(errno));
397       exit(1);
398    }
399 #endif
400
401    /*
402     *  Send SMTP headers.  Note, if any of the strings have a <
403     *   in them already, we do not enclose the string in < >, otherwise
404     *   we do.
405     */
406    get_response(); /* banner */
407    chat("helo %s\r\n", my_hostname);
408    if (strchr(from_addr, '<') == NULL) {
409       chat("mail from:<%s>\r\n", from_addr);
410    } else {
411       chat("mail from:%s\r\n", from_addr);
412    }
413
414    for (i = 0; i < argc; i++) {
415       Dmsg1(20, "rcpt to: %s\n", argv[i]);
416       if (strchr(argv[i], '<') == NULL) {
417          chat("rcpt to:<%s>\r\n", argv[i]);
418       } else {
419          chat("rcpt to:%s\r\n", argv[i]);
420       }
421    }
422
423    if (cc_addr) {
424       if (strchr(cc_addr, '<') == NULL) {
425          chat("rcpt to:<%s>\r\n", cc_addr);
426       } else {
427          chat("rcpt to:%s\r\n", cc_addr);
428       }
429    }
430    Dmsg0(20, "Data\n");
431    chat("data\r\n");
432
433    /*
434     *  Send message header
435     */
436    fprintf(sfp, "From: %s\r\n", from_addr);
437    Dmsg1(10, "From: %s\r\n", from_addr);
438    if (subject) {
439       fprintf(sfp, "Subject: %s\r\n", subject);
440       Dmsg1(10, "Subject: %s\r\n", subject);
441    }
442    if (reply_addr) {
443       fprintf(sfp, "Reply-To: %s\r\n", reply_addr);
444       Dmsg1(10, "Reply-To: %s\r\n", reply_addr);
445    }
446    if (err_addr) {
447       fprintf(sfp, "Errors-To: %s\r\n", err_addr);
448       Dmsg1(10, "Errors-To: %s\r\n", err_addr);
449    }
450
451 #if defined(HAVE_WIN32)
452    DWORD dwSize = UNLEN + 1;
453    LPSTR lpszBuffer = (LPSTR)alloca(dwSize);
454
455    if (GetUserName(lpszBuffer, &dwSize)) {
456       fprintf(sfp, "Sender: %s@%s\r\n", lpszBuffer, my_hostname);
457       Dmsg2(10, "Sender: %s@%s\r\n", lpszBuffer, my_hostname);
458    } else {
459       fprintf(sfp, "Sender: unknown-user@%s\r\n", my_hostname);
460       Dmsg1(10, "Sender: unknown-user@%s\r\n", my_hostname);
461    }
462 #else
463    if ((pwd = getpwuid(getuid())) == 0) {
464       fprintf(sfp, "Sender: userid-%d@%s\r\n", (int)getuid(), my_hostname);
465       Dmsg2(10, "Sender: userid-%d@%s\r\n", (int)getuid(), my_hostname);
466    } else {
467       fprintf(sfp, "Sender: %s@%s\r\n", pwd->pw_name, my_hostname);
468       Dmsg2(10, "Sender: %s@%s\r\n", pwd->pw_name, my_hostname);
469    }
470 #endif
471
472    fprintf(sfp, "To: %s", argv[0]);
473    Dmsg1(10, "To: %s", argv[0]);
474    for (i = 1; i < argc; i++) {
475       fprintf(sfp, ",%s", argv[i]);
476       Dmsg1(10, ",%s", argv[i]);
477    }
478
479    fprintf(sfp, "\r\n");
480    Dmsg0(10, "\r\n");
481    if (cc_addr) {
482       fprintf(sfp, "Cc: %s\r\n", cc_addr);
483       Dmsg1(10, "Cc: %s\r\n", cc_addr);
484    }
485
486    if (content_utf8) {
487       fprintf(sfp, "Content-Type: text/plain; charset=UTF-8\r\n");
488       Dmsg0(10, "Content-Type: text/plain; charset=UTF-8\r\n");
489    }
490
491    get_date_string(buf, sizeof(buf));
492    fprintf(sfp, "Date: %s\r\n", buf);
493    Dmsg1(10, "Date: %s\r\n", buf);
494
495    fprintf(sfp, "\r\n");
496
497    /*
498     *  Send message body
499     */
500    lines = 0;
501    while (fgets(buf, sizeof(buf), stdin)) {
502       if (maxlines > 0 && ++lines > maxlines) {
503          Dmsg1(20, "skip line because of maxlines limit: %lu\n", maxlines);
504          break;
505       }
506       buf[sizeof(buf)-1] = '\0';
507       buf[strlen(buf)-1] = '\0';
508       if (buf[0] == '.' && buf[1] == '\0') { /* quote lone dots */
509          fputs("..\r\n", sfp);
510       } else {                     /* pass body through unchanged */
511          fputs(buf, sfp);
512          fputs("\r\n", sfp);
513       }
514    }
515
516    if (lines > maxlines) {
517       Dmsg1(10, "hit maxlines limit: %lu\n", maxlines);
518       fprintf(sfp, "\r\n[maximum of %lu lines exceeded, skipped %lu lines of output]\r\n", maxlines, lines-maxlines);
519    }
520
521    /*
522     *  Send SMTP quit command
523     */
524    chat(".\r\n");
525    chat("quit\r\n");
526
527    /*
528     *  Go away gracefully ...
529     */
530    exit(0);
531 }