Skip to content

Instantly share code, notes, and snippets.

@tuklusan
Last active October 5, 2016 00:30
Show Gist options
  • Save tuklusan/84c52858bed6232930aa497ea1b43776 to your computer and use it in GitHub Desktop.
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
/* +++
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(&param_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(&param_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(&param_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