int main(int argc, char *argv[]) { struct sockaddr_un sun; struct parse_result *res = NULL; struct imsg imsg; struct smtpd smtpd; int ctl_sock; int done = 0; int n, verbose = 0; /* parse options */ if (strcmp(__progname, "sendmail") == 0 || strcmp(__progname, "send-mail") == 0) sendmail = 1; else if (strcmp(__progname, "mailq") == 0) { if (geteuid()) errx(1, "need root privileges"); setup_env(&smtpd); show_queue(0); return 0; } else if (strcmp(__progname, "smtpctl") == 0) { /* check for root privileges */ if (geteuid()) errx(1, "need root privileges"); setup_env(&smtpd); if ((res = parse(argc - 1, argv + 1)) == NULL) exit(1); /* handle "disconnected" commands */ switch (res->action) { case SHOW_QUEUE: show_queue(0); break; case SHOW_RUNQUEUE: break; default: goto connected; } return 0; } else errx(1, "unsupported mode"); connected: /* connect to smtpd control socket */ if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) err(1, "socket"); bzero(&sun, sizeof(sun)); sun.sun_family = AF_UNIX; strlcpy(sun.sun_path, SMTPD_SOCKET, sizeof(sun.sun_path)); if (connect(ctl_sock, (struct sockaddr *)&sun, sizeof(sun)) == -1) { if (sendmail) return enqueue_offline(argc, argv); err(1, "connect: %s", SMTPD_SOCKET); } if ((ibuf = calloc(1, sizeof(struct imsgbuf))) == NULL) err(1, NULL); imsg_init(ibuf, ctl_sock); if (sendmail) return enqueue(argc, argv); /* process user request */ switch (res->action) { case NONE: usage(); /* not reached */ case SCHEDULE: case REMOVE: { u_int64_t ulval; char *ep; errno = 0; ulval = strtoull(res->data, &ep, 16); if (res->data[0] == '\0' || *ep != '\0') errx(1, "invalid msgid/evpid"); if (errno == ERANGE && ulval == ULLONG_MAX) errx(1, "invalid msgid/evpid"); if (ulval == 0) errx(1, "invalid msgid/evpid"); if (res->action == SCHEDULE) imsg_compose(ibuf, IMSG_SCHEDULER_SCHEDULE, 0, 0, -1, &ulval, sizeof(ulval)); if (res->action == REMOVE) imsg_compose(ibuf, IMSG_SCHEDULER_REMOVE, 0, 0, -1, &ulval, sizeof(ulval)); break; } case SCHEDULE_ALL: { u_int64_t ulval = 0; imsg_compose(ibuf, IMSG_SCHEDULER_SCHEDULE, 0, 0, -1, &ulval, sizeof(ulval)); break; } case SHUTDOWN: imsg_compose(ibuf, IMSG_CTL_SHUTDOWN, 0, 0, -1, NULL, 0); break; case PAUSE_MDA: imsg_compose(ibuf, IMSG_QUEUE_PAUSE_MDA, 0, 0, -1, NULL, 0); break; case PAUSE_MTA: imsg_compose(ibuf, IMSG_QUEUE_PAUSE_MTA, 0, 0, -1, NULL, 0); break; case PAUSE_SMTP: imsg_compose(ibuf, IMSG_SMTP_PAUSE, 0, 0, -1, NULL, 0); break; case RESUME_MDA: imsg_compose(ibuf, IMSG_QUEUE_RESUME_MDA, 0, 0, -1, NULL, 0); break; case RESUME_MTA: imsg_compose(ibuf, IMSG_QUEUE_RESUME_MTA, 0, 0, -1, NULL, 0); break; case RESUME_SMTP: imsg_compose(ibuf, IMSG_SMTP_RESUME, 0, 0, -1, NULL, 0); break; case SHOW_STATS: imsg_compose(ibuf, IMSG_STATS, 0, 0, -1, NULL, 0); break; case MONITOR: /* XXX */ break; case LOG_VERBOSE: verbose = 1; /* FALLTHROUGH */ case LOG_BRIEF: imsg_compose(ibuf, IMSG_CTL_VERBOSE, 0, 0, -1, &verbose, sizeof(verbose)); printf("logging request sent.\n"); done = 1; break; default: errx(1, "unknown request (%d)", res->action); } while (ibuf->w.queued) if (msgbuf_write(&ibuf->w) < 0) err(1, "write error"); while (!done) { if ((n = imsg_read(ibuf)) == -1) errx(1, "imsg_read error"); if (n == 0) errx(1, "pipe closed"); while (!done) { if ((n = imsg_get(ibuf, &imsg)) == -1) errx(1, "imsg_get error"); if (n == 0) break; switch(res->action) { case REMOVE: case SCHEDULE: case SCHEDULE_ALL: case SHUTDOWN: case PAUSE_MDA: case PAUSE_MTA: case PAUSE_SMTP: case RESUME_MDA: case RESUME_MTA: case RESUME_SMTP: case LOG_VERBOSE: case LOG_BRIEF: done = show_command_output(&imsg); break; case SHOW_STATS: done = show_stats_output(&imsg); break; case NONE: break; case MONITOR: break; default: err(1, "unexpected reply (%d)", res->action); } /* insert imsg replies switch here */ imsg_free(&imsg); } } close(ctl_sock); free(ibuf); return (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); }