UnixEntityManager.java
package eu.javaexperience.unix.user;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import eu.javaexperience.arrays.ArrayTools;
import eu.javaexperience.collection.CollectionTools;
import eu.javaexperience.io.IOTools;
import eu.javaexperience.io.file.FileReloadEntry;
import eu.javaexperience.log.JavaExperienceLoggingFacility;
import eu.javaexperience.log.LogLevel;
import eu.javaexperience.log.Loggable;
import eu.javaexperience.log.Logger;
import eu.javaexperience.log.LoggingTools;
import eu.javaexperience.reflect.Mirror;
import eu.javaexperience.regex.RegexTools;
import eu.javaexperience.semantic.references.MayNotNull;
import eu.javaexperience.semantic.references.MayNull;
import eu.javaexperience.unix.user.exception.GroupAlredyExistException;
import eu.javaexperience.unix.user.exception.GroupDoesnotExistException;
import eu.javaexperience.unix.user.exception.InsufficientPermissionException;
import eu.javaexperience.unix.user.exception.UserDoesnotExistException;
public final class UnixEntityManager
{
protected static final Logger LOG = JavaExperienceLoggingFacility.getLogger(new Loggable("UnixEntityManager"));
private static boolean useSUDO = false;
protected static final File passwdFile = new File("/etc/passwd");
protected static final File groupFile = new File("/etc/group");
protected static final FileReloadEntry<PasswdIndex> passwdIndex = new FileReloadEntry<UnixEntityManager.PasswdIndex>(passwdFile)
{
@Override
protected PasswdIndex processFile(File f) throws IOException
{
return PasswdIndex.parseAndIndexPasswdFile("/etc/passwd");
}
};
protected static final FileReloadEntry<GroupIndex> groupIndex = new FileReloadEntry<UnixEntityManager.GroupIndex>(groupFile)
{
@Override
protected GroupIndex processFile(File f) throws IOException
{
return GroupIndex.parseAndIndexGroupFile("/etc/group");
}
};
protected static PasswdIndex getPasswdIndex() throws FileNotFoundException, IOException
{
return passwdIndex.get();
}
protected static GroupIndex getGroupIndex() throws FileNotFoundException, IOException
{
return groupIndex.get();
}
/**
* I've faced an interesting bug in the previous version:
* When i cached the passwd's file content, sometimes i get the older
* version of the from the cache.
* The reason is that the java returns the file last modification time in
* secounds resolution, so if the passwd file modified right since some
* milisecs the cache determines (badly) that the file not modified, so this
* case is important if we apply file cache based on last modification.
* */
protected static boolean needReload(long entryTime, long fileTime)
{
entryTime /= 1000;
fileTime /= 1000;
long currentTime = System.currentTimeMillis()/1000;
return fileTime == currentTime || fileTime > entryTime;
}
public static class struct_passwd
{
public String username;
public int uid;
public int gid;
public String etcData;
public String homeDir;
public String shell;
public static @MayNotNull struct_passwd parseLine(String line)
{
String[] data = RegexTools.COLON.split(line);
struct_passwd ret = new struct_passwd();
ret.username = data[0];
ret.uid = Integer.parseInt(data[2]);
ret.gid = Integer.parseInt(data[3]);
ret.etcData = data[4];
ret.homeDir = data[5];
ret.shell = data[6];
return ret;
}
public static @MayNull struct_passwd parseLineSafe(String line)
{
try
{
return parseLine(line);
}
catch(Throwable t)
{
LoggingTools.tryLogFormatException(LOG, LogLevel.WARNING, t, "Can't parse passwd line: %s", line);
return null;
}
}
}
public static class PasswdIndex
{
public Map<String, struct_passwd> byUsername = new HashMap<>();
public Map<Integer, struct_passwd> byUid = new HashMap<>();
public static PasswdIndex parseAndIndexAll(String[] lines)
{
PasswdIndex ret = new PasswdIndex();
for(String s: lines)
{
if(null != s)
{
struct_passwd p = struct_passwd.parseLineSafe(s);
if(null != p)
{
ret.byUsername.put(p.username, p);
ret.byUid.put(p.uid, p);
}
}
}
return ret;
}
public static PasswdIndex parseAndIndexPasswdFile(String file) throws FileNotFoundException, IOException
{
String[] lines = RegexTools.LINUX_NEW_LINE.split(IOTools.getFileContents(file));
return parseAndIndexAll(lines);
}
}
public static class struct_group
{
public String groupname;
public int gid;
public HashSet<String> users = new HashSet<>();
public static @MayNotNull struct_group parseLine(String line)
{
String[] data = RegexTools.COLON.split(line);
struct_group ret = new struct_group();
ret.groupname = data[0];
ret.gid = Integer.parseInt(data[2]);
if(data.length > 3)
{
CollectionTools.copyInto(RegexTools.COMMA.split(data[3]), ret.users);
}
return ret;
}
public static @MayNull struct_group parseLineSafe(String line)
{
try
{
return parseLine(line);
}
catch(Throwable t)
{
LoggingTools.tryLogFormatException(LOG, LogLevel.WARNING, t, "Can't parse group line: %s", line);
return null;
}
}
}
public static class GroupIndex
{
public Map<String, struct_group> byGroupname = new HashMap<>();
public Map<Integer, struct_group> byGid = new HashMap<>();
public static GroupIndex parseAndIndexAll(String[] lines)
{
GroupIndex ret = new GroupIndex();
for(String s: lines)
{
if(null != s)
{
struct_group g = struct_group.parseLineSafe(s);
if(null != g)
{
ret.byGroupname.put(g.groupname, g);
ret.byGid.put(g.gid, g);
}
}
}
return ret;
}
public static GroupIndex parseAndIndexGroupFile(String file) throws FileNotFoundException, IOException
{
String[] lines = RegexTools.LINUX_NEW_LINE.split(IOTools.getFileContents(file));
return parseAndIndexAll(lines);
}
}
/********************************* Error codes ********************************/
private static void uidNonex(int uid) throws UserDoesnotExistException
{
throw new UserDoesnotExistException("UNIX user with UID: " + uid + " doesnot exist!");
}
private static void gidNonex(int gid) throws GroupDoesnotExistException
{
throw new GroupDoesnotExistException("UNIX group with GID: " + gid + " doesnot exist!");
}
private static void userNonex(String usr) throws UserDoesnotExistException
{
throw new UserDoesnotExistException("UNIX user with username: " + usr + " doesnot exist!");
}
private static void groupNonex(String grp)throws GroupDoesnotExistException
{
throw new GroupDoesnotExistException("UNIX group with groupname: "+ grp + " doesnot exist!");
}
public static struct_passwd getExistingUser(PasswdIndex index, String username) throws UserDoesnotExistException
{
struct_passwd p = index.byUsername.get(username);
if(null == p)
{
userNonex(username);
}
return p;
}
public static struct_passwd getExistingUser(PasswdIndex index, int uid) throws UserDoesnotExistException
{
struct_passwd p = index.byUid.get(uid);
if(null == p)
{
uidNonex(uid);
}
return p;
}
public static struct_group getExistingGroup(GroupIndex index, String grpname) throws GroupDoesnotExistException
{
struct_group grp = index.byGroupname.get(grpname);
if(null == grp)
{
groupNonex(grpname);
}
return grp;
}
public static struct_group getExistingGroup(GroupIndex index, int gid) throws GroupDoesnotExistException
{
struct_group grp = index.byGid.get(gid);
if(null == grp)
{
gidNonex(gid);
}
return grp;
}
public static boolean isUserExistByName(String username) throws IOException
{
return getPasswdIndex().byUsername.containsKey(username);
}
public static boolean isUserExistByUID(int UID) throws IOException
{
return getPasswdIndex().byUid.containsKey(UID);
}
public static int UIDByUsername(String username) throws IOException, UserDoesnotExistException
{
return getExistingUser(getPasswdIndex(), username).uid;
}
public static String usernameByUID(int UID) throws IOException, UserDoesnotExistException
{
return getExistingUser(getPasswdIndex(), UID).username;
}
public static String getUserHomePath(String username) throws IOException,UserDoesnotExistException
{
return getExistingUser(getPasswdIndex(), username).homeDir;
}
public static String getUserRealName(String username) throws IOException, UserDoesnotExistException
{
String[] buf = getExistingUser(getPasswdIndex(), username).etcData.split(",");
return buf.length > 0 ? buf[0] : null;
}
public static String[] usersBetween(int MIN_UID, int MAX_UID) throws IOException
{
PasswdIndex index = getPasswdIndex();
ArrayList<String> users = new ArrayList<>();
for(Entry<Integer, struct_passwd> kv:index.byUid.entrySet())
{
int uidbuf = kv.getValue().uid;
if ((uidbuf >= MIN_UID) && (uidbuf <= MAX_UID))
{
users.add(kv.getValue().username);
}
}
return users.toArray(Mirror.emptyStringArray);
}
public static String[] usersUsernameContains(String part) throws IOException
{
PasswdIndex index = getPasswdIndex();
ArrayList<String> users = new ArrayList<>();
for(Entry<Integer, struct_passwd> kv:index.byUid.entrySet())
{
struct_passwd p = kv.getValue();
if(p.username.contains(part))
{
users.add(p.username);
}
}
return users.toArray(Mirror.emptyStringArray);
}
public static boolean isGroupExistByName(String groupname) throws IOException
{
return getGroupIndex().byGroupname.containsKey(groupname);
}
public static boolean isGroupExistByGID(int GID) throws IOException
{
return getGroupIndex().byGid.containsKey(GID);
}
public static int gidByGroupname(String groupname) throws IOException, GroupDoesnotExistException
{
return getExistingGroup(getGroupIndex(), groupname).gid;
}
public static String groupnameByGID(int GID) throws IOException,GroupDoesnotExistException
{
return getExistingGroup(getGroupIndex(), GID).groupname;
}
public static String[] groupsBetween(int MIN_GID, int MAX_GID)throws IOException
{
GroupIndex index = getGroupIndex();
ArrayList<String> users = new ArrayList<>();
for(Entry<Integer, struct_group> kv:index.byGid.entrySet())
{
int uidbuf = kv.getValue().gid;
if ((uidbuf >= MIN_GID) && (uidbuf <= MAX_GID))
{
users.add(kv.getValue().groupname);
}
}
return users.toArray(Mirror.emptyStringArray);
}
public static String[] groupGroupnameContains(String part) throws IOException
{
GroupIndex index = getGroupIndex();
ArrayList<String> users = new ArrayList<>();
for(Entry<Integer, struct_group> kv:index.byGid.entrySet())
{
struct_group p = kv.getValue();
if(p.groupname.contains(part))
{
users.add(p.groupname);
}
}
return users.toArray(Mirror.emptyStringArray);
}
public static boolean isUserInGroup(String username, String groupname) throws IOException, GroupDoesnotExistException
{
struct_group grp = getExistingGroup(getGroupIndex(), groupname);
return grp.users.contains(username);
}
public static String[] getUserGroups(String username) throws IOException, UserDoesnotExistException
{
ArrayList<String> ret = new ArrayList<>();
for(Entry<String, struct_group> kv:getGroupIndex().byGroupname.entrySet())
{
if(kv.getValue().users.contains(username))
{
ret.add(kv.getKey());
}
}
return ret.toArray(Mirror.emptyStringArray);
}
public static void addUserToGroup(String username, String groupname)throws IOException, GroupDoesnotExistException, UserDoesnotExistException, InsufficientPermissionException
{
if (!isGroupExistByName(groupname))
groupNonex(groupname);
String[] groups = getUserGroups(username);
StringBuilder gs = new StringBuilder();
for (int i = 0; i < groups.length; i++)
{
gs.append(groups[i]);
gs.append(",");
}
gs.append(groupname);
String[] command = {"usermod", "-G", gs.toString(), username};
if (useSUDO)
{
command = ArrayTools.arrayAppend("sudo", command);
}
try
{
new ProcessBuilder(command).start().waitFor();
}
catch (Exception e)
{
throw new InsufficientPermissionException(
"Cannot perform useradd command");
}
}
public static void delUserFromGroup(String username, String groupname)throws IOException, UserDoesnotExistException,InsufficientPermissionException
{
if (!isUserExistByName(username))
userNonex(username);
String[] groups = getUserGroups(username);
StringBuilder gs = new StringBuilder();
for (int i = 0; i < groups.length; i++)
if (!groups[i].equals(groupname))
{
if(i !=0)
gs.append(",");
gs.append(groups[i]);
}
String[] command = {"usermod", "-G", gs.toString(), username};
if (useSUDO)
{
command = ArrayTools.arrayAppend("sudo", command);
}
try
{
new ProcessBuilder(command).start().waitFor();
}
catch (Exception e)
{
throw new InsufficientPermissionException("Cannot perform useradd command");
}
}
public static void createGroup(String groupname) throws IOException,InsufficientPermissionException
{
String[] command ={"addgroup", groupname};
if (useSUDO)
{
command = ArrayTools.arrayAppend("sudo", command);
}
try
{
new ProcessBuilder(command).start();
}
catch (IOException e)
{
throw new InsufficientPermissionException("Cannot perform addgroup command");
}
}
public static String[] usersInGroup(String group) throws IOException,GroupDoesnotExistException
{
return getExistingGroup(getGroupIndex(), group).users.toArray(Mirror.emptyStringArray);
}
public static boolean login(String user, String password) throws IOException, InterruptedException
{
Process p = new ProcessBuilder(new String[]{"pwauth"}).start();
p.getOutputStream().write((user + "\n" + password + "\n").getBytes());
p.getOutputStream().flush();
if (p.waitFor() == 0)
{
return true;
}
return false;
}
public static boolean delGroup(String group) throws InterruptedException,IOException
{
return new ProcessBuilder(new String[]{"groupdel", group}).start().waitFor() == 0;
}
public static boolean lockUser(String user, boolean l_p_u)throws IOException, UserDoesnotExistException, InterruptedException
{
getExistingUser(getPasswdIndex(), user);
return new ProcessBuilder(new String[]{"passwd", user, l_p_u ? "-l" : "-u"}).start().waitFor() == 0;
}
public static void kickUsersInRange(String username, int min, int max) throws IOException, InterruptedException
{
String[] users = usersBetween(min, max);
for (int i = 0; i < users.length; i++)
if (users[i].equals(username))
{
kickUser(username);
return;
}
}
public static void kickUser(String username) throws InterruptedException,IOException
{
new ProcessBuilder(new String[]{"/usr/bin/killall", "-9", "--user", username}).start().waitFor();
}
public static void renameGroup(String mirol, String mire)throws IOException, GroupDoesnotExistException,InterruptedException, GroupAlredyExistException
{
if (!isGroupExistByName(mirol))
groupNonex(mirol);
if (isGroupExistByName(mire))
throw new GroupAlredyExistException(mire+ " nevű csoport már létezik, " + mirol + "-t nem nevezheted át erre.");
new ProcessBuilder(new String[] {"groupmod", mirol, "-n", mire}).start().waitFor();
}
protected static String[] loadShadowLines() throws FileNotFoundException, IOException
{
return IOTools.readAllLine(new File("/etc/shadow"));
}
public static boolean isUserLocked(String user) throws IOException, UserDoesnotExistException
{
String[] lines = loadShadowLines();
String[] unit = lines;
for (int i = 0; i < lines.length; i++)
if ((unit = lines[i].split(":"))[0].equals(user))
{
return unit[1].charAt(0) == '!';
}
userNonex(user);
return false;
}
public static String getUserDefaultGroupName(String username) throws GroupDoesnotExistException, UserDoesnotExistException, FileNotFoundException, IOException
{
return getExistingGroup(getGroupIndex(), getExistingUser(getPasswdIndex(), username).gid).groupname;
}
public static int getUserDefaultGroupGid(String username) throws UserDoesnotExistException, FileNotFoundException, IOException
{
return getExistingUser(getPasswdIndex(), username).gid;
}
}