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