File: getmail_mbox.py

package info (click to toggle)
getmail 3.2.5-3
  • links: PTS
  • area: main
  • in suites: sarge
  • size: 428 kB
  • ctags: 161
  • sloc: python: 1,777; sh: 31; makefile: 12
file content (133 lines) | stat: -rw-r--r-- 4,836 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#!/usr/bin/python
'''getmail_mbox.py - mboxrd delivery agent, using flock locking.
Reads a message from stdin and delivers it to an mbox file specified as
a commandline argument.  Expects the envelope sender address to be in the
environment variable SENDER.
Copyright (C) 2001-2003 Charles Cazabon <getmail @ discworld.dyndns.org>

This program is free software; you can redistribute it and/or
modify it under the terms of version 2 of the GNU General Public License
as published by the Free Software Foundation.  A copy of this license should
be included in the file COPYING.

This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

'''

__version__ = '1.2.0'
__author__ = 'Charles Cazabon <getmail @ discworld.dyndns.org>'

#
# Imports
#


import sys
import os
import string
import time
import fcntl
import stat
import re

res = {
    # Regular expression object to escape "From ", ">From ", ">>From ", ...
    # with ">From ", ">>From ", ... in mbox deliveries.  This is for mboxrd format
    # mboxes.
    'escapefrom' : re.compile(r'^(?P<gts>\>*)From ', re.MULTILINE),
}

#######################################
class DeliveryException(Exception):
    pass

#######################################
def mbox_timestamp():
    '''Return the current time in the format expected in an mbox From_ line.'''
    return time.asctime(time.gmtime(int(time.time())))

#######################################
def lock_file(file):
    '''Do fcntl file locking
    '''
    fcntl.flock(file.fileno(), fcntl.LOCK_EX)

#######################################
def unlock_file(file):
    '''Do fcntl file unlocking
    '''
    fcntl.flock(file.fileno(), fcntl.LOCK_UN)

#######################################
def deliver_mbox(mbox, msg, env_sender, quiet=0):
    'Deliver a mail message into an mbox file.'
    try:
        # When orig_length is None, we haven't opened the file yet
        orig_length = None
        # Open mbox file
        f = open(mbox, 'ab+')
        lock_file(f)
        status_old = os.fstat(f.fileno())
        orig_length = status_old[stat.ST_SIZE]  # Save original length
        # Check if it _is_ an mbox file
        # mbox files must start with "From " in their first line, or
        # are 0-length files.
        f.seek(0, 0)                   # Seek to start
        first_line = f.readline()
        if first_line != '' and first_line[:5] != 'From ':
            # Not an mbox file; abort here
            unlock_file(f)
            f.close()
            raise DeliveryException('destination "%s" is not an mbox file' % mbox)
        # Add mboxrd-style 'From_' line if not already present
        if msg[:5] != 'From ':
            f.write('From %s %s\n' % (env_sender, mbox_timestamp()))
        else:
            first_line, remainder = string.split(msg, '\n', 1)
            f.write('%s\n' % first_line)
            msg = remainder
        # Replace lines beginning with "From ", ">From ", ">>From ", ...
        # with ">From ", ">>From ", ">>>From ", ...
        msg = res['escapefrom'].sub('>\g<gts>From ', msg)
        # Add trailing newline if last line incomplete
        if msg[-1] != '\n':  msg = msg + '\n'
        # Write out message
        f.write(msg)
        # Add trailing blank line
        f.write('\n')
        f.flush()
        os.fsync(f.fileno())
        # Unlock and close file
        status_new = os.fstat(f.fileno())
        unlock_file(f)
        f.close()
        # Reset atime
        try:
            os.utime(mbox, (status_old[stat.ST_ATIME], status_new[stat.ST_MTIME]))
        except OSError, txt:
            # Not root or owner; readers will not be able to reliably
            # detect new mail.  But you shouldn't be delivering to
            # other peoples' mboxes unless you're root, anyways.
            if not quiet:
                sys.stderr.write('Warning:  failed to update atime/mtime of mbox file (%s)...\n' % txt)

    except IOError, txt:
        try:
            if not f.closed and not orig_length is None:
                # If the file was opened and we know how long it was,
                # try to truncate it back to that length
                # If it's already closed, or the error occurred at close(),
                # then there's not much we can do.
                f.truncate(orig_length)
            unlock_file(f)
            f.close()
        except:
            pass
        raise DeliveryException('failure writing message to mbox file "%s" (%s)' % (mbox, txt))