0

I am very new to the C programming language and I am trying to make a login thing, the problem that I am having is that I can't get the user_len to compare in my while statement.

Thank you for any and all feedback

EDIT:

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

struct User_name{
  int user_id;
  char username[20];
  char password[30];
} User_data;


//make all the variable
int i;
int user_len;
int pass_len;

//prototype function
char user ();
int user_name();

int main() { 
    //get username
    user_name (User_data.username);
}

int user_name() {
    while(user_len > 20) {
        printf("Enter your username:\n"); 
        scanf("%s",User_data.username);
        user_len = strlen(User_data.username);
        if (user_len > 20) {
            printf("\nusername is too long please enter again:%d\n",user_len);
        } else {
            printf("Username is: %s\n",User_data.username);
        }
    }
    return(user_len);
}
David Lee
  • 21
  • 3
  • We can't see what `User_data.username` is, but checking the entry length is too late to prevent overflow. Also in `while(user_len < 20)` you haven't yet assigned any value to `user_len` (where is that?) so it might fail the very first iteration. – Weather Vane Feb 18 '22 at 22:33
  • 2
    We cannot see what the initial value of `user_len` even is. The loop control certainly thinks it is important, so... why not include it as part of a **proper** [mcve] ? – WhozCraig Feb 18 '22 at 22:34
  • I just put in the new edit to show what was missing. once again sorry. my first time doing this. – David Lee Feb 18 '22 at 22:56
  • Side note: I have fixed the indentation in your code, because it was unreadable. I strongly suggest that you get into the habit of indenting your code consistently, because otherwise, it will also be very hard for you to read your own code. – Andreas Wenzel Feb 18 '22 at 23:13
  • Thanks for posting the fuller code. In addition to the above `user_len = strlen(User_data.username);` is in the wrong place: it needs to be *after* the data entry. And the `while` condition is the wrong sense, it should be `while(user_len > 20)` to agree with the message after `if (user_len > 20)` – Weather Vane Feb 18 '22 at 23:13
  • Thank you all for the all the feedback. I will get better at it. also when you mean by after data entry. does it mean right after the scanf? – David Lee Feb 18 '22 at 23:17
  • Exactly! But, the `char username[20];` can only hold 19 characters (plus nul terminator). So apart from the off-by-one, you'll break something by entering the string directly into there, when it is too long. – Weather Vane Feb 18 '22 at 23:22
  • @WeatherVane: I do not see much point in checking for a buffer overflow **after** the buffer overflow has occurred. The buffer overflow must be prevented in the first place, for example by changing the `scanf` conversion format specifier from `%s` to `%19s`. However, it would probably be better for OP to use `fgets` instead of `scanf`. – Andreas Wenzel Feb 18 '22 at 23:27
  • @AndreasWenzel I already pointed that out in [this comment](https://stackoverflow.com/questions/71180563/cant-get-my-user-len-to-compare-to-the-while-loop#comment125823073_71180563) and in the recent one: *you'll break something by entering the string directly into there, when it is too long* – Weather Vane Feb 18 '22 at 23:33
  • I apologize. to stop the buffer overflow where would i need to make the changes. – David Lee Feb 18 '22 at 23:34
  • I would also have mentioned the `%19s` restriction, but then you leave something in the buffer, and problem mounts on problem. User input is never trivial. – Weather Vane Feb 18 '22 at 23:35
  • @WeatherVane: My comment was intended to be solely directed against your statement "it needs to be after the data entry", in which you were referring to the `strlen` statement. The point I was trying to make was that this `strlen` statement should not be moved as you suggest, but should be deleted instead, because it is useless (for the reasons that you yourself have mentioned). On the other hand, maybe I was wrong; maybe the statement is not useless, if the program is modified slightly. – Andreas Wenzel Feb 19 '22 at 00:12
  • @WeatherVane: For example, if the `%19s` conversion format specifier is used, then it would make sense to move the `strlen` statement after the `scanf` statement (as you suggested), if the line `if (user_len > 20) {` is also changed to `if (user_len == 19 ) {`. Of course, the condition of the `while` loop would have to be changed accordingly, too. – Andreas Wenzel Feb 19 '22 at 00:13
  • @DavidLee: The easiest way to prevent the buffer overflow would be to change the `scanf` format string from `"%s"` to `"%19s"`, which would limit the number of matched characters to `19`, so that it will not write more than `20` characters to `User_data.username` (including the terminating null character). However, this solution is not ideal, as it is possible that `scanf` will leave non-whitespace characters from that line on the input stream (this is also possible when using `"%s"`). These non-whitespace characters will likely cause trouble in the next loop iteration when `scanf` is called. – Andreas Wenzel Feb 19 '22 at 00:49
  • @DavidLee: For the reasons described in my previous comment, I think it would be best if you used the function [`fgets`](https://en.cppreference.com/w/c/io/fgets) instead of `scanf`. The function `fgets` has the advantage that it will always read exactly one line at a time (provided that the supplied input buffer is large enough to store the whole line), which is not necessarily the case with `scanf`. – Andreas Wenzel Feb 19 '22 at 00:50
  • @AndreasWenzel: thank you so much. for writing it out in much simple and understandable way for me. i really appreciate it. – David Lee Feb 19 '22 at 00:53
  • @DavidLee: I suggest that you don't use `fgets` on `User_data.username` directly. Instead, you should create an array of maybe 100 characters, for example like this: `char line[100];` You can then use that array as an input buffer for `fgets`, by calling `fgets` like this: `fgets( line, sizeof line, stdin );`. When using `fgets`, you will probably also want to [remove the newline character](https://stackoverflow.com/q/2693776/12149471), for example by writing `line[strcspn(line,"\n")] = '\0';`. If the input length is less than 20 characters, you can copy it to `User_data.username`. – Andreas Wenzel Feb 19 '22 at 01:08

1 Answers1

0

The content of the while loop will never be executed, because the condition in the line

while(user_len > 20) {

will be false at the start of the program, because user_len will have the value 0.

Also, the lines

char user ();
int user_name();

are not prototype declarations. They are forward declarations of functions that take an unspecified number of arguments. If you want to declare that they take no arguments, then you should change them to:

char user( void );
int user_name( void );

See the following question for more information:

Warning/error "function declaration isn't a prototype"

The following code has a serious problem:

printf("Enter your username:\n");
scanf("%s",User_data.username);
user_len = strlen(User_data.username);
if (user_len > 20) {
    printf("\nusername is too long please enter again:%d\n",user_len);
} else {
    printf("Username is: %s\n",User_data.username);
}

There is no point in checking for a buffer overflow after the buffer overflow has already occurred. The buffer overflow must be prevented in the first place, in order to prevent your program from invoking undefined behavior (i.e. to prevent your program from possibly crashing).

The easiest way to prevent the buffer overflow would be to change the scanf format string from "%s" to "%19s", which would limit the number of matched characters to 19, so that it will not write more than 20 characters to User_data.username (including the terminating null character).

However, this solution is not ideal, as it is possible that scanf will leave non-whitespace characters from that line on the input stream (this is also possible when using "%s"). These non-whitespace characters will likely cause trouble in the next loop iteration when scanf is called.

For this reason, it would probably be best if you used the function fgets instead of scanf. The function fgets has the advantage that it will always read exactly one line at a time (provided that the supplied input buffer is large enough to store the whole line), which is not necessarily the case with scanf.

Here is my solution to the problem, which uses fgets instead of scanf:

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

struct User_name
{
    int user_id;
    char username[20];
    char password[30];
} User_data;

//make all the variable
int i;
int user_len;
int pass_len;

//prototype function
int user_name( void );

int main( void )
{ 
    //get username
    user_name();
}

int user_name( void )
{
    while( true )
    {
        char line[100];
        char *p;

        //prompt user for input
        printf( "Enter your username: " );
        
        //attempt to read one line of input
        if ( fgets( line, sizeof line, stdin ) == NULL )
        {
            printf( "Error reading input!\n" );
            exit( EXIT_FAILURE );
        }
        
        //find newline character
        p = strchr( line, '\n' );
        
        //make sure that entire line was read in (i.e. that
        //the buffer was not too small)
        if ( p == NULL )
        {
            //a missing newline character is ok on end-of-file condition
            if ( !feof( stdin ) )
            {
                int c;

                //discard remainder of line
                do
                {
                    c = getchar();

                    if ( c == EOF )
                    {
                        fprintf( stderr, "Error reading from input\n" );
                        return false;
                    }

                } while ( c != '\n' );
                
                goto line_too_long;
            }
        }
        else
        {
            //remove newline character by overwriting it with null character
            *p = '\0';
        }
        
        //find length of string
        user_len = strlen( line );

        //check if length is acceptable
        if ( user_len >= 20 )
        {
            goto line_too_long;
        }

        //username is ok, so copy it
        strcpy( User_data.username, line );

        //break out of infinite loop
        break;
        
    line_too_long:
        printf( "Line was too long, try again!\n" );
    }
    
    //print username
    printf( "Username is: %s\n", User_data.username );

    return user_len;
}

This program has the following behavior:

Enter your username: ThisIsAVeryLongUserName
Line was too long, try again!
Enter your username: ShortUserName
Username is: ShortUserName

Note that this program uses one goto label and two goto statements. Normally it is a good idea to try to not use goto, but to use normal control flow statements instead. However, in this case, I believe that using goto was appropriate. The alternative would have been to duplicate the line printf( "Line was too long, try again!\n" ); and use a continue statement in two places in the program. I believe that it is better to handle this error in one place in the program (though I understand that this topic is highly controversial and that some people would consider it better to use code duplication).

Another issue worth mentioning is that you are using global variables (which I have taken over in my solution). This is considered bad programming style and should generally be avoided. Here is a modified version of my solution which avoids the use of global variables:

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

struct user_data
{
    int user_id;
    char username[20];
    char password[30];
};

//prototype function
int user_name( void );

int main( void )
{ 
    //get username
    user_name();
}

int user_name( void )
{
    struct user_data ud;
    int user_len;

    while( true )
    {
        char line[100];
        char *p;

        //prompt user for input
        printf( "Enter your username: " );
        
        //attempt to read one line of input
        if ( fgets( line, sizeof line, stdin ) == NULL )
        {
            printf( "Error reading input!\n" );
            exit( EXIT_FAILURE );
        }
        
        //find newline character
        p = strchr( line, '\n' );
        
        //make sure that entire line was read in (i.e. that
        //the buffer was not too small)
        if ( p == NULL )
        {
            //a missing newline character is ok on end-of-file condition
            if ( !feof( stdin ) )
            {
                int c;

                //discard remainder of line
                do
                {
                    c = getchar();

                    if ( c == EOF )
                    {
                        fprintf( stderr, "Error reading from input\n" );
                        return false;
                    }

                } while ( c != '\n' );
                
                goto line_too_long;
            }
        }
        else
        {
            //remove newline character by overwriting it with null character
            *p = '\0';
        }
        
        //find length of string
        user_len = strlen( line );

        //check if length is acceptable
        if ( user_len >= 20 )
        {
            goto line_too_long;
        }

        //username is ok, so copy it
        strcpy( ud.username, line );

        //break out of infinite loop
        break;
        
    line_too_long:
        printf( "Line was too long, try again!\n" );
    }
    
    //print username
    printf( "Username is: %s\n", ud.username );

    return user_len;
}
Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39