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