void session_pickup(struct session *s, struct submit_status *ss) { void *ssl; s->s_flags &= ~F_WAITIMSG; if ((ss != NULL && ss->code == 421) || (s->s_dstatus & DS_TEMPFAILURE)) { stat_increment("smtp.tempfail", 1); session_respond(s, "421 Service temporarily unavailable"); session_enter_state(s, S_QUIT); io_reload(&s->s_io); return; } switch (s->s_state) { case S_CONNECTED: session_enter_state(s, S_INIT); s->s_msg.session_id = s->s_id; s->s_msg.ss = s->s_ss; session_imsg(s, PROC_MFA, IMSG_MFA_CONNECT, 0, 0, -1, &s->s_msg, sizeof(s->s_msg)); break; case S_INIT: if (ss->code != 250) { session_destroy(s, "rejected by filter"); return; } if (s->s_l->flags & F_SMTPS) { ssl = ssl_smtp_init(s->s_l->ssl_ctx); io_set_read(&s->s_io); io_start_tls(&s->s_io, ssl); return; } session_respond(s, SMTPD_BANNER, env->sc_hostname); session_enter_state(s, S_GREETED); break; case S_AUTH_FINALIZE: if (s->s_flags & F_AUTHENTICATED) session_respond(s, "235 Authentication succeeded"); else session_respond(s, "535 Authentication failed"); session_enter_state(s, S_HELO); break; case S_RSET: session_respond(s, "250 2.0.0 Reset state"); session_enter_state(s, S_HELO); break; case S_HELO: if (ss->code != 250) { session_enter_state(s, S_GREETED); session_respond(s, "%d Helo rejected", ss->code); break; } session_respond(s, "250%c%s Hello %s [%s], pleased to meet you", (s->s_flags & F_EHLO) ? '-' : ' ', env->sc_hostname, s->s_msg.helo, ss_to_text(&s->s_ss)); if (s->s_flags & F_EHLO) { /* unconditionnal extensions go first */ session_respond(s, "250-8BITMIME"); session_respond(s, "250-ENHANCEDSTATUSCODES"); /* XXX - we also want to support reading SIZE from MAIL parameters */ session_respond(s, "250-SIZE %zu", env->sc_maxsize); if (ADVERTISE_TLS(s)) session_respond(s, "250-STARTTLS"); if (ADVERTISE_AUTH(s)) session_respond(s, "250-AUTH PLAIN LOGIN"); session_respond(s, "250 HELP"); } break; case S_MAIL_MFA: if (ss->code != 250) { session_enter_state(s, S_HELO); session_respond(s, "%d Sender rejected", ss->code); break; } session_enter_state(s, S_MAIL_QUEUE); s->s_msg.sender = ss->u.maddr; session_imsg(s, PROC_QUEUE, IMSG_QUEUE_CREATE_MESSAGE, 0, 0, -1, &s->s_msg, sizeof(s->s_msg)); break; case S_MAIL_QUEUE: session_enter_state(s, S_MAIL); session_respond(s, "%d 2.1.0 Sender ok", ss->code); break; case S_RCPT_MFA: /* recipient was not accepted */ if (ss->code != 250) { /* We do not have a valid recipient, downgrade state */ if (s->rcptcount == 0) session_enter_state(s, S_MAIL); else session_enter_state(s, S_RCPT); session_respond(s, "%d 5.0.0 Recipient rejected: %s@%s", ss->code, s->s_msg.rcpt.user, s->s_msg.rcpt.domain); break; } session_enter_state(s, S_RCPT); s->rcptcount++; s->s_msg.dest = ss->u.maddr; session_respond(s, "%d 2.0.0 Recipient ok", ss->code); break; case S_DATA_QUEUE: session_enter_state(s, S_DATACONTENT); session_respond(s, "354 Enter mail, end with \".\" on a line by" " itself"); fprintf(s->datafp, "Received: from %s (%s [%s])\n", s->s_msg.helo, s->s_hostname, ss_to_text(&s->s_ss)); fprintf(s->datafp, "\tby %s (OpenSMTPD) with %sSMTP id %08x", env->sc_hostname, s->s_flags & F_EHLO ? "E" : "", evpid_to_msgid(s->s_msg.id)); if (s->s_flags & F_SECURE) { fprintf(s->datafp, "\n\t(version=%s cipher=%s bits=%d)", SSL_get_cipher_version(s->s_io.ssl), SSL_get_cipher_name(s->s_io.ssl), SSL_get_cipher_bits(s->s_io.ssl, NULL)); } if (s->rcptcount == 1) fprintf(s->datafp, "\n\tfor <%s@%s>; ", s->s_msg.rcpt.user, s->s_msg.rcpt.domain); else fprintf(s->datafp, ";\n\t"); fprintf(s->datafp, "%s\n", time_to_text(time(NULL))); break; case S_DATACONTENT: if (ss->code != 250) s->s_dstatus |= DS_PERMFAILURE; session_read_data(s, ss->u.dataline); break; case S_DONE: session_respond(s, "250 2.0.0 %08x Message accepted for delivery", evpid_to_msgid(s->s_msg.id)); log_info("%08x: from=<%s%s%s>, size=%ld, nrcpts=%zu, proto=%s, " "relay=%s [%s]", evpid_to_msgid(s->s_msg.id), s->s_msg.sender.user, s->s_msg.sender.user[0] == '\0' ? "" : "@", s->s_msg.sender.domain, s->s_datalen, s->rcptcount, s->s_flags & F_EHLO ? "ESMTP" : "SMTP", s->s_hostname, ss_to_text(&s->s_ss)); session_enter_state(s, S_HELO); s->s_msg.id = 0; bzero(&s->s_nresp, sizeof(s->s_nresp)); break; default: fatal("session_pickup: unknown state"); } io_reload(&s->s_io); }
int enqueue(int argc, char *argv[]) { int i, ch, tflag = 0, noheader; char *fake_from = NULL, *buf; struct passwd *pw; FILE *fp, *fout; size_t len; char *line; int dotted; int inheaders = 0; bzero(&msg, sizeof(msg)); time(×tamp); while ((ch = getopt(argc, argv, "A:B:b:E::e:F:f:iJ::L:mo:p:qtvx")) != -1) { switch (ch) { case 'f': fake_from = optarg; break; case 'F': msg.fromname = optarg; break; case 't': tflag = 1; break; case 'v': verbose = 1; break; /* all remaining: ignored, sendmail compat */ case 'A': case 'B': case 'b': case 'E': case 'e': case 'i': case 'L': case 'm': case 'o': case 'p': case 'x': break; case 'q': /* XXX: implement "process all now" */ return (0); default: usage(); } } argc -= optind; argv += optind; if (gethostname(host, sizeof(host)) == -1) err(1, "gethostname"); if ((pw = getpwuid(getuid())) == NULL) user = "******"; if (pw != NULL && (user = strdup(pw->pw_name)) == NULL) err(1, "strdup"); build_from(fake_from, pw); while(argc > 0) { rcpt_add(argv[0]); argv++; argc--; } signal(SIGALRM, sighdlr); alarm(300); fp = tmpfile(); if (fp == NULL) err(1, "tmpfile"); noheader = parse_message(stdin, fake_from == NULL, tflag, fp); if (msg.rcpt_cnt == 0) errx(1, "no recipients"); /* init session */ rewind(fp); if ((msg.fd = open_connection()) == -1) errx(1, "server too busy"); fout = fdopen(msg.fd, "a+"); if (fout == NULL) err(1, "fdopen"); /* * We need to call get_responses after every command because we don't * support PIPELINING on the server-side yet. */ /* banner */ get_responses(fout, 1); fprintf(fout, "EHLO localhost\n"); get_responses(fout, 1); fprintf(fout, "MAIL FROM: <%s>\n", msg.from); get_responses(fout, 1); for (i = 0; i < msg.rcpt_cnt; i++) { fprintf(fout, "RCPT TO: <%s>\n", msg.rcpts[i]); get_responses(fout, 1); } fprintf(fout, "DATA\n"); get_responses(fout, 1); /* add From */ if (!msg.saw_from) fprintf(fout, "From: %s%s<%s>\n", msg.fromname ? msg.fromname : "", msg.fromname ? " " : "", msg.from); /* add Date */ if (!msg.saw_date) fprintf(fout, "Date: %s\n", time_to_text(timestamp)); /* add Message-Id */ if (!msg.saw_msgid) fprintf(fout, "Message-Id: <%"PRIu64".enqueue@%s>\n", generate_uid(), host); if (msg.need_linesplit) { /* we will always need to mime encode for long lines */ if (!msg.saw_mime_version) fprintf(fout, "MIME-Version: 1.0\n"); if (!msg.saw_content_type) fprintf(fout, "Content-Type: text/plain; charset=unknown-8bit\n"); if (!msg.saw_content_disposition) fprintf(fout, "Content-Disposition: inline\n"); if (!msg.saw_content_transfer_encoding) fprintf(fout, "Content-Transfer-Encoding: quoted-printable\n"); } if (!msg.saw_user_agent) fprintf(fout, "User-Agent: OpenSMTPD enqueuer (Demoosh)\n"); /* add separating newline */ if (noheader) fprintf(fout, "\n"); else inheaders = 1; for (;;) { buf = fgetln(fp, &len); if (buf == NULL && ferror(fp)) err(1, "fgetln"); if (buf == NULL && feof(fp)) break; /* newlines have been normalized on first parsing */ if (buf[len-1] != '\n') errx(1, "expect EOL"); dotted = 0; if (buf[0] == '.') { fputc('.', fout); dotted = 1; } line = buf; if (msg.saw_content_transfer_encoding || noheader || inheaders || !msg.need_linesplit) { fprintf(fout, "%.*s", (int)len, line); if (inheaders && buf[0] == '\n') inheaders = 0; continue; } /* we don't have a content transfer encoding, use our default */ do { if (len < LINESPLIT) { qp_encoded_write(fout, line, len); break; } else { qp_encoded_write(fout, line, LINESPLIT - 2 - dotted); fprintf(fout, "=\n"); line += LINESPLIT - 2 - dotted; len -= LINESPLIT - 2 - dotted; } } while (len); } fprintf(fout, ".\n"); get_responses(fout, 1); fprintf(fout, "QUIT\n"); get_responses(fout, 1); fclose(fp); fclose(fout); exit(0); }
int enqueue(int argc, char *argv[]) { int i, ch, tflag = 0, noheader; char *fake_from = NULL, *buf; struct passwd *pw; FILE *fp, *fout; size_t len, envid_sz = 0; int fd; char sfn[] = "/tmp/smtpd.XXXXXXXXXX"; char *line; int dotted; int inheaders = 0; int save_argc; char **save_argv; memset(&msg, 0, sizeof(msg)); time(×tamp); save_argc = argc; save_argv = argv; while ((ch = getopt(argc, argv, "A:B:b:E::e:F:f:iJ::L:mN:o:p:qR:tvV:x")) != -1) { switch (ch) { case 'f': fake_from = optarg; break; case 'F': msg.fromname = optarg; break; case 'N': msg.dsn_notify = optarg; break; case 'R': msg.dsn_ret = optarg; break; case 't': tflag = 1; break; case 'v': verbose = 1; break; case 'V': msg.dsn_envid = optarg; break; /* all remaining: ignored, sendmail compat */ case 'A': case 'B': case 'b': case 'E': case 'e': case 'i': case 'L': case 'm': case 'o': case 'p': case 'x': break; case 'q': /* XXX: implement "process all now" */ return (EX_SOFTWARE); default: usage(); } } argc -= optind; argv += optind; if (getmailname(host, sizeof(host)) == -1) err(EX_NOHOST, "getmailname"); if ((user = getlogin()) != NULL && *user != '\0') pw = getpwnam(user); else if ((pw = getpwuid(getuid())) == NULL) user = "******"; user = xstrdup(pw ? pw->pw_name : user, "enqueue"); build_from(fake_from, pw); while (argc > 0) { rcpt_add(argv[0]); argv++; argc--; } if ((fd = mkstemp(sfn)) == -1 || (fp = fdopen(fd, "w+")) == NULL) { int saved_errno = errno; if (fd != -1) { unlink(sfn); close(fd); } errc(EX_UNAVAILABLE, saved_errno, "mkstemp"); } unlink(sfn); noheader = parse_message(stdin, fake_from == NULL, tflag, fp); if (msg.rcpt_cnt == 0) errx(EX_SOFTWARE, "no recipients"); /* init session */ rewind(fp); /* try to connect */ /* If the server is not running, enqueue the message offline */ if (!srv_connect()) return (enqueue_offline(save_argc, save_argv, fp)); if ((msg.fd = open_connection()) == -1) errx(EX_UNAVAILABLE, "server too busy"); fout = fdopen(msg.fd, "a+"); if (fout == NULL) err(EX_UNAVAILABLE, "fdopen"); /* * We need to call get_responses after every command because we don't * support PIPELINING on the server-side yet. */ /* banner */ get_responses(fout, 1); send_line(fout, verbose, "EHLO localhost\n"); get_responses(fout, 1); if (msg.dsn_envid != NULL) envid_sz = strlen(msg.dsn_envid); send_line(fout, verbose, "MAIL FROM:<%s> %s%s %s%s\n", msg.from, msg.dsn_ret ? "RET=" : "", msg.dsn_ret ? msg.dsn_ret : "", envid_sz ? "ENVID=" : "", envid_sz ? msg.dsn_envid : ""); get_responses(fout, 1); for (i = 0; i < msg.rcpt_cnt; i++) { send_line(fout, verbose, "RCPT TO:<%s> %s%s\n", msg.rcpts[i], msg.dsn_notify ? "NOTIFY=" : "", msg.dsn_notify ? msg.dsn_notify : ""); get_responses(fout, 1); } send_line(fout, verbose, "DATA\n"); get_responses(fout, 1); /* add From */ if (!msg.saw_from) send_line(fout, 0, "From: %s%s<%s>\n", msg.fromname ? msg.fromname : "", msg.fromname ? " " : "", msg.from); /* add Date */ if (!msg.saw_date) send_line(fout, 0, "Date: %s\n", time_to_text(timestamp)); /* add Message-Id */ if (!msg.saw_msgid) send_line(fout, 0, "Message-Id: <%"PRIu64".enqueue@%s>\n", generate_uid(), host); if (msg.need_linesplit) { /* we will always need to mime encode for long lines */ if (!msg.saw_mime_version) send_line(fout, 0, "MIME-Version: 1.0\n"); if (!msg.saw_content_type) send_line(fout, 0, "Content-Type: text/plain; " "charset=unknown-8bit\n"); if (!msg.saw_content_disposition) send_line(fout, 0, "Content-Disposition: inline\n"); if (!msg.saw_content_transfer_encoding) send_line(fout, 0, "Content-Transfer-Encoding: " "quoted-printable\n"); } /* add separating newline */ if (noheader) send_line(fout, 0, "\n"); else inheaders = 1; for (;;) { buf = fgetln(fp, &len); if (buf == NULL && ferror(fp)) err(EX_UNAVAILABLE, "fgetln"); if (buf == NULL && feof(fp)) break; /* newlines have been normalized on first parsing */ if (buf[len-1] != '\n') errx(EX_SOFTWARE, "expect EOL"); dotted = 0; if (buf[0] == '.') { fputc('.', fout); dotted = 1; } line = buf; if (msg.saw_content_transfer_encoding || noheader || inheaders || !msg.need_linesplit) { if (inheaders) send_header(fout, line, len); else send_line(fout, 0, "%.*s", (int)len, line); if (inheaders && buf[0] == '\n') inheaders = 0; continue; } /* we don't have a content transfer encoding, use our default */ do { if (len < LINESPLIT) { qp_encoded_write(fout, line, len); break; } else { qp_encoded_write(fout, line, LINESPLIT - 2 - dotted); send_line(fout, 0, "=\n"); line += LINESPLIT - 2 - dotted; len -= LINESPLIT - 2 - dotted; } } while (len); } send_line(fout, verbose, ".\n"); get_responses(fout, 1); send_line(fout, verbose, "QUIT\n"); get_responses(fout, 1); fclose(fp); fclose(fout); exit(EX_OK); }