Open LDAP Client in C - How to replace deprecated APIs - c

I am struggling while converting a small OpenLDAP client coded in C. The original version of this program works well with deprecated APIs.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ldap.h>
/*-------------------
gcc -w -DLDAP_DEPRECATED -o exe/TstLDAPold sources/TstLDAPold.c -lopenldap
--------------------*/
int main(int argc, char ** argv)
{
char* pstrDatabaseName = "I36_XXX";
char * pstrSchemaName = "DEV_XXX";
char* pstrModuleName = "L8XX";
char * pstrHostName = "mado";
// New LDAP Structure
int version, ldapPort, i, rc, entryCount, NodeFound;
char *ldapHost, *searchBase;
char *attribute, *dn, **values;
const char *sortAttribute = "sn";
struct timeval timeOut;
BerElement *ber;
LDAP *ld;
LDAPMessage *searchResult, *entry;
FILE *fp;
const char * loginDN = "cn=ldap_yyyy,cn=yyyyy,cn=Ixxxxx";
const char * password = "ldap_xxx";
ldapHost = (char *) malloc(100);
searchBase = (char *) malloc(500); // New LDAP Structure
(void) strcpy( (char *) ldapHost, (const char *) "myldapserver");
char * strLdapPort = (char *) malloc(10);
if (getenv("LDAP_PORT") != NULL)
{
ldapPort = atoi(getenv("LDAP_PORT"));
strcpy(strLdapPort, getenv("LDAP_PORT"));
}
else
{
ldapPort = 389;
strcpy(strLdapPort, "389");
}
strcpy(searchBase, "HostName="); // Change attrib ModuleName in ProgramName
strcat(searchBase, pstrHostName);
strcpy(searchBase, "ProgramName="); // Change attrib ModuleName in ProgramName
strcat(searchBase, pstrModuleName);
strcat(searchBase, ",SchemaName="); // New LDAP Structure
strcat(searchBase, pstrSchemaName); // New LDAP Structure
strcat(searchBase, ",DatabaseName="); // New LDAP Structure
strcat(searchBase, pstrDatabaseName); // New LDAP Structure
strcat(searchBase, ",cn=RLM,cn=Ixxx");
/* Set LDAP version to 3 */
version = LDAP_VERSION3;
ldap_set_option(NULL, LDAP_OPT_PROTOCOL_VERSION, &version);
/* Initialize the LDAP session */
/* NEW ROUTINE OPEN LDAP 2.4 */
char * ldapServer = (char *) malloc(200);
strcpy(ldapServer, (const char *) ldapHost);
strcat(ldapServer, (const char *) ":");
strcat(ldapServer, (const char *) strLdapPort);
printf("LDAP Server : %s\n", ldapServer);
//LDAP ld1;
if ((ld = ldap_open(ldapHost, ldapPort)) == NULL)
{
printf("\n LDAP session initialization failed\n");
return (1);
} else {
printf("Open success\n");
}
/* Bind to the server */
rc = ldap_simple_bind_s(ld, loginDN, password);
if (rc != LDAP_SUCCESS)
{
printf(
"ldap_simple_bind_s: %s\nInformations : Login: %s Password: %s\n",
ldap_err2string(rc), loginDN, password);
printf( "Unbind\n" );
ldap_unbind_s(ld);
printf( "Unbinding ok\n" );
return (1);
}
rc = ldap_search_s(ld, searchBase, LDAP_SCOPE_ONELEVEL, "(objectclass=*)",
NULL, 0, &searchResult);
if (rc != LDAP_SUCCESS)
{
printf("ldap_search_ext_s: %s \n", ldap_err2string(rc));
ldap_unbind_s(ld);
return (1);
} else {
printf("Search Success\n");
}
/// DISPLAY RESULTS
NodeFound = 0;
/* Go through the search results by checking entries */
for (entry = ldap_first_entry(ld, searchResult);
entry != NULL & NodeFound == 0; entry = ldap_next_entry(ld, entry))
{
if ((dn = ldap_get_dn(ld, entry)) != NULL)
{
ldap_memfree(dn);
}
for (attribute = ldap_first_attribute(ld, entry, &ber);
attribute != NULL;
attribute = ldap_next_attribute(ld, entry, ber))
{
/* Get values and print. Assumes all values are strings. */
if ((values = ldap_get_values(ld, entry, attribute)) != NULL)
{
printf("attribute : %s \n",attribute );
printf("value : %s\n", values[0] );
if (strcasecmp(attribute, "HostName") == 0)
{
if (strncmp(values[0], pstrHostName, strlen(pstrHostName)) != 0)
{
NodeFound = 0;
} else
{
//strcpy(mstrHostName, values[0]);
NodeFound = 1;
}
}
ldap_value_free(values);
}
ldap_memfree(attribute);
}
}
ldap_msgfree(searchResult);
ldap_unbind_s(ld);
return (0);
}
This code works, it gives :
attribute : ModuleName
value : LIC_058
attribute : BeginDate
value : 19970404
attribute : EndDate
value : 20251231
...
Then, I have changed it to :
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ldap.h>
/*-------------------
gcc -o exe/TstLDAP sources/TstLDAP.c -lopenldap
--------------------*/
int main(int argc, char ** argv)
{
char* pstrDatabaseName = "I36_XXX";
char * pstrSchemaName = "DEV_XXX";
char* pstrModuleName = "LXXX";
char * pstrHostName = "mado";
// New LDAP Structure
int version, ldapPort, i, rc, entryCount, NodeFound;
char *ldapHost, *searchBase;
char *attribute, *dn, **values;
const char *sortAttribute = "sn";
struct timeval timeOut;
BerElement *ber;
LDAP *ld;
LDAPMessage *searchResult, *entry;
FILE *fp;
const char * loginDN = "cn=ldap_yyy,cn=yyyy,cn=Ixxxx";
const char * password = "ldap_xxx";
ldapHost = (char *) malloc(100);
searchBase = (char *) malloc(500); // New LDAP Structure
(void) strcpy( (char *) ldapHost, (const char *) "ldap://myldapserver");
char * strLdapPort = (char *) malloc(10);
if (getenv("LDAP_PORT") != NULL)
{
ldapPort = atoi(getenv("LDAP_PORT"));
strcpy(strLdapPort, getenv("LDAP_PORT"));
}
else
{
ldapPort = 389;
strcpy(strLdapPort, "389");
}
strcpy(searchBase, "HostName="); // Change attrib ModuleName in ProgramName
strcat(searchBase, pstrHostName);
strcpy(searchBase, "ProgramName="); // Change attrib ModuleName in ProgramName
strcat(searchBase, pstrModuleName);
strcat(searchBase, ",SchemaName="); // New LDAP Structure
strcat(searchBase, pstrSchemaName); // New LDAP Structure
strcat(searchBase, ",DatabaseName="); // New LDAP Structure
strcat(searchBase, pstrDatabaseName); // New LDAP Structure
strcat(searchBase, ",cn=RLM,cn=Ixxx");
/* Set LDAP version to 3 */
version = LDAP_VERSION3;
ldap_set_option(NULL, LDAP_OPT_PROTOCOL_VERSION, &version);
/* Initialize the LDAP session */
/* NEW ROUTINE OPEN LDAP 2.4 */
char * ldapServer = (char *) malloc(200);
strcpy(ldapServer, (const char *) ldapHost);
strcat(ldapServer, (const char *) ":");
strcat(ldapServer, (const char *) strLdapPort);
printf("LDAP Server : %s\n", ldapServer);
//LDAP ld1;
if (ldap_initialize(&ld, ldapServer))
{
//logFile("/tmp/licences_manager.log", "LDAP init failed!", "");
printf("LDAP Init failed");
}
else
{
//logFile("/tmp/licences_manager.log", "LDAP init Success!", "");
printf("LDAP Init Success\n");
}
/*
if ((ld = ldap_open(ldapHost, ldapPort)) == NULL)
{
printf("\n LDAP session initialization failed\n");
return (1);
}*/
/* Bind to the server */
struct berval cred;
strcpy(cred.bv_val, password);
cred.bv_len = strlen(password);
rc = ldap_sasl_bind_s(ld, loginDN, LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL);
if (rc != LDAP_SUCCESS)
{
printf("ldap_sasl_bind_s: %s\nInformations : Login: %s Password: %s\n",
ldap_err2string(rc), loginDN, password);
return (1);
} else {
printf( "Binding successful\n");
}
////-------------Phase 2
LDAPControl **serverctrls;
LDAPControl **clientctrls;
struct timeval *timeout;
rc = ldap_search_ext_s( ld,
searchBase,
LDAP_SCOPE_ONELEVEL,
"(objectclass=*)",
NULL,
0,
NULL, // LDAPControl **serverctrls, // NEW
NULL, // LDAPControl **clientctrls, // NEW
NULL, // struct timeval *timeout, //NEW
LDAP_NO_LIMIT, // int sizelimit, // NEW
&searchResult );
if (rc != LDAP_SUCCESS)
{
printf("ldap_search_ext_s: %s \n", ldap_err2string(rc));
//ldap_unbind_s(ld);
return (1);
} else {
printf("Search Success\n");
}
return (0);
}
The first steps seems to work (initialize, bind) but it crashes in phase 2 when I run the search:
LDAP Server : ldap://myldapserver:389
LDAP Init Success
Memory fault(coredump)
It's on Solaris 11 with GCC 4.8 but I don't think it's system related. Am I doing anything wrong with the search API ? I have googled it a lot but I don't find a good reason for it to fail.
Thanks a lot.

