Mercurial > emacs
view lisp/eshell/em-unix.el @ 91817:f0b22bbb77fb
;;; smtpmail.el --- simple SMTP protocol (RFC 821) for sending mail
;; Copyright (C) 1995, 1996, 2001, 2002, 2003, 2004, 2005,
;; 2006, 2007, 2008 Free Software Foundation, Inc.
;; Author: Tomoji Kagatani <kagatani@rbc.ncl.omron.co.jp>
;; Maintainer: Simon Josefsson <simon@josefsson.org>
;; w32 Maintainer: Brian D. Carlstrom <bdc@ai.mit.edu>
;; ESMTP support: Simon Leinen <simon@switch.ch>
;; Hacked by Mike Taylor, 11th October 1999 to add support for
;; automatically appending a domain to RCPT TO: addresses.
;; AUTH=LOGIN support: Stephen Cranefield <scranefield@infoscience.otago.ac.nz>
;; Keywords: mail
;; This file is part of GNU Emacs.
;; GNU Emacs is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;; Commentary:
;; Send Mail to smtp host from smtpmail temp buffer.
;; Please add these lines in your .emacs(_emacs) or use customize.
;;
;;(setq send-mail-function 'smtpmail-send-it) ; if you use `mail'
;;(setq message-send-mail-function 'smtpmail-send-it) ; if you use message/Gnus
;;(setq smtpmail-default-smtp-server "YOUR SMTP HOST")
;;(setq smtpmail-local-domain "YOUR DOMAIN NAME")
;;(setq smtpmail-sendto-domain "YOUR DOMAIN NAME")
;;(setq smtpmail-debug-info t) ; only to debug problems
;;(setq smtpmail-auth-credentials ; or use ~/.authinfo
;; '(("YOUR SMTP HOST" 25 "username" "password")))
;;(setq smtpmail-starttls-credentials
;; '(("YOUR SMTP HOST" 25 "~/.my_smtp_tls.key" "~/.my_smtp_tls.cert")))
;; Where the 25 equals the value of `smtpmail-smtp-service', it can be an
;; integer or a string, just as long as they match (eq).
;; To queue mail, set smtpmail-queue-mail to t and use
;; smtpmail-send-queued-mail to send.
;; Modified by Stephen Cranefield <scranefield@infoscience.otago.ac.nz>,
;; 22/6/99, to support SMTP Authentication by the AUTH=LOGIN mechanism.
;; See http://help.netscape.com/products/server/messaging/3x/info/smtpauth.html
;; Rewritten by Simon Josefsson to use same credential variable as AUTH
;; support below.
;; Modified by Simon Josefsson <jas@pdc.kth.se>, 22/2/99, to support SMTP
;; Authentication by the AUTH mechanism.
;; See http://www.ietf.org/rfc/rfc2554.txt
;; Modified by Simon Josefsson <simon@josefsson.org>, 2000-10-07, to support
;; STARTTLS. Requires external program
;; ftp://ftp.opaopa.org/pub/elisp/starttls-*.tar.gz.
;; See http://www.ietf.org/rfc/rfc2246.txt, http://www.ietf.org/rfc/rfc2487.txt
;;; Code:
(require 'sendmail)
(autoload 'starttls-open-stream "starttls")
(autoload 'starttls-negotiate "starttls")
(autoload 'mail-strip-quoted-names "mail-utils")
(autoload 'message-make-date "message")
(autoload 'message-make-message-id "message")
(autoload 'rfc2104-hash "rfc2104")
(autoload 'netrc-parse "netrc")
(autoload 'netrc-machine "netrc")
(autoload 'netrc-get "netrc")
;;;
(defgroup smtpmail nil
"SMTP protocol for sending mail."
:group 'mail)
(defcustom smtpmail-default-smtp-server nil
"*Specify default SMTP server.
This only has effect if you specify it before loading the smtpmail library."
:type '(choice (const nil) string)
:group 'smtpmail)
(defcustom smtpmail-smtp-server
(or (getenv "SMTPSERVER") smtpmail-default-smtp-server)
"*The name of the host running SMTP server."
:type '(choice (const nil) string)
:group 'smtpmail)
(defcustom smtpmail-smtp-service 25
"*SMTP service port number.
The default value would be \"smtp\" or 25."
:type '(choice (integer :tag "Port") (string :tag "Service"))
:group 'smtpmail)
(defcustom smtpmail-local-domain nil
"*Local domain name without a host name.
If the function `system-name' returns the full internet address,
don't define this value."
:type '(choice (const nil) string)
:group 'smtpmail)
(defcustom smtpmail-sendto-domain nil
"*Local domain name without a host name.
This is appended (with an @-sign) to any specified recipients which do
not include an @-sign, so that each RCPT TO address is fully qualified.
\(Some configurations of sendmail require this.)
Don't bother to set this unless you have get an error like:
Sending failed; SMTP protocol error
when sending mail, and the *trace of SMTP session to <somewhere>*
buffer includes an exchange like:
RCPT TO: <someone>
501 <someone>: recipient address must contain a domain
"
:type '(choice (const nil) string)
:group 'smtpmail)
(defcustom smtpmail-debug-info nil
"Whether to print info in buffer *trace of SMTP session to <somewhere>*.
See also `smtpmail-debug-verb' which determines if the SMTP protocol should
be verbose as well."
:type 'boolean
:group 'smtpmail)
(defcustom smtpmail-debug-verb nil
"Whether this library sends the SMTP VERB command or not.
The commands enables verbose information from the SMTP server."
:type 'boolean
:group 'smtpmail)
(defcustom smtpmail-code-conv-from nil ;; *junet*
"*smtpmail code convert from this code to *internal*..for tiny-mime.."
:type 'boolean
:group 'smtpmail)
(defcustom smtpmail-queue-mail nil
"*If set, mail is queued; otherwise it is sent immediately.
If queued, it is stored in the directory `smtpmail-queue-dir'
and sent with `smtpmail-send-queued-mail'."
:type 'boolean
:group 'smtpmail)
(defcustom smtpmail-queue-dir "~/Mail/queued-mail/"
"*Directory where `smtpmail.el' stores queued mail."
:type 'directory
:group 'smtpmail)
(defcustom smtpmail-auth-credentials "~/.authinfo"
"Specify username and password for servers, directly or via .netrc file.
This variable can either be a filename pointing to a file in netrc(5)
format, or list of four-element lists that contain, in order,
`servername' (a string), `port' (an integer), `user' (a string) and
`password' (a string, or nil to query the user when needed). If you
need to enter a `realm' too, add it to the user string, so that it
looks like `user@realm'."
:type '(choice file
(repeat (list (string :tag "Server")
(integer :tag "Port")
(string :tag "Username")
(choice (const :tag "Query when needed" nil)
(string :tag "Password")))))
:version "22.1"
:group 'smtpmail)
(defcustom smtpmail-starttls-credentials '(("" 25 "" ""))
"Specify STARTTLS keys and certificates for servers.
This is a list of four-element list with `servername' (a string),
`port' (an integer), `key' (a filename) and `certificate' (a
filename).
If you do not have a certificate/key pair, leave the `key' and
`certificate' fields as `nil'. A key/certificate pair is only
needed if you want to use X.509 client authenticated
connections."
:type '(repeat (list (string :tag "Server")
(integer :tag "Port")
(file :tag "Key")
(file :tag "Certificate")))
:version "21.1"
:group 'smtpmail)
(defcustom smtpmail-warn-about-unknown-extensions nil
"*If set, print warnings about unknown SMTP extensions.
This is mainly useful for development purposes, to learn about
new SMTP extensions that might be useful to support."
:type 'boolean
:version "21.1"
:group 'smtpmail)
(defvar smtpmail-queue-index-file "index"
"File name of queued mail index.
This is relative to `smtpmail-queue-dir'.")
(defvar smtpmail-address-buffer)
(defvar smtpmail-recipient-address-list)
(defvar smtpmail-queue-counter 0)
;; Buffer-local variable.
(defvar smtpmail-read-point)
(defvar smtpmail-queue-index (concat smtpmail-queue-dir
smtpmail-queue-index-file))
(defconst smtpmail-auth-supported '(cram-md5 plain login)
"List of supported SMTP AUTH mechanisms.")
;;;
;;;
;;;
(defvar smtpmail-mail-address nil
"Value to use for envelope-from address for mail from ambient buffer.")
;;;###autoload
(defun smtpmail-send-it ()
(let ((errbuf (if mail-interactive
(generate-new-buffer " smtpmail errors")
0))
(tembuf (generate-new-buffer " smtpmail temp"))
(case-fold-search nil)
delimline
(mailbuf (current-buffer))
;; Examine this variable now, so that
;; local binding in the mail buffer will take effect.
(smtpmail-mail-address
(or (and mail-specify-envelope-from (mail-envelope-from))
user-mail-address))
(smtpmail-code-conv-from
(if enable-multibyte-characters
(let ((sendmail-coding-system smtpmail-code-conv-from))
(select-message-coding-system)))))
(unwind-protect
(save-excursion
(set-buffer tembuf)
(erase-buffer)
;; Use the same buffer-file-coding-system as in the mail
;; buffer, otherwise any write-region invocations (e.g., in
;; mail-do-fcc below) will annoy with asking for a suitable
;; encoding.
(set-buffer-file-coding-system smtpmail-code-conv-from nil t)
(insert-buffer-substring mailbuf)
(goto-char (point-max))
;; require one newline at the end.
(or (= (preceding-char) ?\n)
(insert ?\n))
;; Change header-delimiter to be what sendmail expects.
(mail-sendmail-undelimit-header)
(setq delimline (point-marker))
;; (sendmail-synch-aliases)
(if mail-aliases
(expand-mail-aliases (point-min) delimline))
(goto-char (point-min))
;; ignore any blank lines in the header
(while (and (re-search-forward "\n\n\n*" delimline t)
(< (point) delimline))
(replace-match "\n"))
(let ((case-fold-search t))
;; We used to process Resent-... headers here,
;; but it was not done properly, and the job
;; is done correctly in smtpmail-deduce-address-list.
;; Don't send out a blank subject line
(goto-char (point-min))
(if (re-search-forward "^Subject:\\([ \t]*\n\\)+\\b" delimline t)
(replace-match "")
;; This one matches a Subject just before the header delimiter.
(if (and (re-search-forward "^Subject:\\([ \t]*\n\\)+" delimline t)
(= (match-end 0) delimline))
(replace-match "")))
;; Put the "From:" field in unless for some odd reason
;; they put one in themselves.
(goto-char (point-min))
(if (not (re-search-forward "^From:" delimline t))
(let* ((login smtpmail-mail-address)
(fullname (user-full-name)))
(cond ((eq mail-from-style 'angles)
(insert "From: " fullname)
(let ((fullname-start (+ (point-min) 6))
(fullname-end (point-marker)))
(goto-char fullname-start)
;; Look for a character that cannot appear unquoted
;; according to RFC 822.
(if (re-search-forward "[^- !#-'*+/-9=?A-Z^-~]"
fullname-end 1)
(progn
;; Quote fullname, escaping specials.
(goto-char fullname-start)
(insert "\"")
(while (re-search-forward "[\"\\]"
fullname-end 1)
(replace-match "\\\\\\&" t))
(insert "\""))))
(insert " <" login ">\n"))
((eq mail-from-style 'parens)
(insert "From: " login " (")
(let ((fullname-start (point)))
(insert fullname)
(let ((fullname-end (point-marker)))
(goto-char fullname-start)
;; RFC 822 says \ and nonmatching parentheses
;; must be escaped in comments.
;; Escape every instance of ()\ ...
(while (re-search-forward "[()\\]" fullname-end 1)
(replace-match "\\\\\\&" t))
;; ... then undo escaping of matching parentheses,
;; including matching nested parentheses.
(goto-char fullname-start)
(while (re-search-forward
"\\(\\=\\|[^\\]\\(\\\\\\\\\\)*\\)\\\\(\\(\\([^\\]\\|\\\\\\\\\\)*\\)\\\\)"
fullname-end 1)
(replace-match "\\1(\\3)" t)
(goto-char fullname-start))))
(insert ")\n"))
((null mail-from-style)
(insert "From: " login "\n")))))
;; Insert a `Message-Id:' field if there isn't one yet.
(goto-char (point-min))
(unless (re-search-forward "^Message-Id:" delimline t)
(insert "Message-Id: " (message-make-message-id) "\n"))
;; Insert a `Date:' field if there isn't one yet.
(goto-char (point-min))
(unless (re-search-forward "^Date:" delimline t)
(insert "Date: " (message-make-date) "\n"))
;; Possibly add a MIME header for the current coding system
(let (charset)
(goto-char (point-min))
(and (eq mail-send-nonascii 'mime)
(not (re-search-forward "^MIME-version:" delimline t))
(progn (skip-chars-forward "\0-\177")
(/= (point) (point-max)))
smtpmail-code-conv-from
(setq charset
(coding-system-get smtpmail-code-conv-from
'mime-charset))
(goto-char delimline)
(insert "MIME-version: 1.0\n"
"Content-type: text/plain; charset="
(symbol-name charset)
"\nContent-Transfer-Encoding: 8bit\n")))
;; Insert an extra newline if we need it to work around
;; Sun's bug that swallows newlines.
(goto-char (1+ delimline))
(if (eval mail-mailer-swallows-blank-line)
(newline))
;; Find and handle any FCC fields.
(goto-char (point-min))
(if (re-search-forward "^FCC:" delimline t)
;; Force mail-do-fcc to use the encoding of the mail
;; buffer to encode outgoing messages on FCC files.
(let ((coding-system-for-write smtpmail-code-conv-from))
(mail-do-fcc delimline)))
(if mail-interactive
(with-current-buffer errbuf
(erase-buffer))))
;;
;;
;;
(setq smtpmail-address-buffer (generate-new-buffer "*smtp-mail*"))
(setq smtpmail-recipient-address-list
(smtpmail-deduce-address-list tembuf (point-min) delimline))
(kill-buffer smtpmail-address-buffer)
(smtpmail-do-bcc delimline)
; Send or queue
(if (not smtpmail-queue-mail)
(if (not (null smtpmail-recipient-address-list))
(if (not (smtpmail-via-smtp
smtpmail-recipient-address-list tembuf))
(error "Sending failed; SMTP protocol error"))
(error "Sending failed; no recipients"))
(let* ((file-data
(expand-file-name
(format "%s_%i"
(format-time-string "%Y-%m-%d_%H:%M:%S")
(setq smtpmail-queue-counter
(1+ smtpmail-queue-counter)))
smtpmail-queue-dir))
(file-data (convert-standard-filename file-data))
(file-elisp (concat file-data ".el"))
(buffer-data (create-file-buffer file-data))
(buffer-elisp (create-file-buffer file-elisp))
(buffer-scratch "*queue-mail*"))
(unless (file-exists-p smtpmail-queue-dir)
(make-directory smtpmail-queue-dir t))
(with-current-buffer buffer-data
(erase-buffer)
(set-buffer-file-coding-system smtpmail-code-conv-from nil t)
(insert-buffer-substring tembuf)
(write-file file-data)
(set-buffer buffer-elisp)
(erase-buffer)
(insert (concat
"(setq smtpmail-recipient-address-list '"
(prin1-to-string smtpmail-recipient-address-list)
")\n"))
(write-file file-elisp)
(set-buffer (generate-new-buffer buffer-scratch))
(insert (concat file-data "\n"))
(append-to-file (point-min)
(point-max)
smtpmail-queue-index)
)
(kill-buffer buffer-scratch)
(kill-buffer buffer-data)
(kill-buffer buffer-elisp))))
(kill-buffer tembuf)
(if (bufferp errbuf)
(kill-buffer errbuf)))))
;;;###autoload
(defun smtpmail-send-queued-mail ()
"Send mail that was queued as a result of setting `smtpmail-queue-mail'."
(interactive)
(with-temp-buffer
;;; Get index, get first mail, send it, update index, get second
;;; mail, send it, etc...
(let ((file-msg ""))
(insert-file-contents smtpmail-queue-index)
(goto-char (point-min))
(while (not (eobp))
(setq file-msg (buffer-substring (point) (line-end-position)))
(load file-msg)
;; Insert the message literally: it is already encoded as per
;; the MIME headers, and code conversions might guess the
;; encoding wrongly.
(with-temp-buffer
(let ((coding-system-for-read 'no-conversion))
(insert-file-contents file-msg))
(let ((smtpmail-mail-address
(or (and mail-specify-envelope-from (mail-envelope-from))
user-mail-address)))
(if (not (null smtpmail-recipient-address-list))
(if (not (smtpmail-via-smtp smtpmail-recipient-address-list
(current-buffer)))
(error "Sending failed; SMTP protocol error"))
(error "Sending failed; no recipients"))))
(delete-file file-msg)
(delete-file (concat file-msg ".el"))
(delete-region (point-at-bol) (point-at-bol 2)))
(write-region (point-min) (point-max) smtpmail-queue-index))))
;(defun smtpmail-via-smtp (host,port,sender,destination,smtpmail-text-buffer)
(defun smtpmail-fqdn ()
(if smtpmail-local-domain
(concat (system-name) "." smtpmail-local-domain)
(system-name)))
(defsubst smtpmail-cred-server (cred)
(nth 0 cred))
(defsubst smtpmail-cred-port (cred)
(nth 1 cred))
(defsubst smtpmail-cred-key (cred)
(nth 2 cred))
(defsubst smtpmail-cred-user (cred)
(nth 2 cred))
(defsubst smtpmail-cred-cert (cred)
(nth 3 cred))
(defsubst smtpmail-cred-passwd (cred)
(nth 3 cred))
(defun smtpmail-find-credentials (cred server port)
(catch 'done
(let ((l cred) el)
(while (setq el (pop l))
(when (and (equal server (smtpmail-cred-server el))
(equal port (smtpmail-cred-port el)))
(throw 'done el))))))
(defun smtpmail-maybe-append-domain (recipient)
(if (or (not smtpmail-sendto-domain)
(string-match "@" recipient))
recipient
(concat recipient "@" smtpmail-sendto-domain)))
(defun smtpmail-intersection (list1 list2)
(let ((result nil))
(dolist (el2 list2)
(when (memq el2 list1)
(push el2 result)))
(nreverse result)))
(defvar starttls-extra-args)
(defvar starttls-extra-arguments)
(defun smtpmail-open-stream (process-buffer host port)
(let ((cred (smtpmail-find-credentials
smtpmail-starttls-credentials host port)))
(if (null (and cred (condition-case ()
(with-no-warnings
(require 'starttls)
(call-process (if starttls-use-gnutls
starttls-gnutls-program
starttls-program)))
(error nil))))
;; The normal case.
(open-network-stream "SMTP" process-buffer host port)
(let* ((cred-key (smtpmail-cred-key cred))
(cred-cert (smtpmail-cred-cert cred))
(starttls-extra-args
(append
starttls-extra-args
(when (and (stringp cred-key) (stringp cred-cert)
(file-regular-p
(setq cred-key (expand-file-name cred-key)))
(file-regular-p
(setq cred-cert (expand-file-name cred-cert))))
(list "--key-file" cred-key "--cert-file" cred-cert))))
(starttls-extra-arguments
(append
starttls-extra-arguments
(when (and (stringp cred-key) (stringp cred-cert)
(file-regular-p
(setq cred-key (expand-file-name cred-key)))
(file-regular-p
(setq cred-cert (expand-file-name cred-cert))))
(list "--x509keyfile" cred-key "--x509certfile" cred-cert)))))
(starttls-open-stream "SMTP" process-buffer host port)))))
(defun smtpmail-try-auth-methods (process supported-extensions host port)
(let* ((mechs (cdr-safe (assoc 'auth supported-extensions)))
(mech (car (smtpmail-intersection smtpmail-auth-supported mechs)))
(cred (if (stringp smtpmail-auth-credentials)
(let* ((netrc (netrc-parse smtpmail-auth-credentials))
(port-name (format "%s" (or port "smtp")))
(hostentry (netrc-machine netrc host port-name
port-name)))
(when hostentry
(list host port
(netrc-get hostentry "login")
(netrc-get hostentry "password"))))
(smtpmail-find-credentials
smtpmail-auth-credentials host port)))
(passwd (when cred
(or (smtpmail-cred-passwd cred)
(read-passwd
(format "SMTP password for %s:%s: "
(smtpmail-cred-server cred)
(smtpmail-cred-port cred))))))
ret)
(when (and cred mech)
(cond
((eq mech 'cram-md5)
(smtpmail-send-command process (upcase (format "AUTH %s" mech)))
(if (or (null (car (setq ret (smtpmail-read-response process))))
(not (integerp (car ret)))
(>= (car ret) 400))
(throw 'done nil))
(when (eq (car ret) 334)
(let* ((challenge (substring (cadr ret) 4))
(decoded (base64-decode-string challenge))
(hash (rfc2104-hash 'md5 64 16 passwd decoded))
(response (concat (smtpmail-cred-user cred) " " hash))
;; Osamu Yamane <yamane@green.ocn.ne.jp>:
;; SMTP auth fails because the SMTP server identifies
;; only the first part of the string (delimited by
;; new line characters) as a response from the
;; client, and the rest as distinct commands.
;; In my case, the response string is 80 characters
;; long. Without the no-line-break option for
;; base64-encode-sting, only the first 76 characters
;; are taken as a response to the server, and the
;; authentication fails.
(encoded (base64-encode-string response t)))
(smtpmail-send-command process (format "%s" encoded))
(if (or (null (car (setq ret (smtpmail-read-response process))))
(not (integerp (car ret)))
(>= (car ret) 400))
(throw 'done nil)))))
((eq mech 'login)
(smtpmail-send-command process "AUTH LOGIN")
(if (or (null (car (setq ret (smtpmail-read-response process))))
(not (integerp (car ret)))
(>= (car ret) 400))
(throw 'done nil))
(smtpmail-send-command
process (base64-encode-string (smtpmail-cred-user cred) t))
(if (or (null (car (setq ret (smtpmail-read-response process))))
(not (integerp (car ret)))
(>= (car ret) 400))
(throw 'done nil))
(smtpmail-send-command process (base64-encode-string passwd t))
(if (or (null (car (setq ret (smtpmail-read-response process))))
(not (integerp (car ret)))
(>= (car ret) 400))
(throw 'done nil)))
((eq mech 'plain)
;; We used to send an empty initial request, and wait for an
;; empty response, and then send the password, but this
;; violate a SHOULD in RFC 2222 paragraph 5.1. Note that this
;; is not sent if the server did not advertise AUTH PLAIN in
;; the EHLO response. See RFC 2554 for more info.
(smtpmail-send-command process
(concat "AUTH PLAIN "
(base64-encode-string
(concat "\0"
(smtpmail-cred-user cred)
"\0"
passwd) t)))
(if (or (null (car (setq ret (smtpmail-read-response process))))
(not (integerp (car ret)))
(not (equal (car ret) 235)))
(throw 'done nil)))
(t
(error "Mechanism %s not implemented" mech)))
;; Remember the password.
(when (and (not (stringp smtpmail-auth-credentials))
(null (smtpmail-cred-passwd cred)))
(setcar (cdr (cdr (cdr cred))) passwd)))))
(defun smtpmail-via-smtp (recipient smtpmail-text-buffer)
(let ((process nil)
(host (or smtpmail-smtp-server
(error "`smtpmail-smtp-server' not defined")))
(port smtpmail-smtp-service)
;; smtpmail-mail-address should be set to the appropriate
;; buffer-local value by the caller, but in case not:
(envelope-from (or smtpmail-mail-address
(and mail-specify-envelope-from
(mail-envelope-from))
user-mail-address))
response-code
greeting
process-buffer
(supported-extensions '()))
(unwind-protect
(catch 'done
;; get or create the trace buffer
(setq process-buffer
(get-buffer-create (format "*trace of SMTP session to %s*" host)))
;; clear the trace buffer of old output
(with-current-buffer process-buffer
(setq buffer-undo-list t)
(erase-buffer))
;; open the connection to the server
(setq process (smtpmail-open-stream process-buffer host port))
(and (null process) (throw 'done nil))
;; set the send-filter
(set-process-filter process 'smtpmail-process-filter)
(with-current-buffer process-buffer
(set-buffer-process-coding-system 'raw-text-unix 'raw-text-unix)
(make-local-variable 'smtpmail-read-point)
(setq smtpmail-read-point (point-min))
(if (or (null (car (setq greeting (smtpmail-read-response process))))
(not (integerp (car greeting)))
(>= (car greeting) 400))
(throw 'done nil)
)
(let ((do-ehlo t)
(do-starttls t))
(while do-ehlo
;; EHLO
(smtpmail-send-command process (format "EHLO %s" (smtpmail-fqdn)))
(if (or (null (car (setq response-code
(smtpmail-read-response process))))
(not (integerp (car response-code)))
(>= (car response-code) 400))
(progn
;; HELO
(smtpmail-send-command
process (format "HELO %s" (smtpmail-fqdn)))
(if (or (null (car (setq response-code
(smtpmail-read-response process))))
(not (integerp (car response-code)))
(>= (car response-code) 400))
(throw 'done nil)))
(dolist (line (cdr (cdr response-code)))
(let ((name
(with-case-table ascii-case-table
(mapcar (lambda (s) (intern (downcase s)))
(split-string (substring line 4) "[ ]")))))
(and (eq (length name) 1)
(setq name (car name)))
(and name
(cond ((memq (if (consp name) (car name) name)
'(verb xvrb 8bitmime onex xone
expn size dsn etrn
enhancedstatuscodes
help xusr
auth=login auth starttls))
(setq supported-extensions
(cons name supported-extensions)))
(smtpmail-warn-about-unknown-extensions
(message "Unknown extension %s" name)))))))
(if (and do-starttls
(smtpmail-find-credentials smtpmail-starttls-credentials host port)
(member 'starttls supported-extensions)
(numberp (process-id process)))
(progn
(smtpmail-send-command process (format "STARTTLS"))
(if (or (null (car (setq response-code (smtpmail-read-response process))))
(not (integerp (car response-code)))
(>= (car response-code) 400))
(throw 'done nil))
(starttls-negotiate process)
(setq do-starttls nil))
(setq do-ehlo nil))))
(smtpmail-try-auth-methods process supported-extensions host port)
(if (or (member 'onex supported-extensions)
(member 'xone supported-extensions))
(progn
(smtpmail-send-command process (format "ONEX"))
(if (or (null (car (setq response-code (smtpmail-read-response process))))
(not (integerp (car response-code)))
(>= (car response-code) 400))
(throw 'done nil))))
(if (and smtpmail-debug-verb
(or (member 'verb supported-extensions)
(member 'xvrb supported-extensions)))
(progn
(smtpmail-send-command process (format "VERB"))
(if (or (null (car (setq response-code (smtpmail-read-response process))))
(not (integerp (car response-code)))
(>= (car response-code) 400))
(throw 'done nil))))
(if (member 'xusr supported-extensions)
(progn
(smtpmail-send-command process (format "XUSR"))
(if (or (null (car (setq response-code (smtpmail-read-response process))))
(not (integerp (car response-code)))
(>= (car response-code) 400))
(throw 'done nil))))
;; MAIL FROM:<sender>
(let ((size-part
(if (or (member 'size supported-extensions)
(assoc 'size supported-extensions))
(format " SIZE=%d"
(with-current-buffer smtpmail-text-buffer
;; size estimate:
(+ (- (point-max) (point-min))
;; Add one byte for each change-of-line
;; because of CR-LF representation:
(count-lines (point-min) (point-max)))))
""))
(body-part
(if (member '8bitmime supported-extensions)
;; FIXME:
;; Code should be added here that transforms
;; the contents of the message buffer into
;; something the receiving SMTP can handle.
;; For a receiver that supports 8BITMIME, this
;; may mean converting BINARY to BASE64, or
;; adding Content-Transfer-Encoding and the
;; other MIME headers. The code should also
;; return an indication of what encoding the
;; message buffer is now, i.e. ASCII or
;; 8BITMIME.
(if nil
" BODY=8BITMIME"
"")
"")))
; (smtpmail-send-command process (format "MAIL FROM:%s@%s" (user-login-name) (smtpmail-fqdn)))
(smtpmail-send-command process (format "MAIL FROM:<%s>%s%s"
envelope-from
size-part
body-part))
(if (or (null (car (setq response-code (smtpmail-read-response process))))
(not (integerp (car response-code)))
(>= (car response-code) 400))
(throw 'done nil)
))
;; RCPT TO:<recipient>
(let ((n 0))
(while (not (null (nth n recipient)))
(smtpmail-send-command process (format "RCPT TO:<%s>" (smtpmail-maybe-append-domain (nth n recipient))))
(setq n (1+ n))
(setq response-code (smtpmail-read-response process))
(if (or (null (car response-code))
(not (integerp (car response-code)))
(>= (car response-code) 400))
(throw 'done nil)
)
))
;; DATA
(smtpmail-send-command process "DATA")
(if (or (null (car (setq response-code (smtpmail-read-response process))))
(not (integerp (car response-code)))
(>= (car response-code) 400))
(throw 'done nil)
)
;; Mail contents
(smtpmail-send-data process smtpmail-text-buffer)
;;DATA end "."
(smtpmail-send-command process ".")
(if (or (null (car (setq response-code (smtpmail-read-response process))))
(not (integerp (car response-code)))
(>= (car response-code) 400))
(throw 'done nil)
)
;;QUIT
; (smtpmail-send-command process "QUIT")
; (and (null (car (smtpmail-read-response process)))
; (throw 'done nil))
t ))
(if process
(with-current-buffer (process-buffer process)
(smtpmail-send-command process "QUIT")
(smtpmail-read-response process)
; (if (or (null (car (setq response-code (smtpmail-read-response process))))
; (not (integerp (car response-code)))
; (>= (car response-code) 400))
; (throw 'done nil)
; )
(delete-process process)
(unless smtpmail-debug-info
(kill-buffer process-buffer)))))))
(defun smtpmail-process-filter (process output)
(with-current-buffer (process-buffer process)
(goto-char (point-max))
(insert output)))
(defun smtpmail-read-response (process)
(let ((case-fold-search nil)
(response-strings nil)
(response-continue t)
(return-value '(nil ()))
match-end)
(catch 'done
(while response-continue
(goto-char smtpmail-read-point)
(while (not (search-forward "\r\n" nil t))
(unless (memq (process-status process) '(open run))
(throw 'done nil))
(accept-process-output process)
(goto-char smtpmail-read-point))
(setq match-end (point))
(setq response-strings
(cons (buffer-substring smtpmail-read-point (- match-end 2))
response-strings))
(goto-char smtpmail-read-point)
(if (looking-at "[0-9]+ ")
(let ((begin (match-beginning 0))
(end (match-end 0)))
(if smtpmail-debug-info
(message "%s" (car response-strings)))
(setq smtpmail-read-point match-end)
;; ignore lines that start with "0"
(if (looking-at "0[0-9]+ ")
nil
(setq response-continue nil)
(setq return-value
(cons (string-to-number
(buffer-substring begin end))
(nreverse response-strings)))))
(if (looking-at "[0-9]+-")
(progn (if smtpmail-debug-info
(message "%s" (car response-strings)))
(setq smtpmail-read-point match-end)
(setq response-continue t))
(progn
(setq smtpmail-read-point match-end)
(setq response-continue nil)
(setq return-value
(cons nil (nreverse response-strings)))))))
(setq smtpmail-read-point match-end))
return-value))
(defun smtpmail-send-command (process command)
(goto-char (point-max))
(if (= (aref command 0) ?P)
(insert "PASS <omitted>\r\n")
(insert command "\r\n"))
(setq smtpmail-read-point (point))
(process-send-string process command)
(process-send-string process "\r\n"))
(defun smtpmail-send-data-1 (process data)
(goto-char (point-max))
(if (and (multibyte-string-p data)
smtpmail-code-conv-from)
(setq data (string-as-multibyte
(encode-coding-string data smtpmail-code-conv-from))))
(if smtpmail-debug-info
(insert data "\r\n"))
(setq smtpmail-read-point (point))
;; Escape "." at start of a line
(if (eq (string-to-char data) ?.)
(process-send-string process "."))
(process-send-string process data)
(process-send-string process "\r\n")
)
(defun smtpmail-send-data (process buffer)
(let ((data-continue t) sending-data)
(with-current-buffer buffer
(goto-char (point-min)))
(while data-continue
(with-current-buffer buffer
(setq sending-data (buffer-substring (point-at-bol) (point-at-eol)))
(end-of-line 2)
(setq data-continue (not (eobp))))
(smtpmail-send-data-1 process sending-data))))
(defun smtpmail-deduce-address-list (smtpmail-text-buffer header-start header-end)
"Get address list suitable for smtp RCPT TO: <address>."
(unwind-protect
(with-current-buffer smtpmail-address-buffer
(erase-buffer)
(let
((case-fold-search t)
(simple-address-list "")
this-line
this-line-end
addr-regexp)
(insert-buffer-substring smtpmail-text-buffer header-start header-end)
(goto-char (point-min))
;; RESENT-* fields should stop processing of regular fields.
(save-excursion
(setq addr-regexp
(if (re-search-forward "^Resent-\\(to\\|cc\\|bcc\\):"
header-end t)
"^Resent-\\(to\\|cc\\|bcc\\):"
"^\\(To:\\|Cc:\\|Bcc:\\)")))
(while (re-search-forward addr-regexp header-end t)
(replace-match "")
(setq this-line (match-beginning 0))
(forward-line 1)
;; get any continuation lines
(while (and (looking-at "^[ \t]+") (< (point) header-end))
(forward-line 1))
(setq this-line-end (point-marker))
(setq simple-address-list
(concat simple-address-list " "
(mail-strip-quoted-names (buffer-substring this-line this-line-end))))
)
(erase-buffer)
(insert " " simple-address-list "\n")
(subst-char-in-region (point-min) (point-max) 10 ? t);; newline --> blank
(subst-char-in-region (point-min) (point-max) ?, ? t);; comma --> blank
(subst-char-in-region (point-min) (point-max) 9 ? t);; tab --> blank
(goto-char (point-min))
;; tidyness in case hook is not robust when it looks at this
(while (re-search-forward "[ \t]+" header-end t) (replace-match " "))
(goto-char (point-min))
(let (recipient-address-list)
(while (re-search-forward " \\([^ ]+\\) " (point-max) t)
(backward-char 1)
(setq recipient-address-list (cons (buffer-substring (match-beginning 1) (match-end 1))
recipient-address-list))
)
(setq smtpmail-recipient-address-list recipient-address-list))
)
)
)
)
(defun smtpmail-do-bcc (header-end)
"Delete [Resent-]BCC: and their continuation lines from the header area.
There may be multiple BCC: lines, and each may have arbitrarily
many continuation lines."
(let ((case-fold-search t))
(save-excursion
(goto-char (point-min))
;; iterate over all BCC: lines
(while (re-search-forward "^\\(RESENT-\\)?BCC:" header-end t)
(delete-region (match-beginning 0)
(progn (forward-line 1) (point)))
;; get rid of any continuation lines
(while (and (looking-at "^[ \t].*\n") (< (point) header-end))
(replace-match ""))))))
(provide 'smtpmail)
;;; arch-tag: a76992df-6d71-43b7-9e72-4bacc6c05466
;;; smtpmail.el ends here
author | Bastien Guerry <bzg@altern.org> |
---|---|
date | Wed, 13 Feb 2008 20:58:26 +0000 |
parents | 107ccd98fa12 |
children | 606f2d163a64 1e3a407766b9 |
line wrap: on
line source
;;; em-unix.el --- UNIX command aliases ;; Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, ;; 2005, 2006, 2007, 2008 Free Software Foundation, Inc. ;; Author: John Wiegley <johnw@gnu.org> ;; This file is part of GNU Emacs. ;; GNU Emacs is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation; either version 3, or (at your option) ;; any later version. ;; GNU Emacs is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs; see the file COPYING. If not, write to the ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, ;; Boston, MA 02110-1301, USA. ;;; Commentary: ;; This file contains implementations of several UNIX command in Emacs ;; Lisp, for several reasons: ;; ;; 1) it makes them available on all platforms where the Lisp ;; functions used are available ;; ;; 2) it makes their functionality accessible and modified by the ;; Lisp programmer. ;; ;; 3) it allows Eshell to refrain from having to invoke external ;; processes for common operations. ;;; Code: (require 'eshell) (defgroup eshell-unix nil "This module defines many of the more common UNIX utilities as aliases implemented in Lisp. These include mv, ln, cp, rm, etc. If the user passes arguments which are too complex, or are unrecognized by the Lisp variant, the external version will be called (if available). The only reason not to use them would be because they are usually much slower. But in several cases their tight integration with Eshell makes them more versatile than their traditional cousins \(such as being able to use `kill' to kill Eshell background processes by name)." :tag "UNIX commands in Lisp" :group 'eshell-module) (defcustom eshell-unix-load-hook '(eshell-unix-initialize) "*A list of functions to run when `eshell-unix' is loaded." :type 'hook :group 'eshell-unix) (defcustom eshell-plain-grep-behavior nil "*If non-nil, standalone \"grep\" commands will behave normally. Standalone in this context means not redirected, and not on the receiving side of a command pipeline." :type 'boolean :group 'eshell-unix) (defcustom eshell-no-grep-available (not (eshell-search-path "grep")) "*If non-nil, no grep is available on the current machine." :type 'boolean :group 'eshell-unix) (defcustom eshell-plain-diff-behavior nil "*If non-nil, standalone \"diff\" commands will behave normally. Standalone in this context means not redirected, and not on the receiving side of a command pipeline." :type 'boolean :group 'eshell-unix) (defcustom eshell-plain-locate-behavior (featurep 'xemacs) "*If non-nil, standalone \"locate\" commands will behave normally. Standalone in this context means not redirected, and not on the receiving side of a command pipeline." :type 'boolean :group 'eshell-unix) (defcustom eshell-rm-removes-directories nil "*If non-nil, `rm' will remove directory entries. Otherwise, `rmdir' is required." :type 'boolean :group 'eshell-unix) (defcustom eshell-rm-interactive-query (= (user-uid) 0) "*If non-nil, `rm' will query before removing anything." :type 'boolean :group 'eshell-unix) (defcustom eshell-mv-interactive-query (= (user-uid) 0) "*If non-nil, `mv' will query before overwriting anything." :type 'boolean :group 'eshell-unix) (defcustom eshell-mv-overwrite-files t "*If non-nil, `mv' will overwrite files without warning." :type 'boolean :group 'eshell-unix) (defcustom eshell-cp-interactive-query (= (user-uid) 0) "*If non-nil, `cp' will query before overwriting anything." :type 'boolean :group 'eshell-unix) (defcustom eshell-cp-overwrite-files t "*If non-nil, `cp' will overwrite files without warning." :type 'boolean :group 'eshell-unix) (defcustom eshell-ln-interactive-query (= (user-uid) 0) "*If non-nil, `ln' will query before overwriting anything." :type 'boolean :group 'eshell-unix) (defcustom eshell-ln-overwrite-files nil "*If non-nil, `ln' will overwrite files without warning." :type 'boolean :group 'eshell-unix) (defcustom eshell-default-target-is-dot nil "*If non-nil, the default destination for cp, mv or ln is `.'." :type 'boolean :group 'eshell-unix) (defcustom eshell-du-prefer-over-ange nil "*Use Eshell's du in ange-ftp remote directories. Otherwise, Emacs will attempt to use rsh to invoke du on the remote machine." :type 'boolean :group 'eshell-unix) ;;; Functions: (defun eshell-unix-initialize () "Initialize the UNIX support/emulation code." (when (eshell-using-module 'eshell-cmpl) (add-hook 'pcomplete-try-first-hook 'eshell-complete-host-reference nil t)) (make-local-variable 'eshell-complex-commands) (setq eshell-complex-commands (append '("grep" "egrep" "fgrep" "agrep" "glimpse" "locate" "cat" "time" "cp" "mv" "make" "du" "diff") eshell-complex-commands))) (defalias 'eshell/date 'current-time-string) (defalias 'eshell/basename 'file-name-nondirectory) (defalias 'eshell/dirname 'file-name-directory) (eval-when-compile (defvar interactive) (defvar preview) (defvar recursive) (defvar verbose)) (defun eshell/man (&rest args) "Invoke man, flattening the arguments appropriately." (funcall 'man (apply 'eshell-flatten-and-stringify args))) (put 'eshell/man 'eshell-no-numeric-conversions t) (defun eshell/info (&rest args) "Run the info command in-frame with the same behavior as command-line `info', ie: 'info' => goes to top info window 'info arg1' => IF arg1 is a file, then visits arg1 'info arg1' => OTHERWISE goes to top info window and then menu item arg1 'info arg1 arg2' => does action for arg1 (either visit-file or menu-item) and then menu item arg2 etc." (eval-and-compile (require 'info)) (let ((file (cond ((not (stringp (car args))) nil) ((file-exists-p (expand-file-name (car args))) (expand-file-name (car args))) ((file-exists-p (concat (expand-file-name (car args)) ".info")) (concat (expand-file-name (car args)) ".info"))))) ;; If the first arg is a file, then go to that file's Top node ;; Otherwise, go to the global directory (if file (progn (setq args (cdr args)) (Info-find-node file "Top")) (Info-directory)) ;; Treat all remaining args as menu references (while args (Info-menu (car args)) (setq args (cdr args))))) (defun eshell-remove-entries (path files &optional top-level) "From PATH, remove all of the given FILES, perhaps interactively." (while files (if (string-match "\\`\\.\\.?\\'" (file-name-nondirectory (car files))) (if top-level (eshell-error "rm: cannot remove `.' or `..'\n")) (if (and (file-directory-p (car files)) (not (file-symlink-p (car files)))) (let ((dir (file-name-as-directory (car files)))) (eshell-remove-entries dir (mapcar (function (lambda (file) (concat dir file))) (directory-files dir))) (if verbose (eshell-printn (format "rm: removing directory `%s'" (car files)))) (unless (or preview (and interactive (not (y-or-n-p (format "rm: remove directory `%s'? " (car files)))))) (eshell-funcalln 'delete-directory (car files)))) (if verbose (eshell-printn (format "rm: removing file `%s'" (car files)))) (unless (or preview (and interactive (not (y-or-n-p (format "rm: remove `%s'? " (car files)))))) (eshell-funcalln 'delete-file (car files))))) (setq files (cdr files)))) (defun eshell/rm (&rest args) "Implementation of rm in Lisp. This is implemented to call either `delete-file', `kill-buffer', `kill-process', or `unintern', depending on the nature of the argument." (setq args (eshell-flatten-list args)) (eshell-eval-using-options "rm" args '((?h "help" nil nil "show this usage screen") (?f "force" nil force-removal "force removal") (?i "interactive" nil interactive "prompt before any removal") (?n "preview" nil preview "don't change anything on disk") (?r "recursive" nil recursive "remove the contents of directories recursively") (?R nil nil recursive "(same)") (?v "verbose" nil verbose "explain what is being done") :preserve-args :external "rm" :show-usage :usage "[OPTION]... FILE... Remove (unlink) the FILE(s).") (unless interactive (setq interactive eshell-rm-interactive-query)) (if (and force-removal interactive) (setq interactive nil)) (while args (let ((entry (if (stringp (car args)) (directory-file-name (car args)) (if (numberp (car args)) (number-to-string (car args)) (car args))))) (cond ((bufferp entry) (if verbose (eshell-printn (format "rm: removing buffer `%s'" entry))) (unless (or preview (and interactive (not (y-or-n-p (format "rm: delete buffer `%s'? " entry))))) (eshell-funcalln 'kill-buffer entry))) ((eshell-processp entry) (if verbose (eshell-printn (format "rm: killing process `%s'" entry))) (unless (or preview (and interactive (not (y-or-n-p (format "rm: kill process `%s'? " entry))))) (eshell-funcalln 'kill-process entry))) ((symbolp entry) (if verbose (eshell-printn (format "rm: uninterning symbol `%s'" entry))) (unless (or preview (and interactive (not (y-or-n-p (format "rm: unintern symbol `%s'? " entry))))) (eshell-funcalln 'unintern entry))) ((stringp entry) (if (and (file-directory-p entry) (not (file-symlink-p entry))) (if (or recursive eshell-rm-removes-directories) (if (or preview (not interactive) (y-or-n-p (format "rm: descend into directory `%s'? " entry))) (eshell-remove-entries nil (list entry) t)) (eshell-error (format "rm: %s: is a directory\n" entry))) (eshell-remove-entries nil (list entry) t))))) (setq args (cdr args))) nil)) (put 'eshell/rm 'eshell-no-numeric-conversions t) (defun eshell/mkdir (&rest args) "Implementation of mkdir in Lisp." (eshell-eval-using-options "mkdir" args '((?h "help" nil nil "show this usage screen") :external "mkdir" :show-usage :usage "[OPTION] DIRECTORY... Create the DIRECTORY(ies), if they do not already exist.") (while args (eshell-funcalln 'make-directory (car args)) (setq args (cdr args))) nil)) (put 'eshell/mkdir 'eshell-no-numeric-conversions t) (defun eshell/rmdir (&rest args) "Implementation of rmdir in Lisp." (eshell-eval-using-options "rmdir" args '((?h "help" nil nil "show this usage screen") :external "rmdir" :show-usage :usage "[OPTION] DIRECTORY... Remove the DIRECTORY(ies), if they are empty.") (while args (eshell-funcalln 'delete-directory (car args)) (setq args (cdr args))) nil)) (put 'eshell/rmdir 'eshell-no-numeric-conversions t) (eval-when-compile (defvar no-dereference) (defvar preview) (defvar verbose)) (defvar eshell-warn-dot-directories t) (defun eshell-shuffle-files (command action files target func deep &rest args) "Shuffle around some filesystem entries, using FUNC to do the work." (let ((attr-target (eshell-file-attributes target)) (is-dir (or (file-directory-p target) (and preview (not eshell-warn-dot-directories)))) attr) (if (and (not preview) (not is-dir) (> (length files) 1)) (error "%s: when %s multiple files, last argument must be a directory" command action)) (while files (setcar files (directory-file-name (car files))) (cond ((string-match "\\`\\.\\.?\\'" (file-name-nondirectory (car files))) (if eshell-warn-dot-directories (eshell-error (format "%s: %s: omitting directory\n" command (car files))))) ((and attr-target (or (not (eshell-under-windows-p)) (eq system-type 'ms-dos)) (setq attr (eshell-file-attributes (car files))) (nth 10 attr-target) (nth 10 attr) ;; Use equal, not -, since the inode and the device could ;; cons cells. (equal (nth 10 attr-target) (nth 10 attr)) (nth 11 attr-target) (nth 11 attr) (equal (nth 11 attr-target) (nth 11 attr))) (eshell-error (format "%s: `%s' and `%s' are the same file\n" command (car files) target))) (t (let ((source (car files)) (target (if is-dir (expand-file-name (file-name-nondirectory (car files)) target) target)) link) (if (and (file-directory-p source) (or (not no-dereference) (not (file-symlink-p source))) (not (memq func '(make-symbolic-link add-name-to-file)))) (if (and (eq func 'copy-file) (not recursive)) (eshell-error (format "%s: %s: omitting directory\n" command (car files))) (let (eshell-warn-dot-directories) (if (and (not deep) (eq func 'rename-file) ;; Use equal, since the device might be a ;; cons cell. (equal (nth 11 (eshell-file-attributes (file-name-directory (directory-file-name (expand-file-name source))))) (nth 11 (eshell-file-attributes (file-name-directory (directory-file-name (expand-file-name target))))))) (apply 'eshell-funcalln func source target args) (unless (file-directory-p target) (if verbose (eshell-printn (format "%s: making directory %s" command target))) (unless preview (eshell-funcalln 'make-directory target))) (apply 'eshell-shuffle-files command action (mapcar (function (lambda (file) (concat source "/" file))) (directory-files source)) target func t args) (when (eq func 'rename-file) (if verbose (eshell-printn (format "%s: deleting directory %s" command source))) (unless preview (eshell-funcalln 'delete-directory source)))))) (if verbose (eshell-printn (format "%s: %s -> %s" command source target))) (unless preview (if (and no-dereference (setq link (file-symlink-p source))) (progn (apply 'eshell-funcalln 'make-symbolic-link link target args) (if (eq func 'rename-file) (if (and (file-directory-p source) (not (file-symlink-p source))) (eshell-funcalln 'delete-directory source) (eshell-funcalln 'delete-file source)))) (apply 'eshell-funcalln func source target args))))))) (setq files (cdr files))))) (defun eshell-shorthand-tar-command (command args) "Rewrite `cp -v dir a.tar.gz' to `tar cvzf a.tar.gz dir'." (let* ((archive (car (last args))) (tar-args (cond ((string-match "z2" archive) "If") ((string-match "gz" archive) "zf") ((string-match "\\(az\\|Z\\)" archive) "Zf") (t "f")))) (if (file-exists-p archive) (setq tar-args (concat "u" tar-args)) (setq tar-args (concat "c" tar-args))) (if verbose (setq tar-args (concat "v" tar-args))) (if (equal command "mv") (setq tar-args (concat "--remove-files -" tar-args))) ;; truncate the archive name from the arguments (setcdr (last args 2) nil) (throw 'eshell-replace-command (eshell-parse-command (format "tar %s %s" tar-args archive) args)))) ;; this is to avoid duplicating code... (defmacro eshell-mvcpln-template (command action func query-var force-var &optional preserve) `(let ((len (length args))) (if (or (= len 0) (and (= len 1) (null eshell-default-target-is-dot))) (error "%s: missing destination file or directory" ,command)) (if (= len 1) (nconc args '("."))) (setq args (eshell-stringify-list (eshell-flatten-list args))) (if (and ,(not (equal command "ln")) (string-match eshell-tar-regexp (car (last args))) (or (> (length args) 2) (and (file-directory-p (car args)) (or (not no-dereference) (not (file-symlink-p (car args))))))) (eshell-shorthand-tar-command ,command args) (let ((target (car (last args))) ange-cache) (setcdr (last args 2) nil) (eshell-shuffle-files ,command ,action args target ,func nil ,@(append `((if (and (or interactive ,query-var) (not force)) 1 (or force ,force-var))) (if preserve (list preserve))))) nil))) (defun eshell/mv (&rest args) "Implementation of mv in Lisp." (eshell-eval-using-options "mv" args '((?f "force" nil force "remove existing destinations, never prompt") (?i "interactive" nil interactive "request confirmation if target already exists") (?n "preview" nil preview "don't change anything on disk") (?v "verbose" nil verbose "explain what is being done") (nil "help" nil nil "show this usage screen") :preserve-args :external "mv" :show-usage :usage "[OPTION]... SOURCE DEST or: mv [OPTION]... SOURCE... DIRECTORY Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY. \[OPTION] DIRECTORY...") (let ((no-dereference t)) (eshell-mvcpln-template "mv" "moving" 'rename-file eshell-mv-interactive-query eshell-mv-overwrite-files)))) (put 'eshell/mv 'eshell-no-numeric-conversions t) (defun eshell/cp (&rest args) "Implementation of cp in Lisp." (eshell-eval-using-options "cp" args '((?a "archive" nil archive "same as -dpR") (?d "no-dereference" nil no-dereference "preserve links") (?f "force" nil force "remove existing destinations, never prompt") (?i "interactive" nil interactive "request confirmation if target already exists") (?n "preview" nil preview "don't change anything on disk") (?p "preserve" nil preserve "preserve file attributes if possible") (?R "recursive" nil recursive "copy directories recursively") (?v "verbose" nil verbose "explain what is being done") (nil "help" nil nil "show this usage screen") :preserve-args :external "cp" :show-usage :usage "[OPTION]... SOURCE DEST or: cp [OPTION]... SOURCE... DIRECTORY Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.") (if archive (setq preserve t no-dereference t recursive t)) (eshell-mvcpln-template "cp" "copying" 'copy-file eshell-cp-interactive-query eshell-cp-overwrite-files preserve))) (put 'eshell/cp 'eshell-no-numeric-conversions t) (defun eshell/ln (&rest args) "Implementation of ln in Lisp." (eshell-eval-using-options "ln" args '((?h "help" nil nil "show this usage screen") (?s "symbolic" nil symbolic "make symbolic links instead of hard links") (?i "interactive" nil interactive "request confirmation if target already exists") (?f "force" nil force "remove existing destinations, never prompt") (?n "preview" nil preview "don't change anything on disk") (?v "verbose" nil verbose "explain what is being done") :preserve-args :external "ln" :show-usage :usage "[OPTION]... TARGET [LINK_NAME] or: ln [OPTION]... TARGET... DIRECTORY Create a link to the specified TARGET with optional LINK_NAME. If there is more than one TARGET, the last argument must be a directory; create links in DIRECTORY to each TARGET. Create hard links by default, symbolic links with '--symbolic'. When creating hard links, each TARGET must exist.") (let ((no-dereference t)) (eshell-mvcpln-template "ln" "linking" (if symbolic 'make-symbolic-link 'add-name-to-file) eshell-ln-interactive-query eshell-ln-overwrite-files)))) (put 'eshell/ln 'eshell-no-numeric-conversions t) (defun eshell/cat (&rest args) "Implementation of cat in Lisp. If in a pipeline, or the file is not a regular file, directory or symlink, then revert to the system's definition of cat." (setq args (eshell-stringify-list (eshell-flatten-list args))) (if (or eshell-in-pipeline-p (catch 'special (eshell-for arg args (unless (or (and (stringp arg) (> (length arg) 0) (eq (aref arg 0) ?-)) (let ((attrs (eshell-file-attributes arg))) (and attrs (memq (aref (nth 8 attrs) 0) '(?d ?l ?-))))) (throw 'special t))))) (let ((ext-cat (eshell-search-path "cat"))) (if ext-cat (throw 'eshell-replace-command (eshell-parse-command ext-cat args)) (if eshell-in-pipeline-p (error "Eshell's `cat' does not work in pipelines") (error "Eshell's `cat' cannot display one of the files given")))) (eshell-init-print-buffer) (eshell-eval-using-options "cat" args '((?h "help" nil nil "show this usage screen") :external "cat" :show-usage :usage "[OPTION] FILE... Concatenate FILE(s), or standard input, to standard output.") (eshell-for file args (if (string= file "-") (throw 'eshell-external (eshell-external-command "cat" args)))) (let ((curbuf (current-buffer))) (eshell-for file args (with-temp-buffer (insert-file-contents file) (goto-char (point-min)) (while (not (eobp)) (let ((str (buffer-substring (point) (min (1+ (line-end-position)) (point-max))))) (with-current-buffer curbuf (eshell-buffered-print str))) (forward-line))))) (eshell-flush) ;; if the file does not end in a newline, do not emit one (setq eshell-ensure-newline-p nil)))) (put 'eshell/cat 'eshell-no-numeric-conversions t) ;; special front-end functions for compilation-mode buffers (defun eshell/make (&rest args) "Use `compile' to do background makes." (if (and eshell-current-subjob-p (eshell-interactive-output-p)) (let ((compilation-process-setup-function (list 'lambda nil (list 'setq 'process-environment (list 'quote (eshell-copy-environment)))))) (compile (concat "make " (eshell-flatten-and-stringify args)))) (throw 'eshell-replace-command (eshell-parse-command "*make" (eshell-stringify-list (eshell-flatten-list args)))))) (put 'eshell/make 'eshell-no-numeric-conversions t) (defun eshell-occur-mode-goto-occurrence () "Go to the occurrence the current line describes." (interactive) (let ((pos (occur-mode-find-occurrence))) (pop-to-buffer (marker-buffer pos)) (goto-char (marker-position pos)))) (defun eshell-occur-mode-mouse-goto (event) "In Occur mode, go to the occurrence whose line you click on." (interactive "e") (let (pos) (save-excursion (set-buffer (window-buffer (posn-window (event-end event)))) (save-excursion (goto-char (posn-point (event-end event))) (setq pos (occur-mode-find-occurrence)))) (pop-to-buffer (marker-buffer pos)) (goto-char (marker-position pos)))) (defun eshell-poor-mans-grep (args) "A poor version of grep that opens every file and uses `occur'. This eats up memory, since it leaves the buffers open (to speed future searches), and it's very slow. But, if your system has no grep available..." (save-selected-window (let ((default-dir default-directory)) (with-current-buffer (get-buffer-create "*grep*") (let ((inhibit-read-only t) (default-directory default-dir)) (erase-buffer) (occur-mode) (let ((files (eshell-stringify-list (eshell-flatten-list (cdr args)))) (inhibit-redisplay t) string) (when (car args) (if (get-buffer "*Occur*") (kill-buffer (get-buffer "*Occur*"))) (setq string nil) (while files (with-current-buffer (find-file-noselect (car files)) (save-excursion (ignore-errors (occur (car args)))) (if (get-buffer "*Occur*") (with-current-buffer (get-buffer "*Occur*") (setq string (buffer-string)) (kill-buffer (current-buffer))))) (if string (insert string)) (setq string nil files (cdr files))))) (local-set-key [mouse-2] 'eshell-occur-mode-mouse-goto) (local-set-key [(control ?c) (control ?c)] 'eshell-occur-mode-goto-occurrence) (local-set-key [(control ?m)] 'eshell-occur-mode-goto-occurrence) (local-set-key [return] 'eshell-occur-mode-goto-occurrence) (pop-to-buffer (current-buffer) t) (goto-char (point-min)) (resize-temp-buffer-window)))))) (defun eshell-grep (command args &optional maybe-use-occur) "Generic service function for the various grep aliases. It calls Emacs' grep utility if the command is not redirecting output, and if it's not part of a command pipeline. Otherwise, it calls the external command." (if (and maybe-use-occur eshell-no-grep-available) (eshell-poor-mans-grep args) (if (or eshell-plain-grep-behavior (not (and (eshell-interactive-output-p) (not eshell-in-pipeline-p) (not eshell-in-subcommand-p)))) (throw 'eshell-replace-command (eshell-parse-command (concat "*" command) (eshell-stringify-list (eshell-flatten-list args)))) (let* ((args (mapconcat 'identity (mapcar 'shell-quote-argument (eshell-stringify-list (eshell-flatten-list args))) " ")) (cmd (progn (set-text-properties 0 (length args) '(invisible t) args) (format "%s -n %s" command args))) compilation-scroll-output) (grep cmd))))) (defun eshell/grep (&rest args) "Use Emacs grep facility instead of calling external grep." (eshell-grep "grep" args t)) (defun eshell/egrep (&rest args) "Use Emacs grep facility instead of calling external egrep." (eshell-grep "egrep" args t)) (defun eshell/fgrep (&rest args) "Use Emacs grep facility instead of calling external fgrep." (eshell-grep "fgrep" args t)) (defun eshell/agrep (&rest args) "Use Emacs grep facility instead of calling external agrep." (eshell-grep "agrep" args)) (defun eshell/glimpse (&rest args) "Use Emacs grep facility instead of calling external glimpse." (let (null-device) (eshell-grep "glimpse" (append '("-z" "-y") args)))) ;; completions rules for some common UNIX commands (defsubst eshell-complete-hostname () "Complete a command that wants a hostname for an argument." (pcomplete-here (eshell-read-host-names))) (defun eshell-complete-host-reference () "If there is a host reference, complete it." (let ((arg (pcomplete-actual-arg)) index) (when (setq index (string-match "@[a-z.]*\\'" arg)) (setq pcomplete-stub (substring arg (1+ index)) pcomplete-last-completion-raw t) (throw 'pcomplete-completions (eshell-read-host-names))))) (defalias 'pcomplete/ftp 'eshell-complete-hostname) (defalias 'pcomplete/ncftp 'eshell-complete-hostname) (defalias 'pcomplete/ping 'eshell-complete-hostname) (defalias 'pcomplete/rlogin 'eshell-complete-hostname) (defun pcomplete/telnet () (require 'pcmpl-unix) (pcomplete-opt "xl(pcmpl-unix-user-names)") (eshell-complete-hostname)) (defun pcomplete/rsh () "Complete `rsh', which, after the user and hostname, is like xargs." (require 'pcmpl-unix) (pcomplete-opt "l(pcmpl-unix-user-names)") (eshell-complete-hostname) (pcomplete-here (funcall pcomplete-command-completion-function)) (funcall (or (pcomplete-find-completion-function (pcomplete-arg 1)) pcomplete-default-completion-function))) (defalias 'pcomplete/ssh 'pcomplete/rsh) (eval-when-compile (defvar block-size) (defvar by-bytes) (defvar dereference-links) (defvar grand-total) (defvar human-readable) (defvar max-depth) (defvar only-one-filesystem) (defvar show-all)) (defsubst eshell-du-size-string (size) (let* ((str (eshell-printable-size size human-readable block-size t)) (len (length str))) (concat str (if (< len 8) (make-string (- 8 len) ? ))))) (defun eshell-du-sum-directory (path depth) "Summarize PATH, and its member directories." (let ((entries (eshell-directory-files-and-attributes path)) (size 0.0)) (while entries (unless (string-match "\\`\\.\\.?\\'" (caar entries)) (let* ((entry (concat path "/" (caar entries))) (symlink (and (stringp (cadr (car entries))) (cadr (car entries))))) (unless (or (and symlink (not dereference-links)) (and only-one-filesystem (/= only-one-filesystem (nth 12 (car entries))))) (if symlink (setq entry symlink)) (setq size (+ size (if (eq t (cadr (car entries))) (eshell-du-sum-directory entry (1+ depth)) (let ((file-size (nth 8 (car entries)))) (prog1 file-size (if show-all (eshell-print (concat (eshell-du-size-string file-size) entry "\n"))))))))))) (setq entries (cdr entries))) (if (or (not max-depth) (= depth max-depth) (= depth 0)) (eshell-print (concat (eshell-du-size-string size) (directory-file-name path) "\n"))) size)) (defun eshell/du (&rest args) "Implementation of \"du\" in Lisp, passing ARGS." (setq args (if args (eshell-stringify-list (eshell-flatten-list args)) '("."))) (let ((ext-du (eshell-search-path "du"))) (if (and ext-du (not (catch 'have-ange-path (eshell-for arg args (if (eq (find-file-name-handler (expand-file-name arg) 'directory-files) 'ange-ftp-hook-function) (throw 'have-ange-path t)))))) (throw 'eshell-replace-command (eshell-parse-command ext-du args)) (eshell-eval-using-options "du" args '((?a "all" nil show-all "write counts for all files, not just directories") (nil "block-size" t block-size "use SIZE-byte blocks (i.e., --block-size SIZE)") (?b "bytes" nil by-bytes "print size in bytes") (?c "total" nil grand-total "produce a grand total") (?d "max-depth" t max-depth "display data only this many levels of data") (?h "human-readable" 1024 human-readable "print sizes in human readable format") (?H "is" 1000 human-readable "likewise, but use powers of 1000 not 1024") (?k "kilobytes" 1024 block-size "like --block-size 1024") (?L "dereference" nil dereference-links "dereference all symbolic links") (?m "megabytes" 1048576 block-size "like --block-size 1048576") (?s "summarize" 0 max-depth "display only a total for each argument") (?x "one-file-system" nil only-one-filesystem "skip directories on different filesystems") (nil "help" nil nil "show this usage screen") :external "du" :usage "[OPTION]... FILE... Summarize disk usage of each FILE, recursively for directories.") (unless by-bytes (setq block-size (or block-size 1024))) (if (and max-depth (stringp max-depth)) (setq max-depth (string-to-number max-depth))) ;; filesystem support means nothing under Windows (if (eshell-under-windows-p) (setq only-one-filesystem nil)) (let ((size 0.0) ange-cache) (while args (if only-one-filesystem (setq only-one-filesystem (nth 11 (eshell-file-attributes (file-name-as-directory (car args)))))) (setq size (+ size (eshell-du-sum-directory (directory-file-name (car args)) 0))) (setq args (cdr args))) (if grand-total (eshell-print (concat (eshell-du-size-string size) "total\n")))))))) (defvar eshell-time-start nil) (defun eshell-show-elapsed-time () (let ((elapsed (format "%.3f secs\n" (- (eshell-time-to-seconds (current-time)) eshell-time-start)))) (set-text-properties 0 (length elapsed) '(face bold) elapsed) (eshell-interactive-print elapsed)) (remove-hook 'eshell-post-command-hook 'eshell-show-elapsed-time t)) (defun eshell/time (&rest args) "Implementation of \"time\" in Lisp." (let ((time-args (copy-alist args)) (continue t) last-arg) (while (and continue args) (if (not (string-match "^-" (car args))) (progn (if last-arg (setcdr last-arg nil) (setq args '(""))) (setq continue nil)) (setq last-arg args args (cdr args)))) (eshell-eval-using-options "time" args '((?h "help" nil nil "show this usage screen") :external "time" :show-usage :usage "COMMAND... Show wall-clock time elapsed during execution of COMMAND.") (setq eshell-time-start (eshell-time-to-seconds (current-time))) (add-hook 'eshell-post-command-hook 'eshell-show-elapsed-time nil t) ;; after setting (throw 'eshell-replace-command (eshell-parse-command (car time-args) ;;; http://lists.gnu.org/archive/html/bug-gnu-emacs/2007-08/msg00205.html (eshell-stringify-list (eshell-flatten-list (cdr time-args)))))))) (defalias 'eshell/whoami 'user-login-name) (defvar eshell-diff-window-config nil) (defun eshell-diff-quit () "Restore the window configuration previous to diff'ing." (interactive) (if eshell-diff-window-config (set-window-configuration eshell-diff-window-config))) (defun nil-blank-string (string) "Return STRING, or nil if STRING contains only non-blank characters." (cond ((string-match "[^[:blank:]]" string) string) (nil))) (defun eshell/diff (&rest args) "Alias \"diff\" to call Emacs `diff' function." (let ((orig-args (eshell-stringify-list (eshell-flatten-list args)))) (if (or eshell-plain-diff-behavior (not (and (eshell-interactive-output-p) (not eshell-in-pipeline-p) (not eshell-in-subcommand-p)))) (throw 'eshell-replace-command (eshell-parse-command "*diff" orig-args)) (setq args (copy-sequence orig-args)) (if (< (length args) 2) (throw 'eshell-replace-command (eshell-parse-command "*diff" orig-args))) (let ((old (car (last args 2))) (new (car (last args))) (config (current-window-configuration))) (if (= (length args) 2) (setq args nil) (setcdr (last args 3) nil)) (with-current-buffer (condition-case err (diff old new (nil-blank-string (eshell-flatten-and-stringify args))) (error (throw 'eshell-replace-command (eshell-parse-command "*diff" orig-args)))) (when (fboundp 'diff-mode) (make-local-variable 'compilation-finish-functions) (add-hook 'compilation-finish-functions `(lambda (buff msg) (with-current-buffer buff (diff-mode) (set (make-local-variable 'eshell-diff-window-config) ,config) (local-set-key [?q] 'eshell-diff-quit) (if (fboundp 'turn-on-font-lock-if-enabled) (turn-on-font-lock-if-enabled)) (goto-char (point-min)))))) (pop-to-buffer (current-buffer)))))) nil) (put 'eshell/diff 'eshell-no-numeric-conversions t) (defun eshell/locate (&rest args) "Alias \"locate\" to call Emacs `locate' function." (if (or eshell-plain-locate-behavior (not (and (eshell-interactive-output-p) (not eshell-in-pipeline-p) (not eshell-in-subcommand-p))) (and (stringp (car args)) (string-match "^-" (car args)))) (throw 'eshell-replace-command (eshell-parse-command "*locate" (eshell-stringify-list (eshell-flatten-list args)))) (save-selected-window (let ((locate-history-list (list (car args)))) (locate-with-filter (car args) (cadr args)))))) (put 'eshell/locate 'eshell-no-numeric-conversions t) (defun eshell/occur (&rest args) "Alias \"occur\" to call Emacs `occur' function." (let ((inhibit-read-only t)) (if (> (length args) 2) (error "usage: occur: (REGEXP &optional NLINES)") (apply 'occur args)))) (put 'eshell/occur 'eshell-no-numeric-conversions t) (provide 'em-unix) ;;; arch-tag: 2462edd2-a76a-4cf2-897d-92e9a82ac1c9 ;;; em-unix.el ends here