]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/tools/bsmtp.c
Attempt to fix bsmtp editing on OSes without %z editing.
[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
154    /* Add RFC822 date */
155    (void)localtime_r(&now, &tm);
156
157 #if defined(HAVE_WIN32)
158 #if defined(HAVE_MINGW)
159 __MINGW_IMPORT long     _dstbias;
160 #endif
161    long tzoffset = 0;
162
163    _tzset();
164
165    tzoffset = _timezone;
166    tzoffset += _dstbias;
167    tzoffset /= 60;
168
169    size_t length = strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S", &tm);
170    sprintf(&buf[length], " %+2.2ld%2.2u", -tzoffset / 60, abs(tzoffset) % 60);
171 #else
172    strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S", &tm);
173    tzset();
174    timezone /= 60;                  /* timezone offset in mins */
175    if (tm.tm_isdst == 1) {
176       timezone -= 60;              /* adjust for daylight savings */
177    }
178    sprintf(tzbuf, " %+2.2ld%2.2u", -timezone / 60, abs(timezone) % 60);
179    strcat(buf, tzbuf);              /* add +0100 */
180    strftime(tzbuf, sizeof(tzbuf), " (%Z)", &tm);
181    strcat(buf, tzbuf);              /* add (CEST) */
182 #endif
183 }
184
185
186 /*********************************************************************
187  *
188  *  Program to send email
189  */
190 int main (int argc, char *argv[])
191 {
192     char buf[MAXSTRING];
193     struct sockaddr_in sin;
194     struct hostent *hp;
195     int i, ch;
196     unsigned long maxlines, lines;
197 #if defined(HAVE_WIN32)
198     SOCKET s;
199 #else
200     int s, r;
201     struct passwd *pwd;
202 #endif
203     char *cp, *p;
204     
205    setlocale(LC_ALL, "en_US");
206    bindtextdomain("bacula", LOCALEDIR);
207    textdomain("bacula");
208
209    my_name_is(argc, argv, "bsmtp");
210    maxlines = 0;
211
212    while ((ch = getopt(argc, argv, "8c:d:f:h:r:s:l:?")) != -1) {
213       switch (ch) {
214       case '8':
215          content_utf8 = true;
216          break;
217       case 'c':
218          Dmsg1(20, "cc=%s\n", optarg);
219          cc_addr = optarg;
220          break;
221
222       case 'd':                    /* set debug level */
223          debug_level = atoi(optarg);
224          if (debug_level <= 0) {
225             debug_level = 1;
226          }
227          Dmsg1(20, "Debug level = %d\n", debug_level);
228          break;
229
230       case 'f':                    /* from */
231          from_addr = optarg;
232          break;
233
234       case 'h':                    /* smtp host */
235          Dmsg1(20, "host=%s\n", optarg);
236          p = strchr(optarg, ':');
237          if (p) {
238             *p++ = 0;
239             mailport = atoi(p);
240          }
241          mailhost = optarg;
242          break;
243
244       case 's':                    /* subject */
245          Dmsg1(20, "subject=%s\n", optarg);
246          subject = optarg;
247          break;
248
249       case 'r':                    /* reply address */
250          reply_addr = optarg;
251          break;
252
253       case 'l':
254          Dmsg1(20, "maxlines=%s\n", optarg);
255          maxlines = (unsigned long) atol(optarg);
256          break;
257
258       case '?':
259       default:
260          usage();
261
262       }
263    }
264    argc -= optind;
265    argv += optind;
266
267    if (argc < 1) {
268       Pmsg0(0, _("Fatal error: no recipient given.\n"));
269       usage();
270       exit(1);
271    }
272
273 #if defined(HAVE_WIN32)
274    _setmode(0, _O_BINARY);
275 #endif
276
277    /*
278     *  Determine SMTP server
279     */
280    if (mailhost == NULL) {
281       if ((cp = getenv("SMTPSERVER")) != NULL) {
282          mailhost = cp;
283       } else {
284          mailhost = "localhost";
285       }
286    }
287
288 #if defined(HAVE_WIN32)
289    WSADATA  wsaData;
290
291    WSAStartup(MAKEWORD(2,2), &wsaData);
292 #endif
293
294    /*
295     *  Find out my own host name for HELO;
296     *  if possible, get the fully qualified domain name
297     */
298    if (gethostname(my_hostname, sizeof(my_hostname) - 1) < 0) {
299       Pmsg1(0, _("Fatal gethostname error: ERR=%s\n"), strerror(errno));
300       exit(1);
301    }
302    if ((hp = gethostbyname(my_hostname)) == NULL) {
303       Pmsg2(0, _("Fatal gethostbyname for myself failed \"%s\": ERR=%s\n"), my_hostname,
304          strerror(errno));
305       exit(1);
306    }
307    strcpy(my_hostname, hp->h_name);
308    Dmsg1(20, "My hostname is: %s\n", my_hostname);
309
310    /*
311     *  Determine from address.
312     */
313    if (from_addr == NULL) {
314 #if defined(HAVE_WIN32)
315       DWORD dwSize = UNLEN + 1;
316       LPSTR lpszBuffer = (LPSTR)alloca(dwSize);
317
318       if (GetUserName(lpszBuffer, &dwSize)) {
319          sprintf(buf, "%s@%s", lpszBuffer, my_hostname);
320       } else {
321          sprintf(buf, "unknown-user@%s", my_hostname);
322       }
323 #else
324       if ((pwd = getpwuid(getuid())) == 0) {
325          sprintf(buf, "userid-%d@%s", (int)getuid(), my_hostname);
326       } else {
327          sprintf(buf, "%s@%s", pwd->pw_name, my_hostname);
328       }
329 #endif
330       from_addr = bstrdup(buf);
331    }
332    Dmsg1(20, "From addr=%s\n", from_addr);
333
334    /*
335     *  Connect to smtp daemon on mailhost.
336     */
337 hp:
338    if ((hp = gethostbyname(mailhost)) == NULL) {
339       Pmsg2(0, _("Error unknown mail host \"%s\": ERR=%s\n"), mailhost,
340          strerror(errno));
341       if (strcasecmp(mailhost, "localhost") != 0) {
342          Pmsg0(0, _("Retrying connection using \"localhost\".\n"));
343          mailhost = "localhost";
344          goto hp;
345       }
346       exit(1);
347    }
348
349    if (hp->h_addrtype != AF_INET) {
350       Pmsg1(0, _("Fatal error: Unknown address family for smtp host: %d\n"), hp->h_addrtype);
351       exit(1);
352    }
353    memset((char *)&sin, 0, sizeof(sin));
354    memcpy((char *)&sin.sin_addr, hp->h_addr, hp->h_length);
355    sin.sin_family = hp->h_addrtype;
356    sin.sin_port = htons(mailport);
357 #if defined(HAVE_WIN32)
358    if ((s = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, 0)) < 0) {
359       Pmsg1(0, _("Fatal socket error: ERR=%s\n"), strerror(errno));
360       exit(1);
361    }
362 #else
363    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
364       Pmsg1(0, _("Fatal socket error: ERR=%s\n"), strerror(errno));
365       exit(1);
366    }
367 #endif
368    if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
369       Pmsg2(0, _("Fatal connect error to %s: ERR=%s\n"), mailhost, strerror(errno));
370       exit(1);
371    }
372    Dmsg0(20, "Connected\n");
373
374 #if defined(HAVE_WIN32)
375    int fdSocket = _open_osfhandle(s, _O_RDWR | _O_BINARY);
376    if (fdSocket == -1) {
377       Pmsg1(0, _("Fatal _open_osfhandle error: ERR=%s\n"), strerror(errno));
378       exit(1);
379    }
380
381    int fdSocket2 = dup(fdSocket);
382
383    if ((sfp = fdopen(fdSocket, "wb")) == NULL) {
384       Pmsg1(0, _("Fatal fdopen error: ERR=%s\n"), strerror(errno));
385       exit(1);
386    }
387    if ((rfp = fdopen(fdSocket2, "rb")) == NULL) {
388       Pmsg1(0, _("Fatal fdopen error: ERR=%s\n"), strerror(errno));
389       exit(1);
390    }
391 #else
392    if ((r = dup(s)) < 0) {
393       Pmsg1(0, _("Fatal dup error: ERR=%s\n"), strerror(errno));
394       exit(1);
395    }
396    if ((sfp = fdopen(s, "w")) == 0) {
397       Pmsg1(0, _("Fatal fdopen error: ERR=%s\n"), strerror(errno));
398       exit(1);
399    }
400    if ((rfp = fdopen(r, "r")) == 0) {
401       Pmsg1(0, _("Fatal fdopen error: ERR=%s\n"), strerror(errno));
402       exit(1);
403    }
404 #endif
405
406    /*
407     *  Send SMTP headers.  Note, if any of the strings have a <
408     *   in them already, we do not enclose the string in < >, otherwise
409     *   we do.
410     */
411    get_response(); /* banner */
412    chat("helo %s\r\n", my_hostname);
413    if (strchr(from_addr, '<') == NULL) {
414       chat("mail from:<%s>\r\n", from_addr);
415    } else {
416       chat("mail from:%s\r\n", from_addr);
417    }
418
419    for (i = 0; i < argc; i++) {
420       Dmsg1(20, "rcpt to: %s\n", argv[i]);
421       if (strchr(argv[i], '<') == NULL) {
422          chat("rcpt to:<%s>\r\n", argv[i]);
423       } else {
424          chat("rcpt to:%s\r\n", argv[i]);
425       }
426    }
427
428    if (cc_addr) {
429       if (strchr(cc_addr, '<') == NULL) {
430          chat("rcpt to:<%s>\r\n", cc_addr);
431       } else {
432          chat("rcpt to:%s\r\n", cc_addr);
433       }
434    }
435    Dmsg0(20, "Data\n");
436    chat("data\r\n");
437
438    /*
439     *  Send message header
440     */
441    fprintf(sfp, "From: %s\r\n", from_addr);
442    Dmsg1(10, "From: %s\r\n", from_addr);
443    if (subject) {
444       fprintf(sfp, "Subject: %s\r\n", subject);
445       Dmsg1(10, "Subject: %s\r\n", subject);
446    }
447    if (reply_addr) {
448       fprintf(sfp, "Reply-To: %s\r\n", reply_addr);
449       Dmsg1(10, "Reply-To: %s\r\n", reply_addr);
450    }
451    if (err_addr) {
452       fprintf(sfp, "Errors-To: %s\r\n", err_addr);
453       Dmsg1(10, "Errors-To: %s\r\n", err_addr);
454    }
455
456 #if defined(HAVE_WIN32)
457    DWORD dwSize = UNLEN + 1;
458    LPSTR lpszBuffer = (LPSTR)alloca(dwSize);
459
460    if (GetUserName(lpszBuffer, &dwSize)) {
461       fprintf(sfp, "Sender: %s@%s\r\n", lpszBuffer, my_hostname);
462       Dmsg2(10, "Sender: %s@%s\r\n", lpszBuffer, my_hostname);
463    } else {
464       fprintf(sfp, "Sender: unknown-user@%s\r\n", my_hostname);
465       Dmsg1(10, "Sender: unknown-user@%s\r\n", my_hostname);
466    }
467 #else
468    if ((pwd = getpwuid(getuid())) == 0) {
469       fprintf(sfp, "Sender: userid-%d@%s\r\n", (int)getuid(), my_hostname);
470       Dmsg2(10, "Sender: userid-%d@%s\r\n", (int)getuid(), my_hostname);
471    } else {
472       fprintf(sfp, "Sender: %s@%s\r\n", pwd->pw_name, my_hostname);
473       Dmsg2(10, "Sender: %s@%s\r\n", pwd->pw_name, my_hostname);
474    }
475 #endif
476
477    fprintf(sfp, "To: %s", argv[0]);
478    Dmsg1(10, "To: %s", argv[0]);
479    for (i = 1; i < argc; i++) {
480       fprintf(sfp, ",%s", argv[i]);
481       Dmsg1(10, ",%s", argv[i]);
482    }
483
484    fprintf(sfp, "\r\n");
485    Dmsg0(10, "\r\n");
486    if (cc_addr) {
487       fprintf(sfp, "Cc: %s\r\n", cc_addr);
488       Dmsg1(10, "Cc: %s\r\n", cc_addr);
489    }
490
491    if (content_utf8) {
492       fprintf(sfp, "Content-Type: text/plain; charset=UTF-8\r\n");
493       Dmsg0(10, "Content-Type: text/plain; charset=UTF-8\r\n");
494    }
495
496    get_date_string(buf, sizeof(buf));
497    fprintf(sfp, "Date: %s\r\n", buf);
498    Dmsg1(10, "Date: %s\r\n", buf);
499
500    fprintf(sfp, "\r\n");
501
502    /*
503     *  Send message body
504     */
505    lines = 0;
506    while (fgets(buf, sizeof(buf), stdin)) {
507       if (maxlines > 0 && ++lines > maxlines) {
508          Dmsg1(20, "skip line because of maxlines limit: %lu\n", maxlines);
509          break;
510       }
511       buf[sizeof(buf)-1] = '\0';
512       buf[strlen(buf)-1] = '\0';
513       if (buf[0] == '.' && buf[1] == '\0') { /* quote lone dots */
514          fputs("..\r\n", sfp);
515       } else {                     /* pass body through unchanged */
516          fputs(buf, sfp);
517          fputs("\r\n", sfp);
518       }
519    }
520
521    if (lines > maxlines) {
522       Dmsg1(10, "hit maxlines limit: %lu\n", maxlines);
523       fprintf(sfp, "\r\n[maximum of %lu lines exceeded, skipped %lu lines of output]\r\n", maxlines, lines-maxlines);
524    }
525
526    /*
527     *  Send SMTP quit command
528     */
529    chat(".\r\n");
530    chat("quit\r\n");
531
532    /*
533     *  Go away gracefully ...
534     */
535    exit(0);
536 }