home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / sendmail / sendmail-5.65c+IDA-1.4.4.1 / src / srvrsmtp.c < prev    next >
Encoding:
C/C++ Source or Header  |  1991-06-24  |  15.3 KB  |  700 lines

  1. /*
  2.  * Copyright (c) 1983 Eric P. Allman
  3.  * Copyright (c) 1988 Regents of the University of California.
  4.  * All rights reserved.
  5.  *
  6.  * Redistribution and use in source and binary forms are permitted provided
  7.  * that: (1) source distributions retain this entire copyright notice and
  8.  * comment, and (2) distributions including binaries display the following
  9.  * acknowledgement:  ``This product includes software developed by the
  10.  * University of California, Berkeley and its contributors'' in the
  11.  * documentation or other materials provided with the distribution and in
  12.  * all advertising materials mentioning features or use of this software.
  13.  * Neither the name of the University nor the names of its contributors may
  14.  * be used to endorse or promote products derived from this software without
  15.  * specific prior written permission.
  16.  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
  17.  * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
  18.  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  19.  */
  20.  
  21. #include "sendmail.h"
  22. #include <errno.h>
  23. #include <sys/signal.h>
  24.  
  25. #ifndef lint
  26. # ifdef SMTP
  27. static char sccsid[] = "@(#)srvrsmtp.c    5.28 (Berkeley) 6/1/90 (with SMTP)";
  28. static char  rcsid[] = "@(#)$Id: srvrsmtp.c,v 5.28.0.15 1991/06/24 20:27:06 paul Exp $ (with SMTP)";
  29. # else /* !SMTP */
  30. static char sccsid[] = "@(#)srvrsmtp.c    5.28 (Berkeley) 6/1/90 (without SMTP)";
  31. static char  rcsid[] = "@(#)$Id: srvrsmtp.c,v 5.28.0.15 1991/06/24 20:27:06 paul Exp $ (without SMTP)";
  32. # endif /* SMTP */
  33. #endif /* not lint */
  34.  
  35. #ifdef SMTP
  36.  
  37. #ifdef __STDC__
  38. static char * skipword(char *, const char *);
  39. static void help(char *);
  40. static int runinchild(const char *);
  41. #else /* !__STDC__ */
  42. static char * skipword();
  43. static void help();
  44. static int runinchild();
  45. #endif /* __STDC__ */
  46.  
  47. /*
  48. **  SMTP -- run the SMTP protocol.
  49. **
  50. **    Parameters:
  51. **        none.
  52. **
  53. **    Returns:
  54. **        never.
  55. **
  56. **    Side Effects:
  57. **        Reads commands from the input channel and processes
  58. **            them.
  59. */
  60.  
  61. struct cmd
  62. {
  63.     char    *cmdname;    /* command name */
  64.     int    cmdcode;    /* internal code, see below */
  65. };
  66.  
  67. /* values for cmdcode */
  68. # define CMDERROR    0    /* bad command */
  69. # define CMDMAIL    1    /* mail -- designate sender */
  70. # define CMDRCPT    2    /* rcpt -- designate recipient */
  71. # define CMDDATA    3    /* data -- send message text */
  72. # define CMDRSET    4    /* rset -- reset state */
  73. # define CMDVRFY    5    /* vrfy -- verify address */
  74. # define CMDHELP    6    /* help -- give usage info */
  75. # define CMDNOOP    7    /* noop -- do nothing */
  76. # define CMDQUIT    8    /* quit -- close connection and die */
  77. # define CMDHELO    9    /* helo -- be polite */
  78. # define CMDONEX    10    /* onex -- sending one transaction only */
  79. # define CMDVERB    11    /* verb -- go into verbose mode */
  80. # define CMDTICK    12    /* tick -- batch SMTP sync command */
  81. /* debugging-only commands, only enabled if SMTPDEBUG is defined */
  82. # define CMDDBGQSHOW    13    /* showq -- show send queue */
  83. # define CMDDBGDEBUG    14    /* debug -- set debug mode */
  84.  
  85. static struct cmd    CmdTab[] =
  86. {
  87.     "mail",        CMDMAIL,
  88.     "rcpt",        CMDRCPT,
  89.     "data",        CMDDATA,
  90.     "rset",        CMDRSET,
  91.     "vrfy",        CMDVRFY,
  92.     "expn",        CMDVRFY,
  93.     "help",        CMDHELP,
  94.     "noop",        CMDNOOP,
  95.     "quit",        CMDQUIT,
  96.     "helo",        CMDHELO,
  97.     "verb",        CMDVERB,
  98.     "onex",        CMDONEX,
  99.     "tick",        CMDTICK,
  100.     /*
  101.      * remaining commands are here only
  102.      * to trap and log attempts to use them
  103.      */
  104.     "showq",    CMDDBGQSHOW,
  105.     "debug",    CMDDBGDEBUG,
  106.     NULL,        CMDERROR,
  107. };
  108.  
  109. bool    InChild = FALSE;        /* true if running in a subprocess */
  110. bool    OneXact = FALSE;        /* one xaction only this run */
  111.  
  112. # define EX_QUIT    22        /* special code for QUIT command */
  113.  
  114. static bool hasmail;            /* mail command received */
  115. static char *sendinghost;
  116. static char *ptr;
  117.  
  118. void
  119. smtp(batched)
  120.     bool batched;            /* running non-interactively? */
  121. {
  122.     int la;
  123.     register struct cmd *c;
  124.     char *cmd;
  125.     auto ADDRESS *vrfyqueue;
  126.     ADDRESS *a;
  127.     char inp[MAXLINE];
  128.     char TickArg[20];
  129.     char cmdbuf[100];
  130.     extern ENVELOPE BlankEnvelope;
  131.  
  132.     hasmail = FALSE;
  133.     sendinghost = NULL;
  134.     if (OutChannel != stdout)
  135.     {
  136.         /* arrange for debugging output to go to remote host */
  137.         (void) close(1);
  138.         (void) dup(fileno(OutChannel));
  139.     }
  140.     settime();
  141.     if (RealHostName != NULL)
  142.     {
  143.         CurHostName = RealHostName;
  144.         setproctitle("srvrsmtp %s", CurHostName);
  145.     }
  146.     else
  147.     {
  148.         /* this must be us!! */
  149.         CurHostName = MyHostName;
  150.     }
  151.  
  152.     /* see if we are rejecting connections (see daemon.c) */
  153.     la = getla();
  154.     if (batched && la > RefuseLA)
  155.     {
  156.         message("421", "%s too busy, try again later", MyHostName);
  157.         exit (EX_TEMPFAIL);
  158.     }
  159.     expand("\001e", inp, &inp[(sizeof(inp) - 1)], CurEnv);
  160.     message("220", "%s", inp);
  161.     SmtpPhase = "startup";
  162.     DeclHostName = NULL;
  163.     for (;;)
  164.     {
  165.         /* arrange for backout */
  166.         if (setjmp(TopFrame) > 0 && InChild)
  167.             finis();
  168.         QuickAbort = FALSE;
  169.         HoldErrs = FALSE;
  170.  
  171.         /* setup for the read */
  172.         CurEnv->e_to = NULL;
  173.         Errors = 0;
  174.         (void) fflush(stdout);
  175.  
  176.         /* read the input line */
  177.         ptr = sfgets(inp, sizeof inp, InChannel);
  178.  
  179.         /* handle errors */
  180.         if (ptr == NULL)
  181.         {
  182.             /* end of file, just die */
  183.             message("421", "%s Lost input channel from %s",
  184.                 MyHostName, CurHostName);
  185.             finis();
  186.         }
  187.  
  188.         /* clean up end of line */
  189.         fixcrlf(inp, TRUE);
  190.  
  191.         /* echo command to transcript */
  192.         if (CurEnv->e_xfp != NULL)
  193.             fprintf(CurEnv->e_xfp, "<<< %s\n", inp);
  194.  
  195.         /* break off command */
  196.         for (ptr = inp; isspace(*ptr); ptr++)
  197.             continue;
  198.         for (cmd = cmdbuf; *ptr != '\0' && !isspace(*ptr); )
  199.             *cmd++ = *ptr++;
  200.         *cmd = '\0';
  201.  
  202.         /* throw away leading whitespace */
  203.         while (isspace(*ptr))
  204.             ptr++;
  205.  
  206.         /* decode command */
  207.         for (c = CmdTab; c->cmdname != NULL; c++)
  208.         {
  209.             if (!strcasecmp(c->cmdname, cmdbuf))
  210.                 break;
  211.         }
  212.  
  213.         /* process command */
  214.         switch (c->cmdcode)
  215.         {
  216.           case CMDHELO:        /* hello -- introduce yourself */
  217.             SmtpPhase = "HELO";
  218.             setproctitle("%s: %s", CurHostName, inp);
  219.  
  220.             /* find canonical name */
  221.             if (!strcmp(ptr, MyHostName))
  222.             {
  223.                 /*
  224.                  * didn't know about alias,
  225.                  * or connected to an echo server
  226.                  */
  227.                 message("553", "Local configuration error, hostname not recognized as local");
  228.                 break;
  229.             }
  230.             if (RealHostName != NULL && strcasecmp(ptr, RealHostName))
  231.             {
  232.                 char hostbuf[MAXNAME];
  233.  
  234.                 (void) sprintf(hostbuf, "%s (%s)", ptr, RealHostName);
  235.                 DeclHostName = newstr(hostbuf);
  236.                 message("250", "Hello %s, why do you call yourself %s?",
  237.                     RealHostName, ptr);
  238.             }
  239.             else
  240.             {
  241.                 sendinghost = newstr(ptr);
  242.                 message("250", "Hello %s, pleased to meet you", ptr);
  243.             }
  244.             break;
  245.  
  246.           case CMDMAIL:        /* mail -- designate sender */
  247.             SmtpPhase = "MAIL";
  248.  
  249.             /* force a sending host even if no HELO given */
  250.             if (RealHostName != NULL && macvalue('s', CurEnv) == NULL)
  251.             {
  252.                 if (sendinghost)
  253.                     free(sendinghost);
  254.                 sendinghost = newstr(RealHostName);
  255.             }
  256.  
  257.             /* check for validity of this command */
  258.             if (hasmail)
  259.             {
  260.                 message("503", "Sender already specified");
  261.                 break;
  262.             }
  263.             if (InChild)
  264.             {
  265.                 errno = 0;
  266.                 syserr("Nested MAIL command");
  267.                 exit(0);
  268.             }
  269.  
  270.             /* fork a subprocess to process this command */
  271.             if (runinchild("SMTP-MAIL") > 0)
  272.                 break;
  273.             define('s', sendinghost, CurEnv);
  274.             define('r', "SMTP", CurEnv);
  275.             initsys();
  276.             setproctitle("%s %s: %s", CurEnv->e_id,
  277.                 CurHostName, inp);
  278.  
  279.             /* child -- go do the processing */
  280.             ptr = skipword(ptr, "from");
  281.             if (ptr == NULL)
  282.                 break;
  283.             setsender(ptr);
  284.             if (Errors == 0)
  285.             {
  286.                 message("250", "Sender ok");
  287.                 hasmail = TRUE;
  288.             }
  289.             else if (InChild)
  290.                 finis();
  291.             break;
  292.  
  293.           case CMDRCPT:        /* rcpt -- designate recipient */
  294.             SmtpPhase = "RCPT";
  295.             setproctitle("%s %s: %s", CurEnv->e_id,
  296.                 CurHostName, inp);
  297.             if (setjmp(TopFrame) > 0)
  298.             {
  299.                 if (!batched)
  300.                     CurEnv->e_flags &= ~EF_FATALERRS;
  301.                 break;
  302.             }
  303.             QuickAbort = TRUE;
  304.             ptr = skipword(ptr, "to");
  305.             if (ptr == NULL)
  306.                 break;
  307.             a = parseaddr(ptr, (ADDRESS *) NULL, 1, '\0');
  308.             if (a == NULL)
  309.                 break;
  310.             a->q_flags |= QPRIMARY;
  311.             a = recipient(a, &CurEnv->e_sendqueue);
  312.             if (Errors != 0)
  313.                 break;
  314.  
  315.             /* no errors during parsing, but might be a duplicate */
  316.             CurEnv->e_to = ptr;
  317.             if (!bitset(QBADADDR, a->q_flags))
  318.                 message("250", "Recipient ok");
  319.             else
  320.             {
  321.                 /* punt -- should keep message in ADDRESS.... */
  322.                 message("550", "Addressee unknown");
  323.             }
  324.             CurEnv->e_to = NULL;
  325.             break;
  326.  
  327.           case CMDDATA:        /* data -- text of mail */
  328.             SmtpPhase = "DATA";
  329.             if (!hasmail)
  330.             {
  331.                 message("503", "Need valid MAIL command");
  332.                 if (batched)
  333.                     Errors++;
  334.                 else
  335.                 break;
  336.             }
  337.             else if (CurEnv->e_nrcpts <= 0)
  338.             {
  339.                 message("503", "Need valid RCPT (recipient)");
  340.                 if (batched)
  341.                     Errors++;
  342.                 else
  343.                 break;
  344.             }
  345.  
  346.             /* collect the text of the message */
  347.             SmtpPhase = "collect";
  348.             setproctitle("%s %s: %s", CurEnv->e_id,
  349.                 CurHostName, inp);
  350.             collect(TRUE);
  351.             if (Errors != 0)
  352.                 break;
  353.  
  354.             /*
  355.             **  Arrange to send to everyone.
  356.             **    If sending to multiple people, mail back
  357.             **        errors rather than reporting directly.
  358.             **    In any case, don't mail back errors for
  359.             **        anything that has happened up to
  360.             **        now (the other end will do this).
  361.             **    Truncate our transcript -- the mail has gotten
  362.             **        to us successfully, and if we have
  363.             **        to mail this back, it will be easier
  364.             **        on the reader.
  365.             **    Then send to everyone.
  366.             **    Finally give a reply code.  If an error has
  367.             **        already been given, don't mail a
  368.             **        message back.
  369.             **    We goose error returns by clearing error bit.
  370.             */
  371.  
  372.             SmtpPhase = "delivery";
  373.             if (CurEnv->e_nrcpts != 1 || batched)
  374.             {
  375.                 HoldErrs = TRUE;
  376.                 ErrorMode = EM_MAIL;
  377.             }
  378.             if (!batched)
  379.             {
  380.                 CurEnv->e_flags &= ~EF_FATALERRS;
  381.                 CurEnv->e_xfp = freopen(queuename(CurEnv, 'x'),
  382.                             "w", CurEnv->e_xfp);
  383.             }
  384.  
  385.             /* send to all recipients */
  386.             sendall(CurEnv, SM_DEFAULT);
  387.             CurEnv->e_to = NULL;
  388.  
  389.             /* save statistics */
  390.             markstats(CurEnv, (ADDRESS *) NULL);
  391.  
  392.             /* issue success if appropriate and reset */
  393.             if (Errors == 0 || HoldErrs)
  394.                 message("250", "Ok");
  395.             else
  396.                 CurEnv->e_flags &= ~EF_FATALERRS;
  397.  
  398.             /* if in a child, pop back to our parent */
  399.             if (InChild)
  400.                 finis();
  401.  
  402.             /* clean up a bit */
  403.             hasmail = 0;
  404.             dropenvelope(CurEnv);
  405.             CurEnv = newenvelope(CurEnv);
  406.             CurEnv->e_flags = BlankEnvelope.e_flags;
  407.             break;
  408.  
  409.           case CMDRSET:        /* rset -- reset state */
  410.             message("250", "Reset state");
  411.             if (InChild)
  412.                 finis();
  413.             break;
  414.  
  415.           case CMDVRFY:        /* vrfy -- verify address */
  416.             if (runinchild("SMTP-VRFY") > 0)
  417.                 break;
  418.             setproctitle("%s: %s", CurHostName, inp);
  419.             vrfyqueue = NULL;
  420.             QuickAbort = TRUE;
  421.             sendtolist(ptr, (ADDRESS *) NULL, &vrfyqueue);
  422.             if (Errors != 0)
  423.             {
  424.                 if (InChild)
  425.                     finis();
  426.                 break;
  427.             }
  428.             while (vrfyqueue != NULL)
  429.             {
  430.                 register ADDRESS *a = vrfyqueue->q_next;
  431.                 char *code;
  432.  
  433.                 while (a != NULL && bitset(QDONTSEND|QBADADDR, a->q_flags))
  434.                     a = a->q_next;
  435.  
  436.                 if (!bitset(QDONTSEND|QBADADDR, vrfyqueue->q_flags))
  437.                 {
  438.                     if (a != NULL)
  439.                         code = "250-";
  440.                     else
  441.                         code = "250";
  442.                     if (vrfyqueue->q_fullname == NULL)
  443.                         message(code, "<%s>", vrfyqueue->q_paddr);
  444.                     else
  445.                         message(code, "%s <%s>",
  446.                             vrfyqueue->q_fullname, vrfyqueue->q_paddr);
  447.                 }
  448.                 else if (a == NULL)
  449.                     message("554", "Self destructive alias loop");
  450.                 vrfyqueue = a;
  451.             }
  452.             if (InChild)
  453.                 finis();
  454.             break;
  455.  
  456.           case CMDHELP:        /* help -- give user info */
  457.             if (*ptr == '\0')
  458.                 ptr = "SMTP";
  459.             help(ptr);
  460.             break;
  461.  
  462.           case CMDNOOP:        /* noop -- do nothing */
  463.             message("200", "OK");
  464.             break;
  465.  
  466.           case CMDQUIT:        /* quit -- leave mail */
  467.             message("221", "%s closing connection", MyHostName);
  468.             if (InChild)
  469.                 ExitStat = EX_QUIT;
  470.             finis();
  471.  
  472.           case CMDVERB:        /* set verbose mode */
  473.             Verbose = TRUE;
  474.             SendMode = SM_DELIVER;
  475.             message("200", "Verbose mode");
  476.             break;
  477.  
  478.           case CMDONEX:        /* doing one transaction only */
  479.             OneXact = TRUE;
  480.             message("200", "Only one transaction");
  481.             break;
  482.  
  483.           case CMDTICK:        /* BSMTP TICK */
  484.             (void) strncpy(TickArg, ptr, 20-1);
  485.             message("250", "OK");
  486.             break;
  487.  
  488. # ifdef SMTPDEBUG
  489.           case CMDDBGQSHOW:    /* show queues */
  490.             printf("Send Queue=");
  491.             printaddr(CurEnv->e_sendqueue, TRUE);
  492.             break;
  493.  
  494.           case CMDDBGDEBUG:    /* set debug mode */
  495.             tTsetup(tTdvect, sizeof tTdvect, "0-99.1");
  496.             tTflag(ptr);
  497.             message("200", "Debug set");
  498.             break;
  499.  
  500. # else /* not SMTPDEBUG */
  501.  
  502.           case CMDDBGQSHOW:    /* show queues */
  503.           case CMDDBGDEBUG:    /* set debug mode */
  504. #  ifdef LOG
  505.             if (RealHostName != NULL && LogLevel > 0)
  506.                 syslog(LOG_NOTICE,
  507.                     "\"%s\" command from %s (%s)\n",
  508.                     c->cmdname, RealHostName,
  509.                     inet_ntoa(RealHostAddr.sin_addr));
  510. #  endif /* LOG */
  511.             /* FALL THROUGH */
  512. # endif /* SMTPDEBUG */
  513.  
  514.           case CMDERROR:    /* unknown command */
  515.             message("500", "Command unrecognized");
  516. # ifdef LOG
  517.             syslog(LOG_NOTICE, "\"%s\" command unrecognized\n", cmdbuf);
  518. # endif /* LOG */
  519.             break;
  520.  
  521.           default:
  522.             errno = 0;
  523.             syserr("smtp: unknown code %d", c->cmdcode);
  524.             break;
  525.         }
  526.     }
  527.     /* NOTREACHED */
  528. }
  529. /*
  530. **  SKIPWORD -- skip a fixed word.
  531. **
  532. **    Parameters:
  533. **        p -- place to start looking.
  534. **        w -- word to skip.
  535. **
  536. **    Returns:
  537. **        p following w.
  538. **        NULL on error.
  539. **
  540. **    Side Effects:
  541. **        clobbers the p data area.
  542. */
  543.  
  544. static char *
  545. skipword(p, w)
  546.     register char *p;
  547.     const char *w;
  548. {
  549.     register char *q;
  550.  
  551.     /* find beginning of word */
  552.     while (isspace(*p))
  553.         p++;
  554.     q = p;
  555.  
  556.     /* find end of word */
  557.     while (*p != '\0' && *p != ':' && !isspace(*p))
  558.         p++;
  559.     while (isspace(*p))
  560.         *p++ = '\0';
  561.     if (*p != ':')
  562.     {
  563.       syntax:
  564.         message("501", "Syntax error");
  565.         Errors++;
  566.         return (NULL);
  567.     }
  568.     *p++ = '\0';
  569.     while (isspace(*p))
  570.         p++;
  571.  
  572.     /* see if the input word matches desired word */
  573.     if (strcasecmp(q, w))
  574.         goto syntax;
  575.  
  576.     return (p);
  577. }
  578. /*
  579. **  HELP -- implement the HELP command.
  580. **
  581. **    Parameters:
  582. **        topic -- the topic we want help for.
  583. **
  584. **    Returns:
  585. **        none.
  586. **
  587. **    Side Effects:
  588. **        outputs the help file to message output.
  589. */
  590.  
  591. static void
  592. help(topic)
  593.     char *topic;
  594. {
  595.     register FILE *hf;
  596.     int len;
  597.     char buf[MAXLINE];
  598.     bool noinfo;
  599.  
  600.     if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL)
  601.     {
  602.         /* no help */
  603.         errno = 0;
  604.         message("502", "HELP not implemented");
  605.         return;
  606.     }
  607.  
  608.     topic = newstr(topic);
  609.     len = strlen(topic);
  610.     makelower(topic);
  611.     noinfo = TRUE;
  612.  
  613.     while (fgets(buf, sizeof buf, hf) != NULL)
  614.     {
  615.         if (strncmp(buf, topic, len) == 0)
  616.         {
  617.             register char *p;
  618.  
  619.             p = index(buf, '\t');
  620.             if (p == NULL)
  621.                 p = buf;
  622.             else
  623.                 p++;
  624.             fixcrlf(p, TRUE);
  625.             message("214-", "%s", p);
  626.             noinfo = FALSE;
  627.         }
  628.     }
  629.  
  630.     if (noinfo)
  631.         message("504", "HELP topic unknown");
  632.     else
  633.         message("214", "End of HELP info");
  634.     free(topic);
  635.     (void) fclose(hf);
  636. }
  637. /*
  638. **  RUNINCHILD -- return twice -- once in the child, then in the parent again
  639. **
  640. **    Parameters:
  641. **        label -- a string used in error messages
  642. **
  643. **    Returns:
  644. **        zero in the child
  645. **        one in the parent
  646. **
  647. **    Side Effects:
  648. **        none.
  649. */
  650.  
  651. static int
  652. runinchild(label)
  653.     const char *label;
  654. {
  655.     int childpid;
  656.  
  657.     if (!OneXact)
  658.     {
  659.         childpid = dofork();
  660.         if (childpid > 0 && tTd(4, 2))
  661.             printf("runinchild: forking (pid = %d)\n", childpid);
  662.         if (childpid < 0)
  663.         {
  664.             syserr("%s: cannot fork", label);
  665.             return (1);
  666.         }
  667.         if (childpid > 0)
  668.         {
  669.             auto int st;
  670.  
  671.             /* parent -- wait for child to complete */
  672.             st = waitfor(childpid);
  673.             if (st == -1)
  674.                 syserr("%s: lost child", label);
  675.  
  676.             /* if we exited on a QUIT command, complete the process */
  677.             if (st == (EX_QUIT << 8))
  678.                 finis();
  679.  
  680.             return (1);
  681.         }
  682.         else
  683.         {
  684.             /* child */
  685.             InChild = TRUE;
  686.             QuickAbort = FALSE;
  687.             clearenvelope(CurEnv, FALSE);
  688.         }
  689.     }
  690.  
  691. #if !defined(DBM_AUTOBUILD) || ( !defined(NDBM) && !defined(OTHERDBM) )
  692.     /* open alias database */
  693.     initaliases(FALSE);
  694. #endif /* !DBM_AUTOBUILD || (!NDBM && !OTHERDBM) */
  695.  
  696.     return (0);
  697. }
  698.  
  699. #endif /* SMTP */
  700.