/* (C)opright 2001 by PlasmaHH */ /** * $Id: smtpmap.cpp,v 1.33 2002/10/21 08:38:29 plasmahh Exp $ * $Revision: 1.33 $ * $Author: plasmahh $ * $Date: 2002/10/21 08:38:29 $ */ #include #include #include #include #include #include #include // for some defaults #include "smtpmap.h" // new c++ includes/*{{{*/ #ifndef __FP_CONF_H #include "fp_conf.h" #endif #ifndef __FP_RFC_H #include "fp_rfc.h" #endif #ifndef __FP_RETURN_H #include "fp_return.h" #endif #ifndef __MSTRING_H #include "mstring.h" #endif #ifndef __DLLIST_H #include "dllist.h" #endif #ifndef __SMALL_SOCKET_H #include "small_socket.h" #endif #ifndef __SCANNER_SMTP_H #include "scanner_smtp.h" #endif #ifndef __PARSING_H #include "parsing.h" #endif/*}}}*/ // some data structures that will be needed.../*{{{*/ /** * double linked list, that will hold all the targets * we have to scan (IP addresses & hostnames, will be * converted during run to ip adresses only) */ dllist< mstring > usertargets; /** * double linked list that will hold the real targets * to be scanned */ dllist< mstring > scantargets; /** * this is our connection to the outer world, we reuse * it for every scanned host. Perhaps we will do something like * multithreading/multiconnection in a far future version... perhaps.... */ small_socket connection; /** * */ dllist < fp_return > allreturns; dllist < fp_conf > allconfs; dllist < fp_rfc > allrfcs; // define global and default options bool stripadvanced=false; bool nodns=false; bool printbanner = true; bool polite=false; bool dofast=false; const char *server; long conn_timeout = DEFAULT_C_TIMEOUT; long recv_timeout = DEFAULT_A_TIMEOUT; fd_set fdvar; double trg = DEFAULT_TRG; int debuglevel=0; int smtp_port = DEFAULT_PORT; long testnb =0; /*}}}*/ bool expandhostlist ( void )/*{{{*/ { // This will expand all the host into a list of IPs, and save it. // With big lists, this will consume lots of memory, *.*.*.* at least will crash the // program with an out of mem error. // We don't expand during the run, since even 10000 hosts will not consume so much memory, // but scanning needs so much time, that higher amounts make no sence // cout << "expanding hostlist to scannable IP list... taking non-expandable IP ranges as hostnames.... " << endl; dlliterator uit(usertargets); for ( uit.first(); uit.valid(); uit.next() ) { try { // try to parse the IP-range.... scantargets += parsing::expand_iplist( uit.item() ); } catch (Exception* e) { e->handled(); delete e; // it was not possible to parse it, so take it just as a hostname... scantargets.add( uit.item() ); } } if ( debuglevel > 2 ) { cout << "The following is a list of hosts to be scanned : " << endl; dlliterator< mstring > scanit(scantargets); for ( scanit.first() ; scanit.valid(); scanit.next() ) { cout << scanit.item() << endl; } } return true; }/*}}}*/ int scan( ) // manage the actual scan/*{{{*/ { /* * Ok, we iterate through all our hosts, dns them or so, and initiate a * scan for all of them... Until here, all the option stuff will be done I * guess, so here we initialize our first scanning module If this failes , * we return ... */ small_socket mysock; /* * I guess the scanner can remain in this function as a local object... it * will feed with the socket, and The targets will be set in a loop... we * should give the outputting & sorting for the single hosts to another * function */ scanner_smtp scn(mysock,debuglevel,polite, conn_timeout, recv_timeout); // if init fails, return. if ( ! scn.initialize() ) { return -1; } expandhostlist(); dllist < mstring > descs; dllist < dllist < fp > > results; dllist < fp > actual; dlliterator sit ( scantargets ); mstring rhost, rip; for (sit.first();sit.valid();sit.next()) { cout << "\nScanning " << sit.item() << " ( "; if ( ! nodns ) { rip = small_socket::get_resolved_ip (sit.item()); rhost = small_socket::get_resolved_host(sit.item()); } if ( sit.item() != rip && rip.length() != 0) { cout << "[ " << rip << " ] "; } if ( sit.item() != rhost && rhost.length() != 0 ) { cout << rhost; } cout << " )" << endl; scn.set_target ( sit.item(), smtp_port ); if ( dofast ) { cout << "Doing a fast scan..." << endl; cout << "Server claims to be : " << scn.is_server() << endl; } else if ( ! scn.do_scan() ) { cout << "Error scanning host " << sit.item() << endl; continue; } scn.finalize(); descs = scn.get_descs(); results = scn.get_fps(); dlliterator dit(descs); dlliterator< dllist > fit(results); // As a definition result of the scanner, tnumber of description should // match the number of fp-lists fit.first(); actual.set_sort_mode(up); /* setting the sort mode to down will cause the fps with the highest values to be at the end, so the best matching are at the beginning */ cout << endl << endl; int maxerr,err; double errval; int outnmb = 0; mstring ver; for ( dit.first() ; dit.valid(); dit.next() ) { cout << "According to " << dit.item() << " the server matches the following : " << endl; cout << " Version \t\t\t\t\tProbability" << endl; actual.set_sort_mode(up); actual = fit.item(); dlliterator< fp > ffi(actual); ffi.last(); maxerr = ffi.item().Error(); outnmb = 0; for ( ffi.first() ; ffi.valid(); ffi.next() ) { outnmb++; err = ffi.item().Error(); errval = 100.0 - ((double)err / (double)maxerr)*100.0; if ( outnmb > 3 && errval < trg ) { break; } ver = ffi.item().Version() + " "; ver = ver.substring(1,47); cout << ver << " " << errval << " % "<< endl; if ( debuglevel > 2 ) { cout << maxerr << " -> " << err << endl; } } cout << endl; fit.next(); } } int rtn = 0; return rtn; };/*}}}*/ void help(void) // print out usage information/*{{{*/ { cout << "Usage : smtpmap [-h] [-a] [-P ] [-n ] " << endl << endl; cout << " : connect to SMTP server running on (IP or name)" << endl; cout << "-v,--verbose : be verbose. Up to 3 times for maximum verbosity " << endl; cout << "-h,--help : print help ( this screen )" << endl; cout << "-d,--nodns : Don't do additional DNS lookups, except for connect." << endl; cout << "-f,--fast : Do a fast identification & scan probe of the server" << endl; cout << "-n,--trigger : show all hits above Percent" << endl; cout << "-p,--port : connect to port (default=" << DEFAULT_PORT << ")" << endl; cout << "-l,--polite : be polite when reading input" << endl; cout << "-t,--timeout : timeout in secs when waiting for an answer (default=" << DEFAULT_A_TIMEOUT/1000 << ")" << endl; cout << "-c,--conn_timeout : timeout in secs when connecting to a host (default=" << DEFAULT_C_TIMEOUT/1000 << ")" << endl; // cout << "\nNote : The AUTH feature as defined in rfc2554 is not yet available" << endl; }/*}}}*/ void parseoptions(int argc, char *argv[]) // parse command line options/*{{{*/ { int fodder; debuglevel = 0; // for 1. field : no_argument=0, required_argument=1 and // optional_argument=2 last is a flag, dont know which, but NULL works. // last is the option given back, so set to the same as short options... option myopts[] = { {"verbose", 0, NULL, 'v'}, {"help", 0, NULL, 'h'}, {"port", 1, NULL, 'p'}, {"nodns", 0, NULL, 'd'}, {"trigger", 1, NULL, 'n'}, {"timeout", 1, NULL, 't'}, {"conn_timeout",1, NULL, 'c'}, {"polite", 0, NULL, 'l'}, {"fast", 0, NULL, 'f'}, {"version", 0, NULL, 'V'} }; // perhaps add something like --polite=true/yes or so to a future version ;) while ( (fodder = getopt_long(argc, argv, "davflht:p:n:c:",myopts,NULL)) != EOF) { switch(fodder) { case 'v': if (debuglevel == 3) { cout << "Note: More than -v -v -v will not produce more verbositiy" << endl; } debuglevel++; break; case 'f': dofast = true; break; case 'l': polite = true; break; case 'h': help(); exit(EXIT_SUCCESS); case 'a': stripadvanced = true; break; case 't': recv_timeout = strtol ( optarg, NULL, 10) * 1000; break; case 'c': conn_timeout = strtol ( optarg, NULL, 10) * 1000; if ( conn_timeout < 1 ) { cout << "Invalid argument to option conn_timeout" << endl; exit(EXIT_FAILURE); } break; case 'n': trg = strtod(optarg, NULL); break; case 'd': nodns = true; break; case 'V': cout << "smtp-map " << VERSION << endl; exit(EXIT_SUCCESS); break; case 's': usertargets.add(optarg); break; case 'p': int prt; prt = atoi(optarg); if ( prt <= 0 || prt > 65535 ) { cerr << "Invalid option for --port specified" << endl; exit ( EXIT_FAILURE ); } smtp_port = prt; break; default: if ( optarg != NULL ) { cerr << "Unknown option : " << optarg << endl; } else { break; } help(); exit(EXIT_FAILURE); }// of switch }// of while while ( optind < argc ) { usertargets.add( argv[optind] ); optind++; }; // print debug output TODO write all the other options too ;) if (debuglevel > 2) { cout << " Server(s) to be scanned : " << endl; dlliterator< mstring > uit(usertargets); for ( uit.first(); uit.valid(); uit.next() ) { cout << uit.item() << endl; } cout << " Match trigger value : " << trg << endl; cout << " Server port : " << smtp_port << endl; cout << " Scan timeout : " << conn_timeout << endl; cout << " Advanced response stripping : " << stripadvanced << endl; }; }/*}}}*/ // ******************************************************************************************** // **************************************** MAIN ********************************************** // ******************************************************************************************** int main(int argc, char *argv[])/*{{{*/ { int scanresult; // saves return value of the server scan parseoptions(argc, argv); cout << "smtp-map " << VERSION << endl; // If no server was given we can't do anything so quit if ( usertargets.size() <= 0 ) { cerr << "Can't locate a target server in your options" << endl; cerr << "Valid options are : " << endl; help(); return EXIT_FAILURE; } // Do the actual scan. scanresult = scan ( ); if ( scanresult != 0) { cerr << "Error during the scan !!" << endl; if (debuglevel > 2) { cout << "scan() return value : " << scanresult << endl; }; return EXIT_FAILURE; } cout << "\nNote: If the banner says something slightly different than smtpmap," << endl; cout << "but the main release number, then the version in the banner is usually correct." << endl; cout << "\nIf you know the name of the SMTP server you just scanned, please" << endl; cout << "contribute to this program by sending the whole output with -v " << endl; cout << "and name of the server software to : plasmahh@gmx.net" << endl; cout << "Best would be some additional information about the server configuration and" << endl; cout << "perhaps even what server it was, if its accesible from the internet" << endl; return 0; }/*}}}*/ /** *$Log: smtpmap.cpp,v $ *Revision 1.33 2002/10/21 08:38:29 plasmahh *begin cleanup and structure changes for smtpmap 0.8-stable * *Revision 1.32 2002/10/16 23:06:17 plasmahh *new function for reading fingerprints from file *fixes some stuff * *Revision 1.31 2002/08/14 20:01:19 plasmahh *intermediate release * *Revision 1.30 2002/08/12 13:13:05 plasmahh *removed some memory leaks * *Revision 1.29 2002/07/24 18:06:47 plasmahh *some slight fixes *fingerprint changes and cleanups *default value changes * *Revision 1.28 2002/06/20 21:17:21 iceblox *Checked the language and prolly broke some functions ;) * *Revision 1.27 2002/06/20 20:37:01 plasmahh *new fingerprints *fixed correct fingerprinting on multiple hosts * *Revision 1.26 2002/06/17 21:50:05 plasmahh *removed warning while compiloing * *Revision 1.25 2002/06/17 16:44:22 plasmahh *fixed parsing of timeout options * *Revision 1.24 2002/06/17 14:21:35 plasmahh *Cooler display ;) * *Revision 1.23 2002/06/17 14:04:13 plasmahh *Added cooler output for hostnames * *Revision 1.22 2002/06/17 06:24:56 plasmahh *typo(s) *begin implementing some advanced features, use tag before this for releases... * *Revision 1.21 2002/06/16 21:30:04 plasmahh *slight fixes * *Revision 1.20 2002/06/12 20:21:27 plasmahh *fixed segfault for timeout value * *Revision 1.19 2002/06/12 14:39:34 plasmahh *new fingerprints *beautified output * *Revision 1.18 2002/06/12 14:12:08 plasmahh *fingerprint cleanup * *Revision 1.17 2002/06/11 20:04:15 plasmahh *fixed bad connection behaviour bug * *Revision 1.16 2002/06/11 18:54:35 plasmahh *fixed timeout behaviour, need only to fix reconnects... *new fingerprint * *Revision 1.15 2002/06/11 14:27:57 plasmahh *Removed some variables to get rid of noisy compiler warnings... but still the one with testcmds remains... why that ? * *Revision 1.14 2002/06/11 06:17:31 plasmahh *compiles now even on cygwin ! * *Revision 1.13 2002/06/10 22:54:45 plasmahh *fixed wrong calculation of results * *Revision 1.12 2002/06/10 22:10:35 plasmahh *first partly working fingerprinting, moving to BETA state * *Revision 1.11 2002/06/10 19:43:27 plasmahh *Added sample fingerprints for testing * *Revision 1.10 2002/06/10 11:56:17 plasmahh *scanning of the hosts is done, implementation of comparing & ranking is still missing * *Revision 1.9 2002/06/10 10:30:01 plasmahh *Added capabilities for gnu longopts *begin migrating to new fingerprint structure *Added multiple target options *Added IP axpand capabilities * *Revision 1.8 2002/05/28 18:48:09 plasmahh *more and more... * *Revision 1.7 2002/05/14 16:31:59 plasmahh *implementing new scanner structure, that will be e xtendible * *Revision 1.6 2002/05/13 14:36:42 plasmahh *Added new scanner technologies to plug in new scanners in the future * *Revision 1.5 2002/05/06 21:27:44 plasmahh *redesigned the classes a bit... *begin implementing main & static * *Revision 1.4 2002/05/01 23:08:50 plasmahh *mh... * *Revision 1.3 2002/04/30 06:04:35 plasmahh *minor documentation changes * *Revision 1.2 2002/04/16 20:10:24 plasmahh *Added new headers * */