--- /dev/null
+;; Super-basic bell-and-whistle-free SMTP server.
+;;
+;; Intended for a single-user system
+
+(import tcp6
+ (chicken port)
+ (chicken io)
+ (chicken string)
+ (chicken pathname)
+ (chicken file)
+ (chicken time posix)
+ srfi-1 srfi-13 matchable)
+
+(define lambdamail-version "0.0.1")
+
+(define-record config port)
+(define-record message to from text helo)
+(define (make-empty-message) (make-message "" "" "" ""))
+
+(define (run-server config)
+ (set-buffering-mode! (current-output-port) #:line)
+ (let ((listener (tcp-listen (config-port config) 10 "::")))
+ (print "LambdaMail listening on port " (config-port config) " ...")
+ (server-loop listener config)))
+
+(define (server-loop listener config)
+ (let-values (((in-port out-port) (tcp-accept listener)))
+ (let-values (((local-ip remote-ip) (tcp-addresses in-port)))
+ (print "Accepted connection from " remote-ip " on " (seconds->string)))
+ (write-line (conc "Welcome to lambamail v" lambdamail-version) out-port)
+ (process-smtp in-port out-port)
+ (print "Connection terminated.")
+ (close-input-port in-port)
+ (close-output-port out-port))
+ (server-loop listener config))
+
+(define (smtp-reply reply out-port)
+ (write-line (conc reply "\r\n") out-port))
+
+(define (smtp-ok out-port)
+ (smtp-reply "250 OK" out-port))
+
+(define (smtp-close out-port)
+ (smtp-reply "221 Closing transmission channel" out-port))
+
+(define (smtp-no out-port)
+ (smtp-reply "502 Command not implemented" out-port))
+
+(define (process-smtp in-port out-port)
+ (let loop ((msg (make-empty-message))
+ (line (string-downcase (read-line in-port))))
+ (cond
+ ((string-prefix? "helo" line)
+ (message-helo-set! msg (string-drop line (string-length "helo")))
+ (smtp-ok out-port)
+ (loop msg (string-downcase (read-line in-port))))
+ ((string-prefix? "mail from:" line)
+ (message-from-set! msg (string-drop line (string-length "mail from:")))
+ (smtp-ok out-port)
+ (loop msg (string-downcase (read-line in-port))))
+ ((string-prefix? "rcpt to:" line)
+ (message-to-set! msg (string-drop line (string-length "rcpt to:")))
+ (smtp-ok out-port)
+ (loop msg (string-downcase (read-line in-port))))
+ ((string-prefix? "data" line)
+ (let text-loop ((text-line (read-line in-port))
+ (text ""))
+ (print "Received '" text-line "'")
+ (if (string=? "." text-line)
+ (message-text-set! msg text)
+ (text-loop (read-line in-port)
+ (conc text text-line))))
+ (deliver-message msg)
+ (smtp-ok out-port)
+ (loop (make-empty-message)
+ (string-downcase (read-line in-port))))
+ ((string-prefix? "quit" line)
+ (smtp-close out-port)
+ 'done)
+ (else
+ (smtp-no out-port)))))
+
+(define (deliver-message msg)
+ (print "Message delivered:")
+ (print " * From: " (message-from msg))
+ (print " * To: " (message-to msg))
+ (print " * Text: " (message-text msg)))
+
+(define (test)
+ (run-server (make-config 2525)))
+
+(test)