AlmSmtpReceiver.Mod 28 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111
  1. (* Aubrey McIntosh, Ph.D. Jan 21, 2003
  2. * This code may be distributed under the same terms and conditions as the Bluebottle operating system
  3. * from ETH, Zürich.
  4. *
  5. * RFC 821 Receiver module. Listen on port 25, put messages and envelopes into files.
  6. *
  7. * Hand modified Coco-R output.
  8. * The scanner differs from the Coco provided scanner.
  9. *
  10. * This code is a result of hand merging a working but poorly designed prototype,
  11. * AosSMTPReceiver1.Mod, and the Coco output.
  12. *
  13. * Jan 21, 2003 21:53 CST: The merge is essentially complete, the scanner has not been
  14. * written, a code walk through has not been done. i.e. major omissions may exist.
  15. * Jan 23, 2003 16:35 CST: Accepted first message.
  16. * Jan 25, 2003 20:00: Have log files. Capture raw data sendmail sends here.
  17. * Jan 26, 2003 20:00: Place onto alternate path: FAT:/Mail/<message name>
  18. * Feb 1, 2003 0.1.28 Add Logging: flood of connections from 160.94.128.45
  19. * Feb 11, 2003 0.1.29 Test whether DNS extension is on name when writing message id.
  20. * Feb 18, 2003 0.1.33 Limit total number of connections. Place diagnostics in log.
  21. * accept core.inf.ethz.ch always.
  22. * Feb 19, 2003 0.1.34 Add diagnostic test for EOF condition.
  23. * 0.1.38 Fix error where 1st of many messages is dropped (overwritten?)
  24. * Feb 23, 2003
  25. * 0.1.40 Reconcile behavior of emwac and the .Rcp file format.
  26. * Mar 16, 2003
  27. * 0.1.41 Make file name initialization more robust.
  28. * Mar 31, 2003 The program seems to hang if the remote sender does not issue QUIT.
  29. * Jan 3, 2004 Name changed to AlmSMTPReceiver for release to BlueBottle community.
  30. * Changes 0.1.42 appear lost in move.
  31. * Sept 21, 2004 Initialise config file on first execution. Use AOS filesystem names.
  32. (* Initial SMTP handshake *)
  33. BEGIN {EXCLUSIVE}
  34. TakeNumber;
  35. AdvanceNumber
  36. END;
  37. OpenLog;
  38. LOOP (*Parse SMTP*)
  39. OpenMail;
  40. IF Finished THEN EXIT END
  41. BEGIN {EXCLUSIVE}
  42. TakeNumber;
  43. AdvanceNumber
  44. END
  45. END
  46. *
  47. *
  48. * To Do:
  49. * Make robust. E.g. HALT is not good error recovery.
  50. * Integrate with Frey's Abstract Mail
  51. * Produce message and broadcast mechanism. E.g., for use in poping up display when VIP
  52. * sends message.
  53. *
  54. *)
  55. MODULE AlmSmtpReceiver;
  56. IMPORT DNS, Files, Streams, IP, Modules, KernelLog, TCP, TCPServices, Dates, Strings;
  57. CONST
  58. (* Some of the configurable items. *)
  59. AlmSmtpReceiverPort = 25; (* Well, semi-configurable. *)
  60. MaxActive = 3+1;
  61. ID = "BlueBottle Receiver ";
  62. Version = "MailBottle (0.2.00.16)";
  63. Rcp = ".Rcp";
  64. Msg = ".Msg";
  65. Log = ".Log";
  66. ConfigFileName = "mail.config";
  67. ToDisk = TRUE; (* Debug *)
  68. DebugMsg = FALSE;
  69. RcptInFileName = TRUE;
  70. MaxUserName = 11;
  71. Prefix = "In."; (*Administer must create this manually.*)
  72. AlwaysAccept = "129.132.178.196";
  73. (* End of these configurable items. *)
  74. (* Constants for the Scanner *)
  75. CONST
  76. EOF = 0X;
  77. maxLexLen = 127;
  78. noSym = 13;
  79. (* Types for the Scanner *)
  80. TYPE
  81. ErrorProc* = PROCEDURE (n: INTEGER);
  82. StartTable = ARRAY 128 OF INTEGER;
  83. (* Variables for the Scanner *)
  84. VAR
  85. errors*: INTEGER; (*number of errors detected*)
  86. lasterror* : INTEGER;
  87. charcount : LONGINT;
  88. getCalls : LONGINT;
  89. start: StartTable; (*start state for every character*)
  90. Pattern, Ack : ARRAY 6 OF CHAR;
  91. active : LONGINT;
  92. CONST
  93. maxP = 13;
  94. maxT = 13;
  95. nrSets = 3;
  96. setSize = 32; nSets = (maxT DIV setSize) + 1;
  97. SyEol = 1;
  98. SyCopy = 2;
  99. SyHelo =3;
  100. SyQuit =4;
  101. SyNoop =5;
  102. SyRset =6;
  103. SyData =7;
  104. SyDot =8;
  105. SyRcpt =9;
  106. SyTo =10;
  107. SyMail =11;
  108. SyFrom =12;
  109. SyTimeout = 14;
  110. Tab = 09X;
  111. LF = 0AX;
  112. CR = 0DX;
  113. TYPE
  114. SymbolSet = ARRAY nSets OF SET;
  115. TYPE
  116. String = ARRAY 128 OF CHAR;
  117. TokenPtr = POINTER TO Token;
  118. Token = RECORD s : String; next : TokenPtr END;
  119. EnvelopePtr = POINTER TO Envelope;
  120. Envelope = RECORD
  121. mta, revMta, from : String;
  122. to : TokenPtr;
  123. END;
  124. Message* = RECORD env* : EnvelopePtr; file* :Files.File; END;
  125. SmtpAgent* = OBJECT (TCPServices.Agent)
  126. VAR
  127. ch: CHAR; (*current input character*)
  128. res: WORD;
  129. out: Streams.Writer; in: Streams.Reader;
  130. log : Files.Writer;
  131. env : Envelope;
  132. thisName, verbSy : String;
  133. finished : BOOLEAN;
  134. sym: INTEGER; (* current input symbol *)
  135. state : INTEGER;
  136. badTokens : LONGINT;
  137. auxString : String;
  138. (* Support procedures *)
  139. PROCEDURE GetCh():CHAR;
  140. VAR ch : CHAR;
  141. BEGIN
  142. ch := in.Get();
  143. log.Char (ch); log.Update;
  144. RETURN ch
  145. END GetCh;
  146. PROCEDURE ConsumeName;
  147. BEGIN {EXCLUSIVE}
  148. COPY (nextName, thisName);
  149. UpdateName (nextName)
  150. END ConsumeName;
  151. PROCEDURE AvailableName;
  152. VAR
  153. name : String;
  154. msgFile: Files.File;
  155. BEGIN
  156. COPY (Prefix, name);
  157. AddExt (name, thisName);
  158. AddExt (name, Log);
  159. WHILE (Files.Old (name) # NIL)
  160. DO
  161. ConsumeName;
  162. COPY (Prefix, name);
  163. AddExt (name, thisName);
  164. AddExt (name, Log);
  165. msgFile := Files.Old (name);
  166. END;
  167. END AvailableName;
  168. PROCEDURE OpenLog; (*1 file per session. Name is same as when session opens, i.e., not agree w/ .Msg & .Rcp *)
  169. VAR
  170. msgFile: Files.File;
  171. name : String;
  172. BEGIN
  173. COPY (Prefix, name);
  174. AddExt (name, thisName);
  175. AddExt (name, Log);
  176. msgFile := Files.Old (name);
  177. ToLog0 ("before search."); KernelLog.Exit;
  178. WHILE msgFile # NIL
  179. DO
  180. ToLog0 ("during search."); KernelLog.String (name); KernelLog.Exit;
  181. ConsumeName;
  182. COPY (Prefix, name);
  183. AddExt (name, thisName);
  184. AddExt (name, Log);
  185. msgFile := Files.Old (name);
  186. END;
  187. ToLog0 ("after search."); KernelLog.Exit;
  188. msgFile := Files.New (name);
  189. Files.OpenWriter ( log, msgFile, 0);
  190. Files.Register (msgFile);
  191. END OpenLog;
  192. PROCEDURE ToMemory* (VAR token: ARRAY OF CHAR);
  193. VAR maxix, ix : LONGINT; trash, next : CHAR;
  194. BEGIN
  195. next := in.Peek();
  196. WHILE (next=" ") OR (next=Tab) DO trash := GetCh (); INC (charcount); next := in.Peek() END;
  197. maxix := LEN (token)-1;
  198. WHILE (next#" ") & (next#Tab) & (next#CR) & (next#LF)
  199. DO
  200. ch := GetCh (); INC (charcount); next := in.Peek(); (* Jan 23, 2003 v. 0.1.02 *)
  201. IF ix < maxix
  202. THEN
  203. token [ix] := ch;
  204. INC (ix)
  205. END
  206. END;
  207. token [ix] := 0X;
  208. Expect (SyCopy)
  209. END ToMemory;
  210. PROCEDURE DebugMsg1* (msg : ARRAY OF CHAR);
  211. BEGIN
  212. IF DebugMsg
  213. THEN
  214. out.String (msg);
  215. out.Ln;
  216. out.Update()
  217. END
  218. END DebugMsg1;
  219. PROCEDURE PutStatus1* (msg : ARRAY OF CHAR);
  220. BEGIN
  221. Confirm(SyEol); (*Expect is split to a Confirm / Get pair to let the output occur.*)
  222. out.String (msg);
  223. out.Ln;
  224. out.Update();
  225. Get
  226. END PutStatus1;
  227. PROCEDURE ChangeStatus1* (newsym : INTEGER; msg : ARRAY OF CHAR);
  228. BEGIN
  229. Confirm(SyEol);
  230. sym := newsym;
  231. out.String (msg);
  232. out.Ln;
  233. out.Update();
  234. END ChangeStatus1;
  235. PROCEDURE PutStatus2* (msg0, msg1 : ARRAY OF CHAR);
  236. BEGIN
  237. Confirm(SyEol); (*Expect is split to a Confirm / Get pair to let the output occur.*)
  238. out.String (msg0);
  239. out.String (msg1);
  240. out.Ln;
  241. out.Update; (* ignore out.res *)
  242. Get
  243. END PutStatus2;
  244. PROCEDURE ChangeStatus2* (newsym : INTEGER; msg0, msg1 : ARRAY OF CHAR);
  245. BEGIN
  246. Confirm(SyEol);
  247. sym := newsym;
  248. out.String (msg0);
  249. out.String (msg1);
  250. out.Ln;
  251. out.Update; (* ignore out.res *)
  252. END ChangeStatus2;
  253. PROCEDURE AddExt* ( VAR name : String; ext : ARRAY OF CHAR);
  254. VAR i, j, skipped : INTEGER;
  255. BEGIN
  256. i := 0;
  257. WHILE ( i < LEN(name)-1 ) & ~(name[i] < " ")
  258. DO
  259. INC (i)
  260. END;
  261. j := 0; skipped := 0;
  262. WHILE ( i+j < LEN(name)-1 ) & (j<LEN(ext)-1) & (ext[j] # 0X)
  263. DO
  264. IF (ext[j] = "<") OR (ext[j] = ">")
  265. THEN
  266. INC (j); INC (skipped)
  267. ELSE
  268. name[i+j-skipped] := ext[j];
  269. INC (j)
  270. END;
  271. END;
  272. name[i+j] := 0X
  273. END AddExt;
  274. PROCEDURE PutBareName ( name : String; VAR wr : Files.Writer );
  275. VAR ix : LONGINT; ch : CHAR;
  276. BEGIN
  277. ix := 0;
  278. WHILE (ix<LEN(name)) & (name[ix]#0X)
  279. DO
  280. ch := name [ix];
  281. IF (ch#"<") & (ch#">") THEN wr.Char (ch) END;
  282. INC (ix)
  283. END
  284. END PutBareName;
  285. PROCEDURE PutEnvelope ( (* not VAR! *) name : String );
  286. VAR envF : Files.File; ew : Files.Writer; to: TokenPtr;
  287. msgName, rcpPathName : String;
  288. BEGIN
  289. COPY (name, msgName);
  290. (*
  291. AddExt (msgName, "@");
  292. AddExt (msgName, NetSystem.hostName);
  293. AddExt (msgName, ".");
  294. AddExt (msgName, DNS.domain);
  295. *)
  296. COPY (Prefix, rcpPathName);
  297. AddExt (rcpPathName, name); (*alm 9/21/2004*)
  298. AddExt (rcpPathName, Rcp); (*Name with no prefix*)
  299. envF := Files.New (rcpPathName);
  300. (*A trap sometimes happens when here:
  301. Process: 354 run 0 3 01F159B0:AlmSmtpReceiver.SmtpAgent ATADisks.Interrupt.Wait pc=815 {}
  302. *)
  303. Files.OpenWriter ( ew, envF, 0);
  304. ew.String ("Message-ID: <");
  305. ew.String (msgName);
  306. (*
  307. ew.Char ("@");
  308. ew.String (DNS.domain);
  309. *)
  310. ew.Char (">");
  311. ew.Ln;
  312. ew.String ("Return-path: ");
  313. PutBareName (env.from, ew);
  314. ew.Ln;
  315. to := env.to;
  316. WHILE to # NIL DO
  317. ew.String ("Recipient: ");
  318. PutBareName (to.s, ew);
  319. to := to.next;
  320. ew.Ln;
  321. END;
  322. ew.Update;
  323. Files.Register (envF);
  324. END PutEnvelope;
  325. PROCEDURE UpdateName (VAR s : String);
  326. VAR i : INTEGER; ch : CHAR; carry : INTEGER;
  327. BEGIN
  328. i := 10; (* 10 digits significant in name *)
  329. carry := 1;
  330. WHILE (1<=i) & (carry = 1) DO
  331. ch := CHR (ORD(s[i]) + carry);
  332. IF '9' < ch
  333. THEN
  334. ch := "0";
  335. carry := 1
  336. ELSE
  337. carry := 0
  338. END;
  339. s[i] := ch;
  340. DEC (i)
  341. END
  342. END UpdateName;
  343. (* Begin Parser Productions *)
  344. PROCEDURE HELO*;
  345. VAR res : WORD;
  346. BEGIN
  347. Confirm(SyHelo);
  348. sym := SyCopy; ToMemory (env.mta);
  349. DNS.HostByNumber (SELF.client.fip, env.revMta, res);
  350. PutStatus2 ("250 Your email is welcome here, ", env.mta);
  351. END HELO;
  352. PROCEDURE RSET*;
  353. BEGIN
  354. Expect(SyRset);
  355. env.mta := ""; env.from := ""; env.to := NIL;
  356. PutStatus1 ("250 Requested mail action okay, completed.");
  357. END RSET;
  358. PROCEDURE NOOP*;
  359. BEGIN
  360. Expect(SyNoop);
  361. PutStatus1 ("250 Requested mail action okay, completed.");
  362. END NOOP;
  363. PROCEDURE QUIT*;
  364. BEGIN
  365. Expect(SyQuit);
  366. finished := TRUE;
  367. ChangeStatus1 (SyQuit, "221 Goodbye.."); (*Avoid executing another Get.*)
  368. client.Close();
  369. END QUIT;
  370. PROCEDURE RCPT*;
  371. VAR to : TokenPtr;
  372. BEGIN
  373. Expect(SyRcpt);
  374. Confirm(SyTo);
  375. NEW (to);
  376. sym := SyCopy; ToMemory (to.s);
  377. to.next := env.to; env.to := to;
  378. PutStatus2 ("250 Recipient okay: ", to.s);
  379. END RCPT;
  380. PROCEDURE Test;
  381. BEGIN
  382. IF in.Available() < 1
  383. THEN HALT( 44 )
  384. END
  385. END Test;
  386. PROCEDURE ToFile(name : String);
  387. VAR
  388. msg: Files.File;
  389. msgWr : Files.Writer;
  390. ix, testIx : LONGINT;
  391. receiveTime, remoteIP : String;
  392. PROCEDURE WriteIPNr( ip : IP.Adr );
  393. VAR result : WORD; str : ARRAY 128 OF CHAR;
  394. BEGIN
  395. IP.AdrToStr(ip, remoteIP);
  396. msgWr.String (" (");
  397. msgWr.String (remoteIP);
  398. DNS.HostByNumber (ip, str, result);
  399. msgWr.String (" --> ");
  400. IF result = DNS.Ok
  401. THEN
  402. msgWr.String (str)
  403. ELSE
  404. msgWr.String ("lookup failed.")
  405. END;
  406. msgWr.Char (")");
  407. END WriteIPNr;
  408. BEGIN
  409. AddExt (name, Msg);
  410. IF ToDisk THEN
  411. msg := Files.New (name);
  412. Files.OpenWriter ( msgWr, msg, 0);
  413. ToLog0 (name);
  414. KernelLog.Exit;
  415. Strings.FormatDateTime("www, dd mmm yyyy hh:nn:ss -0600 (CST)", Dates.Now(), receiveTime);
  416. msgWr.String ("Received: ");
  417. msgWr.Ln; msgWr.Char (Tab); msgWr.String ("from ");
  418. msgWr.String (env.mta);
  419. WriteIPNr(SELF.client.fip);
  420. msgWr.Ln; msgWr.Char (Tab); msgWr.String ("by ");
  421. msgWr.String (DNS.domain);
  422. WriteIPNr(SELF.client.int.localAdr);
  423. msgWr.Ln; msgWr.Char (Tab);
  424. msgWr.String ("with ");
  425. msgWr.String (Version);
  426. msgWr.String (" id "); msgWr.String (thisName);
  427. msgWr.Char ("@"); msgWr.String (DNS.domain);
  428. msgWr.Ln; msgWr.Char (Tab); msgWr.String ("for ");
  429. msgWr.String (env.to.s);
  430. msgWr.Char (Tab); msgWr.String ("; "); msgWr.String (receiveTime);
  431. msgWr.Ln
  432. END;
  433. ch := GetCh (); INC (charcount); (* Read first v 0.1.02 *)
  434. testIx := 0;
  435. LOOP
  436. IF in.res = Streams.EOF
  437. THEN
  438. ToLog0 ("EOF on input stream."); KernelLog.Exit; sym := SyEol; EXIT
  439. END;
  440. IF ch=Pattern[0]
  441. THEN
  442. LOOP
  443. ch := GetCh (); INC (charcount);
  444. testIx := 1;
  445. WHILE (testIx <= 4) & (ch=Pattern[testIx])
  446. DO
  447. IF testIx < 4
  448. THEN
  449. ch := GetCh ();
  450. INC (charcount);
  451. END;
  452. INC (testIx)
  453. END;
  454. IF DebugMsg
  455. THEN
  456. FOR ix := 0 TO testIx-1
  457. DO
  458. out.Char (Ack[ix])
  459. END;
  460. out.Update
  461. END;
  462. IF testIx=5
  463. THEN
  464. msgWr.Char (CR); msgWr.Char (LF);
  465. sym := SyEol; (*Have read both "." and CR/LF*)
  466. ELSE
  467. FOR ix := 0 TO testIx-1
  468. DO
  469. msgWr.Char (Pattern[ix])
  470. END;
  471. (* msgWr.Char (ch); *)
  472. (* testIx := 0 *)
  473. END;
  474. EXIT
  475. END;
  476. IF testIx=5 THEN EXIT END
  477. ELSE
  478. msgWr.Char (ch)
  479. END;
  480. IF testIx#0 THEN testIx := 0 (*Start test again at current character.*) ELSE ch := GetCh (); INC (charcount) END
  481. END ;
  482. IF DebugMsg THEN out.Char ("!"); out.Update END;
  483. IF ToDisk THEN msgWr.Update END;
  484. IF ToDisk THEN Files.Register (msg) END
  485. END ToFile;
  486. PROCEDURE DATA* (name : String);
  487. BEGIN
  488. Expect(SyData);
  489. ChangeStatus1 (SyCopy, "354 Send message now, end with CRLF . CRLF");
  490. sym := SyCopy; ToFile (name);
  491. Confirm(SyEol);
  492. END DATA;
  493. PROCEDURE AddUserToName (VAR thisName : String);
  494. VAR
  495. pos : INTEGER;
  496. BEGIN
  497. IF RcptInFileName
  498. THEN
  499. AddExt ( thisName, "."); (*Preparation for mailbox-in-name interpretation.*)
  500. pos := 0;
  501. WHILE (pos < LEN (thisName)) & (thisName [pos] # 0X) DO INC (pos) END;
  502. AddExt ( thisName, env.to.s); (*Preparation for mailbox-in-name interpretation.*)
  503. thisName [pos + MaxUserName] := 0X;
  504. WHILE (pos < LEN (thisName)) & (thisName [pos] # "@")
  505. DO
  506. INC (pos)
  507. END;
  508. IF pos < LEN (thisName) THEN thisName [pos] := 0X END;
  509. END;
  510. END AddUserToName;
  511. PROCEDURE MAIL*;
  512. VAR
  513. to : TokenPtr;
  514. pathName : String;
  515. localSym : INTEGER; (*to debug*)
  516. BEGIN
  517. Expect(SyMail);
  518. env.from := ""; env.to := NIL;
  519. Confirm(SyFrom);
  520. sym := SyCopy; ToMemory (env.from);
  521. PutStatus2 ("250 Sender okay. ", env.from);
  522. NEW( to );
  523. IF StartOf(1) THEN
  524. reset; IF finished THEN RETURN END;
  525. ELSIF (sym = SyRcpt) THEN
  526. RCPT;
  527. WHILE (sym = SyRcpt) DO
  528. RCPT;
  529. END ;
  530. AddUserToName (thisName);
  531. COPY (Prefix, pathName);
  532. AddExt (pathName, thisName);
  533. AddExt (pathName, Rcp);
  534. (* alm 3/16/2003 Skips previously used names. *)
  535. WHILE (Files.Old (pathName) # NIL)
  536. DO
  537. ConsumeName;
  538. AddUserToName (thisName);
  539. COPY (Prefix, pathName);
  540. AddExt (pathName, thisName);
  541. AddExt (pathName, Rcp);
  542. END;
  543. COPY (Prefix, pathName);
  544. AddExt (pathName, thisName);
  545. IF StartOf(1) THEN
  546. reset;
  547. ToLog0 ("Post RCPT cmd in mail.");
  548. KernelLog.Exit;
  549. IF finished THEN RETURN END;
  550. ELSIF (sym = SyData) THEN
  551. ToLog0 ("Data cmd in mail.");
  552. KernelLog.Exit;
  553. DATA (pathName);
  554. ELSE Error1(14)
  555. END ;
  556. ELSE Error1(15)
  557. END ;
  558. PutEnvelope (thisName);
  559. IF DebugMsg THEN out.Char ("@"); out.Update END;
  560. localSym := SELF.sym;
  561. PutStatus2 ("250 Your confirmation number is ", thisName);
  562. (* Feb. 22, 2003 *)
  563. CASE sym OF
  564. SyQuit : ToLog0 ("Quit detected.")
  565. | SyMail : ToLog0 ("Mail detected.")
  566. | SyRset : ToLog0 ("Rset detected.")
  567. | SyNoop : ToLog0 ("Noop detected.")
  568. | SyEol : ToLog0 ("dead connection detected.")
  569. ELSE
  570. ToLog0 ("Unexpected path in case statement.")
  571. END;
  572. KernelLog.Exit;
  573. IF sym IN {SyMail, SyRset, SyNoop} (*Noop DOES allow more mail in this session.*)
  574. THEN
  575. ToLog0 ("update name.");
  576. ConsumeName;
  577. KernelLog.Exit
  578. (* PutRegistry (nextName) *)
  579. ELSE
  580. ToLog0 ("Keep existing name.");
  581. KernelLog.Exit;
  582. RETURN
  583. END
  584. END MAIL;
  585. PROCEDURE reset;
  586. BEGIN
  587. DebugMsg1 ("Entering reset.");
  588. IF (sym = SyHelo) THEN HELO;
  589. ELSIF (sym = SyNoop) THEN NOOP;
  590. ELSIF (sym = SyRset) THEN RSET;
  591. ELSIF (sym = SyMail) THEN MAIL;
  592. ELSE Error1(16)
  593. END ;
  594. DebugMsg1 ("Exiting reset.")
  595. END reset;
  596. PROCEDURE Get;
  597. BEGIN
  598. INC (getCalls);
  599. ch := GetCh (); INC (charcount); (*No characters in buffer on entry.*)
  600. WHILE (ch=" ") OR (ch=Tab) DO ch := GetCh (); INC (charcount) END;
  601. IF ch > 7FX THEN ch := " " END;
  602. IF ("a"<=ch) & (ch<="z") THEN ch := CAP (ch) END;
  603. state := start[ORD(ch)];
  604. (*Intercept single character symbols to avoid read-ahead*)
  605. CASE state OF
  606. 24: sym := SyDot; RETURN
  607. | 3: IF (CAP(in.Peek()) ="R") THEN (* state := 35; (*does not block across CR LF on legal input.*) *)
  608. ELSE sym := SyCopy; RETURN
  609. END;
  610. ELSE (* Continue with multi character symbols. *)
  611. END;
  612. LOOP
  613. ch := GetCh (); INC (charcount);
  614. IF ("a"<=ch) & (ch<="z") THEN ch := CAP (ch) END;
  615. IF state > 0 THEN
  616. CASE state OF
  617. | 1: IF (ch=LF) THEN state := 2; sym := SyEol; RETURN
  618. ELSE sym := noSym; RETURN
  619. END;
  620. | 2: HALT (52) (*Avoid look ahead character read*)
  621. | 3: IF (ch ="R") THEN state := 35;
  622. ELSE sym := SyCopy; RETURN
  623. END;
  624. | 4: IF (ch ="E") THEN state := 5;
  625. ELSE sym := noSym; RETURN
  626. END;
  627. | 5: IF (ch ="L") THEN state := 6;
  628. ELSE sym := noSym; RETURN
  629. END;
  630. | 6: IF (ch ="O") THEN state := 7; sym := SyHelo; RETURN
  631. ELSE sym := noSym; RETURN
  632. END;
  633. | 7: HALT (57) (*Avoid look ahead character read*)
  634. | 8: IF (ch ="U") THEN state := 9;
  635. ELSE sym := noSym; RETURN
  636. END;
  637. | 9: IF (ch ="I") THEN state := 10;
  638. ELSE sym := noSym; RETURN
  639. END;
  640. | 10: IF (ch ="T") THEN state := 11; sym := SyQuit; RETURN
  641. ELSE sym := noSym; RETURN
  642. END;
  643. | 11: HALT (61) (*Avoid look ahead character read*)
  644. | 12: IF (ch ="O") THEN state := 13;
  645. ELSE sym := noSym; RETURN
  646. END;
  647. | 13: IF (ch ="O") THEN state := 14;
  648. ELSE sym := noSym; RETURN
  649. END;
  650. | 14: IF (ch ="P") THEN state := 15; sym := SyNoop; RETURN
  651. ELSE sym := noSym; RETURN
  652. END;
  653. | 15: HALT (65) (*Avoid look ahead character read*)
  654. | 16: IF (ch ="S") THEN state := 17;
  655. ELSIF (ch ="C") THEN state := 25;
  656. ELSE sym := noSym; RETURN
  657. END;
  658. | 17: IF (ch ="E") THEN state := 18;
  659. ELSE sym := noSym; RETURN
  660. END;
  661. | 18: IF (ch ="T") THEN state := 19; sym := SyRset; RETURN
  662. ELSE sym := noSym; RETURN
  663. END;
  664. | 19: HALT (69) (*Avoid look ahead character read*)
  665. | 20: IF (ch ="A") THEN state := 21;
  666. ELSE sym := noSym; RETURN
  667. END;
  668. | 21: IF (ch ="T") THEN state := 22;
  669. ELSE sym := noSym; RETURN
  670. END;
  671. | 22: IF (ch ="A") THEN state := 23; sym := SyData; RETURN
  672. ELSE sym := noSym; RETURN
  673. END;
  674. | 23: HALT (73) (*Avoid look ahead character read*)
  675. | 24: sym := SyDot; HALT(74); RETURN
  676. | 25: IF (ch ="P") THEN state := 26;
  677. ELSE sym := noSym; RETURN
  678. END;
  679. | 26: IF (ch ="T") THEN state := 27; sym := SyRcpt; RETURN
  680. ELSE sym := noSym; RETURN
  681. END;
  682. | 27: HALT (77) (*Avoid look ahead character read*)
  683. | 28: IF (ch ="O") THEN state := 29;
  684. ELSE sym := noSym; RETURN
  685. END;
  686. | 29: IF (ch =":") THEN state := 30; sym := SyTo; RETURN
  687. ELSE sym := noSym; RETURN
  688. END;
  689. | 30: HALT (80) (*Avoid look ahead character read*)
  690. | 31: IF (ch ="A") THEN state := 32;
  691. ELSE sym := noSym; RETURN
  692. END;
  693. | 32: IF (ch ="I") THEN state := 33;
  694. ELSE sym := noSym; RETURN
  695. END;
  696. | 33: IF (ch ="L") THEN state := 34; sym := SyMail; RETURN
  697. ELSE sym := noSym; RETURN
  698. END;
  699. | 34: HALT (84) (*Avoid look ahead character read*)
  700. | 35: IF (ch ="O") THEN state := 36;
  701. ELSE sym := noSym; RETURN
  702. END;
  703. | 36: IF (ch ="M") THEN state := 37;
  704. ELSE sym := noSym; RETURN
  705. END;
  706. | 37: IF (ch =":") THEN state := 38; sym := SyFrom; RETURN
  707. ELSE sym := noSym; RETURN
  708. END;
  709. | 38: HALT (88) (*Avoid look ahead character read*)
  710. | 39: sym := 0; ch := 0X; RETURN
  711. END (*CASE*)
  712. ELSE sym := noSym; RETURN (*NextCh already done*)
  713. END; (*IF*)
  714. END (*LOOP*)
  715. END Get;
  716. PROCEDURE ErrMsg(msg : String);
  717. BEGIN
  718. KernelLog.String (msg);
  719. END ErrMsg;
  720. PROCEDURE Error1(n: INTEGER);
  721. BEGIN
  722. INC(errors);
  723. lasterror := n;
  724. KernelLog.Enter;
  725. CASE n OF
  726. | 13: ErrMsg("??? expected")
  727. | 14: ErrMsg("invalid MAIL")
  728. | 15: ErrMsg("invalid MAIL")
  729. | 16: ErrMsg("invalid reset")
  730. ELSE END;
  731. KernelLog.Exit
  732. END Error1;
  733. PROCEDURE Error2 (n, sym: INTEGER);
  734. BEGIN
  735. INC(errors);
  736. lasterror := n;
  737. KernelLog.Enter;
  738. CASE n OF
  739. 0: ErrMsg("EOF expected, ")
  740. | 1: ErrMsg("Eol expected, ")
  741. | 2: ErrMsg("ident expected, ")
  742. | 3: ErrMsg("'HELO' expected, ")
  743. | 4: ErrMsg("'QUIT' expected, ")
  744. | 5: ErrMsg("'NOOP' expected, ")
  745. | 6: ErrMsg("'RSET' expected, ")
  746. | 7: ErrMsg("'DATA' expected, ")
  747. | 8: ErrMsg("'.' expected, ")
  748. | 9: ErrMsg("'RCPT' expected, ")
  749. | 10: ErrMsg("'TO:' expected, ")
  750. | 11: ErrMsg("'MAIL' expected, ")
  751. | 12: ErrMsg("'FROM:' expected, ")
  752. ELSE END;
  753. CASE sym OF
  754. 0: ErrMsg("EOF found")
  755. | 1: ErrMsg("Eol found")
  756. | 2: ErrMsg("ident found")
  757. | 3: ErrMsg("'HELO' found")
  758. | 4: ErrMsg("'QUIT' found")
  759. | 5: ErrMsg("'NOOP' found")
  760. | 6: ErrMsg("'RSET' found")
  761. | 7: ErrMsg("'DATA' found")
  762. | 8: ErrMsg("'.' found")
  763. | 9: ErrMsg("'RCPT' found")
  764. | 10: ErrMsg("'TO:' found")
  765. | 11: ErrMsg("'MAIL' found")
  766. | 12: ErrMsg("'FROM:' found")
  767. ELSE END;
  768. KernelLog.Exit;
  769. END Error2;
  770. PROCEDURE Confirm(n: INTEGER);
  771. BEGIN IF sym = n THEN (* Nothing *) ELSE Error2(n, sym) END
  772. END Confirm;
  773. PROCEDURE Expect(n: INTEGER);
  774. BEGIN IF sym = n THEN Get ELSE Error2(n, sym) END
  775. END Expect;
  776. PROCEDURE StartOf(s: INTEGER): BOOLEAN;
  777. BEGIN RETURN (sym MOD setSize) IN symSet[s, sym DIV setSize]
  778. END StartOf;
  779. PROCEDURE Who;
  780. VAR ipStr : String;
  781. BEGIN
  782. IP.AdrToStr (SELF.client.fip, ipStr);
  783. KernelLog.String (ipStr);
  784. END Who;
  785. PROCEDURE BackStagePass (pass : String) : BOOLEAN;
  786. VAR ipStr : String; ix: LONGINT;
  787. BEGIN
  788. IP.AdrToStr (SELF.client.fip, ipStr);
  789. ix := 0;
  790. WHILE (ix<=15) & (ipStr[ix] = pass[ix]) & (ipStr[ix] # 0X)
  791. DO
  792. INC (ix)
  793. END;
  794. RETURN pass[ix] = 0X
  795. END BackStagePass;
  796. BEGIN {ACTIVE}
  797. BEGIN {EXCLUSIVE}
  798. INC (active)
  799. END;
  800. (* open streams *)
  801. Streams.OpenReader(in, client.Receive);
  802. Streams.OpenWriter(out, client.Send);
  803. IF (active < MaxActive) OR BackStagePass (AlwaysAccept)
  804. THEN
  805. ConsumeName;
  806. finished := FALSE;
  807. charcount := 0;
  808. getCalls := 0;
  809. ToLog0 ("Connection made. ");
  810. Who;
  811. KernelLog.Exit;
  812. Announce(out);
  813. ToLog0 ("Log open sequence. ");
  814. KernelLog.Exit;
  815. OpenLog;
  816. log.String ("Log file opened on ");
  817. Strings.FormatDateTime("www, dd mmm yyyy hh:nn:ss -0600 (CST)", Dates.Now(), auxString);
  818. log.String (auxString);
  819. log.Ln;
  820. log.String ("From IP ");
  821. IP.AdrToStr(SELF.client.fip, auxString);
  822. log.String (auxString);
  823. DNS.HostByNumber (SELF.client.fip, auxString, res);
  824. IF res = DNS.Ok
  825. THEN
  826. log.String (" <");
  827. log.String (auxString);
  828. log.String ("> ")
  829. END;
  830. log.Ln;
  831. ToLog0 ("Log now open. ");
  832. KernelLog.Exit;
  833. (* production Smtp *)
  834. Get;
  835. badTokens := 0;
  836. WHILE ~finished & (badTokens < 100) & (sym#0) DO
  837. WHILE ~StartOf(2) DO
  838. out.String ("500 Not implemented"); out.Ln; out.Update;
  839. ch := GetCh (); WHILE ch # CR DO ch := GetCh () END; ch := GetCh ();
  840. Get; INC (badTokens);
  841. END;
  842. WHILE StartOf(1)
  843. DO
  844. reset
  845. END;
  846. QUIT
  847. END
  848. ELSE
  849. out.String ("421 PeerGrade.mrs.umn.edu, Service Not Available, Max connections exceeded.");
  850. out.Ln; out.Update;
  851. ToLog0 ("Connection rejected, too many connections. ");
  852. Who;
  853. KernelLog.Exit
  854. END;
  855. Terminate;
  856. BEGIN {EXCLUSIVE} DEC (active) END;
  857. ToLog0 ("Connection closed. ");
  858. Who;
  859. KernelLog.Exit
  860. END SmtpAgent;
  861. VAR
  862. symSet: ARRAY nrSets OF SymbolSet;
  863. smtp: TCPServices.Service;
  864. nextName : String;
  865. PROCEDURE ToLog0 (msg : String);
  866. BEGIN
  867. KernelLog.Enter;
  868. KernelLog.String (ID);
  869. KernelLog.String (" ");
  870. KernelLog.String (msg);
  871. END ToLog0;
  872. PROCEDURE InitSmtpSTable;
  873. BEGIN
  874. start[0]:=39; start[1]:=0; start[2]:=0; start[3]:=0;
  875. start[4]:=0; start[5]:=0; start[6]:=0; start[7]:=0;
  876. start[8]:=0; start[9]:=0; start[10]:=0; start[11]:=0;
  877. start[12]:=0; start[13]:=1; start[14]:=0; start[15]:=0;
  878. start[16]:=0; start[17]:=0; start[18]:=0; start[19]:=0;
  879. start[20]:=0; start[21]:=0; start[22]:=0; start[23]:=0;
  880. start[24]:=0; start[25]:=0; start[26]:=0; start[27]:=0;
  881. start[28]:=0; start[29]:=0; start[30]:=0; start[31]:=0;
  882. start[32]:=0; start[33]:=0; start[34]:=0; start[35]:=0;
  883. start[36]:=0; start[37]:=0; start[38]:=0; start[39]:=0;
  884. start[40]:=0; start[41]:=0; start[42]:=0; start[43]:=0;
  885. start[44]:=0; start[45]:=0; start[46]:=24; start[47]:=0;
  886. start[48]:=0; start[49]:=0; start[50]:=0; start[51]:=0;
  887. start[52]:=0; start[53]:=0; start[54]:=0; start[55]:=0;
  888. start[56]:=0; start[57]:=0; start[58]:=0; start[59]:=0;
  889. start[60]:=0; start[61]:=0; start[62]:=0; start[63]:=0;
  890. start[64]:=0; start[65]:=0; start[66]:=3; start[67]:=0;
  891. start[68]:=20; start[69]:=0; start[70]:=3; start[71]:=3;
  892. start[72]:=4; start[73]:=0; start[74]:=3; start[75]:=3;
  893. start[76]:=0; start[77]:=31; start[78]:=12; start[79]:=0;
  894. start[80]:=0; start[81]:=8; start[82]:=16; start[83]:=0;
  895. start[84]:=28; start[85]:=0; start[86]:=3; start[87]:=3;
  896. start[88]:=3; start[89]:=3; start[90]:=3; start[91]:=0;
  897. start[92]:=0; start[93]:=0; start[94]:=0; start[95]:=0;
  898. start[96]:=0; start[97]:=0; start[98]:=3; start[99]:=0;
  899. start[100]:=0; start[101]:=0; start[102]:=3; start[103]:=3;
  900. start[104]:=0; start[105]:=0; start[106]:=3; start[107]:=3;
  901. start[108]:=0; start[109]:=0; start[110]:=0; start[111]:=0;
  902. start[112]:=0; start[113]:=0; start[114]:=0; start[115]:=0;
  903. start[116]:=0; start[117]:=0; start[118]:=3; start[119]:=3;
  904. start[120]:=3; start[121]:=3; start[122]:=3; start[123]:=0;
  905. start[124]:=0; start[125]:=0; start[126]:=0; start[127]:=0;
  906. END InitSmtpSTable;
  907. PROCEDURE NewSmtpAgent(c: TCP.Connection; s: TCPServices.Service): TCPServices.Agent;
  908. VAR a: SmtpAgent;
  909. BEGIN
  910. NEW(a, c, s); RETURN a
  911. END NewSmtpAgent;
  912. (* This should become XML aware. *)
  913. PROCEDURE GetRegistry (VAR filename : String);
  914. VAR regF : Files.File; regR : Files.Reader;
  915. BEGIN
  916. regF := Files.Old (ConfigFileName);
  917. IF regF # NIL
  918. THEN
  919. Files.OpenReader (regR, regF, 0);
  920. regR.RawString (filename)
  921. ELSE
  922. filename := "D0000000000.Msg";
  923. regF := Files.New (ConfigFileName);
  924. Files.Register (regF)
  925. END;
  926. END GetRegistry;
  927. PROCEDURE PutRegistry (VAR filename : String);
  928. VAR regF : Files.File; regW : Files.Writer;
  929. BEGIN
  930. regF := Files.Old (ConfigFileName);
  931. IF regF=NIL THEN regF := Files.New (ConfigFileName); Files.Register (regF) END;
  932. Files.OpenWriter (regW, regF, 0);
  933. regW.RawString (filename);
  934. regW.Update;
  935. regF.Update;
  936. END PutRegistry;
  937. PROCEDURE Announce ( VAR out: Streams.Writer);
  938. BEGIN
  939. out.String ("220 ");
  940. out.String (DNS.domain);
  941. out.Char (" ");
  942. out.String ("SMTP");
  943. out.Char (" ");
  944. out.String (ID);
  945. out.String (Version);
  946. out.String (" Ready ");
  947. out.Ln();
  948. out.Update;
  949. END Announce;
  950. PROCEDURE Open*;
  951. VAR res : WORD;
  952. BEGIN
  953. IF smtp = NIL THEN
  954. NEW(smtp, AlmSmtpReceiverPort, NewSmtpAgent, res);
  955. active := 0;
  956. GetRegistry (nextName);
  957. ToLog0 (Version); KernelLog.String(" opened. Next name: ");
  958. KernelLog.String (nextName);
  959. KernelLog.Exit
  960. END;
  961. END Open;
  962. PROCEDURE Close*;
  963. BEGIN
  964. IF smtp # NIL THEN
  965. smtp.Stop(); smtp := NIL;
  966. PutRegistry (nextName);
  967. ToLog0 (Version); KernelLog.String(" closed"); KernelLog.Exit
  968. END;
  969. END Close;
  970. PROCEDURE Cleanup;
  971. BEGIN
  972. Close;
  973. END Cleanup;
  974. BEGIN
  975. Pattern[0] := CR;
  976. Pattern[1] := LF;
  977. Pattern[2] := ".";
  978. Pattern[3] := CR;
  979. Pattern[4] := LF;
  980. Pattern[5] := 0X;
  981. Ack[0] := "0";
  982. Ack[1] := "1";
  983. Ack[2] := "2";
  984. Ack[3] := "3";
  985. Ack[4] := "4";
  986. Ack[5] := 0X;
  987. symSet[0, 0] := {0};
  988. symSet[1, 0] := {SyHelo,SyNoop,SyRset,SyMail};
  989. symSet[2, 0] := {SyHelo,SyQuit,SyNoop,SyRset,SyMail};
  990. InitSmtpSTable;
  991. Modules.InstallTermHandler(Cleanup);
  992. END AlmSmtpReceiver.
  993. AlmSmtpReceiver.Tool
  994. System.Directory FAT:/Mail/Incoming/*\d
  995. System.Directory C0*\d
  996. Aos.Call AlmSmtpReceiver.Open
  997. Aos.Call AlmSmtpReceiver.Close
  998. Aos.Call NetTracker.Open 100 ~
  999. System.Free AlmSmtpReceiver ~
  1000. System.Free AlmSmtpReceiver ~
  1001. EditTools.OpenAscii ^
  1002. Telnet.Open cda
  1003. System.State AlmSmtpReceiver ~
  1004. Builder.Compile *
  1005. Telnet.Open "sci1355-am.mrs.umn.edu" 27
  1006. Colors.Panel
  1007. Hex.Open mail.config
  1008. ch = 0000000DX
  1009. charcount = 26
  1010. config = ""
  1011. errors = 0
  1012. lasterror = 0
  1013. nextName = "D0000000101"
  1014. smtp = 022685D0H
  1015. start = 39, 0, 0, 0, 0, 0, 0, 0, 0, 0 ...
  1016. state = 7
  1017. sym = 3