How to retrieve issuer alternative name for ssl certificate by openssl - c

I can get the subject alternative name like
X509_NAME_get_text_by_NID(X509_get_subject_name(x), NID_subject_alt_name, hc->https_domain_name, 256)
With same method by changing 2. parameter to NID_issuer_alt_name I am expecting to get issuer name like;
X509_NAME_get_text_by_NID(X509_get_subject_name(x), NID_issuer_alt_name, hc->https_ca_name, 256);
But instead I am getting a empty string . How can I retrieve issuer alternative name correctly?

You could try the following solution, as recommended in https://github.com/iSECPartners/ssl-conservatory :
static HostnameValidationResult matches_subject_alternative_name (const char *hostname, const X509 *server_cert) {
HostnameValidationResult result = MatchNotFound;
int i;
int san_names_nb = -1;
STACK_OF(GENERAL_NAME) *san_names = NULL;
// Try to extract the names within the SAN extension from the certificate
san_names = X509_get_ext_d2i((X509 *) server_cert, NID_subject_alt_name, NULL, NULL);
if (san_names == NULL) {
return NoSANPresent;
}
san_names_nb = sk_GENERAL_NAME_num(san_names);
// Check each name within the extension
for (i=0; i<san_names_nb; i++) {
const GENERAL_NAME *current_name = sk_GENERAL_NAME_value(san_names, i);
if (current_name->type == GEN_DNS) {
// Current name is a DNS name, let's check it
char *dns_name = (char *) ASN1_STRING_data(current_name->d.dNSName);
// Make sure there isn't an embedded NUL character in the DNS name
if (ASN1_STRING_length(current_name->d.dNSName) != strlen(dns_name)) {
result = MalformedCertificate;
break;
}
else { // Compare expected hostname with the DNS name
if (strcasecmp(hostname, dns_name) == 0) {
result = MatchFound;
break;
}
}
}
}
sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free);
return result;
}
Hope it helps !

In your call to X509_NAME_get_text_by_NID with the NID_issuer_alt_name constant, I would have replaced X509_get_subject_name(x) by X509_get_issuer_name(x). I think this should do the trick you are after.

Related

C: How to access value returned by Net-SNMP GET

