/* tcprobe.c - Marcus Fritzsch - 20040320 - 0.2.3 */ /***history: * 20040320 2031 begin of development (0.1.0) * 20040321 2254 working version, accepting ips and names (0.1.1) * 20040322 0858 added tp_get_service (0.1.2) * 20040322 1414 added ip-range handling through ip1-ip2 (0.1.3) * 20040323 1155 added getopt option handler, valid options * are -p, -v, -h (0.1.4) * 20040323 1258 extended getopt handler -p to handle portranges like * 22:80,515 (0.1.5) * 20040324 1017 removed dead code, fixed minor bugs (0.1.6) * 20040324 1113 added name resolution for scanned hosts (0.1.7) * 20040324 2312 added name caching through binary tree, * keys for comparisons are generated with * crc32-algorithm by Craig Bruce (0.1.8) * 20040324 2342 embedded crc32 to tcprobe.c (0.1.9) * 20040325 1313 fixed some miscelanious bugs (0.1.10) * 20040401 1040 added option -P for procnum (0.1.11) * 20040511 0821 removed mega-fork code... now using select ()!! (0.1.12) * 20040511 1850 removed select with connect/usleep test_connect * and start_process were completele rewritten (0.2.0) * 20040512 0830 minor bugfixes and enhancements (0.2.1) * 20040512 2038 if possilble read data from socket for identification * of service running there, added option -n (no-lookup) * to scan without doing host lookups - only for ip- * arguments :o) (0.2.2) * 20040513 0810 removed the name caching stuff and crc32... * done misc cleanups/checking for bounds (0.2.3) */ /***TODO: * 20040321 2343 ip ranges through 192.168.1.0/24 * 20040324 2354 add ipc through shm support or the like, to order output * of childs */ /***XXX: * if you _REALLY_ want to see some interesting code about network programming * go and get netcat!!!! */ /* define to get some more-or-less useful dbg-output */ //#define DBG #define _GNU_SOURCE #define _BSD_SOURCE #define _SVID_SOURCE /* XXX: don't know if this one is right!! */ #define _POSIX_C_SOURCE 2 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*********************************************************************/ /*#define DBG*/ #define VER "0.2.3" #define PGM "tcprobe" /* my makefile does this for me... */ #ifndef USR # define USR "m" #endif #ifndef MCH # define MCH "localhost" #endif #define TP_TRUE 1 #define TP_FALSE 0 /* that many processes will be created... */ /* when scanning a host that drops packets, you might get better results * with more processes, but when scanning a host that refuses connection * you run really well with about 10 */ #define TP_MAXPROC 25 /* all failing connections timeout after .. 1/10 seconds */ #define TP_TIMEOUT 25 #define MAXHOSTNAMELEN 256 #define TP_SOCK_CNT 128 /* 128 should be enough for a service id... */ #define MAXLINE 128 #define out_of_mem (void) write (STDERR_FILENO, "OUT OF MEMORY\n", 14), exit (EXIT_FAILURE) #define SAI struct sockaddr_in #define SA struct sockaddr #define TV struct timeval #define IA struct in_addr #define SL (socklen_t) sizeof (SA) /*********************************************************************/ /* if there was an ip-ip range specified on the cmd line */ typedef struct TP_TWOIPS { char one [16]; char two [16]; } tp_twoips_t; typedef struct TP_PORT { unsigned short port; int fd; SAI saddr; } tp_port_t; /*********************************************************************/ static int tp_proc_count = 0; static tp_twoips_t tp_iprng; static tp_port_t * tp_ports; static unsigned long tp_nports = 0; static unsigned int tp_max_procs = TP_MAXPROC; static float tp_timeout = TP_TIMEOUT; static int tp_names = 1; static int tp_getid = 1; /*********************************************************************/ struct sockaddr_in tp_create_sockaddr_in (const char * host, unsigned short port); char * tp_get_hostname (const char * host); char * tp_get_service (unsigned short port); char * tp_parse_arg (char * arg); short tp_get_ip_octet (char * ip, int octet); void tp_inc_ip (char * ip); int tp_ip_le (char * ip1, char * ip2); void tp_version (); void tp_help (); void tp_add_port (unsigned short n); void tp_reset_ports (); int tp_getopts (int * argcp, char ** argv); void tp_shutdown (); void tp_init (); void * tp_alloc (size_t sz); void * tp_realloc (void * p, size_t sz); /*********************************************************************/ void warn (char * fmt, ...) { va_list v; va_start (v,fmt); vfprintf (stderr, fmt, v); va_end (v); } void bail (char * fmt, ...) { va_list v; va_start (v,fmt); vfprintf (stderr, fmt, v); va_end (v); exit (EXIT_FAILURE); } void debug (char * fmt, ...) { #ifdef DBG char * a; #define _D_ "DEBUG: " #define _D_l 7 va_list v; a = tp_alloc (_D_l + strlen (fmt) + 1); strncat (a, _D_, _D_l); #undef _D_ strcat (a, fmt); va_start (v,fmt); vfprintf (stderr, a, v); va_end (v); free (a); #endif } void pexit (const char * c) { perror (c); exit (EXIT_FAILURE); } void * tp_alloc (size_t sz) { /* alloc some storage with malloc, zero out and return, * bail out if an error occured */ void * t; t = malloc (sz); if (t == NULL) bail ("OUT OF MEMORY in tp_alloc\n"); memset (t, 0, sz); return t; } void * tp_realloc (void * p, size_t sz) { /* realloc storage and return, bail out if an error occured */ if (sz == 0) { free (p); return NULL; } else p = realloc (p, sz); if (p == NULL) bail ("OUT OF MEMORY in tp_realloc\n"); return p; } struct sockaddr_in tp_create_sockaddr_in (const char * host, unsigned short port) { /* the only reason for this routine is convenience... */ struct sockaddr_in addr; bzero (&addr, sizeof (addr)); addr.sin_family = AF_INET; addr.sin_port = htons(port); /* could have used inet_addr () too... */ if (inet_pton (AF_INET, host, &addr.sin_addr) <= 0) pexit ("inet_pton ()"); return addr; /*@out@*/ } char * tp_get_hostname (const char * host) { /* well... try to get hostname, if found return, else return NULL * if hostname was found copy it immediately to another buffer... * else there might be trouble! */ int ret; struct sockaddr_in sai; char * r = tp_alloc (MAXHOSTNAMELEN); /* 110: I liked it much... my first chat with a server through nc! */ sai = tp_create_sockaddr_in (host, 110); ret = getnameinfo ((const struct sockaddr*) &sai, sizeof (sai), r, MAXHOSTNAMELEN, NULL, 0, /* no need for service... */ 0 ); if (ret == 0) return r; /* the caller needs to copy immediately! */ free (r); return NULL; } char * tp_get_service (unsigned short port) { struct servent *s; s = getservbyport (htons (port), "tcp"); if (s == NULL) return NULL; /*@null@*/ return s->s_name; } void check (int i, char * c) { /* quick'n dirty error check and exit!! */ if (i != -1) return; fprintf (stderr, "%s: %s\n", c, strerror (errno)); exit (EXIT_FAILURE); } void s_close (int fd) { while (close (fd) < 0 && errno == EINTR); } char * tp_parse_arg (char * arg) { /* returns arg if arg is a hostname or an ip, else NULL */ /* if NULL is returned, tp_twoips_t tp_iprng will be set... */ int rc_ret, re_ret; regmatch_t match; regmatch_t match2 [2]; regex_t re_ip, re_ip2, re_ip_rng; rc_ret = regcomp (&re_ip, "^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}", REG_EXTENDED|REG_ICASE); rc_ret = regcomp (&re_ip2, "[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$", REG_EXTENDED|REG_ICASE); rc_ret = regcomp (&re_ip_rng, "^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}-[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}", REG_EXTENDED|REG_ICASE); /* from here on the code is messy like h*ll.... */ /* (sorry about that... i've got no experience with regexes in c) */ if (regexec (&re_ip_rng, arg, 1, &match, 0) == 0) { /* a range constits of 2 ips seperated by a '-' */ re_ret = regexec (&re_ip, arg, 2, match2, 0); if (re_ret != 0) return arg; /* something was wrong?!? */ memset (tp_iprng.one, 0, 16); strncpy (tp_iprng.one, &arg [match2 [0].rm_so], match2 [0].rm_eo - match2 [0].rm_so); /* a range constits of 2 ips seperated by a '-' */ re_ret = regexec (&re_ip2, arg, 1, &match2 [1], 0); if (re_ret != 0) return arg; /* something was wrong?!? */ memset (tp_iprng.two, 0, 16); strncpy (tp_iprng.two, &arg [match2 [1].rm_so], match2 [1].rm_eo - match2 [1].rm_so); return NULL; } /* ...no more ugly code?! */ /* seems to be a hostname... if it isn't the error will be reported later */ return arg; } short tp_get_ip_octet (char * ip, int octet) { /* get an octet from an ip address, octet may be 1..4 */ /* I do _not_ guarantee that it works for all cases... * but it does work for me! */ int c = 1; if (octet < 4) while (c < octet) ip = strchr (ip, '.') + 1, c++; else ip = strrchr (ip, '.') + 1; return atoi (ip); } void tp_inc_ip (char * ip) { /* increase an ip by 1... */ short ia, ib, ic, id; char * oip = ip; debug ("entered tp_inc_ip (%s)\n", ip); /* first, split ip into parts a.b.c.d */ ia = tp_get_ip_octet (ip, 1); ib = tp_get_ip_octet (ip, 2); ic = tp_get_ip_octet (ip, 3); id = tp_get_ip_octet (ip, 4); debug ("a='%d' b='%d' c='%d' d='%d'\n", ia, ib, ic, id); /* the same as my perl-code... do not know if it's optimal, guess not */ if (id < 254) { ++id; } else if (ic < 254) { ++ic; id = 1; } else if (ib < 254) { ++ib; ic = 0; id = 1; } else if (ia < 254) { ++ia; ib = 0; ic = 0; id = 1; } memset (oip, 0, 16); snprintf (oip, 16, "%d.%d.%d.%d", ia, ib, ic, id); debug ("leaving tp_inc_ip (%s)\n", oip); } int tp_ip_le (char * ip1, char * ip2) { /* ip less-or-equal */ register unsigned long a, b; debug ("%s <= %s ?\n", ip1, ip2); a = 0 | (tp_get_ip_octet (ip1, 1) << 24) | (tp_get_ip_octet (ip1, 2) << 16) | (tp_get_ip_octet (ip1, 3) << 8) | tp_get_ip_octet (ip1, 4); b = 0 | (tp_get_ip_octet (ip2, 1) << 24) | (tp_get_ip_octet (ip2, 2) << 16) | (tp_get_ip_octet (ip2, 3) << 8) | tp_get_ip_octet (ip2, 4); return a <= b; } void tp_version () { fprintf (stderr,"%s %s build on %s at %s by %s@%s\n", __FILE__, VER, __DATE__, __TIME__, USR, MCH); exit (EXIT_SUCCESS); } void tp_help () { fprintf (stderr, "%s ...\n\n", PGM); fprintf (stderr, "Overview of all options\n"); fprintf (stderr, "--ports or -p ... probe only the given port\n"); fprintf (stderr, "--processes or -P N use N processes\n"); fprintf (stderr, " ports can be specified like follows:\n" " -p 22:80,21,110 (scans from 22..80, 21 and 110)\n" " -p 79 (looks only for this one)\n"); fprintf (stderr, "--timeout or -t N use N/10 seconds timeout\n"); fprintf (stderr, "--no-lookup or -n disable hostname lookups\n" " this options works only if you specified an ip" " Scans will be faster with this option!\n"); fprintf (stderr, "--no-id or -i disable read from target\n" " this options will disable trying to read some data" " from the target host. Might speedup the scan!\n"); fprintf (stderr, "--version or -v show version and build information\n"); fprintf (stderr, "--help or -h this help message\n"); fprintf (stderr, "\nby default a selection of tp_ports will be tested, see source for\n" "further details\n"); fprintf (stderr, "\nbe sure to only scan hosts that are up and that they do not drop\n" "too many packets... it might take a loooong time...\n"); exit (EXIT_SUCCESS); } void tp_add_port (unsigned short p) { /* adds a port n to tp_ports and successively adds a zero as last one */ tp_ports = (tp_port_t *) tp_realloc (tp_ports, sizeof (tp_port_t) * (tp_nports + 2)); tp_ports [tp_nports].port = p; tp_ports [tp_nports].fd = 0; bzero (&tp_ports [tp_nports].saddr, sizeof (SAI)); ++tp_nports; tp_ports [tp_nports].port = 0; } void tp_reset_ports () { /* to properly handle tp_ports */ tp_nports = 0; free (tp_ports); tp_ports = NULL; } int tp_getopts (int * argcp, char ** argv) { /* well, get opts from argv... nothin' more */ int c, n; unsigned short on; char * p, * g, * oa, buf [12]; /* buf can hold a port:port */ while (1) { int index; static struct option long_options [] = { { "processes", 1, 0, 'P' }, { "timeout", 1, 0, 't' }, { "ports", 1, 0, 'p' }, { "no-id", 0, 0, 'i' }, { "version", 0, 0, 'v' }, { "help", 0, 0, 'h' }, { "no-lookup", 0, 0, 'n' }, { 0, 0, 0, 0 } }; c = getopt_long (*argcp, argv, "t:p:P:vhni", long_options, &index); if (c == -1) break; switch (c) { case 'P': /* number of maximum processes, std == TP_MAXPROC */ if (! optarg) bail ("option -P needs argument (how many processes)\n"); tp_max_procs = atoi (optarg); if (tp_max_procs == 0 || tp_max_procs > 256) /* lower ;o) and upper limit */ bail ("invalid argument to option -P (%s)\n", optarg); break; case 'p': /* multiple tp_ports in optarg, seperated by ',' */ if (optarg == NULL) bail ("option -p needs port argument!\n"); if (! isdigit (*optarg)) bail ("option -p needs port (numeric) argument!\n"); debug ("optarg == %p\n", optarg); tp_reset_ports (); oa = optarg; while (1) { int T; memset (buf, 0, 12); p = strchr (optarg, ','); if (p == NULL) p = strchr (optarg, '\0'); T = p - optarg; if (T >= 12) bail ("argument to option -p is invalid!\n"); strncpy (buf, optarg, T); tp_add_port (atoi (buf)); if (tp_ports [tp_nports - 1].port == 0) bail ("option -p needs argument!\n"); /* OK, found a ',' or a '\0'... now check whether buf contains * a range (e.g. 1:80), if there is a colon, add each port * within this range to tp_ports... may not be optimal, but * yet easy to realise */ g = strchr (buf, ':'); if (g != NULL) { n = atoi (g + 1); if (n == 0) bail ("option -p needs argument!\n"); on = tp_ports [tp_nports - 1].port; if (n > 0 && on < n) while (on < n) tp_add_port (++on); } if (*p != '\0') optarg += (p - optarg) + 1; else optarg = oa; /* true when *p == '\0' i.e. when the end of optarg * is reached */ if (optarg == oa) break; } break; case 't': /* set the timeout in 1/10 of a second... */ if (optarg == NULL) bail ("option -t needs argument! (e.g. -t 7)\n"); tp_timeout = atoi (optarg); tp_timeout /= 10.0; if (tp_timeout <= 0.0) bail ("option -t needs positive-non-zero argument!\n"); break; case 'v': /* version and exit */ tp_version (); break; case 'h': /* help an exit */ tp_help (); break; case 'n': /* disable name lookup */ tp_names = 0; break; case 'i': /* disable name lookup */ tp_getid = 0; break; default: warn ("unknown option %s!\n", argv [optind]); break; } } return optind; } void tp_shutdown () { /* atexit callback */ free (tp_ports); } void tp_init () { /* my static port selection... feel free to add some :o) * but do not exagerate */ int i; static unsigned short sports [] = { 79, 8000, 22, 110, 23, 80, 25, 139, 138, 443, 137, 3128, 445, 8080, 21, 16180, 31337, 6667, 515, 0 /* some ugly ports in there hey?... well, i used 16180 for www, do you * ever heard of this strange number 1.6180.... */ }; tp_reset_ports (); for (i = 0; sports [i]; i++) tp_add_port (sports [i]); atexit (tp_shutdown); } char * tp_get_id (int fd) { /* whith ths one I try to get some Info of the service on that port * It reads only the _FIRST_ line and returns it, this kind of id * works well with smtp, pop3, ssh - i think telnet and others too! */ char lnbuf [MAXLINE]; int len, r; char * line = NULL; fd_set rfd; TV tv = { 0, (tp_timeout * 100000) / 10 }; bzero (lnbuf, MAXLINE - 1); FD_ZERO (&rfd); FD_SET (fd, &rfd); r = select (fd + 1, &rfd, NULL, NULL, &tv); /* OK; 1 fd... no need for FD_ISSET :o) */ if (r != 1) return NULL; /* could not read... */ /* peek only... maybe implement some other recv/sends later on */ r = recv (fd, lnbuf, MAXLINE, MSG_PEEK); if (FD_ISSET (fd, &rfd)) { len = r - 1; lnbuf [len] = '\0'; line = tp_alloc (len + 2); memcpy (line, lnbuf, len + 1); if (len <= 0) free (line); return len > 0 ? line : NULL; } return line; } void tp_test_connect (const char * name, const char * argv, const char * fqdn, int pidx, int num) { int i, m = pidx + num, r; tp_port_t * p = NULL; IA addr; char * serv; char * id = NULL; /* service deamon id */ float j; check (inet_aton (name, &addr), "inet_aton ()"); for (i = pidx; i < m; i++) { p = & (tp_ports [i]); p->saddr.sin_family = AF_INET; p->saddr.sin_addr = addr; p->saddr.sin_port = htons (p->port); check (p->fd = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP), "socket ()"); check (r = fcntl (p->fd, F_GETFL), "fcntl ()"); check (r = fcntl (p->fd, F_SETFL, r | O_NONBLOCK), "fcntl ()"); debug ("trying %s:%u\n", argv, p->port); while (connect (p->fd, (SA *) &p->saddr, SL) < 0 && errno == EINTR); if (errno == ECONNREFUSED) p->fd = -1; } for (j = 0.0; j < tp_timeout; j += 0.1) { for (i = pidx; i < m; i++) { p = & (tp_ports [i]); if (p->fd == -1) continue; if ((connect (p->fd, (SA *) &p->saddr, SL) == 0 || errno == EISCONN)) { serv = tp_get_service (p->port); if (tp_names) fprintf (stdout, "%s:%5u ", argv, p->port); else fprintf (stdout, "%15s:%5u ", argv, p->port); /* do not show service if it could not be retrieved */ if (!serv) /* those blanks are not nice... but useful */ fprintf (stdout, " "); else fprintf (stdout, "(%15s)", serv); if (fqdn) fprintf (stdout, " (%s)", fqdn); id = NULL; if (tp_getid) id = tp_get_id (p->fd); if (id) fprintf (stdout, " %s\n", id); else fprintf (stdout, "\n"); free (id); s_close (p->fd); p->fd = -1; } } usleep (100000); /* 0.1 sec */ } for (i = pidx; i < m; i++) if (tp_ports [i].fd != -1) s_close (tp_ports [i].fd); } void tp_start_process (const char * name, const char * argv) { pid_t p; char * fqdn = NULL; /* [MAXHOSTNAMELEN];*/ int pidx, n; if (tp_names) fqdn = tp_get_hostname (name); /* there are too many procs running... wait for some to exit... */ while (tp_proc_count > tp_max_procs) if (wait (NULL) > 0) --tp_proc_count; debug ("tp_proc_count = %d\n", tp_proc_count); p = fork (); //p = 0; if (p == 0) { pidx = n = 0; while (pidx < tp_nports) { n = TP_SOCK_CNT; if (pidx + n >= tp_nports) n = tp_nports - pidx; debug ("inner loop: n = %d, pidx = %d\n", n, pidx); tp_test_connect (name, argv, fqdn, pidx, n); pidx += n; } } else if (p > 0) tp_proc_count++; free (fqdn); check (p, "fork ()"); if (p == 0) exit (EXIT_SUCCESS); } int main (int argc, char ** argv) { int b, d; pid_t p; struct hostent * he; struct in_addr ia; char name [MAXHOSTNAMELEN], * arg; tp_init (); for (d = b = tp_getopts (&argc, argv); argv [b]; b++) { arg = tp_parse_arg (argv [b]); /* if arg == NULL it is a ip-ip range, if not errors will be punished * later on */ if (arg == NULL) while (tp_ip_le (tp_iprng.one, tp_iprng.two)) { fprintf (stderr, "%s\n", tp_iprng.one); debug ("one = %s\n", tp_iprng.one); tp_start_process (tp_iprng.one, tp_iprng.one); tp_inc_ip (tp_iprng.one); } else { /* here we got if we have to scan from a name, a single ip or * arg was NULL because of an error.... */ if (!(he = gethostbyname (argv [b]))) { warn("could not get host '%s'\n", argv [b]); continue; } memcpy (&ia.s_addr, he->h_addr, sizeof (he->h_addr)); memset (name, 0, MAXHOSTNAMELEN); strncpy (name, inet_ntoa (ia), MAXHOSTNAMELEN - 2); tp_start_process (name, argv [b]); } } debug ("waiting for childs to exit...\n"); do { p = waitpid (-1, NULL, WUNTRACED); } while (p > 0); if (!argv [d]) fprintf (stderr, "%s ... (use -h for help)\n", PGM); return 0; }