0

I've just started my way into files in c and I'm trying to create a bit of a silly program that asks asks you to login or create a new account by typing in your name and a password. So the problem is, when I decide to create a new account, i fill in my name and my password and after that I manage to login and the program closes successfully. When i run it again, considering I have already made an account, I login with the same information and it works again. But the third time I run it, it doesn't. It tells me that the information I've given is incorrect and when I open the file it is completely blank. I've went all over my book again and again looking for examples and stuff but still I can't work this out. Here's what I've tried:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void login(void);
void create(void);

int main()
{
    char ans[10];

    printf("Welcome to Facebook!\n\n");
    printf("Type yes to login or no to create a new account: ");
    gets(ans);

    if (strcmp(ans, "yes") == 0) {
        login();
    } else {
        create();
        login();
    }

    return 0;
}

void login(void)
{
    FILE *fp;
    char name[50], fname[50];
    int pin, fpin;

    printf("~Login~\n");

    do {
        fp = fopen("test.txt", "r");

        printf("Name: ");
        scanf("%s", name);

        printf("Password: ");
        scanf("%d", &pin);

        getchar();

        fscanf(fp, "%s%d", fname, &fpin);

        if ((strcmp(name, fname) == 0) && (pin == fpin)) {
            printf("You have successfully logged in!\n");
            fclose(fp);
        } else {
            printf("The information you gave is incorrect. Try again... \n");
        }
    } while ((strcmp(name, fname) != 0) || (pin != fpin));
}

void create(void)
{
    FILE *fp;
    char name[50];
    int pin;

    fp = fopen("test.txt", "w");

    printf("\nEnter your name: ");
    gets(name);
    fprintf(fp, "%s\n", name);

    printf("Enter a numerical password: ");
    scanf("%d", &pin);
    getchar();
    fprintf(fp, "%d", pin);

    printf("\nYour new account has been created! Use your info to login!\n");

    fclose(fp);

    login();
}
Sean Bright
  • 118,630
  • 17
  • 138
  • 146
