[TUT] MySql - Запазване / Зареждане / Полезни неща

В този раздел можете да намерите полезни кодове и уроци свързани с PAWN скриптинга.
Аватар
JustInCase
Извън линия
Потребител
Потребител
Мнения: 582
Регистриран на: 14 Окт 2016, 23:31
Се отблагодари: 4 пъти
Получена благодарност: 15 пъти

[TUT] MySql - Запазване / Зареждане / Полезни неща

Мнение от JustInCase » 06 Яну 2017, 15:34

Здравейте, в този урок ще ви покажа как да използвате MySql в вашите плъгини. Било то как се запазва как се зарежда и други полезни работи.

За пример ще ползваме прост плъгин за XP Mod.

Внимание, имайте предвид, че за да разберете нещо трябва да имате познания по PAWN.


Ще започнем с главната част от плъгина.

Код за потвърждение: Избери целия код

#include <amxmodx>

#define PLUGIN "Tutorial"
#define VERSION "1.0"
#define AUTHOR "Just In Case"

new iExp[33]

// Pcvar-ове
new cKill
new cHeadshot
new cDeath

public plugin_init() {
    register_plugin(PLUGIN, VERSION, AUTHOR)
    
    register_event("DeathMsg", "Event_DeathMsg", "a") // Register death event
    
    // Регистрираме PCVAR-овете
    cKill = register_cvar("exp_kill", "2")
    cHeadshot = register_cvar("exp_headshot", "4")
    cDeath = register_cvar("exp_death", "1")
}

public Event_DeathMsg()
{
    new iKiller = read_data(1) // Взимаме информация за убиеца и жертвата
    new iVictim = read_data(2)
    
    if(is_user_alive(iKiller)) // Проверяваме дали убиеца е жив в случай, че се е самоубил
    {
        if(read_data(3)) // Проверяваме дали е HEADSHOT
        {
            iExp[iKiller] += get_pcvar_num(cHeadshot) // Добавяме PCvar стойността за HEADSHOT към iExp (XP-то)
        }
        else
        {
            iExp[iKiller] += get_pcvar_num(cKill)
        }
    }
    iExp[iVictim] -= get_pcvar_num(cDeath) // Махаме стойността при смърт
}
И започваме да добавяме MySql към него

Първо вкарваме библиотеката :

Код за потвърждение: Избери целия код

#include <sqlx>
И след това започваме да правим MySql Възела ( Където ще установим връзката към MySql сървъра )

Код за потвърждение: Избери целия код

public MySql_Init()
{
    //Казваме на API, че това е информацията към която е нужна за свързването
    // just not yet. basically it's like storing it in global variables
    //не още. Всъщност съхраняваме я във глобални променливи
    g_SqlTuple = SQL_MakeDbTuple(Host,User,Pass,Db)
    
    // Добре, вече сме готови да се свържем
    new ErrorCode,Handle:SqlConnection = SQL_Connect(g_SqlTuple,ErrorCode,g_Error,charsmax(g_Error))
    if(SqlConnection == Empty_Handle)
        // Спираме плъгина с съобщение ГРЕШКА (Error)
    set_fail_state(g_Error)
    
    new Handle:Queries
    // we must now prepare some random queries
    // Сега трябва да се подготвим за някой произволни запитвания
    Queries = SQL_PrepareQuery(SqlConnection,"CREATE TABLE IF NOT EXISTS tutorial (steamid varchar(32),exp INT(11))")
    
    if(!SQL_Execute(Queries))
    {
        // Ако има някакви проблеми плъгина ще се зададе като Bad Load (Лошо зареден - Плъгина няма да тръгне)
        SQL_QueryError(Queries,g_Error,charsmax(g_Error))
        set_fail_state(g_Error)
        
    }
    
    // Освобождаваме запитванията
    SQL_FreeHandle(Queries)
    
    // Освобождаваме всичко с SQL_FreeHandle
    SQL_FreeHandle(SqlConnection)   
}
За да избегнем грешки използваме FreeHandle в plugin_end()

