A nex server written in C
nexserv is a server for the nex protocol written in C and compiled with sheepy (more about sheepy in the links below).
nexserv is a sheepy package and can be installed with spm:
spm -g install nexserv
The server configuration is on top of the source code.
I'm running nexserv in my machine and it serves my gemini capsule.
Using nc as a nex client, it can be accessed like this:
echo index.gmi | nc gmi.noulin.net 1900 | less
nex protocol is a simple protocol used in Nightfall city, the request is a path and the response is a file.
Nightfall city Nightfall express protocol (nex://) specification
Nex protocol specification:
echo nex/info/specification.txt | nc nightfall.city 1900 | less
nexserv is my spartan server spartserv with unnecessary code removed, I only kept the code handling the requested path and the connection is closed when an error is detected.
Simple spartan client and server
The source code is around 200 lines:
#! /usr/bin/env sheepy
/* or direct path to sheepy: #! /usr/local/bin/sheepy */
/* Libsheepy documentation: https://spartatek.se/libsheepy/ */
#include "libsheepyObject.h"
#include <sys/socket.h>
#include <netinet/in.h> // sockaddr_in
#include <limits.h> // PATH_MAX
// inet_ntoa
//already included #include <sys/socket.h>
//already included #include <netinet/in.h>
#include <arpa/inet.h>
/* enable/disable logging */
/* #undef pLog */
/* #define pLog(...) */
// server configuration
#define HOSTNAME "localhost"
#define ROOT "."
#define PORT 1900
#define INDEXFILE "index.txt"
int main(int ARGC, char** ARGV) {
initLibsheepy(ARGV[0]);
//setLogMode(LOG_VERBOSE);
setLogSymbols(LOG_UTF8);
int sock;
struct sockaddr_in server;
int mysock;
char buf[32768];
int rval;
// set root as absolute path
char root[PATH_MAX] = {0};
if (ROOT[0] == '/') {
realpath(ROOT, root);
pErrorNULL(bAppendManyS(root, "/", ""));
}
else {
// create absolute path from relative ROOT path
char p[PATH_MAX] = {0};
getcwd(p, PATH_MAX);
pErrorNULL(bAppendManyS(p, "/", ROOT));
realpath(p, root);
pErrorNULL(bAppendManyS(root, "/", ""));
}
size_t rootLen = strlen(root);
size_t slash = 0;
// count slashes at the end of root
// to compare paths with memcmp correctly
// since realpath removes the slashes at the
// end of the path from client.
while(root[rootLen-1-slash] == '/') {
slash++;
}
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0){
perror("Failed to create socket");
}
server.sin_family = AF_INET;
server.sin_addr.s_addr = INADDR_ANY;
server.sin_port = htons(PORT);
/* setsockopt: Handy debugging trick that lets
* us rerun the server immediately after we kill it;
* otherwise we have to wait about 20 secs.
* Eliminates "ERROR on binding: Address already in use" error.
*/
int optval = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval , sizeof(int));
if (bind(sock, (struct sockaddr *) &server, sizeof(server))){
perror("bind failed");
exit(1);
}
listen(sock, SOMAXCONN);
logP("Serving "HOSTNAME":%d %s index file: "INDEXFILE, PORT, root);
// date for request print
char date[50];
forever {
// struct for printing client ip in terminal with inet_ntoa
struct sockaddr_in addr;
socklen_t len = sizeof(addr);
mysock = accept(sock, &addr, &len);
if (mysock == -1)
perror("accept failed");
else {
// Set 10s timeouts for receive and send (SO_RCVTIMEO and SO_SNDTIMEO)
struct timeval timeout;
timeout.tv_sec = 10;
timeout.tv_usec = 0;
// get YMD HMS date
pErrorNULL(bLGetCurrentDateYMD(date, sizeof(date)));
if (setsockopt(mysock, SOL_SOCKET, SO_RCVTIMEO, (void *) &timeout, sizeof(timeout)) < 0) {
logE("%s ", inet_ntoa(addr.sin_addr));
perror("receive timeout failed");
close(mysock);
continue;
}
if (setsockopt(mysock, SOL_SOCKET, SO_SNDTIMEO, (void *) &timeout, sizeof(timeout)) < 0) {
logE("%s ", inet_ntoa(addr.sin_addr));
perror("send timeout failed");
close(mysock);
continue;
}
// new client
memset(buf, 0, sizeof(buf));
if ((rval = recv(mysock, buf, sizeof(buf), 0)) < 0) {
logE("%s ", inet_ntoa(addr.sin_addr));
perror("reading message");
close(mysock);
continue;
}
elif (rval == 0) {
logE("%s ", inet_ntoa(addr.sin_addr));
puts("Ending connection");
close(mysock);
continue;
}
elif (rval >= PATH_MAX) {
logE("%s ", inet_ntoa(addr.sin_addr));
logE("Request too long, got %d", rval);
close(mysock);
continue;
}
// request
cleanCharP(req) = trimS(buf);
// print request in terminal
logI("%s '%s'", inet_ntoa(addr.sin_addr), req);
char *path = req;
// build server path
char localPath[PATH_MAX] = {0};
if (rootLen + strlen(path) >= PATH_MAX) {
logE("Path too long");
send(mysock, "5 Path too long\r\n", sizeof("5 Path too long\r\n"), MSG_NOSIGNAL);
close(mysock);
continue;
}
pErrorNULL(bCatS(localPath, root, path));
// check path
if (not isPath(localPath)) {
logE("Not found: %s", localPath);
send(mysock, "4 Not found\r\n", sizeof("4 Not found\r\n"), MSG_NOSIGNAL);
close(mysock);
continue;
}
char realPath[PATH_MAX] = {0};
realpath(localPath, realPath);
if (memcmp(realPath, root, rootLen-slash) != 0) {
logE("Not found: %s", realPath);
send(mysock, "4 Not found\r\n", sizeof("4 Not found\r\n"), MSG_NOSIGNAL);
close(mysock);
continue;
}
size_t pathLen = strlen(realPath);
char *cursor = realPath + pathLen;
if (isDir(realPath)) {
if (pathLen + strlen("/"INDEXFILE) >= PATH_MAX) {
logE("Path too long: %s/"INDEXFILE, realPath);
send(mysock, "5 Path too long\r\n", sizeof("5 Path too long\r\n"), MSG_NOSIGNAL);
close(mysock);
continue;
}
memcpy(cursor, "/"INDEXFILE, strlen("/"INDEXFILE));
cursor += strlen("/"INDEXFILE);
}
FILE *f = fopen(realPath, "r");
if (!f) {
logE("Page not found: %s", realPath);
send(mysock, "4 Page not found\r\n", sizeof("4 Page not found\r\n"), MSG_NOSIGNAL);
close(mysock);
continue;
}
// request in buf is not needed anymore, reuse buf for response
// send file
size_t fsz;
while (fsz = fread(buf, 1, (size_t)sizeof(buf) , f)) {
ssize_t r = send(mysock, buf, fsz, MSG_NOSIGNAL);
if (r == -1) {
perror("write failed");
logE("closed socket");
break;
}
}
fclose(f);
close(mysock);
}
}
logP("Server stopped.");
}
// vim: set expandtab ts=2 sw=2: