/* Source file for netreconn LAN scanner. See COPYING file for license details TODO - staticky and checks - add session trace (only one level, none of this d1-N _bullshit_) - add udp support - different socket type support (e.g. raw), look at how dnet does this - perhost timers with -vv option or *something else* - if practical a true pre-ping using ICMP versus a full connect - Support for user to change TCP flags in both directions - Better usage messages */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utils.h" /* all of these are here so ip4.h can be used by other programs */ #define PACKAGE "scanlan" /* binary exec name */ struct scanlan_data { u_short port_start; /* Starting port */ u_short port_end; /* Last port */ int subnet_start; /* Subnet start */ int subnet_end; /* Subnet end, equals start if none specified */ short int cflag; /* Full TCP Connect for every scan flag */ short int iflag; /* Is alive check only flag */ short int xflag; /* Do ports beyond 1024 flag (or eXtra) */ short int inet_timeo; /* Connect timeout in seconds */ short int inet_utimeo; /* Connect timeout useconds value for the anal */ char *start_vector; /* the starting subnet address vector pointer */ char addr[1024]; /* The raw start address from stdin */ char *portstring; /* If only portchecking use this */ char *socktype; /* The socket type */ } scandata; /* Our base struct name */ /* we use "sd" with the idea that we will be protocol version agnostic */ struct scanlan_data *sd = &scandata; /* the quick way to get at it... */ /* simple helper to initialize pertinent scandata */ static void init_scandata(void) { sd->port_start = DEFAULT_START_PORT; sd->port_end = DEFAULT_END_PORT; sd->subnet_start = 0; sd->subnet_end = 0; sd->cflag = 0; sd->iflag = 0; sd->xflag = 0; sd->inet_timeo = DEFAULT_INET_TIMEOUT; /* Set connect timeout in secs */ sd->inet_utimeo = 0; /* default usec for timer */ sd->socktype = "STREAM"; /* default socket type */ } /* static void printime(char *msg) { char buffer[256]; time_t curtime; struct tm *loctime; curtime = time(NULL); loctime = localtime(&curtime); printf("%s", msg); fputs(asctime(loctime), stdout); } static char *return_time(void) { char buffer[256]; time_t curtime; struct tm *loctime; curtime = time(NULL); loctime = localtime(&curtime); return (asctime(loctime)); } */ /* ye olde usage print */ static void usage(void) { printf(PACKAGE " [[option][arguments]][ipadxn-N|host]\n" PACKAGE " [-c][-p n-N][-t n.n][-P][-u][-v][-x]\n" "OPTIONS:\n" " -6 Specify ipv6\n" " -c Connect for each port(not default, slower)\n" " -d Set the socket to datagram instead of stream\n" " -P Is up only check (Pinglike only check)\n" " -p n[-N] Scan port number n or a range of n-N\n" " Defaults are: %i-%i\n" " -t n[.n] Set the default scan timeout to SECONDS.USECONDS\n" " Defaults are: %i.0\n" " -x Xtra thorough scan (slower non-strobe scan)\n" " -u Print help/usage message\n" " -v Be verbose\n" "NOTES: For long hops or systems that get connect errors\n" " try specifying a well known port (e.g. 22 or 80)\n" "EXAMPLES:\n" " " PACKAGE " -v -p 22-80 192.168.1.10\n" " " PACKAGE " -t 5 -p 22-80 192.168.1.2-254\n" " " PACKAGE " -t 2.050 -v -x somehost.domain.net\n", DEFAULT_START_PORT, DEFAULT_END_PORT, DEFAULT_INET_TIMEOUT); } /* capture any strange socket errors here */ static void sockerr(int res) { fprintf(stderr, "Connect error: "); switch (res) { case EADDRINUSE: fprintf(stderr, "EADDRINUSE\n"); break; case EADDRNOTAVAIL: fprintf(stderr, "EADDRNOTAVAIL\n"); break; case EALREADY: fprintf(stderr, "EALREADY\n"); break; case ECONNREFUSED: fprintf(stderr, "ECONNREFUSED\n"); break; case EHOSTUNREACH: fprintf(stderr, "EHOSTUNREACH\n"); break; case ENETDOWN: fprintf(stderr, "ENETDOWN\n"); break; case ENETUNREACH: fprintf(stderr, "ENETUNREACH\n"); break; case ETIMEDOUT: fprintf(stderr, "ETIMEDOUT\n"); break; default: fprintf(stderr, "Host timed out (exists?)\n"); break; } } /* up/down connect test; we do this once/host unless specified otherwise */ static int isalive(struct sockaddr_in scanaddr, short int inet_timeo, short int inet_utimeo) { short int sock; /* our main socket */ long arg; /* for non-block */ fd_set wset; /* file handle for bloc mode */ struct timeval timeout; /* timeout struct for connect() */ sock = -1; if (sd->socktype == "STREAM") sock = socket(AF_INET, SOCK_STREAM, 0); else sock = socket(AF_INET, SOCK_DGRAM, 0); if ((arg = fcntl(sock, F_GETFL, NULL)) < 0) { fprintf(stderr, "Error fcntl(..., F_GETFL) (%s)\n", strerror(errno)); return 1; } arg |= O_NONBLOCK; if (fcntl(sock, F_SETFL, arg) < 0) { fprintf(stderr, "Error fcntl(..., F_SETFL) (%s)\n", strerror(errno)); return 1; } /* * set result stat then try a select if it can take * awhile. This is dirty but works */ int res = connect(sock, (struct sockaddr *)&scanaddr, sizeof(scanaddr)); if (res < 0) { if (errno == EINPROGRESS) { timeout.tv_sec = inet_timeo; timeout.tv_usec = inet_utimeo; FD_ZERO(&wset); FD_SET(sock, &wset); int rc = select(sock + 1, NULL, &wset, NULL, &timeout); /* This works great on dead hosts */ if (rc == 0 && errno != EINTR) { sockerr(res); close(sock); return 1; } } } close(sock); return 0; } /* XXX jrf: Helper function for scanhost, all this does is save nesting space; I'd prefer to have it inline if there is a cleaner way to do so (no that doesn't mean shorter tabs :) */ static void printport(u_short current_port, struct servent *service_info) { if (sd->xflag) { service_info = getservbyport(htons (sd->port_start + current_port), "tcp"); if (!service_info) { printf("%-5d unknown\n", sd->port_start + current_port); } else { printf("%-5d %-30s\n", sd->port_start + current_port, service_info->s_name); } } else { printf("%-5d %-30s\n", ntohs(service_info->s_port), service_info->s_name); } } /* short circuit the other jive if we are doing ipv6 */ static void quickport6(char *addr, char *portstring) { struct addrinfo *res; struct addrinfo hints; register short int isalive6; isalive6 = 0; memset(&hints, '\0', sizeof(hints)); if (sd->socktype == "STREAM") hints.ai_socktype = SOCK_STREAM; else hints.ai_socktype = SOCK_DGRAM; #ifndef NETBSD hints.ai_flags = AI_ADDRCONFIG; #endif int e = getaddrinfo(addr, portstring, &hints, &res); if (e != 0) { printf("Error: %s\n", gai_strerror(e)); exit(EXIT_FAILURE); } int sock = -1; struct addrinfo *r = res; for (; r != NULL; r = r->ai_next) { sock = socket(r->ai_family, r->ai_socktype, r->ai_protocol); if (sock != -1 && connect(sock, r->ai_addr, r->ai_addrlen) == 0) { printf("Port %s open on %s\n", portstring, addr); ++isalive6; break; } } if (sock != -1) close(sock); freeaddrinfo(res); if (sock != -1) if (!isalive6) { printf ("Was able to resolve %s but could not connect to port %s\n", addr, portstring); close(sock); exit(EXIT_FAILURE); } close(sock); exit(EXIT_SUCCESS); } /* Perform a single host scan: would like this to be a module someday */ static void scanhost(struct sockaddr_in scanaddr) { u_short current_port; /* the current port being scanned */ short int sock; /* our main socket */ short int hostalive; /* node is alive - used for skipping */ register u_int finished; /* 1 when deep scanning is finished */ register u_int goodproto; /* 1 when protocol mapped to service */ struct servent *service_info; /* service information structure */ service_info = getservent(); current_port = 0; finished = 0; goodproto = 0; hostalive = 0; sock = -1; if (!sd->xflag) setservent(1); /* not thorough get /etc/services handle */ while (((sd->port_start + current_port) <= sd->port_end) || !finished) { scanaddr.sin_family = AF_INET; if (sd->socktype == "STREAM") sock = socket(AF_INET, SOCK_STREAM, 0); else sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock == -1) { fprintf(stderr, "Error assigning master socket\n"); exit(EXIT_FAILURE); } /* not thorough then strobe using known services */ if (!sd->xflag) { while (!goodproto) { service_info = getservent(); if (!service_info) break; if (!strcmp(service_info->s_proto, "tcp") && (ntohs(service_info->s_port) <= sd->port_end) && (ntohs(service_info->s_port) >= sd->port_start)) { goodproto = 1; break; } } if (!goodproto) break; if (!service_info) { finished = 1; break; } if (goodproto) scanaddr.sin_port = service_info->s_port; goodproto = 0; } else scanaddr.sin_port = htons(sd->port_start + current_port); /* do a basic connect/select test before the real check */ if (!finished) { /* If we have not checked already see if it is alive */ if (hostalive == 0) if (isalive(scanaddr, sd->inet_timeo, sd->inet_utimeo) > 0) return; if (!sd->cflag) hostalive = 1; if (sd->iflag > 0) { printf(" is alive\n"); close(sock); break; } if (connect (sock, (struct sockaddr *)&scanaddr, sizeof(scanaddr)) == 0) { printport(current_port, service_info); } else if (errno == 113) { /* Crap */ fprintf(stderr, "No route to host\n"); finished = 1; break; } } close(sock); current_port++; if (sd->port_start + current_port >= sd->port_end) finished = 1; } if (!sd->xflag) endservent(); } /* * The next set of functions are all helpers for the main program; it may * seem like overkill but the upside is each one reduces the possible size * of memory used which is always a plus. It also allows for simple * manipulation of the parsers themselves. */ /* this is a helper for main() to parse out -p n-N and fill in the scandata structure via the sd-> pointer */ static void portparse(char *argv_port) { char *token; token = strtok(argv_port, "-"); if (!token) { fprintf(stderr, "Error! No port specified\n"); usage(); exit(EXIT_FAILURE); } else { sd->port_start = atoi(token); token = strtok(NULL, "-"); if (token) sd->port_end = atoi(token); else { sd->port_end = sd->port_start; sd->portstring = argv_port; } } if (sd->port_start <= 0) { fprintf(stderr, "Starting port is a negative number\n"); exit(EXIT_FAILURE); } else if (sd->port_start > sd->port_end) { fprintf(stderr, "Starting port is greater than end port\n"); exit(EXIT_FAILURE); } else if (sd->port_end >= 65535) { fprintf(stderr, "End port is past 65534\n"); exit(EXIT_FAILURE); } } /* main parser helper function */ static void timerparse(char *argv_timer) { char *token; token = strtok(argv_timer, "."); if (!token) { fprintf(stderr, "Error! No time specified\n"); usage(); exit(EXIT_FAILURE); } else { sd->inet_timeo = atoi(token); token = strtok(NULL, ".'"); if (token) sd->inet_utimeo = atoi(token); } } /* this is a helper for main() to parse out x.x.x.n-N and fill in the scandata structure via the sd-> pointer */ static int subnetparse(char *argv_cp) { int y; char *end_vp; /* pointer for the ending subnet address */ char *tokenizer; /* tmp pointer for tokenizing */ char start_addr[1023]; /* char array for the address */ /* XXX-bug: x.x.x.-n-N will cause a segv */ sd->start_vector = strtok(argv_cp, "-"); end_vp = strtok(NULL, "-"); /* If there is not a - then make subnet end same as the start */ if (end_vp) { sd->subnet_end = atoi(end_vp); strncpy(start_addr, sd->start_vector, 1023); tokenizer = strtok(start_addr, "."); for (y = 3; y != 0; y--) { tokenizer = strtok(NULL, "."); if (!tokenizer) { strncpy(sd->addr, argv_cp, 1023); return 0; } } sd->subnet_start = atoi(tokenizer); } else sd->subnet_end = sd->subnet_start; strncpy(sd->addr, sd->start_vector, 1023); /* XXX do we need a x.x.x.0 case here? Probably */ if (sd->subnet_start) if (sd->subnet_start > 254) { fprintf(stderr, "Error: Invalid subnet start value\n"); exit(EXIT_FAILURE); } /* XXX-bug: 0 case cannot be caught */ if (sd->subnet_end) { if (sd->subnet_end <= 0) { fprintf(stderr, "Error: subnet end is equal or less than 0\n"); exit(EXIT_FAILURE); } else if (sd->subnet_end >= 255) { fprintf(stderr, "Error: subnet end is equal to or greater than 255\n"); exit(EXIT_FAILURE); } } if (sd->subnet_start) if (sd->subnet_start > sd->subnet_end) { fprintf(stderr, "Error: subnet start is greater than subnet end\n"); exit(EXIT_FAILURE); } return 0; } /* MAIN */ int main(int argc, char **argv) { register int i; /* input parsing */ int verbose; /* verboseflag(local) */ char *start_time; /* Grab the starting time */ struct hostent *host_info; /* Hostinfo struct */ struct sockaddr_in address, address_end, scanaddr; /* Address structs */ struct sockaddr *cur_address; /* Current address in subnet */ struct sockaddr *end_address; /* Ending subnet address */ short int isv6 = 0; /* XXX this is temporary til details worked out */ if (!argv[1]) { fprintf(stderr, "Syntax error\n"); usage(); return EXIT_FAILURE; } /* Init defaults */ init_scandata(); /* call a simple helper to clean up main() */ verbose = 0; /* XXX Thoughts about these: - Be easier with posix getopt? */ switch (argc) { case 2: /* Trap help print request */ if ((!strcmp(argv[1], "-?")) || (!strcmp(argv[1], "-u"))) { usage(); return EXIT_SUCCESS; } else { break; } default: for (i = 1; i < argc - 1; i++) { /* verbose flag */ if (!strcmp(argv[i], "-v")) { ++verbose; /* v6 */ } else if (!strcmp(argv[i], "-6")) { ++isv6; /* isalive only check */ } else if (!strcmp(argv[i], "-P")) { ++verbose; ++sd->iflag; /* force connect on every port flag */ } else if (!strcmp(argv[i], "-c")) { ++sd->cflag; /* set the socket type to datagram */ } else if (!strcmp(argv[i], "-d")) { sd->socktype = "DGRAM"; /* port specification */ } else if (!strcmp(argv[i], "-p")) { portparse(argv[i + 1]); i++; /* timer specification */ } else if (!strcmp(argv[i], "-t")) { timerparse(argv[i + 1]); i++; /* Don't strobe (e.g do not rely on /etc/services) */ } else if (!strcmp(argv[i], "-x")) { ++sd->xflag; } } } /* if ((strstr(argv[argc -1], ".") && (strstr(argv[argc-1], "-")))) */ subnetparse(argv[argc - 1]); if (isv6) quickport6(sd->addr, sd->portstring); /* Initialize the address structure - zero out and assign */ bzero((char *)&address, sizeof(address)); address.sin_family = AF_INET; sd->addr[1023] = 0; /* set the last element in the array to 0 */ /* Try to resolve host in either direction */ if ((host_info = gethostbyname(sd->addr))) { bcopy(host_info->h_addr, (char *)&address.sin_addr, host_info->h_length); } else if ((address.sin_addr.s_addr = inet_addr(sd->start_vector)) == INADDR_NONE) { fprintf(stderr, "Could not resolve host\n"); return EXIT_FAILURE; } else if (verbose) printf(" address valid\n"); if (verbose) printf("Timeout: %i.%i\n", sd->inet_timeo, sd->inet_utimeo); if (verbose) { printime("Scan start: "); start_time = return_time(); } while (sd->subnet_start <= sd->subnet_end) { cur_address = (struct sockaddr *)&address; end_address = (struct sockaddr *)&address_end; end_address->sa_data[5] = sd->subnet_end; printf("Host %d.%d.%d.%d", (u_char) cur_address->sa_data[2], (u_char) cur_address->sa_data[3], (u_char) cur_address->sa_data[4], (u_char) cur_address->sa_data[5]); if (! sd->iflag) printf("\n"); bcopy(cur_address, &scanaddr, sizeof(scanaddr)); if (verbose) if ((sd->port_start != sd->port_end) && (sd->iflag == 0)) printf("Port range: %d-%d\n", sd->port_start, sd->port_end); scanhost(scanaddr); cur_address->sa_data[5]++; sd->subnet_start++; } if (verbose) { printf("Scan start: %s", start_time); printime("Scan end : "); } return EXIT_SUCCESS; }