Last active
October 5, 2016 00:30
-
-
Save tuklusan/84c52858bed6232930aa497ea1b43776 to your computer and use it in GitHub Desktop.
Complete description of Free Online Public FORTUNE - COWSAY Server : Fun with TELNET and C/C++ Linux Multi-threaded Socket Server Programming (CentOS 7) is at http://supratim-sanyal.blogspot.com/2016/09/free-online-public-fortune-cowsay.html
This file contains 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 COWSAY server | |
- If a connection is made to its network port, and if fortune and cowsay are | |
- installed, this waits for some input and returns a random fortune cookie formatted by cowsay | |
- | |
- to build: gcc -o cowsayd -lpthread cowsayd.c | |
- on Centos 7, install fortune with yum install fortune-mod | |
- and install cowsay from rpm at http://www.melvilletheatre.com/articles/el7/ | |
- | |
- derived from Brain Damaged web server (http://supratim-sanyal.blogspot.com/2016/07/httpd410server-tiny-free-web-server-to.html) | |
- | |
- 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<stdio.h> | |
#include<string.h> | |
#include<stdlib.h> | |
#include<sys/socket.h> | |
#include<sys/types.h> | |
#include<arpa/inet.h> | |
#include<unistd.h> | |
#include<pthread.h> | |
#include<sys/time.h> | |
#include<syslog.h> | |
#include<errno.h> | |
#include<fcntl.h> | |
#include<netinet/tcp.h> | |
#include<netinet/in.h> | |
static const int MAXCLIENTS=10; // Should be less than /proc/sys/net/ipv4/tcp_max_syn_backlog | |
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 RECV_TIMEOUT=3; // 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 | |
static const int TCP_PORT_NUMBER=23; // listen on telnet port | |
static const int ZERO=0; // useful in setting socket options | |
static const int ONE=1; // useful in setting socket options | |
static const char *const banner1="COWSAY SERVER AT sanyalnet-cloud-vps.freeddns.org\n\n"; | |
static const char *const banner2="Enter anything to hear the cow speak.\n"; | |
int numclients=0; | |
char cowsayresp[1024]="\0"; | |
const char *const shell_command="if [[ $(which cowsay > /dev/null ; echo $?) -eq 0 ]] && [[ $(which fortune > /dev/null ; echo $?) -eq 0 ]] ; then cowsay -f $(ls $(cowsay -l | awk 'NR==1 {print $4}' | sed 's/://') | shuf -n1) $(fortune); echo; fi"; | |
int throttle(const struct in_addr *const sin_addr, const unsigned short *const sin_port); | |
//mutexes and thread functions | |
pthread_mutex_t clientcount_mutex, shell_exec_mutex, param_mutex; | |
pthread_attr_t thread_attr; | |
pthread_t thread_id; | |
void *connection_handler(void *); | |
int cowsayexec() | |
{ | |
FILE *fp; | |
char c; | |
int i=0,retval=0; | |
fp = popen(shell_command, "r"); | |
if (NULL==fp) | |
{ | |
syslog(LOG_NOTICE,"Failed to open pipe to command [%s]; error: %d/%s",shell_command, errno,strerror(errno)); | |
retval=-1; | |
} | |
else | |
{ | |
memset(cowsayresp,0,sizeof(cowsayresp)); | |
for(i=0;i<(sizeof(cowsayresp)-1);i++) | |
{ | |
if(EOF==(c=getc(fp))) | |
{ | |
cowsayresp[i]='\0'; | |
break; | |
} | |
else | |
{ | |
cowsayresp[i]=c; | |
} | |
} //for | |
} //else | |
pclose(fp); | |
return retval; | |
} //cowsayexec() | |
int main(int argc , char *argv[]) | |
{ | |
int socket_desc, client_sock, c, flags; | |
long long int totalserved=0; // 64 bit integer | |
struct sockaddr_in server, client; | |
char addrbuf[INET_ADDRSTRLEN]; /* defined in <netinet/in.h> */ | |
// We will enforce a timeout for clients to send something after connecting | |
struct timeval recv_to_tv; | |
recv_to_tv.tv_sec = RECV_TIMEOUT; | |
recv_to_tv.tv_usec = 0; | |
setlogmask (LOG_UPTO (LOG_INFO)); | |
openlog ("COWSAYD", 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 clientcount_mutex: %s",strerror(errno)); | |
closelog(); | |
exit(1); | |
} | |
if (pthread_mutex_init(&shell_exec_mutex, NULL) != 0) | |
{ | |
syslog(LOG_NOTICE,"failed to init shell_exec_mutex: %s",strerror(errno)); | |
closelog(); | |
exit(1); | |
} | |
if (pthread_mutex_init(¶m_mutex, NULL) != 0) | |
{ | |
syslog(LOG_NOTICE,"failed to init param_mutex: %s",strerror(errno)); | |
closelog(); | |
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 socket | |
socket_desc = socket(AF_INET , SOCK_STREAM , 0); | |
if (socket_desc == -1) | |
{ | |
syslog(LOG_NOTICE,"Could not create socket: %s", strerror(errno)); | |
closelog(); | |
exit(1); | |
} | |
//Prepare the sockaddr_in structure | |
server.sin_family = AF_INET; | |
server.sin_addr.s_addr = INADDR_ANY; | |
server.sin_port = htons( TCP_PORT_NUMBER ); | |
if (setsockopt(socket_desc, SOL_SOCKET, SO_REUSEADDR, &ONE, sizeof(int)) < 0) | |
{ | |
syslog(LOG_NOTICE,"Could not set SO_REUSEADDR on socket: %s", strerror(errno)); | |
} | |
//Bind | |
if( bind(socket_desc,(struct sockaddr *)&server , sizeof(server)) < 0) | |
{ | |
syslog(LOG_NOTICE,"Could not bind socket: %s", strerror(errno)); | |
closelog(); | |
exit(1); | |
} | |
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 and Accept | |
listen(socket_desc, 1+MAXCLIENTS); // keep queue size small to try avoid DOS flood; maximum is in proc/sys/net/ipv4/tcp_max_syn_backlog | |
c = sizeof(struct sockaddr_in); | |
while(client_sock = accept(socket_desc, (struct sockaddr *)&client, (socklen_t*)&c)) | |
{ | |
if (client_sock < 0) | |
{ | |
syslog(LOG_NOTICE,"Could not accept connection: %s", strerror(errno)); | |
shutdown (socket_desc, SHUT_RDWR); | |
close(socket_desc); | |
closelog(); | |
exit(1); | |
} | |
memset(addrbuf,0,sizeof(addrbuf)); | |
if (NULL==inet_ntop(AF_INET, &client.sin_addr, addrbuf, INET_ADDRSTRLEN)) | |
{ | |
syslog(LOG_NOTICE,"Could not get client address: %d[%s]", errno,strerror(errno)); | |
shutdown (client_sock, SHUT_RDWR); | |
close(client_sock); | |
} | |
else | |
{ | |
totalserved++; | |
syslog(LOG_NOTICE,"ACCEPTED Connection %lld from IP %s PORT %d", totalserved, addrbuf, ntohs(client.sin_port)); | |
// The following lines sends an entry that looks like SSH logon failure to syslogd, to be picked up | |
// by fail2ban if configured, which will ban the ip and report to blocklist.de for multiple connections quickly | |
// from same IP (i.e. source is telnet DOS/spam) | |
// Aug 28 21:04:36 sanyalnet-cloud-vps sshd[17400]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=static-200-105-171-226.acelerate.net | |
closelog(); | |
openlog ("sshd", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1); | |
syslog(LOG_AUTHPRIV|LOG_WARNING,"pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=telnet ruser= rhost=%s", addrbuf); | |
closelog(); | |
openlog ("COWSAYD", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1); | |
pthread_mutex_lock(¶m_mutex); // Lock the client_sock descriptor until connection_handler thread | |
// has copied to local variable | |
// set receive timeout on the accepted connection | |
setsockopt(client_sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&recv_to_tv,sizeof(struct timeval)); | |
if(throttle(&client.sin_addr,&client.sin_port)) // TBD ... some sort of anti-DOS throttle for IP address would be nice... | |
{ | |
syslog(LOG_NOTICE,"THROTTLED IP: %s PORT %d", addrbuf, ntohs(client.sin_port)); | |
shutdown (client_sock, SHUT_RDWR); | |
close(client_sock); | |
} | |
else if( pthread_create( &thread_id , &thread_attr, connection_handler , (void*) &client_sock) < 0) | |
{ | |
syslog(LOG_NOTICE,"Could not create service thread: %d[%s]", errno, strerror(errno)); | |
shutdown (client_sock, SHUT_RDWR); | |
close(client_sock); | |
} | |
} | |
} //while | |
exit(0); // unreachable | |
} //main() | |
// Handle each connection in this created thread | |
void *connection_handler(void *socket_desc) | |
{ | |
//Get the socket descriptor | |
int sock = *(int*)socket_desc; | |
int i=0,read_size=0, thisclient=0, myerrno=0, recv_tries=0; | |
char client_message[128]="\0"; | |
memset(client_message,0,sizeof(client_message)); | |
pthread_mutex_unlock(¶m_mutex); | |
pthread_mutex_lock(&clientcount_mutex); | |
numclients++; | |
thisclient=numclients; | |
pthread_mutex_unlock(&clientcount_mutex); | |
if(thisclient>MAXCLIENTS) // Client numbers start at 1 due to ++ above | |
{ | |
syslog(LOG_NOTICE,"Active Clients %d FD %d | Server full, MAXCLIENTS reached",thisclient,sock); | |
} | |
else | |
{ | |
// Send the banners | |
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *) &ONE, sizeof(int)); // setting TCP_NODELAY on send | |
if(send(sock,banner1,strlen(banner1),0)<0) | |
{ | |
syslog(LOG_NOTICE,"Active Clients %d FD %d | Could not send banner 1: %d/%s",thisclient,sock,errno,strerror(errno)); | |
} | |
else | |
{ | |
if(send(sock,banner2,strlen(banner2),0)<0) | |
{ | |
syslog(LOG_NOTICE,"Active Clients %d FD %d | Could not send banner 2: %d/%s",thisclient,sock,errno,strerror(errno)); | |
} | |
} | |
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *) &ZERO, sizeof(int)); | |
//read and log whatever the client sends | |
recv_tries=0; | |
do | |
{ | |
memset(client_message,0,sizeof(client_message)); | |
read_size = recv(sock, client_message, sizeof(client_message)-1, 0); | |
myerrno=errno; | |
if(read_size < 0) | |
{ | |
if (EAGAIN != myerrno) | |
{ | |
syslog(LOG_NOTICE,"Active Clients %d FD %d | Could not read message: recv error %d",thisclient,sock,myerrno); | |
read_size=0; | |
break; | |
} | |
else | |
{ | |
syslog(LOG_NOTICE,"Active Clients %d FD %d | no data yet",thisclient,sock); | |
recv_tries++; | |
if(recv_tries>=MAX_RECV_RETRIES) | |
{ | |
syslog(LOG_NOTICE,"Active Clients %d FD %d | max recv retries reached, nothing received ",thisclient,sock); | |
read_size=0; | |
} | |
} | |
} | |
} while(read_size<0); | |
if(read_size<=0) // read_size is zero here - empty message or unexpected client disconnet - do nothing | |
{ | |
// bots which do not send anything after connect are not served a cowsay message | |
syslog(LOG_NOTICE,"Active Clients %d FD %d | recvd zero bytes", thisclient,sock); | |
} | |
else | |
{ | |
if(read_size>sizeof(client_message))read_size=sizeof(client_message); | |
client_message[read_size-1]='\0'; // just some unnecessary defensive coding! | |
for(i=0;i<strlen(client_message);i++) if( ('\n'==client_message[i])||('\r'==client_message[i]) ) client_message[i]=' '; // Replace tabs and returns with space for logging | |
syslog(LOG_NOTICE,"Active Clients %d FD %d | RECEIVED: [%s]",thisclient,sock,client_message); | |
pthread_mutex_lock(&shell_exec_mutex); // One expensive shell exec at a time please | |
if(0==cowsayexec()) // get the wisdom | |
{ | |
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *) &ONE, sizeof(int)); // setting TCP_NODELAY on send | |
if(send(sock,cowsayresp,strlen(cowsayresp),0)<0) | |
{ | |
syslog(LOG_NOTICE,"Active Clients %d FD %d | Could not send response: %d/%s",thisclient,sock,errno,strerror(errno)); | |
} | |
else | |
{ | |
syslog(LOG_NOTICE,"Sent Cowsay [%s] to FD %d",cowsayresp,sock); | |
} | |
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *) &ZERO, sizeof(int)); | |
} | |
pthread_mutex_unlock(&shell_exec_mutex); | |
} | |
} | |
syslog(LOG_NOTICE,"Active Clients %d FD %d | Goodbye", thisclient,sock); | |
shutdown (sock, SHUT_RDWR); | |
close(sock); | |
pthread_mutex_lock(&clientcount_mutex); | |
numclients--; | |
pthread_mutex_unlock(&clientcount_mutex); | |
return; | |
} //connection_handler() | |
// TBD - return 1 if IP/Port is to be throttled based on DOS attempt or abuse, 0 if not | |
int throttle(const struct in_addr *const sin_addr, const unsigned short *const sin_port) | |
{ | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment