C Webvserver – STDIN, sockets and CGI piping

1.3k views Asked by At

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?

2

There are 2 answers

0
grufo On

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! :)

2
Giuseppe Pes On

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.