home *** CD-ROM | disk | FTP | other *** search
/ Dream 52 / Amiga_Dream_52.iso / Amiga / Workbench / Archivers / mpackPPC.lha / mpackPPC / src / macnsmtp.c < prev    next >
C/C++ Source or Header  |  1998-04-08  |  12KB  |  411 lines

  1. /* macnsmtp.c -- simple async SMTP client
  2.  */
  3. /* (C) Copyright 1995 by Carnegie Mellon University
  4.  * All Rights Reserved.
  5.  *
  6.  * Permission to use, copy, modify, distribute, and sell this software
  7.  * and its documentation for any purpose is hereby granted without
  8.  * fee, provided that the above copyright notice appear in all copies
  9.  * and that both that copyright notice and this permission notice
  10.  * appear in supporting documentation, and that the name of Carnegie
  11.  * Mellon University not be used in advertising or publicity
  12.  * pertaining to distribution of the software without specific,
  13.  * written prior permission.  Carnegie Mellon University makes no
  14.  * representations about the suitability of this software for any
  15.  * purpose.  It is provided "as is" without express or implied
  16.  * warranty.
  17.  *
  18.  * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
  19.  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  20.  * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
  21.  * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  22.  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
  23.  * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
  24.  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
  25.  * SOFTWARE.
  26.  */
  27. /* (C) Copyright 1994-1995 by Christopher J. Newman
  28.  * All Rights Reserved.
  29.  *
  30.  * Permission to use, copy, modify, distribute, and sell this software and its
  31.  * documentation for any purpose is hereby granted without fee, provided that
  32.  * the above copyright notice appear in all copies and that both that
  33.  * copyright notice and this permission notice appear in supporting
  34.  * documentation, and that the name of Christopher J. Newman not be used in
  35.  * advertising or publicity pertaining to distribution of the software without
  36.  * specific, written prior permission.  Christopher J. Newman makes no
  37.  * representations about the suitability of this software for any purpose.  It
  38.  * is provided "as is" without express or implied warranty.
  39.  *
  40.  * CHRISTOPHER J. NEWMAN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
  41.  * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT
  42.  * SHALL CHRISTOPHER J. NEWMAN BE LIABLE FOR ANY SPECIAL, INDIRECT OR
  43.  * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
  44.  * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
  45.  * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
  46.  * OF THIS SOFTWARE.
  47.  *
  48.  * Author:    Christopher J. Newman
  49.  * Message:    This is a nifty program.
  50.  */
  51.  
  52. #include "macnapp.h"
  53.  
  54. #define SMTP_PORT 25
  55.  
  56. typedef struct {
  57.      na_win w;
  58.      void *context;        /* user context */
  59.      short num, rcpt;    /* recipient count (num), and sent (rcpt) */
  60.      na_smtpstat statf;    /* callback */
  61.      na_tcp tcpid;        /* TCP id */
  62.      short state;        /* SMTP state (see below) */
  63.      short count;        /* bytes used in linebuf */
  64.      short refnum;        /* input file */
  65.      short crfound:1;    /* found a CR in SMTP server data */
  66.      short crlf:1;        /* found a CRLF in SMTP server data */
  67.      short crtrans:1;    /* translate CR to CRLF in file */
  68.      long headsize;        /* size of extra headers starting at data */
  69.      long fsize, fdone;    /* file size & amount written */
  70.      long bufsize;        /* usable buffer size */
  71.      Ptr buf;            /* output buffer */
  72.      char linebuf[1024];    /* input line buffer */
  73.      char data[1];        /* header & envelope */
  74. } na_smtpwin;
  75.  
  76. #define sw ((na_smtpwin *) w)
  77.  
  78. /* states: */
  79. #define S_conn  0  /* connecting to server */
  80. #define S_greet 1  /* waiting for greeting */
  81. #define S_hello 2  /* waiting for local host lookup and HELO reply */
  82. #define S_mailf 3  /* waiting for MAIL FROM reply */
  83. #define S_rcpt  4  /* waiting for RCPT TO reply */
  84. #define S_data  5  /* waiting for DATA continue reply */
  85. #define S_send  6  /* transmitting data */
  86. #define S_done  7  /* waiting for DATA success reply */
  87. #define S_quit  8  /* waiting for QUIT reply */
  88. #define S_wait  9  /* waiting for connection close */
  89. #define S_close 10 /* closed */
  90.  
  91. /* generate and submit SMTP command line (put command & data together with CRLF ending)
  92.  * returns NATCPwrite result code
  93.  */
  94. static int SMTPsend(na_win *w, char *com, char *data)
  95. {
  96.     char buf[512];
  97.     char *dest = buf;
  98.     int result = 0;
  99.     
  100.     while ((*dest = *com) != '\0') ++dest, ++com;
  101.     if (data) {
  102.         while ((*dest = *data++) != '\0') ++dest;
  103.         if (com[-1] == '<') *dest++ = '>';
  104.     }
  105.     *dest++ = '\r';
  106.     *dest++ = '\n';
  107.     result = NATCPwrite(sw->tcpid, buf, dest - buf, -1);
  108.     
  109.     return (result);
  110. }
  111.  
  112. /* do final callback
  113.  */
  114. static void smtpclose(na_win *w, short code, short err, long size, char *data)
  115. {
  116.     if (sw->state < S_wait) {
  117.         NATCPclose(sw->tcpid);
  118.         FSClose(sw->refnum);
  119.         sw->state = S_wait;
  120.         (*sw->statf)(sw->context, code, err, size, data);
  121.     }
  122. }
  123.  
  124. /* TCP read/write callback
  125.  */
  126. static void readp(void *wh, na_tcp s, short status, long size, char *data)
  127. {
  128.     na_win *w, **taskh;
  129.     char *dest;
  130.     short major, count, smtpcode;
  131.     
  132.     /* make sure our SMTP task still exists */
  133.     for (taskh = NAtask; taskh && taskh != wh; taskh = (*taskh)->task);
  134.     if (!taskh) return;
  135.     
  136.     /* handle TCP result */
  137.     w = NAlockWindow((na_win **) wh);
  138.     if (status == NATCP_connect) {
  139.         /* deal with new connection */
  140.         sw->state = S_greet;
  141.     } else if (status < 0) {
  142.         /* deal with TCP errors */
  143.         smtpclose(w, NASMTP_tcpfail, status, size, NULL);
  144.         if (status == NATCP_closed) sw->state = S_close;
  145.     } else if (status & NATCP_closing) {
  146.         /* deal with a closed connection */
  147.         if (sw->state < S_wait) {
  148.             smtpclose(w, NASMTP_conclosed, 0, 0, NULL);
  149.         }
  150.     } else if (status & NATCP_data) {
  151.         do {
  152.             /* buffer SMTP line */
  153.             dest = sw->linebuf + sw->count;
  154.             while (size && sw->count < sizeof (sw->linebuf)) {
  155.                 --size, ++sw->count;
  156.                 if (sw->crfound && *data == '\n') {
  157.                     *--dest = '\0';
  158.                     --sw->count;
  159.                     ++data;
  160.                     sw->crfound = 0;
  161.                     sw->crlf = 1;
  162.                     break;
  163.                 }
  164.                 sw->crfound = (*dest++ = *data++) == '\r';
  165.             }
  166.             if (!sw->crlf) {
  167.                 if (sw->count == sizeof (sw->linebuf)) {
  168.                     sw->linebuf[sw->count - 1] = '\0';
  169.                     smtpclose(w, NASMTP_badprot, 0, 0, sw->linebuf);
  170.                 }
  171.                 break;
  172.             }
  173.             sw->crlf = 0;
  174.             /* parse SMTP result code */
  175.             dest = sw->linebuf;
  176.             if (sw->count < 3 || !isdigit(dest[0])
  177.                 || !isdigit(dest[1]) || !isdigit(dest[2])) {
  178.                 smtpclose(w, NASMTP_badprot, 0, 0, dest);
  179.                 break;
  180.             }
  181.             sw->count = 0;
  182.             major = dest[0] - '0';
  183.             smtpcode = major * 100 + (dest[1] - '0') * 10 + (dest[2] - '0');
  184.             /* handle reply continuation */
  185.             if (dest[3] == '-') continue;
  186.             /* handle major errors */
  187.             if (major > 3) {
  188.                 if (sw->state != S_rcpt) {
  189.                     smtpclose(w, major == 4 ? NASMTP_temperr : NASMTP_permerr,
  190.                         smtpcode, sw->state, dest + 3);
  191.                     break;
  192.                 }
  193.                 (*sw->statf)(sw->context, NASMTP_badaddr, smtpcode, 0, sw->linebuf + 3);
  194.             }
  195.             dest = sw->data + sw->headsize;
  196.             /* state changes */
  197.             switch (sw->state) {
  198.                 case S_greet:
  199.                     if (sw->buf) {
  200.                         SMTPsend(w, "HELO ", sw->buf);
  201.                         if (sw->buf) DisposPtr(sw->buf);
  202.                     }
  203.                     sw->state = S_hello;
  204.                     break;
  205.                 case S_hello:
  206.                     SMTPsend(w, "MAIL FROM:<", dest);
  207.                     (*sw->statf)(sw->context, NASMTP_progress, 5, 0, 0);
  208.                     sw->state = S_mailf;
  209.                     break;
  210.                 case S_mailf:
  211.                 case S_rcpt:
  212.                     count = ++sw->rcpt;
  213.                     if (count < sw->num + 1) {
  214.                         while (count--) {
  215.                             while (*dest++);
  216.                         }
  217.                         SMTPsend(w, "RCPT TO:<", dest);
  218.                         (*sw->statf)(sw->context, NASMTP_progress, 5 + 10 * sw->rcpt / sw->num, 0, 0);
  219.                     } else {
  220.                         sw->state = S_data;
  221.                         SMTPsend(w, "DATA", 0);
  222.                         (*sw->statf)(sw->context, NASMTP_progress, 20, 0, 0);
  223.                     }
  224.                     break;
  225.                 case S_data:
  226.                     if (major != 3) {
  227.                         smtpclose(w, NASMTP_badprot, 0, 0, dest);
  228.                         break;
  229.                     }
  230.                     sw->state = S_send;
  231.                     if (sw->headsize) {
  232.                         sw->buf = NewPtr(sw->bufsize = sw->headsize);
  233.                         if (!sw->buf) {
  234.                             smtpclose(w, NASMTP_nomem, 0, 0, NULL);
  235.                             break;
  236.                         }
  237.                         memcpy(sw->buf, sw->data, sw->headsize);
  238.                     }
  239.                 case S_send:
  240.                     /* NOTE: this case should never happen */
  241.                     break;
  242.                 case S_done:
  243.                     sw->state = S_quit;
  244.                     SMTPsend(w, "QUIT", NULL);
  245.                     (*sw->statf)(sw->context, NASMTP_progress, 95, 0, 0);
  246.                     break;
  247.                 case S_quit:
  248.                     smtpclose(w, NASMTP_completed, 0, 0, 0);
  249.                     break;
  250.             }
  251.         } while (size);
  252.     }
  253.     NAunlockWindowh((na_win **) wh, w)
  254. }
  255.  
  256. /* TCP gethost callback
  257.  */
  258. static void hostp(void *wh, na_tcp s, short status, long size, char *data)
  259. {
  260.     na_win *w, **taskh;
  261.     
  262.     /* make sure our task still exists */
  263.     for (taskh = NAtask; taskh && taskh != wh; taskh = (*taskh)->task);
  264.     if (!taskh) return;
  265.     
  266.     /* store host/error */
  267.     w = NAlockWindow((na_win **) wh);
  268.     if (status == NATCP_connect) {
  269.         if (sw->state == S_hello) {
  270.             SMTPsend(w, "HELO ", data);
  271.         } else {
  272.             sw->buf = NewPtr(size + 1);
  273.             if (sw->buf == NULL) {
  274.                 smtpclose(w, NASMTP_nomem, 0, 0, NULL);
  275.             } else {
  276.                 memcpy(sw->buf, data, size + 1);
  277.             }
  278.         }
  279.     } else {
  280.         smtpclose(w, NASMTP_tcpfail, status, size, NULL);
  281.     }
  282.     NAunlockWindowh((na_win **) wh, w);
  283. }
  284.  
  285. /* translate CR to CRLF
  286.  */
  287. static void crtocrlf(char *buf, long *size)
  288. {
  289.     long crcount = 0;
  290.     char *src, *dst, *end = buf + *size;
  291.     
  292.     for (src = buf; src < end; ++src) {
  293.         if (src[0] == '\r') ++crcount;
  294.     }
  295.     src = end - 1;
  296.     for (dst = src + crcount; dst > src; *dst-- = *src--) {
  297.         if (*src == '\r') *dst-- = '\n';
  298.     }
  299.     *size += crcount;
  300. }
  301.  
  302. /* SMTP task
  303.  */
  304. static short taskp(na_win *w)
  305. {
  306.     OSErr oserr;
  307.     
  308.     if (sw->state == S_send || sw->state == S_done) {
  309.         /*XXX: could be generous with NewPtr() failure if a NATCPwritePending() */
  310.         if (!sw->bufsize) {
  311.             if ((sw->buf = NewPtr(8192)) == NULL) {
  312.                 smtpclose(w, NASMTP_nomem, 0, 0, NULL);
  313.                 return (NA_NOTPROCESSED);
  314.             }
  315.             sw->bufsize = sw->crtrans ? 4096 : 8192;
  316.             oserr = FSRead(sw->refnum, &sw->bufsize, sw->buf);
  317.             if (oserr != noErr && oserr != eofErr) sw->bufsize = 0;
  318.             if (!sw->bufsize) {
  319.                 if (oserr == eofErr && sw->state == S_send) {
  320.                     memcpy(sw->buf, "\r\n.\r\n", 5);
  321.                     sw->bufsize = 5;
  322.                     sw->state = S_done;
  323.                 } else {
  324.                     DisposPtr(sw->buf);
  325.                 }
  326.             } else {
  327.                 if (sw->crtrans) {
  328.                     crtocrlf(sw->buf, &sw->bufsize);
  329.                 }
  330.                 (*sw->statf)(sw->context, NASMTP_progress, 20
  331.                     + 70 * (sw->fdone += sw->bufsize) / sw->fsize, 0, 0);
  332.             }
  333.         }
  334.         if (sw->bufsize && NATCPwrite(sw->tcpid, sw->buf, sw->bufsize, 1) == NATCP_data) {
  335.             sw->bufsize = 0;
  336.         }
  337.     }
  338.     
  339.     return (sw->state == S_close ? NA_REQCLOSE : NA_NOTPROCESSED);
  340. }
  341.  
  342. /* SMTP close procedure
  343.  *  IMPORTANT: if the user quits during mail transmission, we want to
  344.  *    warn the user that mail will be lost.
  345.  */
  346. static short closep(na_win *w)
  347. {
  348.     if (sw->state < S_wait) {
  349.         /*XXX: put modal dialog here, allow abort of close */
  350.         if (sw->tcpid >= 0) NATCPclose(sw->tcpid);
  351.         FSClose(sw->refnum);
  352.     }
  353.     
  354.     return (NA_CLOSED);
  355. }
  356.  
  357. /* submit file to SMTP:
  358.  *  creates SMTP task, initializes data, starts TCP connection
  359.  *  copies from & dest addresses, so they don't need to persist
  360.  *  task is not completed until statf() is called
  361.  */
  362. void NASMTPsubmit(na_smtpstat statf, char *server, FSSpec *fspec, Handle headers,
  363.                 Handle envelope, short flags, void *context)
  364. {
  365.      long size;
  366.      na_win **wh, *w;
  367.      char *src, *dst;
  368.      OSErr oserr;
  369.  
  370.     /* create task */
  371.     size = sizeof (na_smtpwin);
  372.     if (headers) size += GetHandleSize(headers);
  373.     size += GetHandleSize(envelope);
  374.      wh = NAaddtask(taskp, size);
  375.      if (!wh) {
  376.          (*statf)(context, NASMTP_nomem, 0, 0, 0);
  377.          return;
  378.      }
  379.      
  380.      /* init task */
  381.      w = NAlockWindow(wh);
  382.      w->type = NA_SMTPTYPE;
  383.      w->closep = closep;
  384.      sw->context = context;
  385.      sw->statf = statf;
  386.     if (headers && (sw->headsize = GetHandleSize(headers))) {
  387.         memcpy(sw->data, (char *) *headers, sw->headsize);
  388.     }
  389.     size = GetHandleSize(envelope);
  390.     sw->num = -1;
  391.     dst = sw->data + sw->headsize;
  392.     for (src = (char *) *envelope; size; --size) {
  393.         if ((*dst++ = *src++) == '\0') ++sw->num;
  394.     }
  395.     if (flags & NASMTP_crtrans) sw->crtrans = 1;
  396.         
  397.     /* open file */
  398.     if ((oserr = HOpen(fspec->vRefNum, fspec->parID, fspec->name,
  399.         fsRdPerm, &sw->refnum)) != noErr) {
  400.         (*statf)(context, NASMTP_oserr, 0, oserr, 0);
  401.         NAcloseWindow(w, NA_CLOSED);
  402.         return;
  403.     }
  404.     GetEOF(sw->refnum, &sw->fsize);
  405.     
  406.     /* open MacTCP */
  407.      sw->tcpid = NATCPopen(readp, (void *) wh, server, SMTP_PORT, 0);
  408.      if (sw->tcpid != -1) NATCPgethost(hostp, (void *) wh);
  409.      NAunlockWindowh(wh, w);
  410. }
  411.