imapfilter-2.5.2/0002755000175000017500000000000011757662151013511 5ustar frankiefrankieimapfilter-2.5.2/doc/0002755000175000017500000000000011757662105014255 5ustar frankiefrankieimapfilter-2.5.2/doc/imapfilter.10000644000175000017500000000376411757662071016505 0ustar frankiefrankie.Dd February 19, 2011 .Dt IMAPFILTER 1 .Os .Sh NAME .Nm imapfilter .Nd mail filter .Sh SYNOPSIS .Nm .Op Fl iVv .Op Fl c Ar configfile .Op Fl d Ar debugfile .Op Fl e Ar 'command' .Op Fl l Ar logfile .Sh DESCRIPTION .Nm is a mail filtering utility. It connects to remote mail servers using the Internet Message Access Protocol (IMAP), sends searching queries to the server and processes mailboxes based on the results. It can be used to delete, copy, move, flag, etc. messages residing in mailboxes at the same or different mail servers. The 4rev1 and 4 versions of the IMAP protocol are supported. .Pp The command line options of .Xr imapfilter 1 are as follows: .Bl -tag -width Ds .It Fl c Ar configfile Path to the configuration file. The default is .Pa $HOME/.imapfilter/config.lua . .It Fl d Ar debugfile File that contains debugging information about the full communication with the server, along with other inner workings' details. .It Fl e Ar 'command' May be used to enter .Dq one line of configuration, while it is also possible to pipe a full configuration as a string. When this options is used, a configuration file will not be loaded. .It Fl i Enters interactive mode after executing the configuration file. .It Fl l Ar logfile File that contains logs of error messages produced. .It Fl V Displays version and copyright information. .It Fl v Enables printing of some brief details of the communication with the server. .El .Sh ENVIRONMENT .Bl -tag -width Ds .It Ev HOME User's home directory. .El .Sh FILES .Bl -tag -width Ds .It Pa $HOME/.imapfilter/config.lua Default configuration file. Because this file may contain sensitive data such as user passwords, the recommended permissions are read/write for the user, and not accessible by others. .It Pa $HOME/.imapfilter/certificates File where the SSL/TLS certificates are stored. .El .Sh SEE ALSO .Xr imapfilter_config 5 .Sh CONFORMING TO .Bl -tag -width Ds .It IMAP4rev1: RFC 3501, RFC 3348, RFC 2683, RFC 2595, RFC 2342, RFC 2195, RFC 2177 .It IMAP4: RFC 1730 .El imapfilter-2.5.2/doc/imapfilter_config.50000644000175000017500000007542411757662105020036 0ustar frankiefrankie.Dd February 27, 2012 .Dt IMAPFILTER_CONFIG 5 .Os .Sh NAME .Nm imapfilter_config .Nd imapfilter configuration file .Sh SYNOPSIS .Pa $HOME/.imapfilter/config.lua .Sh DESCRIPTION .Xr imapfilter 1 uses the Lua programming language as a configuration and extension language, therefore the configuration file is a Lua script. .Pp Although knowledge of Lua is not required to use .Xr imapfilter 1 , it is nonetheless recommended, especially if one wants to extend it. .Sh CONVENTIONS .Pp A brief description of the Lua values and types mentioned hereafter in the manual page follows: .Bl -item -offset 4n .It The .Vt nil is the type of the value .Dq nil , whose main property is to be different from any other value; usually it represents the absence of a useful value. .It The .Vt boolean is the type of the values .Dq true and .Dq false . Both .Dq nil and .Dq false make a condition false; any other value makes it true. .It The type .Vt number represents real numbers. .It The type .Vt string represents a sequence of characters and can be defined using single quotes, double quotes or double square brackets. .It The type .Vt table implements associative arrays, that is, arrays that can be indexed not only with numbers, but with any value. .It A .Vt function is a first-class value; it can be stored in variables, passed as argument to other functions, and returned as a result. .El .Sh OPTIONS Program's options are set using an already initialised .Vt table named .Dq options , in the following manner: .Bd -literal -offset 4n options.timeout = 120 options.namespace = false options.charset = 'ISO-8859-1' .Ed .Pp Available options are: .Bl -tag -width Ds .It Va cache When this option is enabled, parts of messages are cached locally in memory to avoid being downloaded more than once. The cache is preserved for the current session only. This variable takes a .Vt boolean as a value. Default is .Dq true . .It Va certificates When this option is enabled, the server certificate can be accepted and stored, in order to validate the authenticity of the server in future connections. This variable takes a .Vt boolean as a value. Default is .Dq true . .It Va charset Indicates to the server the character set of the strings for the searching methods. This variable takes a .Vt string as a value. By default no character set is set, and thus plain ASCII should be assumed by the server. .It Va create According to the IMAP specification, when trying to write a message to a non-existent mailbox, the server must send a hint to the client, whether it should create the mailbox and try again or not. However some IMAP servers don't follow the specification and don't send the correct response code to the client. By enabling this option the client tries to create the mailbox, despite of the server's response. This variable takes a .Vt boolean as a value. Default is .Dq false . .It Va close This option controls whether the currently selected mailbox is implicitly closed at the end of each performed operation, thus removing all messages that are marked deleted. This variable takes a .Vt boolean as a value. Default is .Dq false . .It Va crammd5 When this option is enabled and the server supports the Challenge-Response Authentication Mechanism (specifically CRAM-MD5), this method will be used for user authentication instead of a plaintext password LOGIN. This variable takes a .Vt boolean as a value. Default is .Dq true . .It Va expunge Normally, messages are marked for deletion and are actually deleted when the mailbox is closed. When this option is enabled, messages are expunged immediately after being marked deleted. This variable takes a .Vt boolean as a value. Default is .Dq true . .It Va info When this options is enabled, a summary of the program's actions is printed, while processing mailboxes. This variable takes a .Vt boolean as a value. Default is .Dq true . .It Va keepalive The time in minutes before terminating and re-issuing the IDLE command, in order to keep alive the connection, by resetting the inactivity timeout of the server. A standards compliant server must have an inactivity timeout of at least 30 minutes. But it may happen that some IMAP servers don't respect that, or some intermediary network device has a shorter timeout. By setting this option the above problem can be worked around. This variable takes a .Vt number as a value. Default is .Dq 29 minutes. .It Va namespace When enabled, the program gets the namespace of the user's personal mailboxes, and applies automatically the prefix and hierarchy delimiter to any mailboxes residing on the mail server; the user must use the .Sq / character as the delimiter and .Dq (ie. nothing) as the prefix, regardless of the folder format of the mail server. This must be disabled, if the user wants to manually specify mailbox names (eg. because they are not part of the user's personal namespace mailboxes). This variable takes .Vt boolean as a value. Default is .Dq true . .It Va recover With this option it is possible to control the recovery functionality, which restores a session (the connection to the server and the IMAP state at the time), after some unexpected event takes place. Currently there are two types of events that can close abnormally a session, and finally cause the program to terminate: network errors, and the IMAP BYE response which a server can send anytime. When this option is set to .Dq all the recovery function is triggered by both types of events, when set to .Dq errors only in the case of network errors, and when set to .Dq none the mechanism is completely disabled. Default is .Dq all . .It Va starttls When this option is enabled and the server supports the IMAP STARTTLS extension, a TLS connection will be negotiated with the mail server in the beginning of the session. This variable takes a .Vt boolean as value. Default is .Dq true . .It Va subscribe By enabling this option new mailboxes that were automatically created, get also subscribed; they are set active in order for IMAP clients to recognize them. This variable takes a .Vt boolean as a value. Default is .Dq false . .It Va timeout The time in seconds for the program to wait for a mail server's response. If not set, the client will block indefinitely. This variable takes a .Vt number as a value. Default is .Dq 60 seconds. .El .Sh ACCOUNTS Accounts are initialized using the .Fn IMAP function, and the details of the connection are defined using an account .Vt table : .Bd -literal -offset 4n myaccount = IMAP { server = 'imap.mail.server', username = 'me', password = 'secret', ssl = 'ssl3' } .Ed .Pp An account .Vt table must have the following elements: .Bl -tag -width Ds .It Va server The hostname of the IMAP server to connect to. It takes a .Vt string as a value. .It Va username User's name. It takes a .Vt string as a value. .El .Pp An account .Vt table can also have the following optional elements: .Bl -tag -width Ds .It Va password User's secret keyword. If a password wasn't supplied the user will be asked to enter one interactively the first time it will be needed. It takes a .Vt string as a value. .It Va port The port to connect to. It takes a .Vt number as a value. Default is .Dq 143 for imap and .Dq 993 for imaps. .It Va ssl Forces an imaps connection and specifies the SSL/TLS protocol to be used. It takes a .Vt string as a value, specifically one of: .Dq ssl2 , .Dq ssl3 , .Dq tls1 . .El .Pp .Ss LISTING The following methods can be used on an account to list mailboxes in a folder of an account: .Pp .Bl -tag -width Ds -compact .It Fn list_all folder Lists all the available mailboxes in the .Fa folder .Pq Vt string , and returns a .Vt table that contains .Vt strings , the available mailboxes, and a .Vt table that contains .Vt strings , the available folders. .Pp .It Fn list_subscribed folder Lists all the subscribed mailboxes in the .Fa folder .Pq Vt string , and returns a .Vt table that contains .Vt strings , the subscribed mailboxes, and a .Vt table that contains .Vt strings , the subscribed folders. .El .Pp The following methods can be used on an account to list mailboxes, using wildcards, in a folder of an account. The .Sq * wildcard, matches any character and the .Sq % matches any character except the folder delimiter, ie. non-recursively: .Pp .Bl -tag -width Ds -compact .It Fn list_all folder mailbox Lists all the available mailboxes in the .Fa folder .Pq Vt string with the name .Fa mailbox .Pq Vt string , and returns a .Vt table that contains .Vt strings , the available mailboxes, and a .Vt table that contains .Vt strings , the available folders. Wildcards may only be used in the .Fa mailbox argument. .Pp .It Fn list_subscribed folder mailbox Lists all the subscribed mailboxes in the .Fa folder .Pq Vt string with the name .Fa mailbox .Pq Vt string , and returns a .Vt table that contains .Vt strings , the subscribed mailboxes, and a .Vt table that contains .Vt strings , the subscribed folders. Wildcards may only be used in the .Fa mailbox argument. .El .Pp Examples: .Bd -literal -offset 4n mailboxes, folders = myaccount:list_subscribed('myfolder') mailboxes, folders = myaccount:list_all('myfolder/mysubfolder', '*') .Ed .Ss MANIPULATING The following methods can be used to manipulate mailboxes in an account: .Pp .Bl -tag -width Ds -compact .It Fn create_mailbox name Creates the .Fa name .Pq Vt string mailbox. .Pp .It Fn delete_mailbox name Deletes the .Fa name .Pq Vt string mailbox. .Pp .It Fn rename_mailbox oldname newname Renames the .Fa oldname .Pq Vt string mailbox to .Fa newname .Pq Vt string . .Pp .It Fn subscribe_mailbox name Subscribes the .Fa name .Pq Vt string mailbox. .Pp .It Fn unsubscribe_mailbox name Unsubscribes the .Fa name .Pq Vt string mailbox. .El .Pp Examples: .Bd -literal -offset 4n myaccount:create_mailbox('mymailbox') myaccount:subscribe_mailbox('mymailbox') myaccount:unsubscribe_mailbox('myfolder/mymailbox') myaccount:delete_mailbox('myfolder/mymailbox') .Ed .Sh MAILBOXES After an IMAP account has been initialized, mailboxes residing in that account can be accessed simply as elements of the account .Vt table : .Bd -literal -offset 4n myaccount.mymailbox .Ed .Pp If mailbox names don't only include letters, digits and underscores, or begin with a digit, an alternative form must be used: .Bd -literal -offset 4n myaccount['mymailbox'] .Ed .Pp A mailbox inside a folder can be only accessed by using the alternative form: .Bd -literal -offset 4n myaccount['myfolder/mymailbox'] .Ed .Pp The methods that are available for an account (eg. .Fn list_all , .Fn create_mailbox , etc.) , are considered keywords and must not be used as mailbox names, and the same also applies for any string starting with an underscore, as they are considered reserved. .Ss CHECKING The following methods can be used to check the status of a mailbox: .Pp .Bl -tag -width Ds -compact .It Fn check_status .Pp The .Fn check_status method gets the current status of a mailbox, and returns four values of .Vt number type: the total number of messages, the number of recent messages, the number of unseen messages in the mailbox, and the next UID to be assigned to a new message in the mailbox. .Pp .It Fn enter_idle The .Fn enter_idle method implements the IMAP IDLE (RFC 2177) extension. By using this extension it's not necessary to poll the server for changes to the selected mailbox (ie. using the .Fn check_status method), but instead the server sends an update when there is a change in the mailbox (eg. in case of new mail). When the .Fn enter_idle method has been called no more commands in the configuration file are executed until an update is received, at which point the .Fn enter_idle method returns. For the .Fn enter_idle to work, the IDLE extension has to be supported by the IMAP server. The .Fn enter_idle method returns one value of type .Vt boolean : .Dq true if the IDLE extension is supported and there was a update in the mailbox, and .Dq false if the IDLE extension is not supported, in which case the method returns immediately. .El .Pp Examples: .Bd -literal -offset 4n exist, unread, unseen, uidnext = myaccount.mymailbox:check_status() update = myaccount.mymailbox:enter_idle() .Ed .Ss SEARCHING .Pp The searching methods in this subsection can be applied to any mailbox. They return a special form of .Vt table , that contains the messages that match the searching method. This .Vt table can be combined with other .Vt tables using logic theory. There are three available operations, that implement logical .Dq or , logical .Dq and and logical .Dq not . .Pp The logical .Dq or is implemented using the .Sq + operator: .Bd -literal -offset 4n results = myaccount.mymailbox:is_unseen() + myaccount.mymailbox:is_larger(100000) .Ed .Pp The logical .Dq and is implemented using the .Sq * operator: .Bd -literal -offset 4n results = myaccount.mymailbox:is_unseen() * myaccount.mymailbox:is_larger(100000) .Ed .Pp The logical .Dq not is implemented using the .Sq - operator: .Bd -literal -offset 4n results = myaccount.mymailbox:is_unseen() - myaccount.mymailbox:is_larger(100000) .Ed .Pp The three logical operators can be combined in the same expression. The logical .Dq and has higher precedence than the logical .Dq or and the logical .Dq not , with the latter two having the same precedence, and parentheses may be used to change this behaviour: .Bd -literal -offset 4n results = myaccount.mymailbox:is_unseen() + myaccount.mymailbox:is_larger(100000) * myaccount.mymailbox:contain_subject('test') results = ( myaccount.mymailbox:is_unseen() + myaccount.mymailbox:is_larger(100000) ) * myaccount.mymailbox:contain_subject('test') .Ed .Pp The returned .Vt tables of the searching methods can also be stored in variables and then further processed: .Bd -literal -offset 4n unseen = myaccount.myaccount:is_unseen() larger = myaccount.mymailbox:is_larger(100000) subject = myaccount.mymailbox:contain_subject('test') results = unseen + larger * subject .Ed .Pp A composite filter that includes one or more simple rules can be defined: .Bd -literal -offset 4n myfilter = function () return myaccount.mymailbox:is_unseen() + myaccount.mymailbox:is_larger(100000) * myaccount.mymailbox:contain_subject('test') end results = myfilter() .Ed .Pp Composite filters can may be more dynamic by adding arguments: .Bd -literal -offset 4n myfilter = function (mailbox, size, subject) return mailbox:is_unseen() + mailbox:is_larger(size) * mailbox:contain_subject(subject) end results = myfilter(myaccount.mailbox, 100000, 'test') .Ed .Pp It is also possible to combine the searching methods in different mailboxes, either at the same or different accounts, for example when the same actions will be executed on messages residing in different mailboxes or accounts. .Bd -literal -offset 4n results = myaccount.mymailbox:is_unseen() + myaccount.myothermailbox:is_larger(100000) + myotheraccount.myothermailbox:contain_subject('test') .Ed .Pp The following method can be used to get all messages in a mailbox: .Pp .Bl -tag -width Ds -compact .It Fn select_all All messages. .El .Pp The following methods can be used to search for messages that are in a specific state: .Pp .Bl -tag -width Ds -compact .It Fn is_answered Messages that have been answered. .Pp .It Fn is_deleted Messages that are marked for later removal. .Pp .It Fn is_draft Messages that have not completed composition. .Pp .It Fn is_flagged Messages that are flagged for urgent/special attention. .Pp .It Fn is_new Messages that are recently arrived (this session is the first to have been notified about these messages) and have not been read. .Pp .It Fn is_old Messages that are not recently arrived (this session is not the first to have been notified about these messages) and have not been read. .Pp .It Fn is_recent Messages that are recently arrived (this session is the first to have been notified about these messages). .Pp .It Fn is_seen Messages that have been read. .Pp .It Fn is_unanswered Messages that have not been answered. .Pp .It Fn is_undeleted Messages that are not marked for later removal. .Pp .It Fn is_undraft Messages that have completed composition. .Pp .It Fn is_unflagged Messages that are not flagged for urgent/special attention. .Pp .It Fn is_unseen Messages that have not been read. .El .Pp The following method can be used to search for messages that have a specific flag set: .Pp .Bl -tag -width Ds -compact .It Fn has_flag keyword Messages with the specified keyword flag .Pq Vt string set. .El .Pp The following methods can be used to search for messages based on their size: .Pp .Bl -tag -width Ds -compact .It Fn is_larger size Messages that are larger than the size .Pq Vt number in octets (bytes). .Pp .It Fn is_smaller size Messages that are smaller than the size .Pq Vt number in octets (bytes). .El .Pp The following methods can be used to search for messages based on their age: .Pp .Bl -tag -width Ds -compact .It Fn is_newer age Messages that are newer than the .Fa age .Pq Vt number in days. .Pp .It Fn is_older age Messages that are older than the .Fa age .Pq Vt number in days. .El .Pp The following methods can be used to search for messages based on their arrival or sent date, in the .Dq day-month-year form, where day is the day of the month as a decimal number (01-31), month is the abbreviated month ( .Dq Jan , .Dq Feb , .Dq Mar , .Dq Apr , .Dq May , .Dq Jun , .Dq Jul , .Dq Aug , .Dq Sep , .Dq Oct , .Dq Nov , .Dq Dec ) and year is the year as decimal number including the century (eg. 2007): .Pp .Bl -tag -width Ds -compact .It Fn arrived_before date messages that have arrived before the .Fa date .Pq Vt string , where .Fa date is in the .Dq day-month-year form. .Pp .It Fn arrived_on date Messages that have arrived on the .Fa date .Pq Vt string , where .Fa date is in the .Dq day-month-year form. .Pp .It Fn arrived_since date Messages that have arrived after the .Fa date .Pq Vt string , where .Fa date is in the .Dq day-month-year form. .Pp .It Fn sent_before date Messages that have been sent before the .Fa date .Pq Vt string , where .Fa date is in the .Dq day-month-year form. .Pp .It Fn sent_on date Messages that have been sent on the .Fa date .Pq Vt string , where .Fa date is in the .Dq day-month-year form. .Pp .It Fn sent_since date Messages that have been sent after the .Fa date .Pq Vt string , where .Fa date is in the .Dq day-month-year form. .El .Pp The following methods can be used to search for messages that contain a specific word or phrase: .Pp .Bl -tag -width Ds -compact .It Fn contain_bcc string Messages that contain the .Fa string .Pq Vt string in the .Dq Bcc header field. .Pp .It Fn contain_cc string Messages that contain the .Fa string .Pq Vt string in the .Dq Cc header field. .Pp .It Fn contain_from string Messages that contain the .Fa string .Pq Vt string in the .Dq From header field. .Pp .It Fn contain_subject string Messages that contain the .Fa string .Pq Vt string in the .Dq Subject header field. .Pp .It Fn contain_to string Messages that contain the .Fa string .Pq Vt string in the .Dq To header field. .Pp .It Fn contain_field field string Messages that contain the .Fa string .Pq Vt string in the .Fa field .Pq Vt string header field. .Pp .It Fn contain_body string Messages that contain the .Fa string .Pq Vt string in the message body. .Pp .It Fn contain_message string Messages that contain the .Fa string .Pq Vt string in the message. .El .Pp The following methods can be used to search for messages that match a specific regular expression pattern. .Pp This way of searching is not supported by the IMAP protocol, and this means that what actually happens under the hood, is that the relevant parts of all the messages are downloaded and matched locally. It is therefore recommended to use these methods with meta-searching (see following section), in order to narrow down the set of messages that should be searched, and thus minimize what will be downloaded. .Pp Note that due to Lua using backslash .Sq \e as an escape character for its strings, one has to double backslashes in order to insert a single backslash inside a regular expression pattern: .Pp .Bl -tag -width Ds -compact .It Fn match_bcc pattern Messages that match the regular expression .Fa pattern .Pq Vt string in the .Dq Bcc header field. .Pp .It Fn match_cc pattern Messages that match the regular expression .Fa pattern .Pq Vt string in the .Dq Cc header field. .Pp .It Fn match_from pattern Messages that match the regular expression .Fa pattern .Pq Vt string in the .Dq From header field. .Pp .It Fn match_subject pattern Messages that match the regular expression .Fa pattern .Pq Vt string in the .Dq Subject header field. .Pp .It Fn match_to pattern Messages that match the regular expression .Fa pattern .Pq Vt string in the .Dq To header field. .Pp .It Fn match_field field pattern Messages that match the regular expression .Fa pattern .Pq Vt string in the .Fa field .Pq Vt string header field. .Pp .It Fn match_header pattern Messages that match the regular expression .Fa pattern .Pq Vt string in the message header. .Pp .It Fn match_body pattern Messages that match the regular expression .Fa pattern .Pq Vt string in the message body. .Pp .It Fn match_message pattern Messages that match the regular expression .Fa pattern .Pq Vt string in the message. .El .Pp The following method can be used to search for messages using user queries based on the IMAP specification (RFC 3501 Section 6.4.4): .Pp .Bl -tag -width Ds -compact .It Fn send_query criteria Searches messages by sending an IMAP search query as described in the search .Fa criteria .Pq Vt string . .El .Pp Examples: .Bd -literal -offset 4n results = myaccount.mymailbox:select_all() results = myaccount.mymailbox:is_new() results = myaccount.mymailbox:is_recent() results = myaccount.mymailbox:is_larger(100000) results = myaccount.mymailbox:is_older(10) results = myaccount.mymailbox:has_flag('MyFlag') results = myaccount.mymailbox:arrived_before('01-Jan-2007') results = myaccount.mymailbox:sent_since('01-Jan-2007') results = myaccount.mymailbox:contain_subject('test') results = myaccount.mymailbox:contain_field('Sender', 'user@host') results = myaccount.mymailbox:contain_body('hello world') results = myaccount.mymailbox:match_from('.*(user1|user2)@host') results = myaccount.mymailbox:send_query('ALL') results = myaccount['mymailbox']:is_new() results = myaccount['myfolder/mymailbox']:is_recent() .Ed .Sh RESULTS After one of more searching methods have been applied to one or more mailboxes, the result contains all the necessary information, such as which messages matched in which mailboxes. Using this result these messages can be either searched further or processed in various way. .Ss META-SEARCHING The results of the searching methods can be searched further on in the same way as searching is done in mailboxes. The difference is that instead of doing the search in the whole mailbox, ie. in all the messages, it is instead done only to those messages that were returned in a previous search. .Pp Examples: .Bd -literal -offset 4n results:match_message('^[Hh]ello world!?$') myaccount.mymailbox:is_new():match_body('^[Ww]orld, hello!?$') .Ed .Ss PROCESSING The processing methods are applied to the results that searching returned. .Pp The following method can be used to delete messages in a mailbox: .Pp .Bl -tag -width Ds -compact .It Fn delete_messages Deletes the messages that matched. .El .Pp The following methods can be used to copy and move messages in a mailbox at the same or different accounts. If the destination mailbox is in a different account than the source mailbox, then the messages are downloaded and then uploaded to the destination: .Pp .Bl -tag -width Ds -compact .It Fn copy_messages destination Copies the messages to the .Fa destination , which is a mailbox at an account. .Pp .It Fn move_messages destination Moves the messages to the .Fa destination , which is a mailbox at an account. .El .Pp The following methods can be used to mark messages in a mailbox: .Pp .Bl -tag -width Ds -compact .It Fn mark_answered Marks the messages as answered. .Pp .It Fn mark_deleted Marks the messages for later removal. .Pp .It Fn mark_draft Marks the messages as draft. .Pp .It Fn mark_flagged Marks the messages for urgent/special attention. .Pp .It Fn mark_seen Marks the messages as read. .Pp .It Fn unmark_answered Unmarks the messages that have been marked as answered. .Pp .It Fn unmark_deleted Unmarks the messages that have been marked for later removal. .Pp .It Fn unmark_draft Unmarks the messages that have been marked as draft. .Pp .It Fn unmark_flagged Unmarks the messages that have been marked for urgent/special attention. .Pp .It Fn unmark_seen Unmarks the messages that have been marked as read. .Pp .El .Pp The following methods can be used to flag messages in a mailbox. The standard system flags are .Dq \eAnswered , .Dq \eDeleted , .Dq \eDraft , .Dq \eFlagged , .Dq \eSeen , while if the server supports it, new user keywords may be defined: .Pp .Bl -tag -width Ds -compact .It Fn add_flags flags Adds the .Fa flags .Po .Vt table that contains .Vt strings .Pc to the messages. .Pp .It Fn remove_flags flags Removes the .Fa flags .Po .Vt table that contains .Vt strings .Pc from the messages. .Pp .It Fn replace_flags flags Replaces the .Fa flags .Po .Vt table that contains .Vt strings .Pc of the messages. .El .Pp Examples: .Bd -literal -offset 4n results:delete_messages() results:copy_messages(myaccount.myothermailbox) results:move_messages(myotheraccount.mymailbox) results:mark_seen() results:unmark_flagged() results:add_flags({ 'MyFlag', '\e\eSeen' }) results:remove_flags({ '\e\eSeen' }) results:move_messages(myotheraccount['myfolder/mymailbox']) .Ed .Sh MESSAGES The messages that are residing in any mailbox can be also accessed, as a whole or in parts. Messages can be accessed using their unique identifier (UID): .Bd -literal -offset 4n myaccount.mymailbox[22] .Ed .Pp The UIDs of messages the user is interested in, are gained from the results of searching: .Bd -literal -offset 4n results = account.INBOX:is_unseen() for _, message in ipairs(results) do mailbox, uid = table.unpack(message) header = mailbox[uid]:fetch_header() end .Ed .Ss FETCHING .Pp The following methods can be used to fetch parts of messages. The methods return a .Vt string . The downloaded message parts are cached locally, so they can be reused inside the same program session: .Pp .Bl -tag -width Ds -compact .It Fn fetch_message Fetches the header and body of the message. .Pp .It Fn fetch_header Fetches the header of the message. .Pp .It Fn fetch_body Fetches the body of the messages. .Pp .It Fn fetch_field field Fetches the specified header .Fa field .Pq Vt string of the message. .Pp .It Fn fetch_part part Fetches the specified .Fa part .Pq Vt string of the message. .El .Pp The following methods can be used to fetch details about the state of a message: .Pp .Bl -tag -width Ds -compact .It Fn fetch_flags Fetches the flags of the message. Returns a .Vt table of .Vt strings . .Pp .It Fn fetch_date Fetches the internal date of the message. Returns a .Vt string . .Pp .It Fn fetch_size Fetches the size of the message. Returns a .Vt number . .Pp .It Fn fetch_structure Fetches the body structure of the message. Returns a .Vt table that has as keys the parts of the message, and as values a .Vt table that has one mandatory element, the type .Pq Vt string of the part, and two optional elements, the size .Pq Vt number and name .Pq Vt string of the part. .El .Ss APPENDING .Pp The following methods can be used to append a message to a mailbox: .Pp .Bl -tag -width Ds -compact .It Fn append_message message Appends the .Fa message .Pq Vt string to the mailbox. .Pp .It Fn append_message message flags date Appends the .Fa message .Pq Vt string to the mailbox, setting the specified .Fa flags .Po .Vt table of .Vt strings .Pc , as returned by .Fn fetch_flags , and .Fa date .Pq Vt string , as returned by .Fn fetch_date . .El .Pp Examples: .Bd -literal -offset 4n myaccount.mymailbox[2]:fetch_message() myaccount.mymailbox[3]:fetch_field('subject') myaccount.mymailbox[5]:fetch_part('1.1') myaccount['mymailbox'][7]:fetch_message() myaccount['myfolder/mymailbox'][11]:fetch_message() myaccount.mymailbox:append_message(message) .Ed .Sh FUNCTIONS The following auxiliary functions are also available for convenience: .Pp .Bl -tag -width Ds -compact .It Fn form_date days Forms a date in .Dq day-month-year format that the system had before the number of .Fa days .Pq Vt number , and returns it as a .Vt string . .Pp .It Fn get_password prompt Displays the specified .Fa prompt .Pq Vt string , and reads a password, while character echoing is turned off. Returns that password as a .Vt string . .Pp .It Fn become_daemon interval commands Detaches the program from the controlling terminal and runs it in the background as system daemon. The program will then repeatedly poll at the specified .Fa interval .Pq Vt number in seconds. Each time the program wakes up, the .Fa commands .Pq Vt function are executed. .Pp .It Fn become_daemon interval commands nochdir noclose Detaches the program from the controlling terminal and runs it in the background as system daemon. The program will then repeatedly poll at the specified .Fa interval .Pq Vt number in seconds. Each time the program wakes up, the .Fa commands .Pq Vt function are executed. .Pp If .Fa nochdir .Pq Vt boolean is .Dq true , the current working directory is not changed to the root directory .Pq Pa / . If .Fa noclose .Pq Vt boolean is .Dq true , the standard input, standard output and standard error are not redirected to .Pa /dev/null . .Pp .It Fn pipe_to command data Executes the system's .Fa command .Pq Vt string and sends the .Fa data .Pq Vt string to the standard input channel of the subprocess. Returns a .Vt number , the exit status of the child process. .Pp .It Fn pipe_from command Executes the system's .Fa command .Pq Vt string and retrieves the data from the standard output channel of the subprocess. Returns a .Vt number , the exit status of the child process, and a .Vt string , the output of the child process. .Pp .It Fn regex_search pattern string Implements Perl-compatible regular expressions (PCRE). The .Fa pattern .Pq Vt string is a PCRE pattern. The .Vt string .Pq Vt string is the subject string in which the pattern is matched against. Returns at least a .Vt boolean , that denotes if the match was successful, and any captures which are of .Vt string type. Note that due to Lua using backslash .Sq \e as an escape character for its strings, one has to double backslashes in order to insert a single backslash inside a regular expression pattern: .El .Pp Examples: .Bd -literal -offset 4n date = form_date(14) password = get_password('Enter password: ') become_daemon(600, myfunction) status = pipe_to('mycommandline', 'mydata') status, data = pipe_from('mycommandline') success, capture = regex_search('^[PpCcRrEe]: (\e\ew)$', 'mystring') .Ed .Sh EXAMPLES See .Pa samples/config.lua and .Pa samples/extend.lua in the source code distribution. .Sh ENVIRONMENT .Bl -tag -width Ds .It Ev HOME User's home directory. .El .Sh SEE ALSO .Xr imapfilter 1 imapfilter-2.5.2/src/0002755000175000017500000000000011757662105014277 5ustar frankiefrankieimapfilter-2.5.2/src/Makefile0000644000175000017500000000365511757662071015750 0ustar frankiefrankieDESTDIR = PREFIX = /usr/local BINDIR = $(PREFIX)/bin SHAREDIR = $(PREFIX)/share/imapfilter MANDIR = $(PREFIX)/man MYCFLAGS = MYLDFLAGS = MYLIBS = INCDIRS = LIBDIRS = LIBLUA = -llua LIBPCRE = -lpcre LIBSSL = -lssl LIBCRYPTO = -lcrypto CFLAGS = -Wall -O -DCONFIG_SHAREDIR='"$(SHAREDIR)"' $(INCDIRS) $(MYCFLAGS) LDFLAGS = $(LIBDIRS) $(MYLDFLAGS) LIBS = -lm $(LIBLUA) $(LIBPCRE) $(LIBSSL) $(LIBCRYPTO) $(MYLIBS) MAN1 = imapfilter.1 MAN5 = imapfilter_config.5 LUA = common.lua set.lua regex.lua account.lua mailbox.lua message.lua \ options.lua auxiliary.lua BIN = imapfilter OBJ = auth.o buffer.o cert.o core.o file.o imapfilter.o list.o log.o lua.o \ memory.o misc.o namespace.o pcre.o regexp.o request.o response.o \ session.o signal.o socket.o system.o all: $(BIN) $(BIN): $(OBJ) $(CC) -o $(BIN) $(LDFLAGS) $(OBJ) $(LIBS) $(OBJ): imapfilter.h buffer.o: buffer.h cert.o: pathnames.h session.h file.o: pathnames.h imapfilter.o: buffer.h list.h pathnames.h regexp.h session.h version.h list.o: list.h log.o: list.h pathnames.h session.h lua.o: pathnames.h namespace.o: buffer.h regexp.o: regexp.h request.o: buffer.h session.h response.o: buffer.h regexp.h session.h session.o: list.h session.h socket.o: session.h install: $(BIN) mkdir -p $(DESTDIR)$(BINDIR) && \ cp -f $(BIN) $(DESTDIR)$(BINDIR) && \ chmod 0755 $(DESTDIR)$(BINDIR)/$(BIN) mkdir -p $(DESTDIR)$(SHAREDIR) && \ cp -f $(LUA) $(DESTDIR)$(SHAREDIR) && \ chmod 0644 $(DESTDIR)$(SHAREDIR)/$(LUA) mkdir -p $(DESTDIR)$(MANDIR)/man1 && \ cp -f ../doc/$(MAN1) $(DESTDIR)$(MANDIR)/man1 && \ chmod 0644 $(DESTDIR)$(MANDIR)/man1/$(MAN1) mkdir -p $(DESTDIR)$(MANDIR)/man5 && \ cp -f ../doc/$(MAN5) $(DESTDIR)$(MANDIR)/man5 && \ chmod 0644 $(DESTDIR)$(MANDIR)/man5/$(MAN5) uninstall: rm -f $(DESTDIR)$(BINDIR)/$(BIN) cd $(DESTDIR)$(SHAREDIR) && rm -f $(LUA) rm -f $(DESTDIR)$(MANDIR)/man1/$(MAN1) rm -f $(DESTDIR)$(MANDIR)/man5/$(MAN5) clean: rm -f $(OBJ) $(BIN) *~ imapfilter-2.5.2/src/auxiliary.lua0000644000175000017500000000307411757662071017015 0ustar frankiefrankie-- Miscellaneous auxiliary functions. function form_date(days) _check_required(days, 'number') return os.date('%d-%b-%Y', os.time() - days * 60 * 60 * 24) end function get_password(prompt) _check_optional(prompt, 'string') if prompt ~= nil then io.write(prompt) else io.write('Enter password: ') end ifsys.noecho() local p = io.read() ifsys.echo() return p end function pipe_to(command, data) _check_required(command, 'string') _check_required(data, 'string') f = ifsys.popen(command, 'w') ifsys.write(f, data) return ifsys.pclose(f) end function pipe_from(command) _check_required(command, 'string') f = ifsys.popen(command, 'r') local string = '' while true do s = ifsys.read(f) if s ~= nil then string = string .. s else break end end return ifsys.pclose(f), string end function become_daemon(interval, commands, nochdir, noclose) _check_required(interval, 'number') _check_required(commands, 'function') _check_optional(nochdir, 'boolean') _check_optional(noclose, 'boolean') if nochdir == nil then nochdir = false end if noclose == nil then noclose = false end ifsys.daemon(nochdir, noclose) _daemon = true repeat for _, account in pairs(_imap) do if not account._account.session then account:_login_user(account) end end commands() collectgarbage() until ifsys.sleep(interval) ~= 0 end sleep = ifsys.sleep imapfilter-2.5.2/src/options.lua0000644000175000017500000000016411536341114016461 0ustar frankiefrankie-- Options related to the interface implementation. options.cache = true options.close = false options.info = true imapfilter-2.5.2/src/log.c0000644000175000017500000000601611757662071015227 0ustar frankiefrankie#include #include #include #include #include #include #include #include #include #include "imapfilter.h" #include "session.h" #include "list.h" #include "pathnames.h" extern options opts; extern environment env; extern unsigned int flags; extern list *sessions; static FILE *debugfp = NULL; /* Pointer to debug file. */ static FILE *logfp = NULL; /* Pointer to log file. */ char *log_time(void); /* * Print message if in verbose mode. */ void verbose(const char *fmt,...) { va_list args; if (!opts.verbose) return; va_start(args, fmt); vprintf(fmt, args); va_end(args); } /* * Write message to debug file. */ void debug(const char *fmt,...) { va_list args; if (opts.debug <= 0 || !debugfp) return; va_start(args, fmt); vfprintf(debugfp, fmt, args); fflush(debugfp); va_end(args); } /* * Write character to debug file. */ void debugc(char c) { if (opts.debug <= 0 || !debugfp) return; fputc(c, debugfp); } /* * Print error message and write it into log file. */ void error(const char *fmt,...) { va_list args; va_start(args, fmt); fprintf(stderr, "imapfilter: "); vfprintf(stderr, fmt, args); va_end(args); if (logfp) { va_start(args, fmt); fprintf(logfp, "%s: ", log_time()); vfprintf(logfp, fmt, args); fflush(logfp); va_end(args); } } /* * Print error message and exit program. */ void fatal(unsigned int errnum, const char *fmt,...) { va_list args; list *l; session *s; va_start(args, fmt); fprintf(stderr, "imapfilter: "); vfprintf(stderr, fmt, args); va_end(args); if (logfp) { va_start(args, fmt); fprintf(logfp, "%s: ", log_time()); vfprintf(logfp, fmt, args); fflush(logfp); va_end(args); } for (l = sessions; l; l = l->next) { s = l->data; close_connection(s); } close_log(); close_debug(); exit(errnum); } /* * Open temporary debug file and associate a stream with the returned file * descriptor. */ int open_debug(void) { if (!opts.debug) return 0; if (create_file(opts.debug, S_IRUSR | S_IWUSR)) return 1; debugfp = fopen(opts.debug, "w"); if (debugfp == NULL) { error("opening debug file %s: %s\n", opts.debug, strerror(errno)); return 1; } return 0; } /* * Close temporary debug file. */ int close_debug(void) { if (debugfp == NULL) return 0; else return fclose(debugfp); } /* * Open the file for saving of logging information. */ int open_log(void) { if (opts.log == NULL) return 0; debug("log file: '%s'\n", opts.log); if (create_file(opts.log, S_IRUSR | S_IWUSR)) return 1; logfp = fopen(opts.log, "a"); if (logfp == NULL) { error("opening log file %s: %s\n", opts.log, strerror(errno)); return 1; } return 0; } /* * Close the log file. */ int close_log(void) { if (logfp == NULL) return 0; else return fclose(logfp); } /* * Return current local time and date. */ char * log_time(void) { char *ct; time_t t; t = time(NULL); ct = ctime(&t); *(strchr(ct, '\n')) = '\0'; return ct; } imapfilter-2.5.2/src/lua.c0000644000175000017500000001101111757662071015216 0ustar frankiefrankie#include #include #include #include #include #include #include "imapfilter.h" #include "pathnames.h" extern options opts; extern struct sessionhead sessions; static lua_State *lua; /* Lua interpreter state. */ void init_options(void); void interactive_mode(void); /* * Start the Lua interpreter, export IMAP core and system functions, load the * Lua interface functions, load and execute imapfilter's configuration file. */ void start_lua() { lua = luaL_newstate(); luaL_openlibs(lua); luaopen_ifcore(lua); luaopen_ifsys(lua); luaopen_ifre(lua); lua_settop(lua, 0); init_options(); if (luaL_loadfile(lua, PATHNAME_COMMON) || lua_pcall(lua, 0, LUA_MULTRET, 0)) fatal(ERROR_CONFIG, "%s\n", lua_tostring(lua, -1)); if (luaL_loadfile(lua, PATHNAME_SET) || lua_pcall(lua, 0, LUA_MULTRET, 0)) fatal(ERROR_CONFIG, "%s\n", lua_tostring(lua, -1)); if (luaL_loadfile(lua, PATHNAME_REGEX) || lua_pcall(lua, 0, LUA_MULTRET, 0)) fatal(ERROR_CONFIG, "%s\n", lua_tostring(lua, -1)); if (luaL_loadfile(lua, PATHNAME_ACCOUNT) || lua_pcall(lua, 0, LUA_MULTRET, 0)) fatal(ERROR_CONFIG, "%s\n", lua_tostring(lua, -1)); if (luaL_loadfile(lua, PATHNAME_MAILBOX) || lua_pcall(lua, 0, LUA_MULTRET, 0)) fatal(ERROR_CONFIG, "%s\n", lua_tostring(lua, -1)); if (luaL_loadfile(lua, PATHNAME_MESSAGE) || lua_pcall(lua, 0, LUA_MULTRET, 0)) fatal(ERROR_CONFIG, "%s\n", lua_tostring(lua, -1)); if (luaL_loadfile(lua, PATHNAME_OPTIONS) || lua_pcall(lua, 0, LUA_MULTRET, 0)) fatal(ERROR_CONFIG, "%s\n", lua_tostring(lua, -1)); if (luaL_loadfile(lua, PATHNAME_AUXILIARY) || lua_pcall(lua, 0, LUA_MULTRET, 0)) fatal(ERROR_CONFIG, "%s\n", lua_tostring(lua, -1)); if (opts.oneline != NULL) { if (luaL_loadbuffer(lua, opts.oneline, strlen(opts.oneline), "=") || lua_pcall(lua, 0, LUA_MULTRET, 0)) fatal(ERROR_CONFIG, "%s\n", lua_tostring(lua, -1)); } else { if (luaL_loadfile(lua, opts.config) || lua_pcall(lua, 0, LUA_MULTRET, 0)) fatal(ERROR_CONFIG, "%s\n", lua_tostring(lua, -1)); } if (opts.interactive) interactive_mode(); } /* * Stop the Lua interpreter. */ void stop_lua(void) { lua_close(lua); } /* * Set default values to program's options. */ void init_options(void) { lua_newtable(lua); set_table_boolean("certificates", 1); set_table_boolean("crammd5", 1); set_table_boolean("create", 0); set_table_boolean("expunge", 1); set_table_number("keepalive", 29); set_table_boolean("namespace", 1); set_table_string("recover", "all"); set_table_boolean("starttls", 1); set_table_boolean("subscribe", 0); set_table_number("timeout", 60); lua_setglobal(lua, "options"); } /* * Interactive mode. */ void interactive_mode(void) { char buf[LINE_MAX]; for (;;) { printf("> "); fflush(stdout); if (fgets(buf, sizeof(buf), stdin) == NULL) { printf("\n"); break; } if (luaL_loadbuffer(lua, buf, strlen(buf), "=") || lua_pcall(lua, 0, LUA_MULTRET, 0)) { error("%s\n", lua_tostring(lua, -1)); lua_pop(lua, 1); } } } /* * Get from the configuration file the value of a boolean option variable. */ int get_option_boolean(const char *opt) { int b; lua_getglobal(lua, "options"); lua_getfield(lua, -1, opt); b = lua_toboolean(lua, -1); lua_pop(lua, 2); return b; } /* * Get from the configuration file the value of a number option variable. */ lua_Number get_option_number(const char *opt) { lua_Number n; lua_getglobal(lua, "options"); lua_getfield(lua, -1, opt); n = lua_tonumber(lua, -1); lua_pop(lua, 2); return n; } /* * Get from the configuration file the value of a string option variable. */ const char * get_option_string(const char *opt) { const char *s; lua_getglobal(lua, "options"); lua_getfield(lua, -1, opt); s = lua_tostring(lua, -1); lua_pop(lua, 2); if (!s) return ""; return s; } /* * Set a table's element value to the specified boolean. */ int set_table_boolean(const char *key, int value) { lua_pushstring(lua, key); lua_pushboolean(lua, value); lua_settable(lua, -3); return 0; } /* * Set a table's element value to the specified number. */ int set_table_number(const char *key, lua_Number value) { lua_pushstring(lua, key); lua_pushnumber(lua, value); lua_settable(lua, -3); return 0; } /* * Set a table's element value to the specified string. */ int set_table_string(const char *key, const char *value) { lua_pushstring(lua, key); lua_pushstring(lua, value); lua_settable(lua, -3); return 0; } imapfilter-2.5.2/src/account.lua0000644000175000017500000001620111757662071016436 0ustar frankiefrankie-- The Account class represents an IMAP account. Account = {} IMAP = Account imap = Account Account._mt = {} setmetatable(Account, Account._mt) _imap = {} setmetatable(_imap, { __mode = "v" }) Account._mt.__call = function (self, arg) _check_required(arg.server, 'string') _check_required(arg.username, 'string') _check_optional(arg.password, 'string') _check_optional(arg.port, 'number') _check_optional(arg.ssl, 'string') local object = {} object._type = 'account' object._account = {} object._account.server = arg.server object._account.username = arg.username object._account.password = arg.password object._account.port = tostring(arg.port or arg.ssl and 993 or 143) object._account.ssl = arg.ssl or '' object._account.session = nil object._account.selected = nil object._string = arg.username .. '@' .. arg.server for key, value in pairs(Account) do if type(value) == 'function' then object[key] = value end end object._mt = {} object._mt.__index = object._attach_mailbox object._mt.__gc = object._logout_user setmetatable(object, object._mt) table.insert(_imap, object) object._login_user(object) return object end function Account._check_connection(self) if not self._account.session then if not _daemon then error('not connected to ' .. self._string, 0) else return false end end return true end function Account._check_result(self, request, result) if result == nil then self._account.session = nil self._account.selected = nil if not _daemon then error(request .. ' request to ' .. self._string .. ' failed', 0) end end end function Account._login_user(self) if self._account.password == nil then self._account.password = get_password('Enter password for ' .. self._string .. ': ') end if self._account.session then return true end local r, s = ifcore.login(self._account.server, self._account.port, self._account.ssl, self._account.username, self._account.password) self._check_result(self, 'login', r) if r == false then error('authentication to ' .. self._string .. ' failed.', 0) end if not r then return false end self._account.session = s self._account.selected = nil return true end function Account._logout_user(self) if not self._check_connection(self) then return end local r = ifcore.logout(self._account.session) self._check_result(self, 'logout', r) if r == false then return false end self._account.session = nil self._account.selected = nil return true end function Account._attach_mailbox(self, mailbox) self[mailbox] = Mailbox(self, mailbox) return self[mailbox] end function Account._detach_mailbox(self, mailbox) self[mailbox] = nil end function Account.list_all(self, folder, mbox) _check_optional(folder, 'string') _check_optional(mbox, 'string') if folder == nil then folder = '' else if options.namespace == true then if folder == '/' then folder = '' end if folder ~= '' then folder = folder .. '/' end end end if mbox == nil then mbox = '%' end if not self._check_connection(self) then return end local r, mailboxes, folders = ifcore.list(self._account.session, '', folder .. mbox) self._check_result(self, 'list', r) if r == false then return false end local m = {} for s in string.gmatch(mailboxes, '%C+') do table.insert(m, s) end local f = {} for s in string.gmatch(folders, '%C+') do if s ~= folder and s ~= folder .. '/' then table.insert(f, s) end end return m, f end function Account.list_subscribed(self, folder, mbox) _check_optional(folder, 'string') _check_optional(mbox, 'string') if folder == nil then folder = '' else if options.namespace == true then if folder == '/' then folder = '' end if folder ~= '' then folder = folder .. '/' end end end if mbox == nil then mbox = '*' end if not self._check_connection(self) then return end local r, mailboxes, folders = ifcore.lsub(self._account.session, '', folder .. mbox) self._check_result(self, 'lsub', r) if r == false then return false end local m = {} for s in string.gmatch(mailboxes, '%C+') do table.insert(m, s) end local f = {} for s in string.gmatch(folders, '%C+') do if s ~= folder and s ~= folder .. '/' then table.insert(f, s) end end return m, f end function Account.create_mailbox(self, name) _check_required(name, 'string') if not self._check_connection(self) then return end local r = ifcore.create(self._account.session, name) self._check_result(self, 'create', r) if r == false then return false end if options.info == true then print('Created mailbox ' .. self._string .. '/' .. name .. '.') end return r end function Account.delete_mailbox(self, name) _check_required(name, 'string') if not self._check_connection(self) then return end local r = ifcore.delete(self._account.session, name) self._check_result(self, 'delete', r) if r == false then return false end if options.info == true then print('Deleted mailbox ' .. self._string .. '/' .. name .. '.') end return r end function Account.rename_mailbox(self, oldname, newname) _check_required(oldname, 'string') _check_required(newname, 'string') if not self._check_connection(self) then return end local r = ifcore.rename(self._account.session, oldname, newname) self._check_result(self, 'rename', r) if r == false then return false end if options.info == true then print('Renamed mailbox ' .. self._string .. '/' .. oldname .. ' to ' .. self._string .. '/' .. newname .. '.') end return r end function Account.subscribe_mailbox(self, name) _check_required(name, 'string') if not self._check_connection(self) then return end local r = ifcore.subscribe(self._account.session, name) self._check_result(self, 'subscribe', r) if r == false then return false end if options.info == true then print('Subscribed mailbox ' .. self._string .. '/' .. name .. '.') end return r end function Account.unsubscribe_mailbox(self, name) _check_required(name, 'string') if not self._check_connection(self) then return end local r = ifcore.unsubscribe(self._account.session, name) self._check_result(self, 'unsubscribe', r) if r == false then return false end if options.info == true then print('Unsubscribed mailbox ' .. self._string .. '/' .. name .. '.') end return r end Account.login = Account._login_user Account.logout = Account._logout_user Account._mt.__index = function () end Account._mt.__newindex = function () end imapfilter-2.5.2/src/message.lua0000644000175000017500000000747611757662071016444 0ustar frankiefrankie-- The Message class that represents messages inside a mailbox. Message = {} Message._mt = {} setmetatable(Message, Message._mt) Message._mt.__call = function (self, account, mailbox, uid) local object = {} object._type = 'message' object._account = account object._mailbox = mailbox object._uid = uid object._string = account._account.username .. '@' .. account._account.server .. '/' .. mailbox._mailbox .. '[' .. uid .. ']' object._structure = nil object._header = nil object._body = nil object._fields = {} object._parts = {} object._size = nil object._date = nil for key, value in pairs(Message) do if type(value) == 'function' then object[key] = value end end object._mt = {} setmetatable(object, object._mt) return object end function Message.fetch_structure(self) local r = self._mailbox._fetch_structure(self._mailbox, { self._uid }) if not r or not r[self._uid] then return end if options.info == true then print('Fetched the structure of ' .. self._string .. '.') end return r[self._uid] end function Message.fetch_header(self) local r = self._mailbox._fetch_header(self._mailbox, { self._uid }) if not r or not r[self._uid] then return end if options.info == true then print('Fetched the header of ' .. self._string .. '.') end return r[self._uid] end function Message.fetch_body(self) local r = self._mailbox._fetch_body(self._mailbox, { self._uid }) if not r or not r[self._uid] then return end if options.info == true then print('Fetched the body of ' .. self._string .. '.') end return r[self._uid] end function Message.fetch_message(self) local r = self._mailbox._fetch_message(self._mailbox, { self._uid }) if not r or not r[self._uid] then return end if options.info == true then print('Fetched message ' .. self._string .. '.') end return r[self._uid] end function Message.fetch_field(self, field) local r = self._mailbox._fetch_fields(self._mailbox, { field }, { self._uid }) if not r or not r[self._uid] then return end if options.info == true then print('Fetched field "' .. field .. '" of ' .. self._string .. '.') end return r[self._uid] end function Message.fetch_fields(self, fields) local r = self._mailbox._fetch_fields(self._mailbox, fields, { self._uid }) if not r or not r[self._uid] then return end if options.info == true then print('Fetched some of the fields of ' .. self._string .. '.') end return r[self._uid] end function Message.fetch_part(self, part) local r = self._mailbox._fetch_parts(self._mailbox, { part }, self._uid) if not r or not r[part] then return end if options.info == true then print('Fetched part "' .. part .. '" of ' .. self._string .. '.') end return r[part] end function Message.fetch_size(self) local r = self._mailbox._fetch_size(self._mailbox, { self._uid }) if not r or not r[self._uid] then return end if options.info == true then print('Fetched the size of ' .. self._string .. '.') end return r[self._uid] end function Message.fetch_date(self) local r = self._mailbox._fetch_date(self._mailbox, { self._uid }) if not r or not r[self._uid] then return end if options.info == true then print('Fetched the date of ' .. self._string .. '.') end return r[self._uid] end function Message.fetch_flags(self) local r = self._mailbox._fetch_flags(self._mailbox, { self._uid }) if not r or not r[self._uid] then return end if options.info == true then print('Fetched the flags of ' .. self._string .. '.') end return r[self._uid] end Message._mt.__index = function () end Message._mt.__newindex = function () end imapfilter-2.5.2/src/signal.c0000644000175000017500000000115711536341114015707 0ustar frankiefrankie#include #include "imapfilter.h" void signal_handler(int sig); /* * Catch signals that cause program's termination. */ void catch_signals(void) { signal(SIGINT, signal_handler); signal(SIGQUIT, signal_handler); signal(SIGTERM, signal_handler); } /* * Release signals and reset them to default action. */ void release_signals(void) { signal(SIGINT, SIG_DFL); signal(SIGQUIT, SIG_DFL); signal(SIGTERM, SIG_DFL); } /* * Signal handler for signals that cause termination of program. */ void signal_handler(int sig) { release_signals(); fatal(ERROR_SIGNAL, "killed by signal %d\n", sig); } imapfilter-2.5.2/src/response.c0000644000175000017500000004560111757662071016307 0ustar frankiefrankie#include #include #include #include #include #include #include "imapfilter.h" #include "session.h" #include "buffer.h" #include "regexp.h" extern options opts; buffer ibuf; /* Input buffer. */ enum { /* Server data responses to be parsed; * regular expressions index. */ RESPONSE_TAGGED, RESPONSE_CAPABILITY, RESPONSE_AUTHENTICATE, RESPONSE_NAMESPACE, RESPONSE_STATUS, RESPONSE_STATUS_MESSAGES, RESPONSE_STATUS_RECENT, RESPONSE_STATUS_UNSEEN, RESPONSE_STATUS_UIDNEXT, RESPONSE_EXAMINE_EXISTS, RESPONSE_EXAMINE_RECENT, RESPONSE_LIST, RESPONSE_SEARCH, RESPONSE_FETCH, RESPONSE_FETCH_FLAGS, RESPONSE_FETCH_DATE, RESPONSE_FETCH_SIZE, RESPONSE_FETCH_STRUCTURE, RESPONSE_FETCH_BODY, RESPONSE_IDLE, }; regexp responses[] = { /* Server data responses to be parsed; * regular expressions patterns. */ { "([[:xdigit:]]{4,4}) (OK|NO|BAD) [^[:cntrl:]]*\r\n", NULL, 0, NULL }, { "\\* CAPABILITY ([[:print:]]*)\r\n", NULL, 0, NULL }, { "\\+ ([[:graph:]]*)\r\n", NULL, 0, NULL }, { "\\* NAMESPACE (NIL|\\(\\(\"([[:graph:]]*)\" \"([[:print:]])\"\\)" "[[:print:]]*\\)) (NIL|\\([[:print:]]*\\)) (NIL|\\([[:print:]]*\\))" "\r\n", NULL, 0, NULL }, { "\\* STATUS [[:print:]]* \\(([[:alnum:] ]*)\\) *\r\n", NULL, 0, NULL }, { "MESSAGES ([[:digit:]]+)", NULL, 0, NULL }, { "RECENT ([[:digit:]]+)", NULL, 0, NULL }, { "UNSEEN ([[:digit:]]+)", NULL, 0, NULL }, { "UIDNEXT ([[:digit:]]+)", NULL, 0, NULL }, { "\\* ([[:digit:]]+) EXISTS\r\n", NULL, 0, NULL }, { "\\* ([[:digit:]]+) RECENT\r\n", NULL, 0, NULL }, { "\\* (LIST|LSUB) \\(([[:print:]]*)\\) (\"[[:print:]]\"|NIL) " "(\"([[:print:]]+)\"|([[:print:]]+)|\\{([[:digit:]]+)\\}\r\n" "([[:print:]]*))\r\n", NULL, 0, NULL }, { "\\* SEARCH ?([[:digit:] ]*)\r\n", NULL, 0, NULL }, { "\\* [[:digit:]]+ FETCH \\(([[:print:]]*)\\)\r\n", NULL, 0, NULL }, { "FLAGS \\(([[:print:]]*)\\)", NULL, 0, NULL }, { "INTERNALDATE \"([[:print:]]*)\"", NULL, 0, NULL }, { "RFC822.SIZE ([[:digit:]]+)", NULL, 0, NULL }, { "BODYSTRUCTURE (\\([[:print:]]+\\))", NULL, 0, NULL }, { "\\* [[:digit:]]+ FETCH \\([[:print:]]*BODY\\[[[:print:]]*\\] " "(\\{([[:digit:]]+)\\}\r\n|\"([[:print:]]*)\")", NULL, 0, NULL }, { "\\* [[:digit:]]+ (RECENT|EXISTS)\r\n", NULL, 0, NULL }, { NULL, NULL, 0, NULL } }; int receive_response(session *ssn, char *buf, long timeout, int timeoutfail); int check_tag(char *buf, session *ssn, int tag); int check_bye(char *buf); int check_continuation(char *buf); int check_trycreate(char *buf); /* * Read data the server sent. */ int receive_response(session *ssn, char *buf, long timeout, int timeoutfail) { ssize_t n; if ((n = socket_read(ssn, buf, INPUT_BUF, timeout ? timeout : (long)(get_option_number("timeout")), timeoutfail)) == -1) return -1; if (opts.debug) { int i; debug("getting response (%d):\n\n", ssn->socket); for (i = 0; i < n; i++) debugc(buf[i]); debug("\n"); } return n; } /* * Search for tagged response in the data that the server sent. */ int check_tag(char *buf, session *ssn, int tag) { int r; char t[4 + 1]; regexp *re; r = STATUS_NONE; snprintf(t, sizeof(t), "%04X", tag); re = &responses[RESPONSE_TAGGED]; if (!regexec(re->preg, buf, re->nmatch, re->pmatch, 0)) { if (!strncasecmp(buf + re->pmatch[1].rm_so, t, strlen(t))) { if (!strncasecmp(buf + re->pmatch[2].rm_so, "OK", strlen("OK"))) r = STATUS_OK; else if (!strncasecmp(buf + re->pmatch[2].rm_so, "NO", strlen("NO"))) r = STATUS_NO; else if (!strncasecmp(buf + re->pmatch[2].rm_so, "BAD", strlen("BAD"))) r = STATUS_BAD; } } if (r != STATUS_NONE) verbose("S (%d): %s", ssn->socket, buf + re->pmatch[0].rm_so); if (r == STATUS_NO || r == STATUS_BAD) error("IMAP (%d): %s", ssn->socket, buf + re->pmatch[0].rm_so); return r; } /* * Check if server sent a BYE response (connection is closed immediately). */ int check_bye(char *buf) { if (xstrcasestr(buf, "* BYE") && !xstrcasestr(buf, " LOGOUT ")) return 1; else return 0; } /* * Check if server sent a PREAUTH response (connection already authenticated * by external means). */ int check_preauth(char *buf) { if (xstrcasestr(ibuf.data, "* PREAUTH")) return 1; else return 0; } /* * Check if the server sent a continuation request. */ int check_continuation(char *buf) { if ((buf[0] == '+' && buf[1] == ' ') || xstrcasestr(buf, "\r\n+ ")) return 1; else return 0; } /* * Check if the server sent a TRYCREATE response. */ int check_trycreate(char *buf) { if (xstrcasestr(buf, "[TRYCREATE]")) return 1; else return 0; } /* * Get server data and make sure there is a tagged response inside them. */ int response_generic(session *ssn, int tag) { int r; ssize_t n; if (tag == -1) return -1; buffer_reset(&ibuf); do { buffer_check(&ibuf, ibuf.len + INPUT_BUF); if ((n = receive_response(ssn, ibuf.data + ibuf.len, 0, 1)) == -1) return -1; ibuf.len += n; if (check_bye(ibuf.data)) return STATUS_BYE; } while ((r = check_tag(ibuf.data, ssn, tag)) == STATUS_NONE); if (r == STATUS_NO && (check_trycreate(ibuf.data) || get_option_boolean("create"))) return STATUS_TRYCREATE; return r; } /* * Get server data and make sure there is a continuation response inside them. */ int response_continuation(session *ssn, int tag) { int r; ssize_t n; buffer_reset(&ibuf); do { buffer_check(&ibuf, ibuf.len + INPUT_BUF); if ((n = receive_response(ssn, ibuf.data + ibuf.len, 0, 1)) == -1) return -1; ibuf.len += n; if (check_bye(ibuf.data)) return STATUS_BYE; } while ((r = check_tag(ibuf.data, ssn, tag)) == STATUS_NONE && !check_continuation(ibuf.data)); if (r == STATUS_NO && (check_trycreate(ibuf.data) || get_option_boolean("create"))) return STATUS_TRYCREATE; if (r == STATUS_NONE) return STATUS_CONTINUE; return r; } /* * Process the greeting that server sends during connection. */ int response_greeting(session *ssn) { buffer_reset(&ibuf); if (receive_response(ssn, ibuf.data, 0, 1) == -1) return -1; verbose("S (%d): %s", ssn->socket, ibuf.data); if (check_bye(ibuf.data)) return STATUS_BYE; if (check_preauth(ibuf.data)) return STATUS_PREAUTH; return STATUS_NONE; } /* * Process the data that server sent due to IMAP CAPABILITY client request. */ int response_capability(session *ssn, int tag) { int r; char *s; regexp *re; r = response_generic(ssn, tag); if (r == -1 || r == STATUS_BYE) return r; ssn->protocol = PROTOCOL_NONE; re = &responses[RESPONSE_CAPABILITY]; if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0)) { s = xstrndup(ibuf.data + re->pmatch[1].rm_so, re->pmatch[1].rm_eo - re->pmatch[1].rm_so); if (xstrcasestr(s, "IMAP4rev1")) ssn->protocol = PROTOCOL_IMAP4REV1; else if (xstrcasestr(s, "IMAP4")) ssn->protocol = PROTOCOL_IMAP4; else { error("server supports neither the IMAP4rev1 nor the " "IMAP4 protocol\n"); return -1; } ssn->capabilities = CAPABILITY_NONE; if (xstrcasestr(s, "NAMESPACE")) ssn->capabilities |= CAPABILITY_NAMESPACE; if (xstrcasestr(s, "AUTH=CRAM-MD5")) ssn->capabilities |= CAPABILITY_CRAMMD5; if (xstrcasestr(s, "STARTTLS")) ssn->capabilities |= CAPABILITY_STARTTLS; if (xstrcasestr(s, "CHILDREN")) ssn->capabilities |= CAPABILITY_CHILDREN; if (xstrcasestr(s, "IDLE")) ssn->capabilities |= CAPABILITY_IDLE; xfree(s); } return r; } /* * Process the data that server sent due to IMAP AUTHENTICATE client request. */ int response_authenticate(session *ssn, int tag, unsigned char **cont) { int r; regexp *re; re = &responses[RESPONSE_AUTHENTICATE]; if ((r = response_continuation(ssn, tag)) == STATUS_CONTINUE && !regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0)) *cont = (unsigned char *)xstrndup(ibuf.data + re->pmatch[1].rm_so, re->pmatch[1].rm_eo - re->pmatch[1].rm_so); return r; } /* * Process the data that server sent due to IMAP NAMESPACE client request. */ int response_namespace(session *ssn, int tag) { int r, n; regexp *re; r = response_generic(ssn, tag); if (r == -1 || r == STATUS_BYE) return r; ssn->ns.prefix = NULL; ssn->ns.delim = '\0'; re = &responses[RESPONSE_NAMESPACE]; if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0)) { n = re->pmatch[2].rm_eo - re->pmatch[2].rm_so; if (n > 0) ssn->ns.prefix = xstrndup(ibuf.data + re->pmatch[2].rm_so, n); ssn->ns.delim = *(ibuf.data + re->pmatch[3].rm_so); } debug("namespace (%d): '%s' '%c'\n", ssn->socket, (ssn->ns.prefix ? ssn->ns.prefix : ""), ssn->ns.delim); return r; } /* * Process the data that server sent due to IMAP STATUS client request. */ int response_status(session *ssn, int tag, unsigned int *exist, unsigned int *recent, unsigned int *unseen, unsigned int *uidnext) { int r; char *s; regexp *re; r = response_generic(ssn, tag); if (r == -1 || r == STATUS_BYE) return r; re = &responses[RESPONSE_STATUS]; if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0)) { s = xstrndup(ibuf.data + re->pmatch[1].rm_so, re->pmatch[1].rm_eo - re->pmatch[1].rm_so); re = &responses[RESPONSE_STATUS_MESSAGES]; if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0)) *exist = strtol(s + re->pmatch[1].rm_so, NULL, 10); re = &responses[RESPONSE_STATUS_RECENT]; if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0)) *recent = strtol(s + re->pmatch[1].rm_so, NULL, 10); re = &responses[RESPONSE_STATUS_UNSEEN]; if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0)) *unseen = strtol(s + re->pmatch[1].rm_so, NULL, 10); re = &responses[RESPONSE_STATUS_UIDNEXT]; if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0)) *uidnext = strtol(s + re->pmatch[1].rm_so, NULL, 10); xfree(s); } return r; } /* * Process the data that server sent due to IMAP EXAMINE client request. */ int response_examine(session *ssn, int tag, unsigned int *exist, unsigned int *recent) { int r; regexp *re; r = response_generic(ssn, tag); if (r == -1 || r == STATUS_BYE) return r; re = &responses[RESPONSE_EXAMINE_EXISTS]; if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0)) *exist = strtol(ibuf.data + re->pmatch[1].rm_so, NULL, 10); re = &responses[RESPONSE_EXAMINE_RECENT]; if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0)) *recent = strtol(ibuf.data + re->pmatch[1].rm_so, NULL, 10); return r; } /* * Process the data that server sent due to IMAP SELECT client request. */ int response_select(session *ssn, int tag) { int r; r = response_generic(ssn, tag); if (r == -1 || r == STATUS_BYE) return r; if (xstrcasestr(ibuf.data, "[READ-ONLY]")) return STATUS_READONLY; return r; } /* * Process the data that server sent due to IMAP LIST or IMAP LSUB client * request. */ int response_list(session *ssn, int tag, char **mboxs, char **folders) { int r, n; char *b, *a, *s, *m, *f; const char *v; regexp *re; r = response_generic(ssn, tag); if (r == -1 || r == STATUS_BYE) return r; m = *mboxs = (char *)xmalloc((ibuf.len + 1) * sizeof(char)); f = *folders = (char *)xmalloc((ibuf.len + 1) * sizeof(char)); *m = *f = '\0'; re = &responses[RESPONSE_LIST]; b = ibuf.data; while (!regexec(re->preg, b, re->nmatch, re->pmatch, 0)) { a = xstrndup(b + re->pmatch[2].rm_so, re->pmatch[2].rm_eo - re->pmatch[2].rm_so); if (re->pmatch[5].rm_so != -1 && re->pmatch[5].rm_so != -1) s = xstrndup(b + re->pmatch[5].rm_so, re->pmatch[5].rm_eo - re->pmatch[5].rm_so); else if (re->pmatch[6].rm_so != -1 && re->pmatch[6].rm_so != -1) s = xstrndup(b + re->pmatch[6].rm_so, re->pmatch[6].rm_eo - re->pmatch[6].rm_so); else s = xstrndup(b + re->pmatch[8].rm_so, strtoul(b + re->pmatch[7].rm_so, NULL, 10)); v = reverse_namespace(s, ssn->ns.prefix, ssn->ns.delim); n = strlen(v); if (!xstrcasestr(a, "\\NoSelect")) { xstrncpy(m, v, ibuf.len - (m - *mboxs)); m += n; xstrncpy(m, "\n", ibuf.len - (m - *mboxs)); m += strlen("\n"); } if (!xstrcasestr(a, "\\NoInferiors") && (!(ssn->capabilities & CAPABILITY_CHILDREN) || ((ssn->capabilities & CAPABILITY_CHILDREN) && (xstrcasestr(a, "\\HasChildren")) && !xstrcasestr(a, "\\HasNoChildren")))) { xstrncpy(f, v, ibuf.len - (f - *folders)); f += n; xstrncpy(f, "\n", ibuf.len - (f - *folders)); f += strlen("\n"); } b += re->pmatch[0].rm_eo; xfree(a); xfree(s); } return r; } /* * Process the data that server sent due to IMAP SEARCH client request. */ int response_search(session *ssn, int tag, char **mesgs) { int r; unsigned int min; regexp *re; char *b, *m; r = response_generic(ssn, tag); if (r == -1 || r == STATUS_BYE) return r; re = &responses[RESPONSE_SEARCH]; b = ibuf.data; m = NULL; while (!regexec(re->preg, b, re->nmatch, re->pmatch, 0)) { if (!*mesgs) { m = *mesgs = (char *)xmalloc((ibuf.len + 1) * sizeof(char)); *m = '\0'; } min = (unsigned int)(re->pmatch[1].rm_eo - re->pmatch[1].rm_so) < ibuf.len ? (unsigned int)(re->pmatch[1].rm_eo - re->pmatch[1].rm_so) : ibuf.len; xstrncpy(m, b + re->pmatch[1].rm_so, min); m += min; xstrncpy(m++, " ", ibuf.len - min); b += re->pmatch[0].rm_eo; } return r; } /* * Process the data that server sent due to IMAP FETCH FAST client request. */ int response_fetchfast(session *ssn, int tag, char **flags, char **date, char **size) { int r; char *s; regexp *re; r = response_generic(ssn, tag); if (r == -1 || r == STATUS_BYE) return r; re = &responses[RESPONSE_FETCH]; if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0)) { s = xstrndup(ibuf.data + re->pmatch[1].rm_so, re->pmatch[1].rm_eo - re->pmatch[1].rm_so); re = &responses[RESPONSE_FETCH_FLAGS]; if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0)) *flags = xstrndup(s + re->pmatch[1].rm_so, re->pmatch[1].rm_eo - re->pmatch[1].rm_so); re = &responses[RESPONSE_FETCH_DATE]; if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0)) *date = xstrndup(s + re->pmatch[1].rm_so, re->pmatch[1].rm_eo - re->pmatch[1].rm_so); re = &responses[RESPONSE_FETCH_SIZE]; if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0)) *size = xstrndup(s + re->pmatch[1].rm_so, re->pmatch[1].rm_eo - re->pmatch[1].rm_so); xfree(s); } return r; } /* * Process the data that server sent due to IMAP FETCH FLAGS client request. */ int response_fetchflags(session *ssn, int tag, char **flags) { int r; char *s; regexp *re; r = response_generic(ssn, tag); if (r == -1 || r == STATUS_BYE) return r; re = &responses[RESPONSE_FETCH]; if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0)) { s = xstrndup(ibuf.data + re->pmatch[1].rm_so, re->pmatch[1].rm_eo - re->pmatch[1].rm_so); re = &responses[RESPONSE_FETCH_FLAGS]; if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0)) *flags = xstrndup(s + re->pmatch[1].rm_so, re->pmatch[1].rm_eo - re->pmatch[1].rm_so); xfree(s); } return r; } /* * Process the data that server sent due to IMAP FETCH INTERNALDATE client * request. */ int response_fetchdate(session *ssn, int tag, char **date) { int r; char *s; regexp *re; r = response_generic(ssn, tag); if (r == -1 || r == STATUS_BYE) return r; re = &responses[RESPONSE_FETCH]; if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0)) { s = xstrndup(ibuf.data + re->pmatch[1].rm_so, re->pmatch[1].rm_eo - re->pmatch[1].rm_so); re = &responses[RESPONSE_FETCH_DATE]; if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0)) *date = xstrndup(s + re->pmatch[1].rm_so, re->pmatch[1].rm_eo - re->pmatch[1].rm_so); xfree(s); } return r; } /* * Process the data that server sent due to IMAP FETCH RFC822.SIZE client * request. */ int response_fetchsize(session *ssn, int tag, char **size) { int r; char *s; regexp *re; r = response_generic(ssn, tag); if (r == -1 || r == STATUS_BYE) return r; re = &responses[RESPONSE_FETCH]; if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0)) { s = xstrndup(ibuf.data + re->pmatch[1].rm_so, re->pmatch[1].rm_eo - re->pmatch[1].rm_so); re = &responses[RESPONSE_FETCH_SIZE]; if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0)) *size = xstrndup(s + re->pmatch[1].rm_so, re->pmatch[1].rm_eo - re->pmatch[1].rm_so); xfree(s); } return r; } /* * Process the data that server sent due to IMAP FETCH BODYSTRUCTURE client * request. */ int response_fetchstructure(session *ssn, int tag, char **structure) { int r; char *s; regexp *re; r = response_generic(ssn, tag); if (r == -1 || r == STATUS_BYE) return r; re = &responses[RESPONSE_FETCH]; if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0)) { s = xstrndup(ibuf.data + re->pmatch[1].rm_so, re->pmatch[1].rm_eo - re->pmatch[1].rm_so); re = &responses[RESPONSE_FETCH_STRUCTURE]; if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0)) { *structure = xstrndup(s + re->pmatch[1].rm_so, re->pmatch[1].rm_eo - re->pmatch[1].rm_so); } xfree(s); } return r; } /* * Process the data that server sent due to IMAP FETCH BODY[] client request, * ie. FETCH BODY[HEADER], FETCH BODY[TEXT], FETCH BODY[HEADER.FIELDS * ()], FETCH BODY[]. */ int response_fetchbody(session *ssn, int tag, char **body, size_t *len) { int r, match; unsigned int offset; ssize_t n; regexp *re; if (tag == -1) return -1; buffer_reset(&ibuf); match = -1; offset = 0; re = &responses[RESPONSE_FETCH_BODY]; do { buffer_check(&ibuf, ibuf.len + INPUT_BUF); if ((n = receive_response(ssn, ibuf.data + ibuf.len, 0, 1)) == -1) return -1; ibuf.len += n; if (match != 0) { match = regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0); if (match == 0 && re->pmatch[2].rm_so != -1 && re->pmatch[2].rm_eo != -1) { *len = strtoul(ibuf.data + re->pmatch[2].rm_so, NULL, 10); offset = re->pmatch[0].rm_eo + *len; } } if (offset != 0 && ibuf.len >= offset) { if (check_bye(ibuf.data + offset)) return STATUS_BYE; } } while (ibuf.len < offset || (r = check_tag(ibuf.data + offset, ssn, tag)) == STATUS_NONE); if (match == 0) { if (re->pmatch[2].rm_so != -1 && re->pmatch[2].rm_eo != -1) { *body = ibuf.data + re->pmatch[0].rm_eo; } else { *body = ibuf.data + re->pmatch[3].rm_so; *len = re->pmatch[3].rm_eo - re->pmatch[3].rm_so; } } return r; } /* * Process the data that server sent due to IMAP IDLE client request. */ int response_idle(session *ssn, int tag) { regexp *re; if (tag == -1) return -1; re = &responses[RESPONSE_IDLE]; do { buffer_reset(&ibuf); switch (receive_response(ssn, ibuf.data, get_option_number("keepalive") * 60, 0)) { case -1: return -1; break; /* NOTREACHED */ case 0: return STATUS_TIMEOUT; break; /* NOTREACHED */ } verbose("S (%d): %s", ssn->socket, ibuf.data); if (check_bye(ibuf.data)) return STATUS_BYE; } while (regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0)); return STATUS_UNTAGGED; } imapfilter-2.5.2/src/set.lua0000644000175000017500000003526311757662071015606 0ustar frankiefrankie-- A simple implementation of sets. Set = {} Set._mt = {} setmetatable(Set, Set._mt) function Set._new(self, values) local object object = values or {} object._type = 'set' for key, value in pairs(Set) do if type(value) == 'function' then object[key] = value end end object._mt = {} object._mt.__add = object._union object._mt.__mul = object._intersection object._mt.__sub = object._difference setmetatable(object, object._mt) return object end function Set._union(seta, setb) local set = Set() local t = {} for _, v in ipairs(seta) do b, m = table.unpack(v) if not t[b] then t[b] = {} end t[b][m] = true end for _, v in ipairs(setb) do b, m = table.unpack(v) if not t[b] then t[b] = {} end t[b][m] = true end for b in pairs(t) do for m in pairs(t[b]) do table.insert(set, { b, m }) end end return set end function Set._intersection(seta, setb) local set = Set() local ta = {} local tb = {} for _, v in ipairs(seta) do b, m = table.unpack(v) if not ta[b] then ta[b] = {} end ta[b][m] = true end for _, v in ipairs(setb) do b, m = table.unpack(v) if not tb[b] then tb[b] = {} end tb[b][m] = true end for b in pairs(ta) do if tb[b] then for m in pairs(ta[b]) do if tb[b][m] then table.insert(set, { b, m }) end end end end return set end function Set._difference(seta, setb) local set = Set() local t = {} for _, v in ipairs(seta) do b, m = table.unpack(v) if not t[b] then t[b] = {} end t[b][m] = true end for _, v in ipairs(setb) do b, m = table.unpack(v) if t[b] then t[b][m] = nil end end for b in pairs(t) do for m in pairs(t[b]) do table.insert(set, { b, m }) end end return set end function Set.add_flags(self, flags) _check_required(flags, 'table') local r = true for mbox in pairs(_extract_mailboxes(self)) do if not mbox.add_flags(mbox, flags, self) then r = false end end return r end function Set.remove_flags(self, flags) _check_required(flags, 'table') local r = true for mbox in pairs(_extract_mailboxes(self)) do if not mbox.remove_flags(mbox, flags, self) then r = false end end return r end function Set.replace_flags(self, flags) _check_required(flags, 'table') local r = true for mbox in pairs(_extract_mailboxes(self)) do if not mbox.replace_flags(mbox, flags, self) then r = false end end return r end function Set.mark_answered(self) local r = true for mbox in pairs(_extract_mailboxes(self)) do if not mbox.mark_answered(mbox, self) then r = false end end return r end function Set.mark_deleted(self) local r = true for mbox in pairs(_extract_mailboxes(self)) do if not mbox.mark_deleted(mbox, self) then r = false end end return r end function Set.mark_draft(self) local r = true for mbox in pairs(_extract_mailboxes(self)) do if not mbox.mark_draft(mbox, self) then r = false end end return r end function Set.mark_flagged(self) local r = true for mbox in pairs(_extract_mailboxes(self)) do if not mbox.mark_flagged(mbox, self) then r = false end end return r end function Set.mark_seen(self) local r = true for mbox in pairs(_extract_mailboxes(self)) do if not mbox.mark_seen(mbox, self) then r = false end end return r end function Set.unmark_answered(self) local r = true for mbox in pairs(_extract_mailboxes(self)) do if not mbox.unmark_answered(mbox, self) then r = false end end return r end function Set.unmark_deleted(self) local r = true for mbox in pairs(_extract_mailboxes(self)) do if not mbox.unmark_deleted(mbox, self) then r = false end end return r end function Set.unmark_draft(self) local r = true for mbox in pairs(_extract_mailboxes(self)) do if not mbox.unmark_draft(mbox, self) then r = false end end return r end function Set.unmark_flagged(self) local r = true for mbox in pairs(_extract_mailboxes(self)) do if not mbox.unmark_flagged(mbox, self) then r = false end end return r end function Set.unmark_seen(self) local r = true for mbox in pairs(_extract_mailboxes(self)) do if not mbox.unmark_seen(mbox, self) then r = false end end return r end function Set.delete_messages(self) local r = true for mbox in pairs(_extract_mailboxes(self)) do if not mbox.delete_messages(mbox, self) then r = false end end return r end function Set.copy_messages(self, dest) _check_required(dest, 'table') local r = true for mbox in pairs(_extract_mailboxes(self)) do if not mbox.copy_messages(mbox, dest, self) then r = false end end return r end function Set.move_messages(self, dest) _check_required(dest, 'table') local r = true for mbox in pairs(_extract_mailboxes(self)) do if not mbox.move_messages(mbox, dest, self) then r = false end end return r end function Set.select_all(self) local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.select_all(mbox) end return self * set end function Set.send_query(self, criteria) local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.send_query(mbox, criteria) end return self * set end function Set.is_answered(self) local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.is_answered(mbox) end return self * set end function Set.is_deleted(self) local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.is_deleted(mbox) end return self * set end function Set.is_draft(self) local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.is_draft(mbox) end return self * set end function Set.is_flagged(self) local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.is_flagged(mbox) end return self * set end function Set.is_new(self) local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.is_new(mbox) end return self * set end function Set.is_old(self) local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.is_old(mbox) end return self * set end function Set.is_recent(self) local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.is_recent(mbox) end return self * set end function Set.is_seen(self) local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.is_seen(mbox) end return self * set end function Set.is_unanswered(self) local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.is_unanswered(mbox) end return self * set end function Set.is_undeleted(self) local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.is_undeleted(mbox) end return self * set end function Set.is_undraft(self) local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.is_undraft(mbox) end return self * set end function Set.is_unflagged(self) local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.is_unflagged(mbox) end return self * set end function Set.is_unseen(self) local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.is_unseen(mbox) end return self * set end function Set.is_larger(self, size) _check_required(size, 'number') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.is_larger(mbox, size) end return self * set end function Set.is_smaller(self, size) _check_required(size, 'number') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.is_smaller(mbox, size) end return self * set end function Set.arrived_on(self, date) _check_required(date, 'string') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.arrived_on(mbox, date) end return self * set end function Set.arrived_before(self, date) _check_required(date, 'string') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.arrived_before(mbox, date) end return self * set end function Set.arrived_since(self, date) _check_required(date, 'string') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.arrived_since(mbox, date) end return self * set end function Set.sent_on(self, date) _check_required(date, 'string') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.sent_on(mbox, date) end return self * set end function Set.sent_before(self, date) _check_required(date, 'string') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.sent_before(mbox, date) end return self * set end function Set.sent_since(self, date) _check_required(date, 'string') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.sent_since(mbox, date) end return self * set end function Set.is_newer(self, days) _check_required(days, 'number') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.is_newer(mbox, days) end return self * set end function Set.is_older(self, days) _check_required(days, 'number') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.is_older(mbox, days) end return self * set end function Set.has_flag(self, flag) _check_required(flag, 'string') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.has_flag(mbox, flag) end return self * set end function Set.contain_field(self, field, string) _check_required(field, 'string') _check_required(string, 'string') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.contain_field(mbox, field, string) end return self * set end function Set.contain_bcc(self, string) _check_required(string, 'string') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.contain_bcc(mbox, string) end return self * set end function Set.contain_cc(self, string) _check_required(string, 'string') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.contain_cc(mbox, string) end return self * set end function Set.contain_from(self, string) _check_required(string, 'string') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.contain_from(mbox, string) end return self * set end function Set.contain_subject(self, string) _check_required(string, 'string') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.contain_subject(mbox, string) end return self * set end function Set.contain_to(self, string) _check_required(string, 'string') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.contain_to(mbox, string) end return self * set end function Set.contain_header(self, string) _check_required(string, 'string') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.contain_header(mbox, string) end return self * set end function Set.contain_body(self, string) _check_required(string, 'string') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.contain_body(mbox, string) end return self * set end function Set.contain_message(self, string) _check_required(string, 'string') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.contain_message(mbox, string) end return self * set end function Set.match_bcc(self, pattern) _check_required(pattern, 'string') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.match_bcc(mbox, pattern, self) end return self * set end function Set.match_cc(self, pattern) _check_required(pattern, 'string') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.match_cc(mbox, pattern, self) end return self * set end function Set.match_from(self, pattern) _check_required(pattern, 'string') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.match_from(mbox, pattern, self) end return self * set end function Set.match_subject(self, pattern) _check_required(pattern, 'string') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.match_subject(mbox, pattern, self) end return self * set end function Set.match_to(self, pattern) _check_required(pattern, 'string') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.match_to(mbox, pattern, self) end return self * set end function Set.match_field(self, field, pattern) _check_required(field, 'string') _check_required(pattern, 'string') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.match_field(mbox, field, pattern, self) end return self * set end function Set.match_header(self, pattern) _check_required(pattern, 'string') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.match_header(mbox, pattern, self) end return self * set end function Set.match_body(self, pattern) _check_required(pattern, 'string') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.match_body(mbox, pattern, self) end return self * set end function Set.match_message(self, pattern) _check_required(pattern, 'string') local set = Set() for mbox in pairs(_extract_mailboxes(self)) do set = set + mbox.match_message(mbox, pattern, self) end return self * set end Set._mt.__call = Set._new imapfilter-2.5.2/src/session.c0000644000175000017500000000177611757662071016141 0ustar frankiefrankie#include #include #include "imapfilter.h" #include "session.h" #include "list.h" extern list *sessions; void session_init(session *ssn); /* * Allocate memory for a new session and add it to the sessions linked list. */ session * session_new(void) { session *s = (session *)xmalloc(sizeof(session)); session_init(s); sessions = list_append(sessions, s); return s; } /* * Set session variables to safe values. */ void session_init(session *ssn) { ssn->server = NULL; ssn->port = NULL; ssn->sslproto = NULL; ssn->username = NULL; ssn->password = NULL; ssn->socket = -1; ssn->sslconn = NULL; ssn->protocol = PROTOCOL_NONE; ssn->capabilities = CAPABILITY_NONE; ssn->ns.prefix = NULL; ssn->ns.delim = '\0'; ssn->selected = NULL; } /* * Remove session from sessions linked list and free allocated memory. */ void session_destroy(session *ssn) { if (!ssn) return; sessions = list_remove(sessions, ssn); if (ssn->ns.prefix) xfree(ssn->ns.prefix); xfree(ssn); } imapfilter-2.5.2/src/session.h0000644000175000017500000000154211757662071016135 0ustar frankiefrankie#ifndef SESSION_H #define SESSION_H #include /* IMAP session. */ typedef struct session { const char *server; /* Server hostname. */ const char *port; /* Server port. */ const char *sslproto; /* SSL protocol. */ const char *username; /* User name. */ const char *password; /* User password. */ int socket; /* Socket. */ SSL *sslconn; /* SSL connection. */ unsigned int protocol; /* IMAP protocol. Currently IMAP4rev1 and * IMAP4 are supported. */ unsigned int capabilities; /* Capabilities of the mail server. */ struct { /* Namespace of the mail server's mailboxes. */ char *prefix; /* Namespace prefix. */ char delim; /* Namespace delimiter. */ } ns; const char *selected; /* Selected mailbox. */ } session; /* session.c */ session *session_new(void); void session_destroy(session *ssn); #endif /* SESSION_H */ imapfilter-2.5.2/src/mailbox.lua0000644000175000017500000007504411757662071016447 0ustar frankiefrankie-- The Mailbox class represents a mailbox that resides in an IMAP account. Mailbox = {} Mailbox._mt = {} setmetatable(Mailbox, Mailbox._mt) Mailbox._mt.__call = function (self, account, mailbox) local object = {} object._type = 'mailbox' object._account = account object._mailbox = mailbox object._string = account._account.username .. '@' .. account._account.server .. '/' .. mailbox for key, value in pairs(Mailbox) do if type(value) == 'function' then object[key] = value end end object._mt = {} object._mt.__index = object._attach_message setmetatable(object, object._mt) return object end function Mailbox._check_connection(self) if not self._account._account.session then if not _daemon then error('not connected to ' .. self._account._string, 0) else return false end end return true end function Mailbox._check_result(self, request, result) if result == nil then self._account._account.session = nil self._account._account.selected = nil if not _daemon then error(request .. ' request to ' .. self._account._string .. ' failed', 0) end end end function Mailbox._attach_message(self, uid) self[uid] = Message(self._account, self, uid) return self[uid] end function Mailbox._detach_message(self, uid) self[uid] = nil end function Mailbox._cached_select(self) if self._account._account.selected == nil or self._account._account.selected ~= self._mailbox then if not self._check_connection(self) then return end local r = ifcore.select(self._account._account.session, self._mailbox) self._check_result(self, 'select', r) if r == false then return false end self._account._account.selected = self._mailbox end return true end function Mailbox._cached_close(self) if not self._check_connection(self) then return end local r = ifcore.close(self._account._account.session) self._check_result(self, 'close', r) if r == false then return false end self._account._account.selected = nil return true end function Mailbox._send_query(self, criteria, charset) _check_optional(criteria, { 'string', 'table' }) _check_optional(charset, 'string') if self._cached_select(self) ~= true then return {} end local query if criteria == nil then query = 'ALL' elseif type(criteria) == 'string' then query = 'ALL ' .. criteria else query = _make_query(criteria) end if charset == nil then if type(options.charset) == 'string' then charset = options.charset else charset = '' end end if not self._check_connection(self) then return end local r, results = ifcore.search(self._account._account.session, query, charset) self._check_result(self, 'search', r) if r == false then return false end if options.close == true then self._cached_close(self) end if results == nil then return {} end local t = {} for n in string.gmatch(results, '%d+') do table.insert(t, { self, tonumber(n) }) end return t end function Mailbox._flag_messages(self, mode, flags, messages) if not messages or #messages == 0 then return end if self._cached_select(self) ~= true then return end local f = '' if #flags ~= 0 then f = table.concat(flags, ' ') end local m = _make_range(messages) local n = #m local r = false for i = 1, n, 50 do j = i + 49 if n < j then j = n end if not self._check_connection(self) then return end r = ifcore.store(self._account._account.session, table.concat(m, ',', i, j), mode, f) self._check_result(self, 'store', r) if r == false then break end end if options.close == true then self._cached_close(self) end return true end function Mailbox._copy_messages(self, dest, messages) if not messages or #messages == 0 then return end local r = false if self._account._account.session == dest._account._account.session then if self._cached_select(self) ~= true then return end local m = _make_range(messages) local n = #m for i = 1, n, 50 do j = i + 49 if n < j then j = n end if not self._check_connection(self) then return end r = ifcore.copy(self._account._account.session, table.concat(m, ',', i, j), dest._mailbox) self._check_result(self, 'copy', r) if r == false then break end end if options.close == true then self._cached_close(self) end else local fast = self._fetch_fast(self, messages) local mesgs = self._fetch_message(self, messages) for i in pairs(fast) do for k, v in ipairs(fast[i]['flags']) do if string.lower(v) == '\\recent' then table.remove(fast[i]['flags'], k) end end if not self._check_connection(dest) then return end r = ifcore.append(dest._account._account.session, dest._mailbox, mesgs[i], table.concat(fast[i]['flags'], ' '), fast[i]['date']) self._check_result(dest, 'append', r) if r == false then break end end end return true end function Mailbox._fetch_fast(self, messages) if not messages or #messages == 0 then return end if self._cached_select(self) ~= true then return end local results = {} for _, m in ipairs(messages) do if not self._check_connection(self) then return end local r, flags, date, size = ifcore.fetchfast(self._account._account.session, tostring(m)) self._check_result(self, 'fetchfast', r) if r == false then break end if flags ~= nil and date ~= nil and size ~= nil then local f = {} for s in string.gmatch(flags, '%S+') do table.insert(f, s) end results[m] = {} results[m]['flags'] = f results[m]['date'] = date results[m]['size'] = size end end if options.close == true then self._cached_close(self) end return results end function Mailbox._fetch_flags(self, messages) if not messages or #messages == 0 then return end if self._cached_select(self) ~= true then return end local results = {} for _, m in ipairs(messages) do if not self._check_connection(self) then return end local r, flags = ifcore.fetchflags(self._account._account.session, tostring(m)) self._check_result(self, 'fetchfast', r) if r == false then break end if flags ~= nil then local f = {} for s in string.gmatch(flags, '%S+') do table.insert(f, s) end results[m] = f end end if options.close == true then self._cached_close(self) end return results end function Mailbox._fetch_date(self, messages) if not messages or #messages == 0 then return end if self._cached_select(self) ~= true then return end local results = {} for _, m in ipairs(messages) do if options.cache == true and self[m]._date then results[m] = self[m]._date else if not self._check_connection(self) then return end local r, date = ifcore.fetchdate(self._account._account.session, tostring(m)) self._check_result(self, 'fetchdate', r) if r == false then break end if date ~= nil then results[m] = date if options.cache == true then self[m]._date = date end end end end if options.close == true then self._cached_close(self) end return results end function Mailbox._fetch_size(self, messages) if not messages or #messages == 0 then return end if self._cached_select(self) ~= true then return end local results = {} for _, m in ipairs(messages) do if options.cache == true and self[m]._size then results[m] = self[m]._size else if not self._check_connection(self) then return end local r, size = ifcore.fetchsize(self._account._account.session, tostring(m)) self._check_result(self, 'fetchsize', r) if r == false then break end if size ~= nil then results[m] = tonumber(size) if options.cache == true then self[m]._size = tonumber(size) end end end end if options.close == true then self._cached_close(self) end return results end function Mailbox._fetch_header(self, messages) if not messages or #messages == 0 then return end if self._cached_select(self) ~= true then return end local results = {} for _, m in ipairs(messages) do if options.cache == true and self[m]._header then results[m] = self[m]._header else if not self._check_connection(self) then return end local r, header = ifcore.fetchheader(self._account._account.session, tostring(m)) self._check_result(self, 'fetchheader', r) if r == false then break end if header ~= nil then results[m] = header if options.cache == true then self[m]._header = header end end end end if options.close == true then self._cached_close(self) end return results end function Mailbox._fetch_body(self, messages) if not messages or #messages == 0 then return end if self._cached_select(self) ~= true then return end local results = {} for _, m in ipairs(messages) do if options.cache == true and self[m]._body then results[m] = self[m]._body else if not self._check_connection(self) then return end local r, body = ifcore.fetchbody(self._account._account.session, tostring(m)) self._check_result(self, 'fetchbody', r) if r == false then break end if body ~= nil then results[m] = body if options.cache == true then self[m]._body = body end end end end if options.close == true then self._cached_close(self) end return results end function Mailbox._fetch_message(self, messages) if not messages or #messages == 0 then return end if self._cached_select(self) ~= true then return end local header = self._fetch_header(self, messages) local body = self._fetch_body(self, messages) local results = {} for _, m in ipairs(messages) do if header[m] == nil then results[m] = nil elseif body[m] == nil then results[m] = header[m] else results[m] = header[m] .. body[m] end end if options.close == true then self._cached_close(self) end return results end function Mailbox._fetch_fields(self, fields, messages) if not messages or #messages == 0 then return end if self._cached_select(self) ~= true then return end local results = {} for _, m in ipairs(messages) do results[m] = '' for _, f in ipairs(fields) do if options.cache == true and self[m]._fields[f] then results[m] = results[m] .. self[m]._fields[f] else if not self._check_connection(self) then return end local r, field = ifcore.fetchfields(self._account._account.session, tostring(m), f) self._check_result(self, 'fetchfields', r) if r == false then break end if field ~= nil then field = string.gsub(field, '\r\n\r\n$', '\n') results[m] = results[m] .. field if options.cache == true then self[m]._fields[f] = field end end end end results[m] = string.gsub(results[m], '\n$', '') end if options.close == true then self._cached_close(self) end return results end function Mailbox._fetch_structure(self, messages) if not messages or #messages == 0 then return end if self._cached_select(self) ~= true then return end local results = {} for _, m in ipairs(messages) do if options.cache == true and self[m]._structure then results[m] = self[m]._structure else if not self._check_connection(self) then return end local r, structure = ifcore.fetchstructure(self._account._account.session, tostring(m)) self._check_result(self, 'fetchstructure', r) if r == false then break end if structure ~= nil then local parsed = _parse_structure({ ['s'] = structure, ['i'] = 1 }) results[m] = parsed if options.cache == true then self[m]._structure = parsed end end end end if options.close == true then self._cached_close(self) end return results end function Mailbox._fetch_parts(self, parts, message) if self._cached_select(self) ~= true then return end local results = {} for _, part in ipairs(parts) do results[part] = '' if options.cache == true and self[message]._parts[part] then results[part] = self[message]._parts[part] else if not self._check_connection(self) then return end local r, bodypart = ifcore.fetchpart(self._account._account.session, tostring(message), part) self._check_result(self, 'fetchpart', r) if r == false then break end if bodypart ~= nil then results[part] = bodypart self[message]._parts[part] = bodypart end end end if options.close == true then self._cached_close(self) end return results end function Mailbox.check_status(self) if not self._check_connection(self) then return end local r, exist, recent, unseen, uidnext = ifcore.status(self._account._account.session,self._mailbox) self._check_result(self, 'status', r) if r == false then return false end if options.info == true then print(exist .. ' messages, ' .. recent .. ' recent, ' .. unseen .. ' unseen, in ' .. self._string .. '.') end return exist, recent, unseen, uidnext end function Mailbox.send_query(self, criteria, charset) return Set(self._send_query(self, criteria, charset)) end function Mailbox.select_all(self) return self.send_query(self) end function Mailbox.add_flags(self, flags, messages) _check_required(flags, 'table') _check_required(messages, 'table') local mesgs = _extract_messages(self, messages) local r = self._flag_messages(self, 'add', flags, mesgs) if options.info == true and r == true then print(#mesgs .. ' messages flagged in ' .. self._string .. '.') end return r end function Mailbox.remove_flags(self, flags, messages) _check_required(flags, 'table') _check_required(messages, 'table') local mesgs = _extract_messages(self, messages) local r = self._flag_messages(self, 'remove', flags, mesgs) if options.info == true and r == true then print(#mesgs .. ' messages flagged in ' .. self._string .. '.') end return r end function Mailbox.replace_flags(self, flags, messages) _check_required(flags, 'table') _check_required(messages, 'table') local mesgs = _extract_messages(self, messages) local r = self._flag_messages(self, 'replace', flags, mesgs) if options.info == true and r == true then print(#mesgs .. ' messages flagged in ' .. self._string .. '.') end return r end function Mailbox.mark_answered(self, messages) _check_required(messages, 'table') local mesgs = _extract_messages(self, messages) local r = self._flag_messages(self, 'add', { '\\Answered' }, mesgs) if options.info == true and r == true then print(#mesgs .. ' messages marked answered in ' .. self._string .. '.') end return r end function Mailbox.mark_deleted(self, messages) _check_required(messages, 'table') local mesgs = _extract_messages(self, messages) local r = self._flag_messages(self, 'add', { '\\Deleted' }, mesgs) if options.info == true and r == true then print(#mesgs .. ' messages marked deleted in ' .. self._string .. '.') end return r end function Mailbox.mark_draft(self, messages) _check_required(messages, 'table') local mesgs = _extract_messages(self, messages) local r = self._flag_messages(self, 'add', { '\\Draft' }, mesgs) if options.info == true and r == true then print(#mesgs .. ' messages marked draft in ' .. self._string .. '.') end return r end function Mailbox.mark_flagged(self, messages) _check_required(messages, 'table') local mesgs = _extract_messages(self, messages) local r = self._flag_messages(self, 'add', { '\\Flagged' }, mesgs) if options.info == true and r == true then print(#mesgs .. ' messages marked flagged in ' .. self._string .. '.') end return r end function Mailbox.mark_seen(self, messages) _check_required(messages, 'table') local mesgs = _extract_messages(self, messages) local r = self._flag_messages(self, 'add', { '\\Seen' }, mesgs) if options.info == true and r == true then print(#mesgs .. ' messages marked seen in ' .. self._string .. '.') end return r end function Mailbox.unmark_answered(self, messages) _check_required(messages, 'table') local mesgs = _extract_messages(self, messages) local r = self._flag_messages(self, 'remove', { '\\Answered' }, mesgs) if options.info == true and r == true then print(#mesgs .. ' messages unmarked answered in ' .. self._string .. '.') end return r end function Mailbox.unmark_deleted(self, messages) _check_required(messages, 'table') local mesgs = _extract_messages(self, messages) local r = self._flag_messages(self, 'remove', { '\\Deleted' }, mesgs) if options.info == true and r == true then print(#mesgs .. ' messages unmarked deleted in ' .. self._string .. '.') end return r end function Mailbox.unmark_draft(self, messages) _check_required(messages, 'table') local mesgs = _extract_messages(self, messages) local r = self._flag_messages(self, 'remove', { '\\Draft' }, mesgs) if options.info == true and r == true then print(#mesgs .. ' messages unmarked draft in ' .. self._string .. '.') end return r end function Mailbox.unmark_flagged(self, messages) _check_required(messages, 'table') local mesgs = _extract_messages(self, messages) local r = self._flag_messages(self, 'remove', { '\\Flagged' }, mesgs) if options.info == true and r == true then print(#mesgs .. ' messages unmarked flagged in ' .. self._string .. '.') end return r end function Mailbox.unmark_seen(self, messages) _check_required(messages, 'table') local mesgs = _extract_messages(self, messages) local r = self._flag_messages(self, 'remove', { '\\Seen' }, mesgs) if options.info == true and r == true then print(#mesgs .. ' messages unmarked seen in ' .. self._string .. '.') end return r end function Mailbox.delete_messages(self, messages) _check_required(messages, 'table') local mesgs = _extract_messages(self, messages) local r = self._flag_messages(self, 'add', { '\\Deleted' }, mesgs) if options.info == true and r == true then print(#mesgs .. ' messages deleted in ' .. self._string .. '.') end return r end function Mailbox.copy_messages(self, dest, messages) _check_required(dest, 'table') _check_required(messages, 'table') local mesgs = _extract_messages(self, messages) local r = self._copy_messages(self, dest, mesgs) if options.info == true and r == true then print(#mesgs .. ' messages copied from ' .. self._string .. ' to ' .. dest._string .. '.') end return r end function Mailbox.move_messages(self, dest, messages) _check_required(dest, 'table') _check_required(messages, 'table') local mesgs = _extract_messages(self, messages) local rc = self._copy_messages(self, dest, mesgs) local rf = false if rc == true then rf = self._flag_messages(self, 'add', { '\\Deleted' }, mesgs) end if options.info == true and rc == true and rf == true then print(#mesgs .. ' messages moved from ' .. self._string .. ' to ' .. dest._string .. '.') end return rc == true and rf == true end function Mailbox.fetch_flags(self, messages) _check_required(messages, 'table') return self._fetch_flags(self, _extract_messages(self, messages)) end function Mailbox.fetch_date(self, messages) _check_required(messages, 'table') return self._fetch_date(self, _extract_messages(self, messages)) end function Mailbox.fetch_size(self, messages) _check_required(messages, 'table') return self._fetch_size(self, _extract_messages(self, messages)) end function Mailbox.fetch_header(self, messages) _check_required(messages, 'table') return self._fetch_header(self, _extract_messages(self, messages)) end function Mailbox.fetch_body(self, messages) _check_required(messages, 'table') return self._fetch_body(self, _extract_messages(self, messages)) end function Mailbox.fetch_message(self, messages) _check_required(messages, 'table') return self._fetch_message(self, _extract_messages(self, messages)) end function Mailbox.fetch_fields(self, fields, messages) _check_required(fields, 'table') _check_required(messages, 'table') return self._fetch_fields(self, fields, _extract_messages(self, messages)) end function Mailbox.fetch_structure(self, messages) _check_required(messages, 'table') return self._fetch_structure(self, _extract_messages(self, messages)) end function Mailbox.fetch_parts(self, parts, message) _check_required(parts, 'table') _check_required(message, 'number') return self._fetch_parts(self, parts, message) end function Mailbox.append_message(self, message, flags, date) _check_required(message, 'string') _check_optional(flags, { 'string', 'table' }) _check_optional(date, 'string') if type(flags) == 'table' then flags = table.concat(flags, ' ') end if not self._check_connection(self) then return end r = ifcore.append(self._account._account.session, self._mailbox, message, flags, date) self._check_result(self, 'append', r) if r == false then return false end if options.info == true and r == true then print('Appended message of ' .. #message .. ' octets to ' .. self._string .. '.') end return true end function Mailbox.is_answered(self) return self.send_query(self, 'ANSWERED') end function Mailbox.is_deleted(self) return self.send_query(self, 'DELETED') end function Mailbox.is_draft(self) return self.send_query(self, 'DRAFT') end function Mailbox.is_flagged(self) return self.send_query(self, 'FLAGGED') end function Mailbox.is_new(self) return self.send_query(self, 'NEW') end function Mailbox.is_old(self) return self.send_query(self, 'OLD') end function Mailbox.is_recent(self) return self.send_query(self, 'RECENT') end function Mailbox.is_seen(self) return self.send_query(self, 'SEEN') end function Mailbox.is_unanswered(self) return self.send_query(self, 'UNANSWERED') end function Mailbox.is_undeleted(self) return self.send_query(self, 'UNDELETED') end function Mailbox.is_undraft(self) return self.send_query(self, 'UNDRAFT') end function Mailbox.is_unflagged(self) return self.send_query(self, 'UNFLAGGED') end function Mailbox.is_unseen(self) return self.send_query(self, 'UNSEEN') end function Mailbox.is_larger(self, size) _check_required(size, 'number') return self.send_query(self, 'LARGER ' .. tostring(size)) end function Mailbox.is_smaller(self, size) _check_required(size, 'number') return self.send_query(self, 'SMALLER ' .. tostring(size)) end function Mailbox.arrived_on(self, date) _check_required(date, 'string') return self.send_query(self, 'ON ' .. date) end function Mailbox.arrived_before(self, date) _check_required(date, 'string') return self.send_query(self, 'BEFORE ' .. date) end function Mailbox.arrived_since(self, date) _check_required(date, 'string') return self.send_query(self, 'SINCE ' .. date) end function Mailbox.sent_on(self, date) _check_required(date, 'string') return self.send_query(self, 'SENTON ' .. date) end function Mailbox.sent_before(self, date) _check_required(date, 'string') return self.send_query(self, 'SENTBEFORE ' .. date) end function Mailbox.sent_since(self, date) _check_required(date, 'string') return self.send_query(self, 'SENTSINCE ' .. date) end function Mailbox.is_newer(self, days) _check_required(days, 'number') return self.send_query(self, 'SINCE ' .. form_date(days)) end function Mailbox.is_older(self, days) _check_required(days, 'number') return self.send_query(self, 'BEFORE ' .. form_date(days)) end function Mailbox.has_flag(self, flag) _check_required(flag, 'string') return self.send_query(self, 'KEYWORD "' .. flag .. '"') end function Mailbox.contain_field(self, field, string) _check_required(field, 'string') _check_required(string, 'string') return self.send_query(self, 'HEADER ' .. field .. ' "' .. string .. '"') end function Mailbox.contain_bcc(self, string) _check_required(string, 'string') return self.send_query(self, 'BCC "' .. string .. '"') end function Mailbox.contain_cc(self, string) _check_required(string, 'string') return self.send_query(self, 'CC "' .. string .. '"') end function Mailbox.contain_from(self, string) _check_required(string, 'string') return self.send_query(self, 'FROM "' .. string .. '"') end function Mailbox.contain_subject(self, string) _check_required(string, 'string') return self.send_query(self, 'SUBJECT "' .. string .. '"') end function Mailbox.contain_to(self, string) _check_required(string, 'string') return self.send_query(self, 'TO "' .. string .. '"') end function Mailbox.contain_header(self, string) _check_required(string, 'string') return self.send_query(self, 'TEXT "' .. string .. '" NOT BODY "' .. string .. '"') end function Mailbox.contain_body(self, string) _check_required(string, 'string') return self.send_query(self, 'BODY "' .. string .. '"') end function Mailbox.contain_message(self, string) _check_required(string, 'string') return self.send_query(self, 'TEXT "' .. string .. '"') end function Mailbox.match_field(self, field, pattern, messages) _check_required(field, 'string') _check_required(pattern, 'string') if not messages then messages = self._send_query(self) end local mesgs = _extract_messages(self, messages) local fields = self._fetch_fields(self, { field }, mesgs) if #mesgs == 0 or fields == nil then return Set({}) end local results = {} for m, f in pairs(fields) do if regex_search(pattern, (string.gsub(f, '^[^:]*: ?(.*)$', '%1'))) then table.insert(results, {self, m}) end end return Set(results) end function Mailbox.match_bcc(self, pattern, messages) _check_required(pattern, 'string') return self.match_field(self, 'Bcc', pattern, messages) end function Mailbox.match_cc(self, pattern, messages) _check_required(pattern, 'string') return self.match_field(self, 'Cc', pattern, messages) end function Mailbox.match_from(self, pattern, messages) _check_required(pattern, 'string') return self.match_field(self, 'From', pattern, messages) end function Mailbox.match_subject(self, pattern, messages) _check_required(pattern, 'string') return self.match_field(self, 'Subject', pattern, messages) end function Mailbox.match_to(self, pattern, messages) _check_required(pattern, 'string') return self.match_field(self, 'To', pattern, messages) end function Mailbox.match_header(self, pattern, messages) _check_required(pattern, 'string') if not messages then messages = self._send_query(self) end local mesgs = _extract_messages(self, messages) local header = self._fetch_header(self, mesgs) if #mesgs == 0 or header == nil then return Set({}) end local results = {} for m, h in pairs(header) do if regex_search(pattern, h) then table.insert(results, {self, m}) end end return Set(results) end function Mailbox.match_body(self, pattern, messages) _check_required(pattern, 'string') if not messages then messages = self._send_query(self) end local mesgs = _extract_messages(self, messages) local body = self._fetch_body(self, mesgs) if #mesgs == 0 or body == nil then return Set({}) end local results = {} for m, b in pairs(body) do if regex_search(pattern, b) then table.insert(results, {self, m}) end end return Set(results) end function Mailbox.match_message(self, pattern, messages) _check_required(pattern, 'string') if not messages then messages = self._send_query(self) end local mesgs = _extract_messages(self, messages) local full = self._fetch_message(self, mesgs) if #mesgs == 0 or full == nil then return Set({}) end local results = {} for m, f in pairs(full) do if regex_search(pattern, f) then table.insert(results, {self, m}) end end return Set(results) end function Mailbox.enter_idle(self) if self._cached_select(self) ~= true then return false end if not self._check_connection(self) then return end local r = ifcore.idle(self._account._account.session) self._check_result(self, 'idle', r) if r == false then return false end if options.close == true then self._cached_close(self) end return true end Mailbox.open = _cached_select Mailbox.close = _cached_close Mailbox._mt.__index = function () end Mailbox._mt.__newindex = function () end imapfilter-2.5.2/src/auth.c0000644000175000017500000000262311757662071015407 0ustar frankiefrankie#include #include #include #include #include "imapfilter.h" /* * Authenticate to the server with the Challenge-Response Authentication * Mechanism (CRAM). The authentication type associated with CRAM is * "CRAM-MD5". */ unsigned char * auth_cram_md5(const char *user, const char *pass, unsigned char *chal) { size_t n; unsigned int i; unsigned char *resp, *buf, *out; unsigned char md[EVP_MAX_MD_SIZE], mdhex[EVP_MAX_MD_SIZE * 2 + 1]; unsigned int mdlen; HMAC_CTX hmac; n = strlen((char *)(chal)) * 3 / 4 + 1; resp = (unsigned char *)xmalloc(n * sizeof(char)); memset(resp, 0, n); EVP_DecodeBlock(resp, chal, strlen((char *)(chal))); HMAC_Init(&hmac, (const unsigned char *)pass, strlen(pass), EVP_md5()); HMAC_Update(&hmac, resp, strlen((char *)(resp))); HMAC_Final(&hmac, md, &mdlen); xfree(chal); xfree(resp); for (i = 0; i < mdlen; i++) snprintf((char *)(mdhex) + i * 2, mdlen * 2 - i * 2 + 1, "%02x", md[i]); mdhex[mdlen * 2] = '\0'; n = strlen(user) + 1 + strlen((char *)(mdhex)) + 1; buf = (unsigned char *)xmalloc(n * sizeof(unsigned char)); memset(buf, 0, n); snprintf((char *)(buf), n, "%s %s", user, mdhex); n = (strlen((char *)(buf)) + 3) * 4 / 3 + 1; out = (unsigned char *)xmalloc(n * sizeof(unsigned char)); memset(out, 0, n); EVP_EncodeBlock(out, buf, strlen((char *)(buf))); xfree(buf); return out; } imapfilter-2.5.2/src/imapfilter.c0000644000175000017500000000464211757662071016605 0ustar frankiefrankie#include #include #include #include #include #include #include #include #include #include #include "imapfilter.h" #include "session.h" #include "list.h" #include "version.h" #include "buffer.h" #include "pathnames.h" #include "regexp.h" extern buffer ibuf, obuf, nbuf, cbuf; extern regexp responses[]; options opts; /* Program options. */ environment env; /* Environment variables. */ list *sessions = NULL; /* Active IMAP sessions. */ void usage(void); void version(void); /* * IMAPFilter: an IMAP mail filtering utility. */ int main(int argc, char *argv[]) { int c; setlocale(LC_CTYPE, ""); opts.verbose = 0; opts.interactive = 0; opts.log = NULL; opts.config = NULL; opts.oneline = NULL; opts.debug = NULL; env.home = NULL; env.pathmax = -1; while ((c = getopt(argc, argv, "Vc:d:e:il:v?")) != -1) { switch (c) { case 'V': version(); /* NOTREACHED */ break; case 'c': opts.config = optarg; break; case 'd': opts.debug = optarg; break; case 'e': opts.oneline = optarg; break; case 'i': opts.interactive = 1; break; case 'l': opts.log = optarg; break; case 'v': opts.verbose = 1; break; case '?': default: usage(); /* NOTREACHED */ break; } } get_pathmax(); open_debug(); create_homedir(); catch_signals(); open_log(); if (opts.config == NULL) opts.config = get_filepath("config.lua"); buffer_init(&ibuf, INPUT_BUF); buffer_init(&obuf, OUTPUT_BUF); buffer_init(&nbuf, NAMESPACE_BUF); buffer_init(&cbuf, CONVERSION_BUF); regexp_compile(responses); SSL_library_init(); SSL_load_error_strings(); start_lua(); #if LUA_VERSION_NUM < 502 { list *l; session *s; l = sessions; while (l != NULL) { s = l->data; l = l->next; request_logout(s); } } #endif stop_lua(); ERR_free_strings(); regexp_free(responses); buffer_free(&ibuf); buffer_free(&obuf); buffer_free(&nbuf); buffer_free(&cbuf); xfree(env.home); close_log(); close_debug(); exit(0); } /* * Print a very brief usage message. */ void usage(void) { fprintf(stderr, "usage: imapfilter [-iVv] [-c configfile] " "[-d debugfile] [-e 'command'] [-l logfile]\n"); exit(0); } /* * Print program's version and copyright. */ void version(void) { fprintf(stderr, "IMAPFilter %s %s\n", VERSION, COPYRIGHT); exit(0); } imapfilter-2.5.2/src/imapfilter.h0000644000175000017500000001631011757662071016605 0ustar frankiefrankie#ifndef IMAPFILTER_H #define IMAPFILTER_H #include #include #include #include #include #include #include "session.h" /* Fatal error exit codes. */ #define ERROR_SIGNAL 1 #define ERROR_CONFIG 2 #define ERROR_MEMALLOC 3 #define ERROR_PATHNAME 4 #define ERROR_CERTIFICATE 5 /* IMAP protocol supported by the server. */ #define PROTOCOL_NONE 0 #define PROTOCOL_IMAP4REV1 1 #define PROTOCOL_IMAP4 2 /* Capabilities of mail server. */ #define CAPABILITY_NONE 0x00 #define CAPABILITY_NAMESPACE 0x01 #define CAPABILITY_CRAMMD5 0x02 #define CAPABILITY_STARTTLS 0x04 #define CAPABILITY_CHILDREN 0x08 #define CAPABILITY_IDLE 0x10 /* Status responses and response codes. */ #define STATUS_NONE 0 #define STATUS_OK 1 #define STATUS_NO 2 #define STATUS_BAD 3 #define STATUS_UNTAGGED 4 #define STATUS_CONTINUE 5 #define STATUS_BYE 6 #define STATUS_PREAUTH 7 #define STATUS_READONLY 8 #define STATUS_TRYCREATE 9 #define STATUS_TIMEOUT 10 /* Initial size for buffers. */ #define INPUT_BUF 4096 #define OUTPUT_BUF 1024 #define NAMESPACE_BUF 512 #define CONVERSION_BUF 512 /* Maximum length, in bytes, of a utility's input line. */ #ifndef LINE_MAX #define LINE_MAX 2048 #endif /* Program's options. */ typedef struct options { int verbose; /* Verbose mode. */ int interactive; /* Act as an interpreter. */ char *log; /* Log file for error messages. */ char *config; /* Configuration file. */ char *oneline; /* One line of program/configuration. */ char *debug; /* Debug file. */ } options; /* Environment variables. */ typedef struct environment { char *home; /* Program's home directory. */ long pathmax; /* Maximum pathname. */ } environment; /* auth.c */ unsigned char *auth_cram_md5(const char *user, const char *pass, unsigned char *chal); /* cert.c */ int get_cert(session *ssn); /* core.c */ LUALIB_API int luaopen_ifcore(lua_State *lua); /* file.c */ void create_homedir(void); int exists_file(char *fname); int exists_dir(char *fname); int create_file(char *fname, mode_t mode); int get_pathmax(void); char *get_filepath(char *fname); /* log.c */ void verbose(const char *info,...); void debug(const char *debug,...); void debugc(char c); void error(const char *errmsg,...); void fatal(unsigned int errnum, const char *fatal,...); int open_debug(void); int close_debug(void); int open_log(void); int close_log(void); /* lua.c */ void start_lua(void); void stop_lua(void); int get_option_boolean(const char *opt); lua_Number get_option_number(const char *opt); const char *get_option_string(const char *opt); int set_table_boolean(const char *key, int value); int set_table_number(const char *key, lua_Number value); int set_table_string(const char *key, const char *value); /* memory.c */ void *xmalloc(size_t size); void *xrealloc(void *ptr, size_t size); void xfree(void *ptr); char *xstrdup(const char *str); char *xstrndup(const char *str, size_t len); /* misc.c */ const char *xstrcasestr(const char *haystack, const char *needle); char *xstrncpy(char *dest, const char *src, size_t size); /* namespace.c */ const char *apply_namespace(const char *mbox, char *prefix, char delim); const char *reverse_namespace(const char *mbox, char *prefix, char delim); /* pcre.c */ LUALIB_API int luaopen_ifre(lua_State *lua); /* request.c */ int request_noop(session *ssn); int request_login(session **ssn, const char *server, const char *port, const char *protocol, const char *user, const char *pass); int request_logout(session *ssn); int request_status(session *ssn, const char *mbox, unsigned int *exist, unsigned int *recent, unsigned int *unseen, unsigned int *uidnext); int request_select(session *ssn, const char *mbox); int request_close(session *ssn); int request_expunge(session *ssn); int request_list(session *ssn, const char *refer, const char *name, char **mboxs, char **folders); int request_lsub(session *ssn, const char *refer, const char *name, char **mboxs, char **folders); int request_search(session *ssn, const char *criteria, const char *charset, char **mesgs); int request_fetchfast(session *ssn, const char *mesg, char **flags, char **date, char **size); int request_fetchflags(session *ssn, const char *mesg, char **flags); int request_fetchdate(session *ssn, const char *mesg, char **date); int request_fetchsize(session *ssn, const char *mesg, char **size); int request_fetchstructure(session *ssn, const char *mesg, char **structure); int request_fetchheader(session *ssn, const char *mesg, char **header, size_t *len); int request_fetchtext(session *ssn, const char *mesg, char **text, size_t *len); int request_fetchfields(session *ssn, const char *mesg, const char *headerfields, char **fields, size_t *len); int request_fetchpart(session *ssn, const char *mesg, const char *bodypart, char **part, size_t *len); int request_store(session *ssn, const char *mesg, const char *mode, const char *flags); int request_copy(session *ssn, const char *mesg, const char *mbox); int request_append(session *ssn, const char *mbox, const char *mesg, size_t mesglen, const char *flags, const char *date); int request_create(session *ssn, const char *mbox); int request_delete(session *ssn, const char *mbox); int request_rename(session *ssn, const char *oldmbox, const char *newmbox); int request_subscribe(session *ssn, const char *mbox); int request_unsubscribe(session *ssn, const char *mbox); int request_idle(session *ssn); /* response.c */ int response_generic(session *ssn, int tag); int response_continuation(session *ssn, int tag); int response_greeting(session *ssn); int response_capability(session *ssn, int tag); int response_authenticate(session *ssn, int tag, unsigned char **cont); int response_namespace(session *ssn, int tag); int response_status(session *ssn, int tag, unsigned int *exist, unsigned int *recent, unsigned int *unseen, unsigned int *uidnext); int response_examine(session *ssn, int tag, unsigned int *exist, unsigned int *recent); int response_select(session *ssn, int tag); int response_list(session *ssn, int tag, char **mboxs, char **folders); int response_search(session *ssn, int tag, char **mesgs); int response_fetchfast(session *ssn, int tag, char **flags, char **date, char **size); int response_fetchflags(session *ssn, int tag, char **flags); int response_fetchdate(session *ssn, int tag, char **date); int response_fetchsize(session *ssn, int tag, char **size); int response_fetchstructure(session *ssn, int tag, char **structure); int response_fetchbody(session *ssn, int tag, char **body, size_t *len); int response_idle(session *ssn, int tag); /* signal.c */ void catch_signals(void); void release_signals(void); /* socket.c */ int open_connection(session *ssn); int close_connection(session *ssn); ssize_t socket_read(session *ssn, char *buf, size_t len, long timeout, int timeoutfail); ssize_t socket_write(session *ssn, const char *buf, size_t len); int open_secure_connection(session *ssn); int close_secure_connection(session *ssn); ssize_t socket_secure_read(session *ssn, char *buf, size_t len); ssize_t socket_secure_write(session *ssn, const char *buf, size_t len); /* system.c */ LUALIB_API int luaopen_ifsys(lua_State *lua); #endif /* IMAPFILTER_H */ imapfilter-2.5.2/src/cert.c0000644000175000017500000000736411757662071015412 0ustar frankiefrankie#include #include #include #include #include #include #include #include #include #include #include "imapfilter.h" #include "session.h" extern environment env; int check_cert(X509 *pcert, unsigned char *pmd, unsigned int *pmdlen); void print_cert(X509 *cert, unsigned char *md, unsigned int *mdlen); int write_cert(X509 *cert); int mismatch_cert(void); /* * Get SSL/TLS certificate check it, maybe ask user about it and act * accordingly. */ int get_cert(session *ssn) { X509 *cert; unsigned char md[EVP_MAX_MD_SIZE]; unsigned int mdlen; mdlen = 0; if (!(cert = SSL_get_peer_certificate(ssn->sslconn))) return -1; if (!(X509_digest(cert, EVP_md5(), md, &mdlen))) return -1; switch (check_cert(cert, md, &mdlen)) { case 0: if (isatty(STDIN_FILENO) == 0) fatal(ERROR_CERTIFICATE, "%s\n", "can't accept certificate in non-interactive mode"); print_cert(cert, md, &mdlen); if (write_cert(cert) == -1) goto fail; break; case -1: if (isatty(STDIN_FILENO) == 0) fatal(ERROR_CERTIFICATE, "%s\n", "certificate mismatch in non-interactive mode"); print_cert(cert, md, &mdlen); if (mismatch_cert() == -1) goto fail; break; } X509_free(cert); return 0; fail: X509_free(cert); return -1; } /* * Check if the SSL/TLS certificate exists in the certificates file. */ int check_cert(X509 *pcert, unsigned char *pmd, unsigned int *pmdlen) { int r; FILE *fd; char *certf; X509 *cert; unsigned char md[EVP_MAX_MD_SIZE]; unsigned int mdlen; r = 0; cert = NULL; certf = get_filepath("certificates"); if (!exists_file(certf)) { xfree(certf); return 0; } fd = fopen(certf, "r"); xfree(certf); if (fd == NULL) return -1; while ((cert = PEM_read_X509(fd, &cert, NULL, NULL)) != NULL) { if (X509_subject_name_cmp(cert, pcert) != 0 || X509_issuer_name_cmp(cert, pcert) != 0) continue; if (!X509_digest(cert, EVP_md5(), md, &mdlen) || *pmdlen != mdlen) continue; if (memcmp(pmd, md, mdlen) != 0) { r = -1; break; } r = 1; break; } fclose(fd); X509_free(cert); return r; } /* * Print information about the SSL/TLS certificate. */ void print_cert(X509 *cert, unsigned char *md, unsigned int *mdlen) { unsigned int i; char *c; c = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); printf("Server certificate subject: %s\n", c); xfree(c); c = X509_NAME_oneline(X509_get_issuer_name(cert), NULL, 0); printf("Server certificate issuer: %s\n", c); xfree(c); printf("Server key fingerprint: "); for (i = 0; i < *mdlen; i++) printf(i != *mdlen - 1 ? "%02X:" : "%02X\n", md[i]); } /* * Write the SSL/TLS certificate after asking the user to accept/reject it. */ int write_cert(X509 *cert) { FILE *fd; char c, buf[64]; char *certf; do { printf("(R)eject, accept (t)emporarily or " "accept (p)ermanently? "); if (fgets(buf, sizeof(buf), stdin) == NULL) return -1; c = tolower((int)(*buf)); } while (c != 'r' && c != 't' && c != 'p'); if (c == 'r') return -1; else if (c == 't') return 0; certf = get_filepath("certificates"); create_file(certf, S_IRUSR | S_IWUSR); fd = fopen(certf, "a"); xfree(certf); if (fd == NULL) return -1; PEM_write_X509(fd, cert); fclose(fd); return 0; } /* * Ask user to proceed, while a fingerprint mismatch in the SSL/TLS certificate * was found. */ int mismatch_cert(void) { char c, buf[64]; do { printf("ATTENTION: SSL/TLS certificate fingerprint mismatch.\n" "Proceed with the connection (y/n)? "); if (fgets(buf, sizeof(buf), stdin) == NULL) return -1; c = tolower((int)(*buf)); } while (c != 'y' && c != 'n'); if (c == 'y') return 0; else return -1; } imapfilter-2.5.2/src/core.c0000644000175000017500000004522611757662071015404 0ustar frankiefrankie#include #include #include #include #include "imapfilter.h" #include "session.h" static int ifcore_noop(lua_State *lua); static int ifcore_login(lua_State *lua); static int ifcore_logout(lua_State *lua); static int ifcore_status(lua_State *lua); static int ifcore_select(lua_State *lua); static int ifcore_close(lua_State *lua); static int ifcore_expunge(lua_State *lua); static int ifcore_search(lua_State *lua); static int ifcore_list(lua_State *lua); static int ifcore_lsub(lua_State *lua); static int ifcore_fetchfast(lua_State *lua); static int ifcore_fetchflags(lua_State *lua); static int ifcore_fetchdate(lua_State *lua); static int ifcore_fetchsize(lua_State *lua); static int ifcore_fetchheader(lua_State *lua); static int ifcore_fetchtext(lua_State *lua); static int ifcore_fetchfields(lua_State *lua); static int ifcore_fetchstructure(lua_State *lua); static int ifcore_fetchpart(lua_State *lua); static int ifcore_store(lua_State *lua); static int ifcore_copy(lua_State *lua); static int ifcore_append(lua_State *lua); static int ifcore_create(lua_State *lua); static int ifcore_delete(lua_State *lua); static int ifcore_rename(lua_State *lua); static int ifcore_subscribe(lua_State *lua); static int ifcore_unsubscribe(lua_State *lua); static int ifcore_idle(lua_State *lua); /* Lua imapfilter core library functions. */ static const luaL_Reg ifcorelib[] = { { "noop", ifcore_noop }, { "logout", ifcore_logout }, { "login", ifcore_login }, { "select", ifcore_select }, { "create", ifcore_create }, { "delete", ifcore_delete }, { "rename", ifcore_rename }, { "subscribe", ifcore_subscribe }, { "unsubscribe", ifcore_unsubscribe }, { "list", ifcore_list }, { "lsub", ifcore_lsub }, { "status", ifcore_status }, { "append", ifcore_append }, { "close", ifcore_close }, { "expunge", ifcore_expunge }, { "search", ifcore_search }, { "fetchfast", ifcore_fetchfast }, { "fetchflags", ifcore_fetchflags }, { "fetchdate", ifcore_fetchdate }, { "fetchsize", ifcore_fetchsize }, /* * RFC 822: message == header + body * RFC 3501: body == header + text * * RFC 3501 notation is used internally, and RFC 822 notation is used * for the interface available to the user. */ { "fetchheader", ifcore_fetchheader }, { "fetchbody", ifcore_fetchtext }, { "fetchfields", ifcore_fetchfields }, { "fetchstructure", ifcore_fetchstructure }, { "fetchpart", ifcore_fetchpart }, { "store", ifcore_store }, { "copy", ifcore_copy }, { "idle", ifcore_idle }, { NULL, NULL } }; /* * Core function to reset any inactivity autologout timer on the server. */ static int ifcore_noop(lua_State *lua) { int r; if (lua_gettop(lua) != 1) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TLIGHTUSERDATA); while ((r = request_noop((session *)(lua_topointer(lua, 1)))) == STATUS_NONE); lua_pop(lua, 1); if (r == -1) return 0; lua_pushboolean(lua, (r == STATUS_OK)); return 1; } /* * Core function to login to the server. */ static int ifcore_login(lua_State *lua) { session *s = NULL; int r; if (lua_gettop(lua) != 5) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TSTRING); luaL_checktype(lua, 2, LUA_TSTRING); luaL_checktype(lua, 3, LUA_TSTRING); luaL_checktype(lua, 4, LUA_TSTRING); luaL_checktype(lua, 5, LUA_TSTRING); r = request_login(&s, lua_tostring(lua, 1), lua_tostring(lua, 2), lua_tostring(lua, 3), lua_tostring(lua, 4), lua_tostring(lua, 5)); lua_pop(lua, 5); if (r == -1) return 0; lua_pushboolean(lua, (r == STATUS_OK || r == STATUS_PREAUTH)); lua_pushlightuserdata(lua, (void *)(s)); return 2; } /* * Core function to logout from the server. */ static int ifcore_logout(lua_State *lua) { int r; if (lua_gettop(lua) != 1) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TLIGHTUSERDATA); r = request_logout((session *)(lua_topointer(lua, 1))); lua_pop(lua, 1); if (r == -1) return 0; lua_pushboolean(lua, (r == STATUS_OK)); return 1; } /* * Core function to get the status of a mailbox. */ static int ifcore_status(lua_State *lua) { int r; unsigned int exists, recent, unseen, uidnext; exists = recent = unseen = uidnext = -1; if (lua_gettop(lua) != 2) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TLIGHTUSERDATA); luaL_checktype(lua, 2, LUA_TSTRING); while ((r = request_status((session *)(lua_topointer(lua, 1)), lua_tostring(lua, 2), &exists, &recent, &unseen, &uidnext)) == STATUS_NONE); lua_pop(lua, 2); if (r == -1) return 0; lua_pushboolean(lua, (r == STATUS_OK)); lua_pushnumber(lua, (lua_Number) (exists)); lua_pushnumber(lua, (lua_Number) (recent)); lua_pushnumber(lua, (lua_Number) (unseen)); lua_pushnumber(lua, (lua_Number) (uidnext)); return 5; } /* * Core function to select a mailbox. */ static int ifcore_select(lua_State *lua) { int r; if (lua_gettop(lua) != 2) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TLIGHTUSERDATA); luaL_checktype(lua, 2, LUA_TSTRING); while ((r = request_select((session *)(lua_topointer(lua, 1)), lua_tostring(lua, 2))) == STATUS_NONE); lua_pop(lua, 2); if (r == -1) return 0; lua_pushboolean(lua, (r == STATUS_OK)); return 1; } /* * Core function to close a mailbox. */ static int ifcore_close(lua_State *lua) { int r; if (lua_gettop(lua) != 1) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TLIGHTUSERDATA); while ((r = request_close((session *)(lua_topointer(lua, 1)))) == STATUS_NONE); lua_pop(lua, 1); if (r == -1) return 0; lua_pushboolean(lua, (r == STATUS_OK)); return 1; } /* * Core function to expunge a mailbox. */ static int ifcore_expunge(lua_State *lua) { int r; if (lua_gettop(lua) != 1) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TLIGHTUSERDATA); while ((r = request_expunge((session *)(lua_topointer(lua, 1)))) == STATUS_NONE); lua_pop(lua, 1); if (r == -1) return 0; lua_pushboolean(lua, (r == STATUS_OK)); return 1; } /* * Core function to list available mailboxes. */ static int ifcore_list(lua_State *lua) { int r; char *mboxs, *folders; mboxs = folders = NULL; if (lua_gettop(lua) != 3) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TLIGHTUSERDATA); luaL_checktype(lua, 2, LUA_TSTRING); luaL_checktype(lua, 3, LUA_TSTRING); while ((r = request_list((session *)(lua_topointer(lua, 1)), lua_tostring(lua, 2), lua_tostring(lua, 3), &mboxs, &folders)) == STATUS_NONE); lua_pop(lua, 3); if (r == -1) return 0; lua_pushboolean(lua, (r == STATUS_OK)); if (!mboxs && !folders) return 1; lua_pushstring(lua, mboxs); lua_pushstring(lua, folders); xfree(mboxs); xfree(folders); return 3; } /* * Core function to list subscribed mailboxes. */ static int ifcore_lsub(lua_State *lua) { int r; char *mboxs, *folders; mboxs = folders = NULL; if (lua_gettop(lua) != 3) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TLIGHTUSERDATA); luaL_checktype(lua, 2, LUA_TSTRING); luaL_checktype(lua, 3, LUA_TSTRING); while ((r = request_lsub((session *)(lua_topointer(lua, 1)), lua_tostring(lua, 2), lua_tostring(lua, 3), &mboxs, &folders)) == STATUS_NONE); lua_pop(lua, 3); if (r == -1) return 0; lua_pushboolean(lua, (r == STATUS_OK)); if (!mboxs) return 1; lua_pushstring(lua, mboxs); lua_pushstring(lua, folders); xfree(mboxs); return 3; } /* * Core function to search the messages of a mailbox. */ static int ifcore_search(lua_State *lua) { int r; char *mesgs; mesgs = NULL; if (lua_gettop(lua) != 3) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TLIGHTUSERDATA); luaL_checktype(lua, 2, LUA_TSTRING); luaL_checktype(lua, 3, LUA_TSTRING); while ((r = request_search((session *)(lua_topointer(lua, 1)), lua_tostring(lua, 2), lua_tostring(lua, 3), &mesgs)) == STATUS_NONE); lua_pop(lua, 3); if (r == -1) return 0; lua_pushboolean(lua, (r == STATUS_OK)); if (!mesgs) return 1; lua_pushstring(lua, mesgs); xfree(mesgs); return 2; } /* * Core function to fetch message information (flags, date, size). */ static int ifcore_fetchfast(lua_State *lua) { int r; char *flags, *date, *size; flags = date = size = NULL; if (lua_gettop(lua) != 2) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TLIGHTUSERDATA); luaL_checktype(lua, 2, LUA_TSTRING); while ((r = request_fetchfast((session *)(lua_topointer(lua, 1)), lua_tostring(lua, 2), &flags, &date, &size)) == STATUS_NONE); lua_pop(lua, 2); if (r == -1) return 0; lua_pushboolean(lua, (r == STATUS_OK)); if (!flags || !date || !size) return 1; lua_pushstring(lua, flags); lua_pushstring(lua, date); lua_pushstring(lua, size); xfree(flags); xfree(date); xfree(size); return 4; } /* * Core function to fetch message flags. */ static int ifcore_fetchflags(lua_State *lua) { int r; char *flags; flags = NULL; if (lua_gettop(lua) != 2) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TLIGHTUSERDATA); luaL_checktype(lua, 2, LUA_TSTRING); while ((r = request_fetchflags((session *)(lua_topointer(lua, 1)), lua_tostring(lua, 2), &flags)) == STATUS_NONE); lua_pop(lua, 2); if (r == -1) return 0; lua_pushboolean(lua, (r == STATUS_OK)); if (!flags) return 1; lua_pushstring(lua, flags); xfree(flags); return 2; } /* * Core function to fetch message date. */ static int ifcore_fetchdate(lua_State *lua) { int r; char *date; date = NULL; if (lua_gettop(lua) != 2) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TLIGHTUSERDATA); luaL_checktype(lua, 2, LUA_TSTRING); while ((r = request_fetchdate((session *)(lua_topointer(lua, 1)), lua_tostring(lua, 2), &date)) == STATUS_NONE); lua_pop(lua, 2); if (r == -1) return 0; lua_pushboolean(lua, (r == STATUS_OK)); if (!date) return 1; lua_pushstring(lua, date); xfree(date); return 2; } /* * Core function to fetch message size. */ static int ifcore_fetchsize(lua_State *lua) { int r; char *size; size = NULL; if (lua_gettop(lua) != 2) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TLIGHTUSERDATA); luaL_checktype(lua, 2, LUA_TSTRING); while ((r = request_fetchsize((session *)(lua_topointer(lua, 1)), lua_tostring(lua, 2), &size)) == STATUS_NONE); lua_pop(lua, 2); lua_pushboolean(lua, (r == STATUS_OK)); if (!size) return 1; lua_pushstring(lua, size); xfree(size); return 2; } /* * Core function to fetch message body structure. */ static int ifcore_fetchstructure(lua_State *lua) { int r; char *structure; structure = NULL; if (lua_gettop(lua) != 2) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TLIGHTUSERDATA); luaL_checktype(lua, 2, LUA_TSTRING); while ((r = request_fetchstructure((session *)(lua_topointer(lua, 1)), lua_tostring(lua, 2), &structure)) == STATUS_NONE); lua_pop(lua, 3); if (r == -1) return 0; lua_pushboolean(lua, (r == STATUS_OK)); if (!structure) return 1; lua_pushstring(lua, structure); return 2; } /* * Core function to fetch message header. */ static int ifcore_fetchheader(lua_State *lua) { int r; char *header; size_t len; header = NULL; len = 0; if (lua_gettop(lua) != 2) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TLIGHTUSERDATA); luaL_checktype(lua, 2, LUA_TSTRING); while ((r = request_fetchheader((session *)(lua_topointer(lua, 1)), lua_tostring(lua, 2), &header, &len)) == STATUS_NONE); lua_pop(lua, 2); if (r == -1) return 0; lua_pushboolean(lua, (r == STATUS_OK)); if (!header) return 1; lua_pushlstring(lua, header, len); return 2; } /* * Core function to fetch message text. */ static int ifcore_fetchtext(lua_State *lua) { int r; char *text; size_t len; text = NULL; len = 0; if (lua_gettop(lua) != 2) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TLIGHTUSERDATA); luaL_checktype(lua, 2, LUA_TSTRING); while ((r = request_fetchtext((session *)(lua_topointer(lua, 1)), lua_tostring(lua, 2), &text, &len)) == STATUS_NONE); lua_pop(lua, 2); if (r == -1) return 0; lua_pushboolean(lua, (r == STATUS_OK)); if (!text) return 1; lua_pushlstring(lua, text, len); return 2; } /* * Core function to fetch message specific header fields. */ static int ifcore_fetchfields(lua_State *lua) { int r; char *fields; size_t len; fields = NULL; len = 0; if (lua_gettop(lua) != 3) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TLIGHTUSERDATA); luaL_checktype(lua, 2, LUA_TSTRING); luaL_checktype(lua, 3, LUA_TSTRING); while ((r = request_fetchfields((session *)(lua_topointer(lua, 1)), lua_tostring(lua, 2), lua_tostring(lua, 3), &fields, &len)) == STATUS_NONE); lua_pop(lua, 3); if (r == -1) return 0; lua_pushboolean(lua, (r == STATUS_OK)); if (!fields) return 1; lua_pushlstring(lua, fields, len); return 2; } /* * Core function to fetch message specific part. */ static int ifcore_fetchpart(lua_State *lua) { int r; char *part; size_t len; part = NULL; len = 0; if (lua_gettop(lua) != 3) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TLIGHTUSERDATA); luaL_checktype(lua, 2, LUA_TSTRING); luaL_checktype(lua, 3, LUA_TSTRING); while ((r = request_fetchpart((session *)(lua_topointer(lua, 1)), lua_tostring(lua, 2), lua_tostring(lua, 3), &part, &len)) == STATUS_NONE); lua_pop(lua, 3); if (r == -1) return 0; lua_pushboolean(lua, (r == STATUS_OK)); if (!part) return 1; lua_pushlstring(lua, part, len); return 2; } /* * Core function to change message flags. */ static int ifcore_store(lua_State *lua) { int r; if (lua_gettop(lua) != 4) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TLIGHTUSERDATA); luaL_checktype(lua, 2, LUA_TSTRING); luaL_checktype(lua, 3, LUA_TSTRING); luaL_checktype(lua, 4, LUA_TSTRING); while ((r = request_store((session *)(lua_topointer(lua, 1)), lua_tostring(lua, 2), lua_tostring(lua, 3), lua_tostring(lua, 4))) == STATUS_NONE); lua_pop(lua, 4); if (r == -1) return 0; lua_pushboolean(lua, (r == STATUS_OK)); return 1; } /* * Core function to copy messages between mailboxes. */ static int ifcore_copy(lua_State *lua) { int r; if (lua_gettop(lua) != 3) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TLIGHTUSERDATA); luaL_checktype(lua, 2, LUA_TSTRING); luaL_checktype(lua, 3, LUA_TSTRING); while ((r = request_copy((session *)(lua_topointer(lua, 1)), lua_tostring(lua, 2), lua_tostring(lua, 3))) == STATUS_NONE); lua_pop(lua, 3); if (r == -1) return 0; lua_pushboolean(lua, (r == STATUS_OK)); return 1; } /* * Core function to append messages to a mailbox. */ static int ifcore_append(lua_State *lua) { int r; if (lua_gettop(lua) != 5) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TLIGHTUSERDATA); luaL_checktype(lua, 2, LUA_TSTRING); luaL_checktype(lua, 3, LUA_TSTRING); if (lua_type(lua, 4) != LUA_TNIL) luaL_checktype(lua, 4, LUA_TSTRING); if (lua_type(lua, 5) != LUA_TNIL) luaL_checktype(lua, 5, LUA_TSTRING); while ((r = request_append((session *)(lua_topointer(lua, 1)), lua_tostring(lua, 2), lua_tostring(lua, 3), #if LUA_VERSION_NUM < 502 lua_objlen(lua, 3), #else lua_rawlen(lua, 3), #endif lua_type(lua, 4) == LUA_TSTRING ? lua_tostring(lua, 4) : NULL, lua_type(lua, 5) == LUA_TSTRING ? lua_tostring(lua, 5) : NULL)) == STATUS_NONE); lua_pop(lua, lua_gettop(lua)); if (r == -1) return 0; lua_pushboolean(lua, (r == STATUS_OK)); return 1; } /* * Core function to create a mailbox. */ static int ifcore_create(lua_State *lua) { int r; if (lua_gettop(lua) != 2) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TLIGHTUSERDATA); luaL_checktype(lua, 2, LUA_TSTRING); while ((r = request_create((session *)(lua_topointer(lua, 1)), lua_tostring(lua, 2))) == STATUS_NONE); lua_pop(lua, 2); if (r == -1) return 0; lua_pushboolean(lua, (r == STATUS_OK)); return 1; } /* * Core function to delete a mailbox. */ static int ifcore_delete(lua_State *lua) { int r; if (lua_gettop(lua) != 2) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TLIGHTUSERDATA); luaL_checktype(lua, 2, LUA_TSTRING); while ((r = request_delete((session *)(lua_topointer(lua, 1)), lua_tostring(lua, 2))) == STATUS_NONE); lua_pop(lua, 2); if (r == -1) return 0; lua_pushboolean(lua, (r == STATUS_OK)); return 1; } /* * Core function to rename a mailbox. */ static int ifcore_rename(lua_State *lua) { int r; if (lua_gettop(lua) != 3) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TLIGHTUSERDATA); luaL_checktype(lua, 2, LUA_TSTRING); luaL_checktype(lua, 3, LUA_TSTRING); while ((r = request_rename((session *)(lua_topointer(lua, 1)), lua_tostring(lua, 2), lua_tostring(lua, 3))) == STATUS_NONE); lua_pop(lua, 3); if (r == -1) return 0; lua_pushboolean(lua, (r == STATUS_OK)); return 1; } /* * Core function to subscribe a mailbox. */ static int ifcore_subscribe(lua_State *lua) { int r; if (lua_gettop(lua) != 2) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TLIGHTUSERDATA); luaL_checktype(lua, 2, LUA_TSTRING); while ((r = request_subscribe((session *)(lua_topointer(lua, 1)), lua_tostring(lua, 2))) == STATUS_NONE); lua_pop(lua, 2); if (r == -1) return 0; lua_pushboolean(lua, (r == STATUS_OK)); return 1; } /* * Core function to unsubscribe a mailbox. */ static int ifcore_unsubscribe(lua_State *lua) { int r; if (lua_gettop(lua) != 2) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TLIGHTUSERDATA); luaL_checktype(lua, 2, LUA_TSTRING); while ((r = request_unsubscribe((session *)(lua_topointer(lua, 1)), lua_tostring(lua, 2))) == STATUS_NONE); lua_pop(lua, 2); if (r == -1) return 0; lua_pushboolean(lua, (r == STATUS_OK)); return 1; } /* * Core function to go to idle state. */ static int ifcore_idle(lua_State *lua) { int r; if (lua_gettop(lua) != 1) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TLIGHTUSERDATA); while ((r = request_idle((session *)(lua_topointer(lua, 1)))) == STATUS_NONE); lua_pop(lua, 1); if (r == -1) return 0; lua_pushboolean(lua, (r == STATUS_OK)); return 1; } /* * Open imapfilter core library. */ LUALIB_API int luaopen_ifcore(lua_State *lua) { #if LUA_VERSION_NUM < 502 luaL_register(lua, "ifcore", ifcorelib); #else luaL_newlib(lua, ifcorelib); lua_setglobal(lua, "ifcore"); #endif return 1; } imapfilter-2.5.2/src/file.c0000644000175000017500000000502011757662071015357 0ustar frankiefrankie#include #include #include #include #include #include #include #include #include #include "imapfilter.h" #include "pathnames.h" extern environment env; /* * Create imapfilter's home directory. */ void create_homedir(void) { int n; char *h, *i; h = getenv("HOME"); i = getenv("IMAPFILTER_HOME"); if (i == NULL) n = strlen(h ? h : "") + strlen(h ? "/" : "") + strlen(".imapfilter"); else n = strlen(i); if (env.pathmax != -1 && n > env.pathmax) fatal(ERROR_PATHNAME, "pathname limit %ld exceeded: %d\n", env.pathmax, n); env.home = (char *)xmalloc((n + 1) * sizeof(char)); if (i == NULL) snprintf(env.home, n + 1, "%s%s%s", h ? h : "", h ? "/" : "", ".imapfilter"); else snprintf(env.home, n + 1, "%s", i); if (!exists_dir(env.home)) { if (mkdir(env.home, S_IRUSR | S_IWUSR | S_IXUSR)) error("could not create directory %s; %s\n", env.home, strerror(errno)); } } /* * Check if a file exists. */ int exists_file(char *fname) { struct stat fs; if (access(fname, F_OK)) return 0; stat(fname, &fs); if (!S_ISREG(fs.st_mode)) { error("file %s not a regular file\n", fname); return -1; } return 1; } /* * Check if a directory exists. */ int exists_dir(char *dname) { struct stat ds; if (access(dname, F_OK)) return 0; stat(dname, &ds); if (!S_ISDIR(ds.st_mode)) { error("file %s not a directory\n", dname); return -1; } return 1; } /* * Create a file with the specified permissions. */ int create_file(char *fname, mode_t mode) { int fd; fd = 0; if (!exists_file(fname)) { fd = open(fname, O_CREAT | O_WRONLY | O_TRUNC, mode); if (fd == -1) { error("could not create file %s; %s\n", fname, strerror(errno)); return -1; } close(fd); } return 0; } /* * Get the system's maximum number of bytes in a pathname. */ int get_pathmax(void) { int n; errno = 0; n = pathconf("/", _PC_PATH_MAX); if (n == -1 && errno != 0) { error("getting PATH_MAX limit; %s\n", strerror(errno)); return -1; } env.pathmax = n; return 0; } /* * Get the path of a file inside the configuration directory. */ char * get_filepath(char *fname) { int n; char *fp; n = strlen(env.home) + strlen("/") + strlen(fname); if (env.pathmax != -1 && n > env.pathmax) fatal(ERROR_PATHNAME, "pathname limit %ld exceeded: %d\n", env.pathmax, n); fp = (char *)xmalloc((n + 1) * sizeof(char)); snprintf(fp, n + 1, "%s/%s", env.home, fname); return fp; } imapfilter-2.5.2/src/request.c0000644000175000017500000003762611757662071016151 0ustar frankiefrankie#include #include #include #include #include #include "imapfilter.h" #include "session.h" #include "buffer.h" extern options opts; buffer obuf; /* Output buffer. */ static int tag = 0x1000; /* Every IMAP command is prefixed with a * unique [:alnum:] string. */ int send_request(session *ssn, const char *fmt,...); int send_continuation(session *ssn, const char *data, size_t len); #define CHECK(F) \ switch ((F)) { \ case -1: \ goto fail; \ case STATUS_BYE: \ goto abort; \ } #define TRY(F) \ switch ((F)) { \ case -1: \ if ((!strcasecmp(get_option_string("recover"), "all") || \ !strcasecmp(get_option_string("recover"), "errors")) && \ request_login(&ssn, NULL, NULL, NULL, NULL, NULL) != -1) \ return STATUS_NONE; \ return -1; \ case STATUS_BYE: \ close_connection(ssn); \ if (!strcasecmp(get_option_string("recover"), "all")) { \ if (request_login(&ssn, NULL, NULL, NULL, NULL, \ NULL) != -1) \ return STATUS_NONE; \ } else \ session_destroy(ssn); \ return -1; \ } /* * Sends to server data; a command. */ int send_request(session *ssn, const char *fmt,...) { int n; va_list args; int t = tag; if (ssn->socket == -1) return -1; buffer_reset(&obuf); obuf.len = snprintf(obuf.data, obuf.size + 1, "%04X ", tag); va_start(args, fmt); n = vsnprintf(obuf.data + obuf.len, obuf.size - obuf.len - strlen("\r\n") + 1, fmt, args); if (n > (int)obuf.size) { buffer_check(&obuf, n); vsnprintf(obuf.data + obuf.len, obuf.size - obuf.len - strlen("\r\n") + 1, fmt, args); } obuf.len = strlen(obuf.data); va_end(args); snprintf(obuf.data + obuf.len, obuf.size - obuf.len + 1, "\r\n"); obuf.len = strlen(obuf.data); if (!strncasecmp(fmt, "LOGIN", strlen("LOGIN"))) { debug("sending command (%d):\n\n%.*s*\r\n\n", ssn->socket, obuf.len - strlen(ssn->password) - strlen("\"\"\r\n"), obuf.data); verbose("C (%d): %.*s*\r\n", ssn->socket, obuf.len - strlen(ssn->password) - strlen("\"\"\r\n"), obuf.data); } else { debug("sending command (%d):\n\n%s\n", ssn->socket, obuf.data); verbose("C (%d): %s", ssn->socket, obuf.data); } if (socket_write(ssn, obuf.data, obuf.len) == -1) return -1; if (tag == 0xFFFF) /* Tag always between 0x1000 and 0xFFFF. */ tag = 0x0FFF; tag++; return t; } /* * Sends a response to a command continuation request. */ int send_continuation(session *ssn, const char *data, size_t len) { if (ssn->socket == -1) return -1; if (socket_write(ssn, data, len) == -1 || socket_write(ssn, "\r\n", strlen("\r\n")) == -1) return -1; if (opts.debug) { unsigned int i; debug("sending continuation data (%d):\n\n", ssn->socket); for (i = 0; i < len; i++) debugc(data[i]); debug("\r\n\n"); } return 0; } /* * Reset any inactivity autologout timer on the server. */ int request_noop(session *ssn) { int t, r; TRY(t = send_request(ssn, "NOOP")); TRY(r = response_generic(ssn, t)); return r; } /* * Connect to the server, login to the IMAP server, get it's capabilities, get * the namespace of the mailboxes. */ int request_login(session **ssnptr, const char *server, const char *port, const char *ssl, const char *user, const char *pass) { int t, r, rg = -1, rl = -1; session *ssn = *ssnptr; if (*ssnptr && (*ssnptr)->socket != -1) return STATUS_PREAUTH; if (!*ssnptr) { ssn = *ssnptr = session_new(); ssn->server = server; ssn->port = port; ssn->username = user; ssn->password = pass; if ((!strncasecmp(ssl, "tls1", 4) || !strncasecmp(ssl, "ssl3", 4) || !strncasecmp(ssl, "ssl2", 4))) ssn->sslproto = ssl; } else { debug("recovering connection: %s://%s@%s:%s/%s\n", ssn->sslproto ?"imaps" : "imap", ssn->username, ssn->server, ssn->port, ssn->selected ? ssn->selected : ""); } if (open_connection(ssn) == -1) goto fail; CHECK(rg = response_greeting(ssn)); if (opts.debug) { CHECK(t = send_request(ssn, "NOOP")); CHECK(response_generic(ssn, t)); } CHECK(t = send_request(ssn, "CAPABILITY")); CHECK(response_capability(ssn, t)); if (!ssn->sslproto && ssn->capabilities & CAPABILITY_STARTTLS && get_option_boolean("starttls")) { CHECK(t = send_request(ssn, "STARTTLS")); CHECK(r = response_generic(ssn, t)); if (r == STATUS_OK) { if (open_secure_connection(ssn) == -1) goto fail; CHECK(t = send_request(ssn, "CAPABILITY")); CHECK(response_capability(ssn, t)); } } if (rg != STATUS_PREAUTH) { if (ssn->capabilities & CAPABILITY_CRAMMD5 && get_option_boolean("crammd5")) { unsigned char *in, *out; CHECK(t = send_request(ssn, "AUTHENTICATE CRAM-MD5")); CHECK(r = response_authenticate(ssn, t, &in)); if (r == STATUS_CONTINUE) { if ((out = auth_cram_md5(user, pass, in)) == NULL) goto abort; CHECK(send_continuation(ssn, (char *)(out), strlen((char *)(out)))); xfree(out); CHECK(rl = response_generic(ssn, t)); } else goto abort; } if (rl != STATUS_OK) { CHECK(t = send_request(ssn, "LOGIN \"%s\" \"%s\"", ssn->username, ssn->password)); CHECK(rl = response_generic(ssn, t)); } if (rl == STATUS_NO) { error("username %s or password rejected at %s\n", ssn->username, ssn->server); close_connection(ssn); session_destroy(ssn); return STATUS_NO; } } else { rl = STATUS_PREAUTH; } CHECK(t = send_request(ssn, "CAPABILITY")); CHECK(response_capability(ssn, t)); if (!ssn->ns.delim && ssn->capabilities & CAPABILITY_NAMESPACE && get_option_boolean("namespace")) { CHECK(t = send_request(ssn, "NAMESPACE")); CHECK(response_namespace(ssn, t)); } if (ssn->selected) { CHECK(t = send_request(ssn, "SELECT \"%s\"", ssn->selected)); CHECK(response_select(ssn, t)); } return rl; abort: close_connection(ssn); fail: session_destroy(ssn); return -1; } /* * Logout from the IMAP server and disconnect from the server. */ int request_logout(session *ssn) { if (response_generic(ssn, send_request(ssn, "LOGOUT")) == -1) { session_destroy(ssn); } else { close_connection(ssn); session_destroy(ssn); } return STATUS_OK; } /* * Get mailbox's status. */ int request_status(session *ssn, const char *mbox, unsigned int *exists, unsigned int *recent, unsigned int *unseen, unsigned int *uidnext) { int t, r; const char *m; m = apply_namespace(mbox, ssn->ns.prefix, ssn->ns.delim); if (ssn->protocol == PROTOCOL_IMAP4REV1) { TRY(t = send_request(ssn, "STATUS \"%s\" (MESSAGES RECENT UNSEEN UIDNEXT)", m)); TRY(r = response_status(ssn, t, exists, recent, unseen, uidnext)); } else { TRY(t = send_request(ssn, "EXAMINE \"%s\"", m)); TRY(r = response_examine(ssn, t, exists, recent)); } return r; } /* * Open mailbox in read-write mode. */ int request_select(session *ssn, const char *mbox) { int t, r; const char *m; m = apply_namespace(mbox, ssn->ns.prefix, ssn->ns.delim); TRY(t = send_request(ssn, "SELECT \"%s\"", m)); TRY(r = response_select(ssn, t)); if (r == STATUS_OK) ssn->selected = mbox; return r; } /* * Close examined/selected mailbox. */ int request_close(session *ssn) { int t, r; TRY(t = send_request(ssn, "CLOSE")); TRY(r = response_generic(ssn, t)); if (r == STATUS_OK && ssn->selected) ssn->selected = NULL; return r; } /* * Remove all messages marked for deletion from selected mailbox. */ int request_expunge(session *ssn) { int t, r; TRY(t = send_request(ssn, "EXPUNGE")); TRY(r = response_generic(ssn, t)); return r; } /* * List available mailboxes. */ int request_list(session *ssn, const char *refer, const char *name, char **mboxs, char **folders) { int t, r; const char *n; n = apply_namespace(name, ssn->ns.prefix, ssn->ns.delim); TRY(t = send_request(ssn, "LIST \"%s\" \"%s\"", refer, n)); TRY(r = response_list(ssn, t, mboxs, folders)); return r; } /* * List subscribed mailboxes. */ int request_lsub(session *ssn, const char *refer, const char *name, char **mboxs, char **folders) { int t, r; const char *n; n = apply_namespace(name, ssn->ns.prefix, ssn->ns.delim); TRY(t = send_request(ssn, "LSUB \"%s\" \"%s\"", refer, n)); TRY(r = response_list(ssn, t, mboxs, folders)); return r; } /* * Search selected mailbox according to the supplied search criteria. */ int request_search(session *ssn, const char *criteria, const char *charset, char **mesgs) { int t, r; if (charset != NULL && *charset != '\0') { TRY(t = send_request(ssn, "UID SEARCH CHARSET \"%s\" %s", charset, criteria)); } else { TRY(t = send_request(ssn, "UID SEARCH %s", criteria)); } TRY(r = response_search(ssn, t, mesgs)); return r; } /* * Fetch the FLAGS, INTERNALDATE and RFC822.SIZE of the messages. */ int request_fetchfast(session *ssn, const char *mesg, char **flags, char **date, char **size) { int t, r; TRY(t = send_request(ssn, "UID FETCH %s FAST", mesg)); TRY(r = response_fetchfast(ssn, t, flags, date, size)); return r; } /* * Fetch the FLAGS of the messages. */ int request_fetchflags(session *ssn, const char *mesg, char **flags) { int t, r; TRY(t = send_request(ssn, "UID FETCH %s FLAGS", mesg)); TRY(r = response_fetchflags(ssn, t, flags)); return r; } /* * Fetch the INTERNALDATE of the messages. */ int request_fetchdate(session *ssn, const char *mesg, char **date) { int t, r; TRY(t = send_request(ssn, "UID FETCH %s INTERNALDATE", mesg)); TRY(r = response_fetchdate(ssn, t, date)); return r; } /* * Fetch the RFC822.SIZE of the messages. */ int request_fetchsize(session *ssn, const char *mesg, char **size) { int t, r; TRY(t = send_request(ssn, "UID FETCH %s RFC822.SIZE", mesg)); TRY(r = response_fetchsize(ssn, t, size)); return r; } /* * Fetch the body structure, ie. BODYSTRUCTURE, of the messages. */ int request_fetchstructure(session *ssn, const char *mesg, char **structure) { int t, r; TRY(t = send_request(ssn, "UID FETCH %s BODYSTRUCTURE", mesg)); TRY(r = response_fetchstructure(ssn, t, structure)); return r; } /* * Fetch the header, ie. BODY[HEADER], of the messages. */ int request_fetchheader(session *ssn, const char *mesg, char **header, size_t *len) { int t, r; TRY(t = send_request(ssn, "UID FETCH %s BODY.PEEK[HEADER]", mesg)); TRY(r = response_fetchbody(ssn, t, header, len)); return r; } /* * Fetch the text, ie. BODY[TEXT], of the messages. */ int request_fetchtext(session *ssn, const char *mesg, char **text, size_t *len) { int t, r; TRY(t = send_request(ssn, "UID FETCH %s BODY.PEEK[TEXT]", mesg)); TRY(r = response_fetchbody(ssn, t, text, len)); return r; } /* * Fetch the specified header fields, ie. BODY[HEADER.FIELDS ()], of * the messages. */ int request_fetchfields(session *ssn, const char *mesg, const char *headerfields, char **fields, size_t *len) { int t, r; { int n = strlen("BODY.PEEK[HEADER.FIELDS ()]") + strlen(headerfields) + 1; char f[n]; snprintf(f, n, "%s%s%s", "BODY.PEEK[HEADER.FIELDS (", headerfields, ")]"); TRY(t = send_request(ssn, "UID FETCH %s %s", mesg, f)); } TRY(r = response_fetchbody(ssn, t, fields, len)); return r; } /* * Fetch the specified message part, ie. BODY[], of the * messages. */ int request_fetchpart(session *ssn, const char *mesg, const char *part, char **bodypart, size_t *len) { int t, r; { int n = strlen("BODY.PEEK[]") + strlen(part) + 1; char f[n]; snprintf(f, n, "%s%s%s", "BODY.PEEK[", part, "]"); TRY(t = send_request(ssn, "UID FETCH %s %s", mesg, f)); } TRY(r = response_fetchbody(ssn, t, bodypart, len)); return r; } /* * Add, remove or replace the specified flags of the messages. */ int request_store(session *ssn, const char *mesg, const char *mode, const char *flags) { int t, r; TRY(t = send_request(ssn, "UID STORE %s %sFLAGS.SILENT (%s)", mesg, (!strncasecmp(mode, "add", 3) ? "+" : !strncasecmp(mode, "remove", 6) ? "-" : ""), flags)); TRY(r = response_generic(ssn, t)); if (xstrcasestr(flags, "\\Deleted") && get_option_boolean("expunge")) { TRY(t = send_request(ssn, "EXPUNGE")); TRY(response_generic(ssn, t)); } return r; } /* * Copy the specified messages to another mailbox. */ int request_copy(session *ssn, const char *mesg, const char *mbox) { int t, r; const char *m; m = apply_namespace(mbox, ssn->ns.prefix, ssn->ns.delim); TRY(t = send_request(ssn, "UID COPY %s \"%s\"", mesg, m)); TRY(r = response_generic(ssn, t)); if (r == STATUS_TRYCREATE) { TRY(t = send_request(ssn, "CREATE \"%s\"", m)); TRY(response_generic(ssn, t)); if (get_option_boolean("subscribe")) { TRY(t = send_request(ssn, "SUBSCRIBE \"%s\"", m)); TRY(response_generic(ssn, t)); } TRY(t = send_request(ssn, "UID COPY %s \"%s\"", mesg, m)); TRY(r = response_generic(ssn, t)); } return r; } /* * Append supplied message to the specified mailbox. */ int request_append(session *ssn, const char *mbox, const char *mesg, size_t mesglen, const char *flags, const char *date) { int t, r; const char *m; m = apply_namespace(mbox, ssn->ns.prefix, ssn->ns.delim); TRY(t = send_request(ssn, "APPEND \"%s\"%s%s%s%s%s%s {%d}", m, (flags ? " (" : ""), (flags ? flags : ""), (flags ? ")" : ""), (date ? " \"" : ""), (date ? date : ""), (date ? "\"" : ""), mesglen)); TRY(r = response_continuation(ssn, t)); if (r == STATUS_TRYCREATE) { TRY(t = send_request(ssn, "CREATE \"%s\"", m)); TRY(response_generic(ssn, t)); if (get_option_boolean("subscribe")) { TRY(t = send_request(ssn, "SUBSCRIBE \"%s\"", m)); TRY(response_generic(ssn, t)); } TRY(t = send_request(ssn, "APPEND \"%s\"%s%s%s%s%s%s {%d}", m, (flags ? " (" : ""), (flags ? flags : ""), (flags ? ")" : ""), (date ? " \"" : ""), (date ? date : ""), (date ? "\"" : ""), mesglen)); TRY(r = response_continuation(ssn, t)); } if (r == STATUS_CONTINUE) { TRY(send_continuation(ssn, mesg, mesglen)); TRY(r = response_generic(ssn, t)); } return r; } /* * Create the specified mailbox. */ int request_create(session *ssn, const char *mbox) { int t, r; const char *m; m = apply_namespace(mbox, ssn->ns.prefix, ssn->ns.delim); TRY(t = send_request(ssn, "CREATE \"%s\"", m)); TRY(r = response_generic(ssn, t)); return r; } /* * Delete the specified mailbox. */ int request_delete(session *ssn, const char *mbox) { int t, r; const char *m; m = apply_namespace(mbox, ssn->ns.prefix, ssn->ns.delim); TRY(t = send_request(ssn, "DELETE \"%s\"", m)); TRY(r = response_generic(ssn, t)); return r; } /* * Rename a mailbox. */ int request_rename(session *ssn, const char *oldmbox, const char *newmbox) { int t, r; char *o, *n; o = xstrdup(apply_namespace(oldmbox, ssn->ns.prefix, ssn->ns.delim)); n = xstrdup(apply_namespace(newmbox, ssn->ns.prefix, ssn->ns.delim)); TRY(t = send_request(ssn, "RENAME \"%s\" \"%s\"", o, n)); TRY(r = response_generic(ssn, t)); return r; } /* * Subscribe the specified mailbox. */ int request_subscribe(session *ssn, const char *mbox) { int t, r; const char *m; m = apply_namespace(mbox, ssn->ns.prefix, ssn->ns.delim); TRY(t = send_request(ssn, "SUBSCRIBE \"%s\"", m)); TRY(r = response_generic(ssn, t)); return r; } /* * Unsubscribe the specified mailbox. */ int request_unsubscribe(session *ssn, const char *mbox) { int t, r; const char *m; m = apply_namespace(mbox, ssn->ns.prefix, ssn->ns.delim); TRY(t = send_request(ssn, "UNSUBSCRIBE \"%s\"", m)); TRY(r = response_generic(ssn, t)); return r; } int request_idle(session *ssn) { int t, r, ri; if (!(ssn->capabilities & CAPABILITY_IDLE)) return -1; do { ri = 0; TRY(t = send_request(ssn, "IDLE")); TRY(r = response_continuation(ssn, t)); if (r == STATUS_CONTINUE) { TRY(ri = response_idle(ssn, t)); TRY(send_continuation(ssn, "DONE", strlen("DONE"))); TRY(r = response_generic(ssn, t)); } } while (ri == STATUS_TIMEOUT); return r; } imapfilter-2.5.2/src/list.c0000644000175000017500000000142311757662071015416 0ustar frankiefrankie#include #include "imapfilter.h" #include "list.h" /* * Add a new element at the end of the list. */ list * list_append(list *lst, void *data) { list *l, *nl; nl = (list *)xmalloc(sizeof(list)); nl->data = data; nl->prev = nl->next = NULL; if (lst != NULL) { for (l = lst; l->next != NULL; l = l->next); l->next = nl; nl->prev = l; return lst; } else { return nl; } } /* * Remove an element from the list. */ list * list_remove(list *lst, void *data) { list *l; if (!lst) return NULL; l = lst; while (l != NULL) { if (l->data != data) l = l->next; else { if (l->prev) l->prev->next = l->next; if (l->next) l->next->prev = l->prev; if (lst == l) lst = lst->next; xfree(l); break; } } return lst; } imapfilter-2.5.2/src/list.h0000644000175000017500000000034311536341114015406 0ustar frankiefrankie#ifndef LIST_H #define LIST_H typedef struct list { void *data; struct list *next, *prev; } list; /* list.h */ list *list_append(list *lst, void *data); list *list_remove(list *lst, void *data); #endif /* LIST_H */ imapfilter-2.5.2/src/misc.c0000644000175000017500000000204711536341114015364 0ustar frankiefrankie#include #include #include #include "imapfilter.h" /* * An implementation of strstr() with case-insensitivity. */ const char * xstrcasestr(const char *haystack, const char *needle) { const char *h, *n, *c; size_t hl, nl; c = haystack; n = needle; hl = strlen(haystack); nl = strlen(needle); while (hl >= nl) { while (tolower((int)(*c)) != tolower((int)(*needle))) { c++; hl--; if (hl < nl) return NULL; } h = c; n = needle; while (tolower((int)(*h)) == tolower((int)(*n))) { h++; n++; if (*n == '\0') return c; } c++; hl--; } return NULL; } /* * Copies at most size characters from the string pointed by src to the array * pointed by dest, always NULL terminating (unless size == 0). Returns * pointer to dest. */ char * xstrncpy(char *dst, const char *src, size_t len) { char *d; const char *s; size_t n; d = dst; s = src; n = len; while (n != 0) { if ((*d++ = *s++) == '\0') break; n--; } if (n == 0 && len != 0) *d = '\0'; return dst; } imapfilter-2.5.2/src/pcre.c0000644000175000017500000001351611757662071015402 0ustar frankiefrankie#include #include #include #include #include #include "imapfilter.h" static int ifre_flags(lua_State *lua); static int ifre_compile(lua_State *lua); static int ifre_exec(lua_State *lua); static int ifre_free(lua_State *lua); /* Lua imapfilter library of PCRE related functions. */ static const luaL_Reg ifrelib[] = { { "flags", ifre_flags }, { "compile", ifre_compile }, { "exec", ifre_exec }, { "free", ifre_free }, { NULL, NULL } }; /* * Return PCRE available compile and exec flags. */ static int ifre_flags(lua_State *lua) { if (lua_gettop(lua) != 0) luaL_error(lua, "wrong number of arguments"); lua_newtable(lua); #ifdef PCRE_CASELESS set_table_number("CASELESS", PCRE_CASELESS); #endif #ifdef PCRE_MULTILINE set_table_number("MULTILINE", PCRE_MULTILINE); #endif #ifdef PCRE_DOTALL set_table_number("DOTALL", PCRE_DOTALL); #endif #ifdef PCRE_EXTENDED set_table_number("EXTENDED", PCRE_EXTENDED); #endif #ifdef PCRE_ANCHORED set_table_number("ANCHORED", PCRE_ANCHORED); #endif #ifdef PCRE_DOLLAR_ENDONLY set_table_number("DOLLAR_ENDONLY", PCRE_DOLLAR_ENDONLY); #endif #ifdef PCRE_EXTRA set_table_number("EXTRA", PCRE_EXTRA); #endif #ifdef PCRE_NOTBOL set_table_number("NOTBOL", PCRE_NOTBOL); #endif #ifdef PCRE_NOTEOL set_table_number("NOTEOL", PCRE_NOTEOL); #endif #ifdef PCRE_UNGREEDY set_table_number("UNGREEDY", PCRE_UNGREEDY); #endif #ifdef PCRE_NOTEMPTY set_table_number("NOTEMPTY", PCRE_NOTEMPTY); #endif #ifdef PCRE_UTF8 set_table_number("UTF8", PCRE_UTF8); #endif #ifdef PCRE_NO_AUTO_CAPTURE set_table_number("NO_AUTO_CAPTURE", PCRE_NO_AUTO_CAPTURE); #endif #ifdef PCRE_NO_UTF8_CHECK set_table_number("NO_UTF8_CHECK", PCRE_NO_UTF8_CHECK); #endif #ifdef PCRE_FIRSTLINE set_table_number("FIRSTLINE", PCRE_FIRSTLINE); #endif #ifdef PCRE_AUTO_CALLOUT set_table_number("AUTO_CALLOUT", PCRE_AUTO_CALLOUT); #endif #ifdef PCRE_PARTIAL set_table_number("PARTIAL", PCRE_PARTIAL); #endif #ifdef PCRE_DFA_SHORTEST set_table_number("DFA_SHORTEST", PCRE_DFA_SHORTEST); #endif #ifdef PCRE_DFA_RESTART set_table_number("DFA_RESTART", PCRE_DFA_RESTART); #endif #ifdef PCRE_FIRSTLINE set_table_number("FIRSTLINE", PCRE_FIRSTLINE); #endif #ifdef PCRE_DUPNAMES set_table_number("DUPNAMES", PCRE_DUPNAMES); #endif #ifdef PCRE_NEWLINE_CR set_table_number("NEWLINE_CR)", PCRE_NEWLINE_CR); #endif #ifdef PCRE_NEWLINE_LF set_table_number("NEWLINE_LF", PCRE_NEWLINE_LF); #endif #ifdef PCRE_NEWLINE_CRLF set_table_number("NEWLINE_CRLF", PCRE_NEWLINE_CRLF); #endif #ifdef PCRE_NEWLINE_ANY set_table_number("NEWLINE_ANY", PCRE_NEWLINE_ANY); #endif #ifdef PCRE_NEWLINE_ANYCRLF set_table_number("NEWLINE_ANYCRLF", PCRE_NEWLINE_ANYCRLF); #endif #ifdef PCRE_BSR_ANYCRLF set_table_number("PCRE_BSR_ANYCRLF", PCRE_BSR_ANYCRLF); #endif #ifdef PCRE_BSR_UNICODE set_table_number("PCRE_BSR_UNICODE", PCRE_BSR_UNICODE); #endif #ifdef PCRE_JAVASCRIPT_COMPAT set_table_number("PCRE_JAVASCRIPT_COMPAT", PCRE_JAVASCRIPT_COMPAT); #endif #ifdef PCRE_NO_START_OPTIMIZE set_table_number("PCRE_NO_START_OPTIMIZE", PCRE_NO_START_OPTIMIZE); #endif #ifdef PCRE_NO_START_OPTIMISE set_table_number("PCRE_NO_START_OPTIMISE", PCRE_NO_START_OPTIMISE); #endif #ifdef PCRE_PARTIAL_HARD set_table_number("PCRE_PARTIAL_HARD", PCRE_PARTIAL_HARD); #endif #ifdef PCRE_NOTEMPTY_ATSTART set_table_number("PCRE_NOTEMPTY_ATSTART", PCRE_NOTEMPTY_ATSTART); #endif #ifdef PCRE_UCP set_table_number("PCRE_UCP", PCRE_UCP); #endif return 1; } /* * Lua implementation of the PCRE compile function. */ static int ifre_compile(lua_State *lua) { pcre **re; const char *error; int erroffset; if (lua_gettop(lua) != 2) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TSTRING); luaL_checktype(lua, 2, LUA_TNUMBER); re = (pcre **)(lua_newuserdata(lua, sizeof(pcre *))); *re = pcre_compile(lua_tostring(lua, 1), lua_tonumber(lua, 2), &error, &erroffset, NULL); if (*re == NULL) { fprintf(stderr, "RE failed at offset %d: %s\n", erroffset, error); lua_pop(lua, 1); } lua_remove(lua, 1); lua_remove(lua, 1); lua_pushboolean(lua, (*re != NULL)); lua_insert(lua, 1); return (*re != NULL ? 2 : 1); } /* * Lua implementation of the PCRE exec function. */ static int ifre_exec(lua_State *lua) { int i, n; pcre *re; int ovecsize; int *ovector; if (lua_gettop(lua) != 3) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TUSERDATA); luaL_checktype(lua, 2, LUA_TSTRING); luaL_checktype(lua, 3, LUA_TNUMBER); re = *(pcre **)(lua_touserdata(lua, 1)); pcre_fullinfo(re, NULL, PCRE_INFO_CAPTURECOUNT, &ovecsize); ovector = (int *)xmalloc(sizeof(int) * (ovecsize + 1) * 3); for (i = 0; i <= ovecsize; i++) ovector[2 * i] = ovector[2 * i + 1] = -1; n = pcre_exec(re, NULL, lua_tostring(lua, 2), #if LUA_VERSION_NUM < 502 lua_objlen(lua, 2), #else lua_rawlen(lua, 2), #endif 0, lua_tonumber(lua, 3), ovector, (ovecsize + 1) * 3); if (n > 0) for (i = 1; i < n; i++) if (ovector[2 * i] != -1 && ovector[2 * i + 1] != -1) lua_pushlstring(lua, lua_tostring(lua, 2) + ovector[2 * i], ovector[2 * i + 1] - ovector[2 * i]); else lua_pushnil(lua); xfree(ovector); lua_remove(lua, 1); lua_remove(lua, 1); lua_remove(lua, 1); lua_pushboolean(lua, (n > 0)); lua_insert(lua, 1); return (n > 0 ? n : 1); } /* * Lua implementation of a PCRE free function. */ static int ifre_free(lua_State *lua) { pcre *re; if (lua_gettop(lua) != 1) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TUSERDATA); re = *(pcre **)(lua_touserdata(lua, 1)); xfree(re); lua_remove(lua, 1); return 0; } /* * Open imapfilter library of PCRE related functions. */ LUALIB_API int luaopen_ifre(lua_State *lua) { #if LUA_VERSION_NUM < 502 luaL_register(lua, "ifre", ifrelib); #else luaL_newlib(lua, ifrelib); lua_setglobal(lua, "ifre"); #endif return 1; } imapfilter-2.5.2/src/buffer.c0000644000175000017500000000134111536341114015676 0ustar frankiefrankie#include #include "imapfilter.h" #include "buffer.h" /* * Initialize buffer. */ void buffer_init(buffer *buf, size_t n) { buf->data = (char *)xmalloc((n + 1) * sizeof(char)); *buf->data = '\0'; buf->len = 0; buf->size = n; } /* * Free allocated memory of buffer. */ void buffer_free(buffer *buf) { if (!buf->data) return; xfree(buf->data); buf->data = NULL; } /* * Reset buffer. */ void buffer_reset(buffer *buf) { *buf->data = '\0'; buf->len = 0; } /* * Check if the buffer has enough space to store data and reallocate memory if * needed. */ void buffer_check(buffer *buf, size_t n) { while (n > buf->size) { buf->size *= 2; buf->data = (char *)xrealloc(buf->data, buf->size + 1); } } imapfilter-2.5.2/src/buffer.h0000644000175000017500000000067011536341114015707 0ustar frankiefrankie#ifndef BUFFER_H #define BUFFER_H #include /* Temporary buffer. */ typedef struct buffer { char *data; /* Text or binary data. */ size_t len; /* Length of text or binary data. */ size_t size; /* Maximum size of data. */ } buffer; /* buffer.c */ void buffer_init(buffer *buf, size_t n); void buffer_free(buffer *buf); void buffer_reset(buffer *buf); void buffer_check(buffer *buf, size_t n); #endif /* BUFFER_H */ imapfilter-2.5.2/src/system.c0000644000175000017500000001257311757662071015777 0ustar frankiefrankie#include #include #include #include #include #include #include #include #include #include #include #include "imapfilter.h" static int ifsys_echo(lua_State *lua); static int ifsys_noecho(lua_State *lua); static int ifsys_popen(lua_State *lua); static int ifsys_pclose(lua_State *lua); static int ifsys_read(lua_State *lua); static int ifsys_write(lua_State *lua); static int ifsys_sleep(lua_State *lua); static int ifsys_daemon(lua_State *lua); /* Lua imapfilter library of system's functions. */ static const luaL_Reg ifsyslib[] = { { "echo", ifsys_echo }, { "noecho", ifsys_noecho }, { "popen", ifsys_popen }, { "pclose", ifsys_pclose }, { "read", ifsys_read }, { "write", ifsys_write }, { "sleep", ifsys_sleep }, { "daemon", ifsys_daemon }, { NULL, NULL } }; /* * Enable character echoing. */ static int ifsys_echo(lua_State *lua) { struct termios t; if (lua_gettop(lua) != 0) luaL_error(lua, "wrong number of arguments"); if (tcgetattr(fileno(stdin), &t)) { fprintf(stderr, "getting term attributs; %s\n", strerror(errno)); return 1; } t.c_lflag |= (ECHO); t.c_lflag &= ~(ECHONL); if (tcsetattr(fileno(stdin), TCSAFLUSH, &t)) { fprintf(stderr, "setting term attributes; %s\n", strerror(errno)); return 1; } return 0; } /* * Disable character echoing. */ static int ifsys_noecho(lua_State *lua) { struct termios t; if (lua_gettop(lua) != 0) luaL_error(lua, "wrong number of arguments"); if (tcgetattr(fileno(stdin), &t)) { fprintf(stderr, "getting term attributs; %s\n", strerror(errno)); return 1; } t.c_lflag &= ~(ECHO); t.c_lflag |= (ECHONL); if (tcsetattr(fileno(stdin), TCSAFLUSH, &t)) { fprintf(stderr, "setting term attributes; %s\n", strerror(errno)); return 1; } return 0; } /* * Lua implementation of the POSIX popen() function. */ static int ifsys_popen(lua_State *lua) { FILE **fp; if (lua_gettop(lua) != 2) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TSTRING); luaL_checktype(lua, 2, LUA_TSTRING); fp = (FILE **) lua_newuserdata(lua, sizeof(FILE *)); *fp = NULL; *fp = popen(lua_tostring(lua, 1), lua_tostring(lua, 2)); lua_remove(lua, 1); lua_remove(lua, 1); return (*fp == NULL ? 0 : 1); } /* * Lua implementation of the POSIX pclose() function. */ static int ifsys_pclose(lua_State *lua) { lua_Number r; FILE *fp; if (lua_gettop(lua) != 1) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TUSERDATA); fp = *(FILE **) (lua_touserdata(lua, 1)); r = (lua_Number) (pclose(fp) >> 8); lua_pop(lua, 1); if (r == -1) return 0; fp = NULL; lua_pushnumber(lua, r); return 1; } /* * Reads a line from a file stream. */ static int ifsys_read(lua_State *lua) { FILE *fp; luaL_Buffer b; char *c; size_t n; if (lua_gettop(lua) != 1) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TUSERDATA); fp = *(FILE **) (lua_touserdata(lua, 1)); lua_pop(lua, 1); luaL_buffinit(lua, &b); for (;;) { c = luaL_prepbuffer(&b); if (fgets(c, LUAL_BUFFERSIZE, fp) == NULL && feof(fp)) { luaL_pushresult(&b); return ( #if LUA_VERSION_NUM < 502 lua_objlen(lua, -1) #else lua_rawlen(lua, -1) #endif > 0); } n = strlen(c); if (c[n - 1] != '\n') luaL_addsize(&b, n); else { luaL_addsize(&b, n); luaL_pushresult(&b); return 1; } } } /* * Writes a string to a file stream. */ static int ifsys_write(lua_State *lua) { size_t n; if (lua_gettop(lua) != 2) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TUSERDATA); luaL_checktype(lua, 2, LUA_TSTRING); n = fwrite(lua_tostring(lua, 2), sizeof(char), strlen(lua_tostring(lua, 2)), *(FILE **) (lua_touserdata(lua, 1))); lua_pop(lua, 2); lua_pushboolean(lua, (n != 0)); return 1; } /* * Lua implementation of the POSIX sleep() function. */ static int ifsys_sleep(lua_State *lua) { if (lua_gettop(lua) != 1) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TNUMBER); lua_pushnumber(lua, (lua_Number) (sleep) ((unsigned int)(lua_tonumber(lua, 1)))); lua_remove(lua, 1); return 1; } /* * Lua implementation of the BSD daemon() function. */ static int ifsys_daemon(lua_State *lua) { if (lua_gettop(lua) != 2) luaL_error(lua, "wrong number of arguments"); luaL_checktype(lua, 1, LUA_TBOOLEAN); luaL_checktype(lua, 2, LUA_TBOOLEAN); switch (fork()) { case -1: fprintf(stderr, "forking; %s\n", strerror(errno)); lua_pushboolean(lua, 0); return 1; /* NOTREACHED */ break; case 0: break; default: exit(0); /* NOTREACHED */ break; } if (setsid() == -1) { fprintf(stderr, "creating session; %s\n", strerror(errno)); } if (lua_toboolean(lua, 1) == 0) chdir("/"); if (lua_toboolean(lua, 2) == 0) { close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); if (open("/dev/null", O_RDWR) == -1 || dup(STDIN_FILENO) == -1 || dup(STDIN_FILENO) == -1) fprintf(stderr, "duplicating file descriptors; %s\n", strerror(errno)); } return 0; } /* * Open imapfilter library of system's functions. */ LUALIB_API int luaopen_ifsys(lua_State *lua) { #if LUA_VERSION_NUM < 502 luaL_register(lua, "ifsys", ifsyslib); #else luaL_newlib(lua, ifsyslib); lua_setglobal(lua, "ifsys"); #endif return 1; } imapfilter-2.5.2/src/namespace.c0000644000175000017500000001410111757662071016374 0ustar frankiefrankie#include #include #include #include #include "imapfilter.h" #include "buffer.h" buffer nbuf; /* Namespace buffer. */ buffer cbuf; /* Conversion buffer. */ const char *apply_conversion(const char *mbox); const char *reverse_conversion(const char *mbox); /* * Convert the names of personal mailboxes, using the namespace specified * by the mail server, from internal to mail server format. */ const char * apply_namespace(const char *mbox, char *prefix, char delim) { int n; char *c; const char *m; if (!strcasecmp(mbox, "INBOX")) return mbox; m = apply_conversion(mbox); if ((prefix == NULL && delim == '\0') || (prefix == NULL && delim == '/')) return m; buffer_reset(&nbuf); n = snprintf(nbuf.data, nbuf.size + 1, "%s%s", (prefix ? prefix : ""), m); if (n > (int)nbuf.size) { buffer_check(&nbuf, n); snprintf(nbuf.data, nbuf.size + 0, "%s%s", (prefix ? prefix : ""), m); } for (c = nbuf.data; (c = strchr(c, '/')) != NULL; *(c++) = delim); debug("namespace: '%s' -> '%s'\n", m, nbuf.data); return nbuf.data; } /* * Convert the names of personal mailboxes, using the namespace specified by * the mail server, from mail server format to internal format. */ const char * reverse_namespace(const char *mbox, char *prefix, char delim) { int n, o; char *c; if (!strcasecmp(mbox, "INBOX")) return mbox; if ((prefix == NULL && delim == '\0') || (prefix == NULL && delim == '/')) return reverse_conversion(mbox); buffer_reset(&nbuf); o = strlen(prefix ? prefix : ""); if (strncasecmp(mbox, (prefix ? prefix : ""), o)) o = 0; n = snprintf(nbuf.data, nbuf.size + 1, "%s", mbox + o); if (n > (int)nbuf.size) { buffer_check(&nbuf, n); snprintf(nbuf.data, nbuf.size + 1, "%s", mbox + o); } for (c = nbuf.data; (c = strchr(c, delim)) != NULL; *(c++) = '/'); debug("namespace: '%s' <- '%s'\n", mbox, nbuf.data); return reverse_conversion(nbuf.data); } /* * Convert a mailbox name to the modified UTF-7 encoding, according to RFC 3501 * Section 5.1.3. */ const char * apply_conversion(const char *mbox) { iconv_t cd; char *inbuf, *outbuf; size_t inlen, outlen; char *c, *shift; unsigned char *r, *w; buffer_check(&nbuf, strlen(mbox)); buffer_reset(&nbuf); xstrncpy(nbuf.data, mbox, nbuf.size); nbuf.len = strlen(nbuf.data); buffer_check(&cbuf, nbuf.len * 5); buffer_reset(&cbuf); r = (unsigned char *)nbuf.data; w = (unsigned char *)cbuf.data; inbuf = outbuf = NULL; inlen = outlen = 0; while (*r != '\0') { /* Skip non-printable ASCII characters. */ if (*r < 0x20 || *r == 0x7F) { r++; continue; } /* Escape shift character for modified UTF-7. */ if (*r == '&') { *w++ = '&'; *w++ = '-'; r++; continue; } /* Copy ASCII printable characters. */ if (*r >= 0x20 && *r <= 0x7E) { *w++ = *r++; continue; } /* UTF-8 sequence will follow. */ if (inbuf == NULL) { inbuf = (char *)r; inlen = 0; } if ((*r & 0xE0) == 0xC0) { /* Two byte UTF-8. */ inlen += 2; r += 2; } else if ((*r & 0xF0) == 0xE0) { /* Three byte UTF-8. */ inlen += 3; r += 3; } else if ((*r & 0xF8) == 0xF0) { /* Four byte UTF-8. */ inlen += 4; r += 4; } /* UTF-8 sequence has ended, convert it to UTF-7. */ if (inbuf != NULL && (*r <= 0x7F || *r == '\0')) { outbuf = (char *)w; outlen = cbuf.size - (outbuf - cbuf.data); cd = iconv_open("UTF-7", ""); if (cd == (iconv_t)-1) { error("converting mailbox name; %s\n", strerror(errno)); return mbox; } while (inlen > 0) { if (iconv(cd, &inbuf, &inlen, &outbuf, &outlen) == -1) { if (errno == E2BIG) { buffer_check(&cbuf, cbuf.size * 2); break; } else { error("converting mailbox name;" "%s\n", strerror(errno)); return mbox; } } else { iconv(cd, NULL, NULL, &outbuf, &outlen); } } iconv_close(cd); w = (unsigned char *)outbuf; inbuf = outbuf = NULL; inlen = outlen = 0; } } if (*w != '\0') *w = '\0'; /* Convert UTF-7 sequences to IMAP modified UTF-7. */ for (c = cbuf.data, shift = NULL; *c != '\0'; c++) switch (*c) { case '+': *c = '&'; shift = c; break; case '-': shift = NULL; break; case '/': if (shift != NULL) *c = ','; break; } if (shift != NULL) { *w++ = '-'; *w = '\0'; } debug("conversion: '%s' -> '%s'\n", nbuf.data, cbuf.data); return cbuf.data; } /* * Convert a mailbox name from the modified UTF-7 encoding, according to RFC * 3501 Section 5.1.3. */ const char * reverse_conversion(const char *mbox) { iconv_t cd; char *inbuf, *outbuf; size_t inlen, outlen; char *c, *shift; buffer_check(&cbuf, strlen(mbox)); buffer_reset(&cbuf); xstrncpy(cbuf.data, mbox, cbuf.size); /* Convert IMAP modified UTF-7 sequences to UTF-7. */ for (c = cbuf.data, shift = NULL; *c != '\0'; c++) switch (*c) { case '&': *c = '+'; shift = c; break; case '-': shift = NULL; break; case ',': if (shift != NULL) *c = '/'; break; } do { inbuf = cbuf.data; inlen = strlen(cbuf.data); buffer_check(&nbuf, inlen); buffer_reset(&nbuf); outbuf = nbuf.data; outlen = nbuf.size; cd = iconv_open("", "UTF-7"); if (cd == (iconv_t)-1) { error("converting mailbox name; %s\n", strerror(errno)); return mbox; } while (inlen > 0) { if (iconv(cd, &inbuf, &inlen, &outbuf, &outlen) == -1) { if (errno == E2BIG) { buffer_check(&nbuf, nbuf.size * 2); break; } else { error("converting mailbox name; %s\n", strerror(errno)); return mbox; } } else { iconv(cd, NULL, NULL, &outbuf, &outlen); } } iconv_close(cd); } while (inlen > 0); *outbuf = '\0'; for (c = nbuf.data; (c = strchr(c,'+')) != NULL; *c = '&'); /* Convert UTF-7 sequences to IMAP modified UTF-7. */ for (c = cbuf.data, shift = NULL; *c != '\0'; c++) switch (*c) { case '+': *c = '&'; shift = c; break; case '-': shift = NULL; break; case '/': if (shift != NULL) *c = ','; break; } debug("conversion: '%s' <- '%s'\n", nbuf.data, cbuf.data); return nbuf.data; } imapfilter-2.5.2/src/regex.lua0000644000175000017500000000210211757662071016107 0ustar frankiefrankie-- A simple wrapper for PCRE that uses a cache for compiled expressions. _regex_cache = {} _regex_cache.mt = {} setmetatable(_regex_cache, _regex_cache.mt) _regex_cache.mt.__index = function (self, key) local zero if _VERSION == 'Lua 5.1' then zero = '%z' else zero = '\0' end local _, _, pattern, cflags = string.find(key, '^(.*)' .. zero .. '(.*)$') local _, compiled = ifre.compile(pattern, tonumber(cflags)) self[key] = compiled return compiled end function regex_search(pattern, subject, cflags, eflags) _check_required(pattern, 'string') _check_required(subject, 'string') _check_optional(cflags, 'table') _check_optional(eflags, 'table') if cflags == nil then cflags = {} end if eflags == nil then eflags = {} end local flags = ifre.flags() cf = 0 for _, f in ipairs(cflags) do cf = cf + flags[f] end ef = 0 for _, f in pairs(eflags) do ef = cf + flags[f] end local compiled = _regex_cache[pattern .. '\0' .. cf] if compiled == nil then return nil end return ifre.exec(compiled, subject, ef) end imapfilter-2.5.2/src/version.h0000644000175000017500000000033611757662071016137 0ustar frankiefrankie#ifndef VERSION_H #define VERSION_H /* Program's version number. */ #define VERSION "2.5.2" /* Program's copyright. */ #define COPYRIGHT "Copyright (c) 2001-2012 Eleftherios Chatzimparmpas" #endif /* VERSION_H */ imapfilter-2.5.2/src/common.lua0000644000175000017500000002617511757662071016305 0ustar frankiefrankie-- Common functions for all classes and compatibility workarounds if _VERSION == 'Lua 5.1' then table.unpack = unpack else unpack = table.unpack end function _check_required(arg, argtype) if type(arg) == 'nil' then error('required argument left out', 3) else _check_optional(arg, argtype) end end function _check_optional(arg, argtype) if type(arg) ~= 'nil' then if type(argtype) == 'string' then if type(arg) ~= argtype then error(argtype .. ' argument expected, got ' .. type(arg), 3) end elseif type(argtype) == 'table' then local b = false for _, t in ipairs(argtype) do if type(arg) == t then b = true end end if b == false then error(argtype .. ' argument expected, got ' .. type(arg), 3) end end end end function _extract_mailboxes(messages) local t = {} for _, v in ipairs(messages) do b, _ = table.unpack(v) t[b] = true end return t end function _extract_messages(mailbox, messages) local t = {} for _, v in ipairs(messages) do b, m = table.unpack(v) if mailbox == b then table.insert(t, m) end end return t end function _make_range(messages) for _, m in ipairs(messages) do if type(m) ~= 'number' then return messages end end table.sort(messages) local t = {} local a, z for _, m in ipairs(messages) do if a == nil or z == nil then a = m z = m else if m == z + 1 then z = m else if a == z then table.insert(t, tostring(a)) else table.insert(t, a .. ':' .. z) end a = m z = m end end end if a == z then table.insert(t, tostring(a)) else table.insert(t, a .. ':' .. z) end return t end function _make_query(criteria) local s = 'ALL ' if criteria.invert ~= true then for ka, va in ipairs(criteria) do if type(va) == 'string' then s = s .. '' .. '(' .. va .. ')' .. ' ' elseif type(va) == 'table' then for i = 1, #va - 1 do s = s .. 'OR ' end for ko, vo in ipairs(va) do if type(vo) ~= 'string' then error('filter rule not a string', 2) end s = s .. '(' .. vo .. ') ' end else error('filter element not a string or table', 2) end end else for i = 1, #criteria - 1 do s = s .. 'OR ' end for ko, vo in ipairs(criteria) do if type(vo) == 'string' then s = s .. '' .. '(' .. vo .. ')' .. ' ' elseif type(vo) == 'table' then s = s .. '(' for ka, va in ipairs(vo) do if type(va) ~= 'string' then error('filter rule not a string', 2) end s = s .. va .. ' ' end s = string.gsub(s, '(.+) ', '%1') s = s .. ') ' else error('filter rule not a string or table', 2) end end end s = string.gsub(s, '(.+) ', '%1') return s end function _parse_structure(b) local bs = _parse_body(b) if not bs then error(b.i .. ':' .. b.s) end return _parse_normalize(bs) end function _parse_normalize(bs, key, val) if not key or not val then if #bs == 0 then return { ['1'] = bs } else for k, v in pairs(bs) do if type(k) ~= 'number' then bs[k] = nil end end for k, v in ipairs(bs) do _parse_normalize(bs, k, v) bs[tostring(k)] = v bs[k] = nil end end return bs else for k, v in ipairs(val) do local new = tostring(key) .. '.' .. tostring(k) bs[new] = v _parse_normalize(bs, new, v) val[k] = nil end end end function _parse_body(b) if not _parse_lpar(b) then return end local bp if _parse_lpar(b, true) then bp = _parse_mpart(b) else bp = _parse_1part(b) end if not _parse_rpar(b) then return end return bp end function _parse_1part(b) local i = b.i local t = _parse_string(b) _parse_space(b) local s = _parse_string(b) if t and t:lower() == 'message' and s and s:lower() == 'rfc822' then return _parse_message(b) else b.i = i return _parse_basic(b) end end function _parse_basic(b) local bp = {} local s s = _parse_string(b) if not s then return end bp['type'] = s _parse_space(b) s = _parse_string(b) if not s then return end bp['type'] = bp['type'] .. '/' .. s _parse_space(b) s = _parse_param(b, 'name') if s then bp['name'] = s end _parse_space(b) _parse_nstring(b) _parse_space(b) _parse_nstring(b) _parse_space(b) _parse_string(b) _parse_space(b) bp['size'] = _parse_number(b) if bp['type']:sub(1, 5):lower() == 'text/' then _parse_space(b) _parse_number(b) end if _parse_space(b) then _parse_nstring(b) end if _parse_space(b) then s = _parse_dsp(b) if s then bp['name'] = s end end if _parse_space(b) then _parse_lang(b) end if _parse_space(b) then _parse_nstring(b) end while _parse_space(b) and b.i <= #b.s do _parse_extension(b) end return bp end function _parse_mpart(b) local bp = {} local i = 1 local s bp['type'] = 'multipart' while _parse_lpar(b, true) and b.i <= #b.s do bp[i] = _parse_body(b) i = i + 1 end _parse_space(b) s = _parse_string(b) if not s then return end bp['type'] = bp['type'] .. '/' .. s if _parse_space(b) then s = _parse_param(b, 'name') if s then bp['name'] = s end end if _parse_space(b) then s = _parse_dsp(b) if s then bp['name'] = s end end if _parse_space(b) then _parse_lang(b) end if _parse_space(b) then _parse_nstring(b) end while _parse_space(b) and b.i < #b.s do _parse_extension(b) end return bp end function _parse_message(b) local bp = {} local s bp['type'] = 'message/rfc822' _parse_space(b) s = _parse_param(b, 'name') if s then bp['name'] = s end _parse_space(b) _parse_nstring(b) _parse_space(b) _parse_nstring(b) _parse_space(b) _parse_string(b) _parse_space(b) bp['size'] = _parse_number(b) _parse_space(b) _parse_envelope(b) _parse_space(b) local p = _parse_body(b) if not p then return end if #p == 0 then bp[1] = p else for k, v in pairs(p) do if type(k) == 'number' then bp[k] = v end end end _parse_space(b) _parse_number(b) if _parse_space(b) then _parse_nstring(b) end if _parse_space(b) then s = _parse_dsp(b) if s then bp['name'] = s end end if _parse_space(b) then _parse_lang(b) end if _parse_space(b) then _parse_nstring(b) end while _parse_space(b) and b.i <= #b.s do _parse_extension(b) end return bp end function _parse_envelope(b) _parse_lpar(b) _parse_nstring(b) _parse_space(b) _parse_nstring(b) _parse_space(b) _parse_address(b) _parse_space(b) _parse_address(b) _parse_space(b) _parse_address(b) _parse_space(b) _parse_address(b) _parse_space(b) _parse_address(b) _parse_space(b) _parse_address(b) _parse_space(b) _parse_nstring(b) _parse_space(b) _parse_nstring(b) _parse_rpar(b) end function _parse_address(b) if _parse_lpar(b) then while _parse_lpar(b) and b.i <= #b.s do _parse_nstring(b) _parse_space(b) _parse_nstring(b) _parse_space(b) _parse_nstring(b) _parse_space(b) _parse_nstring(b) _parse_rpar(b) end _parse_rpar(b) elseif _parse_nil(b) then end end function _parse_lang(b) if _parse_lpar(b) then local lang = {} repeat table.insert(lang, _parse_string(b)) until not _parse_space(b) or b.i > #b.s _parse_rpar(b) return lang else return _parse_nstring(b) end end function _parse_dsp(b) local r if _parse_lpar(b) then _parse_string(b) _parse_space(b) r = _parse_param(b, 'filename') _parse_rpar(b) elseif _parse_nil(b) then end return r end function _parse_param(b, key) local r if _parse_lpar(b) then repeat local s = _parse_string(b) _parse_space(b) if s and s:lower() == key then r = _parse_string(b) else _parse_string(b) end until not _parse_space(b) or b.i > #b.s _parse_rpar(b) elseif _parse_nil(b) then end return r end function _parse_extension(b) if _parse_nstring(b) then elseif _parse_number(b) then elseif _parse_lpar(b) then _parse_extension(b) while _parse_space(b) and b.i <= #b.s do _parse_extension(b) end _parse_rpar(b) else end end function _parse_space(b, peek) if b.s:sub(b.i, b.i) == ' ' then if not peek then b.i = b.i + 1 end return true else return false end end function _parse_lpar(b, peek) if b.s:sub(b.i, b.i) == '(' then if not peek then b.i = b.i + 1 end return true else return false end end function _parse_rpar(b, peek) if b.s:sub(b.i, b.i) == ')' then if not peek then b.i = b.i + 1 end return true else return false end end function _parse_nil(b) if b.s:sub(b.i, b.i + 2):upper() == 'NIL' then b.i = b.i + 3 return true else return false end end function _parse_string(b) local i = b.i if b.s:sub(i, i) == '"' then i = i + 1 else return end local j = i local n = 0 while true do n = b.s:find('"', i + n) if not n then return end if b.s:sub(n - 1, n - 1) ~= '\\' then i = n + 1 b.i = i return b.s:sub(j, n - 1) else return end end end function _parse_nstring(b) local i = b.i if b.s:sub(i, i) == '"' then i = i + 1 elseif _parse_nil(b) then return 'NIL' else return end local j = i local n = 0 while true do n = b.s:find('"', i + n) if not n then return end if b.s:sub(n - 1, n - 1) ~= '\\' then i = n + 1 b.i = i return b.s:sub(j, n - 1) else return end end end function _parse_number(b) local j = b.i local n = b.s:find('[^0-9]', b.i) if not n then return end b.i = n return tonumber(b.s:sub(j, n - 1)) end imapfilter-2.5.2/src/pathnames.h0000644000175000017500000000156011757662071016432 0ustar frankiefrankie#ifndef PATHNAMES_H #define PATHNAMES_H /* Lua imapfilter set functions file. */ #define PATHNAME_COMMON CONFIG_SHAREDIR "/common.lua" /* Lua imapfilter set functions file. */ #define PATHNAME_SET CONFIG_SHAREDIR "/set.lua" /* Lua imapfilter account functions file. */ #define PATHNAME_ACCOUNT CONFIG_SHAREDIR "/account.lua" /* Lua imapfilter mailbox functions file. */ #define PATHNAME_MAILBOX CONFIG_SHAREDIR "/mailbox.lua" /* Lua imapfilter message functions file. */ #define PATHNAME_MESSAGE CONFIG_SHAREDIR "/message.lua" /* Lua imapfilter message functions file. */ #define PATHNAME_OPTIONS CONFIG_SHAREDIR "/options.lua" /* Lua imapfilter regex functions file. */ #define PATHNAME_REGEX CONFIG_SHAREDIR "/regex.lua" /* Lua imapfilter auxiliary functions file. */ #define PATHNAME_AUXILIARY CONFIG_SHAREDIR "/auxiliary.lua" #endif /* PATHNAMES_H */ imapfilter-2.5.2/src/regexp.c0000644000175000017500000000143111536341114015717 0ustar frankiefrankie#include #include #include #include "imapfilter.h" #include "regexp.h" /* * Compile all the patterns and allocate the necessary space for the substring * matching. */ void regexp_compile(regexp *reg) { regexp *re; for (re = reg; re->pattern != NULL; re++) { re->preg = (regex_t *)xmalloc(sizeof(regex_t)); regcomp(re->preg, re->pattern, REG_EXTENDED | REG_ICASE); re->nmatch = re->preg->re_nsub + 1; re->pmatch = (regmatch_t *)xmalloc(sizeof(regmatch_t) * re->nmatch); } } /* * Free the compiled regular expressions and the space allocated for the * substring matching. */ void regexp_free(regexp *reg) { regexp *re; for (re = reg; re->pattern != NULL; re++) { regfree(re->preg); xfree(re->preg); xfree(re->pmatch); } } imapfilter-2.5.2/src/regexp.h0000644000175000017500000000102411536341114015722 0ustar frankiefrankie#ifndef REGEXP_H #define REGEXP_H #include #include #include /* Regular expression convenience structure. */ typedef struct regexp { const char *pattern; /* Regular expression pattern string. */ regex_t *preg; /* Compiled regular expression. */ size_t nmatch; /* Number of subexpressions in pattern. */ regmatch_t *pmatch; /* Structure for substrings that matched. */ } regexp; /* regexp.c */ void regexp_compile(regexp *reg); void regexp_free(regexp *reg); #endif /* REGEXP_H */ imapfilter-2.5.2/src/socket.c0000644000175000017500000001757111757662105015744 0ustar frankiefrankie#include #include #include #include #include #include #include #include #include #include #include #include #include #include "imapfilter.h" #include "session.h" /* * Connect to mail server. */ int open_connection(session *ssn) { struct addrinfo hints, *res, *ressave; int n, sockfd; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; n = getaddrinfo(ssn->server, ssn->port, &hints, &res); if (n < 0) { error("gettaddrinfo; %s\n", gai_strerror(n)); return -1; } ressave = res; sockfd = -1; while (res) { sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (sockfd >= 0) { if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0) break; sockfd = -1; } res = res->ai_next; } if (ressave) freeaddrinfo(ressave); if (sockfd == -1) { error("error while initiating connection to %s at port %s\n", ssn->server, ssn->port); return -1; } ssn->socket = sockfd; if (ssn->sslproto) { if (open_secure_connection(ssn) == -1) { close_connection(ssn); return -1; } } return ssn->socket; } /* * Initialize SSL/TLS connection. */ int open_secure_connection(session *ssn) { int r, e; SSL_CTX *ctx; const SSL_METHOD *method; method = NULL; if (ssn->sslproto && (!strncasecmp(ssn->sslproto, "ssl3", 4) || !strncasecmp(ssn->sslproto, "ssl2", 4))) method = SSLv23_client_method(); else method = TLSv1_client_method(); if (!(ctx = SSL_CTX_new(method))) goto fail; if (!(ssn->sslconn = SSL_new(ctx))) goto fail; SSL_set_fd(ssn->sslconn, ssn->socket); for (;;) { if ((r = SSL_connect(ssn->sslconn)) > 0) break; switch (SSL_get_error(ssn->sslconn, r)) { case SSL_ERROR_ZERO_RETURN: error("initiating SSL connection to %s; the " "connection has been closed cleanly\n", ssn->server); goto fail; case SSL_ERROR_NONE: case SSL_ERROR_WANT_CONNECT: case SSL_ERROR_WANT_ACCEPT: case SSL_ERROR_WANT_X509_LOOKUP: case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: break; case SSL_ERROR_SYSCALL: e = ERR_get_error(); if (e == 0 && r == 0) error("initiating SSL connection to %s; EOF in " "violation of the protocol\n", ssn->server); else if (e == 0 && r == -1) error("initiating SSL connection to %s; %s\n", ssn->server, strerror(errno)); else error("initiating SSL connection to %s; %s\n", ssn->server, ERR_error_string(e, NULL)); goto fail; case SSL_ERROR_SSL: error("initiating SSL connection to %s; %s\n", ssn->server, ERR_error_string(ERR_get_error(), NULL)); goto fail; default: break; } } if (get_option_boolean("certificates") && get_cert(ssn) == -1) goto fail; SSL_CTX_free(ctx); return 0; fail: ssn->sslconn = NULL; SSL_CTX_free(ctx); return -1; } /* * Disconnect from mail server. */ int close_connection(session *ssn) { int r; r = 0; close_secure_connection(ssn); if (ssn->socket != -1) { r = close(ssn->socket); ssn->socket = -1; if (r == -1) error("closing socket; %s\n", strerror(errno)); } return r; } /* * Shutdown SSL/TLS connection. */ int close_secure_connection(session *ssn) { if (ssn->sslconn) { SSL_shutdown(ssn->sslconn); SSL_free(ssn->sslconn); ssn->sslconn = NULL; } return 0; } /* * Read data from socket. */ ssize_t socket_read(session *ssn, char *buf, size_t len, long timeout, int timeoutfail) { int s; ssize_t r; fd_set fds; struct timeval tv; struct timeval *tvp; r = 0; s = 1; tvp = NULL; memset(buf, 0, len + 1); if (timeout > 0) { tv.tv_sec = timeout; tv.tv_usec = 0; tvp = &tv; } FD_ZERO(&fds); FD_SET(ssn->socket, &fds); if (ssn->sslconn) { if (SSL_pending(ssn->sslconn) > 0 || ((s = select(ssn->socket + 1, &fds, NULL, NULL, tvp)) > 0 && FD_ISSET(ssn->socket, &fds))) { r = socket_secure_read(ssn, buf, len); if (r <= 0) goto fail; } } else { if ((s = select(ssn->socket + 1, &fds, NULL, NULL, tvp)) > 0 && FD_ISSET(ssn->socket, &fds)) { r = read(ssn->socket, buf, len); if (r == -1) { error("reading data; %s\n", strerror(errno)); goto fail; } else if (r == 0) { goto fail; } } } if (s == -1) { error("waiting to read from socket; %s\n", strerror(errno)); goto fail; } else if (s == 0 && timeoutfail) { error("timeout period expired while waiting to read data\n"); goto fail; } return r; fail: close_connection(ssn); return -1; } /* * Read data from a TLS/SSL connection. */ ssize_t socket_secure_read(session *ssn, char *buf, size_t len) { int r, e; for (;;) { if ((r = (ssize_t) SSL_read(ssn->sslconn, buf, len)) > 0) break; switch (SSL_get_error(ssn->sslconn, r)) { case SSL_ERROR_ZERO_RETURN: error("reading data through SSL; the connection has " "been closed cleanly\n"); goto fail; case SSL_ERROR_NONE: case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: case SSL_ERROR_WANT_CONNECT: case SSL_ERROR_WANT_ACCEPT: case SSL_ERROR_WANT_X509_LOOKUP: break; case SSL_ERROR_SYSCALL: e = ERR_get_error(); if (e == 0 && r == 0) error("reading data through SSL; EOF in " "violation of the protocol\n"); else if (e == 0 && r == -1) error("reading data through SSL; %s\n", strerror(errno)); else error("reading data through SSL; %s\n", ERR_error_string(e, NULL)); goto fail; case SSL_ERROR_SSL: error("reading data through SSL; %s\n", ERR_error_string(ERR_get_error(), NULL)); goto fail; default: break; } } return r; fail: SSL_set_shutdown(ssn->sslconn, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN); return -1; } /* * Write data to socket. */ ssize_t socket_write(session *ssn, const char *buf, size_t len) { int s; ssize_t r, t; fd_set fds; r = t = 0; s = 1; FD_ZERO(&fds); FD_SET(ssn->socket, &fds); while (len) { if ((s = select(ssn->socket + 1, NULL, &fds, NULL, NULL) > 0 && FD_ISSET(ssn->socket, &fds))) { if (ssn->sslconn) { r = socket_secure_write(ssn, buf, len); if (r <= 0) goto fail; } else { r = write(ssn->socket, buf, len); if (r == -1) { error("writing data; %s\n", strerror(errno)); goto fail; } else if (r == 0) { goto fail; } } if (r > 0) { len -= r; buf += r; t += r; } } } if (s == -1) { error("waiting to write to socket; %s\n", strerror(errno)); goto fail; } else if (s == 0) { error("timeout period expired while waiting to write data\n"); goto fail; } return t; fail: close_connection(ssn); return -1; } /* * Write data to a TLS/SSL connection. */ ssize_t socket_secure_write(session *ssn, const char *buf, size_t len) { int r, e; for (;;) { if ((r = (ssize_t) SSL_write(ssn->sslconn, buf, len)) > 0) break; switch (SSL_get_error(ssn->sslconn, r)) { case SSL_ERROR_ZERO_RETURN: error("writing data through SSL; the connection has " "been closed cleanly\n"); goto fail; case SSL_ERROR_NONE: case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: case SSL_ERROR_WANT_CONNECT: case SSL_ERROR_WANT_ACCEPT: case SSL_ERROR_WANT_X509_LOOKUP: break; case SSL_ERROR_SYSCALL: e = ERR_get_error(); if (e == 0 && r == 0) error("writing data through SSL; EOF in " "violation of the protocol\n"); else if (e == 0 && r == -1) error("writing data through SSL; %s\n", strerror(errno)); else error("writing data through SSL; %s\n", ERR_error_string(e, NULL)); goto fail; case SSL_ERROR_SSL: error("writing data through SSL; %s\n", ERR_error_string(ERR_get_error(), NULL)); goto fail; default: break; } } return r; fail: SSL_set_shutdown(ssn->sslconn, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN); return -1; } imapfilter-2.5.2/src/memory.c0000644000175000017500000000245211536341114015741 0ustar frankiefrankie#include #include #include #include #include "imapfilter.h" /* * A malloc() that checks the results and dies in case of error. */ void * xmalloc(size_t size) { void *ptr; ptr = (void *)malloc(size); if (ptr == NULL) fatal(ERROR_MEMALLOC, "allocating memory; %s\n", strerror(errno)); return ptr; } /* * A realloc() that checks the results and dies in case of error. */ void * xrealloc(void *ptr, size_t size) { ptr = (void *)realloc(ptr, size); if (ptr == NULL) fatal(ERROR_MEMALLOC, "allocating memory; %s\n", strerror(errno)); return ptr; } /* * A free() that dies if fed with NULL pointer. */ void xfree(void *ptr) { if (ptr == NULL) fatal(ERROR_MEMALLOC, "NULL pointer given as argument\n"); free(ptr); } /* * A strdup() that checks the results and dies in case of error. */ char * xstrdup(const char *str) { char *dup; dup = strdup(str); if (dup == NULL) fatal(ERROR_MEMALLOC, "allocating memory; %s\n", strerror(errno)); return dup; } /* * A strndup() implementation that also checks the results and dies in case of * error. */ char * xstrndup(const char *str, size_t len) { char *dup; dup = (char *)xmalloc((len + 1) * sizeof(char)); memcpy(dup, str, len); dup[len] = '\0'; return dup; } imapfilter-2.5.2/NEWS0000644000175000017500000002677111757662071014224 0ustar frankiefrankieIMAPFilter 2.5.2 - 29 Feb 2012 - Persistent errors or connection failures are now ignored when running in daemon mode, and a reconnection is attempted during the next loop iteration. - Bug fix; problems with failure handling during login/logout. IMAPFilter 2.5.1 - 27 Feb 2012 - Support for recovery of a session after a BYE response is received. - Option to control in which cases a terminated session will be restored. - Bug fix; a BYE response could sometimes get incorrectly ignored. IMAPFilter 2.5 - 23 Feb 2012 - Support for recovery of a session when a network failure is encountered, and other robustness improvements. - Informational messages are printed also for the fetch and append methods. - Lua 5.2 compatibility, while the codebase can still be compiled with version 5.1. - The OpenSSL library is now a mandatory build requirement. - Bug fix; unrecoverable login failures did not result in aborting of the execution of the configuration. - Bug fix; when messages were appended to a mailbox that did not exist, it failed to create the mailbox and then retry the appending. - Bug fix; misleading errors were printed on some SSL failures. - Bug fix; protected call of the commands to execute in the daemon function could hide important failures. - Bug fix; the man page had an incorrect description of the -d option. * Support for the old deprecated 1.x configuration format has been removed, and the current 2.x format can only be executed from now on. IMAPFilter 2.4.2 - 19 Jan 2012 - Bug fix; some ASCII characters in mailbox names were incorrectly converted to UTF-7. IMAPFilter 2.4.1 - 8 Dec 2011 - Bug fix; become_daemon() failure. IMAPFilter 2.4 - 6 Dec 2011 - Support for non-ASCII mailbox names. - New environment variable to set the configuration directory. - Bug fix; parsing of some server responses was broken since the previous release. - Bug fix; the match_field() method matched on the whole header field, instead of only the header field body. - Bug fix; debug file check caused printing of a misleading error message. - Bug fix; typo error in a configuration man page example. IMAPFilter 2.3 - 6 Aug 2011 - Support for appending/uploading messages to mailboxes. - Debug file option now takes filename argument. - New simplified configuration and building procedure. - Bug fix; in some cases a mailbox was incorrectly assumed selected. - Bug fix; in some cases server capabilities needed update after login. - Bug fix; timeout problem with CRAM-MD5 authentication. - Bug fix; some servers send non-ASCII characters in their responses. IMAPFilter 2.2.3 - 6 Mar 2011 - Project moved to GitHub. - Changed file and directory structure. - The next UID is returned as an additional return value of check_status(). - All processing methods now return a boolean based on their success. - Bug fix; a lost connection is now handled better by trying to reconnect. - Bug fix; in some cases in IDLE a message had arrived but was ignored. - Bug fix; in some servers the initial IDLE reply wasn't handled correctly. - Bug fix; typo errors in the documentation. IMAPFilter 2.2.2 - 23 Jan 2010 - Bug fix; a couple of errors in the extending examples file. IMAPFilter 2.2.1 - 20 Jan 2010 - A global option for the IDLE refreshing interval was added. - Bug fix; more detailed reporting when SSL socket errors occur. IMAPFilter 2.2 - 30 Dec 2009 - Support for combining searching methods in multiple mailboxes at the same or different accounts and processing of the results in bulk. - Support for meta-searching that allows searching on the previous searching results. - The processing and fetching methods were enhanced to reflect the new changes and the documentation was updated. - Global options for the message cache and the certificates were added. - Bug fix; questions for certificates are not asked while in daemon mode, but instead an error is printed. * A different format is used for the returned structures of the searching methods, due to the introduction of multiple mailbox searching and meta-searching, and thus any configuration files that rely on them should be updated. Consequently, the processing and fetching methods have been also enhanced and the relevant documentation updated, and while these changes are backwards compatible, an update of the configuration file is still recommended. IMAPFilter 2.1.2 - 3 Dec 2009 - Bug fix; cache for message parts didn't work correctly. - Bug fix; documentation error. IMAPFilter 2.1.1 - 24 Nov 2009 - Bug fix; global option timeout and enter_idle() didn't play well together. IMAPFilter 2.1 - 23 Nov 2009 - Support for the IMAP IDLE extension (RFC 2177) through the enter_idle() method. - Support for fetching of a message's body structure through the fetch_structure() method, and of a message's specific body part through the fetch_parts() method. - Addition of a global option that controls the character set used for all the searching methods. - Bug fix; fetching of non-existent messages. - Bug fix; no trailing end-of-line characters in the results of fetch_fields(). IMAPFilter 2.0.11 - 20 Sep 2009 - Bug fix; fetching of messages with empty body. - Workaround for problematic IMAP server sending non-compliant mailbox status information. IMAPFilter 2.0.10 - 16 Feb 2008 - Bug fix; failed a great number (tens of thousands) of commands were exchanged with an IMAP server. - Bug fix; failed to fetch the body of some messages in some extremely rare occasions. - Bug fix; the description for the contain_header() method was clarified. IMAPFilter 2.0.9 - 26 Dec 2007 - Bug fix; the match_*() methods failed to match messages. - Bug fix; the match_*() methods failed with an error when no messages matched. - Bug fix; note added in the documentation about the need to use double backslashes inside of regular expression patterns. IMAPFilter 2.0.8 - 23 Dec 2007 - Bug fix; on some platforms it is necessary to link against the math library. IMAPFilter 2.0.7 - 22 Dec 2007 - Bug fix; the match_*() methods failed with an error message. IMAPFilter 2.0.6 - 7 Oct 2007 - Bug fix; the search query that was sent with the select_all() method had an incorrect format and this caused an error in some mail servers. IMAPFilter 2.0.5 - 4 Oct 2007 - Bug fix; an error in the sample extensions file. - Bug fix; typo errors in the manual page. - The documentation was updated with details and examples on how to access mailboxes inside folders. - Examples were added on how to define composite filters that include multiple searching rules. IMAPFilter 2.0.4 - 27 Sep 2007 - Bug fix; the send_query() method didn't return the special form of table that the rest of the searching methods did. - An additional searching method has been added to search for keyword flags set. - A new variable that was added to the Makefile makes it possible to set an alternative environment for the installation path. IMAPFilter 2.0.3 - 27 Jul 2007 - Bug fix; part of the program's functionality didn't seem to work at all. (did nothing), due to problem when providing the results from searching methods to processing methods. IMAPFilter 2.0.2 - 30 Jun 2007 - Bug fix; message cache problem due to non-use of message UIDs. IMAPFilter 2.0.1 - 29 Jun 2007 - Bug fix; character set problem with 1.x configuration files. - Bug fix; typo errors in the documentation. IMAPFilter 2.0 - 27 Jun 2007 - New, more powerful, feature rich and yet simpler configuration file. - Easier object oriented view of accounts and mailboxes. - Simpler approach to filters, with infix logical or/and/not operators. - No more need to mess with server search queries. - More and simpler functions instead of few and complicated ones. - More feature complete interface that can now even manipulate mailboxes. - Regular expressions integrated into the searching interface. - Effective caching subsystem when fetching message parts. - Can still read old version 1.x configuration files for compatibility. - Lua 5.1 and the PCRE library are now requirements. * The configuration file format has changed. The new format is not backwards compatible, and thus it should not be mixed with the old format. Nevertheless, configuration files that employ the old, and now deprecated, format can still be read and executed as before. IMAPFilter 1.3 - 13 Feb 2007 - Perl Compatible Regular Expression (PCRE) support. - Compile against Lua 5.1 by default. - Bug fix; program fault in some cases and when namespace prefix was empty. - Bug fix; program fault on some platforms when running in verbose mode. IMAPFilter 1.2.2 - 1 Aug 2006 - Bug fix; a mix up of connections could happen in certain circumstances, when a hostname and/or username was a prefix of another hostname and/or username respectively, or when the same hostname and username was used to connect to a different port. - Bug fix; the list()/lsub() functions parsed mailboxes/folders whose names contained spaces incorrectly. - The list() function now does not return the folder itself, when listing mailboxes inside a specific folder. - It is now possible to define new user keywords for messages inside a mailbox, apart from the standard system flags. IMAPFilter 1.2.1 - 9 Mar 2006 - Buf fix; program fault when using the fetch*() family of functions. IMAPFilter 1.2 - 2 Mar 2006 - IPv6 support. - Lua 5.1 compatibility. - Bug fix; handle messages containing binary data. - Bug fix; problems with CPU utilisation when the inactivity timeout timer was set. IMAPFilter 1.1.1 - 11 Nov 2005 - Bug fix; minor memory leak. - Bug fix; on some systems, failure resulted while disconnecting from all the servers, during the shutdown phase just before exiting. IMAPFilter 1.1 - 24 Aug 2005 - Addition of the list() and lsub() commands, that make it possible to get a list of the available mailboxes or only of those that are subscribed. Implementation of the IMAP LIST/LSUB commands, with additional support for the IMAP CHILDREN (RFC 3348) and IMAP NAMESPACE (RFC 2342) extensions. - New program option to execute a string from the command line, without loading a configuration file. - New program option to enter interactive mode after executing the configuration file or the command line. - Servers that reply with multiple SEARCH responses are taken into consideration. - Bug fix; failure to parse the response to fetchfast() that some mail servers sent. - Bug fix; in some systems and when in debug mode, an empty namespace caused program fault. IMAPFilter 1.0.1 - 22 Aug 2004 - Bug fix; in some cases processing of messages with an empty body caused failure. - Bug fix; an invalid namespace prefix was inserted in mailbox names of some mail servers. - Unique message identifiers are now used by default, instead of message sequence numbers, when accessing messages in a mailbox. - Sequence set ranges are generated and sent to the mail server instead of enumerations, when this is possible. - The client now limits the length of the command lines it generates to approximately 1000 octets, by splitting the request into multiple commands. - Systems that have no limit on the number of bytes in a pathname are now considered. - Debug files are now written in $HOME/.imapfilter/ instead of /tmp/. IMAPFilter 1.0 - 23 May 2004 - Initial release of IMAPFilter with extension language Lua. imapfilter-2.5.2/Makefile0000644000175000017500000000006311757662071015147 0ustar frankiefrankieall install uninstall clean: cd src && $(MAKE) $@ imapfilter-2.5.2/LICENSE0000644000175000017500000000206311757662071014516 0ustar frankiefrankieCopyright (c) 2001-2012 Eleftherios Chatzimparmpas Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. imapfilter-2.5.2/README0000644000175000017500000000247511757662071014400 0ustar frankiefrankie IMAPFilter Description IMAPFilter is a mail filtering utility. It connects to remote mail servers using the Internet Message Access Protocol (IMAP), sends searching queries to the server and processes mailboxes based on the results. It can be used to delete, copy, move, flag, etc. messages residing in mailboxes at the same or different mail servers. The 4rev1 and 4 versions of the IMAP protocol are supported. IMAPFilter uses the Lua programming language as a configuration and extension language. Website http://github.com/lefcha/imapfilter Changes All the changes in each new release up to the latest are in the NEWS file. Installation Compile time requirements are Lua (version 5.2 or 5.1), the PCRE library, and the OpenSSL library. Compile and install the program: make all make install Documentation There is detailed description of the command line options in the imapfilter(1) manual page, and of the configuration file format in the imapfilter_config(5) manual page. There are also more configuration examples in the samples/config.lua file, and some examples of extensions through Lua in the samples/extend.lua file. License Released under the terms and conditions of the MIT/X11 license, included in the LICENSE file. Authors See AUTHORS file. imapfilter-2.5.2/samples/0002755000175000017500000000000011757662105015154 5ustar frankiefrankieimapfilter-2.5.2/samples/config.lua0000644000175000017500000000747011536341114017117 0ustar frankiefrankie--------------- -- Options -- --------------- options.timeout = 120 options.subscribe = true ---------------- -- Accounts -- ---------------- -- Connects to "imap1.mail.server", as user "user1" with "secret1" as -- password. account1 = IMAP { server = 'imap1.mail.server', username = 'user1', password = 'secret1', } -- Another account which connects to the mail server using the SSLv3 -- protocol. account2 = IMAP { server = 'imap2.mail.server', username = 'user2', password = 'secret2', ssl = 'ssl3', } -- Get a list of the available mailboxes and folders mailboxes, folders = account1:list_all() -- Get a list of the subscribed mailboxes and folders mailboxes, folders = account1:list_subscribed() -- Create a mailbox account1:create_mailbox('Friends') -- Subscribe a mailbox account1:subscribe_mailbox('Friends') ----------------- -- Mailboxes -- ----------------- -- Get the status of a mailbox account1.INBOX:check_status() -- Get all the messages in the mailbox. results = account1.INBOX:select_all() -- Get newly arrived, unread messages results = account1.INBOX:is_new() -- Get unseen messages with the specified "From" header. results = account1.INBOX:is_unseen() * account1.INBOX:contain_from('weekly-news@news.letter') -- Copy messages between mailboxes at the same account. results:copy_messages(account1.news) -- Get messages with the specified "From" header but without the -- specified "Subject" header. results = account1.INBOX:contain_from('announce@my.unix.os') - account1.INBOX:contain_subject('security advisory') -- Copy messages between mailboxes at a different account. results:copy_messages(account2.security) -- Get messages with any of the specified headers. results = account1.INBOX:contain_from('marketing@company.junk') + account1.INBOX:contain_from('advertising@annoying.promotion') + account1.INBOX:contain_subject('new great products') -- Delete messages. results:delete_messages() -- Get messages with the specified "Sender" header, which are older than -- 30 days. results = account1.INBOX:contain_field('sender', 'owner@announce-list') * account1.INBOX:is_older(30) -- Move messages to the "announce" mailbox inside the "lists" folder. results:move_messages(account1['lists/announce']) -- Get messages, in the "devel" mailbox inside the "lists" folder, with the -- specified "Subject" header and a size less than 50000 octets (bytes). results = account1['lists/devel']:contain_subject('[patch]') * account1['lists/devel']:is_smaller(50000) -- Move messages to the "patch" mailbox. results:move_messages(account2.patch) -- Get recent, unseen messages, that have either one of the specified -- "From" headers, but do not have the specified pattern in the body of -- the message. results = ( account1.INBOX:is_recent() * account1.INBOX:is_unseen() * ( account1.INBOX:contain_from('tux@penguin.land') + account1.INBOX:contain_from('beastie@daemon.land') ) ) - account1.INBOX:match_body('.*all.work.and.no.play.*') -- Mark messages as important. results:mark_flagged() -- Get all messages in two mailboxes residing in the same server. results = account1.news:select_all() + account1.security:select_all() -- Mark messages as seen. results:mark_seen() -- Get recent messages in two mailboxes residing in different servers. results = account1.INBOX:is_recent() + account2.INBOX:is_recent() -- Flag messages as seen and important. results:add_flags({ '\\Seen', '\\Flagged' }) -- Get unseen messages. results = account1.INBOX:is_unseen() -- From the messages that were unseen, match only those with the specified -- regular expression in the header. newresults = results:match_header('^.+MailScanner.*Check: [Ss]pam$') -- Delete those messages. newresults:delete_messages() imapfilter-2.5.2/samples/extend.lua0000644000175000017500000000741211757662105017150 0ustar frankiefrankie-- -- This file contains examples on how IMAPFilter can be extended using -- the Lua programming language. -- -- IMAPFilter can be detached from the controlling terminal and run in -- the background as a system daemon. -- -- The auxiliary function become_daemon() is supplied for conveniency. -- The following example puts imapfilter in the background and runs -- endlessly, executing the commands in the forever() function and -- sleeping for 600 seconds between intervals: function forever() results = myaccount.mymailbox:is_old() results:move_messages(myaccount.myothermailbox) end become_daemon(600, forever) -- IMAPFilter can take advantage of all those filtering utilities that -- are available and use a wide range of heuristic tests, text analysis, -- internet-based realtime blacklists, advanced learning algorithms, -- etc. to classify mail. IMAPFilter can pipe a message to a program -- and act on the message based on the program's exit status. -- -- The auxiliary function pipe_to() is supplied for conveniency. For -- example if there was a utility named "bayesian-spam-filter", which -- returned 1 when it considered the message "spam" and 0 otherwise: all = myaccount.mymailbox:select_all() results = Set {} for _, mesg in ipairs(all) do mbox, uid = table.unpack(mesg) text = mbox[uid]:fetch_message() if (pipe_to('bayesian-spam-filter', text) == 1) then table.insert(results, mesg) end end results:delete_messages() -- One might want to run the bayesian filter only in those parts (attachments) -- of the message that are of type text/plain and smaller than 1024 bytes. -- This is possible using the fetch_structure() and fetch_part() functions: all = myaccount.mymailbox:select_all() results = Set {} for _, mesg in ipairs(all) do mbox, uid = table.unpack(mesg) structure = mbox[uid]:fetch_structure() for partid, partinf in pairs(structure) do if partinf.type:lower() == 'text/plain' and partinf.size < 1024 then part = mbox[uid]:fetch_part(partid) if (pipe_to('bayesian-spam-filter', part) == 1) then table.insert(results, mesg) break end end end end results:delete_messages() -- Messages can be appended to a mailbox. One can fetch a message from a -- mailbox, optionally process it, and then upload it to the same or different -- mailbox, at the same or different mail servers. In the following example a -- header field is added to all messages, and the processed messages are then -- appended to a different mailbox. all = myaccount.mymailbox:select_all() for _, mesg in ipairs(all) do mbox, uid = table.unpack(all) header = mbox[uid]:fetch_header() body = mbox[uid]:fetch_body() message = header:gsub('[\r\n]+$', '\r\n') .. 'My-Header: My-Content\r\n' .. '\r\n' .. body myaccount.myothermaibox:append_message(message) end -- Passwords could be extracted during execution time from an encrypted -- file. -- -- The file is encrypted using the openssl(1) command line tool. For -- example the "passwords.txt" file: -- -- secret1 -- secret2 -- -- ... is encrypted and saved to a file named "passwords.enc" with the -- command: -- -- $ openssl bf -in passwords.txt -out passwords.enc -- -- The auxiliary function pipe_from() is supplied for conveniency. The -- user is prompted to enter the decryption password, the file is -- decrypted and the account passwords are set accordingly: status, output = pipe_from('openssl bf -d -in ~/passwords.enc') _, _, password1, password2 = string.find(output, '([%w%p]+)\n([%w%p]+)') account1 = IMAP { server = 'imap1.mail.server', username = 'user1', password = password1 } account2 = IMAP { server = 'imap2.mail.server', username = 'user2', password = password2 } imapfilter-2.5.2/AUTHORS0000644000175000017500000000005611536341114014544 0ustar frankiefrankieLefteris Chatzimparmpas