Disassemble HTTP Response (C) - c

In continuation of this question I ask the following question.
I've managed to read and understand what D.Shawley has recommended and I already finished a lot of work. But a problem occured to me again. I've managed to get the Content-Length of the page out of the HTTP response, but now I'm experiencing some problems with getting the length of the HTTP response header. As I understand from the RFC 2616 document, a response header can be preceded with several CRLFs and must be followed by one or more CRLFs. Now I'm heaving trouble with determining the end of the response header.
I've came up with the following files:
IEFDowner.h
/*
* IEFDowner.h
* Downer
*
* Created by ief2 on 2/08/10.
* Copyright 2010 ief2. All rights reserved.
*
* http://developerief2.site11.com
*
*/
#include <stdio.h> /* snprintf() */
#include <sys/socket.h> /* SOCKET */
#include <netdb.h> /* struct addrinfo */
#include <stdlib.h> /* exit() */
#include <string.h> /* memset(), strlen(), strcpy(), strstr() */
#include <errno.h> /* errno */
#include <unistd.h> /* close() */
enum _IEFDownerErrorType {
IEFNoError,
IEFGAIError, /* Use gai_strerror() to get the error message */
IEFConnectError, /* Failed to connect with the socket */
IEFSendError, /* Use strerror() to get the error message */
IEFReceiveError /* Error with recv() */
};
typedef enum _IEFDownerErrorType IEFDownerErrorType;
/*
Requests the data from the host at the given path thru the given protocol.
PAREMTERS
- host
The host to connect to.
- filepath
The path to the file which to download from the host.
- buffer
A buffer to fill with the received data
- maxSize
The maximum size of the buffer
- receivedBytes
The amount of received bytes
- errorType
The type of error received. See the header file for more info (enum _IEFDownerErrorType)
RETURN VALUE
The function returns 0 if it succeeded or another code if it failed.
*/
int
IEFDownerHTTPDownload(const char *host,
const char *filepath,
void *buffer,
unsigned long maxSize,
long *receivedBytes,
IEFDownerErrorType *errorType);
/*
Returns a pointer to a structure containing the IP4 or IP6 addresss.
PARAMETERS
- sa
A pointer to a structure of sockaddr.
RETURN VALUE
Returns a pointer to a structure of the type sockaddr_in or the type sockaddr_in6.
*/
void *
IEFDownerGetInAddr(struct sockaddr *sa);
/*
Gets the content-length information out of an HTTP response header.
PARAMETERS
- httpHeader
The null terminated response.
- contentLength
Upon return it contains the content length.
RETURN VALUE
The function returns 0 if it succeeded or -1 if it did not.
*/
int
IEFDownerGetContentLengthOfPage(const char *httpHeader,
int *contentLength);
/*
Gets the string lenth of the header information
PARAMETERS
- received
The received header
- headerSize
Upon return contains the header length
RETURN VALUE
The function returns 0 if it succeeded. If there was no header information found, it returns -1.
DISCUSSION
The header size includes the trailing CRLF's behind the header data.
All empty CRLF's are included until a non-empty line is met.
If there're zero empty lines after the first non empty line, the header information was not found.
*/
int
IEFDownerGetSizeOfHeader(const char *received,
int *headerSize);
IEFDowner.c
/*
* IEFDowner.c
* Downer
*
* Created by ief2 on 2/08/10.
* Copyright 2010 ief2. All rights reserved.
*
* http://developerief2.site11.com
*
*/
#include "IEFDowner.h"
int
IEFDownerHTTPDownload(const char *host, const char *filepath, void *buffer, unsigned long maxSize, long *rB, IEFDownerErrorType *errorType) {
int status; // contains returned statuses
int sockfd; // the socket file descriptor
struct addrinfo *infos; // linked list
struct addrinfo hints; // hints to getaddrinfo()
struct addrinfo *p; // used in the loop to get valid addrinfo
long receivedBytes; // the received bytes
unsigned requestLength; // the length of the request
char *request; // the http request
// GET ADDRESS INFO
// fill hints
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
hints.ai_family = AF_UNSPEC;
// getaddrinfo
status = getaddrinfo(host,
"80",
&hints,
&infos);
if(status != 0) {
if(errorType) *errorType = IEFGAIError;
return status;
}
// FIND FIRST VALID SOCKET
for(p = infos; p != NULL; p = p->ai_next) {
// create
sockfd = socket(p->ai_family,
p->ai_socktype,
p->ai_protocol);
if(sockfd == -1)
continue;
// try to connect
status = connect(sockfd,
p->ai_addr,
p->ai_addrlen);
if(status == -1) {
close(sockfd);
continue;
}
break;
}
// no valid socket found
if(p == NULL) {
if(errorType) *errorType = IEFConnectError;
return status;
}
// GET RID OF INFOS
freeaddrinfo(infos);
// SEND HTTP REQUEST
// calculate request length and make
requestLength = strlen("GET ") + strlen(filepath) + strlen(" HTTP\1.0\n") + strlen("Host: ") + strlen(host) + strlen(":80\n\n") + 1;
request = malloc(sizeof(char) * requestLength);
snprintf(request, requestLength, "GET %s HTTP\1.0\nHost: %s:80\n\n", filepath, host);
// send
status = send(sockfd,
request,
requestLength - 1,
0);
if(status == -1) {
if(errorType) *errorType = IEFSendError;
close(sockfd);
return errno;
}
// RECEIVE DATA
receivedBytes = recv(sockfd,
buffer,
maxSize,
0);
if(receivedBytes == -1 || receivedBytes == 0) {
if(errorType) *errorType = IEFReceiveError;
close(sockfd);
return receivedBytes;
}
// SET POINTERS
if(errorType) *errorType = IEFNoError;
if(rB) *rB = receivedBytes;
return 0;
}
void *
IEFDownerGetInAddr(struct sockaddr *sa) {
// IP4
if(sa->sa_family == AF_INET)
return &(((struct sockaddr_in *) sa)->sin_addr);
// IP6
return &(((struct sockaddr_in6 *) sa)->sin6_addr);
}
int
IEFDownerGetContentLengthOfPage(const char *httpHeader, int *contentLength) {
register int i;
int length;
char *next;
char *completeHeader;
char *header;
char *fieldName;
// MAKE HEADER MUTABLE
completeHeader = malloc(sizeof(char) * (strlen(httpHeader) + 1));
strcpy(completeHeader, httpHeader);
header = completeHeader;
// SEARCH FOR LINE
// loop for all lines
next = header;
do {
header = next;
// replace newline
next = strstr(header, "\n");
if(next == NULL) {
free(header);
return -1;
}
*next = '\0';
for(i = 0; i != strlen("\n"); i++)
next++;
} while (strcasestr(header, "Content-Length:") == NULL);
// SCAN INTEGER
fieldName = strcasestr(header, "Content-Length:");
for(i = 0; i != strlen("Content-Length:"); i++)
fieldName++;
sscanf(fieldName, "%d", &length);
if(contentLength) *contentLength = length;
free(completeHeader);
return 0;
}
int
IEFDownerGetSizeOfHeader(const char *received, int *headerSize) {
int length;
int receivedLength;
char *next;
char *completeHeader;
char *header;
char *checkChar;
int firstNonEmptyLineFound;
int emptiesFound;
// MAKE HEADER MUTABLE
completeHeader = malloc(sizeof(char) * (strlen(received) + 1));
strcpy(completeHeader, received);
header = completeHeader;
// SEARCH FOR FIRST NON EMPTY LINE
receivedLength = strlen(header);
firstNonEmptyLineFound = 0;
for(next = header; *next != (char)NULL; next++) {
printf("%c",*next);
if(*next != '\n' && *next != '\r') {
firstNonEmptyLineFound = 1;
printf("\nFirst Non Empty Found\n\n");
next++;
break;
}
}
if(firstNonEmptyLineFound == 0) {
free(completeHeader);
return -1;
}
// SEARCH FOR FIRST EMPTY LINE
emptiesFound = 0;
for(; *next != (char)NULL; next++) {
checkChar = next;
printf("%c", *checkChar);
if(*checkChar == '\n' || *checkChar == '\r') {
checkChar++;
printf("%c", *checkChar);
if(*checkChar == '\n' || *checkChar == '\r') {
emptiesFound = 1;
printf("Empty Line Found\n\n");
break;
}
}
}
if(emptiesFound == 0) {
free(completeHeader);
return -1;
}
// GET END OF HEADER
for(; *next != (char)NULL; next++) {
printf("%c", *next);
if(*next != '\n' && *next != '\r') {
printf("End of header found");
break;
}
}
// INSERT NULL
*next == '\0';
length = strlen(header);
if(headerSize) *headerSize = length;
free(completeHeader);
return 0;
}
main.c
#include <stdio.h>
#include <stdarg.h>
#include "IEFDowner.h"
#define SERVERNAME "developerief2.site11.com"
#define PROTOCOL "80"
#define FILENAME "http://developerief2.site11.com/welcome/welcome.php"
#define MAXHEADERSIZE (1024*1024)
#define DESTFILE "/Users/ief2/Desktop/file.png"
void errorOut(int status, const char *format, ...);
int main (int argc, const char * argv[]) {
int status; // return status
void *headerData; // header data
int headerSize; // size of header
long rB; // received bytes
int pageSize; // size of bytes of page
// GET PAGE SIZE
// get data
headerData = malloc(1024);
status = IEFDownerHTTPDownload(SERVERNAME,
FILENAME,
(void *)headerData,
1024 - 1,
&rB, NULL);
if(status != 0)
errorOut(status, "An error occured while downloading header info\n");
// null terminate data
((char *)headerData)[rB] = '\0';
// get size
status = IEFDownerGetContentLengthOfPage((const char *)headerData, &pageSize);
if(status != 0)
errorOut(status, "An error occured while retreiving page size\n");
printf("Page Size: %d\n", pageSize);
printf("---------------------\n%s\n---------------------\n", headerData);
// CALCULATE HEADER SIZE
status = IEFDownerGetSizeOfHeader(headerData, &headerSize);
if(status != 0)
errorOut(status, "An error occured while getting the header size\n");
printf("Header Size: %d\n", headerSize);
return 0;
}
void errorOut(int status, const char *format, ...) {
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
exit(status);
}
The result I'm getting is not correct. I'm getting the end of the response after the first line already:
[Session started at 2010-08-03 21:32:47 +0000.]
Page Size: 3600
---------------------
HTTP/1.1 200 OK
Date: Tue, 03 Aug 2010 19:32:44 GMT
Server: Apache
X-Powered-By: PHP/5.2.11
Content-Length: 3600
Connection: close
Content-Type: text/html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xml:lang="en">
<!-- HEAD -->
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-16" />
<meta name="description" content="A site containing quality freeware for Mac OS X Tiger and newer. All applications are made by a 14 year old developer who masters AppleScript Studio, Objective-C, HTML, PHP, CSS and JavaScript." />
<meta name="keywords" content="free, freeware, mac os x, 10, tiger, leopard, mac os x, young, developer, freeware, AppleScript, Studio, Xcode, Objective-C, Cocoa" />
<title>Free OSX Software :: Welcome</title>
<!-- Shared Styles -->
<link rel="stylesheet" href="../site-style/styles.css" type="text/css" media="screen" title="no title" charset="utf-8" />
</head>
<!-- PAGE CONTENTS -->
<body>
---------------------
H
First Non Empty Found
TTP/1.1 200 OK
Empty Line Found
DEnd of header foundHeader Size: 1023
The Debugger has exited with status 0.
I hope somebody can help me,
ief2

It seems I have to read things better people write. Due to the previous question I should have already known that the new line in the HTTP responses are two characters: Carriage Return and a Line Feed ("\r\n"), so I should do a string compare.
New Code
int
IEFDownerGetSizeOfHeader(const char *received, int *headerSize) {
int length;
int receivedLength;
char *next;
char *completeHeader;
char *header;
char *checkChar;
int firstNonEmptyLineFound;
int emptiesFound;
// MAKE HEADER MUTABLE
completeHeader = malloc(sizeof(char) * (strlen(received) + 1));
strcpy(completeHeader, received);
header = completeHeader;
// SEARCH FOR FIRST NON EMPTY LINE
receivedLength = strlen(header);
firstNonEmptyLineFound = 0;
for(next = header; *next != (char)NULL; next++) {
if(strncmp(next, "\r\n", 2) != 0) {
firstNonEmptyLineFound = 1;
next++;
break;
}
}
if(firstNonEmptyLineFound == 0) {
free(completeHeader);
return -1;
}
// SEARCH FOR FIRST EMPTY LINE
emptiesFound = 0;
for(; *next != (char)NULL; next++) {
checkChar = next;
if(strncmp(checkChar, "\r\n", 2) == 0) {
checkChar++;
checkChar++;
if(strncmp(checkChar, "\r\n", 2) == 0) {
emptiesFound = 1;
next = checkChar;
break;
}
}
}
if(emptiesFound == 0) {
free(completeHeader);
return -1;
}
// GET END OF HEADER
for(; *next != (char)NULL; next++) {
if(strncmp(next, "\r\n", 2) != 0) {
break;
}
next++;
}
// INSERT NULL
*next = '\0';
length = strlen(header);
if(headerSize) *headerSize = length;
free(completeHeader);
return 0;
}
I hope this can be useful to some people,
ief2