Johnny
  • 25
  • 5
  • 3
    First things first - don't use `gets` – Eugene Sh. Jan 22 '20 at 20:47
  • 2
    [Why gets() is so dangerous it should never be used!](https://stackoverflow.com/questions/1694036/why-is-the-gets-function-dangerous-why-should-it-not-be-used) – David C. Rankin Jan 22 '20 at 20:47
  • 1
    Why your `login` is recursive? Why do you call `create` after `login`? – Eugene Sh. Jan 22 '20 at 20:48
  • 1
    In `login` you open a file for reading and then call `fputs(name, fp);`. That's incorrect. – Fiddling Bits Jan 22 '20 at 20:50
  • 1
    Let's say your `name="you";` and `pin=1234`. When you write the information to a file you write `"you1234"` to the file (`fputs` does not automatically add a `'\n'`). So when you attempt to read with `fscanf(fp, "%s%d", fname, &fpin);`, `name="you1234"` and you have an *input failure* for `fpin` which gets nothing -- but you don't know that because you fail to ***check the return***. Moral of the story **VALIDATE EVERY INPUT**. – David C. Rankin Jan 22 '20 at 20:50
  • @EugeneSh. It's supposed to be recursive as long as the information provided is wrong. Meaning that it would ask you to fill in your information until it's correct. – Johnny Jan 22 '20 at 20:51
  • @DavidC.Rankin I've actually thought of that, as I did it wrong the first time, and then added a \n myself. You can see that on my code. – Johnny Jan 22 '20 at 20:52
  • 3
    @Giannis I understand that, but having a `while` loop would be a way better solution. Consider this program is a "real" security system, then the attacker will simply feed it numerous wrong info, until overflowing the stack and crashing it / exploiting to their advantage. – Eugene Sh. Jan 22 '20 at 20:52
  • I see `fputs(name, fp);` and `fprintf(fp,"%d", pin);` in `login(void)` for the write, I'll look later in the code for something different. (you have it later, but your first write does not include `'\n'`) – David C. Rankin Jan 22 '20 at 20:53
  • In `login()`, you open your file in `"r"` mode and then attempt to write to it with `fputs (name, fp);` -- that's not going to work `:)` – David C. Rankin Jan 22 '20 at 21:02
  • @DavidC.Rankin I just realized that and I actually tend to find my mistakes only as soon as i ask for help lol. I corrected it though but it still does the same thing for some reason. – Johnny Jan 22 '20 at 21:06
  • I'm still picking through it, I'll provide the rest as I stumble across it `:)` – David C. Rankin Jan 22 '20 at 21:07
  • @DavidC.Rankin I edited a little bit and added a while in the login function. What I notice is that even when it tells me that i have successfully logged in (which somehow happens only twice) it asks me again to fill in my info making it look like the condition in the while loop is true (which is only true when the information is wrong). It looks really strange to me although it might be that I'm too new to the files. – Johnny Jan 22 '20 at 21:13
  • 1
    It took a white to unwind what you had done, but I think I captured what you are attempting and dropped an answer providing a few solutions. – David C. Rankin Jan 22 '20 at 22:14

1 Answers1

2

Most of the problems we have already covered in the comments above. The big issue you are running into is the confusion you cause be including login() in your create() function and vice-versa. The design choice leading you to do so is the fact that the void return type chosen for both cannot indicate success or failure on function return.

Don't mix your chocolate and peanut-butter

Meaning, your login() function should provide the code necessary to login() to the system and should not include create() within the login() function. Likewise, the create() function is independent of login() and should just provide the code necessary to create the credentials in the credential file.

To fix this situation (and with every function you write), choose a meaningful return type that can adequately indicate success or failure of your function operation so you can handle the error (or success) properly back in the calling function. Here you simply need to know whether login() and create() succeeded or failed, a simple true/false or 1/0 indication is all you need. A simple return type of int is all you need. Here for true/false we will return 1 if the function succeeds, and 0 if it fails

(note: it is equally correct to choose 1 to indicate error and 0 to indicate no-error -- your choice)

With a meaningful return, your function prototypes become:

int login(void);    /* choose a "meaningful" return type for all functions */
int create(void);   /* int is fine for 1-true/0-false information */

Let's also talk about the 10 and 50 Magic-Numbers you include in your code and the hardcoded filename "test.txt". Avoid using Magic-Numbers and hardcoding filenames. How?

#define MAXC 256    /* if you need a constant, #define one (or more) */
#define TRIES  3
#define CREDFILE "dat/credfile.txt"

(this provides a single convenient location at the top of your source file where you can make changes without having to pick though array declarations, loop definitions, etc..)

Now let's look at login with the changes made as described in the comment on Why gets() is so dangerous it should never be used!. Further, your inclusion of stray getchar(); here and there in you code is symptomatic of a problem you need to fix. Currently you are using getchar() to consume the stray '\n' left in stdin following your scanf of pin. This is horribly fragile. What if the user enters "1234a" as the pin? Your getchar() reads the 'a' but misses the '\n' and you end up skipping your next name input...

How do you solve this problem? Read All Input Into A Buffer with fgets(). Then parse the integer (or whatever) values you need from the buffer using sscanf instead of scanf. Why? With fgets() and a sufficiently sized buffer -- you ensure a complete line of input is consumed on each read, and what remains in stdin does NOT depend on the conversion specifier used and whether a matching failure occurred. Example:

    printf ("Password: ");
    if (!fgets (buf, MAXC, stdin)) {        /* read pin with fgets()!!! */
        fputs ("(user canceled input)\n", stderr);
        return 0;
    }
    if (sscanf (buf, "%d", &pin) != 1) {    /* now use sscanf to parse int */
        fputs ("error: invalid integer input.\n", stderr);
        return 0;
    }

This is the preferred way of handing mixed character and integer input. The only caveat is that all line-oriented functions (fgets() and POSIX getline()) read and include the trailing '\n' in the buffers they fill. You simply need to trim the trailing '\n' by overwriting the '\n' with '\0' (or simply the equivalent 0). strcspn provides a simple way of getting the number of characters (not including the '\n') which allows a simple way to trim the '\n', e.g.

    printf("Type yes to login or no to create a new account: ");
    if (!fgets (ans, MAXC, stdin)) {            /* NEVER EVER use gets() */
        fputs ("(user canceled input)\n", stderr);
        return 1;
    }
    ans[strcspn(ans, "\n")] = 0;                /* trim trialing '\n' */

The other problem you have is failing to VALIDATE each input (whether from the user or from a file). You must validate every input succeeds or handle the error. Otherwise, you invite Undefined Behavior in your code.

Adding the validations to login() and create(), and giving the user 3-tries to login or create an account, you can do something like the following:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXC 256    /* if you need a constant, #define one (or more) */
#define TRIES  3
#define CREDFILE "dat/credfile.txt"

int login(void);    /* choose a "meaningful" return type for all functions */
int create(void);   /* int is fine for 1-true/0-false information */

int main(void)
{
    char ans[MAXC];

    printf("Welcome to Facebook!\n\n");
    printf("Type yes to login or no to create a new account: ");
    if (!fgets (ans, MAXC, stdin)) {            /* NEVER EVER use gets() */
        fputs ("(user canceled input)\n", stderr);
        return 1;
    }
    ans[strcspn(ans, "\n")] = 0;                /* trim trialing '\n' */

    if (strcmp(ans, "yes") == 0) {          /* handle login */
        int tries = TRIES, success = 0;     /* 3-tries and success flag */
        while (tries--)                     /* loop at most 3 times */
            if ((success = login()))        /* on success break loop */
                break;
        if (!success) { /* if login was not a success, handle error */
            fprintf (stderr, "error: %d login tries exhausted.\n", TRIES);
            return 1;
        }
    }
    else {  /* otherwise create() */
        int tries = TRIES, success = 0;     /* same 3-tries */
        while (tries--)                     /* loop at most 3-times */
            if ((success = create()))       /* on success break loop */
                break;
        if (!success) { /* if create was not a success, handle error */
            fprintf (stderr, "error: %d create tries exhausted.\n", TRIES);
            return 1;
        }
        login();    /* now you can have user login to verify account */
    }

    return 0;
}

/* returns 1 on success, 0 otherwise */
int login (void)
{
    FILE *fp;
    char name[MAXC], fname[MAXC], buf[MAXC];    /* buf - read all with fgets */
    int pin, fpin;

    printf("~Login~\n");

    printf ("Name: ");
    if (!fgets (name, MAXC, stdin)) {       /* using fgets() */
        fputs ("(user canceled input)\n", stderr);
        return 0;
    }
    name[strcspn (name, "\n")] = 0;         /* so trim trailing '\n' */

    printf ("Password: ");
    if (!fgets (buf, MAXC, stdin)) {        /* read pin with fgets()!!! */
        fputs ("(user canceled input)\n", stderr);
        return 0;
    }
    if (sscanf (buf, "%d", &pin) != 1) {    /* now use sscanf to parse int */
        fputs ("error: invalid integer input.\n", stderr);
        return 0;
    }

    if (!(fp = fopen (CREDFILE, "r"))) {    /* open/validate file open */
        perror ("fopen-file");
        exit (EXIT_FAILURE);
    }

    if (fscanf (fp, " %255s%d", fname, &fpin) != 2) {   /* read fname/fpin */
        fputs ("error: read of fname/fpin failed.\n", stderr);
        exit (EXIT_FAILURE);
    }
    fclose (fp);

    if ((strcmp(name, fname) == 0) && (pin == fpin)) {  /* validate login */
        printf ("You have successfully logged in!\n");
        return 1;   /* return success */
    }

    printf ("The information you gave is incorrect. Try again... \n");

    return 0;       /* return failure */
}

/* returns 1 on success, 0 otherwise */
int create (void)
{
    FILE *fp;
    char name[MAXC], buf[MAXC];
    int pin;

    printf ("\nEnter your name: ");
    if (!fgets (name, MAXC, stdin)) {       /* using fgets() */
        fputs ("(user canceled input.}\n", stderr);
        return 0;
    }
    name[strcspn (name, "\n")] = 0;         /* so trim trailing '\n' */

    printf ("Enter a numerical password: ");
    if (!fgets (buf, MAXC, stdin)) {        /* read pin with fgets()!!! */
        fputs ("(user canceled input)\n", stderr);
        return 0;
    }
    if (sscanf (buf, "%d", &pin) != 1) {    /* now use sscanf to parse int */
        fputs ("error: invalid integer input.\n", stderr);
        return 0;
    }

    if (!(fp = fopen(CREDFILE, "w"))) {    /* open/validate file open */
        perror ("fopen-failed");
        exit (EXIT_FAILURE);
    }

    fprintf (fp, "%s\n%d\n", name, pin);    /* write creditials to file */
    fclose(fp);

    printf("\nYour new account has been created!\n"
            "You may now login to the system!\n");

    return 1;       /* return success */
}

Example Use/Output

Testing the code.

Create example:

$ ./bin/loginsimple
Welcome to Facebook!

Type yes to login or no to create a new account: no

Enter your name: david
Enter a numerical password: 1234

Your new account has been created!
You may now login to the system!
~Login~
Name: david
Password: 1234
You have successfully logged in!

Checking credential file:

$ cat dat/credfile.txt
david
1234

Login example:

$ ./bin/loginsimple
Welcome to Facebook!

Type yes to login or no to create a new account: yes
~Login~
Name: david
Password: 1234
You have successfully logged in!

You can adjust things to fit your needs. Look things over and let me know if you have further questions.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • As for the problem with gets(), does the same problem remain if instead of using gets(), I use scanf("%s",....) ? – Johnny Jan 23 '20 at 11:16
  • 1
    @Giannis, yes and no, and it brings up another host of problems on its own. First the `"%s"` *conversion specifier* will stop reading when it encounters the first *whitespace*. Second, unless you provide the *field-width* modifier, it provides no check on the number of characters read (similar to the issue with `gets`). So if your buffer is `256-chars`, you must provide a maximum *field-width* of `"%255s"`. Lastly, `scanf` is full of pitfalls for new programmers due to what remains in `stdin` after a *matching-failure*. Those are a few reasons why `fgets` is recommended for user-input. – David C. Rankin Jan 23 '20 at 20:39