Вход и регистрация пользователя: не может зарегистрировать более одного пользователя в C - PullRequest
0 голосов
/ 21 ноября 2018

Я работаю над этим c проектом, который занимается регистрацией пользователей и программой входа в систему.Проблема в том, что программа прошла успешно, но только для одного пользователя.Ребята, не могли бы вы объяснить, что не так и что я должен изменить?Заранее спасибо.

PS Это мой первый вопрос в stackoverflow

Английский не мой основной язык

В любом случае, вот мой код на C ниже:

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

static int i=0;
struct w
{
char nama[30],pass[30];
}
w[100];
int n;
void login(void);
void reg(void);
int main(void)
{
menu:
system("cls");
printf("\n\n\n\n\n\t\t\t\tWELCOME!");
printf("\n\n\n\n\t\t\t\t\t\t[ENTER]");
if(getch()==13)
{
    system("cls");
}
    else
{
    goto menu;
}
menu_main:
printf("\n\n\n\t\t\t[1] LOGIN\t\t[2] REGISTRATION\t\t[3] EXIT APP");
printf("\n\n\n\t\t\t\t  INPUT YOUR SELECTION THEN PRESS [ENTER]: ");
scanf("%d",&n);
switch(n)
  {
    case 1: system("cls");
        login();
        break;
    case 2: system("cls");
        reg();
        break;
    case 3:system("cls");
        printf("\n\n\t\t\t\tTHANK YOU FOR USING THIS APP\n");
        break;
    default: system("cls"); printf("\n\n\t\t\t\tNOT AVAILABLE");
        printf("\n\n\t\t\tPRESS ANY KEY TO GO BACK");
        getch();
        system("cls"); goto menu_main;
    }
}
void reg()
  {
    FILE *fp;
    char c,username[30]; int z=0;
    fp=fopen("file.txt","ab+");
    system("cls");
    printf("\n\n\t\tPLEASE INPUT USERNAME & PASSWORD");
    for(i=0;i<99;i++)
    {
      printf("\n\n\t\t\t\t  USERNAME: ");
      scanf("%s",username);
        while(!feof(fp))
        {
          fread(&w[i],sizeof(w[i]),1,fp);
          if(strcmp(username,w[i].nama)==0)
            {
            system("cls");
            printf("\n\n\t\t\t  USERNAME IS NOT AVAILABLE");
            printf("\n\n\t\t\t  PRESS ANY KEY TO GO BACK");
            getch();
            system("cls"); reg();
            }
          else
          {
            strcpy(w[i].nama,username);
            break;
          }
        }
      z=0;
      printf("\n\n\t\t\t\t  PASSWORD: ");
      while((c=getch())!=13)
        {
          w[i].pass[z++]=c;
          printf("%c",'*');
        }
      fwrite(&w[i],sizeof(w[i]),1,fp);
      fclose(fp);
      printf("\n\n\tPRESS [ENTER] IF YOU AGREE");
      if((c=getch())==13)
        {
        system("cls");
        printf("\n\n\t\tYOU ARE REGISTERED!");
        printf("\n\n\t\tWOULD YOU LIKE TO LOGIN?\n\n\t\t  ");
        printf(" PRESS [1] FOR YES\n\n\t\t   PRESS [2] FOR NO\n\n\t\t\t\t");
        scanf("%d",&n);
        if(n==1)
          { 
            system("cls");
            login();
          }
            else if(n==2)
          {
            system("cls");
            printf("\n\n\n\t\t\t\tTHANK YOU FOR REGISTERING IN THIS APP\n");
          } 
        }
        break;
      }
  }
  void login()
    {
      FILE *fp;
      char c,nama[10],pass[10]; int z=0;
      int cekun,cekpw;
      fp=fopen("file.txt","r");
      for(i=0;i<=10;i++)
      {
        printf("\n\n\t\t\t\t  USERNAME: ");
        scanf("%s",nama);

        system("cls");
        printf("\n\n\t\t\t\t  PASSWORD: ");
        while((c=getch())!=13)
        {
          pass[z++]=c;
          printf("%c",'*');
        }
        pass[z]='\0';
        while(!feof(fp))
        {
        fread(&w[i],sizeof(w[i]),1,fp);
          cekun=strcmp(nama,w[i].nama);
          cekpw=strcmp(pass,w[i].pass);
          if(cekun==0&&cekpw==0)
          {
            system("cls");
            printf("\n\n\n\t\t\tLOGIN SUCCESSFUL!");
            break;
          }
        else if(cekun==0)
          {
            printf("\n\n\n\t\t\tWRONG PASSWORD!");
            printf("\n\n\t\t\t\t  (PRESS [Y] TO RE-LOGIN)");
            if(getch()=='y'||getch()=='Y')
              system("cls"); login();
          }
        else if(cekun!=0&&cekpw!=0)
          {
            h:
            printf("\n\n\n\t\t\tYOU ARE NOT REGISTERED\n \t\t\tPRESS [ENTER] TO REGISTER");
            if(getch()==13)
            system("cls"); reg();
          }
          else if(cekun!=0&&cekpw==0)
          {
            goto h;
          }
        }
        break;
      }
      getch();
    }

