smtp.c
上传用户:skhuanbao
上传日期:2007-01-04
资源大小:43k
文件大小:17k
源码类别:

代理服务器

开发平台:

Unix_Linux

  1. /*
  2.     File: smtp/smtp.c
  3.     
  4.     Copyright (C) 1999 by Wolfgang Zekoll <wzk@quietsche-entchen.de>
  5.     This source 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 1, or (at your option)
  8.     any later version.
  9.     This source is distributed in the hope that it will be useful,
  10.     but WITHOUT ANY WARRANTY; without even the implied warranty of
  11.     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12.     GNU General Public License for more details.
  13.     You should have received a copy of the GNU General Public License
  14.     along with this program; if not, write to the Free Software
  15.     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  16. */
  17. #include <stdlib.h>
  18. #include <stdio.h>
  19. #include <string.h>
  20. #include <unistd.h>
  21. #include <sys/types.h>
  22. #include <sys/stat.h>
  23. #include <sys/fcntl.h>
  24. #include <sys/socket.h>
  25. #include <signal.h>
  26. #include <netdb.h>
  27. #include <netinet/in.h>
  28. #include <syslog.h>
  29. #include <sys/time.h>
  30. #include "smtp.h"
  31. #include "ip-lib.h"
  32. #include "lib.h"
  33. int sdebug(char *line)
  34. {
  35. if (debug != 0) {
  36. noctrl(line);
  37. fprintf (stderr, "*** %sn", line);
  38. }
  39. return (0);
  40. }
  41. char *getline(char *line, int size, FILE *fp, int debug)
  42. {
  43. *line = 0;
  44. if (fgets(line, size, fp) == NULL) {
  45. syslog(LOG_NOTICE, "sendmail closed read pipe.");
  46. return (NULL);
  47. }
  48. noctrl(line);
  49. if (debug != 0)
  50. sdebug(line);
  51. return (line);
  52. }
  53. int getresp(char *line, int size, FILE *fp, int debug)
  54. {
  55. if (getline(line, size, fp, debug) == NULL)
  56. return (-1);
  57. return (atoi(line));
  58. }
  59. int echoline(FILE *fp, char *line, int rc)
  60. {
  61. if (rc == -1) {
  62. syslog(LOG_NOTICE, "closing connection with 421");
  63. fprintf (fp, "421 service unavailablern");
  64. }
  65. else {
  66. noctrl(line);
  67. fprintf (fp, "%srn", line);
  68. }
  69. fflush(fp);
  70. return (0);
  71. }
  72. int putcmd(FILE *fp, char *cmd, char *par, char *dbg)
  73. {
  74. fprintf (fp, "%s", cmd);
  75. if (par != NULL  &&  *par != 0)
  76. fprintf (fp, " %s", par);
  77. fprintf (fp, "n");
  78. fflush(fp);
  79. if (debug != 0  &&  dbg != NULL  &&  *dbg != 0)
  80. fprintf (stderr, ">>> %s: %s %sn", dbg, cmd, par);
  81. return (0);
  82. }
  83. int reset_connection(smtp_t *x)
  84. {
  85. x->nrcpt    = 0;
  86. x->state    = WAITING;
  87. *x->sender  = 0;
  88. x->nrcpt    = 0;
  89. *x->jobid   = 0;
  90. *x->msgid   = 0;
  91. x->size     = 0;
  92. return (0);
  93. }
  94. void sigalrm_handler(int sig)
  95. {
  96. syslog(LOG_NOTICE, "client timed out");
  97. fclose (stdin);
  98. return;
  99. }
  100. char *get_clientinput(char *line, int size, int timeout)
  101. {
  102. char *p;
  103. alarm(timeout);
  104. signal(SIGALRM, sigalrm_handler);
  105. *line = 0;
  106. p = fgets(line, size, stdin);
  107. noctrl(line);
  108. return (p);
  109. }
  110. char *get_emailadr(char *envelope, char *email, int size)
  111. {
  112. char *p, *r;
  113. *email = 0;
  114. /*
  115.  * Sonderfall: Es ist moeglich, dass die envelope Adresse `<>'
  116.  * ist.  In diesem liefern wir sie unveraendert zurueck.  Das
  117.  * beisst sich nicht mit Adressen der Form `<<>>', da geschachtelte
  118.  * spitze Klammern nicht unterstuetzt werden.
  119.  */
  120. if (strcmp(envelope, "<>") == 0) {
  121. strcpy(email, "<>");
  122. return (email);
  123. }
  124. p = envelope;
  125. if (*p != '<')
  126. return (email);
  127. p++;
  128. if ((r = strchr(p, '>')) == NULL)
  129. return (email);
  130. else if (r[1] != 0)
  131. return (email);
  132. get_quoted(&p, '>', email, size);
  133. return (email);
  134. }
  135. int check_emailadr(char *emailadr)
  136. {
  137. char *domain;
  138. if (strcmp(emailadr, "<>") == 0)
  139. return (1);
  140. else if (*emailadr == 0)
  141. return (0);
  142. /* else if (*emailadr == '@')
  143.  * return (0);
  144.  */
  145. else if ((domain = strchr(emailadr, '@')) == NULL)
  146. return (0);
  147. /* else if (domain[1] == '0')
  148.  * return (0);
  149.  */
  150. else if (strchr(&domain[1], '@') != NULL)
  151. return (0);
  152. else if (strchr(emailadr, '!') != NULL  ||  strchr(emailadr, '%') != NULL)
  153. return (0);
  154. else if (strchr(emailadr, ':') != NULL  ||  strchr(emailadr, ',') != NULL)
  155. return (0);
  156. return (1);
  157. }
  158. int search_allowlist(char *emailadr, char *list)
  159. {
  160. char *p, *domain, pattern[200];
  161. if (strcmp(emailadr, "<>") == 0)
  162. return (1);
  163. domain = strchr(emailadr, '@');
  164. if (domain == NULL  ||  *domain == 0) {
  165. /*
  166.  * Das kann eigentlich nicht passieren, die E-Mail Adresse
  167.  * wurde schon auf genau ein @-Zeichen getestet.
  168.  */
  169. return (0);
  170. }
  171. else if (list == NULL  ||  *list == 0) {
  172. /*
  173.  * Kann eigentlich auch nicht vorkommen.
  174.  */
  175. return (0);
  176. }
  177. p = list;
  178. while ((p = skip_ws(p)), *get_quoted(&p, ',', pattern, sizeof(pattern)) != 0) {
  179. noctrl(pattern);
  180. if (*pattern == '@'  &&  strpcmp(domain, pattern) == 0)
  181. return (1);
  182. else if (strpcmp(emailadr, pattern) == 0)
  183. return (1);
  184. }
  185. return (0);
  186. }
  187. int receive_data(smtp_t *x)
  188. {
  189. int lineno, isheader, bytes;
  190. char line[2048];
  191. lineno   = 0;
  192. isheader = 1;
  193. while (1) {
  194. if (get_clientinput(line, sizeof(line), x->config->timeout) == NULL) {
  195. syslog(LOG_NOTICE, "client terminated while sending data");
  196. return (-1);
  197. }
  198. lineno++;
  199. noctrl(line);
  200. /* Received Zeile einbauen
  201.  */
  202. if (lineno == 1) {
  203. if (x->config->droppath != 2) {
  204. fprintf (x->sout, "Received: from %s (%s) by %srn",
  205. x->client, x->ipnum, x->hostname);
  206. }
  207. }
  208. /*
  209.  * Ggf. Message-ID aus Mail-Header lesen, bzw. Mail-Path
  210.  * aus dem Header loeschen.
  211.  */
  212. if (isheader == 1) {
  213. if (*line == 0) {
  214. isheader = 0;
  215. if (*x->msgid == 0) {
  216. unsigned long now;
  217. now = time(NULL);
  218. snprintf (x->msgid, sizeof(x->msgid) - 2, "%lu.%d.%d", now, getpid(), x->mailcount);
  219. syslog(LOG_NOTICE, "no message id, assuming %s, client= %s", x->msgid, x->client);
  220. }
  221. }
  222. else {
  223. char *p, word[80];
  224. p = line;
  225. get_word(&p, word, sizeof(word));
  226. strlwr(word);
  227. if (strcmp(word, "message-id:") == 0) {
  228. if (*x->msgid != 0) {
  229. syslog(LOG_NOTICE, "duplicate message id, client= %s", x->client);
  230. continue;
  231. }
  232. else {
  233. if ((p = strchr(p, '<')) != NULL) {
  234. p++;
  235. get_quoted(&p, '>', x->msgid, sizeof(x->msgid));
  236. }
  237. }
  238. }
  239. else if (strcmp(word, "received:") == 0) {
  240. if (x->config->droppath != 0)
  241. continue;
  242. }
  243. }
  244. }
  245. /* Zeile an Sendmail weitergeben
  246.  */
  247. if ((bytes = fprintf(x->sout, "%srn", line)) != strlen(line) + 2) {
  248. syslog(LOG_NOTICE, "server terminated while receiving data");
  249. return (-1);
  250. }
  251. x->size += bytes;
  252. /* Ende der Mail erreicht?
  253.  */
  254. if (line[0] == '.'  &&  line[1] == 0) {
  255. fflush(x->sout);
  256. break;
  257. }
  258. }
  259. return (0);
  260. }
  261. int proxy_request(config_t *config)
  262. {
  263. int rc;
  264. char *p, command[10], word[200], line[2048];
  265. smtp_t *x;
  266. x = allocate(sizeof(smtp_t));
  267. get_client_info(0, x->ipnum, x->client);
  268. x->mailcount = 0;
  269. syslog(LOG_NOTICE, "connected to client: %s", x->ipnum);
  270. if (*config->clientdir != 0) {
  271. char logfile[200];
  272. unsigned long now;
  273. struct stat sbuf;
  274. now = time(NULL);
  275. snprintf (logfile, sizeof(logfile) - 2, "%s/%s", config->clientdir, x->ipnum);
  276. if (stat(logfile, &sbuf) != 0  ||  (now - sbuf.st_mtime) >= config->accepttime) {
  277. printf ("421 service unavailable - authenticate with POP3 firstrn");
  278. syslog(LOG_NOTICE, "client not permitted: %s", x->ipnum);
  279. goto end;
  280. }
  281. }
  282. if (*config->server != 0) {
  283. unsigned int port;
  284. char server[200];
  285. copy_string(server, config->server, sizeof(server));
  286. port = get_port(server, 25);
  287. if ((x->sin = ip_open(server, port)) == NULL) {
  288. printf ("451 Service unavailablern");
  289. syslog(LOG_NOTICE, "can't connect to server: %s:%u, %m", server, port);
  290. exit (1);
  291. }
  292. x->sout = x->sin;
  293. p = config->server;
  294. }
  295. else if (config->argv != NULL) {
  296. int pid, pin[2], pout[2];
  297. if (pipe(pin) != 0  ||  pipe(pout) != 0) {
  298. printf ("451 Service unavailablern");
  299. syslog(LOG_NOTICE, "can't pipe(): %m");
  300. exit (-1);
  301. }
  302. else if ((pid = fork()) < 0) {
  303. printf ("451 Service unavailablern");
  304. syslog(LOG_NOTICE, "can't fork(): %m");
  305. exit (-1);
  306. }
  307. else if (pid == 0) {
  308. dup2(pin[1], 1);
  309. close (pin[0]);
  310. dup2(pout[0], 0);
  311. close(pout[1]);
  312. close (2);
  313. execvp(config->argv[0], config->argv);
  314. printf ("451 Service unavailablern");
  315. syslog(LOG_NOTICE, "can't execute: %s: %m", config->argv[0]);
  316. exit (1);
  317. }
  318. else {
  319. x->sin  = fdopen(pin[0], "r");
  320. close(pin[1]);
  321. x->sout = fdopen(pout[1], "w");
  322. close(pout[0]);
  323. p = config->argv[0];
  324. }
  325. }
  326. else {
  327. printf ("451 Service unavailablern");
  328. syslog(LOG_NOTICE, "no server specified");
  329. exit (1);
  330. }
  331. syslog(LOG_NOTICE, "connected to server: %s", p);
  332. /* Konfiguration uebernehmen
  333.  */
  334. x->config = config;
  335. /* Hostnamen holen
  336.  */
  337. gethostname(word, sizeof(word));
  338. getdomainname(line, sizeof(line));
  339. snprintf (x->hostname, sizeof(x->hostname) - 2, word, line);
  340. /* Greeting Message vom Sendmail Server lesen, und an
  341.  * Client schicken.
  342.  */
  343. rc = getresp(line, sizeof(line), x->sin, debug);
  344. while (line[3] != ' ') {
  345. rc = getresp(line, sizeof(line), x->sin, debug);
  346. if (rc == -1) {
  347. syslog(LOG_NOTICE, "lost server while reading server greeting");
  348. exit (1);
  349. }
  350. }
  351. echoline(stdout, line, rc);
  352. /* Wir stellen uns beim lokalen Sendmail Server vor. Die
  353.  * EHLO-replys werden 'verschluckt' und durch einen eigenen
  354.  * ersetzt.
  355.  */
  356. putcmd(x->sout, "EHLO", "localhost", "SVR");
  357. while ((rc = getresp(line, sizeof(line), x->sin, debug)) != -1) {
  358. if (line[3] == ' ')
  359. break;
  360. }
  361. rc = atol(line);
  362. if (rc != 250) {
  363. syslog(LOG_NOTICE, "server HELO: status is not 250");
  364. echoline(stdout, "421 service unavailable", 0);
  365. return (-1);
  366. }
  367. /*
  368.  *  **  S M T P   M A I N L O O P
  369.  */
  370. x->state = WAITING;
  371. while (1) {
  372. rc = 0; /* Server response code loeschen */
  373. /* Naechstes Kommando vom Client holen
  374.  */
  375. fflush(stdout);
  376. if (get_clientinput(line, sizeof(line), x->config->timeout) == NULL) {
  377. syslog(LOG_NOTICE, "client closed connection");
  378. break;
  379. }
  380. /* Kommando isolieren
  381.  */
  382. p = noctrl(line);
  383. get_word(&p, command, sizeof(command));
  384. strupr(command);
  385. p = skip_ws(p);
  386. /* QUIT ist immer moeglich.
  387.  */
  388. if (strcmp(command, "QUIT") == 0) {
  389. putcmd(x->sout, "QUIT", "", "SVR");
  390. rc = getresp(line, sizeof(line), x->sin, debug);
  391. echoline(stdout, line, rc);
  392. x->state = SEND_QUIT;
  393. break;
  394. }
  395. /* HELP
  396.  */
  397. else if (strcmp(command, "HELP") == 0) {
  398. echoline(stdout, "503 no help available", 0);
  399. }
  400. /* NOOP 
  401.  */
  402. else if (strcmp(command, "NOOP") == 0) {
  403. putcmd(x->sout, "NOOP", "", "SVR");
  404. rc = getresp(line, sizeof(line), x->sin, debug);
  405. echoline(stdout, line, rc);
  406. }
  407. /* RSET
  408.  */
  409. else if (strcmp(command, "RSET") == 0) {
  410. putcmd(x->sout, "RSET", "", "SVR");
  411. rc = getresp(line, sizeof(line), x->sin, debug);
  412. echoline(stdout, line, rc);
  413. reset_connection(x);
  414. syslog(LOG_NOTICE, "RSET command, client= %s", x->client);
  415. }
  416. /* ETRN
  417.  */
  418. else if (strcmp(command, "ETRN") == 0) {
  419. if (x->config->etrn == 0) {
  420. echoline(stdout, "500 unrecognized command", 0);
  421. syslog(LOG_NOTICE, "ETRN request rejected: client= %s", x->client);
  422. }
  423. else {
  424. if (*get_word(&p, word, sizeof(word)) == 0)
  425. echoline(stdout, "500 ETRN needs parameter", 0);
  426. else {
  427. putcmd(x->sout, "ETRN", word, "SVR");
  428. rc = getresp(line, sizeof(line), x->sin, debug);
  429. echoline(stdout, line, rc);
  430. if (rc != 250)
  431. syslog(LOG_NOTICE, "ETRN rejected by server, client= %s", x->client);
  432. }
  433. }
  434. }
  435. /* HELO und EHLO sind auch immer verfuegbar, aber nur
  436.  * einmal.
  437.  */
  438. else if (strcmp(command, "HELO") == 0  ||  strcmp(command, "EHLO") == 0) {
  439. if (x->helloseen != 0)
  440. echoline(stdout, "503 duplicate HELO/EHLO", 0);
  441. else if (*get_word(&p, word, sizeof(word)) == 0) {
  442. snprintf (line, sizeof(line) - 2, "501 %s requires domain name", command);
  443. echoline(stdout, line, 0);
  444. }
  445. else {
  446. if (strcmp(command, "HELO") == 0) {
  447. snprintf (line, sizeof(line) - 2, "250 SMTP server v%s ready %s [%s]", VERSION, x->client, x->ipnum);
  448. echoline(stdout, line, 0);
  449. }
  450. else {
  451. snprintf (line, sizeof(line) - 2, "250-SMTP server v%s ready %s [%s]", VERSION, x->client, x->ipnum);
  452. echoline(stdout, line, 0);
  453. echoline(stdout, "250-8BITMIME", 0);
  454. if (x->config->etrn != 0)
  455. echoline(stdout, "250-ETRN", 0);
  456. echoline(stdout, "250 HELP", 0);
  457. }
  458. x->helloseen = 1;
  459. }
  460. }
  461. /* MAIL, SEND, SOML, SAML
  462.  *
  463.  * Laut RFC 821 kann das MAIL Kommando jederzeit abgesetzt
  464.  * werden, es macht dabei einen impliziten SMTP-Reset. Der
  465.  * real existierende Sendmail will davon aber nichts wissen.
  466.  */
  467. else if (strcmp(command, "MAIL") == 0  || strcmp(command, "SEND") == 0  ||
  468.     strcmp(command, "SOML") == 0  || strcmp(command, "SAML") == 0) {
  469. get_quoted(&p, ':', word, sizeof(word));
  470. if (strcasecmp(word, "FROM") != 0)
  471. echoline(stdout, "500 syntax error", 0);
  472. else if (*x->sender != 0)
  473. echoline(stdout, "503 sender already specified", 0);
  474. else {
  475. int allowed;
  476. char sender[200], emailadr[200];
  477. p = skip_ws(p);
  478. get_word(&p, sender, sizeof(sender));
  479. strlwr(sender);
  480. /*
  481.  * Wir machen ein paar grundsaetzliche Tests mit
  482.  * der Absenderadresse:
  483.  *
  484.  *  - Ist die Adresse von spitzen Klammern
  485.  *    umgeben?
  486.  * ...
  487.  */
  488. allowed = 1;
  489. get_emailadr(sender, emailadr, sizeof(emailadr));
  490. if (*emailadr == 0)
  491. allowed = 0;
  492. /*
  493.  * ...
  494.  *  - Enthaelt die Adresse mindestens ein @-Zeichen?
  495.  *  - Enthaelt die Adresse genau ein @-Zeichen?
  496.  *  - Ist in der Adresse kein !- und kein %-Zeichen
  497.  *    enthalten.
  498.  * ...
  499.  */
  500. else if (check_emailadr(emailadr) == 0)
  501. allowed = 0;
  502. /*
  503.  * ...
  504.  *  - Schliesslich wird ggf. noch getestet,
  505.  *    ob die Absenderadresse auch auf der
  506.  *    allow-Liste steht.
  507.  *
  508.  * Mit den Empfaengeradressen werden die gleichen Tests
  509.  * durchgefuehrt.
  510.  */
  511. else if ((p = x->config->senderlist) == NULL  ||  *p == 0)
  512. allowed = 1; /* kein Adresstest */
  513. else 
  514. allowed = search_allowlist(emailadr, x->config->senderlist);
  515. if (allowed == 0) {
  516. char line[300];
  517. snprintf (line, sizeof(line) - 2, "550 not allowed: %s", sender);
  518. echoline(stdout, line, 0);
  519. syslog(LOG_NOTICE, "sender rejected: %s, client= %s", sender, x->client);
  520. }
  521. else {
  522. snprintf (line, sizeof(line) - 2, "%s FROM: %s", command, sender);
  523. putcmd(x->sout, line, "", "SVR");
  524. rc = getresp(line, sizeof(line), x->sin, debug);
  525. echoline(stdout, line, rc);
  526. if (rc == 250) {
  527. copy_string(x->sender, sender, sizeof(sender));
  528. x->state = MAIL_SEEN;
  529. }
  530. }
  531. }
  532. }
  533. /* RCPT
  534.  */
  535. else if (strcmp(command, "RCPT") == 0) {
  536. get_quoted(&p, ':', word, sizeof(word));
  537. if (strcasecmp(word, "TO") != 0)
  538. echoline(stdout, "500 syntax error", 0);
  539. else if (x->state != MAIL_SEEN  &&  x->state != RCPT_SEEN)
  540. echoline(stdout, "503 specify sender first", 0);
  541. else {
  542. int allowed;
  543. char rcpt[200], emailadr[200];
  544. p = skip_ws(p);
  545. get_word(&p, rcpt, sizeof(rcpt));
  546. strlwr(rcpt);
  547. get_emailadr(rcpt, emailadr, sizeof(emailadr));
  548. if (*emailadr == 0)
  549. allowed = 0;
  550. else if (check_emailadr(emailadr) == 0)
  551. allowed = 0;
  552. else if ((p = x->config->rcptlist) == NULL  ||  *p == 0)
  553. allowed = 1;
  554. else 
  555. allowed = search_allowlist(emailadr, x->config->rcptlist);
  556. if (allowed == 0) {
  557. char line[300];
  558. snprintf (line, sizeof(line) - 2, "550 no such user: %s", rcpt);
  559. echoline(stdout, line, 0);
  560. syslog(LOG_NOTICE, "recipient rejected: %s, client= %s", rcpt, x->client);
  561. }
  562. else {
  563. putcmd(x->sout, "RCPT TO:", rcpt, "SVR");
  564. rc = getresp(line, sizeof(line), x->sin, debug);
  565. echoline(stdout, line, rc);
  566. if (rc == 250  ||  rc == 251) {
  567. x->nrcpt++;
  568. x->state = RCPT_SEEN;
  569. }
  570. }
  571. }
  572. }
  573. /* DATA
  574.  */
  575. else if (strcmp(command, "DATA") == 0) {
  576. x->mailcount++;
  577. if (x->state != RCPT_SEEN)
  578. echoline(stdout, "503 specify receipients first", 0);
  579. else {
  580. putcmd(x->sout, "DATA", "", "SVR");
  581. rc = getresp(line, sizeof(line), x->sin, debug);
  582. echoline(stdout, line, rc);
  583. if (rc == 354) {
  584. if ((rc = receive_data(x)) == 0) {
  585. rc = getresp(line, sizeof(line), x->sin, debug);
  586. echoline(stdout, line, rc);
  587. if (rc == 250) {
  588. p = line;
  589. get_word(&p, word, sizeof(word));
  590. get_word(&p, x->jobid, sizeof(x->jobid));
  591. }
  592. }
  593. }
  594. snprintf (line, sizeof(line) - 2, "client= %s, sender= %s, nrcpt= %d, size= %ld, jobid= <%s>, message-id= <%s>, status= %d",
  595. x->client, x->sender, x->nrcpt, x->size,
  596. x->jobid, x->msgid, rc);
  597. syslog(LOG_NOTICE, line);
  598. reset_connection(x);
  599. x->state = WAITING;
  600. }
  601. }
  602. /* Alles andere ist unserem Server unbekannt.
  603.  */
  604. else {
  605. fprintf (stderr, "500 unrecognized commandrn");
  606. }
  607. if (rc == 421) {
  608. syslog(LOG_NOTICE, "sendmail returned 421, state= %d, command= %s", x->state, command);
  609. break;
  610. }
  611. else if (rc == -1) {
  612. syslog(LOG_NOTICE, "terminating (sendmail terminated)");
  613. x->state = NO_SENDMAIL;
  614. break;
  615. }
  616. }
  617. if (x->state != SEND_QUIT  &&  x->state != NO_SENDMAIL) {
  618. putcmd(x->sout, "QUIT", "", "SVR");
  619. rc = getresp(line, sizeof(line), x->sin, debug);
  620. }
  621. end:
  622. syslog(LOG_NOTICE, "client %s disconnecting, %d mails", x->client, x->mailcount);
  623. return (0);
  624. }