]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/tools/bsmtp.c
Tweak fix MySQL quoting again :-(
[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    Version $Id$
68
69  */
70
71
72 #include "bacula.h"
73 #include "jcr.h"
74 #define MY_NAME "bsmtp"
75
76 #if defined(HAVE_WIN32)
77 #include <lmcons.h>
78 #endif
79
80 /* Dummy functions */
81 int generate_daemon_event(JCR *jcr, const char *event) 
82    { return 1; }
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
209 static long tz_offset(time_t lnow, struct tm &tm)
210 {
211 #if defined(HAVE_WIN32)
212 #if defined(HAVE_MINGW)
213 __MINGW_IMPORT long     _dstbias;
214 #endif
215 #if defined(MINGW64)
216 # define _tzset tzset
217 #endif
218    /* Win32 code */
219    long offset;
220    _tzset();
221    offset = _timezone;
222    if (tm.tm_isdst) {
223       offset += _dstbias;
224    }
225    return offset /= 60;
226 #else
227
228    /* Unix/Linux code */
229    struct tm tm_utc;
230    time_t now = lnow;
231
232    (void)gmtime_r(&now, &tm_utc);
233    tm_utc.tm_isdst = tm.tm_isdst;
234    return (long)difftime(mktime(&tm_utc), now) / 60;
235 #endif
236 }
237
238 static void get_date_string(char *buf, int buf_len)
239 {
240    time_t now = time(NULL);
241    struct tm tm;
242    char tzbuf[MAXSTRING];
243    long my_timezone;
244
245    /* Add RFC822 date */
246    (void)localtime_r(&now, &tm);
247
248    my_timezone = tz_offset(now, tm);
249    strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S", &tm);
250    sprintf(tzbuf, " %+2.2ld%2.2u", -my_timezone / 60, abs(my_timezone) % 60);
251    strcat(buf, tzbuf);              /* add +0100 */
252    strftime(tzbuf, sizeof(tzbuf), " (%Z)", &tm);
253    strcat(buf, tzbuf);              /* add (CEST) */
254 }
255
256
257 /*********************************************************************
258  *
259  *  Program to send email
260  */
261 int main (int argc, char *argv[])
262 {
263     char buf[1000];
264     struct sockaddr_in sin;
265     struct hostent *hp;
266     int i, ch;
267     unsigned long maxlines, lines;
268 #if defined(HAVE_WIN32)
269     SOCKET s;
270 #else
271     int s, r;
272     struct passwd *pwd;
273 #endif
274     char *cp, *p;
275     
276    setlocale(LC_ALL, "en_US");
277    bindtextdomain("bacula", LOCALEDIR);
278    textdomain("bacula");
279
280    my_name_is(argc, argv, "bsmtp");
281    maxlines = 0;
282
283    while ((ch = getopt(argc, argv, "8c:d:f:h:r:s:l:?")) != -1) {
284       switch (ch) {
285       case '8':
286          content_utf8 = true;
287          break;
288       case 'c':
289          Dmsg1(20, "cc=%s\n", optarg);
290          cc_addr = optarg;
291          break;
292
293       case 'd':                    /* set debug level */
294          if (*optarg == 't') {
295             dbg_timestamp = true;
296          } else {
297             debug_level = atoi(optarg);
298             if (debug_level <= 0) {
299                debug_level = 1;
300             }
301          }
302          Dmsg1(20, "Debug level = %d\n", debug_level);
303          break;
304
305       case 'f':                    /* from */
306          from_addr = optarg;
307          break;
308
309       case 'h':                    /* smtp host */
310          Dmsg1(20, "host=%s\n", optarg);
311          p = strchr(optarg, ':');
312          if (p) {
313             *p++ = 0;
314             mailport = atoi(p);
315          }
316          mailhost = optarg;
317          break;
318
319       case 's':                    /* subject */
320          Dmsg1(20, "subject=%s\n", optarg);
321          subject = optarg;
322          break;
323
324       case 'r':                    /* reply address */
325          reply_addr = optarg;
326          break;
327
328       case 'l':
329          Dmsg1(20, "maxlines=%s\n", optarg);
330          maxlines = (unsigned long) atol(optarg);
331          break;
332
333       case '?':
334       default:
335          usage();
336
337       }
338    }
339    argc -= optind;
340    argv += optind;
341
342    if (argc < 1) {
343       Pmsg0(0, _("Fatal error: no recipient given.\n"));
344       usage();
345       exit(1);
346    }
347
348
349    /*
350     *  Determine SMTP server
351     */
352    if (mailhost == NULL) {
353       if ((cp = getenv("SMTPSERVER")) != NULL) {
354          mailhost = cp;
355       } else {
356          mailhost = "localhost";
357       }
358    }
359
360 #if defined(HAVE_WIN32)
361    WSADATA  wsaData;
362
363    _setmode(0, _O_BINARY);
364    WSAStartup(MAKEWORD(2,2), &wsaData);
365 #endif
366
367    /*
368     *  Find out my own host name for HELO;
369     *  if possible, get the fully qualified domain name
370     */
371    if (gethostname(my_hostname, sizeof(my_hostname) - 1) < 0) {
372       Pmsg1(0, _("Fatal gethostname error: ERR=%s\n"), strerror(errno));
373       exit(1);
374    }
375    if ((hp = gethostbyname(my_hostname)) == NULL) {
376       Pmsg2(0, _("Fatal gethostbyname for myself failed \"%s\": ERR=%s\n"), my_hostname,
377          strerror(errno));
378       exit(1);
379    }
380    strcpy(my_hostname, hp->h_name);
381    Dmsg1(20, "My hostname is: %s\n", my_hostname);
382
383    /*
384     *  Determine from address.
385     */
386    if (from_addr == NULL) {
387 #if defined(HAVE_WIN32)
388       DWORD dwSize = UNLEN + 1;
389       LPSTR lpszBuffer = (LPSTR)alloca(dwSize);
390
391       if (GetUserName(lpszBuffer, &dwSize)) {
392          sprintf(buf, "%s@%s", lpszBuffer, my_hostname);
393       } else {
394          sprintf(buf, "unknown-user@%s", my_hostname);
395       }
396 #else
397       if ((pwd = getpwuid(getuid())) == 0) {
398          sprintf(buf, "userid-%d@%s", (int)getuid(), my_hostname);
399       } else {
400          sprintf(buf, "%s@%s", pwd->pw_name, my_hostname);
401       }
402 #endif
403       from_addr = bstrdup(buf);
404    }
405    Dmsg1(20, "From addr=%s\n", from_addr);
406
407    /*
408     *  Connect to smtp daemon on mailhost.
409     */
410 hp:
411    if ((hp = gethostbyname(mailhost)) == NULL) {
412       Pmsg2(0, _("Error unknown mail host \"%s\": ERR=%s\n"), mailhost,
413          strerror(errno));
414       if (strcasecmp(mailhost, "localhost") != 0) {
415          Pmsg0(0, _("Retrying connection using \"localhost\".\n"));
416          mailhost = "localhost";
417          goto hp;
418       }
419       exit(1);
420    }
421
422    if (hp->h_addrtype != AF_INET) {
423       Pmsg1(0, _("Fatal error: Unknown address family for smtp host: %d\n"), hp->h_addrtype);
424       exit(1);
425    }
426    memset((char *)&sin, 0, sizeof(sin));
427    memcpy((char *)&sin.sin_addr, hp->h_addr, hp->h_length);
428    sin.sin_family = hp->h_addrtype;
429    sin.sin_port = htons(mailport);
430 #if defined(HAVE_WIN32)
431    if ((s = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, 0)) < 0) {
432       Pmsg1(0, _("Fatal socket error: ERR=%s\n"), strerror(errno));
433       exit(1);
434    }
435 #else
436    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
437       Pmsg1(0, _("Fatal socket error: ERR=%s\n"), strerror(errno));
438       exit(1);
439    }
440 #endif
441    if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
442       Pmsg2(0, _("Fatal connect error to %s: ERR=%s\n"), mailhost, strerror(errno));
443       exit(1);
444    }
445    Dmsg0(20, "Connected\n");
446
447 #if defined(HAVE_WIN32)
448    int fdSocket = _open_osfhandle(s, _O_RDWR | _O_BINARY);
449    if (fdSocket == -1) {
450       Pmsg1(0, _("Fatal _open_osfhandle error: ERR=%s\n"), strerror(errno));
451       exit(1);
452    }
453
454    int fdSocket2 = dup(fdSocket);
455
456    if ((sfp = fdopen(fdSocket, "wb")) == NULL) {
457       Pmsg1(0, _("Fatal fdopen error: ERR=%s\n"), strerror(errno));
458       exit(1);
459    }
460    if ((rfp = fdopen(fdSocket2, "rb")) == NULL) {
461       Pmsg1(0, _("Fatal fdopen error: ERR=%s\n"), strerror(errno));
462       exit(1);
463    }
464 #else
465    if ((r = dup(s)) < 0) {
466       Pmsg1(0, _("Fatal dup error: ERR=%s\n"), strerror(errno));
467       exit(1);
468    }
469    if ((sfp = fdopen(s, "w")) == 0) {
470       Pmsg1(0, _("Fatal fdopen error: ERR=%s\n"), strerror(errno));
471       exit(1);
472    }
473    if ((rfp = fdopen(r, "r")) == 0) {
474       Pmsg1(0, _("Fatal fdopen error: ERR=%s\n"), strerror(errno));
475       exit(1);
476    }
477 #endif
478
479    /*
480     *  Send SMTP headers.  Note, if any of the strings have a <
481     *   in them already, we do not enclose the string in < >, otherwise
482     *   we do.
483     */
484    get_response(); /* banner */
485    chat("HELO %s\r\n", my_hostname);
486    chat("MAIL FROM:%s\r\n", cleanup_addr(from_addr, buf, sizeof(buf)));
487    
488    for (i = 0; i < argc; i++) {
489       Dmsg1(20, "rcpt to: %s\n", argv[i]);
490       chat("RCPT TO:%s\r\n", cleanup_addr(argv[i], buf, sizeof(buf)));
491    }
492
493    if (cc_addr) {
494       chat("RCPT TO:%s\r\n", cleanup_addr(cc_addr, buf, sizeof(buf)));
495    }
496    Dmsg0(20, "Data\n");
497    chat("DATA\r\n");
498
499    /*
500     *  Send message header
501     */
502    fprintf(sfp, "From: %s\r\n", from_addr);
503    Dmsg1(10, "From: %s\r\n", from_addr);
504    if (subject) {
505       fprintf(sfp, "Subject: %s\r\n", subject);
506       Dmsg1(10, "Subject: %s\r\n", subject);
507    }
508    if (reply_addr) {
509       fprintf(sfp, "Reply-To: %s\r\n", reply_addr);
510       Dmsg1(10, "Reply-To: %s\r\n", reply_addr);
511    }
512    if (err_addr) {
513       fprintf(sfp, "Errors-To: %s\r\n", err_addr);
514       Dmsg1(10, "Errors-To: %s\r\n", err_addr);
515    }
516
517 #if defined(HAVE_WIN32)
518    DWORD dwSize = UNLEN + 1;
519    LPSTR lpszBuffer = (LPSTR)alloca(dwSize);
520
521    if (GetUserName(lpszBuffer, &dwSize)) {
522       fprintf(sfp, "Sender: %s@%s\r\n", lpszBuffer, my_hostname);
523       Dmsg2(10, "Sender: %s@%s\r\n", lpszBuffer, my_hostname);
524    } else {
525       fprintf(sfp, "Sender: unknown-user@%s\r\n", my_hostname);
526       Dmsg1(10, "Sender: unknown-user@%s\r\n", my_hostname);
527    }
528 #else
529    if ((pwd = getpwuid(getuid())) == 0) {
530       fprintf(sfp, "Sender: userid-%d@%s\r\n", (int)getuid(), my_hostname);
531       Dmsg2(10, "Sender: userid-%d@%s\r\n", (int)getuid(), my_hostname);
532    } else {
533       fprintf(sfp, "Sender: %s@%s\r\n", pwd->pw_name, my_hostname);
534       Dmsg2(10, "Sender: %s@%s\r\n", pwd->pw_name, my_hostname);
535    }
536 #endif
537
538    fprintf(sfp, "To: %s", argv[0]);
539    Dmsg1(10, "To: %s", argv[0]);
540    for (i = 1; i < argc; i++) {
541       fprintf(sfp, ",%s", argv[i]);
542       Dmsg1(10, ",%s", argv[i]);
543    }
544
545    fprintf(sfp, "\r\n");
546    Dmsg0(10, "\r\n");
547    if (cc_addr) {
548       fprintf(sfp, "Cc: %s\r\n", cc_addr);
549       Dmsg1(10, "Cc: %s\r\n", cc_addr);
550    }
551
552    if (content_utf8) {
553       fprintf(sfp, "Content-Type: text/plain; charset=UTF-8\r\n");
554       Dmsg0(10, "Content-Type: text/plain; charset=UTF-8\r\n");
555    }
556
557    get_date_string(buf, sizeof(buf));
558    fprintf(sfp, "Date: %s\r\n", buf);
559    Dmsg1(10, "Date: %s\r\n", buf);
560
561    fprintf(sfp, "\r\n");
562
563    /*
564     *  Send message body
565     */
566    lines = 0;
567    while (fgets(buf, sizeof(buf), stdin)) {
568       if (maxlines > 0 && ++lines > maxlines) {
569          Dmsg1(20, "skip line because of maxlines limit: %lu\n", maxlines);
570          while (fgets(buf, sizeof(buf), stdin)) {
571             ++lines;
572          }
573          break;
574       }
575       buf[sizeof(buf)-1] = '\0';
576       buf[strlen(buf)-1] = '\0';
577       if (buf[0] == '.') {         /* add extra . see RFC 2821 4.5.2 */
578          fputs(".", sfp);
579       }
580       fputs(buf, sfp);
581       fputs("\r\n", sfp);
582    }
583
584    if (lines > maxlines) {
585       Dmsg1(10, "hit maxlines limit: %lu\n", maxlines);
586       fprintf(sfp, "\r\n\r\n[maximum of %lu lines exceeded, skipped %lu lines of output]\r\n", maxlines, lines-maxlines);
587    }
588
589    /*
590     *  Send SMTP quit command
591     */
592    chat(".\r\n");
593    chat("QUIT\r\n");
594
595    /*
596     *  Go away gracefully ...
597     */
598    exit(0);
599 }