Ответы [ 2 ]

0 голосов
/ 21 ноября 2018

Вместо написания функций, которые запрашивают и проверяют, выполните проверку в отдельной функции.

Например, допустим, вы используете

#define  MAX_NAME_LEN  31
#define  MAX_PASS_LEN  31

typedef struct {
    char  name[MAX_NAME_LEN + 1];
    char  pass[MAX_PASS_LEN + 1];
} userinfo;

для хранения информации об известных пользователях.Ваша база данных пользователей может тогда быть

#define  MAX_USERS  100

static userinfo  known_user[MAX_USERS];
static size_t    known_users = 0;

. Чтобы зарегистрировать нового пользователя, вы пишете функцию, которая принимает имя пользователя и пароль в качестве параметров:

int register_user(const char *user, const char *pass)
{
    const size_t  userlen = (user) ? strlen(user) : 0;
    const size_t  passlen = (pass) ? strlen(pass) : 0;
    size_t        i;

    if (userlen < 1)
        return -1; /* Empty username */
    if (userlen > MAX_NAME_LEN)
        return -2; /* Username is too long */
    if (passlen < 1)
        return -3; /* Empty password */
    if (passlen > MAX_PASS_LEN)
        return -4; /* Password is too long */

    /* Check if this user is already known. */
    for (i = 0; i < known_users; i++)
        if (!strcmp(user, known_user[i].name))
            return -5; /* Username is already registered. */

    if (known_users >= MAX_USERS)
        return -6; /* User database is full. */

    /* Add this user to the user database. */
    strncpy(known_user[known_users].name, user, MAX_NAME_LEN + 1);
    strncpy(known_user[known_users].pass, pass, MAX_PASS_LEN + 1);

    known_users++;

    return 0; /* Success */
}

Обратите внимание, что (user) ? strlen(user) : 0 являетсятроичное выражение.Это означает, что если user не равно NULL, выражение оценивается как strlen(user), в противном случае оно оценивается как 0. (Да, это похоже на компактную форму предложения if.) Обычно можно увидеть strlen()обернута так, потому что strlen(NULL) небезопасен и может привести к сбою программы: это страшно Неопределенное поведение , в соответствии со стандартом C.(Говорят, что UB может вызвать появление демонов у вас из носа, но я думаю, это просто страшная история от серых бород, заставляющая нас воспринимать UB всерьез.)

(!strcmp(...)) эквивалентно (strcmp(...) == 0) и истинно, если две строки точно совпадают.

Аналогично, чтобы проверить, совпадает ли пара имя пользователя и пароль с известным пользователем,

int verify_user(const char *user, const char *pass)
{
    const size_t  userlen = (user) ? strlen(user) : 0;
    const size_t  passlen = (pass) ? strlen(pass) : 0;
    size_t        i;

    if (userlen < 1)
        return -1; /* Empty username */
    if (userlen > MAX_NAME_LEN)
        return -2; /* Username is too long */
    if (passlen < 1)
        return -3; /* Empty password */
    if (passlen > MAX_PASS_LEN)
        return -4; /* Password is too long */

    for (i = 0; i < known_users; i++)
        if (!strcmp(user, known_user[i].name)) {
            if (!strcmp(pass, known_user[i].pass))
                return 0;  /* Valid user */
            else
                return -6; /* Wrong password */
        }

    return -7; /* No such user. */
}

На этом этапе вы можете написать небольшую тестовую программу и проверить, работают ли вышеуказанные функции.Это называется модульное тестирование :