I apologize for the naive question, Iam new to Net-SNMP. I have tried using this simple SNMP demo app given in Net-SNMP website.
This code performs a SNMP-GET and manipulates the response to check if the value returned is a ASN_OCTET_STRING, and if yes, access the string using vars->val.string and assigned to a character pointer sp.
But Iam unable to figure out how to access this value if the type is anything other than ASN_OCTET_STRING. For example how do I take this value and, say, assign it to a variable if it is of type 'ASN_INTEGER' or 'ASN_OBJECT_ID'.
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <string.h>
#define DEMO_USE_SNMP_VERSION_3
#ifdef DEMO_USE_SNMP_VERSION_3
const char *our_v3_passphrase = "MD5Password";
#endif
int main(int argc, char ** argv)
{
netsnmp_session session, *ss;
netsnmp_pdu *pdu;
netsnmp_pdu *response;
oid anOID[MAX_OID_LEN];
size_t anOID_len;
netsnmp_variable_list *vars;
int status;
int count=1;
init_snmp("snmpdemoapp");
snmp_sess_init( &session );
session.peername = strdup("localhost:161");
#ifdef DEMO_USE_SNMP_VERSION_3
session.version=SNMP_VERSION_3;
session.securityName = strdup("user2");
session.securityNameLen = strlen(session.securityName);
session.securityLevel = SNMP_SEC_LEVEL_AUTHNOPRIV;
session.securityAuthProto = usmHMACMD5AuthProtocol;
session.securityAuthProtoLen = sizeof(usmHMACMD5AuthProtocol)/sizeof(oid);
session.securityAuthKeyLen = USM_AUTH_KU_LEN;
if (generate_Ku(session.securityAuthProto,
session.securityAuthProtoLen,
(u_char *) our_v3_passphrase, strlen(our_v3_passphrase),
session.securityAuthKey,
&session.securityAuthKeyLen) != SNMPERR_SUCCESS) {
snmp_perror(argv[0]);
snmp_log(LOG_ERR,
"Error generating Ku from authentication pass phrase. \n");
exit(1);
}
#else /* we'll use the insecure (but simplier) SNMPv1 */
session.version = SNMP_VERSION_1;
session.community = "demopublic";
session.community_len = strlen(session.community);
#endif /* SNMPv1 */
SOCK_STARTUP;
ss = snmp_open(&session);
if (!ss) {
snmp_sess_perror("ack", &session);
SOCK_CLEANUP;
exit(1);
}
pdu = snmp_pdu_create(SNMP_MSG_GET);
anOID_len = MAX_OID_LEN;
if (!snmp_parse_oid("ip.21.1.8.xx.xx.xx.xx", anOID, &anOID_len)) {
snmp_perror("ip.21.1.8.xx.xx.xx.xx");
SOCK_CLEANUP;
exit(1);
}
snmp_add_null_var(pdu, anOID, anOID_len);
status = snmp_synch_response(ss, pdu, &response);
if (status == STAT_SUCCESS && response->errstat == SNMP_ERR_NOERROR) {
for(vars = response->variables; vars; vars = vars->next_variable)
print_variable(vars->name, vars->name_length, vars);
/* manipuate the information ourselves */
for(vars = response->variables; vars; vars = vars->next_variable) {
if (vars->type == ASN_OCTET_STR) {
char *sp = (char *)malloc(1 + vars->val_len);
memcpy(sp, vars->val.string, vars->val_len);
sp[vars->val_len] = '\0';
printf("value #%d is a string: %s\n", count++, sp); //Here sp now has the string - But this doesnt work when the string is for eg."HOST-RESOURCES-MIB::hrSWInstalledDate.1953 = STRING: 0-1-1,0:0:0.0"
free(sp);
}
else if(vars->type == ASN_INTEGER) {
printf("value is an Integer\n");
int ObjVal;
// How do I get the Integer value and assign it to 'ObjVal'
}
else if(vars->type == ASN_OBJECT_ID) {
printf("value is an OID\n");
// How do I get the OID and assign it to some variable
}
else if(vars->type == ASN_TIMETICKS) {
printf("value is in Timeticks\n");
// How do I get the Timeticks and assign it to some variable for further processing
}
}
} else {
if (status == STAT_SUCCESS)
fprintf(stderr, "Error in packet\nReason: %s\n",
snmp_errstring(response->errstat));
else if (status == STAT_TIMEOUT)
fprintf(stderr, "Timeout: No response from %s.\n",
session.peername);
else
snmp_sess_perror("snmpdemoapp", ss);
}
if (response)
snmp_free_pdu(response);
snmp_close(ss);
SOCK_CLEANUP;
return (0);
}
Tried vars->val.integer or vars->val.object_id, but that doesnot contain the value. What am I missing here?
My another question is, even when it is of type ASN_OCTET_STRING, when the GET reply is something like this,
HOST-RESOURCES-MIB::hrSWInstalledDate.1953 = STRING: 0-1-1,0:0:0.0
then vars->val.string doesnt have "0-1-1,0:0:0.0" as string.
Basically my question is How does the value get stored in the response structure from which I can retrieve the values?
Thanks in Advance!!
P.S: Makefile link from Net-SNMP website.
Edit1:
For Integers, i can read using *vars->val->string as pointed out by immibis. Any Ideas about how to access other datatypes?
As you can see in /usr/include/net-snmp/types.h file or similar on your system, net-snmp vars->val has the following union type:
typedef union {
long *integer;
u_char *string;
oid *objid;
u_char *bitstring;
struct counter64 *counter64;
#ifdef NETSNMP_WITH_OPAQUE_SPECIAL_TYPES
float *floatVal;
double *doubleVal;
/*
* t_union *unionVal;
*/
#endif /* NETSNMP_WITH_OPAQUE_SPECIAL_TYPES */
} netsnmp_vardata;
also *vars has val_len field, where the length of data stored.
So you can access integer as *vars->val.integer, string as pointer to u_char vars->val.string with vars->val_len chars, oid as pointer to oid vars->val.objid with vars->val_len/sizeof(oid) oid elements and so on.