Oops, it seems the binding does not work either. I forgot to allocate the cred.bv_val to receive the password. This is the reason of the coredump.

Related

Modifying extension list in X509 certificate using OpenSSL in C

I am tring to insert signed sct into a precertificate that has a poison extension.
So I first remove the poison extension, then add the SCT.
This is what I've done:
int main(int argc, char **argv) {
size_t lenCert = 0, lenCert2 = 0;
char *filePEM = "testpem/precert3.cert.pem";
char *strCertPem = loadFileContent(filePEM, &lenCert);
const X509 *cert = parse_certificate(strCertPem);
X509 *certRef = X509_dup(cert);
if(!cert || cert==NULL){
printf("Failed parsing\n");
return -1;
}
int len_init = -1;
unsigned char *buf_init = NULL;
len_init = i2d_X509(certRef, &buf_init);
if(len_init < 0){
printf("INIT: failed conversion to DER\n");
return -1;
} else {
printf("INIT: Successful conversion to DER[%d]\n", len_init);
}
printf("size certificate: %ld\n", lenCert);
X509_EXTENSION *tmpext;
const STACK_OF(X509_EXTENSION) *allExt = X509_get0_extensions(cert);
const STACK_OF(X509_EXTENSION) *allExt2 = X509_get0_extensions(certRef);
int my_idx = X509v3_get_ext_by_NID(allExt, NID_ct_precert_poison, -1);
int idx = my_idx;
int cc = X509_get_ext_count(cert);
printf("Extension count in cert BEFORE = %d\n", cc);
printf((allExt==NULL) ? "Extensions extraction FAILED\n" : "Extensions extraction SUCCESS\n");
int counter = X509v3_get_ext_count(allExt);
printf("Extension[%d] count BEFORE = %d\n", idx, counter);
do {
tmpext = X509v3_get_ext(allExt, idx);
X509v3_delete_ext(allExt, idx);
X509_EXTENSION_free(tmpext);
idx = X509v3_get_ext_by_NID(allExt, NID_ct_precert_poison, -1);
printf("pass\n");
} while (idx != -1);
counter = X509v3_get_ext_count(allExt);
printf("Extension count AFTER = %d\n", counter);
if(X509_cmp( cert, certRef)){
printf("Certificate modified\n\n");
} else {
printf("FAILED!!! \n");
}
cc = X509_get_ext_count(cert);
printf("Extension count in cert AFTER = %d\n", cc);
int len_inter;
unsigned char *buf_inter = NULL;
len_inter = i2d_X509(cert, &buf_inter);
if(len_inter < 0){
printf("INTERMEDIATE: failed conversion to DER\n");
return -1;
} else {
printf("INTERMEDIATE: Successful conversion to DER[%d]\n", len_inter);
}
unsigned char *dersct;
size_t lenSCTList = 0;
char *b64SCTList = "BIF6AHgAdgCwzIPlpfl9a698CcwoSQSHKsfoixMsY1C3xv0m4WxsdwAAAWZ7z/DQAAAEAwBHMEUCIQDKJPPQhWqje1rQq+T06x0iNlLT7rX71k23VPZkhm/QCwIgfhwNK7izeq0fHAlu7HuYRjmvym51RRdlNWhd50LQdu4=";
int b64Res = Base64Decode(b64SCTList, &dersct, &lenSCTList); //Decodes a base64 encoded string
printf("size final SCT List: %ld\n", lenSCTList);
STACK_OF(SCT) * scts = d2i_SCT_LIST(NULL, (const unsigned char **) &dersct, lenSCTList);
if(scts==NULL){
printf("Could not convert SCT List!");
return -1;
}
printf("SCT List converted !\n");
ASN1_OCTET_STRING *val = ASN1_OCTET_STRING_new();
ASN1_OCTET_STRING_set(val, dersct, (int)lenSCTList);
X509_EXTENSION* extSCT = X509_EXTENSION_create_by_NID(NULL, NID_ct_precert_scts, 0, val);
if(extSCT){
printf("created extension\n");
} else {
printf("Failed to create extension\n");
return -1;
}
if( X509_add_ext(cert, extSCT, -1)) {
printf("Extension added\n");
// X509_EXTENSION_free(extSCT);
} else {
printf("failed to add extension\n");
return -1;
}
int len_final;
unsigned char *buf_final = NULL;
len_final = i2d_X509(cert, &buf_final);
if(len_final < 0){
printf("FINAL: failed conversion to DER\n");
return -1;
} else {
printf("FINAL: Successful conversion to DER[%d]\n", len_final);
}
BIO *Cout = BIO_new(BIO_s_mem());
PEM_write_bio_X509(Cout, cert);
char* data;
const long len = BIO_get_mem_data(Cout, &data);
cc = X509_get_ext_count(cert);
printf("Extension count in cert AFTER = %d\n", cc);
printf("\ndata[%ld]: \n%s\n\n", len, data);
BIO_free_all(Cout);
int my_idx2 = X509_get_ext_by_NID(cert, NID_ct_precert_poison, -1);
X509_EXTENSION* extPoison2 = X509_get_ext(cert, my_idx2);
if(!extPoison2){
printf("failed last extension[%d] extract \n ", my_idx2);
return -1;
} else {
printf("Succeeded last extension extract[%d]\n ", my_idx2);
}
return 0;
}
This code seems to work, all step are fine, problem is, the final certificate displayed, when I save it to a file and run the command:
openssl x509 -in cert.pem -noout -text
it is the same as the original precertificate, it contains the poison extension and no SCT.
Even comparing both files, they are identical.
Where did I go wrong?
Your main problem is that for X509 X509_CRL X509_REQ when created by parsing input i.e. not built up from scratch OpenSSL saves the tbs encoding and reuses it on output (and digesting and comparison, which is why your X509_cmp failed) even if you have changed some of the fields that go in that encoding, unless you sign the change(s) which you must to make the resulting object valid anyway. In short, you need to call X509_sign() or the extended form X509_sign_ctx() after making your changes.
After fixing that you have another problem: it does remove the poison ext and add an SCT ext -- which contains garbage, because your unnecessary call to d2i_SCT_LIST has changed the pointer you use. Removing that, plus your other unneeded cruft, produces the following code that works (with a precert and key of my own) to produce a correct-looking cert, though of course the SCTs you provided aren't valid for it:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/asn1.h>
#include <openssl/ct.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
int main(int argc, char **argv) {
// size_t lenCert = 0, lenCert2 = 0;
// char *filePEM = "testpem/precert3.cert.pem";
// replace mystery routines by one PEM:
FILE *infile = fopen(argv[1],"r"); if(!infile) exit(1);
X509 *cert = PEM_read_X509 (infile, NULL, NULL, NULL);
fclose(infile);
X509 *certRef = X509_dup(cert);
if(!cert || cert==NULL){ // redundant, and too late
printf("Failed parsing\n");
return -1;
}
#if 0 // useless
unsigned char *buf_init = NULL;
int len_init = i2d_X509(certRef, &buf_init);
#endif
//--printf("size certificate: %ld\n", lenCert);
//--X509_EXTENSION *tmpext;
const STACK_OF(X509_EXTENSION) *allExt = X509_get0_extensions(cert);
const STACK_OF(X509_EXTENSION) *allExt2 = X509_get0_extensions(certRef);
int my_idx = X509v3_get_ext_by_NID(allExt, NID_ct_precert_poison, -1);
int idx = my_idx;
#if 0
int cc = X509_get_ext_count(cert);
printf("Extension count in cert BEFORE = %d\n", cc);
//--printf((allExt==NULL) ? "Extensions extraction FAILED\n" : "Extensions extraction SUCCESS\n");
#endif
#if 0 // useless
int counter = X509v3_get_ext_count(allExt);
printf("Extension[%d] count BEFORE = %d\n", idx, counter);
#endif
#if 0
do {
X509_EXTENSION * tmpext = X509v3_get_ext(allExt, idx);
#endif
X509v3_delete_ext(allExt, idx);
#if 0
X509_EXTENSION_free(tmpext);
idx = X509v3_get_ext_by_NID(allExt, NID_ct_precert_poison, -1);
printf("pass\n");
} while (idx != -1);
printf("Extension count AFTER = %d\n", X509v3_get_ext_count(allExt));
#endif
#if 0
if(X509_cmp( cert, certRef)){
printf("Certificate modified\n\n");
} else {
printf("CENSORED \n");
}
printf("Extension count in cert AFTER = %d\n", X509_get_ext_count(cert));
#endif
#if 0 // useless
unsigned char *buf_inter = NULL;
int len_inter = i2d_X509(cert, &buf_inter);
#endif
unsigned char *dersct;
size_t lenSCTList = 0;
char *b64SCTList = "BIF6AHgAdgCwzIPlpfl9a698CcwoSQSHKsfoixMsY1C3xv0m4WxsdwAAAWZ7z/DQAAAEAwBHMEUCIQDKJPPQhWqje1rQq+T06x0iNlLT7rX71k23VPZkhm/QCwIgfhwNK7izeq0fHAlu7HuYRjmvym51RRdlNWhd50LQdu4=";
// replace mystery routine
dersct = malloc(strlen(b64SCTList)); // more than needed but convenient
lenSCTList = EVP_DecodeBlock(dersct, (unsigned char*)b64SCTList, strlen(b64SCTList));
printf("size final SCT List: %ld\n", lenSCTList);
#if 0 // useless and harmful
STACK_OF(SCT) * scts = d2i_SCT_LIST(NULL, (const unsigned char **) &dersct, lenSCTList);
if(scts==NULL){
printf("Could not convert SCT List!");
return -1;
}
printf("SCT List converted !\n");
#endif
ASN1_OCTET_STRING *val = ASN1_OCTET_STRING_new();
ASN1_OCTET_STRING_set(val, dersct, (int)lenSCTList);
free(dersct); // added
X509_EXTENSION* extSCT = X509_EXTENSION_create_by_NID(NULL, NID_ct_precert_scts, 0, val);
#if 0
if(extSCT){
printf("created extension\n");
} else {
printf("Failed to create extension\n");
return -1;
}
#endif
if( X509_add_ext(cert, extSCT, -1)) {
printf("Extension added\n");
// X509_EXTENSION_free(extSCT);
} else {
printf("failed to add extension\n");
return -1;
}
#if 0 // useless
unsigned char *buf_final = NULL;
int len_final = i2d_X509(cert, &buf_final);
#endif
// added
FILE * keyfile = fopen(argv[2],"r"); if(!keyfile) exit(2);
EVP_PKEY * signkey = PEM_read_PrivateKey (keyfile, NULL, NULL, NULL);
fclose(keyfile);
if( X509_sign(cert,signkey,EVP_sha256())<=0 ) exit(9);
BIO *Cout = BIO_new(BIO_s_mem());
PEM_write_bio_X509(Cout, cert);
char* data;
const long len = BIO_get_mem_data(Cout, &data);
printf("Extension count in cert AFTER = %d\n", X509_get_ext_count(cert));
printf("\ndata[%ld]: \n%s\n\n", len, data);
// added
FILE *outfile = fopen(argv[3],"w"); if(!outfile) exit(3);
fwrite(data,1,len,outfile); fclose(outfile);
BIO_free_all(Cout);
#if 0 // useless
int my_idx2 = X509_get_ext_by_NID(cert, NID_ct_precert_poison, -1);
X509_EXTENSION* extPoison2 = X509_get_ext(cert, my_idx2);
if(!extPoison2){
printf("failed last extension[%d] extract \n ", my_idx2);
return -1;
} else {
printf("Succeeded last extension extract[%d]\n ", my_idx2);
}
#endif
return 0;
}
However, modifying a value returned by get0 -- and 'discarding' the const on it -- is not good style, and might fail in some future implementation. It would be safer and also simpler to use X509_get_ext_by_NID and X509_delete_ext directly on cert.

