Last active
September 22, 2016 15:42
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* +++ | |
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