Example #1
0
int ServeClientInternal(struct AmmServer_Instance * instance , struct HTTPTransaction * transaction)
{

  if (instance==0) { errorID(ASV_ERROR_INSTANCE_NOT_ALLOCATED); return 0; } else
                   { /*fprintf(stderr,"ServeClient instance pointing @ %p \n",instance);*/ }

  if (!setSocketTimeouts(transaction->clientSock))
   {
    errorID(ASV_ERROR_COULD_NOT_SET_SOCKET_TIMEOUT);
    fprintf(stderr,"This means something weird is going on , skipping everything");
    return 0;
   }

  transaction->clientListID = findOutClientIDOfPeer(instance ,transaction->clientSock);

  //----------------------------- ---------------------------- ----------------------------
  // Check if client is banned
  //----------------------------- ---------------------------- ----------------------------
  int clientIsBanned = clientList_isClientBanned(instance->clientList,transaction->clientListID);
  //----------------------------- ---------------------------- ----------------------------
  if (!clientIsBanned)
  {
     //If client is ok go ahead to serve him..
     unsigned int keepAliveShots = 1;
     while ( ( ServeClientKeepAliveLoop(instance,transaction) ) && (instance->server_running) )
    {
      //fprintf(stderr,"Another KeepAlive Loop Served ( %u ) \n",keepAliveShots);
      ++keepAliveShots;
      clientIsBanned = clientList_isClientBanned(instance->clientList,transaction->clientListID);
      if (clientIsBanned)
      {
       warningID(ASV_WARNING_CLIENT_DENIED_ACCESS);
       SendErrorCodeHeader(instance,transaction,403 /*Forbidden*/,"403.html",instance->templates_root);
       break;
      }
    }
  }
  //----------------------------- ---------------------------- ----------------------------

  //fprintf(stderr,"Done with client / Closing Socket ( %u )  ..\n",transaction->clientSock);
  close(transaction->clientSock);
//  shutdown(transaction->clientSock,SHUT_RDWR);


  if (transaction->incomingHeader.headerRAW!=0)
  {
   //fprintf(stderr,"Done with client / Freeing incoming memory..\n");
   safeFree(transaction->incomingHeader.headerRAW,transaction->incomingHeader.headerRAWSize);
   transaction->incomingHeader.headerRAW=0;
  }

  //fprintf(stderr,"closed\n");

  if (!transaction->prespawnedThreadFlag)
   { //If we are running in a prespawned thread it is wrong to count this thread as a *dynamic* one that just stopped !
     //Clear thread id handler and we can gracefully exit..! ( LOCKLESS OPERATION)
     if (instance->threads_pool[transaction->threadID]==0) { fprintf(stderr,"While exiting thread , thread_pool id[%u] is already zero.. This could be a bug ..\n",transaction->threadID); }
     instance->threads_pool[transaction->threadID]=0;
     ++instance->CLIENT_THREADS_STOPPED;

     //We also only want to stop the thread if itsnot prespawned !
     //fprintf(stderr,"Exiting Thread\n");
     //pthread_join(instance->threads_pool[transaction->threadID],0);
     //pthread_exit(0);


     //This should make the thread release all of its resources (?)
     pthread_detach(pthread_self());
   }

  return 0;
}
Example #2
0
unsigned long SendFile
  (
    struct AmmServer_Instance * instance,
    struct HTTPTransaction * transaction,
    char * verified_filename_pending_copy, // The filename to be served on the socket above
    unsigned int force_error_code
  )
{
 int clientsock = transaction->clientSock;
 unsigned int resourceCacheID=transaction->resourceCacheID;
 unsigned long start_at_byte = transaction->incomingHeader.range_start;
 unsigned long end_at_byte = transaction->incomingHeader.range_end;
 unsigned char keepalive = transaction->incomingHeader.keepalive;
 unsigned char compression_supported = transaction->incomingHeader.supports_compression ;


  struct HTTPHeader * request = &transaction->incomingHeader;

  char verified_filename[MAX_FILE_PATH+1]={0};
  char reply_header[MAX_HTTP_REQUEST_HEADER_REPLY+1]={0};

  if (verified_filename_pending_copy != 0)
  {
     strncpy(verified_filename,verified_filename_pending_copy,MAX_FILE_PATH);
  }



/*!   Start sending the header first..!
      Due to error messages also having body payloads they are also handled here , creating
      clutter in the code but this way there is no need to write the same thing twice..! !*/

/*! PRELIMINARY HEADER SENDING START ----------------------------------------------*/
  unsigned int WeWantA200OK=0;

  if (force_error_code!=0)
  {
    //We want to force a specific error_code!
    if (! SendErrorCodeHeader(instance,clientsock,force_error_code,verified_filename,instance->templates_root) ) { fprintf(stderr,"Failed sending error code %u\n",force_error_code); return 0; }
  } else
  if (!FilenameStripperOk(verified_filename))
  {
     //Unsafe filename , bad request :P
     if (! SendErrorCodeHeader(instance,clientsock,400,verified_filename,instance->templates_root) ) { fprintf(stderr,"Failed sending error code 400\n"); return 0; }
     //verified_filename should now point to the template file for 400 messages
  } else
   {
      //We have a legitimate file to send , if we want to send it all , we must emmit a 200 OK header
      //if we are serving it with an offset , we must emmit a 206 OK header!
      if ( (start_at_byte!=0) || (end_at_byte!=0) )
       {
         error("No checking on Range Provided is done , the underlying mechanisms are safe , but the header could potentially display wrong things ..");
         error("We dont know the filesize yet so can't fix it here..");

         //Range Accepted 206 OK header
         if (! SendSuccessCodeHeader(instance,clientsock,206,verified_filename)) { fprintf(stderr,"Failed sending Range Acknowledged success code \n"); return 0; }
       } else
       {
         //Normal 200 OK header
         /*! TODO Reorganize this : THIS SHOULD NOT BE SENT YET , SINCE WE MAY WANT TO EMMIT A 304 Not Modified Header if content is unmodified..!*/
         WeWantA200OK=1;
       }
   }
/*! PRELIMINARY HEADER SEND END ----------------------------------------------*/


  struct cache_item * cache = (struct cache_item *) instance->cache;

  int opres=0;
  unsigned int index=0;
  unsigned long cached_lSize=0;
  unsigned char cached_buffer_is_compressed = compression_supported;
  unsigned char free_cached_buffer_after_use=0;
  unsigned char serveAsRegularFile=0;
  char * cached_buffer = cache_GetResource(instance,request,resourceCacheID,verified_filename,MAX_FILE_PATH,&index,&cached_lSize,0,&cached_buffer_is_compressed,&free_cached_buffer_after_use,&serveAsRegularFile);

  if  (cached_buffer!=0) //If we have already a cached version of the file there is a change we might send a 304 Not Modified response
   {
      unsigned char ok_to_serve_not_modified = 1;

      /*OK We have a cached buffer on this page BUT a good question to ask is the following..
        Is it a regular file we are talking about , or a dynamic page ? */

      if (cache[index].dynamicRequestCallbackFunction!=0)
              {
                /*It seems we have ourselves a dynamic page..! Are we on a callback cooldown ? */
                /*The only built in way to serve a not modified response is if the request is too soon ( callback_every_x_msec callback cooldown ) ! */
                struct AmmServer_RH_Context * shared_context = cache[index].dynamicRequest;
                if ( shared_context->callback_cooldown ) { ok_to_serve_not_modified=1; } else // <- the dynamic page is still fresh.. so lets serve not modified..!
                                                         { ok_to_serve_not_modified=0; } // <- the dynamic page has expired is dynamic so it can't be cached
              } else
              {
                  //It seems we have ourselves a regular page
                  //ok_to_serve_not_modified already should equal 1 so leave this here as documentation.. :P
              }

      //The application might want the file to always be served as a fresh one..
      if ( cache[index].doNOTCacheRule ) { ok_to_serve_not_modified = 0; } /*We have written orders that we want this file to NEVER get cached.. EVER :P */
      if (force_error_code!=0) { ok_to_serve_not_modified = 0; } /*We want 404 etc messages to remain 404 :P , no point in serving 404 and then 304 ( that the 404 didn't change )*/

      if (ok_to_serve_not_modified)
      {
       //Check E-Tag here..!
       unsigned int cache_etag = cache_GetHashOfResource(instance,index);
       if ((request->eTag!=0)&&(cache_etag!=0))
        {
          char LocalETag[MAX_ETAG_SIZE]={0};
          snprintf(LocalETag,MAX_ETAG_SIZE,"%u%u%lu%lu",instance->cacheVersionETag,cache_etag,start_at_byte,end_at_byte);

          //fprintf(stderr,"E-Tag is `%s` , local hash is `%s` \n",request->eTag,LocalETag);
          if ( strncmp(request->eTag,LocalETag,request->eTagLength)==0 )
           {
              fprintf(stderr,"The content matches our ETag , we will reply with 304 NOT MODIFIED! :) \n");
              SendNotModifiedHeader(instance,clientsock);

              //The Etag is mandatory on 304 messages..!
              char ETagSendChunk[MAX_ETAG_SIZE+64]={0};
              snprintf(ETagSendChunk,MAX_ETAG_SIZE+64,"ETag: \"%s\" \n",LocalETag);
              if (!SendPart(instance,clientsock,ETagSendChunk,strlen(ETagSendChunk))) { fprintf(stderr,"Failed sending content length @  SendMemoryBlockAsFile ..!\n");  }

              WeWantA200OK=0;
              request->requestType=HEAD;
           }
             else
          {
            warning("eTag Mismatch\n"); // <- for now mismatches are probably bugs
          }
        }
     }
   }




   unsigned int have_last_modified=0;
   struct stat last_modified;

   if ( WeWantA200OK )
   {
       if (! SendSuccessCodeHeader(instance,clientsock,200,verified_filename)) { fprintf(stderr,"Failed sending success code \n"); freeMallocIfNeeded(cached_buffer,free_cached_buffer_after_use); return 0; }

       /* TODO : TEMPORARILY DISABLED LAST-MODIFIED :P
       if (stat(verified_filename, &last_modified))  { fprintf(stderr,"Could not stat modification time for file %s\n",verified_filename); } else
                                                     {  have_last_modified=1; }*/

       //TODO -> Check with last modified -> char * cached_buffer = CheckForCachedVersionOfThePage(request,verified_filename,&index,&cached_lSize,0,gzip_supported);
   }


   if (have_last_modified)
     {

       struct tm * ptm = gmtime ( &last_modified.st_mtime ); //This is not a particularly thread safe call , must add a mutex or something here..!
       //Last-Modified: Sat, 29 May 2010 12:31:35 GMT
       GetDateString(reply_header,MAX_HTTP_REQUEST_HEADER_REPLY,"Last-Modified",0,ptm->tm_wday,ptm->tm_mday,ptm->tm_mon,EPOCH_YEAR_IN_TM_YEAR+ptm->tm_year,ptm->tm_hour,ptm->tm_min,ptm->tm_sec);
       opres=send(clientsock,reply_header,strlen(reply_header),MSG_WAITALL|MSG_NOSIGNAL);  //Send filesize as soon as we've got it
       if (opres<=0) { fprintf(stderr,"Error sending Last-Modified header \n"); freeMallocIfNeeded(cached_buffer,free_cached_buffer_after_use); return 0; }
     }


                 //This used to also emmit --> Keep-Alive: timeout=5, max=100\n <--
                 /* RedBot says ( http://redbot.org/?uri=http%3A%2F%2Fammar.gr%3A8080%2F ) ..!
                     The Keep-Alive header is completely optional; it is defined primarily because the keep-alive connection token implies that such a header exists, not because anyone actually uses it.
                    Some implementations (e.g., Apache) do generate a Keep-Alive header to convey how many requests they're willing to serve on a single connection, what the connection timeout is and other information. However, this isn't usually used by clients.
                    It's safe to remove this header if you wish to save a few bytes in the response.*/
  if (keepalive) { if (!SendPart(instance,clientsock,"Connection: keep-alive\n",strlen("Connection: keep-alive\n")) ) { /*TODO : HANDLE failure to send Connection: Keep-Alive */}  } else
                 { if (!SendPart(instance,clientsock,"Connection: close\n",strlen("Connection: close\n"))) { /*TODO : HANDLE failure to send Connection: Close */}  }


if (request->requestType!=HEAD)
 {
  if ( (cached_buffer!=0) && //If we haven't got a buffer cached.. AND
        ( (!cache[index].doNOTCacheRule) /*If we dont forbid caching */ || ( (cache[index].doNOTCacheRule)&&(cache[index].dynamicRequestCallbackFunction!=0) ) /*Or we forbid caching but we are talking about a dynamic page*/)
     ) // its ok to serve a cached file..
   { /*!Serve cached file !*/
     //if (gzip_supported) { strcat(reply_header,"Content-encoding: gzip\n"); } // Cache can serve gzipped files
     //Last-Modified: Sat, 29 May 2010 12:31:35 GMT
     //GetDateString(reply_header,"Date",1,0,0,0,0,0,0,0);


     unsigned int  cache_etag = cache_GetHashOfResource(instance,index);
     if (cache_etag!=0)
     {
        snprintf(reply_header,MAX_HTTP_REQUEST_HEADER_REPLY,"ETag: \"%u%u%lu%lu\"\n", instance->cacheVersionETag,cache_etag,start_at_byte,end_at_byte);
        opres=ASRV_Send(instance,clientsock,reply_header,strlen(reply_header),MSG_WAITALL|MSG_NOSIGNAL);  //Send E-Tag as soon as we've got it
        if (opres<=0) { fprintf(stderr,"Error sending ETag header \n"); freeMallocIfNeeded(cached_buffer,free_cached_buffer_after_use); return 0; }

     }

     if (cached_lSize==0) { warning("Bug(?) detected , zero cache payload\n"); }


     if ( cached_buffer_is_compressed )
     {
        strncpy(reply_header,"Content-Encoding: deflate\n",MAX_HTTP_REQUEST_HEADER_REPLY);
        opres=ASRV_Send(instance,clientsock,reply_header,strlen(reply_header),MSG_WAITALL|MSG_NOSIGNAL);  //Send E-Tag as soon as we've got it
        if (opres<=0) { fprintf(stderr,"Error sending Compression header \n"); freeMallocIfNeeded(cached_buffer,free_cached_buffer_after_use); return 0; }
     }

    //THIS ALSO EXISTS IN THE TransmitFileToSocket  CODE
    //Send Content Length , as a range , or as the whole file!
      if ( (start_at_byte!=0) || (end_at_byte!=0) )
       {
         //error("Resource Content-Range response");
         //Content-Range: bytes 1000-3979/3980
         int endAtBytePrinted = end_at_byte;
         if (endAtBytePrinted == 0 ) { endAtBytePrinted = cached_lSize; }
          snprintf(reply_header,MAX_HTTP_REQUEST_HEADER_REPLY,"Content-Range: bytes %lu-%u/%lu\nContent-length: %lu\n\n",start_at_byte,endAtBytePrinted,cached_lSize,cached_lSize-start_at_byte);
       } else
       {
         //error("Resource Plain Content-Length ");
         //This is the last header part , so we are appending an extra \n to mark the end of the header
         snprintf(reply_header,MAX_HTTP_REQUEST_HEADER_REPLY,"Content-length: %u\n\n",(unsigned int) cached_lSize);
       }
     opres=ASRV_Send(instance,clientsock,reply_header,strlen(reply_header),MSG_WAITALL|MSG_NOSIGNAL);  //Send filesize as soon as we've got it
     if (opres<=0) { fprintf(stderr,"Error sending cached header \n"); freeMallocIfNeeded(cached_buffer,free_cached_buffer_after_use); return 0; }

     opres=ASRV_Send(instance,clientsock,cached_buffer,cached_lSize,MSG_WAITALL|MSG_NOSIGNAL);  //Send file as soon as we've got it
     freeMallocIfNeeded(cached_buffer,free_cached_buffer_after_use);

     if (opres<=0) { fprintf(stderr,"Error sending cached body\n"); return 0; }
     return 1;
   }
     else
  {
    /*!Serve file by reading it from disk !*/

    if ((cached_buffer==0)&&(cached_lSize==1)) { /*TODO : Cache indicates that file doesn't exist */ } else
    if ((cached_buffer==0)&&(cached_lSize==0)) { /*TODO : Cache indicates that file is not in cache :P */ }


     if ( !TransmitFileToSocket(instance,clientsock,verified_filename,start_at_byte,end_at_byte) )
      {
         fprintf(stderr,"Could not transmit file %s \n",verified_filename);
         return 0;
      }
      return 1;
  }
  //
} //we also want a body with that header END
 else
{
  //We only served a header so lets append the last new line char..!
  send(clientsock,"\n",strlen("\n"),MSG_WAITALL|MSG_NOSIGNAL);
  return 1; //This does not mean we failed..! 2016-04-03
}


 return 0;
}
Example #3
0
int ServeClientKeepAliveLoop(struct AmmServer_Instance * instance,struct HTTPTransaction * transaction)
{
  //Remember the IP of this client..
  getSocketIPAddress(instance,transaction->clientSock,transaction->ipStr,MAX_IP_STRING_SIZE,&transaction->port);

   //We have our connection / instancing /etc covered if we are here
   //In order to serve our client we must first receive the request header , so we do it now..!
   int httpHeaderReceivedWithNoProblems = receiveAndHandleHTTPHeaderSentByClient(instance,transaction);

   if (!httpHeaderReceivedWithNoProblems)
   {
     if (transaction->clientDisconnected)
     {
      warningID(ASV_WARNING_CONNECTION_CLOSED_WHILE_KEEPALIVE);
     } else
     {
      /*We got a bad http request so we will rig it to make server emmit the 400 message*/
      errorID(ASV_ERROR_FAILED_TO_RECEIVE_HEADER);
      SendErrorFile(instance,transaction,400);
      logError(instance,transaction,400,"400.html");
     }
      return 0;
   }
      else
   if (!clientList_isClientAllowedToUseResource(instance->clientList,transaction->clientListID,transaction->incomingHeader.resource))
   {
     //Client is forbidden but he is not IP banned to use resource ( already opened too many connections or w/e other reason )
     //Doesnt have access to the specific file , etc..!
     warningID(ASV_WARNING_CLIENT_DENIED_ACCESS);
     SendErrorCodeHeader(instance,transaction,403 ,"403.html",instance->templates_root);
     logError(instance,transaction,403,"403.html");
     return 0;
   } else
   if ((instance->settings.PASSWORD_PROTECTION)&&(!transaction->incomingHeader.authorized))
   {
     errorID(ASV_ERROR_UNAUTHORIZED_REQUEST);
     respondToClientRequestingAuthorization(instance,transaction);
     return 0;
   }
     else
   { // Not a Bad request Start
      //This is a hack and should be probably be changed..!
      if ( ( transaction->incomingHeader.requestType==POST ) && (instance->settings.ENABLE_POST) && (MASTER_ENABLE_POST) )
       {

         fprintf(stderr,GREEN "POST HEADER : %lu length \n %s \n" NORMAL,transaction->incomingHeader.ContentLength,transaction->incomingHeader.headerRAW);
         //TODO ADD Here a possibly rfc1867 , HTTP POST FILE compatible (multipart/form-data) recv handler..
         //TODO TODO TODO

         fprintf(stderr,"Found a POST query %lu bytes long , %s \n",transaction->incomingHeader.POSTrequestSize, transaction->incomingHeader.POSTrequest);
         warningID(ASV_WARNING_PRETENDING_IT_IS_A_GET_REQUEST);
         //Will now pretend that we are a GET request for the rest of the page to be served nicely
         transaction->incomingHeader.requestType=GET;
       }


     if (
          (transaction->incomingHeader.requestType==GET)  ||
          (transaction->incomingHeader.requestType==HEAD) ||
          (transaction->incomingHeader.requestType==POST)
        )
     {
      char servefile[(MAX_FILE_PATH*2)+1]={0}; // Since we are strcat-ing the file on top of the webserver_root it is only logical to
      // reserve space for two MAX_FILE_PATHS they are a software security limitation ( system max_path is much larger ) so its not a problem anywhere..!
      int resource_is_a_directory=0,resource_is_a_file=0,resource_is_a_template=0,generate_directory_list=0;

      /*!
             PART 1 : Sense what we want to serve , and set the flags
             resource_is_a_directory , resource_is_a_file , generate_directory_list
             accordingly..!
      */

      decideAboutHowToHandleRequestedResource
            (
              instance,
              transaction,
              servefile,
              &resource_is_a_directory,
              &resource_is_a_file ,
              &resource_is_a_template ,
              &generate_directory_list
            );

      /*!
             PART 2 : The flags
             resource_is_a_directory , resource_is_a_file , generate_directory_list
             have been set to the correct ( :P ) value so all we have to do now is serve the correct repsonse..!
      */
     if (resource_is_a_template)
     {
         //We have a specific request for a file ( transaction->incomingHeader.resource )
         fprintf(stderr,"It is a template request for %s ..!\n",servefile);
             // ------------------------------------------------------
             // ------------------------------------------------------
             // ------------------------------------------------------
             if (
                  SendEmbeddedFile
                      (
                         instance,
                         transaction,
                         servefile  // -- Log What was asked to be served
                      )
                )
                {
                  logSuccess(instance,transaction,200,servefile);
                }
                 else
                {
                 //We where unable to serve request , closing connections..\n
                 errorID(ASV_ERROR_UNABLE_TO_SERVE_TEMPLATE);
                 return 0;
                }
             // ------------------------------------------------------
             // ------------------------------------------------------
             // ------------------------------------------------------
     }
       else
     if (generate_directory_list)
     {
       respondToClientBySendingAGeneratedDirectoryList(instance,transaction,servefile);
       return 0;
     }
        else
      if  (resource_is_a_file)
      {
         //We have a specific request for a file ( transaction->incomingHeader.resource )
         //fprintf(stderr,"It is a file request for %s ..!\n",servefile);
             // ------------------------------------------------------
             // ------------------------------------------------------
             // ------------------------------------------------------
             if (
                  SendFile
                      (
                         instance,
                         transaction,
                         servefile,  // -- Log What was asked to be served
                         0 // <- We dont want to force an error code!
                      )
                )
                {
                  logSuccess(instance,transaction,200,servefile);
                }
                 else
                {
                 //We where unable to serve request , closing connections..\n
                 errorID(ASV_ERROR_UNABLE_TO_SERVE_REQUEST);
                 return 0;
                }
             // ------------------------------------------------------
             // ------------------------------------------------------
             // ------------------------------------------------------
      }
       else
     {
        fprintf(stderr,"404 not found..!!\n");
        SendErrorFile(instance,transaction,404);
        logError(instance,transaction,404,servefile);
        return 0;
     }
   } else
     if (transaction->incomingHeader.requestType==BAD)
           { //In case some of the security features of the server sensed a BAD! request we should log it..
            warningID(ASV_WARNING_PREDATORY_REQUST);
            //TODO : call -> int ErrorLogAppend(char * IP,char * DateStr,char * Request,unsigned int ResponseCode,unsigned long ResponseLength,char * Location,char * Useragent)
            SendErrorFile(instance,transaction,400);
            logError(instance,transaction,400,"400.html");
            return 0;
           } else
          if (transaction->incomingHeader.requestType==NONE)
           { //We couldnt find a request type so it is a weird input that doesn't seem to be HTTP based
            warningID(ASV_WARNING_UNRECOGNIZED_REQUEST);
            SendErrorFile(instance,transaction,400);
            logError(instance,transaction,400,"400.html");
            return 0;
           } else
           { //The request we got requires not implemented functionality , so we will admit not implementing it..! :P
            warningID(ASV_WARNING_NOTIMPLEMENTED_REQUEST);
            SendErrorFile(instance,transaction,501);
            logError(instance,transaction,501,"501.html");
            return 0;
           }
   } // Not a Bad request END

    clientList_signalClientStoppedUsingResource(instance->clientList,transaction->clientListID,transaction->incomingHeader.resource); // This in order for client_list to correctly track client behaviour..!


  if ( transaction->incomingHeader.headerRAW!=0 )
        {
          safeFree(transaction->incomingHeader.headerRAW,transaction->incomingHeader.headerRAWSize);
          transaction->incomingHeader.headerRAW=0;
        }
  //We are done with request!


  if (!transaction->incomingHeader.keepalive) { return 0; } // Close_connection controls the receive "keep-alive" loop
  //If we are on a keepalive streak , then go on !
  return 1;
}