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