Read certificate files from memory instead of a file using OpenSSL

I have a server which would listen on HTTPS using OpenSSL. For this, I have to provide the certificate to use. However, the current implementation uses a filename to be provided to the OpenSSL API.
I want the certificate information to be read from memory, so that I don't have to ship the certificate file opening. I tried to google, but I didn't come up with any options.
Is is possible? If so, how do I read certificate files from memory instead of a file using OpenSSL?
EDIT: The following was moved from the comments to the question.
// CURRENT
void start_server()
{
const char *fileName = "cert_and_key.pem";
set_server_ssl_file(fileName);
}
set_server_ssl_file(const char *fileName)
{
//initialize context
SSL_CTX_use_certificate_file(CTX, pem, SSL_FILETYPE_PEM);
SSL_CTX_use_PrivateKey_file(CTX, pem, SSL_FILETYPE_PEM);
}
//REQUIRED
void start_server()
{
const char *cert = "--BEGIN CERTIFICATE--............";
const char *key = "--BEGIN RSA PRIVATE KEY--.......";
set_server_ssl_options(cert, key);
}
set_server_ssl_options(const char *cert, const char *key)
{
//IMPLEMENTATION REQUIRED
}
The following code did the job for me:
SSL_CTX *CTX;
X509 *cert = NULL;
RSA *rsa = NULL;
BIO *cbio, *kbio;
const char *cert_buffer = "";
const char *key_buffer = "";
cbio = BIO_new_mem_buf((void*)cert_buffer, -1);
cert = PEM_read_bio_X509(cbio, NULL, 0, NULL);
assert(cert != NULL);
SSL_CTX_use_certificate(CTX, cert);
kbio = BIO_new_mem_buf((void*)key_buffer, -1);
rsa = PEM_read_bio_RSAPrivateKey(kbio, NULL, 0, NULL);
assert(rsa != NULL);
SSL_CTX_use_RSAPrivateKey(CTX, rsa);
The other snippets will only load one certificate. The content of files like http://curl.haxx.se/ca/cacert.pem that contain a lot of different certificates need a new approach. This is adapted from openssl 1.0.1p (mostly openssl-1.0.1p\crypto\x509\by_file.c, char* buf contains the content of a *.pem file, ctx is a boost::asio::ssl::context), add error handling on your own:
BIO *cbio = BIO_new_mem_buf((void*)buf, (int)length);
X509_STORE *cts = SSL_CTX_get_cert_store(ctx.native_handle());
if(!cts || !cbio)
return false;
X509_INFO *itmp;
int i, count = 0, type = X509_FILETYPE_PEM;
STACK_OF(X509_INFO) *inf = PEM_X509_INFO_read_bio(cbio, NULL, NULL, NULL);
if (!inf)
{
BIO_free(cbio);//cleanup
return false;
}
//itterate over all entries from the pem file, add them to the x509_store one by one
for (i = 0; i < sk_X509_INFO_num(inf); i++) {
itmp = sk_X509_INFO_value(inf, i);
if (itmp->x509) {
X509_STORE_add_cert(cts, itmp->x509);
count++;
}
if (itmp->crl) {
X509_STORE_add_crl(cts, itmp->crl);
count++;
}
}
sk_X509_INFO_pop_free(inf, X509_INFO_free); //cleanup
BIO_free(cbio);//cleanup
unsigned char *cert_data = (....);
int cert_len = (....);
X509 *cert = d2i_X509(NULL, &cert_data, cert_len);
SSL_CTX_use_certificate(ctx, cert);
unsigned char *pkey_data = /* ... */;
int pkey_len = /* ... */;
RSA *pkey = d2i_RSAPrivateKey(NULL, &pkey_data, pkey_len);
SSL_CTX_use_RSAPrivateKey(ctx, pkey);
Don't forget & before cert_data and pkey_data - and note that OpenSSL modifies these pointers.
There is another response that uses X509_STORE_add_cert, which is up-voted but incorrect. That answer is a way to do SSL_CTX_load_verify_locations in memory, but does not load the server certificate chain. Replies to that comment also indicate that it does not work.
The following code is a load-from-memory implementation of SSL_CTX_use_certificate_chain_file based on the implementation of that function in OpenSSL:
bool load_cert_chain_from_shared_mem(SSL_CTX *context, const char *cert_buffer)
{
BIO *cbio = BIO_new_mem_buf((void*)cert_buffer, -1);
if (!cbio)
return false;
X509_INFO *itmp;
int i, count = 0, type = X509_FILETYPE_PEM;
STACK_OF(X509_INFO) *inf = PEM_X509_INFO_read_bio(cbio, NULL, NULL, NULL);
if (!inf)
{
BIO_free(cbio);
return false;
}
/* Iterate over contents of the PEM buffer, and add certs. */
BOOL first = TRUE;
for (i = 0; i < sk_X509_INFO_num(inf); i++) {
itmp = sk_X509_INFO_value(inf, i);
if (itmp->x509)
{
/* First cert is server cert. Remaining, if any, are intermediate certs. */
if (first)
{
first = FALSE;
/*
* Set server certificate. Note that this operation increments the
* reference count, which means that it is okay for cleanup to free it.
*/
if (!SSL_CTX_use_certificate(context, itmp->x509))
goto Error;
if (ERR_peek_error() != 0)
goto Error;
/* Get ready to store intermediate certs, if any. */
SSL_CTX_clear_chain_certs(context);
}
else
{
/* Add intermediate cert to chain. */
if (!SSL_CTX_add0_chain_cert(context, itmp->x509))
goto Error;
/*
* Above function doesn't increment cert reference count. NULL the info
* reference to it in order to prevent it from being freed during cleanup.
*/
itmp->x509 = NULL;
}
}
}
sk_X509_INFO_pop_free(inf, X509_INFO_free);
BIO_free(cbio);
return true;
Error:
sk_X509_INFO_pop_free(inf, X509_INFO_free);
BIO_free(cbio);
return false;
}

