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