Код за потвърждение: Избери целия код

public plugin_end()
{
    SQL_FreeHandle(g_SqlTuple)
}
Сега ни трябва код, с който да запазваме / взимаме неговото XP

Код за потвърждение: Избери целия код

public Save_MySql(id)
{
    new szName[32], szTemp[512]
    get_user_name(id, szName, charsmax(szName))
    
    // Тук ще ъпдейтваме XP-то на играча но само ако името съвпадне с някое от имената в таблицата
    format(szTemp,charsmax(szTemp),"UPDATE `tutorial` SET `exp` = '%i' WHERE `tutorial`.`steamid` = '%s';",iExp[id], szSteamId)
    SQL_ThreadQuery(g_SqlTuple,"IgnoreHandle",szTemp)
}
public Load_MySql(id)
{
    new szName[32], szTemp[512]
    get_user_name(id, szName, charsmax(szName))
    
    new Data[1]
    Data[0] = id
    
    //Сега ще вземем от таблица "tutorial" информация, когато Nick-a
    format(szTemp,charsmax(szTemp),"SELECT * FROM `tutorial` WHERE (`tutorial`.`steamid` = '%s')", szName)
    SQL_ThreadQuery(g_SqlTuple,"register_client",szTemp,Data,1)
}public register_client(FailState,Handle:Query,Error[],Errcode,Data[],DataSize)
{
    if(FailState == TQUERY_CONNECT_FAILED)
    {
        log_amx("Load - Could not connect to SQL database.  [%d] %s", Errcode, Error)
    }
    else if(FailState == TQUERY_QUERY_FAILED)
    {
        log_amx("Load Query failed. [%d] %s", Errcode, Error)
    }
    
    new id
    id = Data[0]
    
    if(SQL_NumResults(Query) < 1) 
    {
        
        new szSteamId[32]
        get_user_authid(id, szSteamId, charsmax(szSteamId)) // get user's steamid
        
        //  Ако още изчакваме не може да направим нищо
        if (equal(szSteamId,"ID_PENDING"))
            return PLUGIN_HANDLED
        
        new szTemp[512]
        
        // Сега ще сложим нашите данни в нашата таблица
        format(szTemp,charsmax(szTemp),"INSERT INTO `tutorial` ( `steamid` , `exp`)VALUES ('%s','0');",szName)
        SQL_ThreadQuery(g_SqlTuple,"IgnoreHandle",szTemp)
    } 
    else 
    {
        // Ако няма намерени резултати
        iExp[id]         = SQL_ReadResult(Query, 1)
    }
    
    return PLUGIN_HANDLED
}
Крайният код, който получихме изглежда така.

Код за потвърждение: Избери целия код

#include <amxmodx>
#include <sqlx>

#define PLUGIN "Tutorial"
#define VERSION "1.0"
#define AUTHOR "Just In Case"

// Ur Mysql Information - Тук попълвате данните на SQL сървърът ви
new Host[]     = "hostname"
new User[]    = "username"
new Pass[]     = "password"
new Db[]     = "database"


new Handle:g_SqlTuple
new g_Error[512]

new iExp[33]

// Pcvar-ове
new cKill
new cHeadshot
new cDeath

public plugin_init() {
    register_plugin(PLUGIN, VERSION, AUTHOR)
    
    register_event("DeathMsg", "Event_DeathMsg", "a") // Register death event
    
    // Регистрираме PCVAR-овете
    cKill = register_cvar("exp_kill", "2")
    cHeadshot = register_cvar("exp_headshot", "4")
    cDeath = register_cvar("exp_death", "1")
}
public MySql_Init()
{
    //Казваме на API, че това е информацията към която е нужна за свързването
    // just not yet. basically it's like storing it in global variables
    //не още. Всъщност съхраняваме я във глобални променливи
    g_SqlTuple = SQL_MakeDbTuple(Host,User,Pass,Db)
    
    // Добре, вече сме готови да се свържем
    new ErrorCode,Handle:SqlConnection = SQL_Connect(g_SqlTuple,ErrorCode,g_Error,charsmax(g_Error))
    if(SqlConnection == Empty_Handle)
        // Спираме плъгина с съобщение ГРЕШКА (Error)
    set_fail_state(g_Error)
    
    new Handle:Queries
    // we must now prepare some random queries
    // Сега трябва да се подготвим за някой произволни запитвания
    Queries = SQL_PrepareQuery(SqlConnection,"CREATE TABLE IF NOT EXISTS tutorial (name varchar(32),exp INT(11))")
    
    if(!SQL_Execute(Queries))
    {
        // Ако има някакви проблеми плъгина ще се зададе като Bad Load (Лошо зареден - Плъгина няма да тръгне)
        SQL_QueryError(Queries,g_Error,charsmax(g_Error))
        set_fail_state(g_Error)
        
    }
    
    // Освобождаваме запитванията
    SQL_FreeHandle(Queries)
    
    // Освобождаваме всичко с SQL_FreeHandle
    SQL_FreeHandle(SqlConnection)   
}
public plugin_end()
{
    SQL_FreeHandle(g_SqlTuple)
}
public Save_MySql(id)
{
    new szName[32], szTemp[512]
    get_user_name(id, szName, charsmax(szName))
    
    // Тук ще ъпдейтваме XP-то на играча но само ако името съвпадне с някое от имената в таблицата
    format(szTemp,charsmax(szTemp),"UPDATE `tutorial` SET `exp` = '%i' WHERE `tutorial`.`name` = '%s';",iExp[id], szName)
    SQL_ThreadQuery(g_SqlTuple,"IgnoreHandle",szTemp)
}
public Load_MySql(id)
{
    new szName[32], szTemp[512]
    get_user_name(id, szName, charsmax(szName))
    
    new Data[1]
    Data[0] = id
    
    //Сега ще вземем от таблица "tutorial" информация, когато Nick-a
    format(szTemp,charsmax(szTemp),"SELECT * FROM `tutorial` WHERE (`tutorial`.`name` = '%s')", szName)
    SQL_ThreadQuery(g_SqlTuple,"register_client",szTemp,Data,1)
}
public Event_DeathMsg()
{
    new iKiller = read_data(1) // Взимаме информация за убиеца и жертвата
    new iVictim = read_data(2)
    
    if(is_user_alive(iKiller)) // Проверяваме дали убиеца е жив в случай, че се е самоубил
    {
        if(read_data(3)) // Проверяваме дали е HEADSHOT
        {
            iExp[iKiller] += get_pcvar_num(cHeadshot) // Добавяме PCvar стойността за HEADSHOT към iExp (XP-то)
        }
        else
        {
            iExp[iKiller] += get_pcvar_num(cKill)
        }
    }
    iExp[iVictim] -= get_pcvar_num(cDeath) // Махаме стойността при смърт (от PCVAR-a за взимане на XP при смърт) 
}
public register_client(FailState,Handle:Query,Error[],Errcode,Data[],DataSize)
{
    if(FailState == TQUERY_CONNECT_FAILED)
    {
        log_amx("Load - Could not connect to SQL database.  [%d] %s", Errcode, Error)
    }
    else if(FailState == TQUERY_QUERY_FAILED)
    {
        log_amx("Load Query failed. [%d] %s", Errcode, Error)
    }
    
    new id
    id = Data[0]
    
    if(SQL_NumResults(Query) < 1) //Ако няма записи
    {
        
        new szName[32]
        get_user_name(id, szName, charsmax(szName)) // Взимаме името на играча
        
        //  Ако още изчакваме не може да направим нищо
        if (equal(szName,"ID_PENDING"))
            return PLUGIN_HANDLED
        
        new szTemp[512]
        
        // Сега ще сложим нашите данни в нашата таблица
        format(szTemp,charsmax(szTemp),"INSERT INTO `tutorial` ( `name` , `exp`)VALUES ('%s','0');",szName)
        SQL_ThreadQuery(g_SqlTuple,"IgnoreHandle",szTemp)
    } 
    else //Ако има записи
    {

        iExp[id]         = SQL_ReadResult(Query, 1)
    }
    
    return PLUGIN_HANDLED
}