x509 certificate verification in C

I do have certificates in DER and PEM format, my goal is to retrieve the fields of Issuer and Subject and verify the certificate with the CA public key and simultaneously verify CA certificate with the root public key.
I am able to retrieve all the details of issuer and subject but unable to verify the certificate.
The API used:
x509 = d2i_X509_fp (fp, &x509); //READING DER Format
x509 = PEM_read_X509 (fp, &x509, NULL, NULL); //READING PEM Format
//to retrieve the Subject:
X509_NAME_oneline(X509_get_subject_name(x509), subject, sizeof (subject));
//to retrieve the Issuer:
X509_NAME_oneline(X509_get_issuer_name(x509), issuer, sizeof (issuer));
//To store the CA public key (in unsigned char *key) that will be used to verify the
//certificate (in my case always sha1WithRSAEncryption):
RSA *x = X509_get_pubkey(x509)->pkey.rsa;
bn = x->n;
//extracts the bytes from public key & convert into unsigned char buffer
buf_len = (size_t) BN_num_bytes (bn);
stored_CA_pubKey = (unsigned char *)malloc (buf_len);
i_n = BN_bn2bin (bn, (unsigned char *)stored_CA_pubKey);
if (i_n != buf_len)
LOG(ERROR," : key error\n");
if (key[0] & 0x80)
LOG(DEBUG, "00\n");
stored_CA_pubKeyLen = EVP_PKEY_size(X509_get_pubkey(x509));
For Verification I went through different approaches but I am unable to verify:
a)
i_x509_verify = X509_verify(cert_x509, ca_pubkey);
b)
/* verify the signature */
int iRet1, iRet2, iReason;
iRet1 = EVP_VerifyInit(&md_ctx, EVP_sha1());
iRet2 = EVP_VerifyUpdate(&md_ctx, cert_code, cert_code_len);
rv = EVP_VerifyFinal(&md_ctx, (const unsigned char *)stored_CA_pubKey,
stored_CA_pubKeyLen, cert_pubkey);
NOTE : cert_code and stored_CA_pubKey are unsigned char buffers.
I use following code for verifying a certificate
init CertStore:
X509_STORE* m_store = X509_STORE_new();
X509_LOOKUP* m_lookup = X509_STORE_add_lookup(m_store,X509_LOOKUP_file());
X509_STORE_load_locations(m_store, "CAFile.pem", NULL);
X509_STORE_set_default_paths(m_store);
X509_LOOKUP_load_file(m_lookup,"CAFile.pem",X509_FILETYPE_PEM)
// alternative lookup by hashdir
// X509_LOOKUP* m_lookup=X509_STORE_add_lookup(m_store,X509_LOOKUP_hash_dir());
VerifyCert:
X509_STORE_CTX *storeCtx = X509_STORE_CTX_new();
X509_STORE_CTX_init(storeCtx,m_store,cert,NULL);
X509_STORE_CTX_set_flags(storeCtx, X509_V_FLAG_CB_ISSUER_CHECK);
if (X509_verify_cert(storeCtx) == 1)
{
printf("success");
}
else
{
printf("Verificatione rror: %s",X509_verify_cert_error_string(storeCtx->error));
}
X509_STORE_CTX_free(storeCtx);
you also need to cleanup m_store
if(m_store != NULL)
{
X509_STORE_free(m_store);
m_store = NULL;
}
Take a look at my self-answered question: https://stackoverflow.com/questions/3412032/openssl-c-how-do-you-verify-a-public-key-was-issued-by-your-private-ca it goes a long way to doing what you need.
X509_STORE* m_store = NULL;
X509_LOOKUP *m_lookup = NULL;
X509_STORE_CTX *storeCtx = NULL;
m_store = X509_STORE_new();
if(NULL == m_store) goto exit;
m_lookup = X509_STORE_add_lookup(m_store, X509_LOOKUP_file());
if(NULL == m_lookup) goto exit;
X509_STORE_load_locations(m_store, CA_CERT_PATH, NULL);
X509_STORE_set_default_paths(m_store);
X509_LOOKUP_load_file(m_lookup,CA_CERT_PATH, X509_FILETYPE_ASN1);
m_lookup = X509_STORE_add_lookup(m_store, X509_LOOKUP_hash_dir());
if(NULL == m_lookup) goto exit;
storeCtx = X509_STORE_CTX_new();
if(NULL == storeCtx) goto exit;
X509_STORE_CTX_init(storeCtx,m_store,cer_x509,NULL);
X509_STORE_CTX_set_flags(storeCtx, /*X509_V_FLAG_CHECK_SS_SIGNATURE*/0x4000);
if (X509_verify_cert(storeCtx) == 1)
{
printf("success\n");
}
else
{
printf("Verification error: %s\n",X509_verify_cert_error_string(storeCtx->error));
}
exit:
if(NULL != storeCtx) X509_STORE_CTX_free(storeCtx);
if(m_store != NULL)
{
X509_STORE_free(m_store);
m_store = NULL;
}
After Doing this also I am unable to verify Self signed certificate

