C Webvserver – STDIN, sockets and CGI piping

I want to create a remote control for GNUNet, so I started writing a self-made multithreaded-generical-purpose webserver for the GNU OS, able to authenticate users (reading from the system user database) and able to execute generic CGI programs/scripts. I started from scratch and it's just a draft for now. However, everything seems to work fine.

I have just a question.

As you know, a CGI programs/scripts read the POST string from the STDIN and send their content to the STDOUT. The following is (part of) the code I wrote. And it seems to work.

if (pipe(cgiPipe))
{ perror("pipe");
}
cgiPid = fork();
if (cgiPid == 0)
{ /* child */ /* piping the POST content... */ /* first, send the truncated part of the POST string contained within the request string... */ if (nPOSTLength && (nSentChrs = write(cgiPipe[1], sPOSTSegment, nReqLen + requestString - sPOSTSegment)) > 0) { nPOSTLength -= nSentChrs; /* after, read and send the rest of the POST string not received yet... */ while (nPOSTLength > 0 && (nReadChrs = read(nRemote, reservedBuffer, BUFFER_SIZE_PER_USER)) > 0 && (nSentChrs = write(cgiPipe[1], reservedBuffer, nReadChrs)) > 0 && nReadChrs == nSentChrs) { nPOSTLength -= nReadChrs; } if (nReadChrs < 0) { printf("Error reading POST string.\n"); goto closeThread; } if (nSentChrs < 0) { printf("Error sending POST string.\n"); goto closeThread; } } else { write(cgiPipe[1], "(null)", 6); } close(cgiPipe[1]); /* redirecting the output of the pipe to the STDIN of the child process */ dup2(cgiPipe[0], STDIN_FILENO); /* redirecting STDOUT of the child process to the remote client */ dup2(nRemote, STDOUT_FILENO); setuid(nUserID); if (execve(sLocalPath, NULL, aCGIEnv)) { /* unable to execute CGI... */ perror("execve"); sendString(nRemote, "HTTP/1.1 200 OK\r\n" "Content-length: 97\r\n" "Content-Type: text/html\r\n\r\n" "<!doctype html><html><head><title>CGI Error</title></head><body><h1>CGI Error.</h1></body></html>\r\n" ); } goto closeThread;
}
else if (cgiPid > 0)
{ /* parent */ close(cgiPipe[0]); /* wait for child process. */ if (waitpid(cgiPid, NULL, 0) == -1) { perror("wait"); } goto closeThread;
}
else
{ /* parent */ perror("fork"); /* let's try to send it as normal file, if the user has the right permissions... */
}

As you can see, before executing the CGI program, the whole POST string is received from the client and piped (first the truncated part of it contained within the request string – usually few bytes – and then the rest). Then, the CGI program is executed.

And now my question…

If I try to upload a file of several MBs, several MBs are piped before the invocation of the CGI: is there any way to redirect the socket directly to the STDIN of the new process, in order to not read it before? But, for sure, I have to send the read truncated part of the POST string, before. So, I can schematize what I would like to do in this way:

  • piping a string (few bytes) to the STDIN, then
  • redirecting the socket (the client) to the STDIN, then
  • executing an external process (the CGI program)

Is it possible? Can you show me how?

8

2 Answers

Solved!!

I just had to put the sending process inside the parent rather than the child. In this way the CGI is executed immediately:

if (pipe(cgiPipe))
{ perror("pipe");
}
cgiPid = fork();
if (cgiPid == 0)
{ /* child */ /* piping the POST content... */ close(cgiPipe[1]); /* redirecting the output of the pipe to the STDIN of the child process */ dup2(cgiPipe[0], STDIN_FILENO); /* redirecting STDOUT of the child process to the remote client */ dup2(nRemote, STDOUT_FILENO); setuid(nUserID); if (execve(sLocalPath, NULL, aCGIEnv)) { /* unable to execute CGI... */ perror("execve"); sendString(nRemote, "HTTP/1.1 200 OK\r\n" "Content-length: 97\r\n" "Content-Type: text/html\r\n\r\n" "<!doctype html><html><head><title>CGI Error</title></head><body><h1>CGI Error.</h1></body></html>\r\n" ); } goto closeThread;
}
else if (cgiPid > 0)
{ /* parent */ close(cgiPipe[0]); /* first, send the truncated part of the POST string contained within the request string... */ if (nPOSTLength && (nSentChrs = write(cgiPipe[1], sPOSTSegment, nReqLen + requestString - sPOSTSegment)) > 0) { nPOSTLength -= nSentChrs; /* after, read and send the rest of the POST string not received yet... */ while (nPOSTLength > 0 && (nReadChrs = read(nRemote, reservedBuffer, BUFFER_SIZE_PER_USER)) > 0 && (nSentChrs = write(cgiPipe[1], reservedBuffer, nReadChrs)) > 0 && nReadChrs == nSentChrs) { nPOSTLength -= nReadChrs; } if (nReadChrs < 0) { printf("Error reading POST string.\n"); goto closeThread; } if (nSentChrs < 0) { printf("Error sending POST string.\n"); goto closeThread; } } else { write(cgiPipe[1], "(null)", 6); } /* wait for child process. */ if (waitpid(cgiPid, NULL, 0) == -1) { perror("wait"); } goto closeThread;
}
else
{ /* parent */ perror("fork"); /* let's try to send it as normal file, if the user has the right permissions... */
}

Thank you for your help!

And… let's hope to see a remote control for GNUNet as early as possble! :)

This can be achieved by replacing the file descriptor STDIN_FILENO with the opened socket using dup2(). You should also then close the original socket in the child process:

dup2(socket_fd, STDIN_FILENO);
close(socket_fd); ;
execve("cgi_process", args, env);

The execve puts into execution another process whose STDIN is bound to socket_fd.

2

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

You Might Also Like