1 |
edhill |
1.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 |
|
|
} |