Добре е да знаете също :

Взимане на Ранк:

Код за потвърждение: Избери целия код

public Show_Rank(id) // Трябв да регистрирате команда за тази функция
{
    for(new i; i < MaxPlayers; i++)
    {
        if(is_user_connected(i))
            Save_MySql(i) // Запазваме всички данни за да дадем точен rank
    }
    
    new Data[1]
    Data[0] = id
    
    new szTemp[512]
    format(szTemp,charsmax(szTemp),"SELECT COUNT(*) FROM `tutorial` WHERE `exp` >= %d", Exp[id])
    // Select the count where the exp is matching or higher (Incase of equal exp)
    // Избираме броя където XP-то е еднакво или по високо ( В случай с еднакво XP )
    SQL_ThreadQuery(g_SqlTuple,"Sql_Rank",szTemp,Data,1)
    
    return PLUGIN_CONTINUE
}

public Sql_Rank(FailState,Handle:Query,Error[],Errcode,Data[],DataSize)
{
    if(FailState == TQUERY_CONNECT_FAILED)
        log_amx("Load - Could not connect to SQL database.  [%d] %s", Errcode, Error)
    else if(FailState == TQUERY_QUERY_FAILED)
        log_amx("Load Query failed. [%d] %s", Errcode, Error)
    
    new count = 0
    count = SQL_ReadResult(Query,0)
    if(count == 0)
        count = 1
    
    new id
    id = Data[0]
    
    client_print(id, print_chat, "You're rank is %i with %i exp", count, Exp[id]); //Съобщението с което изписваме на играча, кой ранк е
    
    return PLUGIN_HANDLED
}
Как да запазваме плаващи числа :

Код за потвърждение: Избери целия код

//Пример
Queries[0] = SQL_PrepareQuery(SqlConnection, "CREATE TABLE IF NOT EXISTS %s (name varchar(32), yourfloat FLOAT(11,3))", szTableName);

// ПЛАВАЩО число ( 11,3 )
// 11 = Максималното число
// 3 = Максималното число зад запетаята

// Зареждане на информация

new Float: FloatA = 11,3
SQL_ReadResult( Query, Column, FloatA );
Как да трием редове в таблицата :

Код за потвърждение: Избери целия код

new szTemp[256], Data[1];

Data[0] = id

format(szTemp,charsmax(szTemp),"DELETE FROM `%s` WHERE `Name` = '%s'", szMainTable, szName);
SQL_ThreadQuery(SqlTuple,"IgnoreHandle",szTemp,Data,1);
Най-често срещани проблеми поради, които урокът няма да стане:
  • SQL сървърът ви не поддържа външни връзки.
  • 1 от 4 данни за връзка са грешни (username, password, hostname, databasename)

Ако имате някакви допълнения ще се радвам да ги споделите за да може да ги добавим към урока.
Ако откриете грешки ги споделете за да ги отстраня.


Урокът е взет от ТУК


Преведен е от мен : JustInCase


И ЗАБРАНЯВАМ КОПИРАНЕТО НА ПРЕВОДА ПО ДРУГИ САЙТОВЕ И ФОРУМИ !!!

:bg:
Последно промяна от JustInCase на 14 Сеп 2020, 22:50, променено общо 1 път.
MANSION - HNS : 45.144.155.99:27026
ONLINE

Аватар
x7s
Извън линия
Потребител
Потребител
Мнения: 47
Регистриран на: 20 Яну 2018, 11:00
Местоположение: България
Се отблагодари: 19 пъти
Обратна връзка:

