author | umopapisdn <umopapisdn> | 2002-09-28 06:36:30 (UTC) |
---|---|---|
committer | umopapisdn <umopapisdn> | 2002-09-28 06:36:30 (UTC) |
commit | 8a95ca149ff5eedc9215bb012c8d7d09cdcaf96c (patch) (side-by-side diff) | |
tree | 50aed468fa8888266c589ce47131d0acaf7f82b7 | |
parent | 996c0d02d16c185acc3190baa5b4a1fc934e730c (diff) | |
download | opie-8a95ca149ff5eedc9215bb012c8d7d09cdcaf96c.zip opie-8a95ca149ff5eedc9215bb012c8d7d09cdcaf96c.tar.gz opie-8a95ca149ff5eedc9215bb012c8d7d09cdcaf96c.tar.bz2 |
Now actually makes the homedirectories owned by the user they belong to. :)
-rw-r--r-- | noncore/settings/usermanager/passwd.cpp | 5 | ||||
-rw-r--r-- | noncore/settings/usermanager/userdialog.cpp | 10 | ||||
-rw-r--r-- | noncore/settings/usermanager/userdialog.h | 2 | ||||
-rw-r--r-- | noncore/settings/usermanager/usermanager.pro | 2 |
4 files changed, 15 insertions, 4 deletions
diff --git a/noncore/settings/usermanager/passwd.cpp b/noncore/settings/usermanager/passwd.cpp index 310cef8..5063661 100644 --- a/noncore/settings/usermanager/passwd.cpp +++ b/noncore/settings/usermanager/passwd.cpp @@ -1,271 +1,274 @@ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "passwd.h" // Needed for crypt_make_salt(); #include <sys/types.h> +#include <sys/stat.h> #include <unistd.h> #include <time.h> Passwd::Passwd() { } Passwd::~Passwd() { } // This function is taken from 'busybox'. int Passwd::i64c(int i) { if (i <= 0) return ('.'); if (i == 1) return ('/'); if (i >= 2 && i < 12) return ('0' - 2 + i); if (i >= 12 && i < 38) return ('A' - 12 + i); if (i >= 38 && i < 63) return ('a' - 38 + i); return ('z'); } // This function is taken from 'busybox'. char *Passwd::crypt_make_salt() { time_t now; static unsigned long x; static char result[3]; time(&now); x += now + getpid() + clock(); result[0] = i64c(((x >> 18) ^ (x >> 6)) & 077); result[1] = i64c(((x >> 12) ^ x) & 077); result[2] = '\0'; return result; } bool Passwd::open() { int returnvalue=false; QFile passwd_file("/etc/passwd"); QFile group_file("/etc/group"); passwdStringList.clear(); groupStringList.clear(); if((passwd_file.open(IO_ReadOnly))) { if((group_file.open(IO_ReadOnly))) { QTextStream ts_passwd(&passwd_file); while(!(ts_passwd.eof())) { passwdStringList << ts_passwd.readLine(); } QTextStream ts_group(&group_file); while(!(ts_group.eof())) { groupStringList << ts_group.readLine(); } returnvalue=true; group_file.close(); } passwd_file.close(); } return returnvalue; } bool Passwd::close() { int returnvalue=false; QFile passwd_file("/etc/passwd"); QFile group_file("/etc/group"); if((passwd_file.open(IO_WriteOnly))) { if((group_file.open(IO_WriteOnly))) { QTextStream ts_passwd(&passwd_file); for(QStringList::Iterator it=passwdStringList.begin(); it!=passwdStringList.end(); ++it) { ts_passwd << (*it) + "\n"; } QTextStream ts_group(&group_file); for(QStringList::Iterator it=groupStringList.begin(); it!=groupStringList.end(); ++it) { ts_group << (*it) + "\n"; } returnvalue=true; group_file.close(); } passwd_file.close(); } return returnvalue; } void Passwd::splitPasswdEntry(QString &userString) { userdataStringList=QStringList::split(":",userString,true); QStringList::Iterator it=userdataStringList.begin(); pw_name=(*it++); pw_passwd=(*it++); pw_uid=(*it++).toInt(); pw_gid=(*it++).toInt(); pw_gecos=(*it++); pw_dir=(*it++); pw_shell=(*it++); } void Passwd::splitGroupEntry(QString &groupString) { groupdataStringList=QStringList::split(":",groupString,true); QStringList::Iterator it=groupdataStringList.begin(); gr_name=(*it++); it++; gr_gid=(*it++).toInt(); gr_mem=QStringList::split(" ",(*it++)); } bool Passwd::searchUser(QRegExp &userRegExp) { QStringList tempStringList(passwdStringList.grep(userRegExp)); if((tempStringList.isEmpty())) { return false; } else { userString=(*(tempStringList.begin())); splitPasswdEntry(userString); } return true; } bool Passwd::findUser(const char *username) { QRegExp userRegExp(QString("^%1\\:").arg(username)); return searchUser(userRegExp); } bool Passwd::findUser(int uid) { QRegExp userRegExp(QString(":%1\\:").arg(uid)); return searchUser(userRegExp); } -bool Passwd::addUser(QString pw_name, QString pw_passwd, int pw_uid, int pw_gid, QString pw_gecos,QString pw_dir, QString pw_shell, bool createGroup=true) { +bool Passwd::addUser(QString pw_name, QString pw_passwd, int pw_uid, int pw_gid, QString pw_gecos,QString pw_dir, QString pw_shell, bool createGroup) { QString tempString; if((createGroup) && (!(findGroup(pw_gid)))) addGroup(pw_name,pw_gid); pw_passwd = crypt(pw_passwd, crypt_make_salt()); tempString=pw_name+":"+pw_passwd+":"+QString::number(pw_uid)+":"+QString::number(pw_gid)+":"+pw_gecos+":"+pw_dir+":"+pw_shell; passwdStringList.append(tempString); // Make home dir. QDir d; if(!(d.exists(pw_dir))) { d.mkdir(pw_dir); + chown(pw_dir,pw_uid,pw_gid); + chmod(pw_dir,S_IRUSR|S_IWUSR|S_IXUSR); } return 1; } bool Passwd::updateUser(QString login) { QRegExp userRegExp(QString("^%1\\:").arg(login)); for(QStringList::Iterator it=passwdStringList.begin(); it!=passwdStringList.end(); ++it) { if(userRegExp.find((*it),0)!=-1) { *it=QString(pw_name+":"+pw_passwd+":"+QString::number(pw_uid)+":"+QString::number(pw_gid)+":"+pw_gecos+":"+pw_dir+":"+pw_shell); return true; } } return false; } bool Passwd::deleteUser(QRegExp &userRegExp, bool delGroup) { for(QStringList::Iterator it=passwdStringList.begin(); it!=passwdStringList.end(); ++it) { if(userRegExp.find((*it),0)!=-1) { splitPasswdEntry(*it); if(delGroup) this->delGroup(pw_gid); passwdStringList.remove(it); return true; } } return false; } bool Passwd::delUser(const char *username, bool delGroup) { QRegExp userRegExp(QString("^%1\\:").arg(username)); return deleteUser(userRegExp,delGroup); } bool Passwd::delUser(int uid, bool delGroup) { QRegExp userRegExp(QString(":%1\\:").arg(uid)); return deleteUser(userRegExp,delGroup); } bool Passwd::searchGroup(QRegExp &groupRegExp) { QStringList tempStringList(groupStringList.grep(groupRegExp)); if((tempStringList.isEmpty())) { return false; } else { groupString=(*(tempStringList.begin())); splitGroupEntry(groupString); } return true; } bool Passwd::findGroup(const char *groupname) { QRegExp groupRegExp(QString("^%1\\:").arg(groupname)); return searchGroup(groupRegExp); } bool Passwd::findGroup(int gid) { QRegExp groupRegExp(QString(":%1\\:").arg(gid)); return searchGroup(groupRegExp); } bool Passwd::addGroup(QString gr_name, int gr_gid) { QString tempString; tempString=gr_name+":*:"+QString::number(gr_gid)+":"; groupStringList.append(tempString); return 1; } bool Passwd::updateGroup(int gid) { QRegExp groupRegExp(QString(":%1\\:").arg(QString::number(gid))); for(QStringList::Iterator it=groupStringList.begin(); it!=groupStringList.end(); ++it) { if(groupRegExp.find((*it),0)!=-1) { *it=QString(gr_name+":*:"+QString::number(gr_gid)+":"); for(QStringList::Iterator member=gr_mem.begin(); member!=gr_mem.end(); ++member) { *it+=*member; *it+=" "; } return true; } } return false; } bool Passwd::deleteGroup(QRegExp &groupRegExp) { for(QStringList::Iterator it=groupStringList.begin(); it!=groupStringList.end(); ++it) { if(groupRegExp.find((*it),0)!=-1) { groupStringList.remove(it); return true; } } return false; } bool Passwd::delGroup(const char *groupname) { QRegExp groupRegExp(QString("^%1\\:").arg(groupname)); return deleteGroup(groupRegExp); } bool Passwd::delGroup(int gid) { QRegExp groupRegExp(QString(":%1\\:").arg(gid)); return deleteGroup(groupRegExp); } bool Passwd::addGroupMember(QString groupname, QString member) { if(!(findGroup(groupname))) return false; gr_mem << member; if(!(updateGroup(gr_gid))) return false; return true; } bool Passwd::delGroupMember(QString groupname, QString member) { if(!(findGroup(groupname))) return false; for(QStringList::Iterator it=gr_mem.begin(); it!=gr_mem.end(); ++it) { if(*it==member) { gr_mem.remove(it); it=gr_mem.end(); } } if(!(updateGroup(gr_gid))) return false; return true; } Passwd *accounts; diff --git a/noncore/settings/usermanager/userdialog.cpp b/noncore/settings/usermanager/userdialog.cpp index f31775d..6940a3b 100644 --- a/noncore/settings/usermanager/userdialog.cpp +++ b/noncore/settings/usermanager/userdialog.cpp @@ -1,319 +1,325 @@ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "userdialog.h" #include <qlayout.h> #include <qlabel.h> #include <qmessagebox.h> #include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <signal.h> #include "passwd.h" /** * UserDialog constructor. Setup the dialog, fill the groupComboBox & groupsListView with all groups. * */ UserDialog::UserDialog(QWidget* parent, const char* name, bool modal, WFlags fl) : QDialog(parent, name, modal, fl) { QVBoxLayout *layout = new QVBoxLayout(this); myTabWidget=new QTabWidget(this,"User Tab Widget"); layout->addWidget(myTabWidget); setupTab1(); setupTab2(); // And also fill the listview & the combobox with all available groups. for( QStringList::Iterator it = accounts->groupStringList.begin(); it!=accounts->groupStringList.end(); ++it) { accounts->splitGroupEntry(*it); - new QListViewItem(groupsListView,accounts->gr_name); + //new QListViewItem(groupsListView,accounts->gr_name); + new QCheckListItem(groupsListView,accounts->gr_name,QCheckListItem::CheckBox); groupComboBox->insertItem(accounts->gr_name); } showMaximized(); } /** * Empty destructor. * */ UserDialog::~UserDialog() { } /** * Creates the first tab, all userinfo is here. * */ void UserDialog::setupTab1() { QPixmap mypixmap; QWidget *tabpage = new QWidget(myTabWidget,"page1"); QVBoxLayout *layout = new QVBoxLayout(tabpage); layout->setMargin(5); // Picture picturePushButton = new QPushButton(tabpage,"Label"); picturePushButton->setMinimumSize(48,48); picturePushButton->setMaximumSize(48,48); picturePushButton->setPixmap(Resource::loadPixmap("usermanager/usericon")); // Load default usericon. connect(picturePushButton,SIGNAL(clicked()),this,SLOT(clickedPicture())); // Clicking the picture should invoke pictureselector. // Login QLabel *loginLabel=new QLabel(tabpage,"Login: "); loginLabel->setText("Login: "); loginLineEdit=new QLineEdit(tabpage,"Login: "); // UID QLabel *uidLabel=new QLabel(tabpage,"uid: "); uidLabel->setText("UserID: "); uidLineEdit=new QLineEdit(tabpage,"uid: "); uidLineEdit->setEnabled(false); // Username (gecos) QLabel *gecosLabel=new QLabel(tabpage,"gecos"); gecosLabel->setText("Username: "); gecosLineEdit=new QLineEdit(tabpage,"gecos"); // Password QLabel *passwordLabel=new QLabel(tabpage,"password"); passwordLabel->setText("Password: "); passwordLineEdit=new QLineEdit(tabpage,"password"); passwordLineEdit->setEchoMode(QLineEdit::Password); // Shell QLabel *shellLabel=new QLabel(tabpage,"shell"); shellLabel->setText("Shell: "); shellComboBox=new QComboBox(tabpage,"shell"); shellComboBox->setEditable(true); shellComboBox->insertItem("/bin/sh"); shellComboBox->insertItem("/bin/ash"); shellComboBox->insertItem("/bin/false"); // Primary Group QLabel *groupLabel=new QLabel(tabpage,"group"); groupLabel->setText("Primary group: "); groupComboBox=new QComboBox(tabpage,"PrimaryGroup"); // Widget layout QHBoxLayout *hlayout=new QHBoxLayout(-1,"hlayout"); layout->addWidget(picturePushButton); layout->addSpacing(5); layout->addLayout(hlayout); QVBoxLayout *vlayout1=new QVBoxLayout(-1,"vlayout1"); QVBoxLayout *vlayout2=new QVBoxLayout(-1,"vlayout2"); // First column, labels vlayout1->addWidget(loginLabel); vlayout1->addSpacing(5); vlayout1->addWidget(uidLabel); vlayout1->addSpacing(5); vlayout1->addWidget(gecosLabel); vlayout1->addSpacing(5); vlayout1->addWidget(passwordLabel); vlayout1->addSpacing(5); vlayout1->addWidget(shellLabel); vlayout1->addSpacing(5); vlayout1->addWidget(groupLabel); // Second column, data vlayout2->addWidget(loginLineEdit); vlayout2->addSpacing(5); vlayout2->addWidget(uidLineEdit); vlayout2->addSpacing(5); vlayout2->addWidget(gecosLineEdit); vlayout2->addSpacing(5); vlayout2->addWidget(passwordLineEdit); vlayout2->addSpacing(5); vlayout2->addWidget(shellComboBox); vlayout2->addSpacing(5); vlayout2->addWidget(groupComboBox); hlayout->addLayout(vlayout1); hlayout->addLayout(vlayout2); myTabWidget->addTab(tabpage,"User Info"); } /** * Creates the second tab containing additional groups for the user. * */ void UserDialog::setupTab2() { QWidget *tabpage = new QWidget(myTabWidget,"page2"); QVBoxLayout *layout = new QVBoxLayout(tabpage); layout->setMargin(5); // Additional groups groupsListView=new QListView(tabpage,"groups"); groupsListView->addColumn("Additional groups"); groupsListView->setColumnWidthMode(0,QListView::Maximum); groupsListView->setMultiSelection(true); + groupsListView->setAllColumnsShowFocus(false); layout->addSpacing(5); // Grouplist layout->addWidget(groupsListView); myTabWidget->addTab(tabpage,"User Groups"); } /** * Static function that creates the userinfo dialog. * The user will be prompted to add a user. * * @param uid This is a suggested available UID. * @param gid This is a suggested available GID. * * @return <code>true</code> if the user was successfully added, otherwise <code>false</code>. * */ bool UserDialog::addUser(int uid, int gid) { UserDialog *adduserDialog=new UserDialog(); adduserDialog->setCaption(tr("Add User")); adduserDialog->userID=uid; // Set next available UID as default uid. adduserDialog->groupID=gid; // Set next available GID as default gid. // Insert default group into groupComboBox adduserDialog->groupComboBox->insertItem("<create new group>",0); adduserDialog->uidLineEdit->setText(QString::number(uid)); // Show the dialog! if(!(adduserDialog->exec())) return false; if((adduserDialog->groupComboBox->currentItem()!=0)) { accounts->findGroup(adduserDialog->groupComboBox->currentText()); adduserDialog->groupID=accounts->gr_gid; qWarning(QString::number(accounts->gr_gid)); } if(!(accounts->addUser(adduserDialog->loginLineEdit->text(), adduserDialog->passwordLineEdit->text(), adduserDialog->uidLineEdit->text().toInt(), adduserDialog->groupID, adduserDialog->gecosLineEdit->text(), QString("/home/")+adduserDialog->loginLineEdit->text() , adduserDialog->shellComboBox->currentText()))) { QMessageBox::information(0,"Ooops!","Something went wrong!\nUnable to add user."); return false; } // Add User to additional groups. QListViewItemIterator it( adduserDialog->groupsListView ); for ( ; it.current(); ++it ) { if ( it.current()->isSelected() ) accounts->addGroupMember(it.current()->text(0),adduserDialog->loginLineEdit->text()); } return true; } /** * Deletes the user account. * * @param username User to be deleted. * * @return <code>true</code> if the user was successfully deleted, otherwise <code>false</code>. * */ bool UserDialog::delUser(const char *username) { if((accounts->findUser(username))) { // Does that user exist? if(!(accounts->delUser(username))) { // Delete the user. QMessageBox::information(0,"Ooops!","Something went wrong\nUnable to delete user: "+QString(username)+"."); } } else { QMessageBox::information(0,"Invalid Username","That username ("+QString(username)+")does not exist."); return false; } return true; } /** * This displays a confirmation dialog wether a user should be deleted or not. * (And also deletes the account) * * @param username User to be deleted. * * @return <code>true</code> if the user was successfully deleted, otherwise <code>false</code>. * */ bool UserDialog::editUser(const char *username) { UserDialog *edituserDialog=new UserDialog(); // Create Dialog edituserDialog->setCaption(tr("Edit User")); accounts->findUser(username); // Locate user in database and fill variables in 'accounts' object. accounts->findGroup(accounts->pw_gid); // Locate the user's primary group, and fill group variables in 'accounts' object. // Fill widgets with userinfo. edituserDialog->loginLineEdit->setText(accounts->pw_name); edituserDialog->uidLineEdit->setText(QString::number(accounts->pw_uid)); edituserDialog->gecosLineEdit->setText(accounts->pw_gecos); // Set password to '........', we will later check if this still is the contents, if not, the password has been changed. edituserDialog->passwordLineEdit->setText("........"); // If this user is not using /bin/sh,/bin/ash or /bin/false as shell, add that entry to the shell-combobox. if(accounts->pw_shell!="/bin/sh" && accounts->pw_shell!="/bin/ash" && accounts->pw_shell!="/bin/false") { edituserDialog->shellComboBox->insertItem(accounts->pw_shell,0); edituserDialog->shellComboBox->setCurrentItem(0); } // Select the primary group for this user. for(int i=0;i<edituserDialog->groupComboBox->count();++i) { if(accounts->gr_name==edituserDialog->groupComboBox->text(i)) { edituserDialog->groupComboBox->setCurrentItem(i); } } // Select the groups in the listview, to which the user belongs. QRegExp userRegExp(QString("[:\\s]%1\\s").arg(username)); QStringList tempList=accounts->groupStringList.grep(userRegExp); // Find all entries in the group database, that the user is a member of. for(QStringList::Iterator it=tempList.begin(); it!=tempList.end(); ++it) { // Iterate over all of them. QListViewItemIterator lvit( edituserDialog->groupsListView ); // Compare to all groups. for ( ; lvit.current(); ++lvit ) { if(lvit.current()->text(0)==(*it).left((*it).find(":"))) { lvit.current()->setSelected(true); // If we find a line with that groupname, select it.; } } } if(!(edituserDialog->exec())) return false; // SHOW THE DIALOG! accounts->findUser(username); // Fill user variables in 'acccounts' object. accounts->pw_name=edituserDialog->loginLineEdit->text(); // Has the password been changed ? Make a new "crypt":ed password. if(edituserDialog->passwordLineEdit->text()!="........") accounts->pw_passwd=crypt(edituserDialog->passwordLineEdit->text(), accounts->crypt_make_salt()); // Set all variables in accounts object, that will be used when calling 'updateUser()' accounts->pw_uid=edituserDialog->uidLineEdit->text().toInt(); accounts->findGroup(edituserDialog->groupComboBox->currentText()); // Fill all group variables in 'accounts' object. accounts->pw_gid=accounts->gr_gid; accounts->pw_gecos=edituserDialog->gecosLineEdit->text(); accounts->pw_shell=edituserDialog->shellComboBox->currentText(); // Update userinfo, using the information stored in the user variables stored in the accounts object. accounts->updateUser(username); // Remove user from all groups he/she is a member of. (could be done in a better way I guess, this was simple though.) for(QStringList::Iterator it=tempList.begin(); it!=tempList.end(); ++it) { accounts->delGroupMember((*it).left((*it).find(":")),username); } // Add User to additional groups that he/she is a member of. QListViewItemIterator it( edituserDialog->groupsListView ); for ( ; it.current(); ++it ) { if ( it.current()->isSelected() ) accounts->addGroupMember(it.current()->text(0),edituserDialog->loginLineEdit->text()); } return true; } /** * "OK" has been clicked. Verify some information before closing the dialog. * */ void UserDialog::accept() { // Add checking... valid username? username taken? if(loginLineEdit->text().isEmpty()) { QMessageBox::information(0,"Empty Login","Please enter a login."); return; } QDialog::accept(); } /** * This slot is called when the usericon is clicked, this loads (should) the iconselector. * */ void UserDialog::clickedPicture() { - QMessageBox::information(0,"Sorry!","Icon selection not yet implemented.\nComming real soon now! (tm)"); + QString filename=OFileDialog::getOpenFileName(OFileSelector::EXTENDED,"/opt/QtPalmtop/pics"); + // OFileDialog *fd=new OFileDialog("Select Icon",this, OFileSelector::OPEN, OFileSelector::EXTENDED,"/"); + //fd->showMaximized(); + //fd->exec(); + QMessageBox::information(0,"Sorry!","Icon selection not yet implemented.\nComming real soon now! (tm)\n"+filename); } diff --git a/noncore/settings/usermanager/userdialog.h b/noncore/settings/usermanager/userdialog.h index b44de9e..df54269 100644 --- a/noncore/settings/usermanager/userdialog.h +++ b/noncore/settings/usermanager/userdialog.h @@ -1,55 +1,57 @@ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef USERDIALOG_H #define USERDIALOG_H #include <qdialog.h> #include <qlineedit.h> #include <qcombobox.h> #include <qlistview.h> #include <qtabwidget.h> #include <qpushbutton.h> #include <qpe/resource.h> +#include <opie/ofiledialog.h> + class UserDialog : public QDialog { Q_OBJECT private: QTabWidget *myTabWidget; QPushButton *picturePushButton; QLineEdit *loginLineEdit; QLineEdit *uidLineEdit; QLineEdit *gecosLineEdit; QLineEdit *passwordLineEdit; QComboBox *shellComboBox; QComboBox *groupComboBox; QListView *groupsListView; QStringList groupMembers; QString pictureLocation; int groupID; int userID; void setupTab1(void); void setupTab2(void); void accept(void); private slots: void clickedPicture(void); public: UserDialog( QWidget* parent = 0, const char* name = 0, bool modal=true, WFlags fl = 0 ); ~UserDialog(); static bool addUser(int uid, int gid); static bool editUser(const char *username); static bool delUser(const char *username); }; #endif diff --git a/noncore/settings/usermanager/usermanager.pro b/noncore/settings/usermanager/usermanager.pro index fc45f93..2f3212f 100644 --- a/noncore/settings/usermanager/usermanager.pro +++ b/noncore/settings/usermanager/usermanager.pro @@ -1,10 +1,10 @@ TEMPLATE = app #CONFIG = qt warn_on debug CONFIG = qt warn_on release HEADERS = usermanager.h userdialog.h groupdialog.h passwd.h SOURCES = usermanager.cpp userdialog.cpp groupdialog.cpp passwd.h main.cpp INCLUDEPATH += $(OPIEDIR)/include DEPENDPATH += $(OPIEDIR)/include -LIBS += -lqpe -lcrypt +LIBS += -lqpe -lopie -lcrypt TARGET = usermanager DESTDIR = $(OPIEDIR)/bin |