int main(void)
{
    int result;

    result = register_user("foo", "bar");
    if (result) {
        fprintf(stderr, "register_user(\"foo\", \"bar\") failed: %d.\n", result);
        exit(EXIT_FAILURE);
    } else
        fprintf(stderr, "register_user(\"foo\", \"bar\") worked as expected (0).\n");

    result = register_user("user", "pass");
    if (result) {
        fprintf(stderr, "register_user(\"user\", \"pass\") failed: %d.\n", result);
        exit(EXIT_FAILURE);
    } else
        fprintf(stderr, "register_user(\"user\", \"pass\") worked as expected (0).\n");

    result = register_user("foo", "irrelevant");
    if (result) {
        fprintf(stderr, "register_user(\"foo\", \"irrelevant\") failed. Expected -5, received %d.\n", result);
        exit(EXIT_FAILURE);
    } else
        fprintf(stderr, "register_user(\"foo\", \"irrelevant\") worked as expected (-5).\n");

    result = verify_user("no such", "user or password");
    if (result != -7) {
        fprintf(stderr, "verify_user(\"no such\", \"user or password\") failed. Expected -7, received %d.\n", result);
        exit(EXIT_FAILURE);
    } else
        fprintf(stderr, "verify_user(\"no such\", \"user or password\") worked as expected (-7).\n");

    result = verify_user("foo", "wrong password");
    if (result != -6) {
        fprintf(stderr, "verify_user(\"foo\", \"wrong password\") failed. Expected -6, received %d.\n", result);
        exit(EXIT_FAILURE);
    } else
        fprintf(stderr, "verify_user(\"foo\", \"wrong password\") worked as expected (-6).\n");

    result = verify_user("foo", "bar");
    if (result != 0) {
        fprintf(stderr, "verify_user(\"foo\", \"bar\") failed. Expected 0, received %d.\n", result);
        exit(EXIT_FAILURE);
    } else
        fprintf(stderr, "verify_user(\"foo\", \"bar\") worked as expected (0).\n");

    printf("All tests successful.\n");
    return EXIT_SUCCESS;
}

Когда вы научитесь лучше программировать, вы можете «автоматизировать» такое тестирование различными способами.

Только когда ваша тестовая программаработает корректно для тестов (и проверяет правильные и неправильные входные данные и их результаты!), продолжая решение исходной задачи.

Таким образом, вы знаете, что эти две функции работают, и вы знаете, что любые проблемыили ошибки должны быть в другом месте.(Если вы не уверены, вы можете добавить эти данные в программу модульного тестирования и проверить!)


Не рекомендуется хранить пароли в виде простого текста.Когда-либо.Вам также не нужно этого делать.

Что вам нужно сделать, это использовать хеш-функцию для преобразования пароля в большое число;хеш-функция должна быть криптографически безопасной .Пароль также должен быть salted : случайное число или строка, которые сохраняются в виде обычного текста, которые добавляются (или добавляются, или оба) к паролю, и комбинация передается в хэш-функцию.Чтобы замедлить любую атаку по словарю, хеш-функция обычно применяется большое количество раз;это число может варьироваться, но фиксированное число раундов, как правило, столь же безопасно.

В этом случае пользовательская информация выглядит примерно так:

#define  MAX_NAME_LEN  31
#define  HASH_SIZE     64    /* for 512-bit hashes like SHA512 */
#define  SALT_SIZE     16    /* for 128-bit salt */
#define  HASH_ROUNDS   5000

typedef struct {
    char           name[MAX_NAME_LEN + 1];
    unsigned char  salt[SALT_SIZE];
    unsigned char  hash[HASH_SIZE];
} userinfo;

Скажем, криптографически безопасная хеш-функция, которую мыМожно использовать, объявлено как

void crypto_hash(unsigned char *hash, const void *data, const size_t size);

, и у нас есть криптографически безопасный генератор случайных чисел, который генерирует случайную соль, когда нам нужно, используя

void crypto_salt(unsigned char *salt);

, тогда наша функция регистрации пользователя становится

int register_user(const char *user, const char *pass)
{
    const size_t   userlen = (user) ? strlen(user) : 0;
    const size_t   passlen = (pass) ? strlen(pass) : 0;
    unsigned char *temp;
    unsigned char  hash[2][HASH_SIZE];
    size_t         i;

    if (userlen < 1)
        return -1; /* Empty username */
    if (userlen > MAX_NAME_LEN)
        return -2; /* Username is too long */
    if (passlen < 1)
        return -3; /* Empty password */

    /* TODO: Password strength checking! */

    /* Check if this user is already known. */
    for (i = 0; i < known_users; i++)
        if (!strcmp(user, known_user[i].name))
            return -5; /* Username is already registered. */

    if (known_users >= MAX_USERS)
        return -6; /* User database is full. */

    /* Generate a random salt for this new user. */
    crypto_salt(known_user[i].salt);

    /* Allocate a temporary buffer for the salt and the password.
       Here, for simplicity, we just prepend the salt to the password,
       and include the string-terminating nul byte, '\0'.
       This is to protect against a certain type of tail attacks. */
    temp = malloc(SALT_SIZE + pass_len + 1);
    if (!temp)
        return -8; /* Not enough memory. */

    /* Combine the salt and the password, including '\0' at end. */
    memcpy(temp, known_user[known_users].salt, SALT_SIZE);
    memcpy(temp + SALT_SIZE, pass, passlen + 1);

    /* First hashing round. */
    crypto_hash(hash[0], temp, SALT_SIZE + passlen + 1);

    /* The temporary buffer is no longer needed.
       We wipe, then free it. */
    memset(temp, 0, SALT_SIZE + passlen + 1);
    free(temp);

    /* Repeated rounds. */
    for (i = 0; i < HASH_ROUNDS - 2; i++)
        crypto_hash(hash[(i+1) & 1], hash[i & 1], HASH_SIZE);

    /* Final round. Since i was incremented after the last
       crypto_hash() call, the last hash is in hash[i & 1]. */
    crypto_hash(known_user[known_users].hash, hash[i & 1], HASH_SIZE);

    /* Wipe the temporary hash table. */
    memset(hash[0], 0, HASH_SIZE);
    memset(hash[1], 0, HASH_SIZE);

    /* Add this user to the user database. */
    strncpy(known_user[known_users].name, user, MAX_NAME_LEN + 1);
    known_users++;
    return 0; /* Success */
}