[TUT] MySql - Запазване / Зареждане / Полезни неща

Мнение от x7s » 20 Яну 2018, 19:39

Ще е хубаво, ако Автора на темата я обнови.
Благодаря предварително.
Става въпрос за библиотеката която е използвал в урока:

Код за потвърждение: Избери целия код

#include <mysql>  
Трябва да е:

Код за потвърждение: Избери целия код

#include <sqlx>
:tnx:

Аватар
sianbg
Извън линия
Потребител
Потребител
Мнения: 232
Регистриран на: 13 Ное 2017, 12:18
Получена благодарност: 1 път
Обратна връзка:

[TUT] MySql - Запазване / Зареждане / Полезни неща

Мнение от sianbg » 07 Фев 2018, 19:04

Благодаря за урока. Има нещо, което ме притеснява. Никъде не виждам prepare stmt или някакъв escape на входните данни. Специално обръщам внимание на името на играча.

Код за потвърждение: Избери целия код

format(szTemp,charsmax(szTemp),"DELETE FROM `%s` WHERE `Name` = '%s'", szMainTable, szName);
така си форматираш заявката. Формата обаче мисля, че няма да ескейпне ',",; и др. Следователно, ако имам sql инжекция в името мога да изтрия всички таблици и данни. Мога да променям и общо взето каквото си искам. Потърсих няколко плъгина, които се ползват и учудващо навсякъде е така. Няма да споменавам плъгините, защото някой може да се възползва. Не че не казах достатъчно, но все пак ... Тук проблема е малко по-голям от колкото на php старото mysql_query. Там не можеш да изпълняваш 2 заявки в 1. Тук обаче можеш. Следователно имаш пълен достъп до mysql-a.

https://forums.alliedmods.net/showthread.php?p=1453064 - това мисля, че е хубава библиотека за работа с mysql. Има ескейп функция. Можете ръчно да си направите. Функцията е взета от amxbans,където програмистите са се сетили за това :)

Код за потвърждение: Избери целия код

stock mysql_escape_string(dest[],len)
{
    //copy(dest, len, source);
    replace_all(dest,len,"\\","\\\\");
    replace_all(dest,len,"\0","\\0");
    replace_all(dest,len,"\n","\\n");
    replace_all(dest,len,"\r","\\r");
    replace_all(dest,len,"\x1a","\Z");
    replace_all(dest,len,"'","\'");
    replace_all(dest,len,"^"","\^"");
    return dest;
}  
Използване

Код за потвърждение: Избери целия код

new szName[96]
mysql_escape_string(szName,charsmax(szName))
format(szTemp,charsmax(szTemp),"INSERT INTO `orewest` ( `auth` , `minutes`) VALUES ('%s','0');",szName)  

Линк към темата с информацията, която намерих. https://forums.alliedmods.net/showthread.php?t=251556 Просто и аз се бях заблудил, че това е сигурно, но не е.

Аватар
TheRedShoko
Извън линия
Модератор
Модератор
Мнения: 1016
Регистриран на: 06 Окт 2016, 07:42
Местоположение: Бургас
Се отблагодари: 5 пъти
Получена благодарност: 84 пъти

[TUT] MySql - Запазване / Зареждане / Полезни неща

Мнение от TheRedShoko » 07 Фев 2018, 22:34

Без проба не може да се разбере, но разгледах source на AMXX-a и видях, че в SQL модула има функция за escape-ване на SQL query-та. Все пак трябва някой да го пробва да направи SQL injection и да каже дали е успял.

Аватар
sianbg
Извън линия
Потребител
Потребител
Мнения: 232
Регистриран на: 13 Ное 2017, 12:18
Получена благодарност: 1 път
Обратна връзка:

[TUT] MySql - Запазване / Зареждане / Полезни неща

Мнение от sianbg » 08 Фев 2018, 13:45

Нямаше нужда да се тества, защото аз виждам теоретично нещата и съм на 100% сигурен, че е дупка. Реших да тествам и отново да докажа думите си с доказателства.