Problem in retrieving the ini file through web page

I am using an .ini file to store some values and retrieve values from it using the iniparser.
When I give (hardcode) the query and retrive the value through the command line, I am able to retrive the ini file and do some operation.
But when I pass the query through http, then I am getting an error (file not found), i.e., the ini file couldn't be loaded.
Command line :
int main(void)
{
printf("Content-type: text/html; charset=utf-8\n\n");
char* data = "/cgi-bin/set.cgi?pname=x&value=700&url=http://IP/home.html";
//perform some operation
}
Through http:
.html
function SetValue(id)
{
var val;
var URL = window.location.href;
if(id =="set")
{
document.location = "/cgi-bin/set.cgi?pname="+rwparams+"&value="+val+"&url="+URL;
}
}
.c
int * Value(char* pname)
{
dictionary * ini ;
char *key1 = NULL;
char *key2 =NULL;
int i =0;
int val;
ini = iniparser_load("file.ini");
if(ini != NULL)
{
//key for fetching the value
key1 = (char*)malloc(sizeof(char)*50);
if(key1 != NULL)
{
strcpy(key1,"ValueList:");
key2 = (char*)malloc(sizeof(char)*50);
if(key2 != NULL)
{
strcpy(key2,pname);
strcat(key1,key2);
val = iniparser_getint(ini, key1, -1);
if(-1 == val || 0 > val)
{
return 0;
}
}
else
{
//error
free(key1);
return;
}
}
else
{
printf("ERROR : Memory Allocation Failure ");
return;
}
}
else
{
printf("ERROR : .ini File Missing");
return;
}
iniparser_freedict(ini);
free(key1);
free(key2);
return (int *)val;
}
void get_Value(char* pname,char* value)
{
int result =0;
result = Value(pname);
printf("Result : %d",result);
}
int main(void)
{
printf("Content-type: text/html; charset=utf-8\n\n");
char* data = getenv("QUERY_STRING");
//char* data = "/cgi-bin/set.cgi?pname=x&value=700&url=http://10.50.25.40/home.html";
//Parse to get the values seperately as parameter name, parameter value, url
//Calling get_Value method to set the value
get_Value(final_para,final_val);
}
*
file.ini
*
[ValueList]
x = 100;
y = 70;
When the request is sent through html page, I am always getting .ini file missing. If directly the request is sent from C file them it works fine.
How to resolve this?
Perhaps you have a problem with encoding of the URL parameters? You can't just pass any arbitrary string through a URL - there are some characters that must be encoded. Read this page about URL encoding.
Showing the value of the data string in your C program could be of great help with solving your problem.
Update:
There could be a difference as to where your program executes when called by the web server or directly by you. Are you sure it's being executed with the same "current directory". Chances are it's different, and thus when you attempt to open the ini file you fail. Try to print out the current directory (i.e. using the getcwd function) and compare both cases.

