#!/usr/bin/perl -w # # sms_biff # # AUTHOR: # Dan Harkless # # COPYRIGHT: # This file is Copyright (C) 2008 by Dan Harkless, and is released under the # GNU General Public License . # # USAGE: # % sms_biff [-p|-s] # # DESCRIPTION: # Just as xbiff extends the biff concept (of instant notification of email # arrivial) from tty to X11, sms_biff extends the concept to SMS messaging. # If you set up your email filtering to call this script on a copy of incoming # messages, it'll send a terse version of each message to your cell phone via # SMS. # # sms_biff sends SMS messages via your provider's email-to-SMS gateway. It # doesn't currently have the ability to send SMS messages via web forms. # # Some email-to-SMS gateways extract the contents of the From: and Subject: # headers and add them to the SMS message body. If this is the case with your # provider (as it is now, but was not previously the case with Sprint PCS), # then you can use sms_biff without setting any special options. It will use # these standard headers to convey this information in the SMS email. # # If your provider doesn't do its own From: and/or Subject: extraction into # SMS message bodies, you can have sms_biff do this itself, by setting the # sms_biff_BODY_FROM_CONSTRUCT and/or sms_biff_BODY_SUBJECT_CONSTRUCT # environment variables to "complete". These variables will cause sms_biff to # make "F:" and "S:" headers, respectively (header names shortened to save SMS # message space), and put them in the SMS message body. The content of these # headers will be truncated to the value of the sms_biff_BODY_{FROM,SUBJECT}_ # TOTAL_MAX environment variables (see below). # # If your provider unconditionally reserves space for the pseudo-headers it # generates in SMS message bodies (Sprint used to do this, but no longer does # as of January 2004), look into the "continuation" option of the # sms_biff_BODY_*_CONSTRUCT variables. # # After the constructed body headers, if any, sms_biff will append as much of # the message body as will fit within sms_biff_MAX_SMS_LEN. # # To use sms_biff, the only thing you _must_ do is make sure the # sms_biff_SMS_EMAIL_ADDRESS environment variable is set to your phone's SMS # email address. You can also optionally set the other environment variables # described in full below. For an example of how to call this script from # procmail (note you shouldn't call it directly from your .forward file, or # you'll get SMS notifications, but lose your actual mails!), see the sms_biff # entry on my Software page (URL above). # # Note that this script requires the MailTools and MIME-tools module # collections and the Net::SMTP module, part of the libnet module collection. # As usual, these are available from CPAN. # # COMMANDLINE OPTIONS: # -p # Just print the constructed SMS email to stdout rather than emailing it. # # -s # See a log of Net::SMTP's SMTP transactions on stderr while it sends. # # ENVIRONMENT VARIABLES: # MAILADDRESS # If sms_biff is not doing SMTP envelope From forging (see # sms_biff_FORGE_ENVELOPE_FROM below), it will use your own email address # (your main address, not your SMS email address) as the envelope From. It # will guess your address with the Mail::Util::mailaddress() function, but # if that function guesses wrong, you can hardcode its response by setting # this environment variable. # # sms_biff_BODY_FROM_CONSTRUCT # If set to "complete", sms_biff will construct its own "F:" pseudo-header # and put it in the SMS body. Use this setting if your cell phone provider # doesn't extract the From: info from emails sent to its email-to-SMS # gateway and make its own pseudo-header. (This setting was appropriate # for Sprint PCS until 2003-01-23.) # # If set to "continuation", sms_biff will assume that your cell phone # provider _does_ make its own From pseudo-header, but that it has a maximum # length that's objectionably low, and what's more, the space for the # provider's version of the pseudo-header gets reserved whether you use it # in full or not, subtracting from the space available for the actual body # of the SMS message (if this last condition isn't the case, there's no # reason to use this setting -- just use the "complete" setting). For # clarity, the continuation From header starts with "F\" rather than "F:" # (the mnemonic is that '\' is the usual line continuation character in # programming languages and config files). The maximum number of header # characters that will be extracted into this pseudo-header is # $sms_biff_BODY_FROM_TOTAL_MAX - $sms_biff_BODY_FROM_PROVIDER_MAX. (This # setting was appropriate for Sprint PCS between 2003-01-23 and 2004-01-21.) # # If set to some other value, or if not set, sms_biff will rely on the cell # phone provider to construct its own pseudo-header based on the headers # (and/or SMTP envelope) of the email we send to its email-to-SMS gateway. # (This setting is appropriate for Sprint PCS since 2003-01-23, if you don't # mind Sprint's 32-character maximum for its pseudo-header.) # # Note that if sms_biff_FORGE_ENVELOPE_FROM is "yes" or "auto" and our # attempt to forge fails (e.g. because the email being processed is from a # spammer using an unresolvable domain name), sms_biff_BODY_FROM_CONSTRUCT # will go into "complete" mode regardless of what you had set it to. # # sms_biff_BODY_FROM_PROVIDER_MAX # If your cell phone provider constructs its own From pseudo-header but cuts # off the content at a certain number of characters, set this value to that # maximum. Until January 2004, for instance, the appropriate value for # Sprint was 32. If your provider doesn't impose a maximum, you can leave # this unset (same as explicitly setting it to 0). # # sms_biff_BODY_FROM_TOTAL_MAX # If sms_biff_BODY_FROM_CONSTRUCT is in "complete" mode, this variable # defines the maximum number of characters we'll extract from the incoming # email's From: header and put in the "F:" pseudo-header we construct. # # If sms_biff_BODY_FROM_CONSTRUCT is in "continuation" mode, this variable # defines the maximum number of From: header characters that can be # extracted to appear in the provider's pseudo-header and our continuation # pseudo-header _combined_. # # If sms_biff_BODY_FROM_CONSTRUCT isn't set to one of those values, this # variable isn't used. # # If you don't wish to impose a maximum length on this header, you can set # this variable to 0. If not set, it will default to 74 (based on a # full-terminal-width 80 column (email-address-only) From line minus the # space for "From: " and 81st-column newline). # # sms_biff_BODY_SUBJECT_CONSTRUCT # sms_biff_BODY_SUBJECT_PROVIDER_MAX # sms_biff_BODY_SUBJECT_TOTAL_MAX # These are just like the corresponding ..._FROM_... variables, but # substitue "Subject" for "From", "S:" for "F:", "S\" for "F\", and 71 for # 74. # # As of January 2004, Sprint no longer cuts off From: content at a certain # length, but still does so for the Subject:, so # sms_biff_BODY_SUBJECT_PROVIDER_MAX defaults to 32 rather than 0. Sprint # will properly leave out the text "Subject: " if the SMS email doesn't # contain a subject header, so the cleanest way to get around their # 32-character limit (and use the space-saving label "S:" rather than # "Subject: ") is to set sms_biff_BODY_SUBJECT_CONSTRUCT to "complete". # # sms_biff_ELIDE_QUOTED_IN # A regular expression determining how the elide_quoted filter (see below) # recognizes quote and attribution lines. Defaults to # "(^[A-Z].*:\s*\n)?(^ ?[>|].*\n)+". # # Note that this regexp is used with Perl's /g and /m modifiers, as follows: # # $text =~ s/$sms_biff_ELIDE_QUOTED_IN/$sms_biff_ELIDE_QUOTED_OUT/gm; # # sms_biff_ELIDE_QUOTED_OUT # When the elide_quoted filter is used, any strings matching the # sms_biff_ELIDE_QUOTED_IN regexp will be replaced with this text. Defaults # to ">...\n". # # sms_biff_EMPTY_BODY_FILLER # Up until January 2004, Sprint PCS had a bug in their email-to-SMS # gateway where if it received a mail with an empty body (e.g. if someone # sent you a message contained entirely in the Subject: line), the SMS would # consist of just the From: pseudo-header and then as much of the Received: # headers as would fit in the 91-character body limit (just a portion of the # first one, generally). The Subject: pseudo-header would not appear. # # To work around a bug like this, you can set this variable. It will cause # a filler string to be put in the SMS email body if it would otherwise be # blank. If set to "1", a default filler string will be used, "[fake body # to work around empty body bug in email-to-SMS gateway]". If set to any # other value, that value will be used as the filler string. # # sms_biff_FILTER_BODY # If the entire body of the incoming email won't fit within the # sms_biff_MAX_SMS_LEN limit, try compacting it with the specified filters. # # The following filters are built in to sms_biff and do not require any # additional modules: # # elide_quoted # Converts blocks of quoted lines in replies, along with the attribution # line, into ">...". For instance, the following input: # # On February 3, 2005, Dan Harkless wrote: # > # > What time are we getting together on Saturday again? # # 8 PM. # # > What should I wear? # # Clothes. # # Will be converted to the following output: # # >... # 8 PM. # # >... # Clothes. # # Recognizing quoted and attribution lines can't be done without the # possibility of a few false positives or false negatives, but you can # tailor how sms_biff does this with the sms_biff_ELIDE_QUOTED_IN # variable. Also, sms_biff_ELIDE_QUOTED_OUT can be used to change the # ">...\n" string to something else. # # rm_pgp # Removes these lines: # # -----BEGIN PGP SIGNED MESSAGE----- # Hash: # # # and these lines: # # -----BEGIN PGP SIGNATURE----- # # -----END PGP SIGNATURE----- # # from the message. # # rm_ws # Removes all whitespace and CausesWordBoundariesToBeMarkedWithCapitals. # # The filters below are functions implemented by particular Perl modules # available from CPAN. Trying to use one of these filters without # installing its appropriate module will have no effect, other than causing # sms_biff to output a warning. But conversely there's no need to install # modules for filters you don't plan to use -- sms_biff uses conditional # 'require's for these, not 'use's. Here are the module-based filters: # # keywords # This is the function from the Lingua::EN::Keywords module. We prefix # the space-separated list of keywords output by this function with # "KW: ". # # Note that I do _not_ recommend installing version 2.0 of # Lingua::EN::Keywords. It has a heavy-duty prerequisite tree of 9 CPAN # packages, most of which are specific to various non-English languages. # In addition, it has bugs that cause it to return "keywords" containing # spaces, containing repeated substrings, or consisting entirely of # non-word characters. Worse, its prerequisite module # Lingua::EN::Tagger has a bug that will cause your procmail log (or # equivalent) to be filled with errors about an uninitialized value # being used on line 935 of Tagger.pm. # # Luckily, Lingua::EN::Keywords 1.3 is still available from CPAN (on # search.cpan.org, use the "Other Releases" pulldown menu). It does not # suffer from the above bugs, and has no special prerequisites. # # SqueezeText # This is the function from the Lingua::EN::Squeeze module. Its output # is somewhat cryptic, so if you're going to use it, you should read its # documentation to familiarize yourself with the abbreviations it uses. # # (Note that support for the module's $SQZ_OPTIMIZE_LEVEL control is # unnecessary because our rm_ws filter, below, has the same effect.) # # summarize # This is the function from the Lingua::EN::Summarize module. # # If you'd like to try more than one filter, you can separate them with # commas, as in: # # sms_biff_FILTER_BODY = "rm_ws,summarize,keywords" # # What that will do is try each filter in turn to see if it can compact the # incoming email body text small enough to fit within the remaining bytes of # the SMS body. If the filter being tried does not succeed in doing this, # the body text will be unchanged and the next filter will be tried. # # At this point you're probably thinking that it'd be nice to be able to # chain multiple filters together to see if _together_ they'd be able to # compact an email body small enough. Well, indeed you can do this. Just # separate the filters with '|'s: # # sms_biff_FILTER_BODY = "summarize|keywords|rm_ws" # # Unlike with comma-separated filters, the effect of these filters will be # cumulative and the length of the filtered body won't be checked to see if # it's small enough until the _end_ of the pipeline. # # In fact, the sms_biff_FILTER_BODY variable is actually not a # comma-separated list of filters, but a comma-separated list of filter # pipelines. Before I added MIME parsing and added the elide_quoted filter, # I used to use the following setting in my .procmailrc: # # sms_biff_FILTER_BODY = \ # "rm_ws,"\ # "SqueezeText,"\ # "SqueezeText|rm_ws,"\ # "summarize,"\ # "summarize|rm_ws,"\ # "summarize|SqueezeText,"\ # "summarize|SqueezeText|rm_ws,"\ # "keywords,"\ # "keywords|rm_ws,"\ # "keywords|SqueezeText,"\ # "keywords|SqueezeText|rm_ws" # # As you can see, the pipelines are organized in increasing order of # destructiveness. Also note that rm_ws is always put at the _end_ of # pipelines. This is because further filters wouldn't be able to do # anything useful with text where the whitespace word boundaries have been # removed. # # Note that even if the very final pipeline in the list fails to compact the # body small enough to fit in the remaining SMS body bytes, it'll still be # used (and the results will get truncated). # # If not set, sms_biff_FILTER_BODY defaults to "elide_quoted|rm_pgp" (these # days I use this default rather than the fancy setting above). # # sms_biff_FILTER_MAX_INPUT_LEN # Some of the filters are extremely slow on large inputs. Therefore, text # will be truncated to $sms_biff_FILTER_MAX_INPUT_LEN bytes prior to being # sent through the filter pipelines. This variable defaults to 1048576 (1 # MiB). If you set it to 0, that means "no limit". # # sms_biff_FILTER_SUBJECT # If the entire subject of the incoming mail won't fit within the # sms_biff_BODY_SUBJECT_TOTAL_MAX limit, try sending it through the # specified filters as described for sms_biff_FILTER_BODY above. # # I use the following setting in my .procmailrc: # # sms_biff_FILTER_SUBJECT = \ # "rm_ws,"\ # "SqueezeText,"\ # "SqueezeText|rm_ws" # # If not set, sms_biff_FILTER_SUBJECT defaults to "rm_ws". # # sms_biff_FORGE_ENVELOPE_FROM # If set to "yes", sms_biff will forge the SMTP envelope From address as # being the address of the person who sent you the email sms_biff is # processing. (This setting is not recommended, as explained below.) # # If set to "no", or to an unrecognized value, the envelope From will be # your address, derived as described under the MAILADDRESS variable above. # # If set to "auto", or if not set, sms_biff will only forge the envelope # From if the SMTP server it is talking to has a domain name ending in # "messaging.sprintpcs.com". (If there are other providers that require # envelope From forging, let me know, and I'll add their domains to the # regexp that the "auto" setting uses.) # # Do *NOT* cause the envelope From to be forged unless your cell phone # provider's email-to-SMS gateway requires it, as Sprint's does. If your # provider's gateway properly extracts From: info from the message header, # then you don't need this. It's dangerous to forge the envelope From # because it's where any non-delivery notifications will be sent. # # For instance, if sms_biff_SMTP_SERVER is pointing at your own (or your # ISP's) server, then if your cell phone provider's email-to-SMS gateway is # temporarily down, then the people who sent you the emails sms_biff is # notifying you of will be spammed with "could not send message for past # hours" or full-blown bounce messages. And if sms_biff processes a message # _from_ a spamer using a legitimate address (granted, this is rare), then # these non-delivery notifications will additionally leak your SMS email # address to them. # # If you are a Sprint PCS user, it's recommended to not set this variable, # and to set sms_biff_SMTP_SERVER as discussed under its description below. # # sms_biff_MAX_SMS_LEN # The maximum length SMS message allowed by your provider (or the maximum # you're comfortable sending, if you're unlucky enough to have a provider # that charges you by the byte). If you have some kind of bizarre provider # that has *no* SMS size limit, you can set this to 0. If not set, it # defaults to the standard limit of 160 bytes. # # sms_biff_OMIT_LIMIT_MARKER # At the end of Subject: header lines that exceed $sms_biff_BODY_SUBJECT_ # PROVIDER_MAX, and at the end of the body, if it exceeds $sms_biff_MAX_SMS_ # LEN, sms_biff appends the string "^LiMiT InCrEaSeD^". If you ever see # that rather distinctive-looking string, or the beginning of it, in one of # your SMS notifications, you'll know that your provider has increased one # of their limits, and you can fix your environment variables appropriately. # # However, this is only appropriate if you've really set those # ...MAX... variables according to your provider's actual limits. If you've # set the variables artificially low because your provider charges you per # byte for SMSes, and you want to save money, then you don't want these # strings added, because they'll appear in your mails and you'll be charged # for them. In this case, set sms_biff_OMIT_LIMIT_MARKER (to any value). # # Note that we never append "^LiMiT InCrEaSeD^" to From: addresses that # exceed $sms_biff_BODY_FROM_PROVIDER_MAX, because that would result in an # illegal address. In the future we may add a special option to append # ".LiMiT.InCrEaSeD" to these email addresses, but even that would only be # usable if your $sms_biff_SMTP_SERVER is one (like # mx.messaging.sprintpcs.com, currently) that allows nonexistent domain # names in From: addresses (many don't, as an anti-spam measure). # # sms_biff_SMS_EMAIL_ADDRESS # This environment variable is the only one that's always required -- it # needs to be set to the email address used to send SMS messages to your # cell phone. If you're not sure what this email address would be for your # provider, read . # # If you're using sms_biff_FORGE_ENVELOPE_FROM = "yes" (or "auto" with # Sprint), please be careful not to get this wrong. If you put an address # that's going to bounce, the bounces will go to the people who are sending # you mail rather than to you. This is because sms_biff forges the envelope # From (which the RFCs define as the place to send bounces) because that's # what Sprint's email-to-SMS gateway looks at (rather than the body From:). # One way to avoid this possibility is to set your sms_biff_SMTP_SERVER (see # below) to Sprint's MX server (since that way the incorrect address will be # discovered during the SMTP transaction rather than in the future). # # sms_biff_SMTP_SERVER # If this variable is set, it'll be used as the outgoing SMTP server. If # not set, or if injecting the mail into this server fails, we'll next try # localhost. If that fails, we'll try to let Net::SMTP use its default SMTP # server(s), which are set via the Net::Config module. On my Red Hat # system, with the perl-libnet RPM installed, there's no man page for # Net::Config, but you can edit or run perldoc on # /usr/lib/perl5/vendor_perl/5.6.1/Net/Config.pm (this path may vary on your # system). # # For the fastest possible notification of incoming emails, you might # consider setting this variable to (one of) the MX host(s) of your # email-to-SMS gateway domain, rather than having notifications make a hop # through your own mail server. For Sprint, this is currently # mx.messaging.sprintpcs.com, but of course this could change at any # time. Perhaps in the future I'll add an option to have sms_biff do an MX # lookup of the gateway domain and try contacting the MX hosts in priority # order. # # Using your cell phone provider's MX is STRONGLY recommended if you're # using sms_biff_FORGE_ENVELOPE_FROM = "yes" (or "auto" with Sprint). If # you don't do that, then when your own mail server temporarily can't # contact your provider's email-to-SMS gateway, non-delivery notices will be # spammed out to the people sending you mail. Currently Sprint's gateway # doesn't do any internal queueing, so it's safe to use a forged address # when sending to it. If the provider's email-to-SMS gateway is temporarily # down, sms_biff will try again with localhost, and then the servers set via # Net::Config, as described above, and if sms_biff_FORGE_ENVELOPE_FROM=auto, # then when backing off to the additional servers, it will not forge, and # sms_biff_BODY_FROM_CONSTRUCT will be forced to "complete". # # You can optionally set this variable to be a comma-separated list of # servers rather than a single one -- they'll be tried in order until one # succeeds, prior to moving on to trying "localhost". And if it tickles # your fancy, you can name the variable sms_biff_SMTP_SERVERS (plural) # rather than sms_biff_SMTP_SERVER. # # sms_biff_UNLABELED_HEADERS # If set to "space", pseudo-headers we generate will not be in the form # described elsewhere in this documentation, "{F,S}{:,\}", # but rather, simply ": ", the same style as the pseudo-headers # Sprint generated until 2004-01-22. # # If set to "newline", the pseudo-headers will be a cross between the two # styles, looking like ":". # # If set to any other value, or if not set, we generate "F" or "S" labeled # headers as described elsewhere in this documentation. # # Other than saving 1 byte per pseudo-header, unlabeled headers are # especially recommended if you're using "continuation" construction. # "The latest release, version 1.2.: 3:" is a bit more readable than # "The latest release, version 1.2.: S\3". # # As of 2004-01-22, Sprint has changed the line-terminator character on # their pseudoheader lines (perhaps from CR or CRLF to just LF?), and in the # Inbox application of Pocket PC 2002 Phone Edition, these show up as square # boxes in the message folder view, and in the single-message view, they are # completely invisible, causing From: content to run right into the Subject: # content, and Subject: content to run right into the body (if letting # Sprint generate the Subject: pseudoheader). Therefore, if you're using # sms_biff_BODY_SUBJECT_CONSTRUCT = complete with a Pocket PC device (not # sure if other devices are affected), I recommend using the default setting # for sms_biff_UNLABELED_HEADERS rather than "space" or "newline" -- the # "S:" makes it easier to see the transition point between From: and # Subject: content in the single-message view. # # X_LOOP # To prevent mail loops (e.g. if your SMS email address is bouncing for some # reason), you should set this environment variable to "X-Loop: ", # where is some unique string. This header will be added to the # outgoing SMS email. You should then check for this header in your email # filter setup, and not call sms_biff on emails that contain it. Again, a # procmail-based example is available from my Software page. # # TO DO: # Optionally use a module for the HTML->text conversion? If it causes # semantic tag information to be used, allow the use of Lingua::EN:: # Summarize's filter=>'html'? Add support for optionally using HTML::Summary # rather than Lingua::EN::Summarize? # # DATE MODIFICATION # ========== ================================================================== # 2008-09-02 "use English qw(-no_match_vars)": avoid regex performance penalty. # 2006-06-04 Just added a link to the http://en.wikipedia.org/wiki/SMS_gateways # article I created in the docs of sms_biff_SMS_EMAIL_ADDRESS. # 2005-12-04 Clarified sms_biff_SMS_EMAIL_ADDRESS and sms_biff_SMTP_SERVER docs # to say sms_biff_FORGE_ENVELOPE_FROM=auto only dangerous w/ Sprint. # 2005-02-04 Improved HTML -> plain text conversion (whitespace, entities...). # 2005-02-03 Call MIME::Words::decode_mimewords() on the Subject (shouldn't # need it for the From, since we throw away the real name part). # 2005-02-03 Added rm_pgp filter and changed sms_biff_FILTER_BODY's default to # "elide_quoted|rm_pgp". # 2005-02-03 Since rm_ws doesn't change ALLCAPSWORD to Allcapsword, it needs to # pass input that's _entirely_ in caps through unchanged to not # destroy legibility. # 2005-02-03 For consistency, all environment variables should be checked for # with defined(). This means that setting a variable to a value # Perl interprets as false may act differently now; if any setting # is made, including a setting to the emtpy string, it will be used. # 2005-02-03 Added elide_quoted filter and sms_biff_ELIDE_QUOTED_IN and _OUT. # Changed sms_biff_FILTER_BODY's default from blank to elide_quoted. # Changed sms_biff_FILTER_SUBJECT's default from blank to rm_ws. # 2005-02-03 MIME-tools was leaving msg-* temporary files sitting around after # we terminated if we didn't use MIME::Parser::output_to_core(1). # 2005-02-03 MIME::Entity::tidy_body(), unlike Mail::Internet::tidy_body(), is # a no-op; eliminate leading and trailing blank lines ourselves. # 2005-02-02 Now does MIME parsing. Requires MIME::Parser now rather than just # Mail::Header and Mail::Internet. Currently only one part from # multipart mails will be used, even if there would be room for text # from multiple parts. text/plain is preferred when available. # When unavailable, simple conversion to plain text is done for # text/enriched and text/html. When no text parts are available, # the MIME type and filename (when available) are printed in square # brackets. Increased sms_biff_FILTER_MAX_INPUT_LEN default from # 1024 to 1048576 now that non-text parts will no longer be sent # through the filters. # 2004-01-22 As of today, Sprint PCS is no longer unconditionally reserving 32 # characters for the From: and Subject: content. Additionally, # From: content now has no limit, while Subject: content still cuts # off at 32 characters, but only if the SMS email includes that # header (so I personally will be using # sms_biff_BODY_SUBJECT_CONSTRUCT = complete from now on). Changed # sms_biff_BODY_FROM_PROVIDER_MAX default to 0 and # sms_biff_MAX_SMS_LEN default to 160. Removed the TO DO item to # add the ability to use the Sprint web-to-SMS gateway, since this # is no longer necessary to get full 160-character usage in SMSes. # Also, Sprint fixed their empty body bug, so noted that it's no # longer necessary to set sms_biff_EMPTY_BODY_FILLER. Sprint also # changed the line terminator for their pseudoheader lines; # annotated sms_biff_UNLABELED_HEADERS appropriately. # 2003-10-07 If Lingua::EN::Keywords can't find enough keywords, it returns a # a list containing 'undef's. This requires us to iterate through # the list rather than using a simple "KW: @keywords" array # interpolation. Also, Lingua::EN::Keywords 2.0's prerequisite # package Lingua::EN::Tagger has a bug that causes voluminous # warnings to appear in the procmail log. Strengthened the already- # existing suggestion to use Lingua::EN::Keywords 1.3, not 2.0. # 2003-09-16 There's a small chance someone could be using sms_biff on a box # connected to the Internet via a Sprint PCS cell modem, so # sms_biff_FORGE_ENVELOPE_FROM = auto needs to check for servers # named *.messaging.sprintpcs.com, not just *.sprintpcs.com. # 2003-09-16 sms_biff_SMTP_SERVER can now be specified as sms_biff_SMTP_SERVERS # and can be a comma-separated list of servers to try one-by-one. # 2003-09-16 Realized while going through the mail to Postmaster on my system # that our default envelope From forging was causing non-delivery # notifications due to Sprint's email-to-SMS gateway being # temporarily down to be spammed out to people sending us mail. # Added a new variable called sms_biff_FORGE_ENVELOPE_FROM, whose # default setting of "auto" requires that Sprint users now set # sms_biff_SMTP_SERVER to mx.messaging.sprintpcs.com. # 2003-08-16 Noted that Lingua::EN::Keywords 1.3 is easier to install than 2.0. # 2003-03-01 Just added a TO DO to support Sprint's web-to-SMS gateway now that # they've fixed it, and a warning not to mistype your sms_biff_SMS_ # EMAIL_ADDRESS since bounces will go to people sending you mail. # 2003-02-25 When final filter of final pipeline wasn't able to shrink content # to within $limit, filtering done by final pipeline was being lost. # 2003-02-25 Added sms_biff_FILTER_MAX_INPUT_LEN due to the extreme slowness of # some of the filters on large binary attachments. # 2003-02-25 Changed sms_biff_FILTER_{BODY,SUBJECT} from being a series of # cumulative filters with special non-cumulative rules for some to # being a series of non-cumulative pipelines, the individual filters # of which are unconditionally cumulative. Because this means # longer settings for these variables, changed the filter names from # being module names to being the names of the functions within # those modules. Added an internal filter, rm_ws. It renders the # old sms_biff_FILTER_SQZ_OPTIMIZE_LEVEL variable unnecessary, so # removed that. # 2003-02-25 Added sms_biff_FILTER_BODY, sms_biff_FILTER_SUBJECT, and # sms_biff_FILTER_SQZ_OPTIMIZE_LEVEL. # 2003-02-25 Changed -p to print the message to stdout rather than stderr (was # previously mimicking -s / Net::SMTP's use of stderr), with a \n. # 2003-02-24 Added sms_biff_EMPTY_BODY_FILLER to work around bug Sprint has in # their email-to-SMS gateway on emails with blank bodies. # 2003-02-09 Need to call $header->unfold(), or long header lines get folded # and inserted into the SMS with embedded "\n " sequences, # whether or not those lines were folded in original incoming mail. # 2003-02-08 If sms_biff_UNLABELED_HEADERS is set to "space", generate pseudo- # headers just like Sprint's, rather than using labels like "F:" # and "S\". If set to "newline", make them like Sprint's but ending # with a colon and newline rather than a colon and space. # 2003-02-08 Sprint might come to their senses and stop unconditionally # reserving space for their pseudo-headers. If they do this, we'll # find out, because now the string "^LiMiT InCrEaSeD^" appears just # after the limit point (of the Subject pseudo-header and the # overall body, but not, currently, for the From pseudo-header). If # you have your limits set artificially low because your provider # charges you for SMSes by the byte, you can set sms_biff_OMIT_ # LIMIT_MARKER to suppress this string. # 2003-02-08 I previously was using sms_biff_MAKE_BODY_SUBJECT because I didn't # want my subjects lopped off at 32 characters. However, now that I # know that Sprint is reserving the 34 characters for their version # of the Subject pseudo-header whether I use them or not, I see that # this is wasteful of body space. Implemented a new behavior for # the body headers called "continuation". In this mode, the first # 32 characters will go in Sprint's reserved-space pseudo-header, # and if there are more characters remaining, they'll go in a # pseudo-header we construct. These continuation headers are # indicated by "F\" and "S\" rather than "F:" and "S:". Sprint's # limit for its own pseudo-header is encoded in the new # sms_biff_BODY_{FROM,SUBJECT}_PROVIDER_MAX variables. Split the # old sms_biff_MAX_BODY_HEADER_LEN into individual # sms_biff_BODY_{FROM,SUBJECT}_TOTAL_MAX variables for per-header # control of the overall maximum. Renamed sms_biff_MAKE_BODY_ # {FROM,SUBJECT} to sms_biff_BODY_{FROM,SUBJECT}_CONSTRUCT to comply # with the naming of the new variables. The old behavior, gotten by # setting those variables to any nonzero value, of creating our own # complete pseudo-headers is now achieved by setting them to # "complete". Setting them to "continuation" gets the # above-described new behavior. # 2003-02-08 I discovered that when Sprint starting adding their own From: and # Subject: pseudo-headers, they implemented it in a really stupid # way. Instead of decreasing the potential body space by the amount # of space _actually_used_ by these pseudo-headers in a particular # SMS message, they simply always reserve 68 characters (32 # characters for the From + 2 characters for its ": " + 32 # characters for the Subject + 2 characters for its ": ") whether # they get used or not. Sprint has not announced this, fixed their # online documentation, or fixed the "characters remaining" counter # at http://messaging.sprintpcs.com, but my testing has revealed # that the SMS body limit is now 91 characters, rather than the old # 160 (you might notice that 91 + 68 = 159, not 160 -- perhaps the # 160th character is a newline or NUL terminator and the old limit # was actually 159, not 160). Changed the default value of # sms_biff_MAX_SMS_LEN to 91. # 2003-01-24 Renamed -d(ebug) to -p(rint) and added -s to show Net::SMTP's # debugging output. # 2003-01-23 Renamed all our environment variables to be prefixed with # "sms_biff_". I was going to still accept SMS_BIFF_EMAIL as a # synonym for the new sms_biff_SMS_EMAIL_ADDRESS for backwards # compatibility, but it occurred to me that it's probably better to # force it to fail so that people will notice the change and will # rename their other, optional, variables, whose failure otherwise # might not be noticed for some time. # 2003-01-23 Because we now let the email-to-SMS gateway do its own From: # extraction by default, we need to change from using Mail::Mailer # to using Net::SMTP to send, so we can catch problems where the # forged return address is rejected by the mail server. # 2003-01-23 Today Sprint started extracting From: and Subject: headers from # emails sent to #@messaging.sprintpcs.com. This resulted in double # From info, one the email's original sender and one your own email # address. Now we assume by default that the email-to-SMS gateway # will do its own From: and Subject: extraction unless # sms_biff_MAKE_BODY_FROM and/or sms_biff_MAKE_BODY_SUBJECT are set. # 2002-11-23 The Handspring Treo SMS notification popup doesn't let you scroll # down to see the rest of the message that doesn't fit on the screen # (though of course the SMS app itself allows this). Therefore, # don't put a blank line after our pseudo-header, so we can see as # much of the message as possible on the notification screen. # 2002-11-23 Renamed MAX_LINE_LEN to MAX_HEADER_LEN and improved documentation. # 2002-11-22 Original. ## Modules and pragmas ######################################################### use English qw(-no_match_vars); # allow use of names like @ARG rather than @_ use File::Basename; # for basename() use Getopt::Std; # for getopts() use locale; # do accented characters correctly in lc() etc. use Mail::Address; # for email address parsing routines use Mail::Util; # for mailaddress() use MIME::Parser; # MIME parsing and header manipulation routines use MIME::Words; # for decode_mimewords() use Net::SMTP; # for mail sending routines # Use only while debugging (due to major performance hit): #use diagnostics; # turn on -w and output verbose versions of warnings ## Subroutines ################################################################# sub construct_header { my $header_name = shift; my $header_val = shift; my $mode = shift; my $provider_max = shift; my $total_max = shift; chomp $header_val; if ($provider_max == 0) { $provider_max = length($header_val); } if ($total_max == 0) { $total_max = length($header_val); } # Real outgoing email header. if ($mode eq "continuation" or $mode ne "complete") { $sms_header .= "$header_name: "; if (length($header_val) > $provider_max and $header_name ne "From") { $sms_header .= substr($header_val, 0, $provider_max) . $limit_increased; } else { $sms_header .= $header_val; } $sms_header .= "\n"; } # SMS body pseudo-header. my $pseudo_header = ""; if ($mode eq "complete" or ($mode eq "continuation" and length($header_val) > $provider_max)) { if ($sms_biff_UNLABELED_HEADERS ne "newline" and $sms_biff_UNLABELED_HEADERS ne "space") { $pseudo_header = substr($header_name, 0, 1); # 'F' or 'S' if ($mode eq "complete") { $pseudo_header .= ":"; } else { # $mode eq "continuation" $pseudo_header .= "\\"; } } if ($mode eq "complete") { $pseudo_header .= substr($header_val, 0, $total_max); } else { # $mode eq "continuation" $pseudo_header .= substr($header_val, $provider_max, $total_max - $provider_max); } if ($sms_biff_UNLABELED_HEADERS eq "newline") { $pseudo_header .= ":\n"; } elsif ($sms_biff_UNLABELED_HEADERS eq "space") { $pseudo_header .= ": "; } else { $pseudo_header .= "\n"; } } return $pseudo_header; } sub deliver { my $smtp_server = shift; my $attempting_forge_on_this_server; if ($sms_biff_FORGE_ENVELOPE_FROM eq "yes" or ($sms_biff_FORGE_ENVELOPE_FROM eq "auto" and defined $smtp_server and $smtp_server =~ /messaging\.sprintpcs\.com$/)) { $attempting_forge_on_this_server = 1; $attempting_forge_at_all = 1; } my $smtp; if (defined $smtp_server) { $smtp = Net::SMTP->new($smtp_server, Debug=>$opt_s); } else { $smtp = Net::SMTP->new(Debug=>$opt_s); } if (not $smtp) { return 0; } if (not $attempting_forge_on_this_server or not $smtp->mail($from_address)) { # Either we weren't trying to forge, or the mail server didn't let us. if ($attempting_forge_at_all and $sms_biff_BODY_FROM_CONSTRUCT ne "complete") { # We can't forge the email address specified, so we'll have to # convey the From: info with an "F:" body header (if we haven't done # that already, or if we've only done it partially, in # "continuation" mode). if ($sms_biff_BODY_FROM_CONSTRUCT eq "continuation" and length($from_address) > $sms_biff_BODY_FROM_PROVIDER_MAX) { $total_max = $sms_biff_BODY_FROM_PROVIDER_MAX; } else { $total_max = $sms_biff_BODY_FROM_TOTAL_MAX; } $sms_body = construct_header("From", $from_address, "complete", $sms_biff_BODY_FROM_PROVIDER_MAX, $total_max) . $sms_body; $sms_email = $sms_header . "\n" . $sms_body; # Make sure we don't stick on this last-minute body From a second # time if we come through here again with a different $smtp_server # setting. $sms_biff_BODY_FROM_CONSTRUCT = "complete"; } # Now use Mail::Util::mailaddress() to guess the user's actual email # address (wrong guesses can be overridden by setting the MAILADDRESS # environment variable). if (not $smtp->mail(Mail::Util::mailaddress())) { # Hmm, that didn't work either. Panic and try to re-use the To: # address as the From: address. $smtp->mail($sms_biff_SMS_EMAIL_ADDRESS) || return 0; } } $smtp->recipient($sms_biff_SMS_EMAIL_ADDRESS) || return 0; $smtp->data($sms_email) || return 0; $smtp->quit(); } sub filter { my $text = shift; my $limit = shift; my $filter_list = shift; if ($filter_list and length($text) > $limit) { if ($sms_biff_FILTER_MAX_INPUT_LEN != 0) { $text = substr($text, 0, $sms_biff_FILTER_MAX_INPUT_LEN); } my $prefiltered_text = $text; my @pipelines = split /,/, $filter_list; PIPELINE: foreach $pipeline (@pipelines) { my @filters = split /\|/, $pipeline; $text = $prefiltered_text; foreach $filter (@filters) { if ($filter eq "keywords") { require Lingua::EN::Keywords; my @keywords = Lingua::EN::Keywords::keywords($text); # Lingua::EN::Keywords up through 2.0 has a bug / design # flaw that causes it to return 'undef's at the end of the # list if it can't find enough keywords. Therefore we can't # do a simple '$text = "KW: @keywords";', but rather have to # iterate through the list bombing out if we hit an undef. $text = ""; if (@keywords and defined($keywords[0])) { $text = "KW: " . shift(@keywords); foreach $keyword (@keywords) { if (defined($keyword)) { $text .= " $keyword"; } } } } elsif ($filter eq "elide_quoted") { $text =~ s/$sms_biff_ELIDE_QUOTED_IN/$sms_biff_ELIDE_QUOTED_OUT/gm; } elsif ($filter eq "rm_pgp") { $text =~ s/^-----BEGIN\ PGP\ SIGNED\ MESSAGE-----\n Hash:\ .*\n\n //gmx; $text =~ s/^-----BEGIN\ PGP\ SIGNATURE-----\n (.*\n)*? -----END\ PGP\ SIGNATURE-----\n //gmx; } elsif ($filter eq "rm_ws") { if ($text =~ /[a-z]/) { $text =~ s/\s+(.)/\u$1/g; } } elsif ($filter eq "SqueezeText") { require Lingua::EN::Squeeze; $text = Lingua::EN::Squeeze::SqueezeText(lc($text)); } elsif ($filter eq "summarize") { require Lingua::EN::Summarize; $text = Lingua::EN::Summarize::summarize($text); } else { print STDERR "$progname: Filter \"$filter\" unknown.\n"; } # Some of the filters can return empty output on certain inputs. if (length($text) == 0) { $text = $prefiltered_text; } } if (length($text) <= $limit) { last PIPELINE; } } } return $text; } sub my_die { print STDERR "$progname: @ARG\n"; exit 1; } ## Environment variables / constant defaults ################################### $progname = basename($PROGRAM_NAME); $sms_body = ""; $sms_header = ""; if (defined $ENV{sms_biff_BODY_FROM_CONSTRUCT}) { $sms_biff_BODY_FROM_CONSTRUCT = $ENV{sms_biff_BODY_FROM_CONSTRUCT}; } else { $sms_biff_BODY_FROM_CONSTRUCT = ""; } if (defined $ENV{sms_biff_BODY_FROM_PROVIDER_MAX}) { $sms_biff_BODY_FROM_PROVIDER_MAX = $ENV{sms_biff_BODY_FROM_PROVIDER_MAX}; } else { $sms_biff_BODY_FROM_PROVIDER_MAX = 0; } if (defined $ENV{sms_biff_BODY_FROM_TOTAL_MAX}) { $sms_biff_BODY_FROM_TOTAL_MAX = $ENV{sms_biff_BODY_FROM_TOTAL_MAX}; } else { $sms_biff_BODY_FROM_TOTAL_MAX = 74; } if (defined $ENV{sms_biff_BODY_SUBJECT_CONSTRUCT}) { $sms_biff_BODY_SUBJECT_CONSTRUCT = $ENV{sms_biff_BODY_SUBJECT_CONSTRUCT}; } else { $sms_biff_BODY_SUBJECT_CONSTRUCT = ""; } if (defined $ENV{sms_biff_BODY_SUBJECT_PROVIDER_MAX}) { $sms_biff_BODY_SUBJECT_PROVIDER_MAX = $ENV{sms_biff_BODY_SUBJECT_PROVIDER_MAX}; } else { $sms_biff_BODY_SUBJECT_PROVIDER_MAX = 32; } if (defined $ENV{sms_biff_BODY_SUBJECT_TOTAL_MAX}) { $sms_biff_BODY_SUBJECT_TOTAL_MAX = $ENV{sms_biff_BODY_SUBJECT_TOTAL_MAX}; } else { $sms_biff_BODY_SUBJECT_TOTAL_MAX = 71; } if (defined $ENV{sms_biff_ELIDE_QUOTED_IN}) { $sms_biff_ELIDE_QUOTED_IN = $ENV{sms_biff_ELIDE_QUOTED_IN}; } else { $sms_biff_ELIDE_QUOTED_IN = '(^[A-Z].*:\s*\n)?(^ ?[>|].*\n)+'; } if (defined $ENV{sms_biff_ELIDE_QUOTED_OUT}) { $sms_biff_ELIDE_QUOTED_OUT = $ENV{sms_biff_ELIDE_QUOTED_OUT}; } else { $sms_biff_ELIDE_QUOTED_OUT = ">...\n"; } if (defined $ENV{sms_biff_FILTER_BODY}) { $sms_biff_FILTER_BODY = $ENV{sms_biff_FILTER_BODY}; } else { $sms_biff_FILTER_BODY = "elide_quoted|rm_pgp"; } if (defined $ENV{sms_biff_FILTER_SUBJECT}) { $sms_biff_FILTER_SUBJECT = $ENV{sms_biff_FILTER_SUBJECT}; } else { $sms_biff_FILTER_SUBJECT = "rm_ws"; } if (defined $ENV{sms_biff_EMPTY_BODY_FILLER}) { $sms_biff_EMPTY_BODY_FILLER = $ENV{sms_biff_EMPTY_BODY_FILLER}; if ($sms_biff_EMPTY_BODY_FILLER eq "1") { $sms_biff_EMPTY_BODY_FILLER = "[fake body to work around empty body bug in email-to-SMS gateway]"; } } if (defined $ENV{sms_biff_FILTER_MAX_INPUT_LEN}) { $sms_biff_FILTER_MAX_INPUT_LEN = $ENV{sms_biff_FILTER_MAX_INPUT_LEN}; } else { $sms_biff_FILTER_MAX_INPUT_LEN = 1048576; } if (defined $ENV{sms_biff_FORGE_ENVELOPE_FROM}) { $sms_biff_FORGE_ENVELOPE_FROM = $ENV{sms_biff_FORGE_ENVELOPE_FROM}; } else { $sms_biff_FORGE_ENVELOPE_FROM = "auto"; } if (defined $ENV{sms_biff_MAX_SMS_LEN}) { $sms_biff_MAX_SMS_LEN = $ENV{sms_biff_MAX_SMS_LEN}; } else { $sms_biff_MAX_SMS_LEN = 160; } if (defined $ENV{sms_biff_OMIT_LIMIT_MARKER}) { $limit_increased = ""; } else { $limit_increased = "^LiMiT InCrEaSeD^"; } if (defined $ENV{sms_biff_SMS_EMAIL_ADDRESS}) { $sms_biff_SMS_EMAIL_ADDRESS = $ENV{sms_biff_SMS_EMAIL_ADDRESS}; } else { my_die "sms_biff_SMS_EMAIL_ADDRESS environment variable" . " must be set to address to send to! Aborting."; } if (defined $ENV{sms_biff_UNLABELED_HEADERS}) { $sms_biff_UNLABELED_HEADERS = $ENV{sms_biff_UNLABELED_HEADERS}; } else { $sms_biff_UNLABELED_HEADERS = ""; } ## Main ######################################################################## # Process commandline arguments. use vars qw($opt_p $opt_s); # eliminate warning if (not getopts("ps")) { print STDERR "Usage: $progname [-p|-s]\n"; # Don't exit -- we need to fail gracefully. } # Read the incoming mail from stdin. $parser = new MIME::Parser; $parser->output_to_core(1); # don't leave msg-* tempfiles around after we exit $entity = $parser->parse(\*STDIN) or my_die '$parser->parse(\*STDIN) failed.'; $header = $entity->head(); # If we don't do the following, then long header lines will get folded prior to # being returned by get() and will contain one or more "\n " sequences, # whether or not they were folded in the original mail itself. $header->unfold(); # Process the From: address (or equivalent) of the incoming email. foreach $header_name ("From", "From ", "Return-Path", "Sender", "Reply-To") { $from = $header->get($header_name); if ($from) { last; } } @from_addresses = Mail::Address->parse($from); if ($from_addresses[0]) { $from_address = $from_addresses[0]->address(); } if ($from_address) { # $from_address should always have a value except in contrived inputs... $sms_body .= construct_header("From", $from_address, $sms_biff_BODY_FROM_CONSTRUCT, $sms_biff_BODY_FROM_PROVIDER_MAX, $sms_biff_BODY_FROM_TOTAL_MAX); } # Add the To: header. $sms_header .= "To: $sms_biff_SMS_EMAIL_ADDRESS\n"; # Process the incoming mail's Subject: line, if any. $subject = $header->get("Subject"); if ($subject) { $subject = MIME::Words::decode_mimewords($subject); $subject = filter($subject, $sms_biff_BODY_SUBJECT_TOTAL_MAX, $sms_biff_FILTER_SUBJECT); $sms_body .= construct_header("Subject", $subject, $sms_biff_BODY_SUBJECT_CONSTRUCT, $sms_biff_BODY_SUBJECT_PROVIDER_MAX, $sms_biff_BODY_SUBJECT_TOTAL_MAX); } # Add the X-Loop header, if defined. $X_LOOP = $ENV{X_LOOP}; if ($X_LOOP) { chomp $X_LOOP; $sms_header .= "$X_LOOP\n"; } # Put the most desirable message part into $body as a string. @parts = $entity->parts_DFS(); $best_ranking = 999; for ($i = 0; $i <= $#parts; $i++) { $i_type = $parts[$i]->effective_type(); if ($i_type eq "text/plain") { $ranking = 1; } elsif ($i_type eq "text/enriched") { $ranking = 2; } elsif ($i_type eq "text/html") { $ranking = 3; } elsif ($i_type =~ m<^text/>) { $ranking = 4; } elsif ($i_type !~ /multipart/) { # This forces multipart/* as well as MIME-tools' synthesized # application/x-unparseable-multipart type up to ranking 6. $ranking = 5; } else { $ranking = 6; } if ($ranking < $best_ranking) { $best_ranking = $ranking; $best_ranking_index = $i; } } if ($parts[$best_ranking_index]->effective_type() =~ m<^text/>) { # TBD: Generalize sms_biff_FILTER_MAX_INPUT_LEN and only get up to that # limit via the I/O interface, rather than always making one big string? $body = $parts[$best_ranking_index]->bodyhandle()->as_string(); # Remove leading and trailing blank lines. $body =~ s/^(\s*\n)?(.*?)\s*$/$2\n/gs; if ($parts[$best_ranking_index]->effective_type() eq "text/enriched") { $body =~ s(.*?)()gsi; # remove parameters $body =~ s/<[^>]*>//g; # remove formatting tags } elsif ($parts[$best_ranking_index]->effective_type() eq "text/html") { $body =~ s/\n//g; # remove newlines in source $body =~ s()()g; # remove comments $body =~ s(]*)?>.*?)()gi; # remove scripts $body =~ s(]*)?>.*?)()gi; # remove inline stylesheets $body =~ s/<(blockquote|br|caption|dt|li|p|table|tr)( [^>]*)?>/\n/gi; $body =~ s/]*)?>/ /gi; # some tags make whitespace $body =~ s/<[^>]*>//g; # remove tags $body =~ s/[ \t]+/ /g; # collapse whitespace $body =~ s/^ //gm; # no spaces at BOL $body =~ s/ $//gm; # no spaces at EOL $body =~ s/\n+/\n/g; # remove blank lines $body =~ s/^\n//; # remove initial newline $body =~ s/&(#153|trade);/(TM)/g; # convert common entities $body =~ s/&(#14[56]|#821[67]|lsquo|rsquo);/'/g; $body =~ s/&(#14[78]|#822[01]|ldquo|rdquo);/"/g; $body =~ s/&(#15[01]|#821[12]|mdash|ndash);/--/g; $body =~ s/&(#169|copy);/(C)/g; $body =~ s/&(#174|reg);/(R)/g; $body =~ s/&/&/g; $body =~ s/>/>/g; $body =~ s/</head(); $body = "["; $body .= $part_header->mime_type(); if ($part_header->recommended_filename()) { $body .= "; name=" . $part_header->recommended_filename(); } $body .= "]"; } # TBD: Append more parts if we haven't made it to $sms_biff_MAX_SMS_LEN? if (length($body) == 0) { if (length($sms_body) == 0 and $sms_biff_EMPTY_BODY_FILLER) { # Work around bug like the one Sprint has in their email-to-SMS gateway. $sms_body = $sms_biff_EMPTY_BODY_FILLER . "\n"; } } else { if ($sms_biff_MAX_SMS_LEN == 0) { # No SMS size limit, so tack on the entire body of the incoming mail. $sms_body .= $body; } else { # This provider's SMSes are not of an unlimited length, so do filtering # and truncation as appropriate. When figuring the current length of # the message, we don't count space used by email headers that will be # extracted and stuck into the message body by the provider, since this # will vary over time and by provider. Just count the bytes we're # directly responsible for, and if we end up going over the limit # somewhat, the provider will do the truncation. (This is only a # problem if your provider, unlike Sprint prior to January 2004, does # dynamic rather than static reservation of body bytes for # pseudo-headers, and at worst means that the $limit_increased marker # will be harder to spot.) $room_left = $sms_biff_MAX_SMS_LEN - length($sms_body); if ($room_left > 0) { # Do filtering. $body = filter($body, $room_left, $sms_biff_FILTER_BODY); if (length($body) > $room_left) { # Do truncation. $body = substr($body, 0, $room_left) . $limit_increased; } $sms_body .= $body; } } } $sms_email = $sms_header . "\n" . $sms_body; if ($opt_p) { # Just print the mail to stdout. chomp $sms_email; $sms_email .= "\n"; print STDOUT $sms_email; } else { # Email the message. if ($ENV{sms_biff_SMTP_SERVER}) { @SMTP_servers = split /,/, $ENV{sms_biff_SMTP_SERVER}; } elsif ($ENV{sms_biff_SMTP_SERVERS}) { @SMTP_servers = split /,/, $ENV{sms_biff_SMTP_SERVERS}; } push @SMTP_servers, ("localhost", undef); foreach $smtp_server (@SMTP_servers) { if (deliver($smtp_server)) { $delivery_succeeded = 1; last; } } if (not $delivery_succeeded) { my_die "All attempts to deliver the SMS email failed. " . " Please ensure that either the sms_biff_SMTP_SERVER environment" . " variable or the Net::Config module has a valid SMTP server" . " setting. Aborting."; } }