Skip to content

Instantly share code, notes, and snippets.

@tuklusan
Last active September 22, 2016 15:42
Show Gist options
  • Save tuklusan/423b5c21ca464eae42841bb1b63b7e78 to your computer and use it in GitHub Desktop.
Save tuklusan/423b5c21ca464eae42841bb1b63b7e78 to your computer and use it in GitHub Desktop.
Complete discussion about this program is in my blog entry at http://supratim-sanyal.blogspot.com/2016/07/httpd410server-tiny-free-web-server-to.html - httpd410server: A TINY FREE WEB SERVER TO ALWAYS RETURN HTTP ERROR LOCALLY FOR DNS BLACKLIST REDIRECTION AND LOGGING
/* +++
Supratim Sanyal's Brain Damaged web server
- ALWAYS RETURNS HTTP 410 (GONE) TO THE CLIENT -
- USE FOR DNS REDIRECTED MALWARE AD BLOCKER BLACKLIST IMPLEMENTATIONS
- Listens on ports http port 80 and https port 443
-
- To build: gcc -o httpd410server -lpthread httpd410server.c
-
- See http://supratim-sanyal.blogspot.com/2016/07/httpd410server-tiny-free-web-server-to.html for details
- I can be reached at http://mcaf.ee/sdlg9f
- Posted under GPL 3.0 License - use and modify freely but retain this header
--- */
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/time.h>
static const int MAXCLIENTS=512;
static const int RECV_TIMEOUT=5; // seconds to wait for client to send something after connecting
static const int MAX_RECV_RETRIES=3; // let recv timeout this many times before closing connection (broweser did not
// send data after opening port for this MAX_RECV_RETRIES*RECV_TIMEOUT seconds, kick it)
static const int UID=99; // this uid is set for this program after bind() for security - 99 is nobody
static const int GID=99; // this gid is set for this program after bind() for security - 99 is nobody
static const int PORT_HTTP=80;
static const int PORT_HTTPS=443;
static const char *const httpresp="HTTP/1.0 410 Brain Damaged Server\nServer: Supratim Sanyal's Brain Damaged Server/0.01-dev\n\n";
static const int ZERO=0;
static const int ONE=1;
struct timeval recv_to_tv;
int numclients=0;
//mutex and thread variables and function
pthread_mutex_t clientcount_mutex;
pthread_attr_t thread_attr;
pthread_t thread_id;
void *connection_handler(void *p_socket_desc);
int main(int argc , char *argv[])
{
int sd_http, sd_https, maxfd, sd_to_accept, flags, retval, i;
struct sockaddr_in server_http, server_https;
fd_set read_fds;
// We enforce a timeout for clients to send something after connecting
// Used in setting socket option by connection handler thread
recv_to_tv.tv_sec = RECV_TIMEOUT;
recv_to_tv.tv_usec = 0;
setlogmask (LOG_UPTO (LOG_INFO));
openlog (argv[0], LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1);
syslog(LOG_NOTICE,"%s starting up",argv[0]);
printf("%s: see system log for messages\n",argv[0]);
if (pthread_mutex_init(&clientcount_mutex, NULL) != 0)
{
syslog(LOG_NOTICE,"failed to init mutex: %s",strerror(errno));
exit(1);
}
// Our service threads will be detached threads
// If we don't do this, threads will default to PTHREAD_CREATE_JOINABLE which
// will keep using up memory because the thread resources are not released at
// thread exit since they are expecting to be joined by the main thread to
// retrieve return status etc.
if(pthread_attr_init(&thread_attr) != 0)
{
syslog(LOG_NOTICE,"failed to init thread_attr: %s",strerror(errno));
closelog();
exit(1);
}
if(pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED)!= 0)
{
syslog(LOG_NOTICE,"failed to set detached thread attr: %s",strerror(errno));
closelog();
exit(1);
}
//Create sockets
sd_http = socket(AF_INET, SOCK_STREAM, 0);
if ( -1 == sd_http)
{
syslog(LOG_NOTICE,"Could not create http socket: %s", strerror(errno));
exit(1);
}
if (setsockopt(sd_http, SOL_SOCKET, SO_REUSEADDR, &ONE, sizeof(int)) < 0)
{
syslog(LOG_NOTICE,"Could not set SO_REUSEADDR on http socket: %s", strerror(errno));
}
flags = fcntl(sd_http, F_GETFL, 0);
if (flags>=0)fcntl(sd_http, F_SETFL, (flags|O_NONBLOCK)); // Set non-blocking Socket
sd_https = socket(AF_INET, SOCK_STREAM, 0);
if ( -1 == sd_https)
{
syslog(LOG_NOTICE,"Could not create https socket: %s", strerror(errno));
exit(1);
}
if (setsockopt(sd_https, SOL_SOCKET, SO_REUSEADDR, &ONE, sizeof(int)) < 0)
{
syslog(LOG_NOTICE,"Could not set SO_REUSEADDR on https socket: %s", strerror(errno));
}
flags = fcntl(sd_https, F_GETFL, 0);
if (flags>=0)fcntl(sd_https, F_SETFL, (flags|O_NONBLOCK)); // Set non-blocking Socket
//Prepare the sockaddr_in structures
server_http.sin_family = AF_INET;
server_http.sin_addr.s_addr = INADDR_ANY;
server_http.sin_port = htons( PORT_HTTP );
server_https.sin_family = AF_INET;
server_https.sin_addr.s_addr = INADDR_ANY;
server_https.sin_port = htons( PORT_HTTPS );
//Bind
if( bind(sd_http,(struct sockaddr *)&server_http , sizeof(server_http)) < 0)
{
syslog(LOG_NOTICE,"Could not bind http socket: %s", strerror(errno));
exit(1);
}
if( bind(sd_https,(struct sockaddr *)&server_https , sizeof(server_https)) < 0)
{
syslog(LOG_NOTICE,"Could not bind https socket: %s", strerror(errno));
exit(1);
}
// Drop root privileges, switch uid and gid to non-privileged account
if(0!=setgid(GID)) // set gid first because it cannot be done after setuid
{
syslog(LOG_NOTICE,"Could not setgid to [%d], proceeding regardless: %d[%s]", GID, errno, strerror(errno));
}
else
{
syslog(LOG_NOTICE,"setgid to [%d] ok", GID);
}
if(0!=setuid(UID))
{
syslog(LOG_NOTICE,"Could not setuid to [%d], proceeding regardless: %d[%s]", UID, errno, strerror(errno));
}
else
{
syslog(LOG_NOTICE,"setuid to [%d] ok", UID);
}
//Listen - needed to mark the socket as passive, that is, that will be used to accept incoming
//connection requests using accept later
if(listen(sd_http, 5)<0) // Maximum limit for 2nd parameter (backlog) is in /proc/sys/net/ipv4/tcp_max_syn_backlog
{
syslog(LOG_NOTICE,"Could not listen on http socket: %s", strerror(errno));
shutdown (sd_http, SHUT_RDWR);
close(sd_http);
shutdown (sd_https, SHUT_RDWR);
close(sd_https);
exit(1);
}
if(listen(sd_https, 5)<0) // Maximum limit for 2nd parameter (backlog) is in /proc/sys/net/ipv4/tcp_max_syn_backlog
{
syslog(LOG_NOTICE,"Could not listen on https socket: %s", strerror(errno));
shutdown (sd_http, SHUT_RDWR);
close(sd_http);
shutdown (sd_https, SHUT_RDWR);
close(sd_https);
exit(1);
}
FD_ZERO(&read_fds);
FD_SET(sd_http, &read_fds);
FD_SET(sd_https, &read_fds);
syslog(LOG_NOTICE,"HTTP listen FD: %d", sd_http);
syslog(LOG_NOTICE,"HTTPS listen FD: %d", sd_https);
maxfd=sd_https; // highest FD so far for incoming data to listen to
for(;;)
{
retval=select((1+maxfd), &read_fds, NULL, NULL, NULL);
if(retval<0) // select failed
{
syslog(LOG_NOTICE,"select failure: %s", strerror(errno));
shutdown (sd_http, SHUT_RDWR);
close(sd_http);
shutdown (sd_https, SHUT_RDWR);
close(sd_https);
exit(1);
}
if(retval>0)
{
for(i=0; i<(1+maxfd);i++)
{
if(FD_ISSET(i, &read_fds))
{
if(sd_http==i)
{
sd_to_accept=i;
syslog(LOG_NOTICE,"HTTP connection on FD %d",sd_to_accept);
}
else if(sd_https==i)
{
sd_to_accept=i;
syslog(LOG_NOTICE,"HTTPS connection on FD %d",sd_to_accept);
}
else
{
sd_to_accept=(-1);
}
if( (-1)!=sd_to_accept) // incoming data in socket of interest; process it
{
// hand the connection over to a thread to serve and close
pthread_mutex_lock(&clientcount_mutex); // Lock active till accept() in created child thread is executed
// ensuring the right socket descriptor is used by accept()
if( pthread_create( &thread_id , &thread_attr, connection_handler , (void*)&sd_to_accept) < 0)
{
pthread_mutex_unlock(&clientcount_mutex);
syslog(LOG_NOTICE,"Could not create thread: %s", strerror(errno));
shutdown (sd_http, SHUT_RDWR);
close(sd_http);
shutdown (sd_https, SHUT_RDWR);
close(sd_https);
exit(1);
}
}
else
{
syslog(LOG_NOTICE,"Ignoring unexpected non-HTTP/HTTPS data on FD %d",i);
}
} //if(FD_ISSET(i, &read_fds))
} //for(i=0; i<(1+sd_https);i++)
} // if(retval>0)
} //for(;;)
exit(0); // unreachable
} // main()
// +++
// Thread to process each connection
// ---
void *connection_handler(void *const p_sd) // parameter has server socket descriptor with data on it to accept and process
{
struct sockaddr_in client;
int server_sock, client_sock;
int i=0,read_size=0, thisclient=0, myerrno=0, recv_tries=0;
char client_message[2048];
int sizeofclient = sizeof(struct sockaddr_in);
server_sock = *(int*)p_sd;
numclients++;
thisclient=numclients;
syslog(LOG_NOTICE,"connection_handler[%d]: starting", thisclient);
//Accept and process the connection
client_sock = accept(server_sock, (struct sockaddr *)&client, (socklen_t*)&sizeofclient);
pthread_mutex_unlock(&clientcount_mutex); // Lock was placed in main() before creating this thread for accept to
// work with right Server FD
if (client_sock < 0)
{
myerrno=errno;
if (EAGAIN==myerrno||EWOULDBLOCK==myerrno)
{
syslog(LOG_NOTICE,"connection_handler[%d]: Nothing to accept() server FD %d: %d(%s)", thisclient, server_sock, myerrno, strerror(myerrno));
}
else
{
syslog(LOG_NOTICE,"connection_handler[%d]: Could not accept() server FD %d: %d(%s)", thisclient, server_sock, myerrno, strerror(myerrno));
}
}
else
{
syslog(LOG_NOTICE,"connection_handler[%d]: accept ok server FD %d on client FD %d", thisclient, server_sock, client_sock);
if(thisclient>MAXCLIENTS)
{
syslog(LOG_NOTICE,"connection_handler[%d]: client FD %d | Server full, MAXCLIENTS reached",thisclient, client_sock);
}
else
{
// set read timeout on client connection
if(setsockopt(client_sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&recv_to_tv,sizeof(struct timeval))<0)
{
syslog(LOG_NOTICE,"connection_handler[%d]: Could not setsockopt() FD %d: %d(%s)", thisclient, client_sock, errno, strerror(errno));
}
recv_tries=0;
do
{
memset(client_message,0,sizeof(client_message));
read_size = recv(client_sock, client_message , sizeof(client_message)-1, 0);
myerrno=errno;
if(read_size < 0)
{
if (EAGAIN != myerrno)
{
syslog(LOG_NOTICE,"connection_handler[%d]: Could not recv() FD %d: %d(%s)",thisclient,client_sock,myerrno,strerror(myerrno));
read_size=0;
break;
}
else
{
syslog(LOG_NOTICE,"connection_handler[%d]: recv() FD %d: no data yet",thisclient,client_sock);
recv_tries++;
if(recv_tries>=MAX_RECV_RETRIES)
{
syslog(LOG_NOTICE,"connection_handler[%d]: recv() FD %d: max recv retries reached, no cookie for you",thisclient,client_sock);
read_size=0;
}
}
}
} while(read_size<0);
if(read_size == 0) // nothing received or unexpected client disconnet - do nothing
{
syslog(LOG_NOTICE,"connection_handler[%d]: FD %d recvd nothing",thisclient,client_sock);
}
else // something received; respond to client
{
for(i=0;i<strlen(client_message);i++) if( ('\n'==client_message[i])||('\r'==client_message[i]) ) client_message[i]=' ';
syslog(LOG_NOTICE,"connection_handler[%d]: FD %d: recvd [%s]",thisclient,client_sock,client_message);
setsockopt(client_sock, IPPROTO_TCP, TCP_NODELAY, (char *) &ONE, sizeof(int)); // setting TCP_NODELAY on send
// flushed socket out
if(send(client_sock,httpresp,strlen(httpresp),0)<0)
{
syslog(LOG_NOTICE,"connection_handler[%d]: failed to send() to FD %d: %d(%s)",thisclient,client_sock,errno,strerror(errno));
}
setsockopt(client_sock, IPPROTO_TCP, TCP_NODELAY, (char *) &ZERO, sizeof(int)); // disable TCP_NODELAY
}
} // else block of if(thisclient>MAXCLIENTS)
syslog(LOG_NOTICE,"connection_handler[%d]: Bye bye FD %d", thisclient,client_sock);
shutdown (client_sock, SHUT_RDWR);
close(client_sock);
} // else block of if (client_sock < 0) after accept()
pthread_mutex_lock(&clientcount_mutex);
syslog(LOG_NOTICE,"connection_handler[%d]: done", thisclient);
numclients--;
pthread_mutex_unlock(&clientcount_mutex);
pthread_exit(NULL);
} // connection_handler()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment