C Webvserver – STDIN, sockets and CGI piping - c

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?

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.

Related

Named pipes for client-server simulation in 2 terminals

I know the title doesn't explain the problem precisely and I apologize.
I've been writing a program in C of named pipes and the goal of it is to open 2 windows of the terminal, each running either the "Server" or the "Client" file.
After the terminals connected to each other via one named pipe, the Client can then send a string to the Server, the server would create a thread that prints the string it had received, reverses it, and then send it back to the Client via another named pipe. Finally, the Client should print the message it had received back from the Server. The client should be able to keep sending strings until the file is exited or the string "exit" is sent.
What my issue is and what I think causes it:
Everything works fine when the user enters a single word in the string, but when it sends a sentence with spaces in it, the Client's fscanf, that is meant to read from "toClient" named pipe, slices the sentence and it can only receive one word at a time.
So then the next time the client sends a message, it will read the second word of the previous message because it was sliced out and stayed in the named pipe.
I tried to use a while loop to keep reading from "toClient" until all the individual words have been taken out and it works like I expected it to, but after the first message the connection hangs and the client can't send new messages. I think it hangs because the while loop doesn't reach EOF for some reason, maybe because both the Client and Server still have the named pipe open.
Server:
/*function that reverses a string*/
char* revstr(char *str1)
{
int i, len, temp;
len = strlen(str1);
for (i = 0; i < len/2; i++)
{
temp = str1[i];
str1[i] = str1[len - i - 1];
str1[len - i - 1] = temp;
}
return str1;
}
/*function that is run by a thread to handle a client's message*/
void *threadFunc(void *arg){
char *string = (char*)arg;
printf("Received from client: %s\n", string);//print message received to terminal
/*make a connection to "toClient" named pipe that in order to send back the reversed string*/
FILE *fdw;
if (!(fdw = fopen("toClient", "w"))) {
perror("cannot open fifo file for w") ;
exit(EXIT_FAILURE) ;
}
/*irrelevant*/
if(strcmp(string,"exit")==0)
{
fprintf(fdw, " Done\n") ;
fflush(fdw) ;
printf("Shutting down...\n");
exit(0) ;
}
char *string2 = revstr(string);//string2 is the reversed string
fprintf(fdw, " %s\n", string2) ;//send string2 into the named pipe labeled "toClient"
fflush(fdw) ;
printf("Sent message back to client...\n");
}
int main()
{
char s[STR_LEN];
FILE *fdr;
if (mkfifo("toServer", 0777) == -1 && errno != EEXIST) {
perror("cannot create fifo1 file") ;
exit(EXIT_FAILURE) ;
}
if (mkfifo("toClient", 0777) == -1 && errno != EEXIST) {
perror("cannot create fifo2 file") ;
exit(EXIT_FAILURE) ;
}
printf("Waiting for client...\n");
if (!(fdr = fopen("toServer", "r"))) {
perror("cannot open fifo file for r") ;
exit(EXIT_FAILURE) ;
}
printf("Client found, waiting for message...\n");
/*this block waits for a message from the client, then creates a thread to handle it*/
while ( fscanf(fdr, " %s", s) != EOF){
int retcode;
pthread_t t1;
retcode = pthread_create(&t1,NULL,&threadFunc,(void *)(s));
if(retcode!=0)
printf("Create thread failed with error %d\n", retcode);
pthread_join(t1,NULL);
}
printf("Client disconnected\n") ;
return EXIT_SUCCESS ;
}
Client:
{
char s[STR_LEN];
FILE *fdw;
FILE *fdr;
if (mkfifo("toServer", 0777) == -1 && errno != EEXIST) {
perror("cannot create fifo file") ;
exit(EXIT_FAILURE) ;
}
if (mknod("toClient", 0777,0) == -1 && errno != EEXIST) {
perror("cannot create fifo file") ;
exit(EXIT_FAILURE) ;
}
if (!(fdw = fopen("toServer", "w"))) {
perror("cannot open fifo file for w") ;
exit(EXIT_FAILURE) ;
}
puts("Connected to server, enter a message:\n");
/*the user now enters a message into the terminal*/
while ( fgets(s, STR_LEN, stdin) != NULL) {
printf("Sent: %s",s);//print the message to terminal
fprintf(fdw, " %s\n", s) ;//send the message into the named pipe labeled "toServer"
fflush(fdw) ;
/*connect to the server to receive it's response using the named pipe labeled "toClient"*/
if (!(fdr = fopen("toClient", "r"))) {
perror("cannot open fifo file for r") ;
exit(EXIT_FAILURE) ;
}
/*this is where my problem is - this block is essentially meant to read from the named pipe "toClient"*/
if/*while*/ ( fscanf(fdr, " %s", s) != EOF )
{
printf("Received from server: %s\n", s);//print the response received to the terminal
/*Irrelevant*/
if(strcmp(s,"Done")==0)
exit(0);
}
}
return EXIT_SUCCESS ;
}
Terminals when using if on fscanf:
Terminals when using while on fscanf:
I know this thread is really long and the problem is probably due to bad code design but I have no idea what to do after almost 8 hours of trying and being relatively inexperienced with the subject .(It's part of an OS course I'm taking in uni)
Well, this is how scanf with %s specifier works. If you want to read whole line using scanf then use %[^\n]s, which means - read until newline character. Anyways it would be better to use fgets.

C socket: FD_ISSET return true with no data waiting

As a school project, I'm implementing a IRC server and I've been stuck on this one problem for the day.
My server use select to choose which client is sending data, it then read one command from this user (commands are \r\n separated), parse it and execute it before passing to the next user.
A user can send multiple command at once like so :
"command1\r\ncommand2\r\n"
If this happen, i want the first command to be executed and the second to stay in the stream so that it can be read at the next select() call. (this way, each user only execute one comamnd per "turn").
To do this, I have a FILE *stream per client that stay open as long as it is connected.
This work perfectly (if I send the double comamnd specified above, the two commands are executed one after the other).
The problem is after that after that, select() continue to tell me that there is something to read in the socket (FD_ISSET of the fd return 1), so my receive function is called for that fd and the getline() in it fail (without setting errno) and it loop like this forever.
I don't understand why the select still consider that there is something to read in the socket and why the getline is failing instead of blocking.
Any idea ?
edit: I'm not allowed to use non blocking socket or fork() for this project
The "main" loop:
while (g_run_server)
{
fd_max = g_socket_fd;
FD_ZERO(fds);
FD_SET(g_socket_fd, fds);
tmp = users;
while (tmp)
{
FD_SET(tmp->fd, fds);
fd_max = (tmp->fd > fd_max ? tmp->fd : fd_max);
tmp = tmp->next;
}
if (select(fd_max + 1, &fds, NULL, NULL, NULL) < 0)
break;
if (FD_ISSET(g_socket_fd, &fds))
accept_new_user(&hdl, &users);
handle_clients(&hdl, &fds);
}
The functions to handle and read client input :
static bool recv_and_execute(t_handle *hdl)
{
char *raw;
size_t len;
len = 0;
raw = NULL;
if (!hdl->sender->stream &&
(hdl->sender->stream = fdopen(dup(hdl->sender->fd), "r")) == NULL)
return (false);
if (getline(&raw, &len, hdl->sender->stream) != -1)
{
printf("Received \"%s\"\n", raw);
parse_cmd(hdl, raw);
exec_cmd(hdl);
}
else
printf("Getline failed %s\n", strerror(errno));
free(raw);
return (true);
}
int handle_clients(t_handle *hdl, fd_set *fds)
{
t_user *tmp;
tmp = *hdl->users;
while (tmp)
{
if (FD_ISSET(tmp->fd, fds))
{
printf("fd %d is ready to be read\n", tmp->fd);
hdl->sender = tmp;
recv_and_execute(hdl);
FD_CLR(tmp->fd, fds);
tmp = tmp->next;
if (hdl->sender->status == DEAD)
del_user(hdl->users, hdl->sender);
}
else
tmp = tmp->next;
}
return (0);
}
And this is the output when I connect one client and send "USER foo\r\nUSER no bo dy :wa\r\n" :
fd 4 is ready to be read
Received "NICK foo
"
[DEBUG] Executing "NICK" with params "foo" "(null)" "(null)" "(null)"
[INFO] Nickname successfully changed.
fd 4 is ready to be read
Received "USER no bo dy :wa
"
[DEBUG] Executing "USER" with params "no" "bo" "dy" ":wa"
[INFO] User registered.
fd 4 is ready to be read
Getline failed Success
fd 4 is ready to be read
Getline failed Success
fd 4 is ready to be read
Getline failed Success
fd 4 is ready to be read
Getline failed Success
fd 4 is ready to be read
Getline failed Success
continue like this....
Edit : I edited my receive function based on the comment of alk:
static bool recv_and_execute(t_handle *hdl)
{
char *raw;
size_t len;
ssize_t nread;
len = 0;
raw = NULL;
if (!hdl->sender->stream &&
(hdl->sender->stream = fdopen(dup(hdl->sender->fd), "r")) == NULL)
return (false);
errno = 0;
if ((nread = getline(&raw, &len, hdl->sender->stream)) > 0)
{
printf("Received \"%s\"\n", raw);
parse_cmd(hdl, raw);
exec_cmd(hdl);
}
else if (errno != 0)
printf("getline failed %s\n", strerror(errno));
else {
printf("EOF reached\n");
fclose(hdl->sender->stream);
hdl->sender->stream = NULL;
}
printf("nread = %zd\n", nread);
free(raw);
return (true);
}
So this time, when EOF is reach (getline return -1), I close the stream and set it to NULL to be reopened the next time select find data on the socket fd. But even when I close the stream, select still detect that there is data available and the inifinite loop continue :/
fd 4 is ready to be read
Received "NICK foo^M
"
nread = 10
fd 4 is ready to be read
Received "USER no bo dy :wa^M
"
nread = 19
fd 4 is ready to be read
EOF reached
nread = -1
fd 4 is ready to be read
EOF reached
nread = -1
fd 4 is ready to be read
EOF reached
nread = -1
and so on...
I'm pretty sure you're using the select wrong. I show you a simple code example on how to use it (I don't handle many errors) and you can edit it as you need.
/*
* If you read at man select bugs you can see
* that select could return that someone is
* ready but it isn't true
*/
int fd_c;
fd_set rdset;
fd_set set; /*That's the mask you'll use when new requests arrive*/
FD_ZERO(&set); /*Clears the mask*/
FD_SET(g_socket_fd,&set); /*Set the listening socket as ready*/
while(g_run_server){
/*
* YOU MUST INITIALIZATE IT EVERY LOOP
* read # man select
*/
rdset = set;
if(select((fd_num_max+1),(&rdset),NULL,NULL,NULL) < 0){
perror("Failed on select\n");
exit(EXIT_FAILURE);
}
/*You go through the ready clients in the rdset*/
for(fd=0;fd<=fd_num_max;fd++) {
if(FD_ISSET(fd,&rdset)) { /*If the bit is set*/
if(fd == fd_skt) { /*If it's a new client*/
/*You can handle the new client here or delegete it to someone*/
fd_c=accept(fd_skt,NULL,0); /*File descriptor of new client*/
FD_SET(fd_c,&set);
if(fd_c > fd_num_max) fd_num_max = fd_c;
}else { /*If it's a request from an existing client*/
/*You can handle the new request here or delegete it to someone*/
FD_SET(fd,&set);
}
}
}
}
You should also modify static bool recv_and_execute(t_handle *hdl) that way:
errno = 0;
if ((nread = getline(&raw, &len, hdl->sender->stream)) != -1){
printf("Received \"%s\"\n", raw);
parse_cmd(hdl, raw);
exec_cmd(hdl);
}else{
if( errno == 0){
printf("EOF reached\n");
fclose(hdl->sender->stream);
hdl->sender->stream = NULL;
}else{
printf("getline failed %s\n", strerror(errno));
exit(EXIT_FAILURE); /*You must handle it in some way, exiting or doing something*/
}
}

Multi-Process/Multi-Threaded C Server reads null-string from socket

I'm working on a Server in C using a Multi-Process / Multi-Threaded architecture. At the start I've a main process that forks 10 different processes. Each process creates a pool of threads. Each process is, also, the controller of its pool (creates new thread / extends the pool when needed).
The processes also listen on the socket (whose access is controlled by the main process with an unnamed semaphore in a shared memory area) and pass the socket file descriptor to one of the thread in their pool when a connection is acquired. The chosen thread (which is awakened by a pthread_cond_signal) reads from the socket with a recv using the option MSG_DONTWAIT.
ssize_t readn, writen;
size_t nleft;
char *buff;
buff = malloc(sizeof(char) * BUFF_SIZE);
if (buff == NULL) {
perror("malloc");
return EXIT_FAILURE;
}
char *ptr = buff;
/*if (fcntl(connsd, F_SETFL, O_NONBLOCK) == -1) { // set to non-blocking
perror("fcntl");
exit(EXIT_FAILURE);
}*/
errno = 0;
nleft = BUFF_SIZE;
while(nleft > 0) {
if ((readn = recv(connsd, ptr, nleft, MSG_DONTWAIT)) < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
*ptr = '\0';
break;
}
else if (errno == EINTR)
readn = 0;
else {
perror("read");
return EXIT_FAILURE;
}
}
else if (readn == 0)
break;
if (buff[strlen(buff)-1] == '\0') {
break;
}
nleft -= readn;
ptr += readn;
}
The problem is that sometimes, when I try to connect to the server using Chrome (or Firefox) 3 threads seems to receive the HTTP Request but each one of them simply closes the connection because of this portion of code.
if (buff[strlen(buff)-1] != '\0') {
printf("%s\n",buff);
fflush(stdout);
buff[strlen(buff)-1] = '\0';
}
errno = 0;
if (strlen(buff) < 1) {
perror("No string");
if (shutdown(connsd,SHUT_RDWR) < 0) {
perror("shutdown");
return EXIT_FAILURE;
}
if (close(connsd) < 0) {
perror("close");
return EXIT_FAILURE;
}
return EXIT_FAILURE;
}
Other times we have 3 threads with different behaviours: one of them receives and reads from the socket the first HTTP Request (GET / HTTP/1.1). The second one is empty (request received (because it is awakened (?)) no string read). The third one receives and reads another HTTP Request (GET /favicon.ico HTTP/1.1).
Where is the problem that cause these behaviours? I can add other portions of code if needed.
Thank you very much for your time.

