├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── resolver.cpp ├── resolver.hpp ├── smtpping.1 ├── smtpping.cpp └── smtpping.dev /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeCache.txt 2 | CMakeFiles/ 3 | cmake_install.cmake 4 | *.o 5 | smtpping 6 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | CMAKE_MINIMUM_REQUIRED(VERSION 2.8) 2 | 3 | PROJECT(smtpping) 4 | SET(TARGET_NAME "smtpping") 5 | 6 | ADD_EXECUTABLE(${TARGET_NAME} 7 | smtpping.cpp 8 | resolver.cpp 9 | ) 10 | 11 | IF("${CMAKE_SYSTEM}" MATCHES "Darwin") 12 | ADD_DEFINITIONS(-DBIND_8_COMPAT) 13 | ENDIF() 14 | 15 | TARGET_LINK_LIBRARIES(${TARGET_NAME} 16 | pthread 17 | ) 18 | 19 | IF (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") 20 | TARGET_LINK_LIBRARIES(${TARGET_NAME} 21 | resolv 22 | ) 23 | ENDIF() 24 | 25 | IF (NOT DEFINED BIN_INSTALL_DIR) 26 | SET(BIN_INSTALL_DIR "bin") 27 | ENDIF(NOT DEFINED BIN_INSTALL_DIR) 28 | IF (NOT DEFINED MAN_INSTALL_DIR) 29 | SET(MAN_INSTALL_DIR "man") 30 | ENDIF(NOT DEFINED MAN_INSTALL_DIR) 31 | 32 | INSTALL(TARGETS ${TARGET_NAME} DESTINATION ${BIN_INSTALL_DIR}) 33 | INSTALL(FILES smtpping.1 DESTINATION ${MAN_INSTALL_DIR}/man1) 34 | -------------------------------------------------------------------------------- /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.md: -------------------------------------------------------------------------------- 1 | A simple, portable tool for measuring SMTP server delay, delay variation and throughput. Feel free to contact 2 | 3 | [![Coverity Scan Build](https://img.shields.io/coverity/scan/7287.svg)](https://scan.coverity.com/projects/halonsecurity-smtpping) 4 | 5 | Usage 6 | ----- 7 | The two first examples measures delay, and the last example measures 8 | throughput (`-r -w0`) using 50 threads (`-P50`). 9 | 10 | ``` 11 | $ smtpping test@halon.io 12 | $ smtpping test@halon.io @10.2.0.31 13 | $ smtpping -P50 -r -w0 test@halon.io @10.2.0.31 14 | ``` 15 | 16 | Building 17 | -------- 18 | Building on *NIX can be done manually using a C++ compiler such as GNU's 19 | `g++` or by using `cmake`. It could be easily ported to a Makefile. 20 | 21 | ``` 22 | $ cmake . 23 | $ make 24 | ``` 25 | 26 | Building on Windows 27 | ------------------- 28 | A project file for Dev-C++ is included, should be quite portable to eg. VS. 29 | -------------------------------------------------------------------------------- /resolver.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SMTP PING 3 | Copyright (C) 2011 Halon Security 4 | 5 | This program is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU General Public License 7 | as published by the Free Software Foundation; either version 2 8 | of the License, or (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program; if not, write to the Free Software 17 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | 19 | */ 20 | 21 | #include "resolver.hpp" 22 | 23 | #include 24 | #include 25 | 26 | #if defined(__WIN32__) 27 | #include 28 | #include 29 | #include 30 | #else 31 | #include 32 | #include 33 | #include 34 | #include 35 | #endif 36 | 37 | #include 38 | 39 | /* 40 | * initialize thread-safe m_res structure 41 | */ 42 | Resolver::Resolver() 43 | { 44 | #ifdef __WIN32__ 45 | m_hDnsInst = LoadLibrary("DNSAPI.DLL"); 46 | if (m_hDnsInst) 47 | { 48 | m_lpfnDnsRecordListFree = reinterpret_cast(GetProcAddress(m_hDnsInst, "DnsRecordListFree")); 49 | m_lpfnDnsQuery = reinterpret_cast(GetProcAddress(m_hDnsInst, "DnsQuery_A")); 50 | } 51 | #else 52 | memset((void*)&m_res, 0, sizeof m_res); 53 | res_ninit(&m_res); 54 | #endif 55 | } 56 | 57 | /* 58 | * close thread-safe m_res structure 59 | */ 60 | Resolver::~Resolver() 61 | { 62 | #ifdef __WIN32__ 63 | if (m_hDnsInst) 64 | { 65 | FreeLibrary(m_hDnsInst); 66 | m_hDnsInst = NULL; 67 | } 68 | #else 69 | res_nclose(&m_res); 70 | #endif 71 | } 72 | 73 | bool Resolver::Lookup(const std::string& domain, RecordType recordType, std::vector& result) 74 | { 75 | std::map > prioMap; 76 | 77 | #ifdef __WIN32__ 78 | if (!m_lpfnDnsRecordListFree || !m_lpfnDnsQuery) 79 | return false; 80 | 81 | int req_rec_type; 82 | #ifndef DNS_TYPE_AAAA 83 | # define DNS_TYPE_AAAA (28) 84 | #endif 85 | switch(recordType) 86 | { 87 | case RR_A: 88 | req_rec_type = DNS_TYPE_A; 89 | break; 90 | case RR_AAAA: 91 | req_rec_type = DNS_TYPE_AAAA; 92 | break; 93 | case RR_MX: 94 | req_rec_type = DNS_TYPE_MX; 95 | break; 96 | default: 97 | return false; 98 | } 99 | 100 | PDNS_RECORD pRec = NULL; 101 | if (m_lpfnDnsQuery(domain.c_str(), req_rec_type, DNS_QUERY_STANDARD, NULL, &pRec, NULL) != ERROR_SUCCESS) 102 | return false; 103 | 104 | PDNS_RECORD pRecFirst = pRec; 105 | while (pRec) 106 | { 107 | if (req_rec_type == pRec->wType && pRec->Flags.S.Section == DNSREC_ANSWER) 108 | { 109 | if (pRec->wType == DNS_TYPE_MX) 110 | { 111 | prioMap[(int)pRec->Data.MX.wPreference].push_back(pRec->Data.MX.pNameExchange); 112 | } 113 | if (pRec->wType == DNS_TYPE_AAAA) 114 | { 115 | SOCKADDR_IN6 addr; 116 | memset(&addr, 0, sizeof addr); 117 | addr.sin6_family = AF_INET6; 118 | addr.sin6_addr = *((in_addr6*)&(pRec->Data.AAAA.Ip6Address)); 119 | char buf[128]; 120 | DWORD bufsize = sizeof buf; 121 | if (WSAAddressToStringA((sockaddr*)&addr, sizeof addr, NULL, buf, &bufsize) == 0) 122 | { 123 | prioMap[0].push_back(buf); 124 | } 125 | } 126 | if (pRec->wType == DNS_TYPE_A) 127 | { 128 | SOCKADDR_IN addr; 129 | memset(&addr, 0, sizeof addr); 130 | addr.sin_family = AF_INET; 131 | addr.sin_addr = *((in_addr*)&(pRec->Data.A.IpAddress)); 132 | char buf[128]; 133 | DWORD bufsize = sizeof buf; 134 | if (WSAAddressToStringA((sockaddr*)&addr, sizeof addr, NULL, buf, &bufsize) == 0) 135 | { 136 | prioMap[0].push_back(buf); 137 | } 138 | } 139 | } 140 | pRec = pRec->pNext; 141 | } 142 | m_lpfnDnsRecordListFree(pRecFirst, DnsFreeRecordList); 143 | #else 144 | unsigned char response[64 * 1024]; 145 | memset(response, 0, sizeof response); 146 | 147 | unsigned char *resData, *resEnd; 148 | unsigned short rec_len, rec_pref; 149 | unsigned short rec_type; 150 | HEADER* header; 151 | 152 | int req_rec_type; 153 | switch(recordType) 154 | { 155 | case RR_A: 156 | req_rec_type = T_A; 157 | break; 158 | case RR_AAAA: 159 | req_rec_type = T_AAAA; 160 | break; 161 | case RR_MX: 162 | req_rec_type = T_MX; 163 | break; 164 | default: 165 | return false; 166 | } 167 | 168 | int len = res_nquery(&m_res, domain.c_str(), C_IN, req_rec_type, (unsigned char*)&response, sizeof response); 169 | if (len < 0) 170 | { 171 | if (m_res.res_h_errno == NO_DATA) 172 | return true; 173 | 174 | return false; 175 | } 176 | if (len > (int)sizeof response) { 177 | return false; 178 | } 179 | 180 | header = (HEADER*)&response; 181 | resData = (unsigned char*)&response + HFIXEDSZ; 182 | resEnd = (unsigned char*)&response + len; 183 | 184 | int answer_count = ntohs((unsigned short)header->ancount); 185 | int query_count = ntohs((unsigned short)header->qdcount); 186 | 187 | for (int i = 0; i < query_count; i++) { 188 | if ((len = dn_skipname(resData, resEnd)) < 0) 189 | return false; 190 | 191 | resData += len + QFIXEDSZ; 192 | } 193 | 194 | char buf[MAXDNAME + 1]; 195 | for (int i = 0; i < answer_count; i++) { 196 | len = dn_expand((unsigned char*)&response, resEnd, resData, (char*)&buf, sizeof buf - 1); 197 | if (len < 0) 198 | return false; 199 | 200 | resData += len; 201 | 202 | GETSHORT(rec_type, resData); 203 | resData += INT16SZ + INT32SZ; 204 | 205 | GETSHORT(rec_len, resData); 206 | 207 | switch(rec_type) 208 | { 209 | case T_MX: 210 | GETSHORT(rec_pref, resData); 211 | rec_len -= sizeof(short); 212 | break; 213 | default: 214 | rec_pref = 0; 215 | break; 216 | } 217 | 218 | if (rec_type == req_rec_type) { 219 | switch(rec_type) 220 | { 221 | case T_A: 222 | { 223 | char buf[INET_ADDRSTRLEN]; 224 | if (inet_ntop(AF_INET, 225 | (const struct sockaddr_in*)resData, buf, INET_ADDRSTRLEN)) { 226 | prioMap[rec_pref].push_back(buf); 227 | } 228 | } 229 | break; 230 | case T_AAAA: 231 | { 232 | char buf[INET6_ADDRSTRLEN]; 233 | if (inet_ntop(AF_INET6, 234 | (const struct sockaddr_in6*)resData, buf, INET6_ADDRSTRLEN)) { 235 | prioMap[rec_pref].push_back(buf); 236 | } 237 | } 238 | break; 239 | case T_MX: 240 | { 241 | char buf[MAXDNAME + 1]; 242 | len = dn_expand((unsigned char*)&response, resEnd, resData, (char*)&buf, sizeof buf - 1); 243 | if (len < 0) 244 | return false; 245 | 246 | prioMap[rec_pref].push_back(buf); 247 | } 248 | break; 249 | } 250 | } 251 | 252 | resData += rec_len; 253 | } 254 | #endif 255 | 256 | // merge map 257 | std::map >::iterator i; 258 | for(i = prioMap.begin(); i != prioMap.end(); ++i) 259 | { 260 | std::sort(i->second.begin(), i->second.end()); 261 | result.insert(result.end(), i->second.begin(), i->second.end()); 262 | } 263 | return true; 264 | } 265 | -------------------------------------------------------------------------------- /resolver.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | SMTP PING 3 | Copyright (C) 2011 Halon Security 4 | 5 | This program is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU General Public License 7 | as published by the Free Software Foundation; either version 2 8 | of the License, or (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program; if not, write to the Free Software 17 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | 19 | */ 20 | 21 | #ifndef _RESOLVER_HPP_ 22 | #define _RESOLVER_HPP_ 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #if defined(__APPLE__) or defined(__FreeBSD__) or defined(__linux) 29 | #include 30 | #include 31 | #endif 32 | #ifdef __WIN32__ 33 | #include 34 | #include 35 | 36 | typedef VOID (WINAPI DNSRECORDLISTFREE)(PDNS_RECORD, DNS_FREE_TYPE); 37 | typedef DNSRECORDLISTFREE* LPDNSRECORDLISTFREE; 38 | typedef DNS_STATUS (WINAPI DNSQUERY)(LPCTSTR, WORD, DWORD, PIP4_ARRAY, PDNS_RECORD*, PVOID*); 39 | typedef DNSQUERY* LPDNSQUERY; 40 | #else 41 | #include 42 | #endif 43 | 44 | #ifndef MSG_NOSIGNAL 45 | #define MSG_NOSIGNAL 0 46 | #endif 47 | 48 | class Resolver 49 | { 50 | public: 51 | typedef enum { 52 | RR_MX, 53 | RR_A, 54 | RR_AAAA, 55 | } RecordType; 56 | 57 | Resolver(); 58 | ~Resolver(); 59 | 60 | bool Lookup(const std::string& domain, RecordType recordType, std::vector& result); 61 | int GetLastError() const { 62 | #ifndef __WIN32__ 63 | return m_res.res_h_errno; 64 | #endif 65 | } 66 | private: 67 | #ifdef __WIN32__ 68 | HINSTANCE m_hDnsInst; 69 | LPDNSRECORDLISTFREE m_lpfnDnsRecordListFree; 70 | LPDNSQUERY m_lpfnDnsQuery; 71 | #else 72 | struct __res_state m_res; 73 | #endif 74 | }; 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /smtpping.1: -------------------------------------------------------------------------------- 1 | .\" Copyright (C) 2015 Halon Security 2 | .\" 3 | .\" This program is free software; you can redistribute it and/or 4 | .\" modify it under the terms of the GNU General Public License 5 | .\" as published by the Free Software Foundation; either version 2 6 | .\" of the License, or (at your option) any later version. 7 | .\" 8 | .\" This program is distributed in the hope that it will be useful, 9 | .\" but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | .\" GNU General Public License for more details. 12 | .\" 13 | .\" You should have received a copy of the GNU General Public License 14 | .\" along with this program; if not, write to the Free Software 15 | .\" Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16 | .\" 17 | .Dd $Mdocdate: December 3 2015 $ 18 | .Dt SMTPPING 1 19 | .Os 20 | .Sh NAME 21 | .Nm smtpping 22 | .Nd SMTP benchmarking and measurement tool 23 | .Sh SYNOPSIS 24 | .Nm 25 | .Op Fl dqrJ46C 26 | .Op Fl p Ar port 27 | .Op Fl w Ar wait 28 | .Op Fl c Ar count 29 | .Op Fl P Ar parallel 30 | .Op Fl s Ar size 31 | .Op Fl f Ar file 32 | .Op Fl H Ar hello 33 | .Op Fl S Ar sender 34 | .Ar recipient 35 | .Op Ar @server 36 | .Sh DESCRIPTION 37 | .Nm 38 | is a small tool that performs SMTP server delay, delay variation and 39 | throughput measurements. 40 | .Pp 41 | It must be invoked with the 42 | .Ar recipient 43 | email address. Normally, the 44 | .Ar server 45 | should also be specified (prefixed with @); otherwise 46 | .Nm 47 | will try to find the recipient domain's 48 | MX record, falling back on A/AAAA records. 49 | .Pp 50 | The following options are available: 51 | .Bl -tag -width Ds 52 | .It Fl 4 53 | Use IPv4. 54 | .It Fl 6 55 | Use IPv6. 56 | .It Fl p Ar port 57 | Specifies the TCP port to use (default: 25). 58 | .It Fl w Ar wait 59 | Time in milliseconds to wait between pings (default: 1000). 60 | .It Fl c Ar count 61 | Number of pings to send (default: unlimited). 62 | .It Fl P Ar processes 63 | Number of parallel worker processes (default: 1). To measure throughput, 64 | it's recommended to use 65 | .Fl r 66 | and 67 | .Fl w0 68 | with this option. 69 | .It Fl s Ar size 70 | Ping message size in kilobytes (default: 10). Cannot be used in 71 | conjunction with the 72 | .Fl f 73 | option. 74 | .It Fl f Ar file 75 | Send the specified email file (message/rfc822) instead of a generated 76 | message. Cannot be used in conjunction with the 77 | .Fl s 78 | option. 79 | .It Fl H Ar helo 80 | HELO name (default: localhost.localdomain). 81 | .It Fl S Ar sender 82 | Sender address (default: <>). 83 | .It Fl C 84 | Use CHUNKING (BDAT) 85 | .It Fl r 86 | Display rate instead of transaction delays. To measure throughput, 87 | it's recommended to use 88 | .Fl w0 89 | and possibly 90 | .Fl P 91 | with this option. 92 | .It Fl q 93 | Display less verbose output. 94 | .It Fl d 95 | Display more verbose output. 96 | .El 97 | .Sh AUTHORS 98 | .An -nosplit 99 | The 100 | .Nm 101 | program was written by 102 | .An Anders Berggren Aq Mt anders@desh.se 103 | and 104 | .An Erik Lax Aq Mt erik@datahack.se 105 | for Halon Security AB. 106 | -------------------------------------------------------------------------------- /smtpping.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SMTP PING 3 | Copyright (C) 2011 Halon Security 4 | 5 | This program is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU General Public License 7 | as published by the Free Software Foundation; either version 2 8 | of the License, or (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program; if not, write to the Free Software 17 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | 19 | */ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | using std::string; 34 | using std::vector; 35 | 36 | #ifdef __WIN32__ 37 | #include 38 | #include 39 | #include 40 | #else 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #if _POSIX_SEMAPHORES && MAP_ANONYMOUS 49 | #define SUPPORT_RATE 50 | #endif 51 | #endif 52 | 53 | /* DNS Resolver */ 54 | #include "resolver.hpp" 55 | 56 | /* 57 | * Global Variables 58 | */ 59 | bool debug = false; 60 | 61 | #define APP_VERSION "1.1.4" 62 | #define APP_NAME "smtpping" 63 | 64 | /* 65 | * Signal Handlers (abort ping and show statistics) 66 | */ 67 | bool abort_ping = false; 68 | void abort(int) 69 | { 70 | abort_ping = true; 71 | signal(SIGINT, SIG_DFL); 72 | } 73 | 74 | /* 75 | * SMTPReadLine: read a smtp line and return status code 76 | * return false on disconnect 77 | */ 78 | bool SMTPReadLine(int s, size_t& ret) 79 | { 80 | #ifdef __WIN32__ 81 | #define MSG_NOSIGNAL 0 82 | #endif 83 | char buf[1]; 84 | string cmd; 85 | int r; 86 | do { 87 | r = recv(s, buf, sizeof buf, MSG_NOSIGNAL); 88 | if (r > 0) cmd += buf[0]; 89 | if (buf[0] == '\n') 90 | { 91 | if (debug) 92 | fprintf(stderr, "response %s", cmd.c_str()); 93 | /* support multi-line responses */ 94 | if (cmd.size() > 4 && cmd[3] == ' ') 95 | { 96 | ret = strtoul(cmd.substr(0, 3).c_str(), NULL, 10); 97 | return true; 98 | } else 99 | cmd.clear(); 100 | } 101 | } while(r > 0); 102 | return false; 103 | } 104 | 105 | /* 106 | * high resolution timers (should return ms with 2 decimals) 107 | */ 108 | #ifdef WIN32 109 | #include 110 | 111 | double PCFreq = 0.0; 112 | __int64 CounterStart = 0; 113 | 114 | /* initialize counters */ 115 | void StartCounter() 116 | { 117 | LARGE_INTEGER li; 118 | if(!QueryPerformanceFrequency(&li)) 119 | return; 120 | 121 | PCFreq = double(li.QuadPart)/1000.0; 122 | 123 | QueryPerformanceCounter(&li); 124 | CounterStart = li.QuadPart; 125 | } 126 | 127 | /* GetHighResTime(): should be ported */ 128 | double GetHighResTime() 129 | { 130 | LARGE_INTEGER li; 131 | QueryPerformanceCounter(&li); 132 | return double(li.QuadPart-CounterStart)/PCFreq; 133 | } 134 | 135 | #else 136 | #include 137 | 138 | /* GetHighResTime(): should be ported */ 139 | double GetHighResTime() 140 | { 141 | struct timeval tv; 142 | if(gettimeofday(&tv, NULL) != 0) 143 | return 0; 144 | return (tv.tv_sec * 1000.0) + (tv.tv_usec / 1000.0); 145 | } 146 | #endif 147 | 148 | /* 149 | * usage information, displays all arugments and a short help 150 | */ 151 | void usage(const char* name, FILE* fp, int status) 152 | { 153 | fprintf(fp, 154 | "Usage: " APP_NAME " [ARGS] x@y.z [@server]\n" 155 | "Where: x@y.z is the address that will receive e-mail\n" 156 | " server is the address to connect to (optional)\n" 157 | " ARGS is one or many of: (optional)\n" 158 | " -h, --help\tShow this help message\n" 159 | " -v, --version\tShow version\n" 160 | " -d, --debug\tShow more debugging\n" 161 | " -4\t\tUse IPv4\n" 162 | " -6\t\tUse IPv6\n" 163 | " -b, --bind\tBind source address\n" 164 | " -p, --port\tWhich TCP port to use [default: 25]\n" 165 | " -w, --wait\tTime to wait between PINGs [default: 1000]" 166 | " (ms)\n" 167 | " -c, --count\tNumber of messages [default: unlimited]\n" 168 | " -P, --parallel\tNumber of parallel workers [default: 1]\n" 169 | " -s, --size\tMessage size in kilobytes [default: 10]" 170 | " (KiB)\n" 171 | " -f, --file\tSend message file (RFC 822)\n" 172 | " -H, --helo\tHELO domain [default: localhost.localdomain]\n" 173 | " -S, --sender\tSender address [default: empty]\n" 174 | " -C, --chunking\tUse CHUNKING (BDAT)\n" 175 | " -r, --rate\tShow message rate per second\n" 176 | " -q, --quiet\tShow less output\n" 177 | " -J\t\tRun in jailed mode (forbid --file)\n" 178 | "\n" 179 | " If no @server is specified, " APP_NAME " will try to find " 180 | "the recipient domain's\n MX record, falling back on A/AAAA " 181 | "records.\n" 182 | "\n" 183 | " " APP_NAME " " APP_VERSION " built on " __DATE__ 184 | " (c) Halon Security \n" 185 | ); 186 | exit(status); 187 | } 188 | 189 | int main(int argc, char* argv[]) 190 | { 191 | /* register signal handlers */ 192 | signal(SIGINT, abort); 193 | 194 | #ifdef __WIN32__ 195 | /* initialize winsock */ 196 | WSAData wData; 197 | WSAStartup(MAKEWORD(2,2), &wData); 198 | #endif 199 | 200 | /* default pareamters */ 201 | const char *smtp_bind = NULL; 202 | const char *smtp_helo = "localhost.localdomain"; 203 | const char *smtp_from = ""; 204 | const char *smtp_port = "25"; 205 | const char *smtp_rcpt = NULL; 206 | const char *smtp_file = NULL; 207 | unsigned int smtp_probes = 0; 208 | unsigned int smtp_probe_wait = 1000; 209 | unsigned int smtp_data_size = 10; 210 | unsigned int forks = 0; 211 | bool show_rate = false; 212 | bool quiet = false; 213 | bool safe_mode = false; 214 | unsigned int proto = 0; 215 | bool chunking = false; 216 | 217 | /* no arguments: show help */ 218 | if (argc < 2) 219 | usage(argv[0], stderr, 2); 220 | 221 | /* getopts/longopts */ 222 | static struct option longopts[] = { 223 | { "help", no_argument, NULL, 'h' }, 224 | { "version", no_argument, NULL, 'v' }, 225 | { "helo", required_argument, NULL, 'H' }, 226 | { "sender", required_argument, NULL, 'S' }, 227 | { "count", required_argument, NULL, 'c' }, 228 | { "wait", required_argument, NULL, 'w' }, 229 | { "parallel", required_argument, NULL, 'P' }, 230 | { "size", required_argument, NULL, 's' }, 231 | { "port", required_argument, NULL, 'p' }, 232 | { "file", required_argument, NULL, 'f' }, 233 | { "rate", no_argument, NULL, 'r' }, 234 | { "quiet", no_argument, NULL, 'q' }, 235 | { "bind", required_argument, NULL, 'b' }, 236 | { "chunking", no_argument, NULL, 'C' }, 237 | { NULL, 0, NULL, 0 } 238 | }; 239 | opterr = 0; 240 | optind = 0; 241 | int ch; 242 | while ((ch = getopt_long(argc, argv, "H:S:s:hw:c:P:p:df:rqJ46b:Cv", longopts, NULL)) != -1) 243 | { 244 | switch(ch) 245 | { 246 | case 'H': 247 | smtp_helo = optarg; 248 | break; 249 | case 'S': 250 | smtp_from = optarg; 251 | break; 252 | case 's': 253 | smtp_data_size = strtoul(optarg, NULL, 10); 254 | break; 255 | case 'h': 256 | usage(argv[0], stdout, 0); 257 | break; 258 | case 'w': 259 | smtp_probe_wait = strtoul(optarg, NULL, 10); 260 | break; 261 | case 'c': 262 | smtp_probes = strtoul(optarg, NULL, 10); 263 | break; 264 | case 'p': 265 | smtp_port = optarg; 266 | break; 267 | case 'P': 268 | forks = strtoul(optarg, NULL, 10); 269 | break; 270 | case 'd': 271 | debug = true; 272 | break; 273 | case 'r': 274 | show_rate = true; 275 | quiet = true; 276 | break; 277 | case 'q': 278 | quiet = true; 279 | break; 280 | case 'f': 281 | smtp_file = optarg; 282 | break; 283 | case 'J': 284 | safe_mode = true; 285 | break; 286 | case '4': 287 | proto = AF_INET; 288 | break; 289 | case '6': 290 | proto = AF_INET6; 291 | break; 292 | case 'b': 293 | smtp_bind = optarg; 294 | break; 295 | case 'C': 296 | chunking = true; 297 | break; 298 | case 'v': 299 | printf("%s\n", APP_VERSION); 300 | exit(0); 301 | break; 302 | default: 303 | usage(argv[0], stderr, 2); 304 | break; 305 | } 306 | } 307 | if (safe_mode && smtp_file) 308 | usage(argv[0], stderr, 2); 309 | 310 | argc -= optind; 311 | argv += optind; 312 | 313 | /* no e-mail or mx specified */ 314 | if (argc < 1) 315 | usage(argv[0], stderr, 2); 316 | 317 | /* mail address */ 318 | smtp_rcpt = argv[0]; 319 | 320 | string data; 321 | if (smtp_file) { 322 | /* read smtp_file */ 323 | std::ifstream ifs(smtp_file, std::ios::in | std::ios::binary); 324 | if (!ifs.good()) 325 | fprintf(stderr, "warning: file %s could not be opened\n" 326 | , smtp_file); 327 | else 328 | data.append(std::istreambuf_iterator(ifs.rdbuf()), 329 | std::istreambuf_iterator()); 330 | if (!chunking) data += ".\r\n"; 331 | } else { 332 | /* generate message with approximatly size */ 333 | data += "Subject: SMTP Ping\r\n"; 334 | data += "Content-Type: text/plain\r\n"; 335 | data += string("From: <") + smtp_from + ">\r\n"; 336 | data += string("To: <") + smtp_rcpt + ">\r\n"; 337 | data += "\r\n"; 338 | while (data.size() / 1024 < smtp_data_size) 339 | data += "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ" 340 | "00112233445566778899\r\n"; 341 | if (!chunking) data += "\r\n.\r\n"; 342 | } 343 | if (chunking) data = "BDAT " + std::to_string(data.size()) + " LAST\r\n" + data; 344 | 345 | Resolver resolv; 346 | vector address; 347 | 348 | /* user@example.com @mailserver */ 349 | if (argc > 1) 350 | { 351 | if (argv[1][0] != '@') 352 | usage(argv[0], stderr, 2); 353 | 354 | /* jmp past '@' */ 355 | const char* domain = argv[1] + 1; 356 | 357 | char buf[sizeof(struct in6_addr)]; 358 | if (inet_pton(AF_INET, domain, &buf) == 1 || inet_pton(AF_INET6, domain, &buf) == 1) 359 | address.push_back(domain); 360 | else 361 | { 362 | /* resolve as A/AAAA */ 363 | if (!resolv.Lookup(domain, Resolver::RR_A, address)) 364 | if (debug) fprintf(stderr, "warning: failed to resolve " 365 | "A for %s\n", domain); 366 | if (!resolv.Lookup(domain, Resolver::RR_AAAA, address)) 367 | if (debug) fprintf(stderr, "warning: failed to resolve " 368 | "AAAA for %s\n", domain); 369 | /* could not resolve, try to use address */ 370 | if (address.empty()) 371 | address.push_back(domain); 372 | } 373 | } else 374 | { 375 | /* use mailaddress as mx */ 376 | const char* domain = strrchr(smtp_rcpt, '@'); 377 | 378 | /* no domain, abort! */ 379 | if (!domain) 380 | usage(argv[0], stderr, 2); 381 | 382 | /* jmp past '@' */ 383 | domain += 1; 384 | 385 | /* resolve as MX, with A/AAAA fallback */ 386 | vector mx; 387 | if (!resolv.Lookup(domain, Resolver::RR_MX, mx)) 388 | { 389 | /* if dns failed, we should not try A/AAAA, 390 | only if no data is returned */ 391 | fprintf(stderr, "failed to resolve %s\n", domain); 392 | } else 393 | { 394 | /* no data, try A/AAAAA */ 395 | if (mx.empty()) 396 | { 397 | if (debug) fprintf(stderr, " no mx, failling " 398 | "back on A/AAAA record for %s\n", 399 | domain); 400 | 401 | if (!resolv.Lookup(domain, Resolver::RR_A, 402 | address)) 403 | if (debug) fprintf(stderr, "failed to " 404 | "resolve A for %s\n", domain); 405 | if (!resolv.Lookup(domain, Resolver::RR_AAAA, 406 | address)) 407 | if (debug) fprintf(stderr, "failed to " 408 | "resolve AAAA for %s\n", 409 | domain); 410 | } else 411 | { 412 | /* resolve all mx */ 413 | for(vector::const_iterator i = mx.begin(); 414 | i != mx.end(); ++i) 415 | { 416 | bool ok = false; 417 | if (!resolv.Lookup(*i, Resolver::RR_A, address)) 418 | { 419 | if (debug) fprintf(stderr, "warning: failed " 420 | "to resolve A for %s\n", i->c_str()); 421 | else 422 | ok = true; 423 | } 424 | if (!resolv.Lookup(*i, Resolver::RR_AAAA, address)) 425 | { 426 | if (debug) fprintf(stderr, "warning: failed to " 427 | "resolve AAAA for %s\n", i->c_str()); 428 | else 429 | ok = true; 430 | } 431 | /* could not reslove as either A or AAAA: 432 | maybe it's an IP */ 433 | if (!ok) 434 | address.push_back(*i); 435 | } 436 | } 437 | } 438 | } 439 | 440 | #ifdef SUPPORT_RATE 441 | sem_t* sem = (sem_t*)mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); 442 | if (sem_init(sem, 1, 1) != 0) 443 | fprintf(stderr, "sem_init: failed\n"); 444 | size_t* counter = (size_t*)mmap(NULL, sizeof(size_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); 445 | *counter = 0; 446 | #else 447 | if (show_rate) { 448 | fprintf(stderr, "-r is not supported on this platform\n"); 449 | return 1; 450 | } 451 | #endif 452 | 453 | unsigned int child = 1; 454 | if (forks > 0) { 455 | #ifdef __WIN32__ 456 | fprintf(stderr, "-P is not supported on this platform\n"); 457 | return 1; 458 | #else 459 | pid_t pid; 460 | for (; child <= forks; ++child) { 461 | pid = fork(); 462 | if (pid == 0) 463 | goto spawn; 464 | } 465 | #ifdef SUPPORT_RATE 466 | while (show_rate && !abort_ping) { 467 | sem_wait(sem); 468 | printf("%zu\n", *counter); 469 | *counter = 0; 470 | sem_post(sem); 471 | sleep(1); 472 | } 473 | #endif 474 | while ((pid = waitpid(-1, NULL, 0))) { 475 | if (errno == ECHILD) { 476 | break; 477 | } 478 | } 479 | return 0; 480 | #endif 481 | } else if (show_rate) { 482 | fprintf(stderr, "-r only works with -P1 or greater\n"); 483 | return 1; 484 | } 485 | spawn: 486 | 487 | /* register statistics */ 488 | #define STATS_GLOB(name) \ 489 | double smtp_##name##_min = -1, smtp_##name##_max = -1,\ 490 | smtp_##name##_sum = 0 , smtp_##name##_num = 0; 491 | 492 | STATS_GLOB(connect); 493 | STATS_GLOB(banner); 494 | STATS_GLOB(helo); 495 | STATS_GLOB(mailfrom); 496 | STATS_GLOB(rcptto); 497 | STATS_GLOB(data); 498 | STATS_GLOB(datasent); 499 | STATS_GLOB(quit); 500 | 501 | #define STATS(name, min) \ 502 | double smtp_##name = GetHighResTime();\ 503 | if (smtp_##name - smtp_##min < smtp_##name##_min ||\ 504 | smtp_##name##_min == -1)\ 505 | smtp_##name##_min = smtp_##name - smtp_##min;\ 506 | if (smtp_##name - smtp_##min > smtp_##name##_max ||\ 507 | smtp_##name##_max == -1)\ 508 | smtp_##name##_max = smtp_##name - smtp_##min;\ 509 | smtp_##name##_sum += smtp_##name - smtp_##min; smtp_##name##_num++; 510 | 511 | #define STATS_TIME(name) \ 512 | (smtp_##name - smtp_init) 513 | #define STATS_SESSION_TIME(name) \ 514 | (smtp_##name - smtp_connect) 515 | 516 | struct addrinfo *bindIP = NULL, bindIPTmp; 517 | if (smtp_bind) 518 | { 519 | memset(&bindIPTmp, 0, sizeof bindIPTmp); 520 | bindIPTmp.ai_family = AF_UNSPEC; 521 | bindIPTmp.ai_socktype = SOCK_STREAM; 522 | int r = getaddrinfo(smtp_bind, 0, &bindIPTmp, &bindIP); 523 | if (r != 0) 524 | { 525 | fprintf(stderr, "getaddrinfo() failed %s: %s\n", 526 | smtp_bind, gai_strerror(r)); 527 | return 1; 528 | } 529 | } 530 | 531 | /* connect to the first working address */ 532 | unsigned int smtp_seq = 0; 533 | vector::const_iterator i; 534 | for(i = address.begin(); i != address.end(); ++i) 535 | { 536 | struct addrinfo *res = NULL, resTmp; 537 | 538 | memset(&resTmp, 0, sizeof resTmp); 539 | resTmp.ai_family = AF_UNSPEC; 540 | resTmp.ai_socktype = SOCK_STREAM; 541 | int r = getaddrinfo(i->c_str(), smtp_port, &resTmp, &res); 542 | if (r != 0) 543 | { 544 | fprintf(stderr, "getaddrinfo() failed %s: %s\n", 545 | i->c_str(), gai_strerror(r)); 546 | continue; 547 | } 548 | 549 | if (proto && res->ai_family != proto) 550 | continue; 551 | 552 | if (bindIP && bindIP->ai_family != res->ai_family) 553 | continue; 554 | 555 | /* print header */ 556 | if (!quiet) 557 | printf("PING %s ([%s]:%s): %d bytes (SMTP DATA)\n", 558 | smtp_rcpt, i->c_str(), smtp_port, 559 | (unsigned int)data.size()); 560 | reconnect: 561 | 562 | /* abort by ctrl+c or if smtp_seq is done */ 563 | if (abort_ping || (smtp_probes && smtp_seq >= smtp_probes)) { 564 | freeaddrinfo(res); 565 | break; 566 | } 567 | 568 | /* sleep between smtp_req */ 569 | if (smtp_seq > 0) 570 | { 571 | #ifdef __WIN32__ 572 | Sleep(smtp_probe_wait); 573 | #else 574 | usleep(smtp_probe_wait * 1000); 575 | #endif 576 | } 577 | 578 | /* only increase if smtp_req > 0 */ 579 | if (smtp_seq > 0) 580 | smtp_seq++; 581 | 582 | int s = socket(res->ai_family, res->ai_socktype, 583 | res->ai_protocol); 584 | if (s == -1) 585 | { 586 | fprintf(stderr, "seq=%u: socket() failed\n", 587 | smtp_seq); 588 | if (smtp_seq == 0) { 589 | freeaddrinfo(res); 590 | continue; 591 | } else { 592 | goto reconnect; 593 | } 594 | } 595 | 596 | if (bindIP && bind(s, bindIP->ai_addr, bindIP->ai_addrlen) != 0) 597 | { 598 | fprintf(stderr, "seq=%u: bind() failed\n", 599 | smtp_seq); 600 | if (smtp_seq == 0) { 601 | freeaddrinfo(res); 602 | continue; 603 | } else { 604 | goto reconnect; 605 | } 606 | } 607 | 608 | /* initiate counters on windows */ 609 | #ifdef __WIN32__ 610 | StartCounter(); 611 | #endif 612 | 613 | /* start up time */ 614 | double smtp_init = GetHighResTime(); 615 | 616 | /* connect */ 617 | if (connect(s, res->ai_addr, res->ai_addrlen) != 0) { 618 | fprintf(stderr, "seq=%u: connect() failed " 619 | "%s\n", smtp_seq, i->c_str()); 620 | if (smtp_seq == 0) { 621 | freeaddrinfo(res); 622 | continue; 623 | } else { 624 | close(s); 625 | goto reconnect; 626 | } 627 | } 628 | STATS(connect, init); 629 | 630 | /* if it's working, start smtp_req */ 631 | if (smtp_seq == 0) 632 | smtp_seq = 1; 633 | 634 | /* 635 | * < SMTP Banner 636 | */ 637 | string cmd; 638 | size_t ret; 639 | if (!SMTPReadLine(s, ret) || ret / 100 != 2) 640 | { 641 | fprintf(stderr, "seq=%u: recv: BANNER failed (%zu)\n", 642 | smtp_seq, ret); 643 | close(s); 644 | goto reconnect; 645 | } 646 | STATS(banner, connect); 647 | 648 | /* 649 | * > HELO helo 650 | * < 250 OK 651 | */ 652 | cmd = string("HELO ") + smtp_helo + "\r\n"; 653 | if (send(s, cmd.c_str(), cmd.size(), 0) != (int)cmd.size()) 654 | { 655 | fprintf(stderr, "seq=%u: send: failed\n", smtp_seq); 656 | close(s); 657 | goto reconnect; 658 | } 659 | if (!SMTPReadLine(s, ret) || ret / 100 != 2) 660 | { 661 | fprintf(stderr, "seq=%u: recv: HELO failed (%zu)\n", 662 | smtp_seq, ret); 663 | close(s); 664 | goto reconnect; 665 | } 666 | STATS(helo, connect); 667 | 668 | /* 669 | * > MAIL FROM:
670 | * < 250 OK 671 | */ 672 | cmd = string("MAIL FROM: <") + smtp_from + ">\r\n"; 673 | if (send(s, cmd.c_str(), cmd.size(), 0) != (int)cmd.size()) 674 | { 675 | fprintf(stderr, "seq=%u: send: failed\n", smtp_seq); 676 | close(s); 677 | goto reconnect; 678 | } 679 | if (!SMTPReadLine(s, ret) || ret / 100 != 2) 680 | { 681 | fprintf(stderr, "seq=%u: recv: MAIL FROM failed (%zu)\n", 682 | smtp_seq, ret); 683 | close(s); 684 | goto reconnect; 685 | } 686 | STATS(mailfrom, connect); 687 | 688 | /* 689 | * > RCPT TO:
690 | * < 250 OK 691 | */ 692 | cmd = string("RCPT TO: <") + smtp_rcpt + ">\r\n"; 693 | if (send(s, cmd.c_str(), cmd.size(), 0) != (int)cmd.size()) 694 | { 695 | fprintf(stderr, "seq=%u: send: failed\n", smtp_seq); 696 | close(s); 697 | goto reconnect; 698 | } 699 | if (!SMTPReadLine(s, ret) || ret / 100 != 2) 700 | { 701 | fprintf(stderr, "seq=%u: recv: RCPT TO failed (%zu)\n", 702 | smtp_seq, ret); 703 | close(s); 704 | goto reconnect; 705 | } 706 | STATS(rcptto, connect); 707 | 708 | if (!chunking) 709 | { 710 | /* 711 | * > DATA 712 | * < 354 Feed me 713 | */ 714 | cmd = string("DATA\r\n"); 715 | if (send(s, cmd.c_str(), cmd.size(), 0) != (int)cmd.size()) 716 | { 717 | fprintf(stderr, "seq=%u: send: failed\n", smtp_seq); 718 | close(s); 719 | goto reconnect; 720 | } 721 | if (!SMTPReadLine(s, ret) || ret / 100 != 3) 722 | { 723 | fprintf(stderr, "seq=%u: recv: DATA failed (%zu)\n", 724 | smtp_seq, ret); 725 | close(s); 726 | goto reconnect; 727 | } 728 | } 729 | STATS(data, connect); 730 | 731 | /* 732 | * > data... 733 | * < ??? Mkay 734 | */ 735 | if (send(s, data.c_str(), data.size(), 0) != (int)data.size()) 736 | { 737 | fprintf(stderr, "seq=%u: send: failed\n", smtp_seq); 738 | close(s); 739 | goto reconnect; 740 | } 741 | if (!SMTPReadLine(s, ret)) 742 | { 743 | fprintf(stderr, "seq=%u: recv: EOM failed (%zu)\n", 744 | smtp_seq, ret); 745 | close(s); 746 | goto reconnect; 747 | } 748 | STATS(datasent, connect); 749 | 750 | /* 751 | * > QUIT 752 | * < ??? Mkay 753 | */ 754 | cmd = string("QUIT\r\n"); 755 | if (send(s, cmd.c_str(), cmd.size(), 0) != (int)cmd.size()) 756 | { 757 | fprintf(stderr, "seq=%u: send: QUIT failed\n", 758 | smtp_seq); 759 | close(s); 760 | goto reconnect; 761 | } 762 | if (!SMTPReadLine(s, ret)) 763 | { 764 | fprintf(stderr, "seq=%u: recv: QUIT failed (%zu)\n", 765 | smtp_seq, ret); 766 | close(s); 767 | goto reconnect; 768 | } 769 | STATS(quit, connect); 770 | 771 | shutdown(s, 2); 772 | close(s); 773 | 774 | #ifdef SUPPORT_RATE 775 | if (show_rate) { 776 | sem_wait(sem); 777 | (*counter)++; 778 | sem_post(sem); 779 | } 780 | #endif 781 | 782 | /* print statistics */ 783 | if (!quiet) 784 | printf("seq=%u, connect=%.2lf ms, helo=%.2lf ms, " 785 | "mailfrom=%.2lf ms, rcptto=%.2lf ms, datasent=%.2lf ms, " 786 | "quit=%.2lf ms\n", 787 | smtp_seq, 788 | STATS_TIME(connect), 789 | STATS_SESSION_TIME(helo), 790 | STATS_SESSION_TIME(mailfrom), 791 | STATS_SESSION_TIME(rcptto), 792 | STATS_SESSION_TIME(datasent), 793 | STATS_SESSION_TIME(quit) 794 | ); 795 | 796 | /* next loop */ 797 | goto reconnect; 798 | } 799 | 800 | /* if we successfully connected somewhere */ 801 | if (forks > 1) 802 | ; 803 | else if (i != address.end() && smtp_seq > 0) 804 | { 805 | printf("\n--- %s SMTP ping statistics ---\n", i->c_str()); 806 | printf("%u e-mail messages transmitted\n", smtp_seq); 807 | 808 | #define SHOWSTAT(x) \ 809 | printf(#x " min/avg/max = %.2lf/%.2lf/%.2lf ms\n", \ 810 | smtp_##x##_min, smtp_##x##_num>0?smtp_##x##_sum / smtp_##x##_num:0, \ 811 | smtp_##x##_max); 812 | 813 | SHOWSTAT(connect); 814 | SHOWSTAT(banner); 815 | SHOWSTAT(helo); 816 | SHOWSTAT(mailfrom); 817 | SHOWSTAT(rcptto); 818 | SHOWSTAT(data); 819 | SHOWSTAT(datasent); 820 | SHOWSTAT(quit); 821 | } else 822 | { 823 | printf("\n--- no pings were sent ---\n"); 824 | } 825 | 826 | #ifdef __WIN32__ 827 | if (abort_ping) 828 | { 829 | printf("Aborted by Control-C\n"); 830 | } 831 | WSACleanup(); 832 | #endif 833 | return 0; 834 | } 835 | -------------------------------------------------------------------------------- /smtpping.dev: -------------------------------------------------------------------------------- 1 | [Project] 2 | FileName=smtpping.dev 3 | Name=smtpping 4 | UnitCount=3 5 | Type=1 6 | Ver=1 7 | ObjFiles= 8 | Includes= 9 | Libs= 10 | PrivateResource= 11 | ResourceIncludes= 12 | MakeIncludes= 13 | Compiler= 14 | CppCompiler= 15 | Linker=-lws2_32_@@_ 16 | IsCpp=1 17 | Icon= 18 | ExeOutput= 19 | ObjectOutput= 20 | OverrideOutput=0 21 | OverrideOutputName=smtpping.exe 22 | HostApplication= 23 | Folders= 24 | CommandLine= 25 | UseCustomMakefile=0 26 | CustomMakefile= 27 | IncludeVersionInfo=0 28 | SupportXPThemes=0 29 | CompilerSet=0 30 | CompilerSettings=0000000000000000000000 31 | 32 | [Unit1] 33 | FileName=resolver.cpp 34 | CompileCpp=1 35 | Folder=smtpping 36 | Compile=1 37 | Link=1 38 | Priority=1000 39 | OverrideBuildCmd=0 40 | BuildCmd= 41 | 42 | [Unit2] 43 | FileName=resolver.hpp 44 | CompileCpp=1 45 | Folder=smtpping 46 | Compile=1 47 | Link=1 48 | Priority=1000 49 | OverrideBuildCmd=0 50 | BuildCmd= 51 | 52 | [Unit3] 53 | FileName=smtpping.cpp 54 | CompileCpp=1 55 | Folder=smtpping 56 | Compile=1 57 | Link=1 58 | Priority=1000 59 | OverrideBuildCmd=0 60 | BuildCmd= 61 | 62 | [VersionInfo] 63 | Major=0 64 | Minor=1 65 | Release=1 66 | Build=1 67 | LanguageID=1033 68 | CharsetID=1252 69 | CompanyName= 70 | FileVersion= 71 | FileDescription=Developed using the Dev-C++ IDE 72 | InternalName= 73 | LegalCopyright= 74 | LegalTrademarks= 75 | OriginalFilename= 76 | ProductName= 77 | ProductVersion= 78 | AutoIncBuildNr=0 79 | 80 | --------------------------------------------------------------------------------