]> git.notmuchmail.org Git - notmuch/blob - test/smtp-dummy.c
contrib: add pick TODO file
[notmuch] / test / smtp-dummy.c
1 /* smtp-dummy - Dummy SMTP server that delivers mail to the given file
2  *
3  * Copyright © 2010 Carl Worth
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see http://www.gnu.org/licenses/ .
17  *
18  * Authors: Carl Worth <cworth@cworth.org>
19  */
20
21 /* This (non-compliant) SMTP server listens on localhost, port 25025
22  * and delivers a mail received to the given filename, (specified as a
23  * command-line argument). It exists after the first client connection
24  * completes.
25  *
26  * It implements very little of the SMTP protocol, even less than
27  * specified as the minimum implementation in the SMTP RFC, (not
28  * implementing RSET, NOOP, nor VRFY). And it doesn't do any
29  * error-checking on the input.
30  *
31  * That is to say, if you use this program, you will very likely find
32  * cases where it doesn't do everything your SMTP client expects. You
33  * have been warned.
34  */
35
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <errno.h>
40 #include <sys/types.h>
41 #include <sys/socket.h>
42 #include <netinet/in.h>
43 #include <netdb.h>
44 #include <unistd.h>
45
46 #define STRNCMP_LITERAL(var, literal) \
47     strncmp ((var), (literal), sizeof (literal) - 1)
48
49 static void
50 receive_data_to_file (FILE *peer, FILE *output)
51 {
52         char *line = NULL;
53         size_t line_size;
54         ssize_t line_len;
55
56         while ((line_len = getline (&line, &line_size, peer)) != -1) {
57                 if (STRNCMP_LITERAL (line, ".\r\n") == 0)
58                         break;
59                 if (line_len < 2)
60                         continue;
61                 if (line[line_len-1] == '\n' && line[line_len-2] == '\r') {
62                         line[line_len-2] = '\n';
63                         line[line_len-1] = '\0';
64                 }
65                 fprintf (output, "%s",
66                          line[0] == '.' ? line + 1 : line);
67         }
68
69         free (line);
70 }
71
72 static int
73 process_command (FILE *peer, FILE *output, const char *command)
74 {
75         if (STRNCMP_LITERAL (command, "EHLO ") == 0) {
76                 fprintf (peer, "502 not implemented\r\n");
77                 fflush (peer);
78         } else if (STRNCMP_LITERAL (command, "HELO ") == 0) {
79                 fprintf (peer, "250 localhost\r\n");
80                 fflush (peer);
81         } else if (STRNCMP_LITERAL (command, "MAIL FROM:") == 0 ||
82                    STRNCMP_LITERAL (command, "RCPT TO:") == 0) {
83                 fprintf (peer, "250 OK\r\n");
84                 fflush (peer);
85         } else if (STRNCMP_LITERAL (command, "DATA") == 0) {
86                 fprintf (peer, "354 End data with <CR><LF>.<CR><LF>\r\n");
87                 fflush (peer);
88                 receive_data_to_file (peer, output);
89                 fprintf (peer, "250 OK\r\n");
90                 fflush (peer);
91         } else if (STRNCMP_LITERAL (command, "QUIT") == 0) {
92                 fprintf (peer, "221 BYE\r\n");
93                 fflush (peer);
94                 return 1;
95         } else {
96                 fprintf (stderr, "Unknown command: %s\n", command);
97         }
98         return 0;
99 }
100
101 static void
102 do_smtp_to_file (FILE *peer, FILE *output)
103 {
104         char *line = NULL;
105         size_t line_size;
106         ssize_t line_len;
107
108         fprintf (peer, "220 localhost smtp-dummy\r\n");
109         fflush (peer);
110
111         while ((line_len = getline (&line, &line_size, peer)) != -1) {
112                 if (process_command (peer, output, line))
113                         break;
114         }
115
116         free (line);
117 }
118
119 int
120 main (int argc, char *argv[])
121 {
122         const char * progname;
123         char *output_filename;
124         FILE *peer_file, *output;
125         int sock, peer, err;
126         struct sockaddr_in addr, peer_addr;
127         struct hostent *hostinfo;
128         socklen_t peer_addr_len;
129         int reuse;
130         int background;
131
132         progname = argv[0];
133
134         background = 0;
135         for (; argc >= 2; argc--, argv++) {
136                 if (argv[1][0] != '-')
137                         break;
138                 if (strcmp (argv[1], "--") == 0) {
139                         argc--;
140                         argv++;
141                         break;
142                 }
143                 if (strcmp (argv[1], "--background") == 0) {
144                         background = 1;
145                         continue;
146                 }
147                 fprintf(stderr, "%s: unregognized option '%s'\n",
148                         progname, argv[1]);
149                 return 1;
150         }
151
152         if (argc != 2) {
153                 fprintf (stderr,
154                          "Usage: %s [--background] <output-file>\n", progname);
155                 return 1;
156         }
157
158         output_filename = argv[1];
159         output = fopen (output_filename, "w");
160         if (output == NULL) {
161                 fprintf (stderr, "Failed to open %s for writing: %s\n",
162                          output_filename, strerror (errno));
163                 return 1;
164         }
165
166         sock = socket (AF_INET, SOCK_STREAM, 0);
167         if (sock == -1) {
168                 fprintf (stderr, "Error: socket() failed: %s\n",
169                          strerror (errno));
170                 return 1;
171         }
172
173         reuse = 1;
174         err = setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof (reuse));
175         if (err) {
176                 fprintf (stderr, "Error: setsockopt() failed: %s\n",
177                          strerror (errno));
178                 return 1;
179         }
180
181         hostinfo = gethostbyname ("localhost");
182         if (hostinfo == NULL) {
183                 fprintf (stderr, "Unknown host: localhost\n");
184                 return 1;
185         }
186
187         memset (&addr, 0, sizeof (addr));
188         addr.sin_family = AF_INET;
189         addr.sin_port = htons (25025);
190         addr.sin_addr = *(struct in_addr *) hostinfo->h_addr;
191         err = bind (sock, (struct sockaddr *) &addr, sizeof(addr));
192         if (err) {
193                 fprintf (stderr, "Error: bind() failed: %s\n",
194                          strerror (errno));
195                 close (sock);
196                 return 1;
197         }
198
199         err = listen (sock, 1);
200         if (err) {
201                 fprintf (stderr, "Error: listen() failed: %s\n",
202                          strerror (errno));
203                 close (sock);
204                 return 1;
205         }
206
207         if (background) {
208                 int pid = fork ();
209                 if (pid > 0) {
210                         printf ("smtp_dummy_pid='%d'\n", pid);
211                         fflush (stdout);
212                         close (sock);
213                         return 0;
214                 }
215                 if (pid < 0) {
216                         fprintf (stderr, "Error: fork() failed: %s\n",
217                                  strerror (errno));
218                         close (sock);
219                         return 1;
220                 }
221                 /* Reached if pid == 0 (the child process). */
222                 /* Close stdout so that the one interested in pid value will
223                    also get EOF. */
224                 close (STDOUT_FILENO);
225                 /* dup2() will re-reserve fd of stdout (1) (opportunistically),
226                    in case fd of stderr (2) is open. If that was not open we
227                    don't care fd of stdout (1) either. */
228                 dup2 (STDERR_FILENO, STDOUT_FILENO);
229
230                 /* This process is now out of reach of shell's job control.
231                    To resolve the rare but possible condition where this
232                    "daemon" is started but never connected this process will
233                    (only) have 30 seconds to exist. */
234                 alarm (30);
235         }
236
237         peer_addr_len = sizeof (peer_addr);
238         peer = accept (sock, (struct sockaddr *) &peer_addr, &peer_addr_len);
239         if (peer == -1) {
240                 fprintf (stderr, "Error: accept() failed: %s\n",
241                          strerror (errno));
242                 return 1;
243         }
244
245         peer_file = fdopen (peer, "w+");
246         if (peer_file == NULL) {
247                 fprintf (stderr, "Error: fdopen() failed: %s\n",
248                          strerror (errno));
249                 return 1;
250         }
251
252         do_smtp_to_file (peer_file, output);
253
254         fclose (output);
255         fclose (peer_file);
256         close (sock);
257
258         return 0;
259 }