├── CHANGELOG ├── LICENSE ├── README-BR.md ├── README.md ├── TODO.md └── sendEmail.pl /CHANGELOG: -------------------------------------------------------------------------------- 1 | 1.56 (Sep 8, 2009) 2 | - Fixed authentication errors caused by invalid base64 encoding. 3 | Thanks for dozens of people who reported this and even submitted 4 | patches! Sorry it took me so long to get this fixed :| 5 | - Updated authentication code so it would try both AUTH PLAIN and 6 | AUTH LOGIN methods before giving up. This should make it even 7 | more robust. 8 | - Detect " 90 | format and it will populate the appropriate fields in the email message. 91 | - All messages are now MIME encoded, even simple text ones. This makes the 92 | program flow much cleaner. 93 | - SMTP Auth is now supported with the -xu and -xp command line parameters. 94 | - You can create multi-line messages on the command line now by putting 95 | a '\n' into the message specified with the -m parameter. 96 | - Always use +0000 for timezone, and get the current time from gmtime() 97 | rather than localtime(). 98 | - Added some color to the help and documentation if the shell is Bash. 99 | - It now aborts with an error message if all file attachments can not be 100 | found before connecting to the remote smtp server. Previously if an 101 | attachment specified couldn't be opened it would simply send the message 102 | without the attachment. 103 | - Allow a single custom email header line to be specified with 104 | -o message-header=HEADER 105 | - Add a -o timeout=SECONDS option so you can set the timeout used for all 106 | network reads and writes. 107 | - Added ability to specify a "Reply-to:" header with -o reply-to=ADDRESS 108 | - Added ability to read message body from a file with -o message-file=FILE 109 | - Added a lot of new documentation available via --help TOPIC. 110 | 111 | 1.42 (June 06, 2003) 112 | - It was possible to send bare CR's, so I fixed that now. (Thanks to 113 | Jared Cheney for the bug report) 114 | - Patch from Jared Cheney to add the MIME-Version field to conform to RFC1521 115 | (made attachments work with more mail clients) 116 | 117 | 1.41 (Apr 22, 2003) 118 | - In some cases bare LF's were *still* getting sent, so I think I fixed it 119 | right this time. (Thanks to Buddy Nahay for that bug report and helping 120 | me test the fix!) 121 | - Fixed bare period encoding (it wasn't working in all cases either) 122 | - Changed HTML detection to require the to be at the beginning of 123 | a line. Otherwise any email with "" in the message would get 124 | tagged as being an html message. (Thanks to John Rouillard for that 125 | bug report!) 126 | 127 | 1.40 (Dec 04, 2002) 128 | - A patch from Paul Kreiner (J.P van Oyen also reported a similar fix): 129 | "This patch forces the timestamp string to always follow 130 | the HH:MM:SS convention required by the RFCs, even if the 131 | fields could be represented with a single digit. I also 132 | took the liberty of adding a global "timezone" variable at 133 | the top, and fixing a couple of typos." 134 | - A patch from Reidar Johansen that included several fixes, but notably 135 | he included a function called tz_offset that determines what timezone 136 | your in. 137 | - Fixed the signal handlers so they display a nice message 138 | - Added a quit() function 139 | - Added a printmsg() function to handle all printed/logged messages 140 | - Added a simple openLogFile() function 141 | - If running in Win32 it says "CTRL-Z" rather than "CTRL-D" 142 | - Applied fix from Yakov Lerner to fix bare period encoding 143 | - Check that attachments exist and are readable before trying to open them 144 | - Rewrote the connect() function 145 | - Rewrote the close/disconnect function 146 | - Made the -v option work with any number of -v's 147 | - Rewrote the read_server_response() function and renamed it to readServerResponse() 148 | - Did a bunch of little fixes so that perl -w won't complain 149 | - Redid the whole file logging stuff - now the printmsg function takes care of it, 150 | and it's no longer partially broken. 151 | - Added a global alarm variable for setting the alarm timeout, and changed 152 | it's default value to 60 rather than 15. 153 | - Fixed all the exit()'s so that sendEmail should now only exit with an 154 | error status of 0 if it successfully sent the email. 155 | - Added support for HTML email (Thanks again to Reidar Johansen) 156 | - Re-worked the mime-encoding function to work with -w and use strict. 157 | - Removed several die() calls in the file attachment process. 158 | - Put a fix in place that should cleanly replace any bare LF's with CRLF. 159 | 160 | 1.33 (Apr 16, 2001) 161 | - Fixed some typo's and problems with the logging option regarding the 162 | printing of attachment(s) names to the log file. 163 | - Fixed several comparisons with null strings. 164 | - Lots of little cleanup things. 165 | 166 | 1.32 (Aug 18, 2000) 167 | - Fixed a fairly serious error which in some circumstances would 168 | cause file attachments to be corrupted because the mime padding at 169 | the end of the attachment was not getting set correctly. 170 | 171 | 1.31 (Aug 9, 2000) 172 | - Totally rewrote all mime encoding code, it should now be 100% 173 | MIME/Base64 compliant, and it now opens files in binary mode in win32. 174 | This was a much bigger operation than it sounds like ;-) 175 | - Now it should work with LARGE file attachments. In v1.30 it had to 176 | load the whole file into memory before encoding it, now it encodes 177 | line by line so it doesn't use nearly as much memory. 178 | - Now checks the message for bare periods and encodes them. 179 | - Fixed a bug that would allow text to be printed even if -q was 180 | set (and -m was not). 181 | - The MIME delimiter is now somewhat random. 182 | 183 | 1.30 (Aug 1, 2000) 184 | - ATTACHMENT SUPPORT ADDED! 185 | - Added CC and BCC support, they are now normal command line options. 186 | - sendEmail should now work easier in Win32. Win32 did not support 187 | the alarm() calls, so I put a check that disables the alarm calls if 188 | $^O matches 'Win'. 189 | - Added autodetection of mime type on attachments. 190 | - Disabled the alarm timer if there are attachments, that way people 191 | on modems can send big attachments without getting their upload killed. 192 | - Added a -q option for quiet mode 193 | - Added a 'Date:' field in the mail headers so messages will 194 | show up in the right order in my mail program ;-) 195 | - Changed the way you specify a port form the command line 196 | now you specify it in the -s option as an 197 | optional :port addendum to the server name. 198 | - Added -h and --help support. 199 | - Updated the help to reflect all changes. 200 | 201 | 1.22 (Jul 8, 2000) 202 | - Removed the 'content-type' and 'transfer encoding type' flags in the email 203 | headers, in preparation for a version that will support real attachments. 204 | - When piping data to sendEmail often email would have lines ending with 205 | just \012 and many email servers do not like this. So I now have \012 206 | replaced with \012\015 in data piped from STDIN. 207 | - When piping data as the message it now displays a message for those manually 208 | typing in an email. It also changes the 15 second alarm to a 60 second alarm 209 | and resets it after every new line. 210 | 211 | 1.21 (May 15, 2000) 212 | - Removed the SIG_ALARM call (gave errors on certain non-slackware distributions) 213 | - Added a missing error check that could have allowed sendEmail to say a 214 | message was sent successfully when the server had actually returned an 215 | error message. 216 | 217 | 218 | 1.20 (May 10, 2000) 219 | VERSION OVERVIEW: 220 | ----------------- 221 | -v option added 222 | -vv option added 223 | Passing the body of the email via STDIN is now an option 224 | Multiple addresses now allowed 225 | Logging feature added 226 | Terminates itself after 15 seconds to avoid dns hangs etc 227 | Lots of small potential bugs fixed and a lot of code cleanup 228 | 229 | DETAILS: 230 | -------- 231 | - DEBUG CODE/MODE: 232 | - Lots of error messages everywhere 233 | - 2 levels of verbosity accessible via command like parameters 234 | - Lots more information messages triggered by $debug's value 235 | - Fixed a problem which could have caused sendEmail to overlook certain 236 | error messages from the email server. 237 | - All invalid command line parameters are now reported correctly 238 | - Some code comments were written incorrectly, and are now fixed. 239 | - Added handler for HUP and ALARM (both kill sendEmail now) 240 | - Possible problems with certain email addresses fixed. 241 | - Modified date routine to not use a system call. (making it more portable) 242 | - Improved command line parsing (preventing a few possible errors). 243 | - Thanks to Nick Pasich for help with these patches: 244 | - You can now send the body of the email via the -m option 245 | as before or optionally leave the -m out and pass the body 246 | of the email to STDIN. 247 | - Alarm to abort sendEmail if it runs longer than 15 seconds 248 | - Multiple 'To' addresses 249 | - Option to log to a file 250 | - Updated the 'help' page to reflect all changes 251 | 252 | 253 | 1.01 254 | - Fixed a bug in the error/success checking routine which caused sendEmail 255 | to report errors when there were none. 256 | Thanks to Michael Santy for this bug report. 257 | - Added some more documentation in the code. 258 | - Added this CHANGELOG file. 259 | - Release v1.01 260 | 261 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README-BR.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zehm/sendEmail/2876ab214c2f1976764879eefb9744e01feb2959/README-BR.md -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sendEmail 2 | SendEmail is a lightweight, command line SMTP email client. If you have the need to send email from a command line, this free program is perfect: simple to use and feature rich. It was designed to be used in bash scripts, batch files, Perl programs and web sites, but is quite adaptable and will likely meet your requirements. SendEmail is written in Perl and is unique in that it requires NO MODULES. It has an intuitive and flexible set of command-line options, making it very easy to learn and use. SendEmail is licensed under the GNU GPL, either version 2 of the License or (at your option) any later version. 3 | [Supported Platforms: Linux, BSD, OS X, Windows 98, Windows NT, Windows 2000, & Windows XP] 4 | 5 | sendEmail - Send email from a console near you! 6 | Written by: Brandon Zehm 7 | 8 | 9 | 10 | ------------------ 11 | Installation 12 | ------------------ 13 | 14 | SendEmail is a perl script/program, and only needs to be copied to a directory 15 | in your path to make it accessible. Most likely the following steps will 16 | be sufficient: 17 | 18 | 1) Extract the package 19 | tar -zxvf sendEmail-v1.XX.tar.gz 20 | 21 | 2) Copy the sendEmail.pl script (and it's symlink sendEmail) to /usr/local/bin 22 | cp -a sendEmail-v1.XX/sendEmail.pl /usr/local/bin 23 | 24 | 3) Make sure its executable 25 | chmod +x /usr/local/bin/sendEmail.pl 26 | 27 | 4) Run it 28 | `sendEmail.pl` 29 | or 30 | `/usr/local/bin/sendEmail.pl` 31 | or if it's symlinked to "sendEmail" and in your path, just run: 32 | `sendEmail` 33 | 34 | NOTES: 35 | 36 | * Running sendEmail without any arguments will display a helpful usage summary. 37 | * SendEmail is written in Perl, so no compilation is needed. 38 | * On a Unix/Linux OS if your perl binary is not installed at /usr/bin/perl 39 | you may need to edit the first line of the script accordingly. 40 | 41 | 42 | 43 | 44 | 45 | 46 | --------------- 47 | Usage Overview 48 | --------------- 49 | 50 | sendEmail-1.56 by Brandon Zehm 51 | 52 | Synopsis: sendEmail -f ADDRESS [options] 53 | 54 | Required: 55 | -f ADDRESS from (sender) email address 56 | * At least one recipient required via -t, -cc, or -bcc 57 | * Message body required via -m, STDIN, or -o message-file=FILE 58 | 59 | Common: 60 | -t ADDRESS [ADDR ...] to email address(es) 61 | -u SUBJECT message subject 62 | -m MESSAGE message body 63 | -s SERVER[:PORT] smtp mail relay, default is localhost:25 64 | 65 | Optional: 66 | -a FILE [FILE ...] file attachment(s) 67 | -cc ADDRESS [ADDR ...] cc email address(es) 68 | -bcc ADDRESS [ADDR ...] bcc email address(es) 69 | -xu USERNAME username for SMTP authentication 70 | -xp PASSWORD password for SMTP authentication 71 | 72 | Paranormal: 73 | -b BINDADDR[:PORT] local host bind address 74 | -l LOGFILE log to the specified file 75 | -v verbosity, use multiple times for greater effect 76 | -q be quiet (i.e. no STDOUT output) 77 | -o NAME=VALUE advanced options, for details try: --help misc 78 | -o message-content-type= 79 | -o message-file=FILE -o message-format=raw 80 | -o message-header=HEADER -o message-charset=CHARSET 81 | -o reply-to=ADDRESS -o timeout=SECONDS 82 | -o username=USERNAME -o password=PASSWORD 83 | -o tls= -o fqdn=FQDN 84 | 85 | Help: 86 | --help the helpful overview you're reading now 87 | --help addressing explain addressing and related options 88 | --help message explain message body input and related options 89 | --help networking explain -s, -b, etc 90 | --help output explain logging and other output options 91 | --help misc explain -o options, TLS, SMTP auth, and more 92 | 93 | 94 | 95 | --------------- 96 | Examples 97 | --------------- 98 | 99 | Send a simple email: 100 | 101 | ``` 102 | sendEmail -f me@gmail.com \ 103 | -t friend@yahoo.com \ 104 | -s smtp.gmail.com:587 \ 105 | -xu me@gmail.com \ 106 | -xp MY-PASSWORD \ 107 | -u "Test email" \ 108 | -m "Hi buddy, this is a test email." 109 | ``` 110 | 111 | Sending to mutiple people: 112 | 113 | ``` 114 | sendEmail -f myaddress@isp.net \ 115 | -t "Scott Thomas " jason@isp.net renee@isp.net \ 116 | -s relay.isp.net \ 117 | -u "Test email" \ 118 | -m "Hi guys, this is a test email." 119 | ``` 120 | 121 | Sending to multiple people using cc and bcc recipients: 122 | (notice the different way I specified multiple To recipients, you can do this for cc and bcc as well) 123 | 124 | ``` 125 | sendEmail -f myaddress@isp.net \ 126 | -t scott@isp.net;jason@isp.net;renee@isp.net \ 127 | -cc jennifer@isp.net paul@isp.net jeremiah@isp.net \ 128 | -bcc troy@isp.net miranda@isp.net jay@isp.net \ 129 | -s relay.isp.net \ 130 | -u "Test email with cc and bcc recipients" \ 131 | -m "Hi guys, this is a test email." 132 | ``` 133 | 134 | 135 | Sending to multiple people with multiple attachments: 136 | 137 | ``` 138 | sendEmail -f myaddress@isp.net \ 139 | -t jason@isp.net \ 140 | -cc jennifer@isp.net paul@isp.net jeremiah@isp.net \ 141 | -s relay.isp.net \ 142 | -u "Test email with cc and bcc recipients" \ 143 | -m "Hi guys, this is a test email." \ 144 | -a /mnt/storage/document.sxw "/root/My Documents/Work Schedule.kwd" 145 | ``` 146 | 147 | Sending an email with the contents of a file as the message body: 148 | 149 | ``` 150 | cat /tmp/file.txt | sendEmail -f myaddress@isp.net \ 151 | -t jason@isp.net \ 152 | -s relay.isp.net \ 153 | -u "Test email with contents of file" 154 | ``` 155 | 156 | Sending an email with the contents of a file as the message body (method 2): 157 | 158 | ``` 159 | sendEmail -f myaddress@isp.net \ 160 | -t jason@isp.net \ 161 | -s relay.isp.net \ 162 | -o message-file=/tmp/file.txt \ 163 | -u "Test email with contents of file" 164 | ``` 165 | 166 | Sending an html email: (make sure your html file has at the beginning) 167 | 168 | ``` 169 | cat /tmp/file.html | sendEmail -f myaddress@isp.net \ 170 | -t jason@isp.net \ 171 | -s relay.isp.net \ 172 | -u "Test email with html content" 173 | ``` 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | ------------ 183 | Contributors 184 | ------------ 185 | 186 | Many thanks go to the people who have submitted ideas and patches. 187 | I know I've forgotten to mention everyone who's helped with sendEmail, 188 | but here is a small list. Please let me know if you feel your name 189 | should be here! 190 | 191 | v1.56 192 | - Several people submitted fixes for the authentication bug. 193 | Thanks to all of you for nagging me to get this release out! 194 | 195 | Simon Matter (v1.55) 196 | - Local bind address patch 197 | 198 | CBL Team and Chris Peay (v1.55) 199 | - Bug reports about sendEmail causing people get blacklisted. 200 | 201 | Jared Cheney (v1.42) 202 | - More bare LF bug fixes and bare period encoding. 203 | - Mime encoding patch 204 | 205 | Buddy Nahay (v1.41) 206 | - Bare LF bug report 207 | 208 | John Rouillard (v1.41) 209 | - html detection bug report 210 | 211 | Reidar Johansen (v1.40) 212 | - Added support for HTML email 213 | - Created a function called tz_offset that determines the local timezone 214 | - Many other fixes and suggestions 215 | 216 | Paul Kreiner (v1.40) 217 | - Submitted a patch that forces the timestamp string to always follow 218 | the HH:MM:SS convention required by the RFCs. 219 | 220 | Al Danial 221 | - Found and reported a logging/typo/attachment issue in v1.32 222 | 223 | Svante Gerhard 224 | - Found and reported the file attachment/padding issue in v1.31 225 | 226 | Charles Leeds 227 | - Put together all the original file attachment code and got me 228 | on the path to v1.3x 229 | - Provided the compiled Windows executable version of sendEmail 230 | for a LONG time. I really appreciate your help! 231 | 232 | Nick Pasich 233 | - Passing the email message via STDIN 234 | - Multiple recpients 235 | - Log file option 236 | - Quiet option 237 | - Cc option 238 | - Lots of other suggestions and code 239 | 240 | Richard Duim 241 | - For mime/content-type/attachment suggestions 242 | 243 | Ulisses Montenegro 244 | - First one to report problems with bare LF's on qmail servers 245 | 246 | Michael Santy 247 | - Reported problems with various SMTP servers and helped me fix a few 248 | fairly serious problems. 249 | 250 | Many other people have submitted bug reports and helped to make sendEmail 251 | what it is today, and my best regards go out to all those .. complainers ;-) 252 | 253 | 254 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | ## v1.56 Release 2 | - [.] Update help message in README (version number at least) 3 | - [x] Fixed base64 encoding issues for authentication 4 | - [x] Detect in first line as html too 5 | - [x] Command line option to force sending as html (or another content-type) 6 | - [x] Add help message about sending to / from a gmail account 7 | - [ ] Brazilian Portuguese Help for SendMail. Nogueira 8 | 9 | ## Reported Bugs 10 | - [ ] But it seems that I can't send more than 16 or 17 KB from the STDIN on Windows (2003) with the EXE version. 11 | Even with -o message-file=... and got the same 17 KB message limitation. 12 | 13 | ## Feature Ideas 14 | - [ ] A preferences file with default settings? 15 | - [ ] Deliver directly to MX server for domain? 16 | - [ ] Command line option requesting read-receipt 17 | X-Confirm-Reading-To: 18 | Disposition-Notification-To: 19 | Return-Receipt-To: 20 | - [ ] Add support for CRAM-MD5 authentication 21 | - [ ] Add a pair of options to add receipt on delivery and on read of sent emails 22 | - [ ] Support for unicode (european) characters in the subject 23 | 24 | ## More Ideas 25 | - [ ] What about an option to send the same message more than once? 26 | - [ ] How about a delay before sending the message? 27 | - [ ] What about an internal queue for messages that wern't sent? 28 | Next time it's invoked it could deliver those messages as well. 29 | - [ ] Convert inline documentation to standard perldoc format? 30 | - [ ] Support for pop before smtp? 31 | 32 | ## Crazy Ideas (not so sure about) 33 | - [ ] Preferences registry settings for the Windows .exe version? 34 | (several people have said No! use a flat file) 35 | 36 | -------------------------------------------------------------------------------- /sendEmail.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | ############################################################################## 3 | ## sendEmail 4 | ## Written by: Brandon Zehm 5 | ## 6 | ## License: 7 | ## sendEmail (hereafter referred to as "program") is free software; 8 | ## you can redistribute it and/or modify it under the terms of the GNU General 9 | ## Public License as published by the Free Software Foundation; either version 10 | ## 2 of the License, or (at your option) any later version. 11 | ## When redistributing modified versions of this source code it is recommended 12 | ## that that this disclaimer and the above coder's names are included in the 13 | ## modified code. 14 | ## 15 | ## Disclaimer: 16 | ## This program is provided with no warranty of any kind, either expressed or 17 | ## implied. It is the responsibility of the user (you) to fully research and 18 | ## comprehend the usage of this program. As with any tool, it can be misused, 19 | ## either intentionally (you're a vandal) or unintentionally (you're a moron). 20 | ## THE AUTHOR(S) IS(ARE) NOT RESPONSIBLE FOR ANYTHING YOU DO WITH THIS PROGRAM 21 | ## or anything that happens because of your use (or misuse) of this program, 22 | ## including but not limited to anything you, your lawyers, or anyone else 23 | ## can dream up. And now, a relevant quote directly from the GPL: 24 | ## 25 | ## NO WARRANTY 26 | ## 27 | ## 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 28 | ## FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 29 | ## OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 30 | ## PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 31 | ## OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 32 | ## MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 33 | ## TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 34 | ## PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 35 | ## REPAIR OR CORRECTION. 36 | ## 37 | ############################################################################## 38 | use strict; 39 | use IO::Socket; 40 | 41 | 42 | ######################## 43 | ## Global Variables ## 44 | ######################## 45 | 46 | my %conf = ( 47 | ## General 48 | "programName" => $0, ## The name of this program 49 | "version" => '1.56', ## The version of this program 50 | "authorName" => 'Brandon Zehm', ## Author's Name 51 | "authorEmail" => 'caspian@dotconf.net', ## Author's Email Address 52 | "timezone" => '+0000', ## We always use +0000 for the time zone 53 | "hostname" => 'changeme', ## Used in printmsg() for all output (is updated later in the script). 54 | "debug" => 0, ## Default debug level 55 | "error" => '', ## Error messages will often be stored here 56 | 57 | ## Logging 58 | "stdout" => 1, 59 | "logging" => 0, ## If this is true the printmsg function prints to the log file 60 | "logFile" => '', ## If this is specified (form the command line via -l) this file will be used for logging. 61 | 62 | ## Network 63 | "server" => 'localhost', ## Default SMTP server 64 | "port" => 25, ## Default port 65 | "bindaddr" => '', ## Default local bind address 66 | "alarm" => '', ## Default timeout for connects and reads, this gets set from $opt{'timeout'} 67 | "tls_client" => 0, ## If TLS is supported by the client (us) 68 | "tls_server" => 0, ## If TLS is supported by the remote SMTP server 69 | 70 | ## Email 71 | "delimiter" => "----MIME delimiter for sendEmail-" ## MIME Delimiter 72 | . rand(1000000), ## Add some randomness to the delimiter 73 | "Message-ID" => rand(1000000) . "-sendEmail", ## Message-ID for email header 74 | 75 | ); 76 | 77 | 78 | ## This hash stores the options passed on the command line via the -o option. 79 | my %opt = ( 80 | ## Addressing 81 | "reply-to" => '', ## Reply-To field 82 | 83 | ## Message 84 | "message-file" => '', ## File to read message body from 85 | "message-header" => '', ## Additional email header line(s) 86 | "message-format" => 'normal', ## If "raw" is specified the message is sent unmodified 87 | "message-charset" => 'iso-8859-1', ## Message character-set 88 | "message-content-type" => 'auto', ## auto, text, html or an actual string to put into the content-type header. 89 | 90 | ## Network 91 | "timeout" => 60, ## Default timeout for connects and reads, this is copied to $conf{'alarm'} later. 92 | "fqdn" => 'changeme', ## FQDN of this machine, used during SMTP communication (is updated later in the script). 93 | 94 | ## eSMTP 95 | "username" => '', ## Username used in SMTP Auth 96 | "password" => '', ## Password used in SMTP Auth 97 | "tls" => 'auto', ## Enable or disable TLS support. Options: auto, yes, no 98 | 99 | ); 100 | 101 | ## More variables used later in the program 102 | my $SERVER; 103 | my $CRLF = "\015\012"; 104 | my $subject = ''; 105 | my $header = ''; 106 | my $message = ''; 107 | my $from = ''; 108 | my @to = (); 109 | my @cc = (); 110 | my @bcc = (); 111 | my @attachments = (); 112 | my @attachments_names = (); 113 | 114 | ## For printing colors to the console 115 | my ${colorRed} = "\033[31;1m"; 116 | my ${colorGreen} = "\033[32;1m"; 117 | my ${colorCyan} = "\033[36;1m"; 118 | my ${colorWhite} = "\033[37;1m"; 119 | my ${colorNormal} = "\033[m"; 120 | my ${colorBold} = "\033[1m"; 121 | my ${colorNoBold} = "\033[0m"; 122 | 123 | ## Don't use shell escape codes on Windows systems 124 | if ($^O =~ /win/i) { 125 | ${colorRed} = ${colorGreen} = ${colorCyan} = ${colorWhite} = ${colorNormal} = ${colorBold} = ${colorNoBold} = ""; 126 | } 127 | 128 | ## Load IO::Socket::SSL if it's available 129 | eval { require IO::Socket::SSL; }; 130 | if ($@) { $conf{'tls_client'} = 0; } 131 | else { $conf{'tls_client'} = 1; } 132 | 133 | 134 | 135 | 136 | 137 | 138 | ############################# 139 | ## ## 140 | ## FUNCTIONS ## 141 | ## ## 142 | ############################# 143 | 144 | 145 | 146 | 147 | 148 | ############################################################################################### 149 | ## Function: initialize () 150 | ## 151 | ## Does all the script startup jibberish. 152 | ## 153 | ############################################################################################### 154 | sub initialize { 155 | 156 | ## Set STDOUT to flush immediatly after each print 157 | $| = 1; 158 | 159 | ## Intercept signals 160 | $SIG{'QUIT'} = sub { quit("EXITING: Received SIG$_[0]", 1); }; 161 | $SIG{'INT'} = sub { quit("EXITING: Received SIG$_[0]", 1); }; 162 | $SIG{'KILL'} = sub { quit("EXITING: Received SIG$_[0]", 1); }; 163 | $SIG{'TERM'} = sub { quit("EXITING: Received SIG$_[0]", 1); }; 164 | 165 | ## ALARM and HUP signals are not supported in Win32 166 | unless ($^O =~ /win/i) { 167 | $SIG{'HUP'} = sub { quit("EXITING: Received SIG$_[0]", 1); }; 168 | $SIG{'ALRM'} = sub { quit("EXITING: Received SIG$_[0]", 1); }; 169 | } 170 | 171 | ## Fixup $conf{'programName'} 172 | $conf{'programName'} =~ s/(.)*[\/,\\]//; 173 | $0 = $conf{'programName'} . " " . join(" ", @ARGV); 174 | 175 | ## Fixup $conf{'hostname'} and $opt{'fqdn'} 176 | if ($opt{'fqdn'} eq 'changeme') { $opt{'fqdn'} = get_hostname(1); } 177 | if ($conf{'hostname'} eq 'changeme') { $conf{'hostname'} = $opt{'fqdn'}; $conf{'hostname'} =~ s/\..*//; } 178 | 179 | return(1); 180 | } 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | ############################################################################################### 197 | ## Function: processCommandLine () 198 | ## 199 | ## Processes command line storing important data in global vars (usually %conf) 200 | ## 201 | ############################################################################################### 202 | sub processCommandLine { 203 | 204 | 205 | ############################ 206 | ## Process command line ## 207 | ############################ 208 | 209 | my @ARGS = @ARGV; ## This is so later we can re-parse the command line args later if we need to 210 | my $numargv = @ARGS; 211 | help() unless ($numargv); 212 | my $counter = 0; 213 | 214 | for ($counter = 0; $counter < $numargv; $counter++) { 215 | 216 | if ($ARGS[$counter] =~ /^-h$/i) { ## Help ## 217 | help(); 218 | } 219 | 220 | elsif ($ARGS[$counter] eq "") { ## Ignore null arguments 221 | ## Do nothing 222 | } 223 | 224 | elsif ($ARGS[$counter] =~ /^--help/) { ## Topical Help ## 225 | $counter++; 226 | if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) { 227 | helpTopic($ARGS[$counter]); 228 | } 229 | else { 230 | help(); 231 | } 232 | } 233 | 234 | elsif ($ARGS[$counter] =~ /^-o$/i) { ## Options specified with -o ## 235 | $counter++; 236 | ## Loop through each option passed after the -o 237 | while ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) { 238 | 239 | if ($ARGS[$counter] !~ /(\S+)=(\S.*)/) { 240 | printmsg("WARNING => Name/Value pair [$ARGS[$counter]] is not properly formatted", 0); 241 | printmsg("WARNING => Arguments proceeding -o should be in the form of \"name=value\"", 0); 242 | } 243 | else { 244 | if (exists($opt{$1})) { 245 | if ($1 eq 'message-header') { 246 | $opt{$1} .= $2 . $CRLF; 247 | } 248 | else { 249 | $opt{$1} = $2; 250 | } 251 | printmsg("DEBUG => Assigned \$opt{} key/value: $1 => $2", 3); 252 | } 253 | else { 254 | printmsg("WARNING => Name/Value pair [$ARGS[$counter]] will be ignored: unknown key [$1]", 0); 255 | printmsg("HINT => Try the --help option to find valid command line arguments", 1); 256 | } 257 | } 258 | $counter++; 259 | } $counter--; 260 | } 261 | 262 | elsif ($ARGS[$counter] =~ /^-f$/) { ## From ## 263 | $counter++; 264 | if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) { $from = $ARGS[$counter]; } 265 | else { printmsg("WARNING => The argument after -f was not an email address!", 0); $counter--; } 266 | } 267 | 268 | elsif ($ARGS[$counter] =~ /^-t$/) { ## To ## 269 | $counter++; 270 | while ($ARGS[$counter] && ($ARGS[$counter] !~ /^-/)) { 271 | if ($ARGS[$counter] =~ /[;,]/) { 272 | push (@to, split(/[;,]/, $ARGS[$counter])); 273 | } 274 | else { 275 | push (@to,$ARGS[$counter]); 276 | } 277 | $counter++; 278 | } $counter--; 279 | } 280 | 281 | elsif ($ARGS[$counter] =~ /^-cc$/) { ## Cc ## 282 | $counter++; 283 | while ($ARGS[$counter] && ($ARGS[$counter] !~ /^-/)) { 284 | if ($ARGS[$counter] =~ /[;,]/) { 285 | push (@cc, split(/[;,]/, $ARGS[$counter])); 286 | } 287 | else { 288 | push (@cc,$ARGS[$counter]); 289 | } 290 | $counter++; 291 | } $counter--; 292 | } 293 | 294 | elsif ($ARGS[$counter] =~ /^-bcc$/) { ## Bcc ## 295 | $counter++; 296 | while ($ARGS[$counter] && ($ARGS[$counter] !~ /^-/)) { 297 | if ($ARGS[$counter] =~ /[;,]/) { 298 | push (@bcc, split(/[;,]/, $ARGS[$counter])); 299 | } 300 | else { 301 | push (@bcc,$ARGS[$counter]); 302 | } 303 | $counter++; 304 | } $counter--; 305 | } 306 | 307 | elsif ($ARGS[$counter] =~ /^-m$/) { ## Message ## 308 | $counter++; 309 | $message = ""; 310 | while ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) { 311 | if ($message) { $message .= " "; } 312 | $message .= $ARGS[$counter]; 313 | $counter++; 314 | } $counter--; 315 | 316 | ## Replace '\n' with $CRLF. 317 | ## This allows newlines with messages sent on the command line 318 | $message =~ s/\\n/$CRLF/g; 319 | } 320 | 321 | elsif ($ARGS[$counter] =~ /^-u$/) { ## Subject ## 322 | $counter++; 323 | $subject = ""; 324 | while ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) { 325 | if ($subject) { $subject .= " "; } 326 | $subject .= $ARGS[$counter]; 327 | $counter++; 328 | } $counter--; 329 | } 330 | 331 | elsif ($ARGS[$counter] =~ /^-s$/) { ## Server ## 332 | $counter++; 333 | if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) { 334 | $conf{'server'} = $ARGS[$counter]; 335 | if ($conf{'server'} =~ /:/) { ## Port ## 336 | ($conf{'server'},$conf{'port'}) = split(":",$conf{'server'}); 337 | } 338 | } 339 | else { printmsg("WARNING - The argument after -s was not the server!", 0); $counter--; } 340 | } 341 | 342 | elsif ($ARGS[$counter] =~ /^-b$/) { ## Bind Address ## 343 | $counter++; 344 | if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) { 345 | $conf{'bindaddr'} = $ARGS[$counter]; 346 | } 347 | else { printmsg("WARNING - The argument after -b was not the bindaddr!", 0); $counter--; } 348 | } 349 | 350 | elsif ($ARGS[$counter] =~ /^-a$/) { ## Attachments ## 351 | $counter++; 352 | while ($ARGS[$counter] && ($ARGS[$counter] !~ /^-/)) { 353 | push (@attachments,$ARGS[$counter]); 354 | $counter++; 355 | } $counter--; 356 | } 357 | 358 | elsif ($ARGS[$counter] =~ /^-xu$/) { ## AuthSMTP Username ## 359 | $counter++; 360 | if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) { 361 | $opt{'username'} = $ARGS[$counter]; 362 | } 363 | else { 364 | printmsg("WARNING => The argument after -xu was not valid username!", 0); 365 | $counter--; 366 | } 367 | } 368 | 369 | elsif ($ARGS[$counter] =~ /^-xp$/) { ## AuthSMTP Password ## 370 | $counter++; 371 | if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) { 372 | $opt{'password'} = $ARGS[$counter]; 373 | } 374 | else { 375 | printmsg("WARNING => The argument after -xp was not valid password!", 0); 376 | $counter--; 377 | } 378 | } 379 | 380 | elsif ($ARGS[$counter] =~ /^-l$/) { ## Logging ## 381 | $counter++; 382 | $conf{'logging'} = 1; 383 | if ($ARGS[$counter] && $ARGS[$counter] !~ /^-/) { $conf{'logFile'} = $ARGS[$counter]; } 384 | else { printmsg("WARNING - The argument after -l was not the log file!", 0); $counter--; } 385 | } 386 | 387 | elsif ($ARGS[$counter] =~ s/^-v+//i) { ## Verbosity ## 388 | my $tmp = (length($&) - 1); 389 | $conf{'debug'} += $tmp; 390 | } 391 | 392 | elsif ($ARGS[$counter] =~ /^-q$/) { ## Quiet ## 393 | $conf{'stdout'} = 0; 394 | } 395 | 396 | else { 397 | printmsg("Error: \"$ARGS[$counter]\" is not a recognized option!", 0); 398 | help(); 399 | } 400 | 401 | } 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | ################################################### 411 | ## Verify required variables are set correctly ## 412 | ################################################### 413 | 414 | ## Make sure we have something in $conf{hostname} and $opt{fqdn} 415 | if ($opt{'fqdn'} =~ /\./) { 416 | $conf{'hostname'} = $opt{'fqdn'}; 417 | $conf{'hostname'} =~ s/\..*//; 418 | } 419 | 420 | if (!$conf{'server'}) { $conf{'server'} = 'localhost'; } 421 | if (!$conf{'port'}) { $conf{'port'} = 25; } 422 | if (!$from) { 423 | quit("ERROR => You must specify a 'from' field! Try --help.", 1); 424 | } 425 | if ( ((scalar(@to)) + (scalar(@cc)) + (scalar(@bcc))) <= 0) { 426 | quit("ERROR => You must specify at least one recipient via -t, -cc, or -bcc", 1); 427 | } 428 | 429 | ## Make sure email addresses look OK. 430 | foreach my $addr (@to, @cc, @bcc, $from, $opt{'reply-to'}) { 431 | if ($addr) { 432 | if (!returnAddressParts($addr)) { 433 | printmsg("ERROR => Can't use improperly formatted email address: $addr", 0); 434 | printmsg("HINT => Try viewing the extended help on addressing with \"--help addressing\"", 1); 435 | quit("", 1); 436 | } 437 | } 438 | } 439 | 440 | ## Make sure all attachments exist. 441 | foreach my $file (@attachments) { 442 | if ( (! -f $file) or (! -r $file) ) { 443 | printmsg("ERROR => The attachment [$file] doesn't exist!", 0); 444 | printmsg("HINT => Try specifying the full path to the file or reading extended help with \"--help message\"", 1); 445 | quit("", 1); 446 | } 447 | } 448 | 449 | if ($conf{'logging'} and (!$conf{'logFile'})) { 450 | quit("ERROR => You used -l to enable logging but didn't specify a log file!", 1); 451 | } 452 | 453 | if ( $opt{'username'} ) { 454 | if (!$opt{'password'}) { 455 | ## Prompt for a password since one wasn't specified with the -xp option. 456 | $SIG{'ALRM'} = sub { quit("ERROR => Timeout waiting for password inpupt", 1); }; 457 | alarm(60) if ($^O !~ /win/i); ## alarm() doesn't work in win32 458 | print "Password: "; 459 | $opt{'password'} = ; chomp $opt{'password'}; 460 | if (!$opt{'password'}) { 461 | quit("ERROR => A username for SMTP authentication was specified, but no password!", 1); 462 | } 463 | } 464 | } 465 | 466 | ## Validate the TLS setting 467 | $opt{'tls'} = lc($opt{'tls'}); 468 | if ($opt{'tls'} !~ /^(auto|yes|no)$/) { 469 | quit("ERROR => Invalid TLS setting ($opt{'tls'}). Must be one of auto, yes, or no.", 1); 470 | } 471 | 472 | ## If TLS is set to "yes", make sure sendEmail loaded the libraries needed. 473 | if ($opt{'tls'} eq 'yes' and $conf{'tls_client'} == 0) { 474 | quit("ERROR => No TLS support! SendEmail can't load required libraries. (try installing Net::SSLeay and IO::Socket::SSL)", 1); 475 | } 476 | 477 | ## Return 0 errors 478 | return(0); 479 | } 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | ## getline($socketRef) 497 | sub getline { 498 | my ($socketRef) = @_; 499 | local ($/) = "\r\n"; 500 | return $$socketRef->getline; 501 | } 502 | 503 | 504 | 505 | 506 | ## Receive a (multiline?) SMTP response from ($socketRef) 507 | sub getResponse { 508 | my ($socketRef) = @_; 509 | my ($tmp, $reply); 510 | local ($/) = "\r\n"; 511 | return undef unless defined($tmp = getline($socketRef)); 512 | return("getResponse() socket is not open") unless ($$socketRef->opened); 513 | ## Keep reading lines if it's a multi-line response 514 | while ($tmp =~ /^\d{3}-/o) { 515 | $reply .= $tmp; 516 | return undef unless defined($tmp = getline($socketRef)); 517 | } 518 | $reply .= $tmp; 519 | $reply =~ s/\r?\n$//o; 520 | return $reply; 521 | } 522 | 523 | 524 | 525 | 526 | ############################################################################################### 527 | ## Function: SMTPchat ( [string $command] ) 528 | ## 529 | ## Description: Sends $command to the SMTP server (on SERVER) and awaits a successful 530 | ## reply form the server. If the server returns an error, or does not reply 531 | ## within $conf{'alarm'} seconds an error is generated. 532 | ## NOTE: $command is optional, if no command is specified then nothing will 533 | ## be sent to the server, but a valid response is still required from the server. 534 | ## 535 | ## Input: [$command] A (optional) valid SMTP command (ex. "HELO") 536 | ## 537 | ## 538 | ## Output: Returns zero on success, or non-zero on error. 539 | ## Error messages will be stored in $conf{'error'} 540 | ## A copy of the last SMTP response is stored in the global variable 541 | ## $conf{'SMTPchat_response'} 542 | ## 543 | ## 544 | ## Example: SMTPchat ("HELO mail.isp.net"); 545 | ############################################################################################### 546 | sub SMTPchat { 547 | my ($command) = @_; 548 | 549 | printmsg("INFO => Sending: \t$command", 1) if ($command); 550 | 551 | ## Send our command 552 | print $SERVER "$command$CRLF" if ($command); 553 | 554 | ## Read a response from the server 555 | $SIG{'ALRM'} = sub { $conf{'error'} = "alarm"; $SERVER->close(); }; 556 | alarm($conf{'alarm'}) if ($^O !~ /win/i); ## alarm() doesn't work in win32; 557 | my $result = $conf{'SMTPchat_response'} = getResponse(\$SERVER); 558 | alarm(0) if ($^O !~ /win/i); ## alarm() doesn't work in win32; 559 | 560 | ## Generate an alert if we timed out 561 | if ($conf{'error'} eq "alarm") { 562 | $conf{'error'} = "ERROR => Timeout while reading from $conf{'server'}:$conf{'port'} There was no response after $conf{'alarm'} seconds."; 563 | return(1); 564 | } 565 | 566 | ## Make sure the server actually responded 567 | if (!$result) { 568 | $conf{'error'} = "ERROR => $conf{'server'}:$conf{'port'} returned a zero byte response to our query."; 569 | return(2); 570 | } 571 | 572 | ## Validate the response 573 | if (evalSMTPresponse($result)) { 574 | ## conf{'error'} will already be set here 575 | return(2); 576 | } 577 | 578 | ## Print the success messsage 579 | printmsg($conf{'error'}, 1); 580 | 581 | ## Return Success 582 | return(0); 583 | } 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | ############################################################################################### 597 | ## Function: evalSMTPresponse (string $message ) 598 | ## 599 | ## Description: Searches $message for either an SMTP success or error code, and returns 600 | ## 0 on success, and the actual error code on error. 601 | ## 602 | ## 603 | ## Input: $message Data received from a SMTP server (ex. "220 604 | ## 605 | ## 606 | ## Output: Returns zero on success, or non-zero on error. 607 | ## Error messages will be stored in $conf{'error'} 608 | ## 609 | ## 610 | ## Example: SMTPchat ("HELO mail.isp.net"); 611 | ############################################################################################### 612 | sub evalSMTPresponse { 613 | my ($message) = @_; 614 | 615 | ## Validate input 616 | if (!$message) { 617 | $conf{'error'} = "ERROR => No message was passed to evalSMTPresponse(). What happened?"; 618 | return(1) 619 | } 620 | 621 | printmsg("DEBUG => evalSMTPresponse() - Checking for SMTP success or error status in the message: $message ", 3); 622 | 623 | ## Look for a SMTP success code 624 | if ($message =~ /^([23]\d\d)/) { 625 | printmsg("DEBUG => evalSMTPresponse() - Found SMTP success code: $1", 2); 626 | $conf{'error'} = "SUCCESS => Received: \t$message"; 627 | return(0); 628 | } 629 | 630 | ## Look for a SMTP error code 631 | if ($message =~ /^([45]\d\d)/) { 632 | printmsg("DEBUG => evalSMTPresponse() - Found SMTP error code: $1", 2); 633 | $conf{'error'} = "ERROR => Received: \t$message"; 634 | return($1); 635 | } 636 | 637 | ## If no SMTP codes were found return an error of 1 638 | $conf{'error'} = "ERROR => Received a message with no success or error code. The message received was: $message"; 639 | return(2); 640 | 641 | } 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | ######################################################### 653 | # SUB: &return_month(0,1,etc) 654 | # returns the name of the month that corrosponds 655 | # with the number. returns 0 on error. 656 | ######################################################### 657 | sub return_month { 658 | my $x = $_[0]; 659 | if ($x == 0) { return 'Jan'; } 660 | if ($x == 1) { return 'Feb'; } 661 | if ($x == 2) { return 'Mar'; } 662 | if ($x == 3) { return 'Apr'; } 663 | if ($x == 4) { return 'May'; } 664 | if ($x == 5) { return 'Jun'; } 665 | if ($x == 6) { return 'Jul'; } 666 | if ($x == 7) { return 'Aug'; } 667 | if ($x == 8) { return 'Sep'; } 668 | if ($x == 9) { return 'Oct'; } 669 | if ($x == 10) { return 'Nov'; } 670 | if ($x == 11) { return 'Dec'; } 671 | return (0); 672 | } 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | ######################################################### 690 | # SUB: &return_day(0,1,etc) 691 | # returns the name of the day that corrosponds 692 | # with the number. returns 0 on error. 693 | ######################################################### 694 | sub return_day { 695 | my $x = $_[0]; 696 | if ($x == 0) { return 'Sun'; } 697 | if ($x == 1) { return 'Mon'; } 698 | if ($x == 2) { return 'Tue'; } 699 | if ($x == 3) { return 'Wed'; } 700 | if ($x == 4) { return 'Thu'; } 701 | if ($x == 5) { return 'Fri'; } 702 | if ($x == 6) { return 'Sat'; } 703 | return (0); 704 | } 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | ############################################################################################### 722 | ## Function: returnAddressParts(string $address) 723 | ## 724 | ## Description: Returns a two element array containing the "Name" and "Address" parts of 725 | ## an email address. 726 | ## 727 | ## Example: "Brandon Zehm " 728 | ## would return: ("Brandon Zehm", "caspian@dotconf.net"); 729 | ## 730 | ## "caspian@dotconf.net" 731 | ## would return: ("caspian@dotconf.net", "caspian@dotconf.net") 732 | ############################################################################################### 733 | sub returnAddressParts { 734 | my $input = $_[0]; 735 | my $name = ""; 736 | my $address = ""; 737 | 738 | ## Make sure to fail if it looks totally invalid 739 | if ($input !~ /(\S+\@\S+)/) { 740 | $conf{'error'} = "ERROR => The address [$input] doesn't look like a valid email address, ignoring it"; 741 | return(undef()); 742 | } 743 | 744 | ## Check 1, should find addresses like: "Brandon Zehm " 745 | elsif ($input =~ /^\s*(\S(.*\S)?)\s*<(\S+\@\S+)>/o) { 746 | ($name, $address) = ($1, $3); 747 | } 748 | 749 | ## Otherwise if that failed, just get the address: 750 | elsif ($input =~ /<(\S+\@\S+)>/o) { 751 | $name = $address = $1; 752 | } 753 | 754 | ## Or maybe it was formatted this way: caspian@dotconf.net 755 | elsif ($input =~ /(\S+\@\S+)/o) { 756 | $name = $address = $1; 757 | } 758 | 759 | ## Something stupid happened, just return an error. 760 | unless ($name and $address) { 761 | printmsg("ERROR => Couldn't parse the address: $input", 0); 762 | printmsg("HINT => If you think this should work, consider reporting this as a bug to $conf{'authorEmail'}", 1); 763 | return(undef()); 764 | } 765 | 766 | ## Make sure there aren't invalid characters in the address, and return it. 767 | my $ctrl = '\000-\037'; 768 | my $nonASCII = '\x80-\xff'; 769 | if ($address =~ /[<> ,;:"'\[\]\\$ctrl$nonASCII]/) { 770 | printmsg("WARNING => The address [$address] seems to contain invalid characters: continuing anyway", 0); 771 | } 772 | return($name, $address); 773 | } 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | ############################################################################################### 791 | ## Function: base64_encode(string $data, bool $chunk) 792 | ## 793 | ## Description: Returns $data as a base64 encoded string. 794 | ## If $chunk is true, the encoded data is returned in 76 character long lines 795 | ## with the final \CR\LF removed. 796 | ## 797 | ## Note: This is only used from the smtp auth section of code. 798 | ## At some point it would be nice to merge the code that encodes attachments and this. 799 | ############################################################################################### 800 | sub base64_encode { 801 | my $data = $_[0]; 802 | my $chunk = $_[1]; 803 | my $tmp = ''; 804 | my $base64 = ''; 805 | my $CRLF = "\r\n"; 806 | 807 | ################################### 808 | ## Convert binary data to base64 ## 809 | ################################### 810 | while ($data =~ s/(.{45})//s) { ## Get 45 bytes from the binary string 811 | $tmp = substr(pack('u', $&), 1); ## Convert the binary to uuencoded text 812 | chop($tmp); 813 | $tmp =~ tr|` -_|AA-Za-z0-9+/|; ## Translate from uuencode to base64 814 | $base64 .= $tmp; 815 | } 816 | 817 | ########################## 818 | ## Encode the leftovers ## 819 | ########################## 820 | my $padding = ""; 821 | if ( ($data) and (length($data) > 0) ) { 822 | $padding = (3 - length($data) % 3) % 3; ## Set flag if binary data isn't divisible by 3 823 | $tmp = substr(pack('u', $data), 1); ## Convert the binary to uuencoded text 824 | chop($tmp); 825 | $tmp =~ tr|` -_|AA-Za-z0-9+/|; ## Translate from uuencode to base64 826 | $base64 .= $tmp; 827 | } 828 | 829 | ############################ 830 | ## Fix padding at the end ## 831 | ############################ 832 | $data = ''; 833 | $base64 =~ s/.{$padding}$/'=' x $padding/e if $padding; ## Fix the end padding if flag (from above) is set 834 | if ($chunk) { 835 | while ($base64 =~ s/(.{1,76})//s) { ## Put $CRLF after each 76 characters 836 | $data .= "$1$CRLF"; 837 | } 838 | } 839 | else { 840 | $data = $base64; 841 | } 842 | 843 | ## Remove any trailing CRLF's 844 | $data =~ s/(\r|\n)*$//s; 845 | return($data); 846 | } 847 | 848 | 849 | 850 | 851 | 852 | 853 | 854 | 855 | 856 | ######################################################### 857 | # SUB: send_attachment("/path/filename") 858 | # Sends the mime headers and base64 encoded file 859 | # to the email server. 860 | ######################################################### 861 | sub send_attachment { 862 | my ($filename) = @_; ## Get filename passed 863 | my (@fields, $y, $filename_name, $encoding, ## Local variables 864 | @attachlines, $content_type); 865 | my $bin = 1; 866 | 867 | @fields = split(/\/|\\/, $filename); ## Get the actual filename without the path 868 | $filename_name = pop(@fields); 869 | push @attachments_names, $filename_name; ## FIXME: This is only used later for putting in the log file 870 | 871 | ########################## 872 | ## Autodetect Mime Type ## 873 | ########################## 874 | 875 | @fields = split(/\./, $filename_name); 876 | $encoding = $fields[$#fields]; 877 | 878 | if ($encoding =~ /txt|text|log|conf|^c$|cpp|^h$|inc|m3u/i) { $content_type = 'text/plain'; } 879 | elsif ($encoding =~ /html|htm|shtml|shtm|asp|php|cfm/i) { $content_type = 'text/html'; } 880 | elsif ($encoding =~ /sh$/i) { $content_type = 'application/x-sh'; } 881 | elsif ($encoding =~ /tcl/i) { $content_type = 'application/x-tcl'; } 882 | elsif ($encoding =~ /pl$/i) { $content_type = 'application/x-perl'; } 883 | elsif ($encoding =~ /js$/i) { $content_type = 'application/x-javascript'; } 884 | elsif ($encoding =~ /man/i) { $content_type = 'application/x-troff-man'; } 885 | elsif ($encoding =~ /gif/i) { $content_type = 'image/gif'; } 886 | elsif ($encoding =~ /jpg|jpeg|jpe|jfif|pjpeg|pjp/i) { $content_type = 'image/jpeg'; } 887 | elsif ($encoding =~ /tif|tiff/i) { $content_type = 'image/tiff'; } 888 | elsif ($encoding =~ /xpm/i) { $content_type = 'image/x-xpixmap'; } 889 | elsif ($encoding =~ /bmp/i) { $content_type = 'image/x-MS-bmp'; } 890 | elsif ($encoding =~ /pcd/i) { $content_type = 'image/x-photo-cd'; } 891 | elsif ($encoding =~ /png/i) { $content_type = 'image/png'; } 892 | elsif ($encoding =~ /aif|aiff/i) { $content_type = 'audio/x-aiff'; } 893 | elsif ($encoding =~ /wav/i) { $content_type = 'audio/x-wav'; } 894 | elsif ($encoding =~ /mp2|mp3|mpa/i) { $content_type = 'audio/x-mpeg'; } 895 | elsif ($encoding =~ /ra$|ram/i) { $content_type = 'audio/x-pn-realaudio'; } 896 | elsif ($encoding =~ /mpeg|mpg/i) { $content_type = 'video/mpeg'; } 897 | elsif ($encoding =~ /mov|qt$/i) { $content_type = 'video/quicktime'; } 898 | elsif ($encoding =~ /avi/i) { $content_type = 'video/x-msvideo'; } 899 | elsif ($encoding =~ /zip/i) { $content_type = 'application/x-zip-compressed'; } 900 | elsif ($encoding =~ /tar/i) { $content_type = 'application/x-tar'; } 901 | elsif ($encoding =~ /jar/i) { $content_type = 'application/java-archive'; } 902 | elsif ($encoding =~ /exe|bin/i) { $content_type = 'application/octet-stream'; } 903 | elsif ($encoding =~ /ppt|pot|ppa|pps|pwz/i) { $content_type = 'application/vnd.ms-powerpoint'; } 904 | elsif ($encoding =~ /mdb|mda|mde/i) { $content_type = 'application/vnd.ms-access'; } 905 | elsif ($encoding =~ /xls|xlt|xlm|xld|xla|xlc|xlw|xll/i) { $content_type = 'application/vnd.ms-excel'; } 906 | elsif ($encoding =~ /doc|dot/i) { $content_type = 'application/msword'; } 907 | elsif ($encoding =~ /rtf/i) { $content_type = 'application/rtf'; } 908 | elsif ($encoding =~ /pdf/i) { $content_type = 'application/pdf'; } 909 | elsif ($encoding =~ /tex/i) { $content_type = 'application/x-tex'; } 910 | elsif ($encoding =~ /latex/i) { $content_type = 'application/x-latex'; } 911 | elsif ($encoding =~ /vcf/i) { $content_type = 'application/x-vcard'; } 912 | else { $content_type = 'application/octet-stream'; } 913 | 914 | 915 | ############################ 916 | ## Process the attachment ## 917 | ############################ 918 | 919 | ##################################### 920 | ## Generate and print MIME headers ## 921 | ##################################### 922 | 923 | $y = "$CRLF--$conf{'delimiter'}$CRLF"; 924 | $y .= "Content-Type: $content_type;$CRLF"; 925 | $y .= " name=\"$filename_name\"$CRLF"; 926 | $y .= "Content-Transfer-Encoding: base64$CRLF"; 927 | $y .= "Content-Disposition: attachment; filename=\"$filename_name\"$CRLF"; 928 | $y .= "$CRLF"; 929 | print $SERVER $y; 930 | 931 | 932 | ########################################################### 933 | ## Convert the file to base64 and print it to the server ## 934 | ########################################################### 935 | 936 | open (FILETOATTACH, $filename) || do { 937 | printmsg("ERROR => Opening the file [$filename] for attachment failed with the error: $!", 0); 938 | return(1); 939 | }; 940 | binmode(FILETOATTACH); ## Hack to make Win32 work 941 | 942 | my $res = ""; 943 | my $tmp = ""; 944 | my $base64 = ""; 945 | while () { ## Read a line from the (binary) file 946 | $res .= $_; 947 | 948 | ################################### 949 | ## Convert binary data to base64 ## 950 | ################################### 951 | while ($res =~ s/(.{45})//s) { ## Get 45 bytes from the binary string 952 | $tmp = substr(pack('u', $&), 1); ## Convert the binary to uuencoded text 953 | chop($tmp); 954 | $tmp =~ tr|` -_|AA-Za-z0-9+/|; ## Translate from uuencode to base64 955 | $base64 .= $tmp; 956 | } 957 | 958 | ################################ 959 | ## Print chunks to the server ## 960 | ################################ 961 | while ($base64 =~ s/(.{76})//s) { 962 | print $SERVER "$1$CRLF"; 963 | } 964 | 965 | } 966 | 967 | ################################### 968 | ## Encode and send the leftovers ## 969 | ################################### 970 | my $padding = ""; 971 | if ( ($res) and (length($res) >= 1) ) { 972 | $padding = (3 - length($res) % 3) % 3; ## Set flag if binary data isn't divisible by 3 973 | $res = substr(pack('u', $res), 1); ## Convert the binary to uuencoded text 974 | chop($res); 975 | $res =~ tr|` -_|AA-Za-z0-9+/|; ## Translate from uuencode to base64 976 | } 977 | 978 | ############################ 979 | ## Fix padding at the end ## 980 | ############################ 981 | $res = $base64 . $res; ## Get left overs from above 982 | $res =~ s/.{$padding}$/'=' x $padding/e if $padding; ## Fix the end padding if flag (from above) is set 983 | if ($res) { 984 | while ($res =~ s/(.{1,76})//s) { ## Send it to the email server. 985 | print $SERVER "$1$CRLF"; 986 | } 987 | } 988 | 989 | close (FILETOATTACH) || do { 990 | printmsg("ERROR - Closing the filehandle for file [$filename] failed with the error: $!", 0); 991 | return(2); 992 | }; 993 | 994 | ## Return 0 errors 995 | return(0); 996 | 997 | } 998 | 999 | 1000 | 1001 | 1002 | 1003 | 1004 | 1005 | 1006 | 1007 | ############################################################################################### 1008 | ## Function: $string = get_hostname (boot $fqdn) 1009 | ## 1010 | ## Description: Tries really hard to returns the short (or FQDN) hostname of the current 1011 | ## system. Uses techniques and code from the Sys-Hostname module. 1012 | ## 1013 | ## Input: $fqdn A true value (1) will cause this function to return a FQDN hostname 1014 | ## rather than a short hostname. 1015 | ## 1016 | ## Output: Returns a string 1017 | ############################################################################################### 1018 | sub get_hostname { 1019 | ## Assign incoming parameters to variables 1020 | my ( $fqdn ) = @_; 1021 | my $hostname = ""; 1022 | 1023 | ## STEP 1: Get short hostname 1024 | 1025 | ## Load Sys::Hostname if it's available 1026 | eval { require Sys::Hostname; }; 1027 | unless ($@) { 1028 | $hostname = Sys::Hostname::hostname(); 1029 | } 1030 | 1031 | ## If that didn't get us a hostname, try a few other things 1032 | else { 1033 | ## Windows systems 1034 | if ($^O !~ /win/i) { 1035 | if ($ENV{'COMPUTERNAME'}) { $hostname = $ENV{'COMPUTERNAME'}; } 1036 | if (!$hostname) { $hostname = gethostbyname('localhost'); } 1037 | if (!$hostname) { chomp($hostname = `hostname 2> NUL`) }; 1038 | } 1039 | 1040 | ## Unix systems 1041 | else { 1042 | local $ENV{PATH} = '/usr/bin:/bin:/usr/sbin:/sbin'; ## Paranoia 1043 | 1044 | ## Try the environment first (Help! What other variables could/should I be checking here?) 1045 | if ($ENV{'HOSTNAME'}) { $hostname = $ENV{'HOSTNAME'}; } 1046 | 1047 | ## Try the hostname command 1048 | eval { local $SIG{__DIE__}; local $SIG{CHLD}; $hostname = `hostname 2>/dev/null`; chomp($hostname); } || 1049 | 1050 | ## Try POSIX::uname(), which strictly can't be expected to be correct 1051 | eval { local $SIG{__DIE__}; require POSIX; $hostname = (POSIX::uname())[1]; } || 1052 | 1053 | ## Try the uname command 1054 | eval { local $SIG{__DIE__}; $hostname = `uname -n 2>/dev/null`; chomp($hostname); }; 1055 | 1056 | } 1057 | 1058 | ## If we can't find anything else, return "" 1059 | if (!$hostname) { 1060 | print "WARNING => No hostname could be determined, please specify one with -o fqdn=FQDN option!\n"; 1061 | return("unknown"); 1062 | } 1063 | } 1064 | 1065 | ## Return the short hostname 1066 | unless ($fqdn) { 1067 | $hostname =~ s/\..*//; 1068 | return(lc($hostname)); 1069 | } 1070 | 1071 | ## STEP 2: Determine the FQDN 1072 | 1073 | ## First, if we already have one return it. 1074 | if ($hostname =~ /\w\.\w/) { return(lc($hostname)); } 1075 | 1076 | ## Next try using 1077 | eval { $fqdn = (gethostbyname($hostname))[0]; }; 1078 | if ($fqdn) { return(lc($fqdn)); } 1079 | return(lc($hostname)); 1080 | } 1081 | 1082 | 1083 | 1084 | 1085 | 1086 | 1087 | 1088 | 1089 | ############################################################################################### 1090 | ## Function: printmsg (string $message, int $level) 1091 | ## 1092 | ## Description: Handles all messages - printing them to the screen only if the messages 1093 | ## $level is >= the global debug level. If $conf{'logFile'} is defined it 1094 | ## will also log the message to that file. 1095 | ## 1096 | ## Input: $message A message to be printed, logged, etc. 1097 | ## $level The debug level of the message. If 1098 | ## not defined 0 will be assumed. 0 is 1099 | ## considered a normal message, 1 and 1100 | ## higher is considered a debug message. 1101 | ## 1102 | ## Output: Prints to STDOUT 1103 | ## 1104 | ## Assumptions: $conf{'hostname'} should be the name of the computer we're running on. 1105 | ## $conf{'stdout'} should be set to 1 if you want to print to stdout 1106 | ## $conf{'logFile'} should be a full path to a log file if you want that 1107 | ## $conf{'debug'} should be an integer between 0 and 10. 1108 | ## 1109 | ## Example: printmsg("WARNING: We believe in generic error messages... NOT!", 0); 1110 | ############################################################################################### 1111 | sub printmsg { 1112 | ## Assign incoming parameters to variables 1113 | my ( $message, $level ) = @_; 1114 | 1115 | ## Make sure input is sane 1116 | $level = 0 if (!defined($level)); 1117 | $message =~ s/\s+$//sgo; 1118 | $message =~ s/\r?\n/, /sgo; 1119 | 1120 | ## Continue only if the debug level of the program is >= message debug level. 1121 | if ($conf{'debug'} >= $level) { 1122 | 1123 | ## Get the date in the format: Dec 3 11:14:04 1124 | my ($sec, $min, $hour, $mday, $mon) = localtime(); 1125 | $mon = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')[$mon]; 1126 | my $date = sprintf("%s %02d %02d:%02d:%02d", $mon, $mday, $hour, $min, $sec); 1127 | 1128 | ## Print to STDOUT always if debugging is enabled, or if conf{stdout} is true. 1129 | if ( ($conf{'debug'} >= 1) or ($conf{'stdout'} == 1) ) { 1130 | print "$date $conf{'hostname'} $conf{'programName'}\[$$\]: $message\n"; 1131 | } 1132 | 1133 | ## Print to the log file if $conf{'logging'} is true 1134 | if ($conf{'logFile'}) { 1135 | if (openLogFile($conf{'logFile'})) { $conf{'logFile'} = ""; printmsg("ERROR => Opening the file [$conf{'logFile'}] for appending returned the error: $!", 1); } 1136 | print LOGFILE "$date $conf{'hostname'} $conf{'programName'}\[$$\]: $message\n"; 1137 | } 1138 | 1139 | } 1140 | 1141 | ## Return 0 errors 1142 | return(0); 1143 | } 1144 | 1145 | 1146 | 1147 | 1148 | 1149 | 1150 | 1151 | 1152 | 1153 | 1154 | 1155 | 1156 | ############################################################################################### 1157 | ## FUNCTION: 1158 | ## openLogFile ( $filename ) 1159 | ## 1160 | ## 1161 | ## DESCRIPTION: 1162 | ## Opens the file $filename and attaches it to the filehandle "LOGFILE". Returns 0 on success 1163 | ## and non-zero on failure. Error codes are listed below, and the error message gets set in 1164 | ## global variable $!. 1165 | ## 1166 | ## 1167 | ## Example: 1168 | ## openFile ("/var/log/sendEmail.log"); 1169 | ## 1170 | ############################################################################################### 1171 | sub openLogFile { 1172 | ## Get the incoming filename 1173 | my $filename = $_[0]; 1174 | 1175 | ## Make sure our file exists, and if the file doesn't exist then create it 1176 | if ( ! -f $filename ) { 1177 | print STDERR "NOTICE: The log file [$filename] does not exist. Creating it now with mode [0600].\n" if ($conf{'stdout'}); 1178 | open (LOGFILE, ">>$filename"); 1179 | close LOGFILE; 1180 | chmod (0600, $filename); 1181 | } 1182 | 1183 | ## Now open the file and attach it to a filehandle 1184 | open (LOGFILE,">>$filename") or return (1); 1185 | 1186 | ## Put the file into non-buffering mode 1187 | select LOGFILE; 1188 | $| = 1; 1189 | select STDOUT; 1190 | 1191 | ## Return success 1192 | return(0); 1193 | } 1194 | 1195 | 1196 | 1197 | 1198 | 1199 | 1200 | 1201 | 1202 | ############################################################################################### 1203 | ## Function: read_file (string $filename) 1204 | ## 1205 | ## Description: Reads the contents of a file and returns a two part array: 1206 | ## ($status, $file-contents) 1207 | ## $status is 0 on success, non-zero on error. 1208 | ## 1209 | ## Example: ($status, $file) = read_file("/etc/passwd"); 1210 | ############################################################################################### 1211 | sub read_file { 1212 | my ( $filename ) = @_; 1213 | 1214 | ## If the value specified is a file, load the file's contents 1215 | if ( (-e $filename and -r $filename) ) { 1216 | my $FILE; 1217 | if(!open($FILE, ' ' . $filename)) { 1218 | return((1, "")); 1219 | } 1220 | my $file = ''; 1221 | while (<$FILE>) { 1222 | $file .= $_; 1223 | } 1224 | ## Strip an ending \r\n 1225 | $file =~ s/\r?\n$//os; 1226 | } 1227 | return((1, "")); 1228 | } 1229 | 1230 | 1231 | 1232 | 1233 | 1234 | 1235 | 1236 | 1237 | 1238 | ############################################################################################### 1239 | ## Function: quit (string $message, int $errorLevel) 1240 | ## 1241 | ## Description: Exits the program, optionally printing $message. It 1242 | ## returns an exit error level of $errorLevel to the 1243 | ## system (0 means no errors, and is assumed if empty.) 1244 | ## 1245 | ## Example: quit("Exiting program normally", 0); 1246 | ############################################################################################### 1247 | sub quit { 1248 | my ( $message, $errorLevel ) = @_; 1249 | $errorLevel = 0 if (!defined($errorLevel)); 1250 | 1251 | ## Print exit message 1252 | if ($message) { 1253 | printmsg($message, 0); 1254 | } 1255 | 1256 | ## Exit 1257 | exit($errorLevel); 1258 | } 1259 | 1260 | 1261 | 1262 | 1263 | 1264 | 1265 | 1266 | 1267 | 1268 | 1269 | 1270 | 1271 | ############################################################################################### 1272 | ## Function: help () 1273 | ## 1274 | ## Description: For all those newbies ;) 1275 | ## Prints a help message and exits the program. 1276 | ## 1277 | ############################################################################################### 1278 | sub help { 1279 | exit(1) if (!$conf{'stdout'}); 1280 | print <${colorNoBold} 1283 | 1284 | Synopsis: $conf{'programName'} -f ADDRESS [options] 1285 | 1286 | ${colorRed}Required:${colorNormal} 1287 | -f ADDRESS from (sender) email address 1288 | * At least one recipient required via -t, -cc, or -bcc 1289 | * Message body required via -m, STDIN, or -o message-file=FILE 1290 | 1291 | ${colorGreen}Common:${colorNormal} 1292 | -t ADDRESS [ADDR ...] to email address(es) 1293 | -u SUBJECT message subject 1294 | -m MESSAGE message body 1295 | -s SERVER[:PORT] smtp mail relay, default is $conf{'server'}:$conf{'port'} 1296 | 1297 | ${colorGreen}Optional:${colorNormal} 1298 | -a FILE [FILE ...] file attachment(s) 1299 | -cc ADDRESS [ADDR ...] cc email address(es) 1300 | -bcc ADDRESS [ADDR ...] bcc email address(es) 1301 | -xu USERNAME username for SMTP authentication 1302 | -xp PASSWORD password for SMTP authentication 1303 | 1304 | ${colorGreen}Paranormal:${colorNormal} 1305 | -b BINDADDR[:PORT] local host bind address 1306 | -l LOGFILE log to the specified file 1307 | -v verbosity, use multiple times for greater effect 1308 | -q be quiet (i.e. no STDOUT output) 1309 | -o NAME=VALUE advanced options, for details try: --help misc 1310 | -o message-content-type= 1311 | -o message-file=FILE -o message-format=raw 1312 | -o message-header=HEADER -o message-charset=CHARSET 1313 | -o reply-to=ADDRESS -o timeout=SECONDS 1314 | -o username=USERNAME -o password=PASSWORD 1315 | -o tls= -o fqdn=FQDN 1316 | 1317 | 1318 | ${colorGreen}Help:${colorNormal} 1319 | --help the helpful overview you're reading now 1320 | --help addressing explain addressing and related options 1321 | --help message explain message body input and related options 1322 | --help networking explain -s, -b, etc 1323 | --help output explain logging and other output options 1324 | --help misc explain -o options, TLS, SMTP auth, and more 1325 | 1326 | EOM 1327 | exit(1); 1328 | } 1329 | 1330 | 1331 | 1332 | 1333 | 1334 | 1335 | 1336 | 1337 | 1338 | ############################################################################################### 1339 | ## Function: helpTopic ($topic) 1340 | ## 1341 | ## Description: For all those newbies ;) 1342 | ## Prints a help message and exits the program. 1343 | ## 1344 | ############################################################################################### 1345 | sub helpTopic { 1346 | exit(1) if (!$conf{'stdout'}); 1347 | my ($topic) = @_; 1348 | 1349 | CASE: { 1350 | 1351 | 1352 | 1353 | 1354 | ## ADDRESSING 1355 | ($topic eq 'addressing') && do { 1356 | print <" 1392 | Just Address: "john.doe\@gmail.com" 1393 | 1394 | The "Full Name" method is useful if you want a name, rather than a plain 1395 | email address, to be displayed in the recipient's From, To, or Cc fields 1396 | when they view the message. 1397 | 1398 | 1399 | ${colorGreen}Multiple Recipients${colorNormal} 1400 | The -t, -cc, and -bcc options each accept multiple addresses. They may be 1401 | specified by separating them by either a white space, comma, or semi-colon 1402 | separated list. You may also specify the -t, -cc, and -bcc options multiple 1403 | times, each occurance will append the new recipients to the respective list. 1404 | 1405 | Examples: 1406 | (I used "-t" in these examples, but it can be "-cc" or "-bcc" as well) 1407 | 1408 | * Space separated list: 1409 | -t jane.doe\@yahoo.com "John Doe " 1410 | 1411 | * Semi-colon separated list: 1412 | -t "jane.doe\@yahoo.com; John Doe " 1413 | 1414 | * Comma separated list: 1415 | -t "jane.doe\@yahoo.com, John Doe " 1416 | 1417 | * Multiple -t, -cc, or -bcc options: 1418 | -t "jane.doe\@yahoo.com" -t "John Doe " 1419 | 1420 | 1421 | EOM 1422 | last CASE; 1423 | }; 1424 | 1425 | 1426 | 1427 | 1428 | 1429 | 1430 | ## MESSAGE 1431 | ($topic eq 'message') && do { 1432 | print < 1442 | -o message-header=EMAIL HEADER 1443 | -o message-charset=CHARSET 1444 | -o message-format=raw 1445 | 1446 | -u SUBJECT 1447 | This option allows you to specify the subject for your email message. 1448 | It is not required (anymore) that the subject be quoted, although it 1449 | is recommended. The subject will be read until an argument starting 1450 | with a hyphen (-) is found. 1451 | Examples: 1452 | -u "Contact information while on vacation" 1453 | -u New Microsoft vulnerability discovered 1454 | 1455 | -m MESSAGE 1456 | This option is one of three methods that allow you to specify the message 1457 | body for your email. The message may be specified on the command line 1458 | with this -m option, read from a file with the -o message-file=FILE 1459 | option, or read from STDIN if neither of these options are present. 1460 | 1461 | It is not required (anymore) that the message be quoted, although it is 1462 | recommended. The message will be read until an argument starting with a 1463 | hyphen (-) is found. 1464 | Examples: 1465 | -m "See you in South Beach, Hawaii. -Todd" 1466 | -m Please ensure that you upgrade your systems right away 1467 | 1468 | Multi-line message bodies may be specified with the -m option by putting 1469 | a "\\n" into the message. Example: 1470 | -m "This is line 1.\\nAnd this is line 2." 1471 | 1472 | HTML messages are supported, simply begin your message with "" and 1473 | sendEmail will properly label the mime header so MUAs properly render 1474 | the message. It is currently not possible without "-o message-format=raw" 1475 | to send a message with both text and html parts with sendEmail. 1476 | 1477 | -o message-file=FILE 1478 | This option is one of three methods that allow you to specify the message 1479 | body for your email. To use this option simply specify a text file 1480 | containing the body of your email message. Examples: 1481 | -o message-file=/root/message.txt 1482 | -o message-file="C:\\Program Files\\output.txt" 1483 | 1484 | -o message-content-type= 1485 | This option allows you to specify the content-type of the email. If your 1486 | email message is an html message but is being displayed as a text message 1487 | just add "-o message-content-type=html" to the command line to force it 1488 | to display as an html message. This actually just changes the Content-Type: 1489 | header. Advanced users will be happy to know that if you specify anything 1490 | other than the three options listed above it will use that as the vaule 1491 | for the Content-Type header. 1492 | 1493 | -o message-header=EMAIL HEADER 1494 | This option allows you to specify additional email headers to be included. 1495 | To add more than one message header simply use this option on the command 1496 | line more than once. If you specify a message header that sendEmail would 1497 | normally generate the one you specified will be used in it's place. 1498 | Do not use this unless you know what you are doing! 1499 | Example: 1500 | To scare a Microsoft Outlook user you may want to try this: 1501 | -o message-header="X-Message-Flag: Message contains illegal content" 1502 | Example: 1503 | To request a read-receipt try this: 1504 | -o message-header="Disposition-Notification-To: " 1505 | Example: 1506 | To set the message priority try this: 1507 | -o message-header="X-Priority: 1" 1508 | Priority reference: 1=highest, 2=high, 3=normal, 4=low, 5=lowest 1509 | 1510 | -o message-charset=CHARSET 1511 | This option allows you to specify the character-set for the message body. 1512 | The default is iso-8859-1. 1513 | 1514 | -o message-format=raw 1515 | This option instructs sendEmail to assume the message (specified with -m, 1516 | read from STDIN, or read from the file specified in -o message-file=FILE) 1517 | is already a *complete* email message. SendEmail will not generate any 1518 | headers and will transmit the message as-is to the remote SMTP server. 1519 | Due to the nature of this option the following command line options will 1520 | be ignored when this one is used: 1521 | -u SUBJECT 1522 | -o message-header=EMAIL HEADER 1523 | -o message-charset=CHARSET 1524 | -a ATTACHMENT 1525 | 1526 | 1527 | ${colorGreen}The Message Body${colorNormal} 1528 | The email message body may be specified in one of three ways: 1529 | 1) Via the -m MESSAGE command line option. 1530 | Example: 1531 | -m "This is the message body" 1532 | 1533 | 2) By putting the message body in a file and using the -o message-file=FILE 1534 | command line option. 1535 | Example: 1536 | -o message-file=/root/message.txt 1537 | 1538 | 3) By piping the message body to sendEmail when nither of the above command 1539 | line options were specified. 1540 | Example: 1541 | grep "ERROR" /var/log/messages | sendEmail -t you\@domain.com ... 1542 | 1543 | If the message body begins with "" then the message will be treated as 1544 | an HTML message and the MIME headers will be written so that a HTML capable 1545 | email client will display the message in it's HTML form. 1546 | Any of the above methods may be used with the -o message-format=raw option 1547 | to deliver an already complete email message. 1548 | 1549 | 1550 | EOM 1551 | last CASE; 1552 | }; 1553 | 1554 | 1555 | 1556 | 1557 | 1558 | 1559 | ## MISC 1560 | ($topic eq 'misc') && do { 1561 | print < 1573 | -o timeout=SECONDS 1574 | -o fqdn=FQDN 1575 | 1576 | -a ATTACHMENT [ATTACHMENT ...] 1577 | This option allows you to attach any number of files to your email message. 1578 | To specify more than one attachment, simply separate each filename with a 1579 | space. Example: -a file1.txt file2.txt file3.txt 1580 | 1581 | -xu USERNAME 1582 | Alias for -o username=USERNAME 1583 | 1584 | -xp PASSWORD 1585 | Alias for -o password=PASSWORD 1586 | 1587 | -o username=USERNAME (synonym for -xu) 1588 | These options allow specification of a username to be used with SMTP 1589 | servers that require authentication. If a username is specified but a 1590 | password is not, you will be prompted to enter one at runtime. 1591 | 1592 | -o password=PASSWORD (synonym for -xp) 1593 | These options allow specification of a password to be used with SMTP 1594 | servers that require authentication. If a username is specified but a 1595 | password is not, you will be prompted to enter one at runtime. 1596 | 1597 | -o tls= 1598 | This option allows you to specify if TLS (SSL for SMTP) should be enabled 1599 | or disabled. The default, auto, will use TLS automatically if your perl 1600 | installation has the IO::Socket::SSL and Net::SSLeay modules available, 1601 | and if the remote SMTP server supports TLS. To require TLS for message 1602 | delivery set this to yes. To disable TLS support set this to no. A debug 1603 | level of one or higher will reveal details about the status of TLS. 1604 | 1605 | -o timeout=SECONDS 1606 | This option sets the timeout value in seconds used for all network reads, 1607 | writes, and a few other things. 1608 | 1609 | -o fqdn=FQDN 1610 | This option sets the Fully Qualified Domain Name used during the initial 1611 | SMTP greeting. Normally this is automatically detected, but in case you 1612 | need to manually set it for some reason or get a warning about detection 1613 | failing, you can use this to override the default. 1614 | 1615 | 1616 | EOM 1617 | last CASE; 1618 | }; 1619 | 1620 | 1621 | 1622 | 1623 | 1624 | 1625 | ## NETWORKING 1626 | ($topic eq 'networking') && do { 1627 | print < 1636 | -o timeout=SECONDS 1637 | 1638 | -s SERVER[:PORT] 1639 | This option allows you to specify the SMTP server sendEmail should 1640 | connect to to deliver your email message to. If this option is not 1641 | specified sendEmail will try to connect to localhost:25 to deliver 1642 | the message. THIS IS MOST LIKELY NOT WHAT YOU WANT, AND WILL LIKELY 1643 | FAIL unless you have a email server (commonly known as an MTA) running 1644 | on your computer! 1645 | Typically you will need to specify your company or ISP's email server. 1646 | For example, if you use CableOne you will need to specify: 1647 | -s mail.cableone.net 1648 | If you have your own email server running on port 300 you would 1649 | probably use an option like this: 1650 | -s myserver.mydomain.com:300 1651 | If you're a GMail user try: 1652 | -s smtp.gmail.com:587 -xu me\@gmail.com -xp PASSWD 1653 | 1654 | -b BINDADDR[:PORT] 1655 | This option allows you to specify the local IP address (and optional 1656 | tcp port number) for sendEmail to bind to when connecting to the remote 1657 | SMTP server. This useful for people who need to send an email from a 1658 | specific network interface or source address and are running sendEmail on 1659 | a firewall or other host with several network interfaces. 1660 | 1661 | -o tls= 1662 | This option allows you to specify if TLS (SSL for SMTP) should be enabled 1663 | or disabled. The default, auto, will use TLS automatically if your perl 1664 | installation has the IO::Socket::SSL and Net::SSLeay modules available, 1665 | and if the remote SMTP server supports TLS. To require TLS for message 1666 | delivery set this to yes. To disable TLS support set this to no. A debug 1667 | level of one or higher will reveal details about the status of TLS. 1668 | 1669 | -o timeout=SECONDS 1670 | This option sets the timeout value in seconds used for all network reads, 1671 | writes, and a few other things. 1672 | 1673 | 1674 | EOM 1675 | last CASE; 1676 | }; 1677 | 1678 | 1679 | 1680 | 1681 | 1682 | 1683 | ## OUTPUT 1684 | ($topic eq 'output') && do { 1685 | print < The help topic specified is not valid!", 1); 1724 | }; 1725 | 1726 | exit(1); 1727 | } 1728 | 1729 | 1730 | 1731 | 1732 | 1733 | 1734 | 1735 | 1736 | 1737 | 1738 | 1739 | 1740 | 1741 | 1742 | 1743 | 1744 | 1745 | 1746 | 1747 | 1748 | 1749 | 1750 | ############################# 1751 | ## ## 1752 | ## MAIN PROGRAM ## 1753 | ## ## 1754 | ############################# 1755 | 1756 | 1757 | ## Initialize 1758 | initialize(); 1759 | 1760 | ## Process Command Line 1761 | processCommandLine(); 1762 | $conf{'alarm'} = $opt{'timeout'}; 1763 | 1764 | ## Abort program after $conf{'alarm'} seconds to avoid infinite hangs 1765 | alarm($conf{'alarm'}) if ($^O !~ /win/i); ## alarm() doesn't work in win32 1766 | 1767 | 1768 | 1769 | 1770 | ################################################### 1771 | ## Read $message from STDIN if -m was not used ## 1772 | ################################################### 1773 | 1774 | if (!($message)) { 1775 | ## Read message body from a file specified with -o message-file= 1776 | if ($opt{'message-file'}) { 1777 | if (! -e $opt{'message-file'}) { 1778 | printmsg("ERROR => Message body file specified [$opt{'message-file'}] does not exist!", 0); 1779 | printmsg("HINT => 1) check spelling of your file; 2) fully qualify the path; 3) doubble quote it", 1); 1780 | quit("", 1); 1781 | } 1782 | if (! -r $opt{'message-file'}) { 1783 | printmsg("ERROR => Message body file specified can not be read due to restricted permissions!", 0); 1784 | printmsg("HINT => Check permissions on file specified to ensure it can be read", 1); 1785 | quit("", 1); 1786 | } 1787 | if (!open(MFILE, "< " . $opt{'message-file'})) { 1788 | printmsg("ERROR => Error opening message body file [$opt{'message-file'}]: $!", 0); 1789 | quit("", 1); 1790 | } 1791 | while () { 1792 | $message .= $_; 1793 | } 1794 | close(MFILE); 1795 | } 1796 | 1797 | ## Read message body from STDIN 1798 | else { 1799 | alarm($conf{'alarm'}) if ($^O !~ /win/i); ## alarm() doesn't work in win32 1800 | if ($conf{'stdout'}) { 1801 | print "Reading message body from STDIN because the '-m' option was not used.\n"; 1802 | print "If you are manually typing in a message:\n"; 1803 | print " - First line must be received within $conf{'alarm'} seconds.\n" if ($^O !~ /win/i); 1804 | print " - End manual input with a CTRL-D on its own line.\n\n" if ($^O !~ /win/i); 1805 | print " - End manual input with a CTRL-Z on its own line.\n\n" if ($^O =~ /win/i); 1806 | } 1807 | while () { ## Read STDIN into $message 1808 | $message .= $_; 1809 | alarm(0) if ($^O !~ /win/i); ## Disable the alarm since at least one line was received 1810 | } 1811 | printmsg("Message input complete.", 0); 1812 | } 1813 | } 1814 | 1815 | ## Replace bare LF's with CRLF's (\012 should always have \015 with it) 1816 | $message =~ s/(\015)?(\012|$)/\015\012/g; 1817 | 1818 | ## Replace bare CR's with CRLF's (\015 should always have \012 with it) 1819 | $message =~ s/(\015)(\012|$)?/\015\012/g; 1820 | 1821 | ## Check message for bare periods and encode them 1822 | $message =~ s/(^|$CRLF)(\.{1})($CRLF|$)/$1.$2$3/g; 1823 | 1824 | ## Get the current date for the email header 1825 | my ($sec,$min,$hour,$mday,$mon,$year,$day) = gmtime(); 1826 | $year += 1900; $mon = return_month($mon); $day = return_day($day); 1827 | my $date = sprintf("%s, %s %s %d %.2d:%.2d:%.2d %s",$day, $mday, $mon, $year, $hour, $min, $sec, $conf{'timezone'}); 1828 | 1829 | 1830 | 1831 | 1832 | ################################## 1833 | ## Connect to the SMTP server ## 1834 | ################################## 1835 | printmsg("DEBUG => Connecting to $conf{'server'}:$conf{'port'}", 1); 1836 | $SIG{'ALRM'} = sub { 1837 | printmsg("ERROR => Timeout while connecting to $conf{'server'}:$conf{'port'} There was no response after $conf{'alarm'} seconds.", 0); 1838 | printmsg("HINT => Try specifying a different mail relay with the -s option.", 1); 1839 | quit("", 1); 1840 | }; 1841 | alarm($conf{'alarm'}) if ($^O !~ /win/i); ## alarm() doesn't work in win32; 1842 | $SERVER = IO::Socket::INET->new( PeerAddr => $conf{'server'}, 1843 | PeerPort => $conf{'port'}, 1844 | LocalAddr => $conf{'bindaddr'}, 1845 | Proto => 'tcp', 1846 | Autoflush => 1, 1847 | timeout => $conf{'alarm'}, 1848 | ); 1849 | alarm(0) if ($^O !~ /win/i); ## alarm() doesn't work in win32; 1850 | 1851 | ## Make sure we got connected 1852 | if ( (!$SERVER) or (!$SERVER->opened()) ) { 1853 | printmsg("ERROR => Connection attempt to $conf{'server'}:$conf{'port'} failed: $@", 0); 1854 | printmsg("HINT => Try specifying a different mail relay with the -s option.", 1); 1855 | quit("", 1); 1856 | } 1857 | 1858 | ## Save our IP address for later 1859 | $conf{'ip'} = $SERVER->sockhost(); 1860 | printmsg("DEBUG => My IP address is: $conf{'ip'}", 1); 1861 | 1862 | 1863 | 1864 | 1865 | 1866 | 1867 | 1868 | ######################### 1869 | ## Do the SMTP Dance ## 1870 | ######################### 1871 | 1872 | ## Read initial greeting to make sure we're talking to a live SMTP server 1873 | if (SMTPchat()) { quit($conf{'error'}, 1); } 1874 | 1875 | ## We're about to use $opt{'fqdn'}, make sure it isn't empty 1876 | if (!$opt{'fqdn'}) { 1877 | ## Ok, that means we couldn't get a hostname, how about using the IP address for the HELO instead 1878 | $opt{'fqdn'} = "[" . $conf{'ip'} . "]"; 1879 | } 1880 | 1881 | ## EHLO 1882 | if (SMTPchat('EHLO ' . $opt{'fqdn'})) { 1883 | printmsg($conf{'error'}, 0); 1884 | printmsg("NOTICE => EHLO command failed, attempting HELO instead"); 1885 | if (SMTPchat('HELO ' . $opt{'fqdn'})) { quit($conf{'error'}, 1); } 1886 | if ( $opt{'username'} and $opt{'password'} ) { 1887 | printmsg("WARNING => The mail server does not support SMTP authentication!", 0); 1888 | } 1889 | } 1890 | else { 1891 | 1892 | ## Determin if the server supports TLS 1893 | if ($conf{'SMTPchat_response'} =~ /STARTTLS/) { 1894 | $conf{'tls_server'} = 1; 1895 | printmsg("DEBUG => The remote SMTP server supports TLS :)", 2); 1896 | } 1897 | else { 1898 | $conf{'tls_server'} = 0; 1899 | printmsg("DEBUG => The remote SMTP server does NOT support TLS :(", 2); 1900 | } 1901 | 1902 | ## Start TLS if possible 1903 | if ($conf{'tls_server'} == 1 and $conf{'tls_client'} == 1 and $opt{'tls'} =~ /^(yes|auto)$/) { 1904 | printmsg("DEBUG => Starting TLS", 2); 1905 | if (SMTPchat('STARTTLS')) { quit($conf{'error'}, 1); } 1906 | if (! IO::Socket::SSL->start_SSL($SERVER, SSL_version => 'SSLv3 TLSv1')) { 1907 | quit("ERROR => TLS setup failed: " . IO::Socket::SSL::errstr(), 1); 1908 | } 1909 | printmsg("DEBUG => TLS: Using cipher: ". $SERVER->get_cipher(), 3); 1910 | printmsg("DEBUG => TLS session initialized :)", 1); 1911 | 1912 | ## Restart our SMTP session 1913 | if (SMTPchat('EHLO ' . $opt{'fqdn'})) { quit($conf{'error'}, 1); } 1914 | } 1915 | elsif ($opt{'tls'} eq 'yes' and $conf{'tls_server'} == 0) { 1916 | quit("ERROR => TLS not possible! Remote SMTP server, $conf{'server'}, does not support it.", 1); 1917 | } 1918 | 1919 | 1920 | ## Do SMTP Auth if required 1921 | if ( $opt{'username'} and $opt{'password'} ) { 1922 | if ($conf{'SMTPchat_response'} !~ /AUTH\s/) { 1923 | printmsg("NOTICE => Authentication not supported by the remote SMTP server!", 0); 1924 | } 1925 | else { 1926 | my $auth_succeeded = 0; 1927 | my $mutual_method = 0; 1928 | 1929 | # ## SASL CRAM-MD5 authentication method 1930 | # if ($conf{'SMTPchat_response'} =~ /\bCRAM-MD5\b/i) { 1931 | # printmsg("DEBUG => SMTP-AUTH: Using CRAM-MD5 authentication method", 1); 1932 | # if (SMTPchat('AUTH CRAM-MD5')) { quit($conf{'error'}, 1); } 1933 | # 1934 | # ## FIXME!! 1935 | # 1936 | # printmsg("DEBUG => User authentication was successful", 1); 1937 | # } 1938 | 1939 | ## SASL LOGIN authentication method 1940 | if ($auth_succeeded == 0 and $conf{'SMTPchat_response'} =~ /\bLOGIN\b/i) { 1941 | $mutual_method = 1; 1942 | printmsg("DEBUG => SMTP-AUTH: Using LOGIN authentication method", 1); 1943 | if (!SMTPchat('AUTH LOGIN')) { 1944 | if (!SMTPchat(base64_encode($opt{'username'}))) { 1945 | if (!SMTPchat(base64_encode($opt{'password'}))) { 1946 | $auth_succeeded = 1; 1947 | printmsg("DEBUG => User authentication was successful (Method: LOGIN)", 1); 1948 | } 1949 | } 1950 | } 1951 | if ($auth_succeeded == 0) { 1952 | printmsg("DEBUG => SMTP-AUTH: LOGIN authenticaion failed.", 1); 1953 | } 1954 | } 1955 | 1956 | ## SASL PLAIN authentication method 1957 | if ($auth_succeeded == 0 and $conf{'SMTPchat_response'} =~ /\bPLAIN\b/i) { 1958 | $mutual_method = 1; 1959 | printmsg("DEBUG => SMTP-AUTH: Using PLAIN authentication method", 1); 1960 | if (SMTPchat('AUTH PLAIN ' . base64_encode("$opt{'username'}\0$opt{'username'}\0$opt{'password'}"))) { 1961 | printmsg("DEBUG => SMTP-AUTH: PLAIN authenticaion failed.", 1); 1962 | } 1963 | else { 1964 | $auth_succeeded = 1; 1965 | printmsg("DEBUG => User authentication was successful (Method: PLAIN)", 1); 1966 | } 1967 | } 1968 | 1969 | ## If none of the authentication methods supported by sendEmail were supported by the server, let the user know 1970 | if ($mutual_method == 0) { 1971 | printmsg("WARNING => SMTP-AUTH: No mutually supported authentication methods available", 0); 1972 | } 1973 | 1974 | ## If we didn't get authenticated, log an error message and exit 1975 | if ($auth_succeeded == 0) { 1976 | quit("ERROR => ERROR => SMTP-AUTH: Authentication to $conf{'server'}:$conf{'port'} failed.", 1); 1977 | } 1978 | } 1979 | } 1980 | } 1981 | 1982 | ## MAIL FROM 1983 | if (SMTPchat('MAIL FROM:<' .(returnAddressParts($from))[1]. '>')) { quit($conf{'error'}, 1); } 1984 | 1985 | ## RCPT TO 1986 | my $oneRcptAccepted = 0; 1987 | foreach my $rcpt (@to, @cc, @bcc) { 1988 | my ($name, $address) = returnAddressParts($rcpt); 1989 | if (SMTPchat('RCPT TO:<' . $address . '>')) { 1990 | printmsg("WARNING => The recipient <$address> was rejected by the mail server, error follows:", 0); 1991 | $conf{'error'} =~ s/^ERROR/WARNING/o; 1992 | printmsg($conf{'error'}, 0); 1993 | } 1994 | elsif ($oneRcptAccepted == 0) { 1995 | $oneRcptAccepted = 1; 1996 | } 1997 | } 1998 | ## If no recipients were accepted we need to exit with an error. 1999 | if ($oneRcptAccepted == 0) { 2000 | quit("ERROR => Exiting. No recipients were accepted for delivery by the mail server.", 1); 2001 | } 2002 | 2003 | ## DATA 2004 | if (SMTPchat('DATA')) { quit($conf{'error'}, 1); } 2005 | 2006 | 2007 | ############################### 2008 | ## Build and send the body ## 2009 | ############################### 2010 | printmsg("INFO => Sending message body",1); 2011 | 2012 | ## If the message-format is raw just send the message as-is. 2013 | if ($opt{'message-format'} =~ /^raw$/i) { 2014 | print $SERVER $message; 2015 | } 2016 | 2017 | ## If the message-format isn't raw, then build and send the message, 2018 | else { 2019 | 2020 | ## Message-ID: 2021 | if ($opt{'message-header'} !~ /^Message-ID:/iom) { 2022 | $header .= 'Message-ID: <' . $conf{'Message-ID'} . '@' . $conf{'hostname'} . '>' . $CRLF; 2023 | } 2024 | 2025 | ## From: "Name" (the pointless test below is just to keep scoping correct) 2026 | if ($from and $opt{'message-header'} !~ /^From:/iom) { 2027 | my ($name, $address) = returnAddressParts($from); 2028 | $header .= 'From: "' . $name . '" <' . $address . '>' . $CRLF; 2029 | } 2030 | 2031 | ## Reply-To: 2032 | if ($opt{'reply-to'} and $opt{'message-header'} !~ /^Reply-To:/iom) { 2033 | my ($name, $address) = returnAddressParts($opt{'reply-to'}); 2034 | $header .= 'Reply-To: "' . $name . '" <' . $address . '>' . $CRLF; 2035 | } 2036 | 2037 | ## To: "Name" 2038 | if ($opt{'message-header'} =~ /^To:/iom) { 2039 | ## The user put the To: header in via -o message-header - dont do anything 2040 | } 2041 | elsif (scalar(@to) > 0) { 2042 | $header .= "To:"; 2043 | for (my $a = 0; $a < scalar(@to); $a++) { 2044 | my $msg = ""; 2045 | 2046 | my ($name, $address) = returnAddressParts($to[$a]); 2047 | $msg = " \"$name\" <$address>"; 2048 | 2049 | ## If we're not on the last address add a comma to the end of the line. 2050 | if (($a + 1) != scalar(@to)) { 2051 | $msg .= ","; 2052 | } 2053 | 2054 | $header .= $msg . $CRLF; 2055 | } 2056 | } 2057 | ## We always want a To: line so if the only recipients were bcc'd they don't see who it was sent to 2058 | else { 2059 | $header .= "To: \"Undisclosed Recipients\" <>$CRLF"; 2060 | } 2061 | 2062 | if (scalar(@cc) > 0 and $opt{'message-header'} !~ /^Cc:/iom) { 2063 | $header .= "Cc:"; 2064 | for (my $a = 0; $a < scalar(@cc); $a++) { 2065 | my $msg = ""; 2066 | 2067 | my ($name, $address) = returnAddressParts($cc[$a]); 2068 | $msg = " \"$name\" <$address>"; 2069 | 2070 | ## If we're not on the last address add a comma to the end of the line. 2071 | if (($a + 1) != scalar(@cc)) { 2072 | $msg .= ","; 2073 | } 2074 | 2075 | $header .= $msg . $CRLF; 2076 | } 2077 | } 2078 | 2079 | if ($opt{'message-header'} !~ /^Subject:/iom) { 2080 | $header .= 'Subject: ' . $subject . $CRLF; ## Subject 2081 | } 2082 | if ($opt{'message-header'} !~ /^Date:/iom) { 2083 | $header .= 'Date: ' . $date . $CRLF; ## Date 2084 | } 2085 | if ($opt{'message-header'} !~ /^X-Mailer:/iom) { 2086 | $header .= 'X-Mailer: sendEmail-'.$conf{'version'}.$CRLF; ## X-Mailer 2087 | } 2088 | ## I wonder if I should put this in by default? 2089 | # if ($opt{'message-header'} !~ /^X-Originating-IP:/iom) { 2090 | # $header .= 'X-Originating-IP: ['.$conf{'ip'}.']'.$CRLF; ## X-Originating-IP 2091 | # } 2092 | 2093 | ## Encode all messages with MIME. 2094 | if ($opt{'message-header'} !~ /^MIME-Version:/iom) { 2095 | $header .= "MIME-Version: 1.0$CRLF"; 2096 | } 2097 | if ($opt{'message-header'} !~ /^Content-Type:/iom) { 2098 | my $content_type = 'multipart/mixed'; 2099 | if (scalar(@attachments) == 0) { $content_type = 'multipart/related'; } 2100 | $header .= "Content-Type: $content_type; boundary=\"$conf{'delimiter'}\"$CRLF"; 2101 | } 2102 | 2103 | ## Send additional message header line(s) if specified 2104 | if ($opt{'message-header'}) { 2105 | $header .= $opt{'message-header'}; 2106 | } 2107 | 2108 | ## Send the message header to the server 2109 | print $SERVER $header . $CRLF; 2110 | 2111 | ## Start sending the message body to the server 2112 | print $SERVER "This is a multi-part message in MIME format. To properly display this message you need a MIME-Version 1.0 compliant Email program.$CRLF"; 2113 | print $SERVER "$CRLF"; 2114 | 2115 | 2116 | ## Send message body 2117 | print $SERVER "--$conf{'delimiter'}$CRLF"; 2118 | ## Send a message content-type header: 2119 | ## If the message contains HTML... 2120 | if ($opt{'message-content-type'} eq 'html' or ($opt{'message-content-type'} eq 'auto' and $message =~ /^\s*( 0) { 2142 | ## Disable the alarm so people on modems can send big attachments 2143 | alarm(0) if ($^O !~ /win/i); ## alarm() doesn't work in win32 2144 | 2145 | ## Send the attachments 2146 | foreach my $filename (@attachments) { 2147 | ## This is check 2, we already checked this above, but just in case... 2148 | if ( ! -f $filename ) { 2149 | printmsg("ERROR => The file [$filename] doesn't exist! Email will be sent, but without that attachment.", 0); 2150 | } 2151 | elsif ( ! -r $filename ) { 2152 | printmsg("ERROR => Couldn't open the file [$filename] for reading: $! Email will be sent, but without that attachment.", 0); 2153 | } 2154 | else { 2155 | printmsg("DEBUG => Sending the attachment [$filename]", 1); 2156 | send_attachment($filename); 2157 | } 2158 | } 2159 | } 2160 | 2161 | 2162 | ## End the mime encoded message 2163 | print $SERVER "$CRLF--$conf{'delimiter'}--$CRLF"; 2164 | } 2165 | 2166 | 2167 | ## Tell the server we are done sending the email 2168 | print $SERVER "$CRLF.$CRLF"; 2169 | if (SMTPchat()) { quit($conf{'error'}, 1); } 2170 | 2171 | 2172 | 2173 | #################### 2174 | # We are done!!! # 2175 | #################### 2176 | 2177 | ## Disconnect from the server (don't SMTPchat(), it breaks when using TLS) 2178 | print $SERVER "QUIT$CRLF"; 2179 | close $SERVER; 2180 | 2181 | 2182 | 2183 | 2184 | 2185 | 2186 | ####################################### 2187 | ## Generate exit message/log entry ## 2188 | ####################################### 2189 | 2190 | if ($conf{'debug'} or $conf{'logging'}) { 2191 | printmsg("Generating a detailed exit message", 3); 2192 | 2193 | ## Put the message together 2194 | my $output = "Email was sent successfully! From: <" . (returnAddressParts($from))[1] . "> "; 2195 | 2196 | if (scalar(@to) > 0) { 2197 | $output .= "To: "; 2198 | for ($a = 0; $a < scalar(@to); $a++) { 2199 | $output .= "<" . (returnAddressParts($to[$a]))[1] . "> "; 2200 | } 2201 | } 2202 | if (scalar(@cc) > 0) { 2203 | $output .= "Cc: "; 2204 | for ($a = 0; $a < scalar(@cc); $a++) { 2205 | $output .= "<" . (returnAddressParts($cc[$a]))[1] . "> "; 2206 | } 2207 | } 2208 | if (scalar(@bcc) > 0) { 2209 | $output .= "Bcc: "; 2210 | for ($a = 0; $a < scalar(@bcc); $a++) { 2211 | $output .= "<" . (returnAddressParts($bcc[$a]))[1] . "> "; 2212 | } 2213 | } 2214 | $output .= "Subject: [$subject] " if ($subject); 2215 | if (scalar(@attachments_names) > 0) { 2216 | $output .= "Attachment(s): "; 2217 | foreach(@attachments_names) { 2218 | $output .= "[$_] "; 2219 | } 2220 | } 2221 | $output .= "Server: [$conf{'server'}:$conf{'port'}]"; 2222 | 2223 | 2224 | ###################### 2225 | # Exit the program # 2226 | ###################### 2227 | 2228 | ## Print / Log the detailed message 2229 | quit($output, 0); 2230 | } 2231 | else { 2232 | ## Or the standard message 2233 | quit("Email was sent successfully!", 0); 2234 | } 2235 | 2236 | --------------------------------------------------------------------------------