1#!/usr/bin/python
2#
3# Usage: faxnotify log-file image-file [image-file ...]
4#
5# This script converts received fax TIFF files to a PDF 
6# file and delivers them according to the fax preferences.
7#
8
9import os, sys, string
10import shutil, errno, socket
11
12from email import Encoders
13from email.Message import Message
14from email.MIMEBase import MIMEBase
15from email.MIMEMultipart import MIMEMultipart
16
17
18def defaults_read(key, domain = '/Library/Preferences/com.apple.print.FaxPrefs'):
19	"""Read the value of 'key' from the defaults system"""
20	i,o,e = os.popen3('/usr/bin/defaults read ' + domain + ' ' + key)
21	i.close()
22	value = string.rstrip(o.read())
23	o.close()
24	e.close()
25	return value
26
27
28def quote(x):
29	"""Add quotes to a shell command argument string"""
30	if '\'' not in x :
31		return '\'' + x + '\''
32	s = '"'
33	for c in x:
34		if c in '\\$"`':
35			s = s + '\\'
36		s = s + c
37	s = s + '"'
38	return s
39
40
41def phone_filter(c):
42	"""Limit a phone number's character set so it can always be used in file names"""
43	return c in ' #()*+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz'
44
45def phone_from_log(logfilename):
46	"""Find the sender's phone number so it can be used in the PDF name"""
47	phone_number = ''
48	PHONE_KEY = 'remote ID ->'
49	logfile = file(logfilename)
50	for line in logfile :
51		index = line.find(PHONE_KEY)
52		if index != -1 :
53			phone_number = string.strip(line[index + len(PHONE_KEY):])
54			phone_number = filter(phone_filter, phone_number)
55			break
56	logfile.close()
57	if len(phone_number) == 0 :
58		phone_number = 'Unknown'
59	return phone_number
60
61
62def copy_versioned_file(srcfile, dstdir):
63	"""Copy a file to a directory adding versioning as needed"""
64	fsrc = None
65	fdst = None
66
67	if not os.path.isdir(dstdir) :
68		print >> sys.stderr, 'faxnotify: Save to directory missing ' + quote(dstdir.encode('unicode-escape'))
69		return -1
70
71	old_uid = os.getuid()
72	old_mask = os.umask(002)
73
74	try:
75		# open src as root
76		fsrc = open(srcfile, 'rb')
77
78		# get the owner of the destination directory
79		dstdir_owner = os.lstat(dstdir).st_uid
80
81		# step down to the dir owner's uid before creating the file
82		os.seteuid(dstdir_owner)
83
84		# create the full path for the destination file and seperate 
85		#  the components in case we have to version it
86		dstfile = dstdir + '/' + os.path.split(srcfile)[1]
87		name, ext = os.path.splitext(dstfile)
88
89		for i in xrange(1, 10000):
90			try:
91				fd = os.open(dstfile, os.O_WRONLY | os.O_CREAT | os.O_EXCL)
92				fdst = os.fdopen(fd, 'wb')
93				break
94			except (OSError, IOError), e:
95				if e.errno != errno.EEXIST:
96					raise
97
98			# file exists, find next available version
99			dstfile = '%s %d%s' % (name, i, ext)
100
101		# we've created a new unique file, now copy the contents of src into it
102		shutil.copyfileobj(fsrc, fdst)
103	
104	except (OSError, IOError), e:
105		print >> sys.stderr, 'faxnotify: Save to failed;' , e
106	except:
107		print >> sys.stderr, 'faxnotify: Save to ' + quote(dstdir.encode('unicode-escape')) + ' failed'
108		pass
109
110	if fsrc:
111		fsrc.close()
112	if fdst:
113		fdst.close()
114	os.umask(old_mask)
115	os.seteuid(old_uid)
116
117	return 0
118
119
120def email_fax(receipents, sender, pdffile):
121	"""Send email to the receipents with an attached file"""
122	msg = MIMEMultipart()
123	msg['Subject'] = pdffile
124	msg['To'] = receipents
125	msg['From'] = sender
126	msg.preamble = 'You will not see this in a MIME-aware mail reader.\n'
127	# To guarantee the message ends with a newline
128	msg.epilogue = ''
129
130	fp = open(pdffile)
131	pdf = MIMEBase('application', 'pdf')
132	pdf.set_payload(fp.read())
133	fp.close()
134
135	# Encode the payload using Base64
136	Encoders.encode_base64(pdf)
137
138	# Set the filename parameter
139	pdf.add_header('Content-Disposition', 'attachment', filename=pdffile)
140	msg.attach(pdf)
141
142	stdout, stdin = os.popen2('/usr/sbin/sendmail -oi -t -r' + quote(sender))
143	stdout.write(msg.as_string())
144	stdout.close()
145	stdin.close()
146
147
148#
149# Make sure we have enough arguments
150#
151if len(sys.argv) == 1 :
152	print >> sys.stderr, 'Usage: faxnotify log-file image-file [image-file ...]'
153	sys.exit(2)
154
155# If there are no tiff files to process just exit quietly.
156if len(sys.argv) == 2 :
157	sys.exit(0)
158
159tiff_files = string.join(map(quote, sys.argv[2:]))
160pdf_file = 'FAX from ' + phone_from_log(sys.argv[1]) + '.pdf'
161
162try:
163	# Convert TIFFs into a single PDF
164	command = '/usr/libexec/fax/imagestopdf ' + tiff_files + ' ' + quote(pdf_file)
165
166	if os.system(command) :
167		print >> sys.stderr, 'faxnotify: ' + command + ' failed'
168		sys.exit(1)
169
170	# Print on printer
171	if defaults_read('PrintFax') == '1' :
172		printer = quote(defaults_read('PrinterID'))
173		faxuser = defaults_read('FaxUser')
174		if faxuser == '' :
175			faxuser = 'FaxNotify'
176		faxuser = quote(faxuser)
177
178		# Add paper size and scale to fit options
179		options = ''
180		paper_id = defaults_read('DefaultPaperID')
181		if paper_id != '' :
182			options = '-o media=' + quote(paper_id)
183		options = options + ' -o fitplot'
184
185		command = '/usr/bin/lpr -P ' + printer + ' -U ' + faxuser + ' ' + options + ' ' + quote(pdf_file)
186		if os.system(command) :
187			print >> sys.stderr, 'faxnotify: Print on printer ' + printer + ' failed'
188
189	# Save to
190	if defaults_read('SaveFax') == '1' :
191		copy_versioned_file(pdf_file, unicode(defaults_read('SavePath'), 'unicode-escape'))
192
193	# Email to
194	if defaults_read('EmailFax') == '1' :
195		recipients = defaults_read('EmailRecipient')
196		sender = defaults_read('EmailSender')
197		if sender == '' :
198			hostname = socket.gethostname()
199			if hostname.endswith('.local') or hostname.endswith('.local.') :
200				sender = recipients.split(',')[0].strip(string.whitespace + '<>')
201			else :
202				sender = 'FaxNotify'
203		email_fax(recipients, sender, pdf_file)
204
205finally:
206	# Remove the TIFFs and converted PDF
207	map(os.remove, sys.argv[2:])
208	if os.path.exists(pdf_file):
209		os.remove(pdf_file)
210