Используются два временных слота хэша, потому что обычно функции crypto_hash() ожидают, что результат будет где-то еще, чем источник.

Проверка имени пользователя и пароля становится

int verify_user(const char *user, const char *pass)
{
    const size_t   userlen = (user) ? strlen(user) : 0;
    const size_t   passlen = (pass) ? strlen(pass) : 0;
    unsigned char *temp;
    unsigned char  hash[2][HASH_SIZE];
    size_t         i, u;
    int            result;

    if (userlen < 1)
        return -1; /* Empty username */
    if (userlen > MAX_NAME_LEN)
        return -2; /* Username is too long */
    if (passlen < 1)
        return -3; /* Empty password */

    for (u = 0; i < known_users; i++)
        if (!strcmp(user, known_user[u].name))
            break; /* u will be less than known_users */
    if (u >= known_users)
        return -7; /* No such user */

    /* known_user[u] matches the name. */

    /* Allocate a temporary area for combining this users' salt
       and the supplied password. */
    temp = malloc(HASH_SIZE + passlen + 1);
    if (!temp)
        return -8; /* Out of memory. */

    /* Combine the salt and the password the same way as when
       the user and password was registered. */
    memcpy(temp, known_user[u].salt, SALT_SIZE);
    memcpy(temp + SALT_SIZE, pass, passlen + 1);

    /* Calculate the hash the same way. First pass: */
    crypto_hash(hash[0], temp, SALT_SIZE + passlen + 1);

    /* Temporary buffer is no longer needed, so clear and free it. */
    memset(temp, 0, SALT_SIZE + passlen + 1);
    free(temp);

    /* Repeated rounds, including the final round. */
    for (i = 0; i < HASH_ROUNDS - 1; i++)
        crypto_hash(hash[(i+1) & 1], hash[i & 1], HASH_SIZE);

    /* Because i was incremented after the last crypto_hash() call,
       hash[i & 1] is where the final hash is. Compare. */
    if (!memcmp(hash[i & 1], known_user[u].hash))
        result =  0; /* Valid password for the user! */
    else
        result = -6; /* Wrong password. */

    /* Wipe the temporary hash buffer. */
    memset(hash[0], 0, HASH_SIZE);
    memset(hash[1], 0, HASH_SIZE);

    return result;
}

В практическом приложении лучше всего , а не различать «неизвестный пользователь» и «неправильный пароль» , потому что зная, какие имена пользователей действительны, может облегчить некоторые атаки.

0 голосов
/ 21 ноября 2018

Одной из причин, по которой это не работает с несколькими пользователями, является логическая структура функции login.

while(!feof(fp))
{
    fread(&w[i],sizeof(w[i]),1,fp);
    cekun=strcmp(nama,w[i].nama);
    cekpw=strcmp(pass,w[i].pass);
    if(cekun==0&&cekpw==0)
    {
        system("cls");
        printf("\n\n\n\t\t\tLOGIN SUCCESSFUL!");
        break;
    }
    else if(cekun==0)
    {
        printf("\n\n\n\t\t\tWRONG PASSWORD!");
        printf("\n\n\t\t\t\t  (PRESS [Y] TO RE-LOGIN)");
        if(getch()=='y'||getch()=='Y')
          system("cls"); login();
    }
    else if(cekun!=0&&cekpw!=0)
    {
        printf("\n\n\n\t\t\tYOU ARE NOT REGISTERED\n ...

Этот код будет отклонять любые попытки входа в систему, если имя пользователя и пароль не совпадают первая запись в файле .

Вместо этого вы должны реализовать следующую логику:

for each entry in file
    if names match
        if passwords match
            login success
        else
            login failure
unknown user
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...