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