smtp.c
上传用户:skhuanbao
上传日期:2007-01-04
资源大小:43k
文件大小:17k
- /*
- File: smtp/smtp.c
-
- Copyright (C) 1999 by Wolfgang Zekoll <wzk@quietsche-entchen.de>
- This source is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 1, or (at your option)
- any later version.
- This source is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <sys/fcntl.h>
- #include <sys/socket.h>
- #include <signal.h>
- #include <netdb.h>
- #include <netinet/in.h>
- #include <syslog.h>
- #include <sys/time.h>
- #include "smtp.h"
- #include "ip-lib.h"
- #include "lib.h"
- int sdebug(char *line)
- {
- if (debug != 0) {
- noctrl(line);
- fprintf (stderr, "*** %sn", line);
- }
- return (0);
- }
- char *getline(char *line, int size, FILE *fp, int debug)
- {
- *line = 0;
- if (fgets(line, size, fp) == NULL) {
- syslog(LOG_NOTICE, "sendmail closed read pipe.");
- return (NULL);
- }
- noctrl(line);
- if (debug != 0)
- sdebug(line);
- return (line);
- }
- int getresp(char *line, int size, FILE *fp, int debug)
- {
- if (getline(line, size, fp, debug) == NULL)
- return (-1);
- return (atoi(line));
- }
- int echoline(FILE *fp, char *line, int rc)
- {
- if (rc == -1) {
- syslog(LOG_NOTICE, "closing connection with 421");
- fprintf (fp, "421 service unavailablern");
- }
- else {
- noctrl(line);
- fprintf (fp, "%srn", line);
- }
- fflush(fp);
- return (0);
- }
- int putcmd(FILE *fp, char *cmd, char *par, char *dbg)
- {
- fprintf (fp, "%s", cmd);
- if (par != NULL && *par != 0)
- fprintf (fp, " %s", par);
- fprintf (fp, "n");
- fflush(fp);
-
- if (debug != 0 && dbg != NULL && *dbg != 0)
- fprintf (stderr, ">>> %s: %s %sn", dbg, cmd, par);
- return (0);
- }
- int reset_connection(smtp_t *x)
- {
- x->nrcpt = 0;
- x->state = WAITING;
- *x->sender = 0;
- x->nrcpt = 0;
- *x->jobid = 0;
- *x->msgid = 0;
- x->size = 0;
- return (0);
- }
- void sigalrm_handler(int sig)
- {
- syslog(LOG_NOTICE, "client timed out");
- fclose (stdin);
- return;
- }
- char *get_clientinput(char *line, int size, int timeout)
- {
- char *p;
-
- alarm(timeout);
- signal(SIGALRM, sigalrm_handler);
-
- *line = 0;
- p = fgets(line, size, stdin);
- noctrl(line);
-
- return (p);
- }
- char *get_emailadr(char *envelope, char *email, int size)
- {
- char *p, *r;
- *email = 0;
- /*
- * Sonderfall: Es ist moeglich, dass die envelope Adresse `<>'
- * ist. In diesem liefern wir sie unveraendert zurueck. Das
- * beisst sich nicht mit Adressen der Form `<<>>', da geschachtelte
- * spitze Klammern nicht unterstuetzt werden.
- */
- if (strcmp(envelope, "<>") == 0) {
- strcpy(email, "<>");
- return (email);
- }
- p = envelope;
- if (*p != '<')
- return (email);
-
- p++;
- if ((r = strchr(p, '>')) == NULL)
- return (email);
- else if (r[1] != 0)
- return (email);
- get_quoted(&p, '>', email, size);
- return (email);
- }
- int check_emailadr(char *emailadr)
- {
- char *domain;
- if (strcmp(emailadr, "<>") == 0)
- return (1);
- else if (*emailadr == 0)
- return (0);
- /* else if (*emailadr == '@')
- * return (0);
- */
- else if ((domain = strchr(emailadr, '@')) == NULL)
- return (0);
- /* else if (domain[1] == '0')
- * return (0);
- */
- else if (strchr(&domain[1], '@') != NULL)
- return (0);
- else if (strchr(emailadr, '!') != NULL || strchr(emailadr, '%') != NULL)
- return (0);
- else if (strchr(emailadr, ':') != NULL || strchr(emailadr, ',') != NULL)
- return (0);
-
- return (1);
- }
- int search_allowlist(char *emailadr, char *list)
- {
- char *p, *domain, pattern[200];
- if (strcmp(emailadr, "<>") == 0)
- return (1);
- domain = strchr(emailadr, '@');
- if (domain == NULL || *domain == 0) {
- /*
- * Das kann eigentlich nicht passieren, die E-Mail Adresse
- * wurde schon auf genau ein @-Zeichen getestet.
- */
- return (0);
- }
- else if (list == NULL || *list == 0) {
-
- /*
- * Kann eigentlich auch nicht vorkommen.
- */
- return (0);
- }
- p = list;
- while ((p = skip_ws(p)), *get_quoted(&p, ',', pattern, sizeof(pattern)) != 0) {
- noctrl(pattern);
- if (*pattern == '@' && strpcmp(domain, pattern) == 0)
- return (1);
- else if (strpcmp(emailadr, pattern) == 0)
- return (1);
- }
-
- return (0);
- }
- int receive_data(smtp_t *x)
- {
- int lineno, isheader, bytes;
- char line[2048];
- lineno = 0;
- isheader = 1;
- while (1) {
- if (get_clientinput(line, sizeof(line), x->config->timeout) == NULL) {
- syslog(LOG_NOTICE, "client terminated while sending data");
- return (-1);
- }
- lineno++;
- noctrl(line);
- /* Received Zeile einbauen
- */
- if (lineno == 1) {
- if (x->config->droppath != 2) {
- fprintf (x->sout, "Received: from %s (%s) by %srn",
- x->client, x->ipnum, x->hostname);
- }
- }
- /*
- * Ggf. Message-ID aus Mail-Header lesen, bzw. Mail-Path
- * aus dem Header loeschen.
- */
- if (isheader == 1) {
- if (*line == 0) {
- isheader = 0;
- if (*x->msgid == 0) {
- unsigned long now;
- now = time(NULL);
- snprintf (x->msgid, sizeof(x->msgid) - 2, "%lu.%d.%d", now, getpid(), x->mailcount);
- syslog(LOG_NOTICE, "no message id, assuming %s, client= %s", x->msgid, x->client);
- }
- }
- else {
- char *p, word[80];
- p = line;
- get_word(&p, word, sizeof(word));
- strlwr(word);
-
- if (strcmp(word, "message-id:") == 0) {
- if (*x->msgid != 0) {
- syslog(LOG_NOTICE, "duplicate message id, client= %s", x->client);
- continue;
- }
- else {
- if ((p = strchr(p, '<')) != NULL) {
- p++;
- get_quoted(&p, '>', x->msgid, sizeof(x->msgid));
- }
- }
- }
- else if (strcmp(word, "received:") == 0) {
- if (x->config->droppath != 0)
- continue;
- }
- }
- }
- /* Zeile an Sendmail weitergeben
- */
- if ((bytes = fprintf(x->sout, "%srn", line)) != strlen(line) + 2) {
- syslog(LOG_NOTICE, "server terminated while receiving data");
- return (-1);
- }
- x->size += bytes;
- /* Ende der Mail erreicht?
- */
- if (line[0] == '.' && line[1] == 0) {
- fflush(x->sout);
- break;
- }
- }
-
- return (0);
- }
- int proxy_request(config_t *config)
- {
- int rc;
- char *p, command[10], word[200], line[2048];
- smtp_t *x;
- x = allocate(sizeof(smtp_t));
- get_client_info(0, x->ipnum, x->client);
- x->mailcount = 0;
- syslog(LOG_NOTICE, "connected to client: %s", x->ipnum);
- if (*config->clientdir != 0) {
- char logfile[200];
- unsigned long now;
- struct stat sbuf;
- now = time(NULL);
- snprintf (logfile, sizeof(logfile) - 2, "%s/%s", config->clientdir, x->ipnum);
- if (stat(logfile, &sbuf) != 0 || (now - sbuf.st_mtime) >= config->accepttime) {
- printf ("421 service unavailable - authenticate with POP3 firstrn");
- syslog(LOG_NOTICE, "client not permitted: %s", x->ipnum);
- goto end;
- }
- }
- if (*config->server != 0) {
- unsigned int port;
- char server[200];
- copy_string(server, config->server, sizeof(server));
- port = get_port(server, 25);
-
- if ((x->sin = ip_open(server, port)) == NULL) {
- printf ("451 Service unavailablern");
- syslog(LOG_NOTICE, "can't connect to server: %s:%u, %m", server, port);
- exit (1);
- }
-
- x->sout = x->sin;
- p = config->server;
- }
- else if (config->argv != NULL) {
- int pid, pin[2], pout[2];
- if (pipe(pin) != 0 || pipe(pout) != 0) {
- printf ("451 Service unavailablern");
- syslog(LOG_NOTICE, "can't pipe(): %m");
- exit (-1);
- }
- else if ((pid = fork()) < 0) {
- printf ("451 Service unavailablern");
- syslog(LOG_NOTICE, "can't fork(): %m");
- exit (-1);
- }
- else if (pid == 0) {
- dup2(pin[1], 1);
- close (pin[0]);
-
- dup2(pout[0], 0);
- close(pout[1]);
-
- close (2);
- execvp(config->argv[0], config->argv);
- printf ("451 Service unavailablern");
- syslog(LOG_NOTICE, "can't execute: %s: %m", config->argv[0]);
- exit (1);
- }
- else {
- x->sin = fdopen(pin[0], "r");
- close(pin[1]);
-
- x->sout = fdopen(pout[1], "w");
- close(pout[0]);
- p = config->argv[0];
- }
- }
- else {
- printf ("451 Service unavailablern");
- syslog(LOG_NOTICE, "no server specified");
- exit (1);
- }
- syslog(LOG_NOTICE, "connected to server: %s", p);
- /* Konfiguration uebernehmen
- */
- x->config = config;
- /* Hostnamen holen
- */
- gethostname(word, sizeof(word));
- getdomainname(line, sizeof(line));
- snprintf (x->hostname, sizeof(x->hostname) - 2, word, line);
-
- /* Greeting Message vom Sendmail Server lesen, und an
- * Client schicken.
- */
- rc = getresp(line, sizeof(line), x->sin, debug);
- while (line[3] != ' ') {
- rc = getresp(line, sizeof(line), x->sin, debug);
- if (rc == -1) {
- syslog(LOG_NOTICE, "lost server while reading server greeting");
- exit (1);
- }
- }
-
- echoline(stdout, line, rc);
- /* Wir stellen uns beim lokalen Sendmail Server vor. Die
- * EHLO-replys werden 'verschluckt' und durch einen eigenen
- * ersetzt.
- */
- putcmd(x->sout, "EHLO", "localhost", "SVR");
- while ((rc = getresp(line, sizeof(line), x->sin, debug)) != -1) {
- if (line[3] == ' ')
- break;
- }
- rc = atol(line);
- if (rc != 250) {
- syslog(LOG_NOTICE, "server HELO: status is not 250");
- echoline(stdout, "421 service unavailable", 0);
- return (-1);
- }
-
- /*
- * ** S M T P M A I N L O O P
- */
- x->state = WAITING;
- while (1) {
- rc = 0; /* Server response code loeschen */
- /* Naechstes Kommando vom Client holen
- */
- fflush(stdout);
- if (get_clientinput(line, sizeof(line), x->config->timeout) == NULL) {
- syslog(LOG_NOTICE, "client closed connection");
- break;
- }
- /* Kommando isolieren
- */
- p = noctrl(line);
- get_word(&p, command, sizeof(command));
- strupr(command);
- p = skip_ws(p);
- /* QUIT ist immer moeglich.
- */
- if (strcmp(command, "QUIT") == 0) {
- putcmd(x->sout, "QUIT", "", "SVR");
- rc = getresp(line, sizeof(line), x->sin, debug);
- echoline(stdout, line, rc);
- x->state = SEND_QUIT;
- break;
- }
- /* HELP
- */
- else if (strcmp(command, "HELP") == 0) {
- echoline(stdout, "503 no help available", 0);
- }
- /* NOOP
- */
- else if (strcmp(command, "NOOP") == 0) {
- putcmd(x->sout, "NOOP", "", "SVR");
- rc = getresp(line, sizeof(line), x->sin, debug);
- echoline(stdout, line, rc);
- }
- /* RSET
- */
- else if (strcmp(command, "RSET") == 0) {
- putcmd(x->sout, "RSET", "", "SVR");
- rc = getresp(line, sizeof(line), x->sin, debug);
- echoline(stdout, line, rc);
- reset_connection(x);
- syslog(LOG_NOTICE, "RSET command, client= %s", x->client);
- }
- /* ETRN
- */
- else if (strcmp(command, "ETRN") == 0) {
- if (x->config->etrn == 0) {
- echoline(stdout, "500 unrecognized command", 0);
- syslog(LOG_NOTICE, "ETRN request rejected: client= %s", x->client);
- }
- else {
- if (*get_word(&p, word, sizeof(word)) == 0)
- echoline(stdout, "500 ETRN needs parameter", 0);
- else {
- putcmd(x->sout, "ETRN", word, "SVR");
- rc = getresp(line, sizeof(line), x->sin, debug);
- echoline(stdout, line, rc);
- if (rc != 250)
- syslog(LOG_NOTICE, "ETRN rejected by server, client= %s", x->client);
- }
- }
- }
- /* HELO und EHLO sind auch immer verfuegbar, aber nur
- * einmal.
- */
- else if (strcmp(command, "HELO") == 0 || strcmp(command, "EHLO") == 0) {
- if (x->helloseen != 0)
- echoline(stdout, "503 duplicate HELO/EHLO", 0);
- else if (*get_word(&p, word, sizeof(word)) == 0) {
- snprintf (line, sizeof(line) - 2, "501 %s requires domain name", command);
- echoline(stdout, line, 0);
- }
- else {
- if (strcmp(command, "HELO") == 0) {
- snprintf (line, sizeof(line) - 2, "250 SMTP server v%s ready %s [%s]", VERSION, x->client, x->ipnum);
- echoline(stdout, line, 0);
- }
- else {
- snprintf (line, sizeof(line) - 2, "250-SMTP server v%s ready %s [%s]", VERSION, x->client, x->ipnum);
- echoline(stdout, line, 0);
-
- echoline(stdout, "250-8BITMIME", 0);
- if (x->config->etrn != 0)
- echoline(stdout, "250-ETRN", 0);
- echoline(stdout, "250 HELP", 0);
- }
-
- x->helloseen = 1;
- }
- }
- /* MAIL, SEND, SOML, SAML
- *
- * Laut RFC 821 kann das MAIL Kommando jederzeit abgesetzt
- * werden, es macht dabei einen impliziten SMTP-Reset. Der
- * real existierende Sendmail will davon aber nichts wissen.
- */
- else if (strcmp(command, "MAIL") == 0 || strcmp(command, "SEND") == 0 ||
- strcmp(command, "SOML") == 0 || strcmp(command, "SAML") == 0) {
-
- get_quoted(&p, ':', word, sizeof(word));
- if (strcasecmp(word, "FROM") != 0)
- echoline(stdout, "500 syntax error", 0);
- else if (*x->sender != 0)
- echoline(stdout, "503 sender already specified", 0);
- else {
- int allowed;
- char sender[200], emailadr[200];
-
- p = skip_ws(p);
- get_word(&p, sender, sizeof(sender));
- strlwr(sender);
- /*
- * Wir machen ein paar grundsaetzliche Tests mit
- * der Absenderadresse:
- *
- * - Ist die Adresse von spitzen Klammern
- * umgeben?
- * ...
- */
- allowed = 1;
- get_emailadr(sender, emailadr, sizeof(emailadr));
- if (*emailadr == 0)
- allowed = 0;
- /*
- * ...
- * - Enthaelt die Adresse mindestens ein @-Zeichen?
- * - Enthaelt die Adresse genau ein @-Zeichen?
- * - Ist in der Adresse kein !- und kein %-Zeichen
- * enthalten.
- * ...
- */
- else if (check_emailadr(emailadr) == 0)
- allowed = 0;
- /*
- * ...
- * - Schliesslich wird ggf. noch getestet,
- * ob die Absenderadresse auch auf der
- * allow-Liste steht.
- *
- * Mit den Empfaengeradressen werden die gleichen Tests
- * durchgefuehrt.
- */
- else if ((p = x->config->senderlist) == NULL || *p == 0)
- allowed = 1; /* kein Adresstest */
- else
- allowed = search_allowlist(emailadr, x->config->senderlist);
- if (allowed == 0) {
- char line[300];
- snprintf (line, sizeof(line) - 2, "550 not allowed: %s", sender);
- echoline(stdout, line, 0);
- syslog(LOG_NOTICE, "sender rejected: %s, client= %s", sender, x->client);
- }
- else {
- snprintf (line, sizeof(line) - 2, "%s FROM: %s", command, sender);
- putcmd(x->sout, line, "", "SVR");
- rc = getresp(line, sizeof(line), x->sin, debug);
- echoline(stdout, line, rc);
- if (rc == 250) {
- copy_string(x->sender, sender, sizeof(sender));
- x->state = MAIL_SEEN;
- }
- }
- }
- }
- /* RCPT
- */
- else if (strcmp(command, "RCPT") == 0) {
- get_quoted(&p, ':', word, sizeof(word));
- if (strcasecmp(word, "TO") != 0)
- echoline(stdout, "500 syntax error", 0);
- else if (x->state != MAIL_SEEN && x->state != RCPT_SEEN)
- echoline(stdout, "503 specify sender first", 0);
- else {
- int allowed;
- char rcpt[200], emailadr[200];
-
- p = skip_ws(p);
- get_word(&p, rcpt, sizeof(rcpt));
- strlwr(rcpt);
- get_emailadr(rcpt, emailadr, sizeof(emailadr));
- if (*emailadr == 0)
- allowed = 0;
- else if (check_emailadr(emailadr) == 0)
- allowed = 0;
- else if ((p = x->config->rcptlist) == NULL || *p == 0)
- allowed = 1;
- else
- allowed = search_allowlist(emailadr, x->config->rcptlist);
- if (allowed == 0) {
- char line[300];
- snprintf (line, sizeof(line) - 2, "550 no such user: %s", rcpt);
- echoline(stdout, line, 0);
- syslog(LOG_NOTICE, "recipient rejected: %s, client= %s", rcpt, x->client);
- }
- else {
- putcmd(x->sout, "RCPT TO:", rcpt, "SVR");
- rc = getresp(line, sizeof(line), x->sin, debug);
- echoline(stdout, line, rc);
- if (rc == 250 || rc == 251) {
- x->nrcpt++;
- x->state = RCPT_SEEN;
- }
- }
- }
- }
- /* DATA
- */
- else if (strcmp(command, "DATA") == 0) {
- x->mailcount++;
- if (x->state != RCPT_SEEN)
- echoline(stdout, "503 specify receipients first", 0);
- else {
- putcmd(x->sout, "DATA", "", "SVR");
- rc = getresp(line, sizeof(line), x->sin, debug);
- echoline(stdout, line, rc);
- if (rc == 354) {
- if ((rc = receive_data(x)) == 0) {
- rc = getresp(line, sizeof(line), x->sin, debug);
- echoline(stdout, line, rc);
- if (rc == 250) {
- p = line;
- get_word(&p, word, sizeof(word));
- get_word(&p, x->jobid, sizeof(x->jobid));
- }
- }
- }
- snprintf (line, sizeof(line) - 2, "client= %s, sender= %s, nrcpt= %d, size= %ld, jobid= <%s>, message-id= <%s>, status= %d",
- x->client, x->sender, x->nrcpt, x->size,
- x->jobid, x->msgid, rc);
- syslog(LOG_NOTICE, line);
- reset_connection(x);
- x->state = WAITING;
- }
- }
- /* Alles andere ist unserem Server unbekannt.
- */
- else {
- fprintf (stderr, "500 unrecognized commandrn");
- }
- if (rc == 421) {
- syslog(LOG_NOTICE, "sendmail returned 421, state= %d, command= %s", x->state, command);
- break;
- }
- else if (rc == -1) {
- syslog(LOG_NOTICE, "terminating (sendmail terminated)");
- x->state = NO_SENDMAIL;
- break;
- }
- }
- if (x->state != SEND_QUIT && x->state != NO_SENDMAIL) {
- putcmd(x->sout, "QUIT", "", "SVR");
- rc = getresp(line, sizeof(line), x->sin, debug);
- }
- end:
- syslog(LOG_NOTICE, "client %s disconnecting, %d mails", x->client, x->mailcount);
- return (0);
- }