Best ways of parsing a URL using C?

I have a URL like this:
http://192.168.0.1:8080/servlet/rece
I want to parse the URL to get the values:
IP: 192.168.0.1
Port: 8080
page: /servlet/rece
How do I do that?
Personally, I steal the HTParse.c module from the W3C (it is used in the lynx Web browser, for instance). Then, you can do things like:
strncpy(hostname, HTParse(url, "", PARSE_HOST), size)
The important thing about using a well-established and debugged library is that you do not fall into the typical
traps of URL parsing (many regexps fail when the host is an IP address, for instance, specially an IPv6 one).
I wrote a simple code using sscanf, which can parse very basic URLs.
#include <stdio.h>
int main(void)
{
const char text[] = "http://192.168.0.2:8888/servlet/rece";
char ip[100];
int port = 80;
char page[100];
sscanf(text, "http://%99[^:]:%99d/%99[^\n]", ip, &port, page);
printf("ip = \"%s\"\n", ip);
printf("port = \"%d\"\n", port);
printf("page = \"%s\"\n", page);
return 0;
}
./urlparse
ip = "192.168.0.2"
port = "8888"
page = "servlet/rece"
May be late,...
what I have used, is - the http_parser_parse_url() function and the required macros separated out from Joyent/HTTP parser lib - that worked well, ~600LOC.
With a regular expression if you want the easy way. Otherwise use FLEX/BISON.
You could also use a URI parsing library
Libcurl now has curl_url_get() function that can extract host, path, etc.
Example code: https://curl.haxx.se/libcurl/c/parseurl.html
/* extract host name from the parsed URL */
uc = curl_url_get(h, CURLUPART_HOST, &host, 0);
if(!uc) {
printf("Host name: %s\n", host);
curl_free(host);
}
This one has reduced size and worked excellent for me http://draft.scyphus.co.jp/lang/c/url_parser.html . Just two files (*.c, *.h). I had to adapt code [1].
[1]Change all the function calls from http_parsed_url_free(purl) to parsed_url_free(purl)
//Rename the function called
//http_parsed_url_free(purl);
parsed_url_free(purl);
Pure sscanf() based solution:
//Code
#include <stdio.h>
int
main (int argc, char *argv[])
{
char *uri = "http://192.168.0.1:8080/servlet/rece";
char ip_addr[12], path[100];
int port;
int uri_scan_status = sscanf(uri, "%*[^:]%*[:/]%[^:]:%d%s", ip_addr, &port, path);
printf("[info] URI scan status : %d\n", uri_scan_status);
if( uri_scan_status == 3 )
{
printf("[info] IP Address : '%s'\n", ip_addr);
printf("[info] Port: '%d'\n", port);
printf("[info] Path : '%s'\n", path);
}
return 0;
}
However, keep in mind that this solution is tailor made for [protocol_name]://[ip_address]:[port][/path] type of URI's. For understanding more about the components present in the syntax of URI, you can head over to RFC 3986.
Now let's breakdown our tailor made format string : "%*[^:]%*[:/]%[^:]:%d%s"
%*[^:] helps to ignore the protocol/scheme (eg. http, https, ftp, etc.)
It basically captures the string from the beginning until it encounters the : character for the first time. And since we have used * right after the % character, therefore the captured string will be ignored.
%*[:/] helps to ignore the separator that sits between the protocol and the IP address, i.e. ://
%[^:] helps to capture the string present after the separator, until it encounters :. And this captured string is nothing but the IP address.
:%d helps to capture the no. sitting right after the : character (the one which was encountered during the capturing of IP address). The no. captured over here is basically your port no.
%s as you may know, will help you to capture the remaining string which is nothing but the path of the resource you are looking for.
This C gist could be useful. It implements a pure C solution with sscanf.
https://github.com/luismartingil/per.scripts/tree/master/c_parse_http_url
It uses
// Parsing the tmp_source char*
if (sscanf(tmp_source, "http://%99[^:]:%i/%199[^\n]", ip, &port, page) == 3) { succ_parsing = 1;}
else if (sscanf(tmp_source, "http://%99[^/]/%199[^\n]", ip, page) == 2) { succ_parsing = 1;}
else if (sscanf(tmp_source, "http://%99[^:]:%i[^\n]", ip, &port) == 2) { succ_parsing = 1;}
else if (sscanf(tmp_source, "http://%99[^\n]", ip) == 1) { succ_parsing = 1;}
(...)
I wrote this
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
typedef struct
{
const char* protocol = 0;
const char* site = 0;
const char* port = 0;
const char* path = 0;
} URL_INFO;
URL_INFO* split_url(URL_INFO* info, const char* url)
{
if (!info || !url)
return NULL;
info->protocol = strtok(strcpy((char*)malloc(strlen(url)+1), url), "://");
info->site = strstr(url, "://");
if (info->site)
{
info->site += 3;
char* site_port_path = strcpy((char*)calloc(1, strlen(info->site) + 1), info->site);
info->site = strtok(site_port_path, ":");
info->site = strtok(site_port_path, "/");
}
else
{
char* site_port_path = strcpy((char*)calloc(1, strlen(url) + 1), url);
info->site = strtok(site_port_path, ":");
info->site = strtok(site_port_path, "/");
}
char* URL = strcpy((char*)malloc(strlen(url) + 1), url);
info->port = strstr(URL + 6, ":");
char* port_path = 0;
char* port_path_copy = 0;
if (info->port && isdigit(*(port_path = (char*)info->port + 1)))
{
port_path_copy = strcpy((char*)malloc(strlen(port_path) + 1), port_path);
char * r = strtok(port_path, "/");
if (r)
info->port = r;
else
info->port = port_path;
}
else
info->port = "80";
if (port_path_copy)
info->path = port_path_copy + strlen(info->port ? info->port : "");
else
{
char* path = strstr(URL + 8, "/");
info->path = path ? path : "/";
}
int r = strcmp(info->protocol, info->site) == 0;
if (r && info->port == "80")
info->protocol = "http";
else if (r)
info->protocol = "tcp";
return info;
}
Test
int main()
{
URL_INFO info;
split_url(&info, "ftp://192.168.0.1:8080/servlet/rece");
printf("Protocol: %s\nSite: %s\nPort: %s\nPath: %s\n", info.protocol, info.site, info.port, info.path);
return 0;
}
Out
Protocol: ftp
Site: 192.168.0.1
Port: 8080
Path: /servlet/rece
Write a custom parser or use one of the string replace functions to replace the separator ':' and then use sscanf().

Resources