* Began (but did not finish) applying GPL3+ declarations to each source file. This...
[citadel.git] / citadel / citmail.c
1 /*
2  * $Id$
3  *
4  * This program attempts to act like a local MDA if you're using sendmail or
5  * some other non-Citadel MTA.  It basically just contacts the Citadel LMTP
6  * listener on a unix domain socket and transmits the message.
7  *
8  * Copyright (c) 1987-2009 by the citadel.org team
9  *
10  *  This program is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License as published by
12  *  the Free Software Foundation; either version 3 of the License, or
13  *  (at your option) any later version.
14  *
15  *  This program is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU General Public License for more details.
19  *
20  *  You should have received a copy of the GNU General Public License
21  *  along with this program; if not, write to the Free Software
22  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23  */
24
25 #include "sysdep.h"
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <ctype.h>
29 #include <stdio.h>
30 #include <signal.h>
31 #include <sys/types.h>
32 #include <sys/socket.h>
33 #include <sys/un.h>
34 #include <netdb.h>
35 #include <string.h>
36 #include <pwd.h>
37 #include <errno.h>
38 #include <stdarg.h>
39 #include <limits.h>
40 #include <libcitadel.h>
41 #include "citadel.h"
42 #ifndef HAVE_SNPRINTF
43 #include "snprintf.h"
44 #endif
45 #include "citadel_dirs.h"
46
47 int serv_sock;
48 int debug = 0;
49
50 void strip_trailing_nonprint(char *buf)
51 {
52         while ( (!IsEmptyStr(buf)) && (!isprint(buf[strlen(buf) - 1])) )
53                 buf[strlen(buf) - 1] = 0;
54 }
55
56
57 void timeout(int signum)
58 {
59         exit(signum);
60 }
61
62
63 int uds_connectsock(char *sockpath)
64 {
65         int s;
66         struct sockaddr_un addr;
67
68         memset(&addr, 0, sizeof(addr));
69         addr.sun_family = AF_UNIX;
70         strncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
71
72         s = socket(AF_UNIX, SOCK_STREAM, 0);
73         if (s < 0) {
74                 fprintf(stderr, "citmail: Can't create socket: %s\n",
75                         strerror(errno));
76                 exit(3);
77         }
78
79         if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
80                 fprintf(stderr, "citmail: can't connect: %s\n",
81                         strerror(errno));
82                 close(s);
83                 exit(3);
84         }
85
86         return s;
87 }
88
89
90 /*
91  * input binary data from socket
92  */
93 void serv_read(char *buf, int bytes)
94 {
95         int len, rlen;
96
97         len = 0;
98         while (len < bytes) {
99                 rlen = read(serv_sock, &buf[len], bytes - len);
100                 if (rlen < 1) {
101                         return;
102                 }
103                 len = len + rlen;
104         }
105 }
106
107
108 /*
109  * send binary to server
110  */
111 void serv_write(char *buf, int nbytes)
112 {
113         int bytes_written = 0;
114         int retval;
115         while (bytes_written < nbytes) {
116                 retval = write(serv_sock, &buf[bytes_written],
117                                nbytes - bytes_written);
118                 if (retval < 1) {
119                         return;
120                 }
121                 bytes_written = bytes_written + retval;
122         }
123 }
124
125
126
127 /*
128  * input string from socket - implemented in terms of serv_read()
129  */
130 void serv_gets(char *buf)
131 {
132         int i;
133
134         /* Read one character at a time.
135          */
136         for (i = 0;; i++) {
137                 serv_read(&buf[i], 1);
138                 if (buf[i] == '\n' || i == (SIZ-1))
139                         break;
140         }
141
142         /* If we got a long line, discard characters until the newline.
143          */
144         if (i == (SIZ-1))
145                 while (buf[i] != '\n')
146                         serv_read(&buf[i], 1);
147
148         /* Strip all trailing nonprintables (crlf)
149          */
150         buf[i] = 0;
151         strip_trailing_nonprint(buf);
152         if (debug) fprintf(stderr, "> %s\n", buf);
153 }
154
155
156 /*
157  * send line to server - implemented in terms of serv_write()
158  */
159 void serv_puts(char *buf)
160 {
161         if (debug) fprintf(stderr, "< %s\n", buf);
162         serv_write(buf, strlen(buf));
163         serv_write("\n", 1);
164 }
165
166
167
168 void cleanup(int exitcode) {
169         char buf[1024];
170
171         if (exitcode != 0) {
172                 fprintf(stderr, "citmail: error #%d occurred while sending mail.\n", exitcode);
173                 fprintf(stderr, "Please check your Citadel configuration.\n");
174         }
175         serv_puts("QUIT");
176         serv_gets(buf);
177         exit(exitcode);
178 }
179
180
181
182 int main(int argc, char **argv) {
183         char buf[1024];
184         char fromline[1024];
185         FILE *fp;
186         int i;
187         struct passwd *pw;
188         int from_header = 0;
189         int in_body = 0;
190         int relh=0;
191         int home=0;
192         char relhome[PATH_MAX]="";
193         char ctdldir[PATH_MAX]=CTDLDIR;
194         char *sp, *ep;
195         char hostname[256];
196         char **recipients = NULL;
197         int num_recipients = 0;
198         int to_or_cc = 0;
199         int read_recipients_from_headers = 0;
200         char *add_these_recipients = NULL;
201
202         for (i=1; i<argc; ++i) {
203                 if (!strcmp(argv[i], "-d")) {
204                         debug = 1;
205                 }
206                 else if (!strcmp(argv[i], "-t")) {
207                         read_recipients_from_headers = 1;
208                 }
209                 else if (argv[i][0] != '-') {
210                         ++num_recipients;
211                         recipients = realloc(recipients, (num_recipients * sizeof (char *)));
212                         recipients[num_recipients - 1] = strdup(argv[i]);
213                 }
214         }
215                
216         /* TODO: should we be able to calculate relative dirs? */
217         calc_dirs_n_files(relh, home, relhome, ctdldir, 0);
218
219         pw = getpwuid(getuid());
220
221         fp = tmpfile();
222         if (fp == NULL) return(errno);
223         serv_sock = uds_connectsock(file_lmtp_socket);  /* FIXME: if called as 'sendmail' connect to file_lmtp_unfiltered_socket */
224         serv_gets(buf);
225         if (buf[0] != '2') {
226                 fprintf(stderr, "%s\n", &buf[4]);
227                 if (debug) fprintf(stderr, "citmail: could not connect to LMTP socket.\n");
228                 cleanup(1);
229         }
230
231         sp = strchr (buf, ' ');
232         if (sp == NULL) {
233                 if (debug) fprintf(stderr, "citmail: ould not calculate hostname.\n");
234                 cleanup(2);
235         }
236         sp ++;
237         ep = strchr (sp, ' ');
238         if (ep == NULL) cleanup(3);
239         *ep = '\0';
240         strncpy(hostname, sp, sizeof hostname);
241
242         snprintf(fromline, sizeof fromline, "From: %s@%s", pw->pw_name, hostname);
243         while (fgets(buf, 1024, stdin) != NULL) {
244                 if ( ( (buf[0] == 13) || (buf[0] == 10)) && (in_body == 0) ) {
245                         in_body = 1;
246                         if (from_header == 0) {
247                                 fprintf(fp, "%s%s", fromline, buf);
248                         }
249                 }
250                 if (in_body == 0 && !strncasecmp(buf, "From:", 5)) {
251                         strcpy(fromline, buf);
252                         from_header = 1;
253                 }
254
255                 if (read_recipients_from_headers) {
256                         add_these_recipients = NULL;
257                         if ((isspace(buf[0])) && (to_or_cc)) {
258                                 add_these_recipients = buf;
259                         }
260                         else {
261                                 if ((!strncasecmp(buf, "To:", 3)) || (!strncasecmp(buf, "Cc:", 3))) {
262                                         to_or_cc = 1;
263                                 }
264                                 else {
265                                         to_or_cc = 0;
266                                 }
267                                 if (to_or_cc) {
268                                         add_these_recipients = &buf[3];
269                                 }
270                         }
271
272                         if (add_these_recipients) {
273                                 int num_recp_on_this_line;
274                                 char this_recp[256];
275
276                                 num_recp_on_this_line = num_tokens(add_these_recipients, ',');
277                                 for (i=0; i<num_recp_on_this_line; ++i) {
278                                         extract_token(this_recp, add_these_recipients,
279                                                 i, ',', sizeof this_recp);
280                                         striplt(this_recp);
281                                         if (!IsEmptyStr(this_recp)) {
282                                                 ++num_recipients;
283                                                 recipients = realloc(recipients,
284                                                         (num_recipients * sizeof (char *)));
285                                                 recipients[num_recipients - 1] = strdup(this_recp);
286                                         }
287                                 }
288                         }
289                 }
290
291                 fprintf(fp, "%s", buf);
292         }
293         strip_trailing_nonprint(fromline);
294
295         sprintf(buf, "LHLO %s", hostname);
296         serv_puts(buf);
297         do {
298                 serv_gets(buf);
299                 strcat(buf, "    ");
300         } while (buf[3] == '-');
301         if (buf[0] != '2') cleanup(4);
302
303         snprintf(buf, sizeof buf, "MAIL %s", fromline);
304         serv_puts(buf);
305         serv_gets(buf);
306         if (buf[0] != '2') cleanup(5);
307
308         for (i=0; i<num_recipients; ++i) {
309                 snprintf(buf, sizeof buf, "RCPT To: %s", recipients[i]);
310                 serv_puts(buf);
311                 serv_gets(buf);
312                 free(recipients[i]);
313         }
314         free(recipients);
315
316         serv_puts("DATA");
317         serv_gets(buf);
318         if (buf[0]!='3') cleanup(6);
319
320         rewind(fp);
321         while (fgets(buf, sizeof buf, fp) != NULL) {
322                 strip_trailing_nonprint(buf);
323                 serv_puts(buf);
324         }
325         serv_puts(".");
326         serv_gets(buf);
327         if (buf[0] != '2') {
328                 fprintf(stderr, "%s\n", &buf[4]);
329                 cleanup(7);
330         }
331         else {
332                 cleanup(0);
333         }
334
335         /* We won't actually reach this statement but the compiler will
336          * display a spurious warning about an invalid return type if
337          * we don't return an int.
338          */
339         return(0);
340 }