ncbi_sendmail.c
上传用户:yhdzpy8989
上传日期:2007-06-13
资源大小:13604k
文件大小:20k
源码类别:

生物技术

开发平台:

C/C++

  1. /*
  2.  * ===========================================================================
  3.  * PRODUCTION $Log: ncbi_sendmail.c,v $
  4.  * PRODUCTION Revision 1000.1  2004/02/12 21:52:59  gouriano
  5.  * PRODUCTION PRODUCTION: UPGRADED [CORE_001] Dev-tree R6.22
  6.  * PRODUCTION
  7.  * ===========================================================================
  8.  */
  9. /*  $Id: ncbi_sendmail.c,v 1000.1 2004/02/12 21:52:59 gouriano Exp $
  10.  * ===========================================================================
  11.  *
  12.  *                            PUBLIC DOMAIN NOTICE
  13.  *               National Center for Biotechnology Information
  14.  *
  15.  *  This software/database is a "United States Government Work" under the
  16.  *  terms of the United States Copyright Act.  It was written as part of
  17.  *  the author's official duties as a United States Government employee and
  18.  *  thus cannot be copyrighted.  This software/database is freely available
  19.  *  to the public for use. The National Library of Medicine and the U.S.
  20.  *  Government have not placed any restriction on its use or reproduction.
  21.  *
  22.  *  Although all reasonable efforts have been taken to ensure the accuracy
  23.  *  and reliability of the software and data, the NLM and the U.S.
  24.  *  Government do not and cannot warrant the performance or results that
  25.  *  may be obtained by using this software or data. The NLM and the U.S.
  26.  *  Government disclaim all warranties, express or implied, including
  27.  *  warranties of performance, merchantability or fitness for any particular
  28.  *  purpose.
  29.  *
  30.  *  Please cite the author in any work or product based on this material.
  31.  *
  32.  * ===========================================================================
  33.  *
  34.  * Author:  Anton Lavrentiev
  35.  *
  36.  * File Description:
  37.  *   Send mail
  38.  *
  39.  */
  40. #include "ncbi_ansi_ext.h"
  41. #include "ncbi_priv.h"
  42. #include <connect/ncbi_sendmail.h>
  43. #include <connect/ncbi_socket.h>
  44. #include <ctype.h>
  45. #include <stdlib.h>
  46. #ifdef NCBI_OS_UNIX
  47. #include <pwd.h>
  48. #include <unistd.h>
  49. #endif
  50. #ifdef NCBI_CXX_TOOLKIT
  51. #define NCBI_SENDMAIL_TOOLKIT "C++"
  52. #else
  53. #define NCBI_SENDMAIL_TOOLKIT "C"
  54. #endif
  55. #define MX_MAGIC_NUMBER 0xBA8ADEDA
  56. #define MX_CRLF         "rn"
  57. #define SMTP_READERR    -1      /* Error reading from socket         */
  58. #define SMTP_REPLYERR   -2      /* Error reading reply prefix        */
  59. #define SMTP_BADCODE    -3      /* Reply code doesn't match in lines */
  60. #define SMTP_BADREPLY   -4      /* Malformed reply text              */
  61. #define SMTP_NOCODE     -5      /* No reply code detected (letters?) */
  62. #define SMTP_WRITERR    -6      /* Error writing to socket           */
  63. /* Read SMTP reply from the socket.
  64.  * Return reply in the buffer provided,
  65.  * and reply code (positive value) as a return value.
  66.  * Return a negative code in case of problem (protocol reply
  67.  * read error or protocol violations).
  68.  * Return 0 in case of call error.
  69.  */
  70. static int s_SockRead(SOCK sock, char* reply, size_t reply_len)
  71. {
  72.     int/*bool*/ done = 0;
  73.     size_t n = 0;
  74.     int code = 0;
  75.     if (!reply  ||  !reply_len)
  76.         return 0;
  77.     do {
  78.         size_t m = 0;
  79.         char buf[4];
  80.         if (SOCK_Read(sock, buf, 4, &m, eIO_ReadPersist) != eIO_Success)
  81.             return SMTP_READERR;
  82.         if (m != 4)
  83.             return SMTP_REPLYERR;
  84.         if (buf[3] == '-'  ||  (done = isspace((unsigned char) buf[3]))) {
  85.             buf[3] = 0;
  86.             if (!code) {
  87.                 if (!(code = atoi(buf)))
  88.                     return SMTP_NOCODE;
  89.             } else if (code != atoi(buf))
  90.                 return SMTP_BADCODE;
  91.         } else
  92.             return SMTP_BADREPLY;
  93.         do {
  94.             m = 0;
  95.             if (SOCK_Read(sock,buf,1,&m,eIO_ReadPlain) != eIO_Success  ||  !m)
  96.                 return SMTP_READERR;
  97.             if (buf[0] != 'r'  &&  n < reply_len)
  98.                 reply[n++] = buf[0];
  99.         } while (buf[0] != 'n');
  100.         /* At least 'n' should sit in buffer */
  101.         assert(n);
  102.         if (done)
  103.             reply[n - 1] = 0;
  104.         else if (n < reply_len)
  105.             reply[n] = ' ';
  106.         
  107.     } while (!done);
  108.     assert(code);
  109.     return code;
  110. }
  111. static int/*bool*/ s_SockReadResponse(SOCK sock, int code, int alt_code,
  112.                                       char* buf, size_t buf_size)
  113. {
  114.     int c = s_SockRead(sock, buf, buf_size);
  115.     if (c <= 0) {
  116.         const char* message = 0;
  117.         switch (c) {
  118.         case SMTP_READERR:
  119.             message = "Read error";
  120.             break;
  121.         case SMTP_REPLYERR:
  122.             message = "Error reading reply prefix";
  123.             break;
  124.         case SMTP_BADCODE:
  125.             message = "Reply code doesn't match in lines";
  126.             break;
  127.         case SMTP_BADREPLY:
  128.             message = "Malformed reply text";
  129.             break;
  130.         case SMTP_NOCODE:
  131.             message = "No reply code detected";
  132.             break;
  133.         case SMTP_WRITERR:
  134.             message = "Write error";
  135.             break;
  136.         default:
  137.             message = "Unknown error";
  138.             break;
  139.         }
  140.         assert(message);
  141.         strncpy0(buf, message, buf_size - 1);
  142.     } else if (c == code  ||  (alt_code  &&  c == alt_code))
  143.         return 1/*success*/;
  144.     return 0/*failure*/;
  145. }
  146. static int/*bool*/ s_SockWrite(SOCK sock, const char* buf)
  147. {
  148.     size_t len = strlen(buf);
  149.     size_t n;
  150.     if (SOCK_Write(sock, buf, len, &n, eIO_WritePersist) == eIO_Success  &&
  151.         n == len) {
  152.         return 1/*success*/;
  153.     }
  154.     return 0/*failure*/;
  155. }
  156. static char* s_ComposeFrom(char* buf, size_t buf_size)
  157. {
  158.     size_t buf_len, hostname_len;
  159. #ifdef NCBI_OS_UNIX
  160.     /* Get the user login name. FIXME: not MT-safe */
  161.     const char* login_name;
  162.     CORE_LOCK_WRITE;
  163.     login_name = getlogin();
  164.     if (!login_name) {
  165.         struct passwd* pwd = getpwuid(getuid());
  166.         if (!pwd) {
  167.             if (!(login_name = getenv("USER"))  &&
  168.                 !(login_name = getenv("LOGNAME"))) {
  169.                 CORE_UNLOCK;
  170.                 return 0;
  171.             }
  172.         } else
  173.             login_name = pwd->pw_name;
  174.     }
  175. #else
  176.     /* Temporary solution for login name */
  177.     const char* login_name = "anonymous";
  178. #  ifdef NCBI_OS_MSWIN
  179.     const char* user_name = getenv("USERNAME");
  180.     if (user_name)
  181.         login_name = user_name;
  182. #  endif
  183. #endif
  184.     strncpy0(buf, login_name, buf_size - 1);
  185. #ifdef NCBI_OS_UNIX
  186.     CORE_UNLOCK;
  187. #endif
  188.     buf_len = strlen(buf);
  189.     hostname_len = buf_size - buf_len;
  190.     if (hostname_len-- > 1) {
  191.         buf[buf_len++] = '@';
  192.         SOCK_gethostname(&buf[buf_len], hostname_len);
  193.     }
  194.     return buf;
  195. }
  196. SSendMailInfo* SendMailInfo_Init(SSendMailInfo* info)
  197. {
  198.     if (info) {
  199.         info->magic_number    = MX_MAGIC_NUMBER;
  200.         info->cc              = 0;
  201.         info->bcc             = 0;
  202.         if (!s_ComposeFrom(info->from, sizeof(info->from)))
  203.             info->from[0]     = 0;
  204.         info->header          = 0;
  205.         info->body_size       = 0;
  206.         info->mx_host         = MX_HOST;
  207.         info->mx_port         = MX_PORT;
  208.         info->mx_timeout.sec  = MX_TIMEOUT;
  209.         info->mx_timeout.usec = 0;
  210.         info->mx_no_header    = 0/*false*/;
  211.     }
  212.     return info;
  213. }
  214. extern const char* CORE_SendMail(const char* to,
  215.                                  const char* subject,
  216.                                  const char* body)
  217. {
  218.     return CORE_SendMailEx(to, subject, body, 0);
  219. }
  220. /* In two macros below the smartest (or, weak-minded?) Sun
  221.  * C compiler warned about unreachable end-of-loop condition
  222.  * (well, it thinks "a condition" is there, dumb!), if we
  223.  * used 'return' right before the end of 'while' statement.
  224.  * So we now added "check" and "conditional" exit, which makes
  225.  * the Sun compiler much happier, and less wordy :-)
  226.  */
  227. #define SENDMAIL_RETURN(reason)                                            
  228.     do {                                                                   
  229.         if (sock)                                                          
  230.             SOCK_Close(sock);                                              
  231.         CORE_LOGF(eLOG_Error, ("[SendMail]  %s", reason));                 
  232.         if (reason/*always true, though, to trick "smart" compiler*/)      
  233.             return reason;                                                 
  234.     } while (0)
  235. #define SENDMAIL_RETURN2(reason, explanation)                              
  236.     do {                                                                   
  237.        if (sock)                                                           
  238.            SOCK_Close(sock);                                               
  239.        CORE_LOGF(eLOG_Error, ("[SendMail]  %s: %s", reason, explanation)); 
  240.        if (reason/*always true, though, to trick "smart" compiler*/)       
  241.            return reason;                                                  
  242.     } while (0)
  243. static const char* s_SendRcpt(SOCK sock, const char* to,
  244.                               char buf[], size_t buf_size,
  245.                               const char what[],
  246.                               const char write_error[],
  247.                               const char proto_error[])
  248. {
  249.     char c;
  250.     while ((c = *to++) != 0) {
  251.         char   quote = 0;
  252.         size_t k = 0;
  253.         if (isspace((unsigned char) c))
  254.             continue;
  255.         while (k < buf_size) {
  256.             if (quote) {
  257.                 if (c == quote)
  258.                     quote = 0;
  259.             } else if (c == '"'  ||  c == '<'  ||  c == ''') {
  260.                 quote = c == '<' ? '>' : c;
  261.             } else if (c == ',')
  262.                 break;
  263.             buf[k++] = c == 't' ? ' ' : c;
  264.             if (!(c = *to++))
  265.                 break;
  266.             if (isspace((unsigned char) c)) {
  267.                 while (isspace((unsigned char)(*to)))
  268.                     to++;
  269.             }
  270.         }
  271.         if (k >= buf_size)
  272.             SENDMAIL_RETURN("Recepient address is too long");
  273.         buf[k] = 0;
  274.         if (quote) {
  275.             CORE_LOGF(eLOG_Warning, ("[SendMail]  Ubalanced delimiters in "
  276.                                      "recepient %s for %s: "%c" expected",
  277.                                      buf, what, quote));
  278.         }
  279.         if (!s_SockWrite(sock, "RCPT TO: <")  ||
  280.             !s_SockWrite(sock, buf)           ||
  281.             !s_SockWrite(sock, ">" MX_CRLF)) {
  282.             SENDMAIL_RETURN(write_error);
  283.         }
  284.         if (!s_SockReadResponse(sock, 250, 251, buf, buf_size))
  285.             SENDMAIL_RETURN2(proto_error, buf);
  286.         if (!c)
  287.             break;
  288.     }
  289.     return 0;
  290. }
  291. #define SENDMAIL_SENDRCPT(what, list, buffer)                              
  292.     s_SendRcpt(sock, list, buffer, sizeof(buffer), what,                   
  293.                "Write error in RCPT (" what ") command",                   
  294.                "Protocol error in RCPT (" what ") command")
  295. #define SENDMAIL_READ_RESPONSE(code, altcode, buffer)                      
  296.     s_SockReadResponse(sock, code, altcode, buffer, sizeof(buffer))
  297. const char* CORE_SendMailEx(const char*          to,
  298.                             const char*          subject,
  299.                             const char*          body,
  300.                             const SSendMailInfo* uinfo)
  301. {
  302.     const SSendMailInfo* info;
  303.     SSendMailInfo ainfo;
  304.     char buffer[1024];
  305.     SOCK sock = 0;
  306.     info = uinfo ? uinfo : SendMailInfo_Init(&ainfo);
  307.     if (info->magic_number != MX_MAGIC_NUMBER)
  308.         SENDMAIL_RETURN("Invalid magic number");
  309.     if ((!to         ||  !*to)        &&
  310.         (!info->cc   ||  !*info->cc)  &&
  311.         (!info->bcc  ||  !*info->bcc)) {
  312.         SENDMAIL_RETURN("At least one message recipient must be specified");
  313.     }
  314.     /* Open connection to sendmail */
  315.     if (SOCK_Create(info->mx_host, info->mx_port, &info->mx_timeout, &sock)
  316.         != eIO_Success) {
  317.         SENDMAIL_RETURN("Cannot connect to sendmail");
  318.     }
  319.     SOCK_SetTimeout(sock, eIO_ReadWrite, &info->mx_timeout);
  320.     /* Follow the protocol conversation, RFC821 */
  321.     if (!SENDMAIL_READ_RESPONSE(220, 0, buffer))
  322.         SENDMAIL_RETURN2("Protocol error in connection init", buffer);
  323.     if (SOCK_gethostname(buffer, sizeof(buffer)) != 0)
  324.         SENDMAIL_RETURN("Unable to get local host name");
  325.     if (!s_SockWrite(sock, "HELO ")         ||
  326.         !s_SockWrite(sock, buffer)          ||
  327.         !s_SockWrite(sock, MX_CRLF)) {
  328.         SENDMAIL_RETURN("Write error in HELO command");
  329.     }
  330.     if (!SENDMAIL_READ_RESPONSE(250, 0, buffer))
  331.         SENDMAIL_RETURN2("Protocol error in HELO command", buffer);
  332.     if (!s_SockWrite(sock, "MAIL FROM: <")  ||
  333.         !s_SockWrite(sock, info->from)      ||
  334.         !s_SockWrite(sock, ">" MX_CRLF)) {
  335.         SENDMAIL_RETURN("Write error in MAIL command");
  336.     }
  337.     if (!SENDMAIL_READ_RESPONSE(250, 0, buffer))
  338.         SENDMAIL_RETURN2("Protocol error in MAIL command", buffer);
  339.     if (to && *to) {
  340.         const char* error = SENDMAIL_SENDRCPT("To", to, buffer);
  341.         if (error)
  342.             return error;
  343.     }
  344.     if (info->cc && *info->cc) {
  345.         const char* error = SENDMAIL_SENDRCPT("Cc", info->cc, buffer);
  346.         if (error)
  347.             return error;
  348.     }
  349.     if (info->bcc && *info->bcc) {
  350.         const char* error = SENDMAIL_SENDRCPT("Bcc", info->bcc, buffer);
  351.         if (error)
  352.             return error;
  353.     }
  354.     if (!s_SockWrite(sock, "DATA" MX_CRLF))
  355.         SENDMAIL_RETURN("Write error in DATA command");
  356.     if (!SENDMAIL_READ_RESPONSE(354, 0, buffer))
  357.         SENDMAIL_RETURN2("Protocol error in DATA command", buffer);
  358.     if (!info->mx_no_header) {
  359.         /* Follow RFC822 to compose message headers. Note that
  360.          * 'Date:'and 'From:' are both added by sendmail automatically.
  361.          */ 
  362.         if (!s_SockWrite(sock, "Subject: ")           ||
  363.             (subject && !s_SockWrite(sock, subject))  ||
  364.             !s_SockWrite(sock, MX_CRLF))
  365.             SENDMAIL_RETURN("Write error in sending subject");
  366.         if (to && *to) {
  367.             if (!s_SockWrite(sock, "To: ")            ||
  368.                 !s_SockWrite(sock, to)                ||
  369.                 !s_SockWrite(sock, MX_CRLF))
  370.                 SENDMAIL_RETURN("Write error in sending To");
  371.         }
  372.         if (info->cc && *info->cc) {
  373.             if (!s_SockWrite(sock, "Cc: ")            ||
  374.                 !s_SockWrite(sock, info->cc)          ||
  375.                 !s_SockWrite(sock, MX_CRLF))
  376.                 SENDMAIL_RETURN("Write error in sending Cc");
  377.         }
  378.     } else if (subject && *subject)
  379.         CORE_LOG(eLOG_Warning,"[SendMail]  Subject ignored in as-is messages");
  380.     if (!s_SockWrite(sock, "X-Mailer: CORE_SendMail (NCBI "
  381.                      NCBI_SENDMAIL_TOOLKIT " Toolkit)" MX_CRLF))
  382.         SENDMAIL_RETURN("Write error in sending mailer information");
  383.     assert(sizeof(buffer) > sizeof(MX_CRLF) && sizeof(MX_CRLF) >= 3);
  384.     if (info->header && *info->header) {
  385.         size_t n = 0, m = strlen(info->header);
  386.         int/*bool*/ newline = 0/*false*/;
  387.         while (n < m) {
  388.             size_t k = 0;
  389.             while (k < sizeof(buffer) - sizeof(MX_CRLF)) {
  390.                 if (info->header[n] == 'n') {
  391.                     memcpy(&buffer[k], MX_CRLF, sizeof(MX_CRLF) - 1);
  392.                     k += sizeof(MX_CRLF) - 1;
  393.                     newline = 1/*true*/;
  394.                 } else {
  395.                     if (info->header[n] != 'r'  ||  !newline)
  396.                         buffer[k++] = info->header[n];
  397.                     newline = 0/*false*/;
  398.                 }
  399.                 if (++n >= m)
  400.                     break;
  401.             }
  402.             buffer[k] = 0;
  403.             if (!s_SockWrite(sock, buffer))
  404.                 SENDMAIL_RETURN("Write error while sending custom header");
  405.         }
  406.         if (!newline && !s_SockWrite(sock, MX_CRLF))
  407.             SENDMAIL_RETURN("Write error while finalizing custom header");
  408.     }
  409.     if (body) {
  410.         size_t n = 0, m = info->body_size ? info->body_size : strlen(body);
  411.         int/*bool*/ newline = 0/*false*/;
  412.         if (!info->mx_no_header  &&  m) {
  413.             if (!s_SockWrite(sock, MX_CRLF))
  414.                 SENDMAIL_RETURN("Write error in message body delimiter");
  415.         }
  416.         while (n < m) {
  417.             size_t k = 0;
  418.             while (k < sizeof(buffer) - sizeof(MX_CRLF)) {
  419.                 if (body[n] == 'n') {
  420.                     memcpy(&buffer[k], MX_CRLF, sizeof(MX_CRLF) - 1);
  421.                     k += sizeof(MX_CRLF) - 1;
  422.                     newline = 1/*true*/;
  423.                 } else {
  424.                     if (body[n] != 'r'  ||  !newline) {
  425.                         if (body[n] == '.'  &&  (newline  ||  !n)) {
  426.                             buffer[k++] = '.';
  427.                             buffer[k++] = '.';
  428.                         } else
  429.                             buffer[k++] = body[n];
  430.                     }
  431.                     newline = 0/*false*/;
  432.                 }
  433.                 if (++n >= m)
  434.                     break;
  435.             }
  436.             buffer[k] = 0;
  437.             if (!s_SockWrite(sock, buffer))
  438.                 SENDMAIL_RETURN("Write error while sending message body");
  439.         }
  440.         if ((!newline  &&  m  &&  !s_SockWrite(sock, MX_CRLF))
  441.             ||  !s_SockWrite(sock, "." MX_CRLF)) {
  442.             SENDMAIL_RETURN("Write error while finalizing message body");
  443.         }
  444.     } else if (!s_SockWrite(sock, "." MX_CRLF))
  445.         SENDMAIL_RETURN("Write error while finalizing message");
  446.     if (!SENDMAIL_READ_RESPONSE(250, 0, buffer))
  447.         SENDMAIL_RETURN2("Protocol error in sending message", buffer);
  448.     if (!s_SockWrite(sock, "QUIT" MX_CRLF))
  449.         SENDMAIL_RETURN("Write error in QUIT command");
  450.     if (!SENDMAIL_READ_RESPONSE(221, 0, buffer))
  451.         SENDMAIL_RETURN2("Protocol error in QUIT command", buffer);
  452.     SOCK_Close(sock);
  453.     return 0;
  454. }
  455. #undef SENDMAIL_READ_RESPONSE
  456. #undef SENDMAIL_SENDRCPT
  457. #undef SENDMAIL_RETURN2
  458. #undef SENDMAIL_RETURN
  459. /*
  460.  * ---------------------------------------------------------------------------
  461.  * $Log: ncbi_sendmail.c,v $
  462.  * Revision 1000.1  2004/02/12 21:52:59  gouriano
  463.  * PRODUCTION: UPGRADED [CORE_001] Dev-tree R6.22
  464.  *
  465.  * Revision 6.22  2004/01/07 19:51:36  lavr
  466.  * Try to obtain user name from USERNAME env.var. on Windows, else fallback
  467.  *
  468.  * Revision 6.21  2003/12/09 15:38:39  lavr
  469.  * Take advantage of SSendMailInfo::body_size;  few little makeup changes
  470.  *
  471.  * Revision 6.20  2003/12/04 14:55:09  lavr
  472.  * Extend API with no-header and multiple recipient capabilities
  473.  *
  474.  * Revision 6.19  2003/04/18 20:59:51  lavr
  475.  * Mixed up SMTP_BADCODE and SMTP_BADREPLY rearranged in order
  476.  *
  477.  * Revision 6.18  2003/03/24 19:46:17  lavr
  478.  * Few minor changes; do not init SSendMailInfo passed as NULL
  479.  *
  480.  * Revision 6.17  2003/02/08 21:05:55  lavr
  481.  * Unimportant change in comments
  482.  *
  483.  * Revision 6.16  2002/10/28 15:43:29  lavr
  484.  * Use "ncbi_ansi_ext.h" privately and use strncpy0()
  485.  *
  486.  * Revision 6.15  2002/09/24 15:05:45  lavr
  487.  * Log moved to end
  488.  *
  489.  * Revision 6.14  2002/08/14 18:55:39  lavr
  490.  * Close socket on error return (was forgotten)
  491.  *
  492.  * Revision 6.13  2002/08/12 15:12:31  lavr
  493.  * Use persistent SOCK_Write()
  494.  *
  495.  * Revision 6.12  2002/08/07 16:33:15  lavr
  496.  * Changed EIO_ReadMethod enums accordingly; log moved to end
  497.  *
  498.  * Revision 6.11  2002/02/11 20:36:44  lavr
  499.  * Use "ncbi_config.h"
  500.  *
  501.  * Revision 6.10  2001/07/13 20:15:12  lavr
  502.  * Write lock then unlock when using not MT-safe s_ComposeFrom()
  503.  *
  504.  * Revision 6.9  2001/05/18 20:41:43  lavr
  505.  * Beautifying: change log corrected
  506.  *
  507.  * Revision 6.8  2001/05/18 19:52:24  lavr
  508.  * Tricks in macros to keep Sun C compiler silent from warnings (details below)
  509.  *
  510.  * Revision 6.7  2001/03/26 18:39:24  lavr
  511.  * Casting to (unsigned char) instead of (int) for ctype char.class macros
  512.  *
  513.  * Revision 6.6  2001/03/06 04:32:00  lavr
  514.  * Better custom header processing
  515.  *
  516.  * Revision 6.5  2001/03/02 20:09:06  lavr
  517.  * Typo fixed
  518.  *
  519.  * Revision 6.4  2001/03/01 00:30:23  lavr
  520.  * Toolkit configuration moved to ncbi_sendmail_.c
  521.  *
  522.  * Revision 6.3  2001/02/28 21:11:47  lavr
  523.  * Bugfix: buffer overrun
  524.  *
  525.  * Revision 6.2  2001/02/28 17:48:53  lavr
  526.  * Some fixes; larger intermediate buffer for message body
  527.  *
  528.  * Revision 6.1  2001/02/28 00:52:26  lavr
  529.  * Initial revision
  530.  *
  531.  * ===========================================================================
  532.  */