Browse Source

Merge branch 'master' of bitbucket.org:ckolivas/ckpool

master
Con Kolivas 10 years ago
parent
commit
04c4f0587f
  1. 25
      pool/base.php
  2. 97
      pool/db.php
  3. 116
      pool/email.php
  4. 7
      pool/page.php
  5. 118
      pool/page_reg.php
  6. 166
      pool/page_reset.php
  7. 70
      pool/prime.php
  8. 2
      sql/ckdb.sql
  9. 17
      sql/initid.sh
  10. 7
      src/ckdb.c

25
pool/base.php

@ -65,6 +65,12 @@ function emailStr($str)
return preg_replace(array($all,$beg,$fin), '', $str);
}
#
function passrequires()
{
return "Passwords require 6 or more characters, including<br>" .
"at least one of each uppercase, lowercase and a digit, but not Tab";
}
#
function safepass($pass)
{
if (strlen($pass) < 6)
@ -171,7 +177,17 @@ function dbd($data, $user)
#
function dbdown()
{
gopage(NULL, 'dbd', 'dbd', NULL, '', '', true, false, false);
gopage(NULL, 'dbd', 'dbd', def_menu(), '', '', true, false, false);
}
#
function syse($data, $user)
{
return "<span class=err><br>System error</span>";
}
#
function syserror()
{
gopage(NULL, 'syse', 'syse', def_menu(), '', '', true, false, false);
}
#
function f404($data)
@ -181,7 +197,7 @@ function f404($data)
#
function do404()
{
gopage(NULL, 'f404', 'f404', NULL, '', '', true, false, false);
gopage(NULL, 'f404', 'f404', def_menu(), '', '', true, false, false);
}
#
function showPage($page, $menu, $name, $user)
@ -200,7 +216,7 @@ function showPage($page, $menu, $name, $user)
#
function showIndex()
{
showPage('index', NULL, '', false);
showPage('index', def_menu(), '', false);
}
#
function offline()
@ -250,7 +266,8 @@ function logout()
function requestRegister()
{
$reg = getparam('Register', false);
if ($reg !== NULL)
$reg2 = getparam('Reset', false);
if ($reg !== NULL || $reg2 !== NULL)
{
logout();
return true;

97
pool/db.php

@ -65,6 +65,12 @@ function repDecode($rep)
return $ans;
}
#
# Convenience function
function zeip()
{
return $_SERVER['REMOTE_ADDR'];
}
#
function msgEncode($cmd, $id, $fields, $user)
{
global $send_sep, $fld_sep, $val_sep;
@ -75,7 +81,7 @@ function msgEncode($cmd, $id, $fields, $user)
$msg .= $name . $val_sep . $value . $fld_sep;
$msg .= 'createcode' . $val_sep . 'php' . $fld_sep;
$msg .= 'createby' . $val_sep . $user . $fld_sep;
$msg .= 'createinet' . $val_sep . $_SERVER['REMOTE_ADDR'];
$msg .= 'createinet' . $val_sep . zeip();
return $msg;
}
#
@ -112,7 +118,7 @@ function checkPass($user, $pass)
{
$passhash = myhash($pass);
$flds = array('username' => $user, 'passwordhash' => $passhash);
$msg = msgEncode('chkpass', 'log', $flds, $user);
$msg = msgEncode('chkpass', 'chkpass', $flds, $user);
$rep = sendsockreply('checkPass', $msg);
if (!$rep)
dbdown();
@ -124,13 +130,24 @@ function setPass($user, $oldpass, $newpass)
$oldhash = myhash($oldpass);
$newhash = myhash($newpass);
$flds = array('username' => $user, 'oldhash' => $oldhash, 'newhash' => $newhash);
$msg = msgEncode('newpass', 'log', $flds, $user);
$msg = msgEncode('newpass', 'newpass', $flds, $user);
$rep = sendsockreply('setPass', $msg);
if (!$rep)
dbdown();
return repDecode($rep);
}
#
function resetPass($user, $newpass)
{
$newhash = myhash($newpass);
$flds = array('username' => $user, 'newhash' => $newhash);
$msg = msgEncode('newpass', 'newpass', $flds, $user);
$rep = sendsockreply('resetPass', $msg);
if (!$rep)
dbdown();
return repDecode($rep);
}
#
function userReg($user, $email, $pass)
{
$passhash = myhash($pass);
@ -204,4 +221,78 @@ function getBlocks($user)
return repDecode($rep);
}
#
# e.g. $atts = array('ua_Reset.str' => 'FortyTwo',
# 'ua_Reset.date' => 'now+3600')
# 'ua_Tanuki.str' => 'Meme',
# 'ua_Tanuki.date' => 'now');
function setAtts($user, $atts)
{
if ($user == false)
showIndex();
$flds = array_merge(array('username' => $user), $atts);
$msg = msgEncode('setatts', 'setatts', $flds, $user);
$rep = sendsockreply('setAtts', $msg);
if (!$rep)
dbdown();
return repDecode($rep);
}
#
# e.g. $attlist = 'Reset.str,Reset.dateexp,Tanuki.str,Tanuki.date'
function getAtts($user, $attlist)
{
if ($user == false)
showIndex();
$flds = array('username' => $user, 'attlist' => $attlist);
$msg = msgEncode('getatts', 'getatts', $flds, $user);
$rep = sendsockreply('getAtts', $msg);
if (!$rep)
dbdown();
return repDecode($rep);
}
#
# e.g. $attlist = 'Reset,Tanuki'
# effectively makes the useratts disappear (i.e. expired)
function expAtts($user, $attlist)
{
if ($user == false)
showIndex();
$flds = array('username' => $user, 'attlist' => $attlist);
$msg = msgEncode('expatts', 'expatts', $flds, $user);
$rep = sendsockreply('expAtts', $msg);
if (!$rep)
dbdown();
return repDecode($rep);
}
#
# e.g. $opts = array('oc_BlockAmountHalf.value' => '25',
# 'oc_BlockAmountHalf.height' => '210000',
# 'oc_BlockAmountHalf.date' => '2012-11-28 15:25:01+00',
# 'oc_BlockAmountQuarter.value' => '12.5',
# 'oc_BlockAmountQuarter.height' => '420000');
# *.value is always required
function setOpts($user, $opts)
{
if ($user == false)
showIndex();
$flds = array_merge(array('username' => $user), $opts);
$msg = msgEncode('setopts', 'setopts', $flds, $user);
$rep = sendsockreply('setOpts', $msg);
if (!$rep)
dbdown();
return repDecode($rep);
}
#
# e.g. $optlist = 'KWebURL,BlockAmountQuarter'
function getOpts($user, $optlist)
{
if ($user == false)
showIndex();
$flds = array('username' => $user, 'optlist' => $optlist);
$msg = msgEncode('getopts', 'getopts', $flds, $user);
$rep = sendsockreply('getOpts', $msg);
if (!$rep)
dbdown();
return repDecode($rep);
}
#
?>

116
pool/email.php

@ -0,0 +1,116 @@
<?php
#
# FYI see PEAR::Mail for functions to add for batch email
#
global $eol;
$eol = "\r\n";
#
function fullsend($to, $subject, $message, $headers, $extra = null)
{
if ($extra == null)
$ret = mail($to, $subject, $message, $headers);
else
$ret = mail($to, $subject, $message, $headers, $extra);
if ($ret == false)
error_log("CKPWARN: ".gmdate("Y-m-d H:i:s \\U\\T\\C").
" sendmail failed? to: '$to'");
return $ret;
}
#
function sendnoheader($to, $subject, $message, $emailinfo)
{
global $eol;
if (!isset($emailinfo['KNoReply']))
return false;
$noreply = $emailinfo['KNoReply'];
$headers = "From: $noreply$eol";
$headers .= "X-Mailer: .";
return fullsend($to, $subject, $message, $headers, "-f$noreply");
}
#
function dontReply($emailinfo)
{
global $eol;
if (!isset($emailinfo['KWebURL']))
return false;
$web = $emailinfo['KWebURL'];
$message = "P.S. don't reply to this e-mail, no one will get the reply$eol";
$message .= "There is a contact e-mail address (that changes often)$eol";
$message .= "at $web/ or visit us on FreeNode IRC #ckpool$eol";
return $message;
}
#
function emailEnd($the, $whoip, $emailinfo)
{
global $eol;
$ret = dontReply($emailinfo);
if ($ret === false)
return false;
$message = "This $the was made '".gmdate("Y-M-d H:i:s \\U\\T\\C");
$message .= "' by '$whoip'$eol$eol";
$message .= $ret;
return $message;
}
#
function passWasReset($to, $whoip, $emailinfo)
{
global $eol;
if (!isset($emailinfo['KWebURL']))
return false;
$web = $emailinfo['KWebURL'];
$ret = emailEnd('reset', $whoip, $emailinfo);
if ($ret === false)
return false;
$message = "Your password has been reset.$eol$eol";
$message .= $ret;
return sendnoheader($to, "Password Reset", $message, $emailinfo);
}
#
function passReset($to, $code, $whoip, $emailinfo)
{
global $eol;
if (!isset($emailinfo['KWebURL']))
return false;
$web = $emailinfo['KWebURL'];
$ret = emailEnd('password reset', $whoip, $emailinfo);
if ($ret === false)
return false;
$message = "Someone requested to reset your password.$eol$eol";
$message .= "You can ignore this message since nothing has changed yet,$eol";
$message .= "or click on the link below to reset your password.$eol";
$message .= "$web/index.php?k=reset&code=$code$eol$eol";
$message .= $ret;
return sendnoheader($to, "Password Reset", $message, $emailinfo);
}
#
# getOpts required for email
# If they aren't all setup in the DB then email functions will return false
function emailOptList()
{
return 'KWebURL,KNoReply';
}
#
?>

7
pool/page.php

@ -327,8 +327,7 @@ function pgtop($dotop, $user, $douser)
</tr></table></td><td>
<table cellpadding=0 cellspacing=0 border=0><tr>
<td>&nbsp;<input type=submit name=Login value=Login></td></tr><tr>
<td>&nbsp;&nbsp;
<input type=submit name=Register value=Register></td></tr></table>
<td>&nbsp;&nbsp;<input type=submit name=Register value='Register/Reset'></td></tr></table>
</td></tr></table></form>";
}
else
@ -355,10 +354,6 @@ function pgtop($dotop, $user, $douser)
#
function pgmenu($menus)
{
if ($menus == NULL)
$menus = array('Home'=>array('Home'=>''));
// $menus = array('Home'=>array('Home'=>''),'gap'=>NULL,'Help'=>array('Help'=>'help'));
$ret = "\n<table cellpadding=0 cellspacing=0 border=0 width=100% id=n42>";
$ret .= '<tr><td width=100%>';
$ret .= '<table cellpadding=0 cellspacing=0 border=0 width=100%>';

118
pool/page_reg.php

@ -1,8 +1,9 @@
<?php
#
include_once('socket.php');
include_once('email.php');
#
function doreg($data, $u)
function doregres($data, $u)
{
if (isset($data['user']))
$user = htmlspecialchars($data['user']);
@ -14,7 +15,9 @@ function doreg($data, $u)
else
$mail = '';
$pg = '<h1>Register</h1>';
$pg = '<br><br><table cellpadding=5 cellspacing=0 border=1><tr><td class=dc>';
$pg .= '<h1>Register</h1>';
if (isset($data['error']))
$pg .= "<br><b>".$data['error']." - please try again</b><br><br>";
$pg .= makeForm('');
@ -30,10 +33,33 @@ function doreg($data, $u)
<td class=dl><input type=password name=pass2></td></tr>
<tr><td>&nbsp;</td>
<td class=dl><input type=submit name=Register value=Register></td></tr>
<tr><td colspan=2 class=dc><br><font size=-1>All fields are required</font></td></tr>
<tr><td colspan=2 class=dc><br><font size=-1><span class=st1>*</span>
All fields are required</font></td></tr>
<tr><td colspan=2 class=dc><br>". passrequires() . "</font></td></tr>
</table>
</form>";
$pg.= '</td></tr><tr><td class=dc>';
$pg .= '<h1>Password Reset</h1>';
$pg .= makeForm('');
$pg .= "
<table>
<tr><td class=dr>Username:</td>
<td class=dl><input name=user value=\"$user\"></td></tr>
<tr><td class=dr>Email:</td>
<td class=dl><input name=mail value=''></td></tr>
<tr><td>&nbsp;</td>
<td class=dl><input type=submit name=Reset value=Reset></td></tr>
<tr><td colspan=2 class=dc><br><font size=-1><span class=st1>*</span>
All fields are required</font></td></tr>
<tr><td colspan=2 class=dc><br><font size=-1>
An Email will be sent to you, to let you reset your password</font></td></tr>
</table>
</form>";
$pg .= '</td></tr></table>';
return $pg;
}
#
@ -53,7 +79,7 @@ function doreg2($data)
return $pg;
}
#
function show_reg($page, $menu, $name, $u)
function try_reg($page, $menu, $name, $u)
{
$user = getparam('user', false);
$mail = trim(getparam('mail', false));
@ -80,8 +106,7 @@ function show_reg($page, $menu, $name, $u)
if (safepass($pass) !== true)
{
$ok = false;
$data['error'] = "Password is unsafe - requires 6 or more characters, including<br>" .
"at least one of each uppercase, lowercase and digits, but not Tab";
$data['error'] = "Password is unsafe";
}
elseif ($pass2 != $pass)
{
@ -108,7 +133,86 @@ function show_reg($page, $menu, $name, $u)
$data['error'] = "Invalid username, password or email address";
}
gopage($data, 'doreg', $page, $menu, $name, $u, true, true, false);
gopage($data, 'doregres', $page, $menu, $name, $u, true, true, false);
}
#
function doreset2($data)
{
$user = $data['user'];
$email = $data['email'];
$emailinfo = getOpts($user, emailOptList());
if ($emailinfo['STATUS'] != 'ok')
syserror();
$ans = getAtts($user, 'KLastReset.dateexp');
if ($ans['STATUS'] != 'ok')
syserror();
// If the last attempt hasn't expired don't do anything but show a fake msg
if (!isset($ans['KLastReset.dateexp']) || $ans['KLastReset.dateexp'] == 'Y')
{
// This line $code = isn't an attempt at security -
// it's simply to ensure the username is readable when we get it back
$code = bin2hex($data['user']). '_';
// A code that's large enough to not be worth guessing
$ran = $ans['STAMP'].$user.$email.rand(100000000,999999999);
$hash = hash('md4', $ran);
$ans = setAtts($user, array('ua_KReset.str' => $hash,
'ua_KReset.date' => 'now+3600',
'ua_LastReset.date' => 'now+3600'));
if ($ans['STATUS'] != 'ok')
syserror();
$ok = passReset($email, $code.$hash, zeip(), $emailinfo);
if ($ok === false)
syserror();
}
$pg = '<h1>Reset Sent</h1>';
$pg .= '<br>An Email has been sent that will allow you to';
$pg .= '<br>reset your password.';
$pg .= '<br>If you got your username or email address wrong,';
$pg .= '<br>you wont get the email.';
return $pg;
}
#
function try_reset($page, $menu, $name, $u)
{
$user = getparam('user', false);
$mail = trim(getparam('mail', false));
// Slow this right down
usleep(500000);
$data = array();
if (!nuem($user))
$user = loginStr($user);
if (!nuem($user) && !nuem($mail))
{
$ans = userSettings($user);
if ($ans['STATUS'] == 'ok' && isset($ans['email']) && $ans['email'] == $mail)
{
$data = array('user' => $user, 'email' => $mail);
gopage($data, 'doreset2', $page, $menu, $name, $u, true, true, false);
}
}
gopage($data, 'doregres', $page, $menu, $name, $u, true, true, false);
}
#
function show_reg($page, $menu, $name, $u)
{
$reg = getparam('Register', false);
if ($reg !== NULL)
try_reg($page, $menu, $name, $u);
else
try_reset($page, $menu, $name, $u);
}
#
?>

166
pool/page_reset.php

@ -0,0 +1,166 @@
<?php
#
include_once('socket.php');
include_once('email.php');
#
function allow_reset($error)
{
$pg = '<br><br><table cellpadding=5 cellspacing=0 border=1><tr><td class=dc>';
$pg .= '<h1>Password Reset</h1>';
if ($error !== null)
$pg .= "<br><b>$error - please try again</b><br><br>";
$pg .= makeForm('reset');
$pg .= "
<table>
<tr><td class=dc colspan=2>Enter a new password twice.<br>
" . passrequires() . "
<input type=hidden name=k value=reset></td></tr>
<tr><td class=dr>Password:</td>
<td class=dl><input type=password name=pass></td></tr>
<tr><td class=dr>Retype Password:</td>
<td class=dl><input type=password name=pass2></td></tr>
<tr><td>&nbsp;</td>
<td class=dl><input type=submit name=Update value=Update></td></tr>
<tr><td colspan=2 class=dc><br><font size=-1><span class=st1>*</span>
All fields are required</font></td></tr>
</table>
</form>";
$pg .= '</td></tr></table>';
return $pg;
}
#
function yok()
{
$pg = '<h1>Password Reset</h1>';
$pg .= '<br>Your password has been reset,';
$pg .= '<br>login with it on the Home page.';
return $pg;
}
#
function resetfail()
{
if (isset($_SESSION['reset_user']))
unset($_SESSION['reset_user']);
if (isset($_SESSION['reset_hash']))
unset($_SESSION['reset_hash']);
if (isset($_SESSION['reset_email']))
unset($_SESSION['reset_email']);
$pg = '<h1>Reset Failed</h1>';
$pg .= '<br>Try again from the Home page Register/Reset button later';
return $pg;
}
#
function dbreset()
{
$user = $_SESSION['reset_user'];
$hash = $_SESSION['reset_hash'];
$email = $_SESSION['reset_email'];
$pass = getparam('pass', true);
$pass2 = getparam('pass2', true);
if (nuem($pass) || nuem($pass2))
return allow_reset('Enter both passwords');
if ($pass2 != $pass)
return allow_reset("Passwords don't match");
if (safepass($pass) !== true)
return allow_reset('Password is unsafe');
$ans = getAtts($user, 'KReset.str,KReset.dateexp');
if ($ans['STATUS'] != 'ok')
return resetfail();
if (!isset($ans['KReset.dateexp']) || $ans['KReset.dateexp'] == 'Y')
return resetfail();
if (!isset($ans['KReset.str']) || $ans['KReset.str'] != $hash)
return resetfail();
$emailinfo = getOpts($user, emailOptList());
if ($emailinfo['STATUS'] != 'ok')
syserror();
$ans = resetPass($user, $pass);
if ($ans['STATUS'] != 'ok')
syserror();
unset($_SESSION['reset_user']);
unset($_SESSION['reset_hash']);
unset($_SESSION['reset_email']);
$ans = expAtts($user, 'KReset');
$ok = passWasReset($email, zeip(), $emailinfo);
return yok();
}
#
function doreset($data, $u)
{
// Slow this right down
usleep(500000);
if (isset($_SESSION['reset_user'])
&& isset($_SESSION['reset_hash'])
&& isset($_SESSION['reset_email']))
return dbreset();
$code = getparam('code', true);
if (nuem($code))
return resetfail();
$codes = explode('_', $code, 2);
if (sizeof($codes) != 2)
return resetfail();
$userhex = $codes[0];
if (strlen($userhex) == 0 || strlen($userhex) % 2)
return resetfail();
$user = loginStr(pack("H*" , $userhex));
$hash = preg_replace('/[^A-Fa-f0-9]/', '', $codes[1]);
if (!nuem($user) && !nuem($hash))
{
$ans = getAtts($user, 'KReset.str,KReset.dateexp');
if ($ans['STATUS'] != 'ok')
return resetfail();
if (!isset($ans['KReset.dateexp']) || $ans['KReset.dateexp'] == 'Y')
return resetfail();
if (!isset($ans['KReset.str']) || $ans['KReset.str'] != $hash)
return resetfail();
$ans = userSettings($user);
if ($ans['STATUS'] != 'ok')
return resetfail();
if (!isset($ans['email']))
return resetfail();
$email = $ans['email'];
$_SESSION['reset_user'] = $user;
$_SESSION['reset_hash'] = $hash;
$_SESSION['reset_email'] = $email;
return allow_reset(null);
}
return resetfail();
}
#
function show_reset($page, $menu, $name, $u)
{
gopage(array(), 'doreset', $page, $menu, $name, $u, true, true, false);
}
#
?>

70
pool/prime.php

@ -6,28 +6,8 @@ $stt = microtime();
include_once('param.php');
include_once('base.php');
#
function process($p, $user)
function process($p, $user, $menu)
{
$menu = array(
'Home' => array(
'Home' => ''
),
'Account' => array(
'Workers' => 'workers',
'Payments' => 'payments',
'Settings' => 'settings'
),
'Pool' => array(
'Stats' => 'stats',
'Blocks' => 'blocks'
),
'Admin' => NULL,
'gap' => NULL,
'Help' => array(
'Help' => 'help',
'Payouts' => 'payout'
)
);
if ($user == 'Kano' || $user == 'ckolivas' || $user == 'wvr2' || $user == 'aphorise')
{
$menu['Admin']['ckp'] = 'ckp';
@ -35,7 +15,10 @@ function process($p, $user)
$menu['Admin']['AllWork'] = 'allwork';
}
else
{
if (isset($menu['Admin']))
unset($menu['Admin']);
}
$page = '';
$n = '';
foreach ($menu as $item => $options)
@ -53,21 +36,60 @@ function process($p, $user)
showPage($page, $menu, $n, $user);
}
#
function def_menu()
{
$dmenu = array('Home' => array('Home' => ''),
'gap' => NULL,
'Help' => array('Help' => 'help',
'Payouts' => 'payout'));
return $dmenu;
}
#
function check()
{
$dmenu = def_menu();
$menu = array(
'Home' => array(
'Home' => ''
),
'Account' => array(
'Workers' => 'workers',
'Payments' => 'payments',
'Settings' => 'settings'
),
'Pool' => array(
'Stats' => 'stats',
'Blocks' => 'blocks'
),
'Admin' => NULL,
'gap' => NULL,
'Help' => array(
'Help' => 'help',
'Payouts' => 'payout'
)
);
tryLogInOut();
$who = loggedIn();
if ($who === false)
{
$p = getparam('k', true);
if ($p == 'reset')
showPage('reset', $dmenu, '', $who);
else
{
if (requestRegister() == true)
showPage('reg', NULL, '', $who);
showPage('reg', $dmenu, '', $who);
else
showIndex();
{
$p = getparam('k', true);
process($p, $who, $dmenu);
}
}
}
else
{
$p = getparam('k', true);
process($p, $who);
process($p, $who, $menu);
}
}
#

2
sql/ckdb.sql

@ -415,4 +415,4 @@ CREATE TABLE version (
PRIMARY KEY (vlock)
);
insert into version (vlock,version) values (1,'0.9');
insert into version (vlock,version) values (1,'0.9.2');

17
sql/initid.sh

@ -2,25 +2,10 @@
#
fldsep="`echo -e '\x09'`"
#
dsp()
{
cut -c4-
# echo
}
process()
{
# <256
len=${#1}
oct="`printf '%03o' "$len"`"
code="`printf "\\\\$oct"`"
all="$code$zero$zero$zero$1"
printf "$code\\0\\0\\000$1" | nc -U -w 1 /opt/ckdb/listener | dsp
}
#
addid()
{
msg="newid.$1.idname=$1${fldsep}idvalue=$2"
process "$msg"
echo "$msg"
}
#
# Default to yyyymmddXXXXXX

7
src/ckdb.c

@ -49,7 +49,7 @@
#define DB_VLOCK "1"
#define DB_VERSION "0.9.2"
#define CKDB_VERSION DB_VERSION"-0.313"
#define CKDB_VERSION DB_VERSION"-0.320"
#define WHERE_FFL " - from %s %s() line %d"
#define WHERE_FFL_HERE __FILE__, __func__, __LINE__
@ -2710,7 +2710,7 @@ static K_ITEM *find_userid(int64_t userid)
return find_in_ktree(userid_root, &look, cmp_userid, ctx);
}
// TODO: endian?
// TODO: endian? (to avoid being all zeros?)
static void make_salt(USERS *users)
{
long int r1, r2, r3, r4;
@ -2976,13 +2976,14 @@ static K_ITEM *users_add(PGconn *conn, char *username, char *emailaddress,
STRNCPY(row->username, username);
row->status[0] = '\0';
STRNCPY(row->emailaddress, emailaddress);
STRNCPY(row->passwordhash, passwordhash);
snprintf(tohash, sizeof(tohash), "%s&#%s", username, emailaddress);
HASH_BER(tohash, strlen(tohash), 1, hash, tmp);
__bin2hex(row->secondaryuserid, (void *)(&hash), sizeof(hash));
make_salt(row);
password_hash(row->username, passwordhash, row->salt,
row->passwordhash, sizeof(row->passwordhash));
HISTORYDATEINIT(row, cd, by, code, inet);
HISTORYDATETRANSFER(trf_root, row);

Loading…
Cancel
Save