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