Sending files with socket : 1 sec sleep or nanosleep needed

i'm writing a client/server application in C and i've noticed a trouble that is confusing me a little bit.
If i send a single file everything works fine,but when i try to send every file in the current working directory client blocks ( on a read system call i think ).
I've tried to write a sleep(1) instruction and it works.
Here is the code of the function that sends all the files.
while((p =readdir(d))){
/* We DO NOT want to transfer these files */
if(!strcmp(p->d_name,".") || !strcmp(p->d_name,"..") || !strcmp(p->d_name,".DS_Store") || !strcmp(p->d_name,"clientS") || !strcmp(p->d_name,"clientS.c"))
continue;
if(stat(p->d_name,&file_stat) < 0){
perror("Stat error");
}
if(S_ISREG(file_stat.st_mode)){
//printf("sending : %s\n ",p->d_name);
send_file(p->d_name,Socket_d);
}
sleep(1);
}
I'm testing with 127.0.0.1 IP address (localhost)
send_file function code :
byte_left = file_stat.st_size;
/* while there are bytes to send loop */
while(byte_left > 0){
memset(sendbuf,0,sizeof(sendbuf));
/*read data from file*/
nread = fread(sendbuf,1,sizeof(sendbuf),filepointer);
sendbuf[nread] = '\0';
if(write(sock_fd,sendbuf,sizeof(sendbuf)) < 0){
perror("Writing error");
return -1;
}
byte_left -= nread;
}
close(fd);

