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