Related

C : dereferencing pointer to a struct has wrong values [mqtt-c, pointers]

I suspect this is a basic issue, I just have a moment of dumbness.
I'm trying to make very simple mqtt client with MQTT-C library as a part of testing tool of my other solution.When I receive subscribed message I have weired issue : inside mqtt-c library (just before a call to a function pointer which happens to be mine "subsriber callback") everything seems ok, but right after a call to my function, dereferencing a pointer shows struct with completly wrong values inside. If I go one step up in callstack, so back to mqtt, the values inspector gives (set to dereference uint32_t value - precisely pointer's address), again, correct structure.
The structure, pointer to which mqtt passes to callback, is allocated on stack inside function in mqtt library. I don't think that thread could change, which would invalidate stack. I have also included mqtt.h, which means that my function should understand the structure.
I will paste some relevant code snippets, any help will be appreciated, including general advices.
ssize_t __mqtt_recv(struct mqtt_client *client)
{
struct mqtt_response response;
ssize_t mqtt_recv_ret = MQTT_OK;
MQTT_PAL_MUTEX_LOCK(&client->mutex);
/* read until there is nothing left to read, or there was an error */
while(mqtt_recv_ret == MQTT_OK) {
/* read in as many bytes as possible */
ssize_t rv, consumed;
struct mqtt_queued_message *msg = NULL;
rv = mqtt_pal_recvall(client->socketfd, client->recv_buffer.curr, client->recv_buffer.curr_sz, 0);
if (rv < 0) {
/* an error occurred */
client->error = (enum MQTTErrors)rv;
MQTT_PAL_MUTEX_UNLOCK(&client->mutex);
return rv;
} else {
client->recv_buffer.curr += rv;
client->recv_buffer.curr_sz -= (unsigned long)rv;
}
/* attempt to parse */
consumed = mqtt_unpack_response(&response, client->recv_buffer.mem_start, (size_t) (client->recv_buffer.curr - client->recv_buffer.mem_start));
if (consumed < 0) {
client->error = (enum MQTTErrors)consumed;
MQTT_PAL_MUTEX_UNLOCK(&client->mutex);
return consumed;
} else if (consumed == 0) {
/* if curr_sz is 0 then the buffer is too small to ever fit the message */
if (client->recv_buffer.curr_sz == 0) {
printf("receive buff sz is zero??\n");
client->error = MQTT_ERROR_RECV_BUFFER_TOO_SMALL;
MQTT_PAL_MUTEX_UNLOCK(&client->mutex);
return MQTT_ERROR_RECV_BUFFER_TOO_SMALL;
}
/* just need to wait for the rest of the data */
MQTT_PAL_MUTEX_UNLOCK(&client->mutex);
return MQTT_OK;
}
switch (response.fixed_header.control_type) {
//(...)
case MQTT_CONTROL_PUBLISH:
/* stage response, none if qos==0, PUBACK if qos==1, PUBREC if qos==2 */
if (response.decoded.publish.qos_level == 1) {
rv = __mqtt_puback(client, response.decoded.publish.packet_id);
if (rv != MQTT_OK) {
client->error = (enum MQTTErrors)rv;
mqtt_recv_ret = rv;
break;
}
} else if (response.decoded.publish.qos_level == 2) {
/* check if this is a duplicate */
if (mqtt_mq_find(&client->mq, MQTT_CONTROL_PUBREC, &response.decoded.publish.packet_id) != NULL) {
break;
}
rv = __mqtt_pubrec(client, response.decoded.publish.packet_id);
if (rv != MQTT_OK) {
client->error = (enum MQTTErrors)rv;
mqtt_recv_ret = rv;
break;
}
}
/* call publish callback */
printf("address: %d; size: %d\n", (uint32_t) &response.decoded.publish, response.decoded.publish.application_message_size);
//all ok here.
client->publish_response_callback(&client->publish_response_callback_state, &response.decoded.publish);
break;
//(...)
{
/* we've handled the response, now clean the buffer */
void* dest = (unsigned char*)client->recv_buffer.mem_start;
void* src = (unsigned char*)client->recv_buffer.mem_start + consumed;
size_t n = (size_t) (client->recv_buffer.curr - client->recv_buffer.mem_start - consumed);
memmove(dest, src, n);
client->recv_buffer.curr -= consumed;
client->recv_buffer.curr_sz += (unsigned long)consumed;
}
}
/* In case there was some error handling the (well formed) message, we end up here */
MQTT_PAL_MUTEX_UNLOCK(&client->mutex);
return mqtt_recv_ret;
}
struct mqtt_response {
/** #brief The mqtt_fixed_header of the deserialized packet. */
struct mqtt_fixed_header fixed_header;
/**
* #brief A union of the possible responses from the broker.
*
* #note The fixed_header contains the control type. This control type corresponds to the
* member of this union that should be accessed. For example if
* fixed_header#control_type == \c MQTT_CONTROL_PUBLISH then
* decoded#publish should be accessed.
*/
union {
struct mqtt_response_connack connack;
struct mqtt_response_publish publish;
struct mqtt_response_puback puback;
struct mqtt_response_pubrec pubrec;
struct mqtt_response_pubrel pubrel;
struct mqtt_response_pubcomp pubcomp;
struct mqtt_response_suback suback;
struct mqtt_response_unsuback unsuback;
struct mqtt_response_pingresp pingresp;
} decoded;
};
static void netReqHandler(void) {
int sockfd = myOpenSocket(ipaddr, port);//open_nb_socket(ipaddr, port);
mqtt_init(&client, sockfd, sendbuf, buffersizes, receivebuf, buffersizes, publish_callback);
printf("client error is %d\n", (&client)->error);
void publish_callback(void** unused, struct mqtt_response_publish *published) {
printf("app msg size : %d, addr: %d\n", published->application_message_size, (uint32_t) published);
char *nullTerminatedMessage = malloc(published->application_message_size + 1);
strncpy(nullTerminatedMessage, published->application_message, published->application_message_size);
nullTerminatedMessage[published->application_message_size] = '\0';

Remove Items from a struct array

So I'm implementing a TCP server chatroom in C and using poll to handle multiple clients. Users register with a username and password and then Login with it. When they login I want to add them to this struct array.
struct onlineUsers{
int sockfd;
char username[9]; //usernames size is maximum 8 characters
};
struct onlineUsers users[10];
//The login function is pretty long so is here the relevant part.
if(!searchExist(search, db)){
printf("Account doesnt exist.\n");
fclose(db);
free(search);
return -1;
}
else{
printf("Logged in\n");
// Add to onlineUser struct arr[]
}
But when the client closes the connection how would you remove that specific element from the onlineUsers array? I haven't implemented the way to add them since I don't really have a good way to remove them.
I reworked this into a minimal example and implemented a linear search for you (use lfind in remove_user() and lsearch in add_user() if available):
#include <assert.h>
#include <string.h>
#define MAX_NAME 8
#define MAX_USERS 2
struct user {
char name[MAX_NAME + 1];
};
unsigned find_pos(struct user *users, const char *name) {
unsigned empty = MAX_USERS;
for(unsigned i = 0; i < MAX_USERS; i++) {
if(empty == MAX_USERS && !users[i].name[0]) empty = i;
if(!strcmp(users[i].name, name)) return i;
}
return empty;
}
int add_user(struct user *users, const char *name) {
unsigned pos = find_pos(users, name);
if(pos == MAX_USERS) return -1;
if(users[pos].name[0]) return 1;
// safe as name[8] is 0 initialized; otherwise
// copy MAX_NAME + 1 then set name[MAX_NAME] = '\0'
// to ensure string is \0 terminated.
strncpy(users[pos].name, name, MAX_NAME);
return 0;
}
int remove_user(struct user *users, const char *name) {
int pos = find_pos(users, name);
if(!users[pos].name[0] || strcmp(users[pos].name, name)) return 1;
users[pos].name[0] = '\0';
return 0;
}
int main() {
struct user users[MAX_USERS] = { 0 };
assert(add_user(users, "bob") == 0); // ok (1 of 2)
assert(add_user(users, "bob") == 1); // duplicate
assert(remove_user(users, "bob") == 0); // ok (empty)
assert(remove_user(users, "jane") == 1); // non-existing
assert(add_user(users, "bob") == 0); // ok (1 of 2)
assert(add_user(users, "jane") == 0); // ok (2 of 2)
assert(add_user(users, "jack") == -1); // full
assert(remove_user(users, "bob") == 0); // ok
assert(find_pos(users, "jane") == 1); // ok (not empty slot)
}

C check if archive file is truncated with struct ar_hdr (ar.h)

I'm using the ar.h's structure : struct ar_hdr to retrieve informations inside my archive file (lib.a) using read to iterate over it, and i'm running into a little problem if the file is truncated.
In using the C language and when the file was truncated it currently makes me get a segmentation fault.
Is there any way to check if the file is truncated beforehand ? like by using stat or stuff like that ?
Thanks in advance
PS: be free to tell me if my question wasn't really understandable and clear
#define SIZE atoi(ar->ar_size)
struct ar_hdr *get_header(int fd)
{
struct ar_hdr *ar = (struct ar_hdr *)malloc(sizeof(struct ar_hdr));
if (read(fd, ar, sizeof(struct ar_hdr)) != sizeof(struct ar_hdr)) {
free(ar);
return NULL;
}
return ar;
}
int handle_ar_files(int fd, char *names[2], int ret)
{
struct ar_hdr *ar = NULL;
void *buf = NULL;
int index = 0;
while ((ar = get_header(fd)) != NULL) {
index = 0;
buf = malloc(SIZE);
if (ar->ar_name[0] == '/') {
my_free(ar, buf, (int [2]){fd, SIZE}, 1);
continue;
}
for (; ar->ar_name[index] && ar->ar_name[index] != '/'; index++);
ar->ar_name[index] = 0;
if ((read(fd, buf, SIZE)) != SIZE)
return my_free(ar, buf, (int [2]){fd, SIZE}, 0);
if ((parse_ar(buf, SIZE, (char *[2]){names[1], ar->ar_name})) == 84)
return my_free(ar, buf, (int [2]){fd, SIZE}, 0);
else
my_free(ar, buf, (int [2]){fd, SIZE}, 0);
}
return ret;
}
void *buf32 = mmap(NULL, st->st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (strncmp((char *)buf32, ARMAG, SARMAG) == 0) {
read(fd, tmp, SARMAG);
return handle_ar_files(fd, names, 0);
}
#ifndef _AR_H
#define _AR_H 1
#include <sys/cdefs.h>
/* Archive files start with the ARMAG identifying string. Then follows a
`struct ar_hdr', and as many bytes of member file data as its `ar_size'
member indicates, for each member file. */
#define ARMAG "!<arch>\n" /* String that begins an archive file. */
#define SARMAG 8 /* Size of that string. */
#define ARFMAG "`\n" /* String in ar_fmag at end of each header. */
__BEGIN_DECLS
struct ar_hdr
{
char ar_name[16]; /* Member file name, sometimes / terminated. */
char ar_date[12]; /* File date, decimal seconds since Epoch. */
char ar_uid[6], ar_gid[6]; /* User and group IDs, in ASCII decimal. */
char ar_mode[8]; /* File mode, in ASCII octal. */
char ar_size[10]; /* File size, in ASCII decimal. */
char ar_fmag[2]; /* Always contains ARFMAG. */
};
__END_DECLS
#endif /* ar.h */

recv all socket (until the message end symbol), C

I want to write a recvall function for socket in C. I assume, that every message in my protocol ends with \r\n. I wrote something like this below:
int recvall (int socket, char *buffer, int numBytes)
{
int bytesRcvd = 0;
numBytes = numBytes - 1;
while(bytesRcvd < numBytes)
{
int chunk = recv(socket, buffer + bytesRcvd, numBytes - bytesRcvd, 0);
if (chunk == 0 || chunk == -1)
break;
if(strstr(buffer, "\r\n"))
break;
bytesRcvd += (chunk);
}
buffer[bytesRcvd] = '\0';
return bytesRcvd;
}
But it shows me that it returns and reads 0 bytes. On the other hand, when I remove:
if(strstr(buffer, "\r\n"))
break;
it hangs. How to improve it?
One mistake here is that strstr expects a zero-terminated string. A fix:
buffer[chunk] = 0;
if(strstr(buffer, "\r\n"))
break;
However, there may be more data following "\r\n", and that data gets lost here.
A common design pattern for receiving data is:
Have a class that maintains a connection with send/receive buffers.
When it has received data into the receive buffer, it calls a message parsing callback passing buffer and buffer_size.
The message parsing callback consumes all available complete messages in the buffer and returns the number of bytes consumed (as you do).
The message parsing callback calls another callback, passing a complete message to it.
Something like this:
typedef struct
{
int socket;
// Message-parsing callback.
size_t(*on_recv_cb)(void*, void*, size_t);
void* on_recv_cb_data;
unsigned char recv_buffer[256 * 1024];
size_t recv_buffer_size; // Initialized with 0;
} Connection;
void Connection_on_socket_ready_for_read(Connection* self) {
// Assumes a non-blocking socket. Read once to avoid starvation of other sockets.
ssize_t received = read(self->socket, self->recv_buffer + self->recv_buffer_size, sizeof self->recv_buffer - self->recv_buffer_size);
if(received > 0) {
self->recv_buffer_size += received;
size_t consumed = self->on_recv_cb(self->on_recv_cb_data, self->recv_buffer, self->recv_buffer_size);
self->recv_buffer_size -= consumed;
memmove(self->on_recv_cb_data, self->recv_buffer + consumed, self->recv_buffer_size);
}
else if(received < 0) {
if(EAGAIN == errno)
return;
perror("error");
// Handle error.
}
else {
// Handle EOF.
}
}
typedef struct {
// Message callback.
void(*on_message_cb)(void*, char*, size_t);
void* on_message_cb_data;
} MessageParserCrLf;
size_t MessageParserCrLf_on_recv(void* cb_data, void* data, size_t data_size) {
MessageParserCrLf* self = cb_data;
char* message_begin = data;
char* message_end = data;
while(data_size - (message_end - (char*)data) >= 2) {
if(message_end[0] == '\r' && message_end[1] == '\n') {
message_end += 2;
self->on_message_cb(self->on_message_cb_data, message_begin, message_end - message_begin);
message_begin = message_end;
}
else {
++message_end;
}
}
return message_begin - (char*)data;
}
void on_message(void* cb_data, char* message, size_t message_size) {
(void)cb_data; // Unused here.
printf("on_message: %.*s\n", (int)message_size, message);
}
int main() {
MessageParserCrLf message_parser = {on_message, NULL};
Connection connection = {0, MessageParserCrLf_on_recv, &message_parser, {}, 0};
Connection_on_socket_ready_for_read(&connection);
}
Outputs:
[~/src/test]$ cat lines.txt
abc
def
end
[~/src/test]$ ./test < lines.txt
on_message: abc
on_message: def
on_message: end
If there is "\r\n" in the received buffer, the following line will break and end the while loop,
if(strstr(buffer, "\r\n")) break;
but bytesRcvd hasn't increased, so the function will return with bytesRcvd=0.
Try to put bytesRcvd increase before judging like this,
bytesRcvd += (chunk);
if(strstr(buffer, "\r\n"))
break;

WinDivert issue - redirecting DNS back to self on windows

I was looking at basil00's torwall, and for fun was trying to pare it down to just intercept DNS. (provide an answer back to myself of 127.0.0.1 for webfiltering purposes, learning project)
however, at this point, I have it hijacking the dns packet, but it does not return a correct address. for every "blocked" domain, it's different.
for example, I put cbc.ca in my hosts.deny file (blacklist), and it returns an address of 0.4.114.2
then blacklisting slashdot, it will return 0.4.0.1
this has been quite confusing and frustrating, and after three days of research, I am out of ideas.
Here is the code to the redirect portion of my program, which seems to be where things go awry.
(note some of the comments will be goofy as I was hacking down a program for a different purpose and haven't cleaned it up yet)
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "windivert.h"
#include "domain.h"
#include "main.h"
#include "redirect.h"
#define MAX_PACKET 4096
#define NUM_WORKERS 4
// DNS headers
#define DNS_MAX_NAME 254
struct dnshdr
{
uint16_t id;
uint16_t options;
uint16_t qdcount;
uint16_t ancount;
uint16_t nscount;
uint16_t arcount;
} __attribute__((__packed__));
struct dnsq
{
uint16_t type;
uint16_t class;
} __attribute__((__packed__));
struct dnsa
{
uint16_t name;
uint16_t type;
uint16_t class;
uint32_t ttl;
uint16_t length;
uint32_t addr;
} __attribute__((__packed__));
static DWORD redirect_worker(LPVOID arg);
static int handle_dns(HANDLE handle, PWINDIVERT_ADDRESS addr,
PWINDIVERT_IPHDR iphdr, PWINDIVERT_UDPHDR udphdr, char *data,
size_t data_len);
// State:
static bool redirect_on = false;
static HANDLE handle = INVALID_HANDLE_VALUE;
static HANDLE workers[NUM_WORKERS] = {NULL}; // Worker threads
// Send a packet asynchronously:
static void send_packet(HANDLE handle, void *packet, size_t packet_len,
PWINDIVERT_ADDRESS addr)
{
addr->Direction = WINDIVERT_DIRECTION_INBOUND;
WinDivertHelperCalcChecksums(packet, packet_len, 0);
if (!WinDivertSend(handle, packet, packet_len, addr, NULL))
debug("Send packet failed (err=%d)\n", (int)GetLastError());
}
// Start traffic redirect through Tor:
extern void redirect_start(void)
{
debug("DNS divert START\n");
if (handle != INVALID_HANDLE_VALUE)
return;
handle = WinDivertOpen(
"outbound and udp.DstPort == 53 or inbound and udp.DstPort = 53", 0, 0, 0);
// Launch threads:
redirect_on = true;
for (size_t i = 0; i < NUM_WORKERS; i++)
{
workers[i] = CreateThread(NULL, MAX_PACKET*3,
(LPTHREAD_START_ROUTINE)redirect_worker, (LPVOID)handle, 0, NULL);
if (workers[i] == NULL)
{
exit(EXIT_FAILURE);
}
}
}
// Stop traffic redirect through Tor:
extern void redirect_stop(void)
{
debug("DNS divert STOP\n");
if (handle == INVALID_HANDLE_VALUE)
return;
// Close the WinDivert handle; will cause the workers to exit.
redirect_on = false;
if (!WinDivertClose(handle))
{
exit(EXIT_FAILURE);
}
handle = INVALID_HANDLE_VALUE;
for (size_t i = 0; i < NUM_WORKERS; i++)
{
WaitForSingleObject(workers[i], INFINITE);
workers[i] = NULL;
}
}
// Redirect worker thread:
static DWORD redirect_worker(LPVOID arg)
{
HANDLE handle = (HANDLE)arg;
// Packet processing loop:
char packet[MAX_PACKET];
UINT packet_len;
WINDIVERT_ADDRESS addr;
while (redirect_on)
{
if (!WinDivertRecv(handle, packet, sizeof(packet), &addr, &packet_len))
{
// Silently ignore any error.
continue;
}
PWINDIVERT_IPHDR iphdr = NULL;
PWINDIVERT_TCPHDR tcphdr = NULL;
PWINDIVERT_UDPHDR udphdr = NULL;
PVOID data = NULL;
UINT data_len;
WinDivertHelperParsePacket(packet, packet_len, &iphdr, NULL, NULL,
NULL, &tcphdr, &udphdr, &data, &data_len);
int dnshandle = 0;
if (udphdr != NULL && ntohs(udphdr->DstPort) == 53)
dnshandle = handle_dns(handle, &addr, iphdr, udphdr, data, data_len);
if(dnshandle != 1)
{
if (!WinDivertSend(handle, packet, packet_len, &addr, NULL))
{
}
}
}
return 0;
}
// Handle DNS requests.
// NOTES:
// - If anything goes wrong, we simply drop the packet without error.
// - An alternative approach would be to let Tor resolve the address, however,
// this would be slow.
static int handle_dns(HANDLE handle, PWINDIVERT_ADDRESS addr,
PWINDIVERT_IPHDR iphdr, PWINDIVERT_UDPHDR udphdr, char *data,
size_t data_len)
{
struct dnshdr *dnshdr = (struct dnshdr *)data;
data += sizeof(struct dnshdr);
data_len -= sizeof(struct dnshdr);
char name[DNS_MAX_NAME + 8]; // 8 bytes extra.
size_t i = 0;
while (i < data_len && data[i] != 0)
{
size_t len = data[i];
if (i + len >= DNS_MAX_NAME)
return -1;
name[i++] = '.';
for (size_t j = 0; j < len; j++, i++)
name[i] = data[i];
}
name[i++] = '\0';
// Generate a fake IP address and associate it with this domain name:
uint32_t fake_addr = domain_lookup_addr(name);
if (fake_addr == 0)
{
// This domain is blocked; so ignore the request.
// Construct a query response:
size_t len = sizeof(struct dnshdr) + data_len + sizeof(struct dnsa);
if (len > 512) // Max DNS packet size.
return -1;
len += sizeof(WINDIVERT_IPHDR) + sizeof(WINDIVERT_UDPHDR) + len;
char buf[len + 8]; // 8 bytes extra.
PWINDIVERT_IPHDR riphdr = (PWINDIVERT_IPHDR)buf;
PWINDIVERT_UDPHDR rudphdr = (PWINDIVERT_UDPHDR)(riphdr + 1);
struct dnshdr *rdnshdr = (struct dnshdr *)(rudphdr + 1);
char *rdata = (char *)(rdnshdr + 1);
UINT local_ip;
DivertHelperParseIPv4Address("127.0.0.1",&local_ip);
memset(riphdr, 0, sizeof(WINDIVERT_IPHDR));
riphdr->Version = 4;
riphdr->HdrLength = sizeof(WINDIVERT_IPHDR) / sizeof(uint32_t);
riphdr->Length = htons(len);
riphdr->Id = htons(0xF00D);
WINDIVERT_IPHDR_SET_DF(riphdr, 1);
riphdr->TTL = 64;
riphdr->Protocol = IPPROTO_UDP;
riphdr->SrcAddr = iphdr->DstAddr;
riphdr->DstAddr = iphdr->SrcAddr;
memset(rudphdr, 0, sizeof(WINDIVERT_UDPHDR));
rudphdr->SrcPort = htons(53); // DNS
rudphdr->DstPort = udphdr->SrcPort;
rudphdr->Length = htons(len - sizeof(WINDIVERT_IPHDR));
rdnshdr->id = dnshdr->id;
rdnshdr->options = htons(0x8180); // Standard DNS response.
rdnshdr->qdcount = htons(0x0001);
rdnshdr->ancount = htons(0x0001);
rdnshdr->nscount = 0;
rdnshdr->arcount = 0;
memcpy(rdata, data, data_len);
struct dnsa *rdnsa = (struct dnsa *)(rdata + data_len);
rdnsa->name = htons(0xC00C);
rdnsa->type = htons(0x0001); // (A)
rdnsa->class = htons(0x0001); // (IN)
rdnsa->ttl = htonl(0x00000258) ; // 1 second
rdnsa->length = htons(0x0004);
rdnsa->addr = htonl(local_ip); // Fake address
send_packet(handle, &buf, len, addr);
debug("address: %u\n",addr->Direction);
debug("Intercept DNS %s\n", (name[0] == '.'? name+1: name));
return 1;
}
// Re-inject the matching packet.
/*
/
*/
return 0;
}
Here's the domain lookup side of it (mostly just hacked down to try to get the results I want:
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include "domain.h"
#include "main.h"
#define RATE_LIMIT 8000
#define rand16() \
(rand() & 0xFF) | ((rand() & 0xFF) << 8)
// Domain blacklist:
struct blacklist
{
size_t size;
size_t len;
char **names;
};
static struct blacklist *blacklist = NULL;
// State:
static struct name *names[UINT16_MAX] = {NULL};
static HANDLE names_lock = NULL;
// Prototypes:
static struct blacklist *domain_blacklist_read(const char *filename);
static bool domain_blacklist_lookup(struct blacklist *blacklist,
const char *name);
static int __cdecl domain_blacklist_compare_0(const void *x, const void *y);
static int domain_blacklist_compare(const char *name0, size_t len,
const char *name1);
// Initialize this module:
extern void domain_init(void)
{
// Load the domain blacklist.
blacklist = domain_blacklist_read("hosts.deny");
}
// Lookup an address given a domain name. If the name does not exist then
// create one.
extern uint32_t domain_lookup_addr(const char *name0)
{
if (name0[0] == '.')
name0++;
if (domain_blacklist_lookup(blacklist, name0))
{
debug("Block %s\n", name0);
return 0; // Blocked!
}
return;
}
// Read the blacklist file:
static struct blacklist *domain_blacklist_read(const char *filename)
{
struct blacklist *blacklist =
(struct blacklist *)malloc(sizeof(struct blacklist));
if (blacklist == NULL)
{
exit(EXIT_FAILURE);
}
blacklist->size = 0;
blacklist->len = 0;
blacklist->names = NULL;
FILE *stream = fopen(filename, "r");
if (stream == NULL)
{
return blacklist;
}
// Read blocked domains:
int c;
char buf[256];
while (true)
{
while (isspace(c = getc(stream)))
;
if (c == EOF)
break;
if (c == '#')
{
while ((c = getc(stream)) != '\n' && c != EOF)
;
continue;
}
size_t i = 0;
while (i < sizeof(buf)-1 && (c == '-' || c == '.' || isalnum(c)))
{
buf[i++] = c;
c = getc(stream);
}
if (i >= sizeof(buf)-1 || !isspace(c))
{
exit(EXIT_FAILURE);
}
buf[i] = '\0';
if (blacklist->len >= blacklist->size)
{
blacklist->size = (blacklist->size == 0? 32: 2 * blacklist->size);
blacklist->names = (char **)realloc(blacklist->names,
blacklist->size * sizeof(char *));
if (blacklist->names == NULL)
{
exit(EXIT_FAILURE);
}
}
size_t size = (i+1) * sizeof(char);
char *name = (char *)malloc(size);
if (name == NULL)
{
exit(EXIT_FAILURE);
}
for (size_t j = 0; j < i; j++)
name[j] = buf[i - 1 - j];
name[i] = '\0';
blacklist->names[blacklist->len++] = name;
}
fclose(stream);
qsort(blacklist->names, blacklist->len, sizeof(char *),
domain_blacklist_compare_0);
return blacklist;
}
// Check if a domain matches the blacklist or not:
static bool domain_blacklist_lookup(struct blacklist *blacklist,
const char *name)
{
if (blacklist->len == 0)
return false;
size_t len = strlen(name);
ssize_t lo = 0, hi = blacklist->len-1;
while (lo <= hi)
{
ssize_t mid = (lo + hi) / 2;
int cmp = domain_blacklist_compare(name, len, blacklist->names[mid]);
if (cmp > 0)
hi = mid-1;
else if (cmp < 0)
lo = mid+1;
else
return true;
}
return false;
}
// Domain compare function(s):
static int __cdecl domain_blacklist_compare_0(const void *x, const void *y)
{
const char *name0 = *(const char **)x;
const char *name1 = *(const char **)y;
return strcmp(name0, name1);
}
static int domain_blacklist_compare(const char *name0, size_t len,
const char *name1)
{
size_t i = 0;
ssize_t j = (ssize_t)len - 1;
for (; j >= 0 && name1[i] != '\0'; i++, j--)
{
int cmp = (int)name1[i] - (int)name0[j];
if (cmp != 0)
return cmp;
}
if (j < 0 && name1[i] != '\0')
return 1;
return 0;
}
any assistance is appreciated.
Also, I have uploaded the code to: Github
Thank you.
There's one of two things going on here. You're reading and writing DNS packets incorrectly, and or you're failing to convert the addresses to or from host to network order before working with them.
I'm going to bet on you reading and writing DNS packets incorrectly. I've implemented my own filtering systems using WinDivert where I was hijacking and passing all DNS traffic through my own local DNS server and I've seen these fudged addresses come out exactly as the results you're getting when I was parsing and writing DNS packets incorrectly.
The bad news is, I can't point you to a full DNS library in C/C++ as I know of none that actually make the job easier (maybe, see edits). I personally had my diversion code written in C++ with WinDivert and then use Arsoft.Tools.Net C# DNS library for actually running a local DNS server and manipulating DNS responses.
There is one project in C++ that calls itself boost::net::dns because I think the writer was hoping it would be part of boost, which it isn't and probably won't be. Avoid this library as the internal mechanism for parsing and storing multiple A Records is bugged out and broken and you'll get the same wacky results you're getting here. I tried to work with him to get it fixed but, he was only interested in blaming my code.
I'll have a gander again and update my answer if I can find a decent lib for working with DNS packets in C++. Don't try to do it yourself, we're talking about entire protocols with entire books of RFC's to do this properly yourself.
Update
As promised, here are some results from my search:
Mozilla Necko (Formerly Netlib)
http://mxr.mozilla.org/mozilla-central/source/netwerk/dns/
C-Ares:
https://github.com/bagder/c-ares
A list of alternative libraries C-Ares compiled:
http://c-ares.haxx.se/otherlibs.html
Also, it is linux specific but pretty clean code. You might be able to pilfer through libcrafter's DNS classes and move them to your own project.
https://github.com/pellegre/libcrafter/blob/master/libcrafter/crafter/Protocols/DNS.h
Again, unless your "learning project" is understanding and recreating one of the foundational pillars of the internet, don't implement this yourself. Try and use one of the previously listed libraries to handle your DNS packets.

Resources