C Sending files with Berkeley socket

I'm trying to send files on a Client-Server application written in c.
Client should upload files and server should receive files.
Client side :
if(fstat(fd,&file_stat) < 0){
perror("Fstat error");
return -1;
}
FILE *filepointer = fopen(filename,"rb");
if(filepointer == NULL){
perror("Opening file error");
return -1;
}
strcat(sendbuf,"8"); // option number
strcat(sendbuf,client_account.user); //user who is sending a file
strcat(sendbuf,"p1.txt"); // file name
printf("sendbuf : %s\n",sendbuf );
if(write(sock_fd,sendbuf,strlen(sendbuf)) < 0 ){
perror("Writing error");
return -1;
}
/* Check server's answer */
if((nread = read(sock_fd,buffer,sizeof(buffer))) < 0){
perror("Reading error");
return -1;
}
buffer[nread] = '\0';
printf("Buffer : %s : %d",buffer,atoi(buffer));
if(atoi(buffer) != GENERIC_OK){
printf("Error occurred\n");
return -1;
}
sprintf(file_size,"%lld",file_stat.st_size);
/* Writing file size */
if((nwritten = write(sock_fd,file_size,sizeof(file_size))) < 0){
perror("Writing error");
return -1;
}
memset(buffer,0,sizeof(buffer));
/* Check second server's answer */
if((nread = read(sock_fd,buffer,sizeof(buffer))) < 0){
perror("Reading error");
return -1;
}
buffer[nread] = '\0';
printf("Buffer : %s : %d",buffer,atoi(buffer));
if(atoi(buffer) != GENERIC_OK){
printf("Error occurred\n");
return -1;
}
while(1){
nbytes = fread(sendbuf,1,sizeof(sendbuf),filepointer);
/* There are bytes to send */
printf("Sendbuf : %s \n" , sendbuf);
if(nbytes > 0){
write(sock_fd,sendbuf,nbytes);
}
if(nbytes < 256){
if(feof(filepointer) || ferror(filepointer))
break;
}
}
Server side :
... first buffer is received well ...`
/* WRITE TO CLIENT TO CONTINUE */
if(write(sock_fd,"500",strlen("500")) < 0){ /*GENERIC OK*/
perror("Writing error");
return -1;
}
memset(recvBuf,0,sizeof(recvBuf));
/*RECEIVING FILE SIZE */
if((nread = read(sock_fd,buffer,sizeof(buffer)))< 0){
perror("Reading error");
return -1;
}
buffer[nread] = '\0';
file_size = atoi(buffer);
printf("file size : %d\n",file_size);
if((fd = open(filename, O_CREAT | O_RDWR | O_TRUNC, S_IRWXU)) < 0){
perror("File opening error"); /* file already exists */
}
recv_file = fopen(filename,"wb");
if(recv_file == NULL){
perror("Opening error");
return -1;
}
remaining_data = file_size;/*i think processes are blocking on while loops*/
while(((nread = read(sock_fd,recvBuf,sizeof(recvBuf))) > 0) && remaining_data > 0){
recvBuf[nread] = '\0';
printf("%d bytes received : %s \n",nread,recvBuf);
fwrite(recvBuf,1,nread,recv_file);
remaining_data -= nread;
}
if(nread < 0){
perror("Reading error");
}
I've tried to use sendfile function but I'm on Mac OS and it's not supported. Have you got any advice for me?
It should go like this :
1) client sends a buffer to server announcing it's going go send a file with its name - OK
2) server receives this buffer and sends a generic ok code to client - OK
3) client sends the size of the file to the server - OK
4) server receives this buffer and sends a generic ok code to client - OK
5) client read stuff from file and sends it to the server - NOT OK
6) server receives stuff from the client and writes stuff into the file - NOT OK
This line, in the server,
while(((nread = read(sock_fd,recvBuf,sizeof(recvBuf))) > 0) && remaining_data > 0){
is trying to read first, without having checked that remaining_data >0. The result is a read operation after all the file is transfered.
Suggest using a while(remaining_data>0) { select()/read() }, where the select() has a reasonable timeout parameter and causes an exit of the loop when a timeout occurs.
this code:
if((fd = open(filename, O_CREAT | O_RDWR | O_TRUNC, S_IRWXU)) < 0){
perror("File opening error"); /* file already exists */
}
recv_file = fopen(filename,"wb");
if(recv_file == NULL){
perror("Opening error");
return -1;
}
is opening the output file, in the server, twice, without an intervening close(). This is probably not what is needed.
I think you have left out some of the relevant code, but very likely the key problem is in the server's while condition:
while(((nread = read(sock_fd,recvBuf,sizeof(recvBuf))) > 0) && remaining_data > 0){
Note that it will always attempt to read data, even if it has already read the expected number of bytes. If the client holds the connection open (maybe it's waiting for a response from the server) then no EOF condition will be detected, and you have a deadlock.
Swapping the order of the conditions should resolve that problem, at least for well-behaved clients. Note, however, that even with that change, you could still get a deadlock if the client sends fewer bytes than it promises, yet holds the connection open. A malicious client could intentionally exhibit that behavior to perform a DoS against your server.
Also, 'read(sock_fd,buffer,sizeof(buffer)', followed by 'buffer[nread] = '\0';' will overflow the buffer by 1 if the the full 'sizeof(buffer)' bytes are received.
'file_size = atoi(buffer)' - not guaranteed to work since you don't know if the whole buffer has been received.
Also endianness issue with same.

Resources