0

I have this python script for inserting raw email to db. Do not ask me why I am inserting raw mail to database.

import sys
from DB import *
import email

full_msg = sys.stdin.readlines()
j =  ''.join(full_msg)
msg = email.message_from_string(j)

sql = '''INSERT INTO Email(Id, Message) VALUES (NULL, %s)'''
db = DB()
db.query(sql, (msg, ))

It would be great if I can get uid of that message, so if I for example delete message from db I can also delete message my uid on imap server.

I do not want to login to imap server and then delete message by uid because I do not know user password since it is encrypted.

I was thinking to get for example msg['Message-Id'] and then to grep files in user maildir for that Message-Id and delete actual file but that sound totally wrong to me.

I know in python you have something like UIDNEXT in imaplib but that is under assumption I am logged in which I'm not.

UPDATE:

With this I can fetch next uid but I have to login. How to get UIDNEXT without login? By the way I use postfix/dovecot with mysql.

import getpass, sys
from imapclient import IMAPClient

try:
    hostname, username = sys.argv[1:]
except ValueError:
    print 'usage %s hostname username' % sys.argv[0]

c = IMAPClient(hostname, ssl=True)

try:
    c.login(username, getpass.getpass())
except c.Error, e:
    print "Could not login in:", e
    sys.exit(1)

else:
    select_dict = c.select_folder('INBOX', readonly=True)
    for k, v in select_dict.items():
        if k == 'UIDNEXT':
            print '%s: %r' % (k,v)
    c.logout()

NEW UPDATE

Sample of dovecot-uidlist

16762 W105493 S104093 :1417408077.2609_1.zumance
16763 S18340 W18608 :1417429204.3464_1.zumance

Code for geeting last line of dovecot-uidlist uid:

l = open("dovecot-uidlist").readlines()
print l[-1].split(" ")[0]

This is completed script for mail pipe:

import sys
import email
import re
from DB import *
full_msg = sys.stdin.readlines()
j =  ''.join(full_msg)

msg = email.message_from_string(j)
match = re.search(r'[\w\.-]+@[\w\.-]+', msg['to'])

address = match.group(0)
address = address.split("@")

with open("/var/vmail/"+address[1]+"/"+address[0]+"/dovecot-uidlist", 'r') as f:
  first_line = f.readline()
  nextuid = first_line.split(" ")
  nextuid = re.findall(r'\d+', nextuid[2])

sql = '''INSERT INTO Email(Id, Message, Uid, Domain, Mbox) VALUES (NULL, %s, %s, %s, %s)'''
db = DB()
db.query(sql, (msg, nextuid[0], address[1], address[0],  ))

Blog post with files at https://pregmatch.org/read/python-procmail

tripleee
  • 175,061
  • 34
  • 275
  • 318
pregmatch
  • 2,629
  • 6
  • 31
  • 68
  • @Oldskool thank you for noticing that out. But this is not just python question it is a global question regarding postfix, dovecot etc. – pregmatch Dec 01 '14 at 12:41

2 Answers2

3

Dovecot maintains the mapping between UID and filename in the file dovecot-uidlist. The file contains first a header line and then one line per message.

The header line looks like this:

1 1173189136 20221

The first digit is the version, the second the IMAP UIDVALIDITY, and the last is the next UID that will be used.

After that, each message has its own line looking like this:

20220 1035478339.27041_118.example.org:2,S

The first word is the UID, the next is the filename.

There's more information at the dovecot wiki.