Custom PAM fails at PAM_CONVERSATION in SSH

I've been trying to implement a custom PAM for 2nd Factor Verification. The Custom PAM needs to read factor selected(integer) and pass it on to the program. The below code works fine in SUDO and SU but I'm getting "Connection Closed by host port 22" error while trying with SSH. Can anyone help me sort the issue.
static char *request_pass(pam_handle_t *pamh, int echocode,PAM_CONST char *prompt)
{
// Query user for verification code
PAM_CONST struct pam_message msg = { .msg_style = PAM_PROMPT_ECHO_ON,.msg = prompt };
PAM_CONST struct pam_message *msgs = &msg;
// PAM_CONST struct pam_message msg;
struct pam_conv conv = {re,NULL};
struct pam_response *resp = NULL;
int retval = re(1,&msgs,&resp,"");
return "";
}
int re(int nummsg,PAM_CONST struct pam_message **msg,struct pam_response **resp, void *appdata_ptr)
{
*resp = malloc(sizeof(struct pam_response));
assert(*resp);
(*resp)->resp = calloc(1024,1);
printf("%s^^ ", msg[0]->msg);
fflush(stdout);
assert(fgets((*resp)->resp, 1024, stdin));
char *ret = NULL;
ret = (*resp)->resp;
// Deallocate temporary storage
if (*resp)
{
if (!ret)
{
free((*resp)->resp);
}
free(*resp);
}
factorselect = atoi(ret);
return PAM_SUCCESS;
}
Edit: Below code worked for me(Solved)
char *
s_prompt(void *arg, const char *prompt, char *buf, size_t bufsz)
{
char *p = NULL;
if (pam_prompt((pam_handle_t *)arg, PAM_PROMPT_ECHO_ON, &p,
"%s", prompt) != PAM_SUCCESS || p == NULL) {
return (NULL);
}
strncpy(buf, p, bufsz);
free(p);
return (buf);
}

