[Openupload-svn-update] SF.net SVN: openupload:[68] trunk/lib
Status: Beta
Brought to you by:
tsdogs
|
From: <ts...@us...> - 2008-10-23 18:15:57
|
Revision: 68
http://openupload.svn.sourceforge.net/openupload/?rev=68&view=rev
Author: tsdogs
Date: 2008-10-23 18:15:55 +0000 (Thu, 23 Oct 2008)
Log Message:
-----------
First buggy version with ldap authentication
Modified Paths:
--------------
trunk/lib/classes.inc.php
trunk/lib/main.inc.php
trunk/lib/modules/auth/default.inc.php
trunk/lib/modules/db/txt.inc.php
trunk/lib/modules/default/admin.inc.php
trunk/lib/modules/default/auth.inc.php
trunk/lib/modules/default/files.inc.php
trunk/lib/user.inc.php
Added Paths:
-----------
trunk/lib/modules/auth/ldap.inc.php
Modified: trunk/lib/classes.inc.php
===================================================================
--- trunk/lib/classes.inc.php 2008-10-23 11:02:34 UTC (rev 67)
+++ trunk/lib/classes.inc.php 2008-10-23 18:15:55 UTC (rev 68)
@@ -11,11 +11,25 @@
}
class authBase {
-var $features = array();
+var $features = array('adminuser' => 'no', 'admingroup' => 'no');
function authBase() {
}
+ function init() {}
+
+ function authenticate($user,$pwd) { return false; }
+
+ function userinfo($login) { return array(); }
+ function groupinfo($group = '') { return array(); }
+
+ function users() { return array(); }
+ function useradd($user) {}
+ function useredit($user) {}
+ function userdel($id) {}
+ function groupadd($group) {}
+ function groupedit($group) {}
+ function groupdel($id) {}
}
Modified: trunk/lib/main.inc.php
===================================================================
--- trunk/lib/main.inc.php 2008-10-23 11:02:34 UTC (rev 67)
+++ trunk/lib/main.inc.php 2008-10-23 18:15:55 UTC (rev 68)
@@ -75,10 +75,10 @@
$this->user->setInfo('lang',$lang);
}
+//unset($_SESSION['user']);
$this->tr->init();
$this->auth->init();
$this->user->init();
-//unset($_SESSION['user']);
if ($this->user->info('max_upload_size')==0)
$this->user->setInfo('max_upload_size',$this->config['max_upload_size']*1024*1024);
ini_set('max_upload_size',$this->user->info('max_upload_size'));
@@ -227,14 +227,22 @@
function loadACL() {
/* loads the acl from the db */
$group = $this->user->group();
- $this->acl = array_merge($this->db->read('acl',array('group_id' => $group),array('module','action'),'',
- array('group_id','module','action')),
- $this->db->read('acl',array('group_id' => '*'),array('module','action'),'',
- array('group_id','module','action')));
- $this->pluginAcl = $this->db->read('plugin_acl',array('group_id' => $group),array('plugin'),'',array('plugin'));
+ if (is_array($group)) {
+ $this->acl = $this->db->read('acl',array(),array('group_name','module','action'),'',
+ array('group_name','module','action'));
+ $this->pluginAcl = $this->db->read('plugin_acl',array(),array('plugin'),'',array('plugin'));
+ } else {
+ $this->acl = array_merge($this->db->read('acl',array('group_name' => $group),array('module','action'),'',
+ array('group_name','module','action')),
+ $this->db->read('acl',array('group_name' => '*'),array('module','action'),'',
+ array('group_name','module','action')));
+ $this->pluginAcl = $this->db->read('plugin_acl',array('group_name' => $group),array('plugin'),'',array('plugin'));
+ }
}
- function checkACL($group,$module,$action) {
+
+ function checkSingleACL($group,$module,$action) {
+
$result = 'deny'; /* not defined are denyed by default */
if (isset($this->acl[$group][$module][$action])) {
$result = $this->acl[$group][$module][$action]['access'];
@@ -249,7 +257,21 @@
} else if (isset($this->acl['*']['*']['*'])) {
$result = $this->acl['*']['*']['*']['access']; /* this should be avoided imho */
}
+ return $result;
+ }
+ function checkACL($group,$module,$action) {
+ if (is_array($group)) {
+ foreach ($group as $g) {
+ $result = $this->checkSingleACL($g,$module,$action);
+ if ($result == 'allow') {
+ return $result;
+ }
+ }
+ } else {
+ $result = $this->checkSingleACL($group,$module,$action);
+ }
+
if ($this->config['debug_acl'] and $result == 'deny') {
echo '<pre>ACL: '.$result.' - group: '.$group.', module: '.$module.', action: '.$action."\n";
print_r($this->acl);
Modified: trunk/lib/modules/auth/default.inc.php
===================================================================
--- trunk/lib/modules/auth/default.inc.php 2008-10-23 11:02:34 UTC (rev 67)
+++ trunk/lib/modules/auth/default.inc.php 2008-10-23 18:15:55 UTC (rev 68)
@@ -5,8 +5,8 @@
var $userfields;
function defaultAuth() {
- $this->userfields = array('id','login','password','name','group_id','email','lang','reg_date','regid','active');
- $this->features = array('info','add', 'update', 'delete');
+ $this->userfields = array('id','login','password','name','group_name','email','lang','reg_date','regid','active');
+ $this->features = array('useradmin' => 'yes', 'groupadmin' => 'yes');
}
function init() {
@@ -22,26 +22,53 @@
return false;
}
- function info($login) {
+ function userinfo($login) {
$result = $this->db->read('users',array('login' => $login));
+ $result['group']=$result['group_name'];
return $result[0];
}
+
+ function groupinfo($group = '') {
+ if ($group != '') {
+ $result = $this->db->read('groups',array('name' => $group));
+ return $result[0]['name'];
+ } else {
+ $result = $this->db->read('groups');
+ return $result;
+ }
+ }
- function add($user) {
+ function users() {
+ return app()->db->read('users',array(),array('login'));
+ }
+
+ function useradd($user) {
$user['password']=crypt($user['password']);
$this->db->insert('users',$user,$this->userfields);
}
- function update($user,$pwd = false) {
+ function useredit($user,$pwd = false) {
if ($pwd) {
$user['password']=crypt($user['password']);
}
$this->db->update('users',$user,array('id' => $user['id']),$this->userfields);
}
- function delete($id) {
+ function userdel($id) {
$this->db->delete('users',array('login' => $id));
}
+
+ function groupadd($group) {
+ $this->db->insert('groups',$group);
+ }
+
+ function groupedit($group) {
+ app()->db->update('groups',$group,array('name' => $group['name']));
+ }
+
+ function groupdel($group_id) {
+ app()->db->delete('groups',array('name' => $group_id));
+ }
}
?>
\ No newline at end of file
Added: trunk/lib/modules/auth/ldap.inc.php
===================================================================
--- trunk/lib/modules/auth/ldap.inc.php (rev 0)
+++ trunk/lib/modules/auth/ldap.inc.php 2008-10-23 18:15:55 UTC (rev 68)
@@ -0,0 +1,104 @@
+<?php
+
+class ldapAuth extends authBase {
+var $config;
+
+ function ldapAuth() {
+ }
+
+ function init() {
+ $this->config = app()->config['ldap'];
+ $this->ufield = isset($this->config['uid'])?$this->config['uid']:'uid';
+ $this->gfield = isset($this->config['gid'])?$this->config['gid']:'gid';
+ /* cannot add or edit users for now */
+ $this->features = array('useradmin' => 'no', 'groupadmin' => 'no');
+ }
+
+ function authenticate($login,$password) {
+ $ds=ldap_connect($this->config['host']);
+ if ($ds) {
+ $uid = $this->ufield.'='.$login.','.$this->config['userdn'];
+ if (ldap_bind($ds, $uid, $password) ) {
+ /* authentication was successfull, save username and password for additional info */
+ $this->uid = $uid;
+ $this->password = $password;
+ return true;
+ }
+ ldap_close($ds);
+ }
+ return false;
+ }
+
+ function userinfo($login) {
+ $ds=@ldap_connect($this->config['host']);
+ $result = array();
+ if (ldap_bind($ds, $this->config['user'],$this->config['password']) ) {
+ $r = @ldap_search($ds, $this->config['userdn'],
+ '(&('.$this->ufield.'='.$login.')(objectclass='.$this->config['userclass'].'))');
+ if ($r) {
+ $res = @ldap_get_entries($ds, $r);
+ /* associate user fields */
+ $res = $res[0];
+ foreach ($this->config['userfields'] as $n => $f) {
+ $result[$f] = $res[$n][0];
+ }
+ }
+ /* now retrieve the main group */
+ $r = @ldap_search($ds, $this->config['groupdn'],
+ '(&('.$this->gfield.'='.$result['group_id'].')(objectclass='.$this->config['groupclass'].'))');
+ if ($r) {
+ $res = @ldap_get_entries($ds, $r);
+ /* associate user fields */
+ $res = $res[0];
+ foreach ($this->config['groupfields'] as $n => $f) {
+ if ($f == 'name') {
+ $result['group'] = $res[$n][0];
+ }
+ }
+ }
+ if (isset($this->config['sgid'])) {
+ $result['group'] = array($result['group']);
+ $r = @ldap_search($ds, $this->config['groupdn'],
+ '(&('.$this->config['sgid'].'='.$result['login'].')(objectclass='.$this->config['groupclass'].'))');
+ if ($r) {
+ $res = @ldap_get_entries($ds, $r);
+ for ($i = 0; $i<$res['count']; $i++) {
+ foreach ($this->config['sgroupfields'] as $n => $f) {
+ if ($f == 'name') {
+ $result['group'][] = $res[$i][$n][0];
+ }
+ }
+ }
+ }
+ }
+ }
+ ldap_close($ds);
+ return $result;
+ }
+
+ function groupinfo($group = '') {
+ $ds=@ldap_connect($this->config['host']);
+ $result = array();
+ @ldap_bind($ds, $this->config['user'], $this->config['password']);
+ if (group != '') {
+ $r = @ldap_search($ds, $this->config['groupdn'],'(objectclass='.$this->config['groupclass'].')');
+ } else {
+ $r = @ldap_search($ds, $this->config['groupdn'],
+ '(&('.$this->gfield.'='.$group.')(objectclass='.$this->config['groupclass'].'))');
+ }
+ if ($r) {
+ $res = @ldap_get_entries($ds, $r);
+ /* associate user fields */
+ for ($i = 0; $i<$res['count']; $i++) {
+ foreach ($this->config['sgroupfields'] as $n => $f) {
+ $result[$i][$f] = $res[$i][$n][0];
+ }
+ }
+ }
+ ldap_close($ds);
+ return $result;
+ }
+
+}
+
+?>
\ No newline at end of file
Modified: trunk/lib/modules/db/txt.inc.php
===================================================================
--- trunk/lib/modules/db/txt.inc.php 2008-10-23 11:02:34 UTC (rev 67)
+++ trunk/lib/modules/db/txt.inc.php 2008-10-23 18:15:55 UTC (rev 68)
@@ -18,7 +18,7 @@
"users" => array (
"type" => "file",
"fields" => array (
- "id", "login", "password", "name", "group_id", "email", "lang", "reg_date", "regid", "active",
+ "id", "login", "password", "name", "group_name", "email", "lang", "reg_date", "regid", "active",
),
"auto_increment" => "id",
),
@@ -38,7 +38,7 @@
"acl" => array (
"type" => "file",
"fields" => array (
- "id", "module", "action", "group_id", "access",
+ "id", "module", "action", "group_name", "access",
),
"auto_increment" => "id",
),
@@ -51,14 +51,14 @@
"plugin_acl" => array (
"type" => "file",
"fields" => array (
- "id", "group_id", "plugin", "access",
+ "id", "group_name", "plugin", "access",
),
"auto_increment" => "id",
),
"plugin_options" => array (
"type" => "file",
"fields" => array (
- "id", "plugin", "group_id", "name", "value",
+ "id", "plugin", "group_name", "name", "value",
),
"auto_increment" => "id",
),
Modified: trunk/lib/modules/default/admin.inc.php
===================================================================
--- trunk/lib/modules/default/admin.inc.php 2008-10-23 11:02:34 UTC (rev 67)
+++ trunk/lib/modules/default/admin.inc.php 2008-10-23 18:15:55 UTC (rev 68)
@@ -73,20 +73,25 @@
function users() {
/* List the users */
- $users = app()->db->read('users',array(),array('login'));
- $this->tpl->assign('users',$users);
+ if (app()->auth->features['useradmin'] == 'yes') {
+ $users = app()->auth->users();
+ $this->tpl->assign('users',$users);
+ } else {
+ app()->error(tr('User administration not supported by Auth Module'));
+ }
}
function useradd() {
global $_POST;
- $groups = app()->db->read('groups',array(),array('name'));
+ $groups = app()->auth->groupinfo();
+ /* do the assoc */
$this->tpl->assign('groups',$groups);
if (isset($_POST['adduserlogin'])) {
/* add the user */
$user['login']=$_POST['adduserlogin'];
$user['name']=$_POST['addusername'];
- $user['group_id']=$_POST['addusergroup'];
+ $user['group_name']=$_POST['addusergroup'];
$user['email']=$_POST['adduseremail'];
$user['active']=$_POST['adduseractive'];
$user['lang']=$_POST['adduserlang'];
@@ -108,14 +113,14 @@
}
if (!$error) {
$user['password']=$_POST['adduserpassword'];
- app()->auth->add($user);
+ app()->auth->useradd($user);
/* redirect */
$this->nextStep(1);
}
}
if (!isset($user)) {
$user['active']=1;
- $user['group_id']=app()->config['register']['default_group'];
+ $user['group_name']=app()->config['register']['default_group'];
}
$this->tpl->assign('adduser',$user);
}
@@ -124,7 +129,7 @@
global $_GET;
if (isset($_GET['id'])) {
- app()->auth->delete($_GET['id']);
+ app()->auth->userdel($_GET['id']);
}
$this->nextStep(1);
}
@@ -134,11 +139,11 @@
if (isset($_GET['id'])) {
$active=$_GET['active']==1?0:1;
- $user = app()->db->read('users',array('login' => $_GET['id']));
+ $user = app()->auth->userinfo($_GET['id']);
$user = $user[0];
if ($user['login']==$_GET['id']) {
$user['active']=$active;
- app()->auth->update($user,false);
+ app()->auth->useredit($user,false);
}
}
$this->nextStep(1);
@@ -148,13 +153,13 @@
global $_GET;
global $_POST;
/* edit the user */
- $groups = app()->db->read('groups',array(),array('name'));
+ $groups = app()->auth->groupinfo();
$this->tpl->assign('groups',$groups);
if (isset($_POST['login'])) {
- $user = app()->db->read('users',array('login' => $_POST['login']));
+ $user = app()->auth->userinfo($_POST['login']);
$user = $user[0];
$user['name']=$_POST['editusername'];
- $user['group_id']=$_POST['editusergroup'];
+ $user['group_name']=$_POST['editusergroup'];
$user['email']=$_POST['edituseremail'];
$user['lang']=$_POST['edituserlang'];
$user['active']=$_POST['edituseractive'];
@@ -175,20 +180,24 @@
$error = true;
}
if (!$error) {
- app()->auth->update($user);
+ app()->auth->useredit($user);
/* redirect */
$this->nextStep(1);
}
} else {
- $user = app()->db->read('users',array('login' => $_GET['id']));
+ $user = app()->auth->info($_GET['id']);
$user = $user[0];
}
$this->tpl->assign('edituser',$user);
}
function groups() {
- $groups = app()->db->read('groups',array(),array('name'));
- $this->tpl->assign('groups',$groups);
+ if (app()->auth->features['groupadmin']=='yes') {
+ $groups = app()->auth->groupinfo();
+ $this->tpl->assign('groups',$groups);
+ } else {
+ app()->error(tr('Group administration not supported by Auth Module'));
+ }
}
function groupadd() {
@@ -198,7 +207,7 @@
$group['name']=$_POST['addgroupname'];
$group['description']=$_POST['addgroupdescription'];
if ($group['name']!='') {
- app()->db->insert('groups',$group);
+ app()->auth->groupadd($group);
$this->nextStep(1);
} else {
app()->error(tr('Please provide a valid group name!'));
@@ -211,13 +220,12 @@
global $_POST;
global $_GET;
- $group = app()->db->read('groups',array('name' => $_GET['id']));
+ $group = app()->auth->groupinfo();
$group = $group[0];
if (isset($_POST['editgroupname'])) {
$group['name']=$_POST['editgroupname'];
$group['description']=$_POST['editgroupdescription'];
- app()->db->update('groups',$group,array('name' => $group['name']));
- $this->nextStep(1);
+ $this->nextStep(1);
}
app()->tpl->assign('group',$group);
}
@@ -226,17 +234,18 @@
global $_GET;
/* should check if sub users exsist */
if (isset($_GET['id'])) {
- app()->db->delete('groups',array('name' => $_GET['id']));
+ app()->auth->groupdel($_GET['id']);
/* delete all the rights of the group */
- app()->db->delete('acl',array('group_id' => $_GET['id']));
+ app()->db->delete('acl',array('group_name' => $_GET['id']));
+ app()->db->delete('pluins_acl',array('group_name' => $_GET['id']));
}
$this->nextStep(1);
}
function rights() {
- $groups = app()->db->read('groups',array(),array('name'));
+ $groups = app()->auth->groupinfo();
$this->tpl->assign('groups',$groups);
- $rights = app()->db->read('acl',array(),array('group_id','module'));
+ $rights = app()->db->read('acl',array(),array('group_name','module'));
$this->tpl->assign('rights',$rights);
}
@@ -246,19 +255,19 @@
$modules = app()->config['modules'];
$modules['*']='*';
$this->tpl->assign('modules',$modules);
- $groups = app()->db->read('groups',array(),array('name'));
+ $groups = app()->auth->groupinfo();
$groups[]='*';
$this->tpl->assign('groups',$groups);
$access['allow']=tr('Allow');
$access['deny']=tr('Deny');
$this->tpl->assign('access',$access);
$right['module']='*';
- $right['group_id']='*';
+ $right['group_name']='*';
$right['action']='*';
$right['access']='deny';
if (isset($_POST['addrightgroup'])) {
$right['id']='';
- $right['group_id']=$_POST['addrightgroup'];
+ $right['group_name']=$_POST['addrightgroup'];
$right['module']=$_POST['addrightmodule'];
$right['action']=$_POST['addrightaction'];
$right['access']=$_POST['addrightaccess'];
@@ -277,7 +286,7 @@
$modules = app()->config['modules'];
$modules['*']='*';
$this->tpl->assign('modules',$modules);
- $groups = app()->db->read('groups',array(),array('name'));
+ $groups = app()->auth->groupinfo();
$groups[]='*';
$this->tpl->assign('groups',$groups);
$access['allow']=tr('Allow');
@@ -286,7 +295,7 @@
if (isset($_POST['editaclid'])) {
$right = app()->db->read('acl',array('id' => $_POST['editaclid']));
$right = $right[0];
- $right['group_id']=$_POST['editrightgroup'];
+ $right['group_name']=$_POST['editrightgroup'];
$right['module']=$_POST['editrightmodule'];
$right['action']=$_POST['editrightaction'];
$right['access']=$_POST['editrightaccess'];
@@ -316,8 +325,6 @@
$count = app()->db->count('files');
$this->tpl->assign('pages',ceil($count / $NUM)+1);
$this->tpl->assign('pagen',$page);
- $users = app()->db->read('users',array(),array('login'),'',array('id'));
- $this->tpl->assign('users',$users);
$files = app()->db->read('files',array(),array('upload_date desc'),$limit);
$this->tpl->assign('files',$files);
}
@@ -354,7 +361,7 @@
$plugins = app()->config['plugins'];
$this->tpl->assign('pluginslist',$plugins);
- $groups = app()->db->read('groups',array(),array('name'));
+ $groups = app()->auth->groupinfo();
$this->tpl->assign('groups',$groups);
$access['enable']=tr('Enable');
$access['disable']=tr('Disable');
@@ -362,7 +369,7 @@
$plugin['access']='disable';
if (isset($_POST['addplugingroup'])) {
$plugin['id']='';
- $plugin['group_id']=$_POST['addplugingroup'];
+ $plugin['group_name']=$_POST['addplugingroup'];
$plugin['plugin']=$_POST['addpluginplugin'];
$plugin['access']=$_POST['addpluginaccess'];
app()->db->insert('plugin_acl',$plugin);
@@ -379,7 +386,7 @@
$plugin = $plugin[0];
$plugins = app()->config['plugins'];
$this->tpl->assign('pluginslist',$plugins);
- $groups = app()->db->read('groups',array(),array('name'));
+ $groups = app()->auth->groupinfo();
$this->tpl->assign('groups',$groups);
$access['enable']=tr('Enable');
$access['disable']=tr('Disable');
@@ -387,7 +394,7 @@
if (isset($_POST['editpluginid'])) {
$plugin = app()->db->read('plugin_acl',array('id' => $_POST['editpluginid']));
$plugin = $plugin[0];
- $plugin['group_id']=$_POST['editplugingroup'];
+ $plugin['group_name']=$_POST['editplugingroup'];
$plugin['plugin']=$_POST['editpluginplugin'];
$plugin['access']=$_POST['editpluginaccess'];
app()->db->update('plugin_acl',$plugin,array('id' => $_POST['editpluginid']));
Modified: trunk/lib/modules/default/auth.inc.php
===================================================================
--- trunk/lib/modules/default/auth.inc.php 2008-10-23 11:02:34 UTC (rev 67)
+++ trunk/lib/modules/default/auth.inc.php 2008-10-23 18:15:55 UTC (rev 68)
@@ -39,7 +39,8 @@
if (!app()->user->loggedin()) {
$this->menu['login']=tr('Login');
} else {
- $this->menu['profile']=tr('Preferences');
+ if (app()->auth->features['adminusers']=='yes')
+ $this->menu['profile']=tr('Preferences');
$this->menu['logout']=tr('Logout');
}
$this->tpl->assign('register',app()->checkACL(app()->user->group(),'auth','register')=='allow');
@@ -140,7 +141,7 @@
$user['password'] = $_POST['registerpassword'];
$user['email'] = $_POST['registeremail'];
$user['lang'] = $_POST['registerlang'];
- $user['group_id'] = app()->config['register']['default_group'];
+ $user['group_name'] = app()->config['register']['default_group'];
$user['reg_date']=date('Y-m-d H:i:s');
$result = app()->pluginAction('registerConfirm',$user);
$_SESSION['register']=$user;
@@ -233,7 +234,7 @@
}
}
if (!$error) {
- app()->auth->update($user);
+ app()->auth->useredit($user);
app()->user->set($user);
$this->nextStep(1);
}
Modified: trunk/lib/modules/default/files.inc.php
===================================================================
--- trunk/lib/modules/default/files.inc.php 2008-10-23 11:02:34 UTC (rev 67)
+++ trunk/lib/modules/default/files.inc.php 2008-10-23 18:15:55 UTC (rev 68)
@@ -88,7 +88,7 @@
$_SESSION['user']['u']['name']=$_FILES['upload']['name'];
$_SESSION['user']['u']['size']=$_FILES['upload']['size'];
$_SESSION['user']['u']['ip']=$_SERVER['REMOTE_ADDR'];
- $_SESSION['user']['u']['user_id']=app()->user->info('id');
+ $_SESSION['user']['u']['user_login']=app()->user->info('login');
$this->nextStep(app()->step);
}
} else if (!isset($_SESSION['user']['u'])) {
@@ -122,7 +122,7 @@
$finfo['id']= app()->db->newRandomId('files','id');
$finfo['remove']= app()->db->newRandomId('files','remove');
$finfo['upload_date'] = date('Y-m-d H:i:s');
- app()->db->insert('files',$finfo,array('id','name','mime','description','size','remove','user_id','ip','upload_date'));
+ app()->db->insert('files',$finfo,array('id','name','mime','description','size','remove','user_login','ip','upload_date'));
foreach (app()->plugins as $plugin) {
if (count($plugin->fields)>0) {
foreach ($plugin->fields as $f) {
Modified: trunk/lib/user.inc.php
===================================================================
--- trunk/lib/user.inc.php 2008-10-23 11:02:34 UTC (rev 67)
+++ trunk/lib/user.inc.php 2008-10-23 18:15:55 UTC (rev 68)
@@ -38,8 +38,8 @@
}
function group() {
- if ($this->info('group_id')!='')
- $group = $this->info('group_id');
+ if ($this->info('group')!='')
+ $group = $this->info('group');
else
$group = app()->config['register']['nologingroup'];
return $group;
@@ -79,7 +79,7 @@
if ($res) {
$_SESSION['user']['login']=$username;
/* retrieve user info */
- $_SESSION['user'] = $this->auth->info($username);;
+ $_SESSION['user'] = $this->auth->userinfo($username);
/* make the post not be resent on refresh */
return true;
} else {
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|