Плъгина с който тествах. https://forums.alliedmods.net/showthread.php?t=89782 . Казаха ми, че е един от най-добрите автори на плъгини. Еми sql не му е силата явно.

Потребителското име с, което изпълних sql injection. Само да кажа, че sqlx подържа multi query, а не е както в php mysql_query да може само 1 заявка.

Код за потвърждение: Избери целия код

sianbg'); DROP TABLE mytable; --
Така си изтрих таблицата. Мога да изтрия цялата база дори и други бази, ако потребителя има достъп. Ако не спазвате принципите разделяй и владей и ползвате 1 потребител за всички бази ...


Мога да намеря 20 сървъра сега и да ги хакна. Просто това е много голяма дупка в сигурността и се чудя как така 10 години никой не се сети.

Добавено преди 3 минути 33 секунди:
сега се сетих да проверя плъгините за vip. Там 100% ще е mysql, защото масово се купуват от сайт-а. Демек тряя сървъра да ги вземе през mysql. Ако може да ми дадете най-използваните плъгини за това да ги прегледам.

Аватар
TheRedShoko
Извън линия
Модератор
Модератор
Мнения: 1016
Регистриран на: 06 Окт 2016, 07:42
Местоположение: Бургас
Се отблагодари: 5 пъти
Получена благодарност: 84 пъти

[TUT] MySql - Запазване / Зареждане / Полезни неща

Мнение от TheRedShoko » 08 Фев 2018, 13:53

С коя версия на AMXX си?

Аватар
sianbg
Извън линия
Потребител
Потребител
Мнения: 232
Регистриран на: 13 Ное 2017, 12:18
Получена благодарност: 1 път
Обратна връзка:

[TUT] MySql - Запазване / Зареждане / Полезни неща

Мнение от sianbg » 08 Фев 2018, 14:04

1.8.3 - Това няма значение. Sql-a си е sql.

Добавено преди 6 минути 59 секунди:
https://forums.alliedmods.net/showthread.php?p=1637334 - тук същата история. Има още много много плъгини с тази огромна грешка ....

Аватар
TheRedShoko
Извън линия
Модератор
Модератор
Мнения: 1016
Регистриран на: 06 Окт 2016, 07:42
Местоположение: Бургас
Се отблагодари: 5 пъти
Получена благодарност: 84 пъти

[TUT] MySql - Запазване / Зареждане / Полезни неща

Мнение от TheRedShoko » 08 Фев 2018, 14:55

Пробвай последния build. Има значение как работи отдолу. Видях, че се извиква функция за обезопасяване на параметрите и би трябвало да не прави проблеми...

Аватар
sianbg
Извън линия
Потребител
Потребител
Мнения: 232
Регистриран на: 13 Ное 2017, 12:18
Получена благодарност: 1 път
Обратна връзка:

[TUT] MySql - Запазване / Зареждане / Полезни неща

Мнение от sianbg » 08 Фев 2018, 15:06

Последен build е. Има функция за ескейп, но програмиста не го е използвал. Това е проблема. Няма как самият модул да ескейпва всичко. Просто не работи така mysql. Правилно е да се използва SQL_PrepareQuery. В урока странното е,че се ползва за създаване на таблицата, но за добавяне на информация се ползва format. Проблема не е amxmodx или нещо подобно. Проблема е в програмистите на плъгини. Трябва да се ползва prepare stmt за всичко, а не format на заявката. Това е фундаментална грешка. Проверих най-използваните плъгини като mysql gag на kostov. Просто всеки, който слага нещо с mysql моля да провери това в плъгина. И моля да се промени в урока, защото това ще обърка много млади програмисти.

Публикувай отговор
  • Подобни теми
    Отговори
    Преглеждания
     Последно мнение

Обратно към “Полезни кодове/уроци”

Кой е на линия

Потребители разглеждащи този форум: 0 регистрирани и 5 госта