1 /* smtp-dummy - Dummy SMTP server that delivers mail to the given file
3 * Copyright © 2010 Carl Worth
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.
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.
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/ .
18 * Authors: Carl Worth <cworth@cworth.org>
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
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.
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
40 #include <sys/types.h>
41 #include <sys/socket.h>
42 #include <netinet/in.h>
46 #define STRNCMP_LITERAL(var, literal) \
47 strncmp ((var), (literal), sizeof (literal) - 1)
50 receive_data_to_file (FILE *peer, FILE *output)
56 while ((line_len = getline (&line, &line_size, peer)) != -1) {
57 if (STRNCMP_LITERAL (line, ".\r\n") == 0)
61 if (line[line_len-1] == '\n' && line[line_len-2] == '\r') {
62 line[line_len-2] = '\n';
63 line[line_len-1] = '\0';
65 fprintf (output, "%s",
66 line[0] == '.' ? line + 1 : line);
73 process_command (FILE *peer, FILE *output, const char *command)
75 if (STRNCMP_LITERAL (command, "EHLO ") == 0) {
76 fprintf (peer, "502 not implemented\r\n");
78 } else if (STRNCMP_LITERAL (command, "HELO ") == 0) {
79 fprintf (peer, "250 localhost\r\n");
81 } else if (STRNCMP_LITERAL (command, "MAIL FROM:") == 0 ||
82 STRNCMP_LITERAL (command, "RCPT TO:") == 0) {
83 fprintf (peer, "250 OK\r\n");
85 } else if (STRNCMP_LITERAL (command, "DATA") == 0) {
86 fprintf (peer, "354 End data with <CR><LF>.<CR><LF>\r\n");
88 receive_data_to_file (peer, output);
89 fprintf (peer, "250 OK\r\n");
91 } else if (STRNCMP_LITERAL (command, "QUIT") == 0) {
92 fprintf (peer, "221 BYE\r\n");
96 fprintf (stderr, "Unknown command: %s\n", command);
102 do_smtp_to_file (FILE *peer, FILE *output)
108 fprintf (peer, "220 localhost smtp-dummy\r\n");
111 while ((line_len = getline (&line, &line_size, peer)) != -1) {
112 if (process_command (peer, output, line))
120 main (int argc, char *argv[])
122 const char * progname;
123 char *output_filename;
124 FILE *peer_file, *output;
126 struct sockaddr_in addr, peer_addr;
127 struct hostent *hostinfo;
128 socklen_t peer_addr_len;
135 for (; argc >= 2; argc--, argv++) {
136 if (argv[1][0] != '-')
138 if (strcmp (argv[1], "--") == 0) {
143 if (strcmp (argv[1], "--background") == 0) {
147 fprintf(stderr, "%s: unregognized option '%s'\n",
154 "Usage: %s [--background] <output-file>\n", progname);
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));
166 sock = socket (AF_INET, SOCK_STREAM, 0);
168 fprintf (stderr, "Error: socket() failed: %s\n",
174 err = setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof (reuse));
176 fprintf (stderr, "Error: setsockopt() failed: %s\n",
181 hostinfo = gethostbyname ("localhost");
182 if (hostinfo == NULL) {
183 fprintf (stderr, "Unknown host: localhost\n");
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));
193 fprintf (stderr, "Error: bind() failed: %s\n",
199 err = listen (sock, 1);
201 fprintf (stderr, "Error: listen() failed: %s\n",
210 printf ("smtp_dummy_pid='%d'\n", pid);
216 fprintf (stderr, "Error: fork() failed: %s\n",
221 /* Reached if pid == 0 (the child process). */
222 /* Close stdout so that the one interested in pid value will
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);
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. */
237 peer_addr_len = sizeof (peer_addr);
238 peer = accept (sock, (struct sockaddr *) &peer_addr, &peer_addr_len);
240 fprintf (stderr, "Error: accept() failed: %s\n",
245 peer_file = fdopen (peer, "w+");
246 if (peer_file == NULL) {
247 fprintf (stderr, "Error: fdopen() failed: %s\n",
252 do_smtp_to_file (peer_file, output);