Сделал такой код:
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. 96. 97. 98. 99. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157. 158. 159. 160. 161. 162. 163. 164. 165. 166. 167. 168. 169. 170. 171. 172. 173. 174. 175. 176. 177. 178. 179. 180. 181. 182. 183. 184. 185. 186. 187. 188. 189. 190. 191. 192. 193. 194. 195. 196. 197. 198. 199. 200. 201. 202. 203. 204. 205. 206. 207. 208. 209. 210. 211. 212. 213. 214. 215. 216. 217. 218. 219. 220. 221. 222. 223. 224. 225. 226. 227. 228. 229. 230. 231. 232. 233. 234. 235. 236. 237. 238. 239. 240. 241. 242. 243. 244. 245. 246. 247. 248. 249. 250. 251. 252. 253. 254. 255. 256. 257. 258. 259. 260. 261. 262. 263. 264. 265. 266. 267. 268. 269. 270. 271. 272. 273. 274. 275. 276. 277. 278. 279. 280. 281. 282. 283. 284. 285. 286. 287. 288. 289. 290. 291. 292. 293. 294. 295. 296. 297. 298. 299. 300. 301. 302. 303. 304. 305. 306. 307. 308. 309. 310. 311. 312. 313. 314. 315. 316. 317. 318. 319. 320. 321. 322. 323. 324. 325. 326. 327. 328. 329. 330. 331. 332. 333. 334. 335. 336. 337. 338. 339. 340. 341. 342. 343. 344. 345. 346. 347. 348. 349. 350. 351. 352. 353. 354. 355. 356. 357. 358. 359. 360. 361. 362. 363. 364. 365. 366. 367. 368. 369. 370. 371. 372. 373. 374. 375. 376. 377. 378. 379. 380. 381. 382. 383. 384. 385. 386. 387. 388. 389. 390. 391. 392. 393. 394. 395. 396. 397. 398. 399. 400. 401. 402. 403. 404. 405. 406. 407. 408. 409. 410. 411. 412. 413. 414. 415. 416. 417. 418. 419.
<?php
// Получить значение из ассоциированного массива
function _get($array, $key)
{
if (!isset($array, $key)) return;
if (empty($array)) return;
if (is_array($array))
{
if (array_key_exists($key, $array)) return $array[$key];
}
}
// Аналог функции NVL
function _nvl($expr, $null=null, $value=null)
{
if (isset($expr))
{
return isset($value) ? $value : $expr;
}
else
{
return $null;
}
}
// Модуль для работы с RADIUS-сервером
class Radius
{
// Типы RADIUS-пакетов
const RAD_ACCESS_REQUEST = 1;
const RAD_ACCESS_ACCEPT = 2;
const RAD_ACCESS_REJECT = 3;
const RAD_ACCOUNTING_REQUEST = 4;
const RAD_ACCOUNTING_RESPONSE = 5;
const RAD_ACCOUNTING_STATUS = 6;
const RAD_ACCESS_CHALLENGE = 11;
const RAD_DISCONNECT_REQUEST = 40;
const RAD_DISCONNECT_ACCEPT = 41;
const RAD_DISCONNECT_REJECT = 42;
const RAD_COA_REQUEST = 43;
const RAD_COA_ACCEPT = 44;
const RAD_COA_ACK = 44;
const RAD_COA_REJECT = 45;
const RAD_COA_NAK = 45;
private $rad;
// Режим отладки класса
static public function debug($mode=null)
{
static $debug;
if (isset($mode)) $debug = (boolean)$mode;
return $debug;
}
// Получить или вернуть код пакета по имени
static public function type($find, $reverse=false)
{
$ref = new ReflectionClass('Radius');
$cl = $ref->getConstants();
foreach ($cl as $name=>$value)
{
if ($reverse)
{
if ($find == $value) return $name;
}
else
{
if ($find == $name) return $value;
}
}
$cl = get_defined_constants(true);
if (array_key_exists('radius', $cl))
{
foreach ($cl['radius'] as $name=>$value)
{
if ($reverse)
{
if ($find == $value) return $name;
}
else
{
if ($find == $name) return $value;
}
}
}
}
// Конструктор
function __construct($template=null)
{
$rad = radius_auth_open();
$cfg = _cfg('radius', true);
$cfg = $cfg[$template];
if (!radius_add_server($rad, $cfg['host'], $cfg['port'], $cfg['secret'], 3, 3))
{
trigger_error('RADIUS connect error:' . radius_strerror($rad), E_USER_NOTICE);
return;
}
$this->dict();
$this->rad = $rad;
}
// Деструктор
function __destruct()
{
if (isset($this->rad))
{
radius_close($this->rad);
}
}
// Загрузить RADIUS-словарь из файла
static private function load($file)
{
$debug = static::debug();
$fh = fopen($file, "r");
if (!$fh) return;
$files = array();
$data = array('vendor'=>array(), 'attr'=>array());
$vendor = null;
if ($debug) echo "*debug: load dictionary $file\n";
while (($line = fgets($fh)) !== false)
{
$line = trim($line);
if (substr($line,0,1) == "#") $line = null;
$items = preg_split("/[\s]+/", $line);
if (count($items) > 1)
{
$cmd = strtolower($items[0]);
$name = (_get($items, 1));
$id = (_get($items, 2));
$type = strtolower(_get($items, 3));
$vid = (_get($items, 4));
if ($cmd == '$include')
{
if ($debug) echo "*debug: add file $items[1]\n";
$files[] = $items[1];
}
else
{
if ($cmd == 'vendor')
{
if ($debug) echo "*debug: add vendor #$id $name\n";
$data['vendor'][$name] = $id;
}
elseif ($cmd == 'begin-vendor')
{
if ($debug) echo "*debug: add vendor $name\n";
$vendor = $name;
}
elseif ($cmd == 'end-vendor')
{
$vendor = null;
}
elseif ($cmd == 'attribute')
{
if (!isset($vid)) $vid = $vendor;
if ($debug) echo "*debug: add attribute #$id $name (vendor $vid)\n";
$data['attr'][$name] = array('name'=>$name, 'id'=>$id, 'type'=>$type, 'vendor'=>$vid);
}
elseif ($cmd == 'value')
{
if (array_key_exists($name, $data['attr']))
{
if ($debug) echo "*debug: add value #$type $id (attribute $name)\n";
if (!array_key_exists('values', $data['attr'][$name])) $data['attr'][$name]['values'] = array();
$data['attr'][$name]['values'][$id] = $type;
}
}
elseif ($cmd == 'vendorattr')
{
$vid = (_get($items, 1));
$name = (_get($items, 2));
$id = (_get($items, 3));
$type = strtolower(_get($items, 4));
foreach ($data['vendor'] as $key=>$item)
{
if ((int)$vid == (int)$item)
{
if ($debug) echo "*debug: add attribute #$id $name (vendor $key)\n";
$data['attr'][$name] = array('name'=>$name, 'id'=>$id, 'type'=>$type, 'vendor'=>$key);
break;
}
}
}
elseif ($cmd == 'vendorvalue')
{
$vid = (_get($items, 1));
$name = (_get($items, 2));
$id = (_get($items, 3));
$type = strtolower(_get($items, 4));
if (array_key_exists($name, $data['attr']))
{
if ($debug) echo "*debug: add value #$type $id (attribute $name)\n";
if (!array_key_exists('values', $data['attr'][$name])) $data['attr'][$name]['values'] = array();
$data['attr'][$name]['values'][$id] = $type;
}
}
}
}
}
fclose($fh);
if ($debug) echo "*debug: load done\n";
return array('data'=>$data, 'file'=>$files);
}
// Обратится к RADIUS-словарю
static public function dict($attr=null, $reverse=false)
{
static $dict;
if (!isset($dict))
{
$base = "/etc/raddb";
$files = array('dictionary'=>false);
$dict = array();
$n = count($files);
while ($n > 0)
{
foreach ($files as $file=>$state)
{
if ($state)
{
$n--;
}
else
{
$ret = static::load("$base/$file");
$files[$file] = true;
$n--;
if (isset($ret))
{
foreach ($ret['data']['vendor'] as $key=>$item) $dict['vendor'][$key] = $item;
foreach ($ret['data']['attr'] as $key=>$item) $dict['attr'][$key] = $item;
foreach ($ret['file'] as $file)
{
if (!array_key_exists($file, $files))
{
$files[$file] = false;
$n++;
}
}
}
}
}
}
}
if (isset($attr))
{
if ($reverse)
{
$a = null;
$v = null;
if (is_array($attr))
{
$a = _get($attr,'id');
$v = _get($attr,'vid');
}
else
{
$a = $attr;
if (is_string($a) && array_key_exists($a, $dict['attr'])) $a = $dict['attr'][$a]['id'];
}
$ret = null;
foreach ($dict['attr'] as $item)
{
$ia = _get($item,'id');
$iv = _get($item,'vendor');
if (isset($iv)) $iv = _get($dict['vendor'],$iv);
if (($ia == $a) && (_nvl($iv,0) == _nvl($v,0)))
{
$ret = $item;
$ret['vid'] = $iv;
break;
}
}
return $ret;
}
else
{
if (!isset($vendor)) $vendor = '';
if (array_key_exists($attr, $dict['attr']))
{
$ret = $dict['attr'][$attr];
$ret['vid'] = null;
if (isset($ret['vendor']) && array_key_exists($ret['vendor'], $dict['vendor']))
{
$ret['vid'] = $dict['vendor'][$ret['vendor']];
}
return $ret;
}
}
}
}
// Выполнить RADIUS-запрос и получить ответ
function request($avpair=null, $type=RADIUS_ACCESS_REQUEST)
{
if (!isset($avpair)) return;
if (!is_array($avpair)) return;
if (empty($avpair)) return;
$debug = static::debug();
$rad = $this->rad;
$t = static::type($type,true);
if ($debug) echo "*debug: create request #$type" . (isset($t) ? " ($t)" : "") . "\n";
if (!radius_create_request($rad, $type))
{
if ($debug) echo "*debug: create request fail: " . radius_strerror($rad) . "\n";
trigger_error('RADIUS create request error:' . radius_strerror($rad), E_USER_NOTICE);
return;
}
foreach ($avpair as $a=>$v)
{
$ret = null;
$a = static::dict($a);
if ($debug) echo "*debug: attribute $a[name] (#$a[id], $a[type]" . (isset($a['vid']) ? ", vendor $a[vendor]" : "") . ") := $v" . ((isset($a['values']) && array_key_exists($v, $a['values'])) ? " (#$a[values])" : "") . "\n";
if (isset($a['values']) && array_key_exists($v, $a['values'])) $v = $a['values'];
if (isset($a['vid']))
{
if ($a['type'] == "integer")
{
$ret = radius_put_vendor_int($rad, $a['vid'], $a['id'], $v);
}
elseif ($a['type'] == "string")
{
$ret = radius_put_vendor_string($rad, $a['vid'], $a['id'], $v);
}
elseif ($a['type'] == "ipaddr")
{
$ret = radius_put_vendor_addr($rad, $a['vid'], $a['id'], $v);
}
else
{
$ret = radius_put_vendor_attr($rad, $a['vid'], $a['id'], $v);
}
}
else
{
if ($a['type'] == "integer")
{
$ret = radius_put_int($rad, $a['id'], $v);
}
elseif ($a['type'] == "string")
{
$ret = radius_put_string($rad, $a['id'], $v);
}
elseif ($a['type'] == "ipaddr")
{
$ret = radius_put_addr($rad, $a['id'], $v);
}
else
{
$ret = radius_put_attr($rad, $a['id'], $v);
}
}
if (!$ret)
{
trigger_error('RADIUS set request error:' . radius_strerror($rad), E_USER_NOTICE);
return;
}
}
$ret = radius_send_request($rad);
if ($ret === false)
{
if ($debug) echo "*debug: send request fail: " . radius_strerror($rad) . "\n";
trigger_error('RADIUS send request error:' . radius_strerror($rad), E_USER_NOTICE);
return;
}
$t = static::type($ret,true);
if ($debug) echo "*debug: response #$type" . (isset($t) ? " ($t)" : "") . "\n";
$reply = array();
while ($res = radius_get_attr($rad))
{
if ($res['attr'] == RADIUS_VENDOR_SPECIFIC)
{
$vsa = radius_get_vendor_attr($res['data']);
$a = static::dict(array('id'=>$vsa['attr'], 'vid'=>$vsa['vendor']));
$v = $vsa['data'];
}
else
{
$a = static::dict($res['attr']);
$v = $res['data'];
}
if (isset($a['values']))
{
foreach ($a['values'] as $key=>$item)
{
if ($item == $v)
{
$v = $key;
break;
}
}
}
if ($debug) echo "*debug: reply $a[name] (#$a[id], $a[type]" . (isset($a['vid']) ? ", vendor $a[vendor]" : "") . ") := $v" . ((isset($a['values']) && array_key_exists($v, $a['values'])) ? " (#$a[values])" : "") . "\n";
$reply[$a['name']] = $v;
}
return array($ret=>$reply);
}
// Сбросить подключение
function reconnect($session=null, $username=null, $ip=null)
{
$req = array();
$req['RBN-Context_Name'] = 'local';
if (isset($session)) $req['Acct-Session-Id'] = $session;
if (isset($username)) $req['User-Name'] = $username;
if (isset($ip)) $req['Framed-IP-Address'] = $ip;
return $this->request($req, static::RAD_DISCONNECT_REQUEST);
}
}
Используется так:
1. 2. 3.
$rad = new Radius('billing');
Radius::debug(true);
dump($rad->reconnect($auth->session, $auth->username, $auth->ip));
Но этот код почему-то не работает для RAD_DISCONNECT_REQUEST, выдает ошибку "No User or Chap Password attributes given".
Скачал исходники пакета, за эту ошибку отвечает такой фрагмент кода:
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23.
if (h->request[POS_CODE] == RAD_ACCOUNTING_REQUEST
|| h->request[POS_CODE] == RAD_COA_REQUEST
|| h->request[POS_CODE] == RAD_COA_ACK
|| h->request[POS_CODE] == RAD_COA_NAK
|| h->request[POS_CODE] == RAD_DISCONNECT_REQUEST
|| h->request[POS_CODE] == RAD_DISCONNECT_ACK
|| h->request[POS_CODE] == RAD_DISCONNECT_NAK) {
/* Make sure no password given */
if (h->pass_pos || h->chap_pass) {
generr(h, "User or Chap Password in non-access request");
return -1;
}
} else {
/* Make sure the user gave us a password */
if (h->pass_pos == 0 && !h->chap_pass) {
generr(h, "No User or Chap Password attributes given");
return -1;
}
if (h->pass_pos != 0 && h->chap_pass) {
generr(h, "Both User and Chap Password attributes given");
return -1;
}
}
И вроде бы для RAD_DISCONNECT_REQUEST пароль не должен требоваться.
Но почему-то требуется.
Еще смущает, что тут и в get_defined_constants() константа определена как RADIUS_DISCONNECT_REQUEST, в то время как в файле radlib.h она задана следующим образом:
#define RAD_DISCONNECT_REQUEST 40
|