Jenny D
  • 1,225
  • 9
  • 20
  • Thanks Jenny D, this was useful info. Please if you take a look my NEW UPDATE you will se that I have some extra stuff in there (3, 4, 5 columns in a row). What do you suggest that I do? Deliver mail and then read last line of domain/user/dovecot-uidlist split with " " and take first column in last row? – pregmatch Dec 01 '14 at 13:21
  • That sounds likely. Try it and see what happens? – Jenny D Dec 01 '14 at 13:36
  • This solution works. I was trying to do some pre-auth with python on dovecot but that does not work. You solution worked. Thank you. – pregmatch Dec 01 '14 at 13:41
  • I am having problems with dovecot. dovecot-uidlist is updated with new email (uid, filename) only when email client check for new emails. Only then email is moved from new/ to cur/ and dovecot-uidlist file is updated. That being said last line of dovecot-uidlist does not contain appropriate uid. Will +1 on last message uid solve this what do you think? – pregmatch Dec 01 '14 at 14:05
  • Look at the header line. As I wrote, the last word on the header line is the next UID that will be used. – Jenny D Dec 01 '14 at 14:07
  • I am having problems again. dovecot-uidlist is updated only when you check for new email. This means when I pipe for example 10 emails all of them will have same uid since none of them are moved from new/ to cur/. Is there any way that I can fix this? – pregmatch Dec 03 '14 at 20:12
  • I found maildir_empty_new = yes (it was no) but that is not doing anything. Maybe I have to uncheck somethig else as well to make this thing work. Are you familiar with this? – pregmatch Dec 03 '14 at 20:34
  • take a look at my solution. – pregmatch Dec 04 '14 at 14:52
  • I'm glad that you found a solution, even if it didn't match the requirement that it should work without login. – Jenny D Dec 04 '14 at 15:27
  • (I was looking for solution without login/pass since i do not know pass). Anyway your suggestion put me on right path. – pregmatch Dec 04 '14 at 15:56
0

Since parsing dovecot-uidlist will not work because list will not be updated until you check email with your email client, I decide to go with other solution. That solution is dovecot pre-auth mechanism. In my python procmail pipe script I decided to do somethig like this:

import subprocess
p = subprocess.Popen( "/usr/libexec/dovecot/imap -u "+user, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
p.stdin.write("b select INBOX\n")
p.stdin.write("e logout\n")
(stdoutdata, stderrdata) = p.communicate()
print stdoutdata

print stderrdata

stdoutdata gives me my output that look like this:

* PREAUTH [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS THREAD=ORDEREDSUBJECT MULTIAPPEND URL-PARTIAL CATENATE UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS SPECIAL-USE BINARY MOVE] Logged in as nikola@pregmatch.org
* FLAGS (\Answered \Flagged \Deleted \Seen \Draft $NotJunk NotJunk $Forwarded)
* OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Seen \Draft $NotJunk NotJunk $Forwarded \*)] Flags permitted.
* 5574 EXISTS
* 0 RECENT
* OK [UIDVALIDITY 1412448500] UIDs valid
* OK [UIDNEXT 16875] Predicted next UID
* OK [HIGHESTMODSEQ 3051] Highest
b OK [READ-WRITE] Select completed (0.009 secs).
* BYE Logging out
e OK Logout completed.

Now all i have to do is parse this part of that output:

* OK [UIDVALIDITY 1412448500] UIDs valid
* OK [UIDNEXT 16875] Predicted next UID

This pre-auth solved my problem. I will post complete solution with parsing part later today (and on my blog).

SOLUTION:

import subprocess
pSub = subprocess.Popen( "/usr/libexec/dovecot/imap -u "+username+"@"+domain_parsed, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
pSub.stdin.write("b select INBOX\n")
pSub.stdin.write("e logout\n")
(stdoutdata, stderrdata) = pSub.communicate()
dovecotStream = open("/var/www/domain.com/scripts/server/dovecot","w")
dovecotStream.write(stdoutdata)
dovecotStream.close()


nextuidNo = []
with open("/var/www/domain.com/scripts/server/dovecot") as dovecotFile:
    dovecotFilelines = dovecotFile.read()
for dovecotFileline in dovecotFilelines.split('\n'):
    matchCheck = re.findall(r'\[UIDNEXT.+\]', dovecotFileline)
        if len(matchCheck):
            nextuidNo = re.findall(r'\d+', matchCheck[0])

print nextuidNo #this is list

Complete blog post at: https://pregmatch.org/read/dovecot-preauth-python

pregmatch
  • 2,629
  • 6
  • 31
  • 68