malloc pointer address changed causing segfault?

I tried to write a simple database using C. However, I tried to debug my segmentation faults and find the memory pointer obtained through malloc seems changing (name and email pointer seems pointing to different memory locations before and after the Database_load program executes). I have two questions:
Why the memory pointer (name and email) points to different locations before and after Database_load is executed?
Why the program generate a seg fault?
Here is the code related to the problem
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int MAX_DATA;
int MAX_ROWS;
struct Address {
int id;
int set;
//int MAX_DATA;
char *name;
char *email;
};
struct Database {
//int MAX_ROWS;
struct Address *rows;
};
struct Connection{
FILE *file;
struct Database *db;
};
void die(const char *message){
if(errno){
//perror(message);
printf("ERROR: %s\n", message);
}
else{
printf("ERROR: %s\n", message);
}
exit(1);
}
void Address_print(struct Address *addr){
printf("%d %s %s\n", addr->id, addr->name, addr->email);
}
void Database_load(struct Connection *conn){
int i;
int rc = fread(conn->db, sizeof(struct Database), 1, conn->file);
if(rc != 1) die("Failed to load database.");
for (i = 0; i < MAX_ROWS; i++) {
printf("test Database_load loop read rows %p\n", &conn->db->rows[i]);
printf("test Database_load loop read rows name %p\n", &conn->db->rows[i].name);
printf("test Database_load loop read rows email %p\n", &conn->db->rows[i].email);
printf("test Database_load loop read rows name %s\n", conn->db->rows[i].name);
printf("test Database_load loop start %d\n", i);
rc = fread(&conn->db->rows[i], sizeof(struct Address), 1, conn->file);
printf("test Database_load loop read rows %d\n", i);
rc = fread(&conn->db->rows[i].name, sizeof(MAX_DATA), 1, conn->file);
printf("test Database_load loop read name %d\n", i);
rc = fread(&conn->db->rows[i].email, sizeof(MAX_DATA), 1, conn->file);
printf("test Database_load loop read email %d\n", i);
if(rc != 1) die("Failed to load database.");
printf("test Database_load loop\n");
}
}
struct Connection *Database_open(const char *filename, char mode){
int i = 0;
struct Connection *conn = malloc(sizeof(struct Connection));
if(!conn) die("Memory error no connection");;
conn->db = malloc(sizeof(struct Database));
if(!conn->db) die("Memory error no database");
conn->db->rows = malloc(sizeof(struct Address) * MAX_ROWS);
if (conn->db->rows == NULL) die("No memory for rows");
for(i = 0; i < MAX_ROWS; i++){
// make a prototype to initialize it
//struct Address addr = {.id = i, .set = 0};
conn->db->rows[i].id = i;
conn->db->rows[i].set = 0;
conn->db->rows[i].name = malloc(sizeof(char) * MAX_DATA);
if (conn->db->rows[i].name == NULL) die("No memory for name");
conn->db->rows[i].email = malloc(sizeof(char) * MAX_DATA);
if (conn->db->rows[i].email == NULL) die("No memory for email");
// then just assign it
if (i == 0) {
printf("test set name = %p\n", &conn->db->rows[i].name);
printf("test set email = %p\n", &conn->db->rows[i].email);
}
}
if(mode == 'c'){
conn->file = fopen(filename, "w");
}
else{
conn->file = fopen(filename, "r+"); //r+?
if(conn->file){
Database_load(conn);
}
}
if(!conn->file) die("Failed to open the file");
return conn;
}
void Database_close(struct Connection *conn){
if(conn) {
if(conn->file) fclose(conn->file);
if(conn->db) free(conn->db);
free(conn);
}
}
void Database_write(struct Connection *conn){
int i = 0;
rewind(conn->file);
int rc = fwrite(conn->db, sizeof(struct Database), 1, conn->file);
if(rc != 1) die("Failed to write database.");
for (i = 0; i < MAX_ROWS; i++) {
rc = fwrite(&conn->db->rows[i], sizeof(struct Address), 1, conn->file);
if(rc != 1) die("Failed to write database.");
rc = fwrite(&conn->db->rows[i].name, sizeof(MAX_DATA), 1, conn->file);
if(rc != 1) die("Failed to write database.");
rc = fwrite(&conn->db->rows[i].email, sizeof(MAX_DATA), 1, conn->file);
if(rc != 1) die("Failed to write database.");
}
rc = fflush(conn->file);
if(rc == -1) die("Cannot flush database");
}
void Database_create(struct Connection *conn, int MAX_DATA, int MAX_ROWS){
int i = 0;
conn->db->rows = malloc(sizeof(struct Address) * MAX_ROWS);
if (conn->db->rows == NULL) die("No memory for rows");
for(i = 0; i < MAX_ROWS; i++){
// make a prototype to initialize it
struct Address addr = {.id = i, .set = 0};
addr.name = malloc(sizeof(char) * MAX_DATA);
if (addr.name == NULL) die("No memory for name");
addr.email = malloc(sizeof(char) * MAX_DATA);
if (addr.email == NULL) die("No memory for email");
// then just assign it
conn->db->rows[i] = addr;
}
}
void Database_set(struct Connection *conn, int id, const char *name, const char *email){
struct Address *addr = &conn->db->rows[id];
if(addr->set) die("Already set, delete it first");
addr->set = 1;
// warning: intentional bug, no relevant this question
char *res = strncpy(addr->name, name, MAX_DATA);
// demonstrate the strncpy bug
if(!res) die("Name copy failed");
res = strncpy(addr->email, email, MAX_DATA);
if(!res) die("Email copy failed");
}
void Database_get(struct Connection *conn, int id){
struct Address *addr = &conn->db->rows[id];
if(addr->set){
Address_print(addr);
}
else{
die("ID is not set");
}
}
void Database_delete(struct Connection *conn, int id){
struct Address addr = {.id = id, .set = 0};
conn->db->rows[id] = addr;
}
void Database_list(struct Connection *conn){
int i = 0;
struct Database *db = conn->db;
for(i = 0; i < MAX_ROWS; i++){
struct Address *cur = &db->rows[i];
if(cur->set) {
Address_print(cur);
}
}
}
int main(int argc, char *argv[]){
if(argc < 3) die("USAGE: ex17 <dbfile> <action> <MAX_ROWS> <MAX_DATA> [action params]");
char *filename = argv[1];
char action = argv[2][0];
MAX_DATA = atoi(argv[3]);
MAX_ROWS = atoi(argv[4]);
int id = 0;
if(argc > 5) id = atoi(argv[5]);
struct Connection *conn = Database_open(filename, action);
// legacy code, does not apply for create case
// if(argc > 3) id = atoi(argv[3]);
// if(id >= MAX_ROWS) die("There's not that many records.");
switch(action){
case 'c':
if(argc != 5) die("Need MAX_DATA and MAX_ROWS");
Database_create(conn, MAX_DATA, MAX_ROWS);
Database_write(conn);
break;
case 'g':
if(argc != 6) die("Need an id to get");
Database_get(conn, id);
break;
case 's':
if(argc != 8) die("Need id, name, email to set");
Database_set(conn, id, argv[6], argv[7]);
Database_write(conn);
break;
case 'd':
if(argc != 6) die("Need id to delete");
Database_delete(conn, id);
Database_write(conn);
break;
case 'l':
Database_list(conn);
break;
default:
die("Invalid action, only: c=create, g=get, s=set, d=del, l=list");
}
Database_close(conn);
return 0;
}
Here is the printf output after i execute the program
$./ex17_arbitrary db_arbitrary.dat c 512 100
$./ex17_arbitrary db_arbitrary.dat s 512 100 1 zed zed#zedshaw.com
test set name = 0x15ad058
test set email = 0x15ad060
test Database_load loop read rows (nil)
test Database_load loop read rows name 0x8
test Database_load loop read rows email 0x10
One thing I did notice is that these two lines never change across multiple executions with the same commands
test Database_load loop read rows name 0x8
test Database_load loop read rows email 0x10
UPDATE:
I also have some additional design questions. It looks like the design of the current data structure is problematic. I will elaborate on the design requirement here:
I dont need any extra functionality beyond the ones I have created. The size of the database (MAX_DATA and MAX_ROWS have to be variable). Right now I am feeding the MAX_DATA and MAX_ROWS everytime I call the program. Can this be improved? I am thinking may be just give MAX_DATA and MAX_ROWS when I need to use the Database_create method. This program is from an interesting exercise in (c.learncodethehardway.org/book/ex17.html), the original program has a fix size database. And the goal is to make it into variable size.
In Database_load, you are doing:
int rc = fread(conn->db, sizeof(struct Database), 1, conn->file);
But, conn->db is a pointer to the type struct Database and the only element of that is:
struct Address *rows;
In short, you're trying to initialize the pointer rows from an fread of a file. This is not the buffer/array that rows points to but the contents of the rows pointer variable itself (i.e. the address in memory that rows points to)
Since you already initialized rows in Database_open, the fread appears suspect because:
You've already set up rows
Doing (e.g.) void *ptr = ...; read(fd,&ptr,sizeof(ptr)); is almost never correct.
A more normal usage is: void *ptr = ...; read(fd,ptr,some_length);
You're overwriting [trashing] the rows value initialized in Database_open, by doing the equivalent of (2) above. It's just as [bad as] if you had written:
conn->db->rows = NULL;
Or:
conn->db->rows = (void *) 0x1234;
I'm not completely sure, because I can't test the program without data, but you may be able to simply remove the above fread. Or, it has to be replaced with something else if there truly is some sort of header in the database that precedes the actual row data.
But, if you take the fread out, rows remains intact, and what it points to will be populated in the for loop as you have now.
UPDATE:
I see the problem. I think it is more a bad design. Basically, I am storing pointers into the database and try to read it out and access the same pointer address across different program execution.
I mentioned that in my original post, but removed it in my edit because I assumed that you weren't trying to do that and the fread was more of a "typo".
But, it is a bad design to try to store persistent values of pointers within a file and restore them on the next invocation.
Particularly so if the pointers come from malloc. The old pointers could collide with malloc on the second invocation. And, how would you tell malloc that the old pointers are now somehow "reserved"?
For pointers that point to global/static memory, what happens if you rebuild your program and add a new variable [that changes the addresses and offsets of everything]?
In short, you can't do this [and that's the long answer, too :-)].
The solution I can think of is to only store struct Address, and name strings, address strings. Will that be a better design?
Yes, if you mean the following:
struct Address {
int id;
int set;
char name[MAX_DATA];
char email[MAX_DATA];
};
Now, struct Address can be read/written to a file because it has no pointers (i.e. that's the key)
As a further example, consider what happens if struct Address had an embedded linked list pointer:
struct Address {
struct Address *link;
int id;
int set;
char name[MAX_DATA];
char email[MAX_DATA];
};
// simple traversal
for (addr = addrlist; addr != NULL; addr = addr->next) {
// do stuff ...
// broken, because struct Address has a pointer
fwrite(addr,sizeof(struct Address),fout);
}
// fix for above
for (addr = addrlist; addr != NULL; addr = addr->next) {
// do stuff ...
fwrite(addr->id,sizeof(addr->id),fout);
fwrite(addr->set,sizeof(addr->set),fout);
fwrite(addr->name,sizeof(addr->name),fout);
fwrite(addr->email,sizeof(addr->email),fout);
}
Here's a more isolated way to do the list:
struct Address {
int id;
int set;
char name[MAX_DATA];
char email[MAX_DATA];
};
// NOTE: either of these works
#if 1
struct AddrLink {
struct AddrLink *link;
struct Address *data;
};
#else
struct AddrLink {
struct AddrLink *link;
struct Address data;
};
#endif
// indirect list traversal
for (link = addrlist; link != NULL; link = link->next) {
// do stuff ...
// works because struct Address does _not_ have a pointer
fwrite(link->data,sizeof(struct Address),fout);
}
In the general case, what you want to do is similar to serialization. Here's a link: C - serialization techniques Here's another: Serialize Data Structures in C
When I do this, I like to prefix the data with a standard "section" header. A similar technique is used in .mp4, .avi files:
struct section {
int type; // section type
int totlen; // total section length
int size; // sizeof of section element
int count; // number of array elements
};
#define TYPE_ADDRESS 1
#define TYPE_CITY 2
#define TYPE_STATE 3
#define TYPE_COUNTRY 4
That way, if your program doesn't understand a new type because it's an older rev, it can still copy or skip over the data it doesn't understand without harming it. (e.g.) That's required behavior when dealing with .mp4 files.
UPDATE #2:
I have posted the full code. Could you suggest a better way to design this? I dont have a specific constraint for formatting on the database file
Okay, working code below ...
I changed a few things around with the structs [and renamed them]. Notably, the master struct [that you called Connection is now called database_t]. Address is now address_t.
You were pretty close. What you were trying to do with your old struct Database, I replaced with dbheader_t. That is, these were the database header structs I was talking about. Your's just had the pointer in it. Mine records the max rows and max data as the first part of the database file before the row data starts
I moved the allocation code to a new function Database_alloc [because it now has to be called in two different places].
Database_open has to be slightly smarter. For the c action, it fills in the DB header. For all other actions, it has to open the DB file and read the on-disk header.
Also, instead of doing conn->db->rows everywhere, with the new struct organization this is now db->rows.
Overall, you were already quite close.
I also reworked main to be a bit more user friendly (i.e. you only have to enter MAX_DATA/MAX_ROWS on the c command.
Anyway, here it is [please pardon the gratuitous style cleanup]:
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
// database element
typedef struct _address {
int id; // ID/slot number
int set; // 1=active, 0=free/available
char *name; // person's name
char *email; // person's email address
} address_t;
// database on-disk header
typedef struct _header {
int max_rows; // maximum number of rows
int max_data; // maximum size of a field
} dbheader_t;
// NOTE: other stuff can be added (e.g. split max_data into max_name and
// max_email so that each field can have its own maximum length)
// database control
typedef struct _database {
FILE *file; // database I/O stream
dbheader_t header; // copy of on-disk header
address_t *rows; // database data
} database_t;
void
die(const char *message)
{
if (errno) {
// perror(message);
printf("ERROR: %s\n", message);
}
else {
printf("ERROR: %s\n", message);
}
exit(1);
}
void
Address_print(address_t *addr)
{
printf("%d %s %s\n",addr->id, addr->name, addr->email);
}
void
Database_load(database_t *db)
{
int i;
address_t *addr;
int rc;
// NOTE: database header has _already_ been read
#if 0
rc = fread(db->file, sizeof(dbheader_t), 1, db->file);
if (rc != 1)
die("Failed to write database.");
#endif
for (i = 0; i < db->header.max_rows; i++) {
addr = &db->rows[i];
rc = fread(&addr->id, sizeof(addr->id), 1, db->file);
if (rc != 1)
die("Failed to write database.");
rc = fread(&addr->set, sizeof(addr->set), 1, db->file);
if (rc != 1)
die("Failed to write database.");
rc = fread(addr->name, db->header.max_data, 1, db->file);
if (rc != 1)
die("Failed to write database.");
rc = fread(addr->email, db->header.max_data, 1, db->file);
if (rc != 1)
die("Failed to write database.");
}
}
void
Database_alloc(database_t *db)
{
address_t *addr;
db->rows = malloc(sizeof(address_t) * db->header.max_rows);
if (db->rows == NULL)
die("No memory for rows");
for (int i = 0; i < db->header.max_rows; i++) {
addr = &db->rows[i];
// NOTE: no need to do it this way
// make a prototype to initialize it
// struct Address addr = {.id = i, .set = 0};
addr->id = i;
addr->set = 0;
addr->name = calloc(db->header.max_data,sizeof(char));
if (addr->name == NULL)
die("No memory for name");
addr->email = calloc(db->header.max_data,sizeof(char));
if (addr->email == NULL)
die("No memory for email");
}
}
database_t *
Database_open(const char *filename, char mode, int max_rows, int max_data)
{
int rc;
database_t *db = calloc(1,sizeof(database_t));
if (!db)
die("Memory error no db pointer");
switch (mode) {
case 'c':
db->file = fopen(filename, "w");
if (!db->file)
die("Failed to open the file");
// set up a header [to write out]
db->header.max_rows = max_rows;
db->header.max_data = max_data;
Database_alloc(db);
break;
default:
db->file = fopen(filename, "r+"); // r+?
if (!db->file)
die("Failed to open the file");
// read in header so we know the number of rows and the max data size
rc = fread(&db->header,sizeof(dbheader_t),1,db->file);
if (rc != 1)
die("Failed to read header.");
Database_alloc(db);
Database_load(db);
}
return db;
}
void
Database_close(database_t *db)
{
address_t *addr;
if (db) {
if (db->file)
fclose(db->file);
db->file = NULL;
if (db->rows) {
for (int rowidx = 0; rowidx < db->header.max_rows; ++rowidx) {
addr = &db->rows[rowidx];
free(addr->name);
free(addr->email);
}
free(db->rows);
db->rows = NULL;
}
free(db);
}
}
void
Database_write(database_t *db)
{
int i;
int rc;
address_t *addr;
rewind(db->file);
// write out the DB header
rc = fwrite(&db->header, sizeof(dbheader_t), 1, db->file);
if (rc != 1)
die("Failed to write database.");
for (i = 0; i < db->header.max_rows; i++) {
addr = &db->rows[i];
rc = fwrite(&addr->id, sizeof(addr->id), 1, db->file);
if (rc != 1)
die("Failed to write database.");
rc = fwrite(&addr->set, sizeof(addr->set), 1, db->file);
if (rc != 1)
die("Failed to write database.");
rc = fwrite(addr->name, db->header.max_data, 1, db->file);
if (rc != 1)
die("Failed to write database.");
rc = fwrite(addr->email, db->header.max_data, 1, db->file);
if (rc != 1)
die("Failed to write database.");
}
rc = fflush(db->file);
if (rc == -1)
die("Cannot flush database");
}
void
Database_set(database_t *db, int id, const char *name, const char *email)
{
address_t *addr = &db->rows[id];
if (addr->set)
die("Already set, delete it first");
addr->set = 1;
// warning: intentional bug, no relevant this question
// demonstrate the strncpy bug
char *res = strncpy(addr->name, name, db->header.max_data);
if (!res)
die("Name copy failed");
addr->name[db->header.max_data - 1] = 0;
res = strncpy(addr->email, email, db->header.max_data);
if (!res)
die("Email copy failed");
addr->email[db->header.max_data - 1] = 0;
}
void
Database_get(database_t *db, int id)
{
address_t *addr = &db->rows[id];
if (addr->set) {
Address_print(addr);
}
else {
die("ID is not set");
}
}
void
Database_delete(database_t *db, int id)
{
// NOTE/BUG: this causes a memory leak because it overwrites the name and
// email fields without freeing them first
#if 0
struct Address addr = {.id = id,.set = 0 };
db->rows[id] = addr;
#else
address_t *addr = &db->rows[id];
addr->id = 0;
addr->set = 0;
memset(addr->name,0,db->header.max_data);
memset(addr->email,0,db->header.max_data);
#endif
}
void
Database_list(database_t *db)
{
int i;
for (i = 0; i < db->header.max_rows; i++) {
address_t *cur = &db->rows[i];
if (cur->set) {
Address_print(cur);
}
}
}
int
main(int argc, char *argv[])
{
int max_data = 0;
int max_rows = 0;
int id = -1;
if (argc < 3) {
printf("USAGE: ex17 <dbfile> <action> [action params]");
printf(" actions:\n");
printf(" c <MAX_DATA> <MAX_ROWS> -- create database\n");
printf(" g <id> -- get id and print\n");
printf(" s <id> <name> <email> -- set id\n");
printf(" d <id> -- delete id\n");
printf(" l -- list database\n");
die("aborting");
}
// skip over program name
--argc;
++argv;
--argc;
char *filename = *argv++;
--argc;
char action = argv[0][0];
++argv;
switch (action) {
case 'c':
if (argc != 2)
die("Need MAX_DATA and MAX_ROWS");
max_data = atoi(argv[0]);
max_rows = atoi(argv[1]);
break;
}
database_t *db = Database_open(filename, action, max_rows, max_data);
// legacy code, does not apply for create case
// if(argc > 3) id = atoi(argv[3]);
// if(id >= db->header.max_rows) die("There's not that many records.");
switch (action) {
case 'c':
Database_write(db);
break;
case 'g':
if (argc != 1)
die("Need an id to get");
id = atoi(argv[0]);
Database_get(db, id);
break;
case 's':
if (argc != 3)
die("Need id, name, email to set");
id = atoi(argv[0]);
Database_set(db, id, argv[1], argv[2]);
Database_write(db);
break;
case 'd':
if (argc != 1)
die("Need id to delete");
id = atoi(argv[0]);
Database_delete(db, id);
Database_write(db);
break;
case 'l':
Database_list(db);
break;
default:
die("Invalid action, only: c=create, g=get, s=set, d=del, l=list");
break;
}
Database_close(db);
return 0;
}

libiptc : adding nat rule with mark based match?

Am using libiptc -library to manipulate the iptables programatically to add nat rule similar to below.
Rule : iptables -t nat -A POSTROUTING -m mark --mark 0x2/0x3 -j SNAT --to 1.1.1.1
The code looks like below.
I understand that iptable nat rules = ipt_entry (IP Header) + ipt_entry_match (match) + ipt_entry_target (target).
// function to create Mark based match
struct ipt_entry_match* get_mark_target() {
struct ipt_entry_match *match;
struct xt_mark_info *m;
size_t size;
size = IPT_ALIGN(sizeof(struct ipt_entry_match))
+ IPT_ALIGN(sizeof(struct xt_mark_info));
match = calloc(1, size);
match->u.match_size = size;
strncpy(match->u.user.name, "mark", sizeof(match->u.user.name));
m = (struct xt_mark_info*)match->data;
m->mark = m->mask = 0xff;
return match;
}
//function : to create final ipt_enrty
static struct ipt_entry*
make_entry(const char * iaddr, const char * rhost, const char *chain_target)
{
int r = 0;
struct ipt_entry * e;
struct ipt_entry_match *match = get_mark_target();
struct xt_mark_info *m = NULL;
struct ipt_entry_target *target = NULL;
e = calloc(1, sizeof(struct ipt_entry));
//m = calloc(1, sizeof(*m));
//m->mark = 0xff;
e->ip.proto = IPPROTO_IP;
e->nfcache = NFC_IP_DST_PT;
if (!strcmp(chain_target, "SNAT")) {
e->ip.src.s_addr = inet_addr(rhost);
e->ip.smsk.s_addr = INADDR_NONE;
//e->ip.smsk.s_addr = INADDR_NONE;
printf("\n SNAT");
target = get_snat_target(iaddr, 0);
} else {
printf("\n DNAT");
e->ip.dst.s_addr = inet_addr(rhost);
e->ip.dmsk.s_addr = INADDR_NONE;
target = get_dnat_target(iaddr, 0);
}
e->nfcache |= NFC_UNKNOWN;
e = realloc(e, sizeof(struct ipt_entry)
+ match->u.match_size + target->u.target_size);
memcpy(e->elems, match, match->u.match_size);
memcpy(e->elems + match->u.match_size, target, target->u.target_size);
e->target_offset = sizeof(struct ipt_entry)
+ match->u.match_size;
e->next_offset = sizeof(struct ipt_entry)
+ match->u.match_size + target->u.target_size;
#if 0
e = realloc(e, sizeof(struct ipt_entry) + sizeof(*m));
//+ target->u.target_size);
//memcpy(e->elems , target, target->u.target_size);
memcpy(e->elems , m, sizeof(*m));
e->target_offset = sizeof(struct ipt_entry);
e->next_offset = sizeof(struct ipt_entry) + sizeof(*m);
#endif
free(target);
//free(m);
return e;
}
static int
insert_nat_rule (char *src, char *dest, int op, const char *target)
{
struct ipt_entry *entry;
struct xtc_handle *h;
int ret = 1;
const char *chain, *table = "nat";
char *match_mask;
h = iptc_init (table);
if (!h) {
fprintf (stderr, "Could not init IPTC library: %s\n", iptc_strerror (errno));
goto out;
}
if (!strcmp(target, "SNAT")) {
chain = "POSTROUTING";
} else if (!strcmp(target, "DNAT")) {
chain = "PREROUTING";
} else {
//invlid target
return 0;
}
entry = make_entry(src, dest, target);
if (op) {
if (!iptc_append_entry (chain, (struct ipt_entry *) entry, h)) {
fprintf (stderr, "Could not insert a rule in iptables (table %s): "
"%s\n", table, iptc_strerror (errno));
goto out;
}
} else {
match_mask = (unsigned char *)malloc(entry->next_offset);
memset(match_mask, 0xFF, entry->next_offset);
if (!iptc_delete_entry (chain, (struct ipt_entry *) entry,
match_mask, h)) {
fprintf (stderr, "Could not delete a rule in iptables (table %s): "
"%s\n", table, iptc_strerror (errno));
goto out;
}
}
if (!iptc_commit (h)) {
fprintf (stderr, "Could not commit changes in iptables (table %s): %s\n"
, table, iptc_strerror (errno));
goto out;
}
ret = 0;
out:
if (entry) free(entry);
if (h) iptc_free (h);
return ret;
}
On executing the above program, we get an error saying "Protocol wrong type for socket". Without the mark based match this works fine, am able to add 1 to 1 SNAT rules without any problems.
Tried multiple structures for mark based match, but that does not seem to work.
Is there something obvious that I am missing ?
Will Appreciate the inputs. Thanks.
Learnt it the hard way, that iptables uses different structures for match & target. The API uses "revision" field to differentiate which structures to use for this particular match/ target.
So adding a line below, made my life easier.
match->u.user.revision = 1;

Segmentation fault (core dumped) [closed]

This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 9 years ago.
I am new to C, I am learning structure now.
This tries to create an entire small program to manage a database.
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#define MAX_DATA 512
#define MAX_ROWS 100
struct Address {
int id;
int set;
char name[MAX_DATA];
char email[MAX_DATA];
};
struct Database {
struct Address rows[MAX_ROWS];
};
struct Connection {
FILE *file;
struct Database *db;
};
void die(const char *message)
{
if(errno) {
perror(message);
} else {
printf("ERROR: %s\n", message);
}
exit(1);
}
void Address_print(struct Address *addr)
{
printf("%d %s %s\n",
addr->id, addr->name, addr->email);
}
void Database_load(struct Connection *conn)
{
int rc = fread(conn->db, sizeof(struct Database), 1, conn->file);
if(rc != 1) die("Failed to load database.");
}
struct Connection *Database_open(const char *filename, char mode)
{
struct Connection *conn = malloc(sizeof(struct Connection));
if(!conn) die("Memory error");
conn->db = malloc(sizeof(struct Database));
if(!conn->db) die("Memory error");
if(mode == 'c') {
conn->file = fopen(filename, "w");
} else {
conn->file = fopen(filename, "r+");
if(conn->file) {
Database_load(conn);
}
}
if(!conn->file) die("Failed to open the file");
return conn;
}
void Database_close(struct Connection *conn)
{
if(conn) {
if(conn->file) fclose(conn->file);
if(conn->db) free(conn->db);
free(conn);
}
}
void Database_write(struct Connection *conn)
{
rewind(conn->file);
int rc = fwrite(conn->db, sizeof(struct Database), 1, conn->file);
if(rc != 1) die("Failed to write database.");
rc = fflush(conn->file);
if(rc == -1) die("Cannot flush database.");
}
void Database_create(struct Connection *conn)
{
int i = 0;
for(i = 0; i < MAX_ROWS; i++) {
// make a prototype to initialize it
struct Address addr = {.id = i, .set = 0};
// then just assign it
conn->db->rows[i] = addr;
}
}
void Database_set(struct Connection *conn, int id, const char *name, const char *email)
{
addr->set = 1;
char *res = strncpy(addr->name, name, MAX_DATA);
addr->name[MAX_DATA] = '\n';
if(!res) die("Name copy failed");
res = strncpy(addr->email, email, MAX_DATA);
addr->email[MAX_DATA] = '\n';
if(!res) die("Email copy failed");
}
void Database_get(struct Connection *conn, int id)
{
struct Address *addr = &conn->db->rows[id];
if(addr->set) {
Address_print(addr);
} else {
die("ID is not set");
}
}
void Database_delete(struct Connection *conn, int id)
{
struct Address addr = {.id = id, .set = 0};
conn->db->rows[id] = addr;
}
void Database_list(struct Connection *conn)
{
int i = 0;
struct Database *db = conn->db;
for(i = 0; i < MAX_ROWS; i++) {
struct Address *cur = &db->rows[i];
if(cur->set) {
Address_print(cur);
}
}
}
int main(int argc, char *argv[])
{
if(argc < 3) die("USAGE: ex17 <dbfile> <action> [action params]");
char *filename = argv[1];
char action = argv[2][0];
struct Connection *conn = Database_open(filename, action);
int id = 0;
if(argc > 3) id = atoi(argv[3]);
if(id >= MAX_ROWS) die("There's not that many records.");
switch(action) {
case 'c':
Database_create(conn);
Database_write(conn);
break;
case 'g':
if(argc != 4) die("Need an id to get");
Database_get(conn, id);
break;
case 's':
if(argc != 6) die("Need id, name, email to set");
Database_set(conn, id, argv[4], argv[5]);
Database_write(conn);
break;
case 'd':
if(argc != 4) die("Need id to delete");
Database_delete(conn, id);
Database_write(conn);
break;
case 'l':
Database_list(conn);
break;
default:
die("Invalid action, only: c=create, g=get, s=set, d=del, l=list");
}
Database_close(conn);
return 0;
}
It should be like this:
$ make ex17
cc -Wall -g ex17.c -o ex17
$ ./ex17 db.dat c
$ ./ex17 db.dat s 1 someone someemail
$
$ ./ex17 db.dat l
1 someone someemail
But I get this Error message: Segmentation fault (core dumped).
$ make ex17
cc -Wall -g ex17.c -o ex17
$ ./ex17 db.dat c
$ ./ex17 db.dat s 1 someone someemail
Segmentation fault (core dumped)
I think I've got something wrong in "Database_set". I want to solve this problem but I can not. I want to know what I did wrong. Appreciate any help on this error.
In the line
void Database_set(struct Connection *conn, int id, const char *name, const char *email)
{
addr->set = 1;
Where did you define addr? I don't even understand how this compiles...
Presumably you need to keep a count of the number of valid records in your database, and point addr to the first unused record when you add a new one. This requires an update to your database structure as well as to the functions used for adding and deleting. Or you loop through the database to the first address that is not set. Either way, your function needs to declare addr and set it to something useful before using it as a pointer.
The following lines of code may help (these would be the first lines of the Database_set function):
struct Address *addr;
int ii=0;
while(conn->db->rows[ii].set) ii++;
addr = conn->db->rows + ii;
addr->set = 1;
addr->id = id;
You also need to make the changes shown by craig65535. There are quite possibly other problems with your code, but with these additions I can execute the instructions you gave in your question; it compiles, it runs, it doesn't complain. It's a start.
Oh - and it was able to list the database with the l command...
Assuming addr is of type struct Address *, this is totally wrong:
addr->name[MAX_DATA] = '\n';
addr->name only contains MAX_DATA bytes, so the maximum index you can write to is addr->name[MAX_DATA-1]. Also, you want that string to be null-terminated, so you want:
addr->name[MAX_DATA-2] = '\n';
addr->name[MAX_DATA-1] = '\0';
The same goes for addr->email.

Resources