/* Editor Settings: expandtabs and use 4 spaces for indentation * ex: set softtabstop=4 tabstop=8 expandtab shiftwidth=4: * * -*- mode: c, c-basic-offset: 4 -*- */ /* * Copyright Likewise Software 2004-2008 * All rights reserved. * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the license, or (at * your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * General Public License for more details. You should have received a copy * of the GNU Lesser General Public License along with this program. If * not, see . * * LIKEWISE SOFTWARE MAKES THIS SOFTWARE AVAILABLE UNDER OTHER LICENSING * TERMS AS WELL. IF YOU HAVE ENTERED INTO A SEPARATE LICENSE AGREEMENT * WITH LIKEWISE SOFTWARE, THEN YOU MAY ELECT TO USE THE SOFTWARE UNDER THE * TERMS OF THAT SOFTWARE LICENSE AGREEMENT INSTEAD OF THE TERMS OF THE GNU * LESSER GENERAL PUBLIC LICENSE, NOTWITHSTANDING THE ABOVE NOTICE. IF YOU * HAVE QUESTIONS, OR WISH TO REQUEST A COPY OF THE ALTERNATE LICENSING * TERMS OFFERED BY LIKEWISE SOFTWARE, PLEASE CONTACT LIKEWISE SOFTWARE AT * license@likewisesoftware.com */ #include "domainjoin.h" #include "ctarray.h" #include "ctstrutils.h" #include "ctfileutils.h" #include "djstr.h" #include "djdistroinfo.h" #define NSSWITCH_CONF_PATH "/etc/nsswitch.conf" #define NSSWITCH_LWIDEFAULTS "/etc/nsswitch.lwi_defaults" #define GCE(x) GOTO_CLEANUP_ON_CENTERROR((x)) typedef struct { char *leadingWhiteSpace; CTParseToken name; DynamicArray modules; char *comment; } NsswitchEntry; typedef struct { char *filename; /*Holds NsswitchEntry*/ DynamicArray lines; BOOLEAN modified; } NsswitchConf; static const NsswitchEntry * GetEntry(const NsswitchConf *conf, size_t index) { if(index >= conf->lines.size) return NULL; return ((NsswitchEntry *)conf->lines.data) + index; } static CTParseToken * GetEntryModule(NsswitchEntry *entry, size_t index) { if(index >= entry->modules.size) return NULL; return ((CTParseToken *)entry->modules.data) + index; } static void FreeNsswitchEntryContents(NsswitchEntry *entry) { size_t i; for(i = 0; i < entry->modules.size; i++) { CTFreeParseTokenContents(GetEntryModule(entry, i)); } CTArrayFree(&entry->modules); CT_SAFE_FREE_STRING(entry->leadingWhiteSpace); CT_SAFE_FREE_STRING(entry->comment); CTFreeParseTokenContents(&entry->name); } static void FreeNsswitchConfContents(NsswitchConf *conf) { size_t i; for(i = 0; i < conf->lines.size; i++) { FreeNsswitchEntryContents(((NsswitchEntry *)conf->lines.data) + i); } } /* Get the printed form of a line from the parsed form by concatenating all of the strings together */ static CENTERROR GetPrintedLine(DynamicArray *dest, NsswitchConf *conf, int line) { CENTERROR ceError = CENTERROR_SUCCESS; size_t len = 0; char *pos; int i; const NsswitchEntry *lineObj = GetEntry(conf, line); len += CTGetTokenLen(&lineObj->name); for(i = 0; i < lineObj->modules.size; i++) { len += CTGetTokenLen(&((CTParseToken *)lineObj->modules.data)[i]); } if(lineObj->comment != NULL) len += strlen(lineObj->comment); //For the terminating NULL len++; if(len > dest->capacity) GCE(ceError = CTSetCapacity(dest, 1, len)); pos = dest->data; CTAppendTokenString(&pos, &lineObj->name); for(i = 0; i < lineObj->modules.size; i++) { CTAppendTokenString(&pos, &((CTParseToken *)lineObj->modules.data)[i]); } if(lineObj->comment != NULL) { memcpy(pos, lineObj->comment, strlen(lineObj->comment)); pos += strlen(lineObj->comment); } *pos = '\0'; dest->size = len; cleanup: return ceError; } static CENTERROR AddFormattedLine(NsswitchConf *conf, const char *filename, const char *linestr, const char **endptr) { CENTERROR ceError = CENTERROR_SUCCESS; NsswitchEntry lineObj; const char *pos = linestr; const char *token_start = NULL; CTParseToken token; memset(&lineObj, 0, sizeof(lineObj)); memset(&token, 0, sizeof(token)); /* Find the leading whitespace in the line */ token_start = pos; while(isblank(*pos)) pos++; GCE(ceError = CTStrndup(token_start, pos - token_start, &lineObj.leadingWhiteSpace)); /* Read the name of the entry and attach its trailing : or = */ GCE(ceError = CTReadToken(&pos, &lineObj.name, "=: \t", ";#\r\n", "")); /* Create an array of the modules for this entry */ while(strchr("\r\n;#", *pos) == NULL) { GCE(ceError = CTReadToken(&pos, &token, "=: \t", ";#\r\n", "")); GCE(ceError = CTArrayAppend(&lineObj.modules, sizeof(CTParseToken), &token, 1)); memset(&token, 0, sizeof(token)); } /*Read the comment, if there is one*/ token_start = pos; while(strchr("\r\n", *pos) == NULL) pos++; if(pos != token_start) GCE(ceError = CTStrndup(token_start, pos - token_start, &lineObj.comment)); GCE(ceError = CTArrayAppend(&conf->lines, sizeof(lineObj), &lineObj, 1)); memset(&lineObj, 0, sizeof(lineObj)); if(endptr != NULL) *endptr = pos; cleanup: FreeNsswitchEntryContents(&lineObj); CTFreeParseTokenContents(&token); return ceError; } static CENTERROR ReadNsswitchFile(NsswitchConf *conf, const char *rootPrefix, const char *filename) { CENTERROR ceError = CENTERROR_SUCCESS; FILE *file = NULL; PSTR buffer = NULL; char *fullPath = NULL; BOOLEAN endOfFile = FALSE; BOOLEAN exists; if(rootPrefix == NULL) rootPrefix = ""; GCE(ceError = CTAllocateStringPrintf( &fullPath, "%s%s", rootPrefix, filename)); DJ_LOG_INFO("Reading nsswitch file %s", fullPath); GCE(ceError = CTCheckFileOrLinkExists(fullPath, &exists)); if(!exists) { DJ_LOG_INFO("File %s does not exist", fullPath); ceError = CENTERROR_INVALID_FILENAME; goto cleanup; } GCE(ceError = CTStrdup(filename, &conf->filename)); GCE(ceError = CTOpenFile(fullPath, "r", &file)); CT_SAFE_FREE_STRING(fullPath); while(TRUE) { CT_SAFE_FREE_STRING(buffer); GCE(ceError = CTReadNextLine(file, &buffer, &endOfFile)); if(endOfFile) break; GCE(ceError = AddFormattedLine(conf, filename, buffer, NULL)); } conf->modified = FALSE; cleanup: CT_SAFE_FREE_STRING(buffer); if(file != NULL) CTCloseFile(file); CT_SAFE_FREE_STRING(fullPath); if(!CENTERROR_IS_OK(ceError)) FreeNsswitchConfContents(conf); return ceError; } static CENTERROR WriteNsswitchConfiguration(const char *rootPrefix, NsswitchConf *conf) { CENTERROR ceError = CENTERROR_SUCCESS; DynamicArray printedLine; int i; char *tempName = NULL; char *finalName = NULL; char *prefixedPath = NULL; FILE *file = NULL; memset(&printedLine, 0, sizeof(printedLine)); GCE(ceError = CTAllocateStringPrintf(&prefixedPath, "%s%s", rootPrefix, conf->filename)); GCE(ceError = CTGetFileTempPath( prefixedPath, &finalName, &tempName)); DJ_LOG_INFO("Writing nsswitch configuration for %s", finalName); ceError = CTOpenFile(tempName, "w", &file); if(!CENTERROR_IS_OK(ceError)) { DJ_LOG_ERROR("Unable to open '%s' for writing", tempName); GCE(ceError); } for(i = 0; i < conf->lines.size; i++) { GCE(ceError = GetPrintedLine(&printedLine, conf, i)); GCE(ceError = CTFilePrintf(file, "%s\n", printedLine.data)); } GCE(ceError = CTCloseFile(file)); file = NULL; GCE(ceError = CTSafeReplaceFile( finalName, tempName)); DJ_LOG_INFO("File moved into place"); cleanup: if(file != NULL) CTCloseFile(file); CTArrayFree(&printedLine); CT_SAFE_FREE_STRING(tempName); CT_SAFE_FREE_STRING(finalName); CT_SAFE_FREE_STRING(prefixedPath); return ceError; } static int FindEntry(const NsswitchConf *conf, int startLine, const char *name) { int i; if(startLine == -1) return -1; for(i = startLine; i < conf->lines.size; i++) { if(GetEntry(conf, i)->name.value != NULL && !strcmp(GetEntry(conf, i)->name.value, name)) { return i; } } return -1; } static CENTERROR AddEntry(NsswitchConf *conf, const DistroInfo *distro, int *addedIndex, const char *name) { CENTERROR ceError = CENTERROR_SUCCESS; int line = -1; NsswitchEntry lineObj; const NsswitchEntry *copy; memset(&lineObj, 0, sizeof(lineObj)); GCE(ceError = CTStrdup(name, &lineObj.name.value)); for(line = 0; line < conf->lines.size; line++) { copy = GetEntry(conf, line); if(copy->name.value != NULL) { GCE(ceError = CTStrdup(copy->name.trailingSeparator, &lineObj.name.trailingSeparator)); break; } } if(lineObj.name.trailingSeparator == NULL) { //Couldn't find an existing line to copy the separator from. We'll //have to guess based on the OS if(distro->os == OS_AIX) GCE(ceError = CTStrdup(" = ", &lineObj.name.trailingSeparator)); else GCE(ceError = CTStrdup(": ", &lineObj.name.trailingSeparator)); } GCE(ceError = CTArrayAppend(&conf->lines, sizeof(NsswitchEntry), &lineObj, 1)); memset(&lineObj, 0, sizeof(lineObj)); conf->modified = 1; if(addedIndex != NULL) *addedIndex = conf->lines.size - 1; cleanup: FreeNsswitchEntryContents(&lineObj); return ceError; } const char * GetModuleSeparator(NsswitchConf *conf, const DistroInfo *distro) { int line; const NsswitchEntry *copy; for(line = 0; line < conf->lines.size; line++) { copy = GetEntry(conf, line); if(copy->modules.size > 1) { /*This line has at least two modules in it. There will be a * separator after the first module. */ return ((CTParseToken *)copy->modules.data)[0].trailingSeparator; } } /* We have to guess based on the OS */ if(distro->os == OS_AIX) { return ", "; } return " "; } static int FindModuleOnLine(const NsswitchConf *conf, int line, const char *name) { const NsswitchEntry *lineObj; int i; if(line < 0) return -1; lineObj = GetEntry(conf, line); for(i = 0; i < lineObj->modules.size; i++) { if(!strcmp(((CTParseToken *)lineObj->modules.data)[i].value, name)) return i; } return -1; } static CENTERROR InsertModule(NsswitchConf *conf, const DistroInfo *distro, int line, int insertIndex, const char *name) { CENTERROR ceError = CENTERROR_SUCCESS; NsswitchEntry *lineObj = (NsswitchEntry *)GetEntry(conf, line); CTParseToken *beforeModule = NULL, *afterModule = NULL; CTParseToken addModule; memset(&addModule, 0, sizeof(addModule)); if(insertIndex == -1) insertIndex = lineObj->modules.size; GCE(ceError = CTStrdup(name, &addModule.value)); if(insertIndex - 1 >= 0) beforeModule = (CTParseToken *)lineObj->modules.data + insertIndex - 1; if(insertIndex < lineObj->modules.size) afterModule = (CTParseToken *)lineObj->modules.data + insertIndex; if(beforeModule != NULL) { /* Copy the separator from the previous module */ GCE(ceError = CTDupOrNullStr(beforeModule->trailingSeparator, &addModule.trailingSeparator)); if(afterModule == NULL) { /*This is the last module. Put in the correct separator after the * previous module */ CT_SAFE_FREE_STRING(beforeModule->trailingSeparator); GCE(ceError = CTStrdup(GetModuleSeparator(conf, distro), &beforeModule->trailingSeparator)); } } else { if(afterModule == NULL) { //This is the last module if(lineObj->comment == NULL) { //Leave the trailingSeparator as NULL } else { GCE(ceError = CTStrdup(" ", &addModule.trailingSeparator)); } } else { //This is the first module. Add the appropriate separator to //distinguish it from the next module. GCE(ceError = CTStrdup(GetModuleSeparator(conf, distro), &addModule.trailingSeparator)); } } GCE(ceError = CTArrayInsert(&lineObj->modules, insertIndex, sizeof(addModule), &addModule, 1)); memset(&addModule, 0, sizeof(addModule)); conf->modified = 1; cleanup: CTFreeParseTokenContents(&addModule); return ceError; } static CENTERROR RemoveModule(NsswitchConf *conf, int line, int moduleIndex) { CENTERROR ceError = CENTERROR_SUCCESS; NsswitchEntry *lineObj = (NsswitchEntry *)GetEntry(conf, line); CTParseToken *beforeModule = NULL, *afterModule = NULL; CTParseToken *removeModule; removeModule = (CTParseToken *)lineObj->modules.data + moduleIndex; if(moduleIndex - 1 >= 0) beforeModule = (CTParseToken *)lineObj->modules.data + moduleIndex - 1; if(moduleIndex + 1 < lineObj->modules.size) afterModule = (CTParseToken *)lineObj->modules.data + moduleIndex + 1; if(afterModule == NULL && beforeModule != NULL) { /* Since the last module is being removed, move the trailingSeparator * to the previous module */ CT_SAFE_FREE_STRING(beforeModule->trailingSeparator); beforeModule->trailingSeparator = removeModule->trailingSeparator; removeModule->trailingSeparator = NULL; } CTFreeParseTokenContents(removeModule); GCE(ceError = CTArrayRemove(&lineObj->modules, moduleIndex, sizeof(CTParseToken), 1)); conf->modified = 1; cleanup: return ceError; } static CENTERROR RemoveLine(NsswitchConf *conf, int line) { CENTERROR ceError = CENTERROR_SUCCESS; NsswitchEntry *lineObj = (NsswitchEntry *)GetEntry(conf, line); if(lineObj == NULL) { GCE(ceError = CENTERROR_INVALID_PARAMETER); } FreeNsswitchEntryContents(lineObj); GCE(ceError = CTArrayRemove(&conf->lines, line, sizeof(NsswitchEntry), 1)); conf->modified = 1; cleanup: return ceError; } CENTERROR UnConfigureNameServiceSwitch() { return DJConfigureNameServiceSwitch(NULL, FALSE); } CENTERROR ReadNsswitchConf(NsswitchConf *conf, const char *testPrefix, BOOLEAN allowFileCreate) { PSTR copyDestPath = NULL; PSTR defaultFilePath = NULL; CENTERROR ceError = CENTERROR_SUCCESS; BOOLEAN bFileExists = FALSE; memset(conf, 0, sizeof(*conf)); //Keep trying to read different filenames until one of them is found if(!bFileExists) { bFileExists = TRUE; ceError = ReadNsswitchFile(conf, testPrefix, "/etc/nsswitch.conf"); if(ceError == CENTERROR_INVALID_FILENAME) { bFileExists = FALSE; ceError = CENTERROR_SUCCESS; } GCE(ceError); } if(!bFileExists) { bFileExists = TRUE; ceError = ReadNsswitchFile(conf, testPrefix, "/etc/netsvc.conf"); if(ceError == CENTERROR_INVALID_FILENAME) { bFileExists = FALSE; ceError = CENTERROR_SUCCESS; } GCE(ceError); } /* HP-UX 11.xx does not appear to have an nsswitch file in place by default. If we don't find on already installed, use our own */ if(!bFileExists) { GCE(ceError = CTAllocateStringPrintf( &defaultFilePath, "%s%s", testPrefix, NSSWITCH_LWIDEFAULTS)); GCE(ceError = CTCheckFileExists(defaultFilePath, &bFileExists)); if (bFileExists) { ceError = ReadNsswitchFile(conf, testPrefix, NSSWITCH_LWIDEFAULTS); GCE(ceError); CT_SAFE_FREE_STRING(conf->filename); GCE(ceError = CTStrdup(NSSWITCH_CONF_PATH, &conf->filename)); conf->modified = TRUE; if(allowFileCreate) { /* Copy over the original file. This way the user can more * clearly see what we changed by comparing nsswitch.conf with * nsswitch.conf.lwidentity.orig. Also, the permissions will be * correct this way when the file is written out. */ DJ_LOG_INFO("Copying default nsswitch file"); GCE(ceError = CTAllocateStringPrintf( ©DestPath, "%s%s", testPrefix, NSSWITCH_CONF_PATH)); ceError = CTCopyFileWithOriginalPerms(defaultFilePath, copyDestPath); GCE(ceError); } } } if(!bFileExists) { GCE(ceError = CENTERROR_INVALID_FILENAME); } cleanup: CT_SAFE_FREE_STRING(copyDestPath); CT_SAFE_FREE_STRING(defaultFilePath); return ceError; } static CENTERROR UsingLsass(BOOLEAN *using) { CENTERROR ceError = CENTERROR_SUCCESS; ceError = CTFindFileInPath("lsassd", BINDIR ":" SBINDIR, NULL); if(ceError == CENTERROR_FILE_NOT_FOUND) { *using = FALSE; ceError = CENTERROR_SUCCESS; } else if(CENTERROR_IS_OK(ceError)) *using = TRUE; return ceError; } static QueryResult RemoveCompat(NsswitchConf *conf, PSTR *description, LWException **exc) { DistroInfo distro; int compatLine; int noncompatLine; int compatModIndex; BOOLEAN passwdNeedUpdate = FALSE; BOOLEAN groupNeedUpdate = FALSE; QueryResult result = FullyConfigured; memset(&distro, 0, sizeof(distro)); /* The default configuration on FreeBSD is: * passwd: compat * passwd_compat: nis * group: compat * group_compat: nis * * The nsswitch man page says that compat must be the only module on the * line if it is used. Unfortunately, if a module is listed on the compat * line, it goes through a different interface which LSASS does not * understand. So this configuration must first be transformed into: * * passwd: files nis * group: files nis * * If the user is using compat mode with a non-default configuration, show * an error message instead. */ LW_CLEANUP_CTERR(exc, DJGetDistroInfo(NULL, &distro)); compatLine = FindEntry(conf, 0, "passwd_compat"); if(compatLine != -1) { const NsswitchEntry *lineEntry = GetEntry(conf, compatLine); passwdNeedUpdate = TRUE; if(lineEntry->modules.size != 1) { result = CannotConfigure; goto done_configuring; } if(FindModuleOnLine(conf, compatLine, "nis") == -1) { result = CannotConfigure; goto done_configuring; } noncompatLine = FindEntry(conf, 0, "passwd"); if(noncompatLine == -1) { result = CannotConfigure; goto done_configuring; } lineEntry = GetEntry(conf, noncompatLine); if(lineEntry->modules.size != 1) { result = CannotConfigure; goto done_configuring; } compatModIndex = FindModuleOnLine(conf, noncompatLine, "compat"); if(compatModIndex == -1) { result = CannotConfigure; goto done_configuring; } result = NotConfigured; LW_CLEANUP_CTERR(exc, InsertModule(conf, &distro, noncompatLine, -1, "files")); LW_CLEANUP_CTERR(exc, InsertModule(conf, &distro, noncompatLine, -1, "nis")); LW_CLEANUP_CTERR(exc, RemoveModule(conf, noncompatLine, compatModIndex)); LW_CLEANUP_CTERR(exc, RemoveLine(conf, compatLine)); } compatLine = FindEntry(conf, 0, "group_compat"); if(compatLine != -1) { const NsswitchEntry *lineEntry = GetEntry(conf, compatLine); groupNeedUpdate = TRUE; if(lineEntry->modules.size != 1) { result = CannotConfigure; goto done_configuring; } if(FindModuleOnLine(conf, compatLine, "nis") == -1) { result = CannotConfigure; goto done_configuring; } noncompatLine = FindEntry(conf, 0, "group"); if(noncompatLine == -1) { result = CannotConfigure; goto done_configuring; } lineEntry = GetEntry(conf, noncompatLine); if(lineEntry->modules.size != 1) { result = CannotConfigure; goto done_configuring; } compatModIndex = FindModuleOnLine(conf, noncompatLine, "compat"); if(compatModIndex == -1) { result = CannotConfigure; goto done_configuring; } result = NotConfigured; LW_CLEANUP_CTERR(exc, InsertModule(conf, &distro, noncompatLine, -1, "files")); LW_CLEANUP_CTERR(exc, InsertModule(conf, &distro, noncompatLine, -1, "nis")); LW_CLEANUP_CTERR(exc, RemoveModule(conf, noncompatLine, compatModIndex)); LW_CLEANUP_CTERR(exc, RemoveLine(conf, compatLine)); } done_configuring: if(result == CannotConfigure && description != NULL) { LW_CLEANUP_CTERR(exc, CTStrdup("Remove the passwd_compat and/or group_compat lines and use passwd and group instead. This cannot be done automatically because your system has a non-default nsswitch configuration.\n", description)); } else if((passwdNeedUpdate || groupNeedUpdate) && description != NULL) { LW_CLEANUP_CTERR(exc, CTStrdup("Remove the passwd_compat and/or group_compat lines and use passwd and group instead.\n", description)); result = NotConfigured; } else if(description != NULL) { LW_CLEANUP_CTERR(exc, CTStrdup("Fully Configured.", description)); } cleanup: DJFreeDistroInfo(&distro); return result; } CENTERROR UpdateNsswitchConf(NsswitchConf *conf, BOOLEAN enable) { CENTERROR ceError = CENTERROR_SUCCESS; DistroInfo distro; int line; int lwiIndex; BOOLEAN usingLsass; PCSTR preferredModule; PCSTR oldModule; GCE(ceError = DJGetDistroInfo(NULL, &distro)); GCE(ceError = UsingLsass(&usingLsass)); if(usingLsass) { preferredModule = "lsass"; oldModule = "lwidentity"; } else { preferredModule = "lwidentity"; oldModule = "lsass"; } line = FindEntry(conf, 0, "passwd"); if(enable && line == -1) { DJ_LOG_INFO("Adding passwd line"); GCE(ceError = AddEntry(conf, &distro, &line, "passwd")); GCE(ceError = InsertModule(conf, &distro, line, -1, "files")); } lwiIndex = FindModuleOnLine(conf, line, preferredModule); if(enable && lwiIndex == -1) { GCE(ceError = InsertModule(conf, &distro, line, -1, preferredModule)); } if(!enable && lwiIndex != -1) { GCE(ceError = RemoveModule(conf, line, lwiIndex)); } lwiIndex = FindModuleOnLine(conf, line, oldModule); if(lwiIndex != -1) { GCE(ceError = RemoveModule(conf, line, lwiIndex)); } // If lwidentity was the only entry // and we removed that now, don't write // an empty entry into the file if(!enable && line != -1 && GetEntry(conf, line)->modules.size == 0) { GCE(ceError = InsertModule(conf, &distro, line, -1, "files")); } line = FindEntry(conf, 0, "group"); if(line == -1) { line = FindEntry(conf, 0, "groups"); } if(enable && line == -1) { /* The nsswitch file doesn't have an existing groups line. We have to * guess based on platform whether it uses 'group' or 'groups'. */ const char *groupName = "group"; if(distro.os == OS_AIX || distro.os == OS_DARWIN) { groupName = "groups"; } DJ_LOG_INFO("Adding %s line", groupName); GCE(ceError = AddEntry(conf, &distro, &line, groupName)); GCE(ceError = InsertModule(conf, &distro, line, -1, "files")); } lwiIndex = FindModuleOnLine(conf, line, preferredModule); if(enable && lwiIndex == -1) { GCE(ceError = InsertModule(conf, &distro, line, -1, preferredModule)); } if(!enable && lwiIndex != -1) { GCE(ceError = RemoveModule(conf, line, lwiIndex)); } lwiIndex = FindModuleOnLine(conf, line, oldModule); if(lwiIndex != -1) { GCE(ceError = RemoveModule(conf, line, lwiIndex)); } // If lwidentity was the only entry // and we removed that now, don't write // an empty entry into the file if(!enable && line != -1 && GetEntry(conf, line)->modules.size == 0) { GCE(ceError = InsertModule(conf, &distro, line, -1, "files")); } cleanup: DJFreeDistroInfo(&distro); return ceError; } CENTERROR DJConfigureNameServiceSwitch(const char *testPrefix, BOOLEAN enable) { CENTERROR ceError = CENTERROR_SUCCESS; NsswitchConf conf; if(testPrefix == NULL) testPrefix = ""; ceError = ReadNsswitchConf(&conf, testPrefix, TRUE); if(ceError == CENTERROR_INVALID_FILENAME) { ceError = CENTERROR_SUCCESS; DJ_LOG_WARNING("Warning: Could not find nsswitch file"); goto cleanup; } GCE(ceError); ceError = UpdateNsswitchConf(&conf, enable); if(conf.modified) WriteNsswitchConfiguration(testPrefix, &conf); else DJ_LOG_INFO("nsswitch not modified"); cleanup: FreeNsswitchConfContents(&conf); return ceError; } const char *GetNameOfHostsByFile(const NsswitchConf *conf, const DistroInfo *distro) { int line = FindEntry(conf, 0, "hosts"); if (FindModuleOnLine(conf, line, "local") != -1) return "local"; if (FindModuleOnLine(conf, line, "files") != -1) return "files"; if(distro->os == OS_AIX) return "local"; else return "files"; } const char *GetNameOfHostsByDns(const NsswitchConf *conf, const DistroInfo *distro) { int line = FindEntry(conf, 0, "hosts"); if (FindModuleOnLine(conf, line, "dns") != -1) return "dns"; if (FindModuleOnLine(conf, line, "bind") != -1) return "bind"; if(distro->os == OS_AIX) return "bind"; else return "dns"; } //Does the platform-specific equivalent of this in nsswitch.conf: // hosts: files dns CENTERROR DJConfigureHostsEntry(const char *testPrefix) { CENTERROR ceError = CENTERROR_SUCCESS; NsswitchConf conf; DistroInfo distro; int line; const char *hostsByFile; const char *hostsByDns; int moduleIndex; if(testPrefix == NULL) testPrefix = ""; memset(&distro, 0, sizeof(distro)); memset(&conf, 0, sizeof(conf)); GCE(ceError = DJGetDistroInfo(testPrefix, &distro)); ceError = ReadNsswitchConf(&conf, testPrefix, TRUE); GCE(ceError); hostsByFile = GetNameOfHostsByFile(&conf, &distro); hostsByDns = GetNameOfHostsByDns(&conf, &distro); line = FindEntry(&conf, 0, "hosts"); if(line == -1) { DJ_LOG_INFO("Adding hosts line"); GCE(ceError = AddEntry(&conf, &distro, &line, "hosts")); GCE(ceError = InsertModule(&conf, &distro, line, 0, hostsByDns)); GCE(ceError = InsertModule(&conf, &distro, line, 0, hostsByFile)); } moduleIndex = FindModuleOnLine(&conf, line, hostsByFile); if(moduleIndex > 0) { /* The local module exists on the line, but it is not the first * entry. */ GCE(ceError = RemoveModule(&conf, line, moduleIndex)); } if(moduleIndex != 0) { GCE(ceError = InsertModule(&conf, &distro, line, 0, hostsByFile)); } if(conf.modified) WriteNsswitchConfiguration(testPrefix, &conf); else DJ_LOG_INFO("nsswitch not modified"); cleanup: FreeNsswitchConfContents(&conf); DJFreeDistroInfo(&distro); return ceError; } CENTERROR ConfigureNameServiceSwitch() { CENTERROR ceError = CENTERROR_SUCCESS; DistroInfo distro; GCE(ceError = DJGetDistroInfo(NULL, &distro)); GCE(ceError = DJConfigureNameServiceSwitch(NULL, TRUE)); /* By default, AIX will check a bind server before it will check its * hosts file after checking DNS. This means that our FQDN setting code * won't necessarily take effect. So instead we rewrite the line to put * local in front. * * TODO: this code should be moved to where the FQDN is set so that it * is not run when the --nohosts option is given. */ if(distro.os == OS_AIX) { GCE(ceError = DJConfigureHostsEntry(NULL)); } cleanup: DJFreeDistroInfo(&distro); return ceError; } #define APPARMOR_NSSWITCH "/etc/apparmor.d/abstractions/nameservice" static CENTERROR HasApparmor(BOOLEAN *hasApparmor) { return CTCheckFileOrLinkExists(APPARMOR_NSSWITCH, hasApparmor); } static CENTERROR IsApparmorConfigured(BOOLEAN *configured) { CENTERROR ceError = CENTERROR_SUCCESS; BOOLEAN hasApparmor; *configured = FALSE; GCE(ceError = HasApparmor(&hasApparmor)); if(hasApparmor) { GCE(ceError = CTCheckFileHoldsPattern(APPARMOR_NSSWITCH, "centeris", configured)); if(!*configured) { GCE(ceError = CTCheckFileHoldsPattern(APPARMOR_NSSWITCH, "likewise", configured)); } } else { *configured = TRUE; } cleanup: return ceError; } static void ConfigureApparmor(BOOLEAN enable, LWException **exc) { CENTERROR ceError = CENTERROR_SUCCESS; BOOLEAN hasApparmor; BOOLEAN configured; BOOLEAN usingMr; FILE *file = NULL; PCSTR addString; PSTR restartPath = NULL; PSTR restartCommand = NULL; char *tempName = NULL; char *finalName = NULL; LW_CLEANUP_CTERR(exc, IsApparmorConfigured(&configured)); if(configured == enable) goto cleanup; LW_CLEANUP_CTERR(exc, CTCheckFileOrLinkExists(APPARMOR_NSSWITCH, &hasApparmor)); if(!hasApparmor) goto cleanup; GCE(ceError = CTGetFileTempPath( APPARMOR_NSSWITCH, &finalName, &tempName)); LW_CLEANUP_CTERR(exc, CTCheckFileHoldsPattern(finalName, "mr,", &usingMr)); if(usingMr) addString = PREFIXDIR "/lib/*.so* mr,\n" PREFIXDIR "/lib64/*.so* mr,\n" "/tmp/.lwidentity/pipe rw,\n" LOCALSTATEDIR "/lib/likewise/.lsassd rw,\n" LOCALSTATEDIR "/tmp/.lsaclient_* rw,\n"; else addString = PREFIXDIR "/lib/*.so* r,\n" PREFIXDIR "/lib64/*.so* r,\n" "/tmp/.lwidentity/pipe rw,\n" LOCALSTATEDIR "/lib/likewise/.lsassd rw,\n" LOCALSTATEDIR "/tmp/.lsaclient_* rw,\n"; if(enable) { LW_CLEANUP_CTERR(exc, CTCopyFileWithOriginalPerms(finalName, tempName)); LW_CLEANUP_CTERR(exc, CTOpenFile(tempName, "a", &file)); LW_CLEANUP_CTERR(exc, CTFilePrintf(file, "# likewise\n%s# end likewise\n", addString)); CTSafeCloseFile(&file); LW_CLEANUP_CTERR(exc, CTSafeReplaceFile(finalName, tempName)); } else { LW_CLEANUP_CTERR(exc, CTRunSedOnFile(finalName, finalName, FALSE, "/^[ \t]*#[ \t]*likewise[ \t]*$/,/^[ \t]*#[ \t]*end likewise[ \t]*$/d")); LW_CLEANUP_CTERR(exc, CTRunSedOnFile(finalName, finalName, FALSE, "/^[ \t]*#[ \t]*centeris[ \t]*$/,/^[ \t]*#[ \t]*end centeris[ \t]*$/d")); } ceError = CTFindFileInPath("rcapparmor", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", &restartPath); if(ceError == CENTERROR_FILE_NOT_FOUND) { ceError = CTFindFileInPath("apparmor", "/etc/init.d/apparmor", &restartPath); } if(ceError == CENTERROR_FILE_NOT_FOUND) { ceError = CENTERROR_SUCCESS; } else if(CENTERROR_IS_OK(ceError)) { LW_CLEANUP_CTERR(exc, CTAllocateStringPrintf(&restartCommand, "%s restart", restartPath)); LW_TRY(exc, CTCaptureOutputToExc(restartCommand, &LW_EXC)); } LW_CLEANUP_CTERR(exc, ceError); cleanup: if(file != NULL) { CTCloseFile(file); CTRemoveFile(tempName); } CT_SAFE_FREE_STRING(restartPath); CT_SAFE_FREE_STRING(restartCommand); CT_SAFE_FREE_STRING(tempName); CT_SAFE_FREE_STRING(finalName); } static CENTERROR UnsuportedSeLinuxEnabled(BOOLEAN *hasBadSeLinux) { BOOLEAN hasSeLinux; CENTERROR ceError = CENTERROR_SUCCESS; PSTR output = NULL; DistroInfo distro; *hasBadSeLinux = FALSE; memset(&distro, 0, sizeof(distro)); GCE(ceError = CTCheckFileOrLinkExists("/usr/sbin/selinuxenabled", &hasSeLinux)); if(!hasSeLinux) goto cleanup; GCE(ceError = CTCheckFileOrLinkExists("/usr/sbin/getenforce", &hasSeLinux)); if(!hasSeLinux) goto cleanup; ceError = CTRunCommand("/usr/sbin/selinuxenabled >/dev/null 2>&1"); if(ceError == CENTERROR_COMMAND_FAILED) { //selinux is not enabled ceError = CENTERROR_SUCCESS; goto cleanup; } GCE(ceError); GCE(ceError = CTCaptureOutputWithStderr("/usr/sbin/getenforce", TRUE, &output)); CTStripWhitespace(output); if(!strcmp(output, "Permissive")) { goto cleanup; } DJ_LOG_INFO("Selinux found to be present, enabled, and enforcing."); GCE(ceError = DJGetDistroInfo("", &distro)); switch(distro.distro) { case DISTRO_CENTOS: case DISTRO_RHEL: if(distro.version[0] < '5') { DJ_LOG_INFO("Safe version of RHEL"); goto cleanup; } break; case DISTRO_FEDORA: if(distro.version[0] < '6') { DJ_LOG_INFO("Safe version of Fedora"); goto cleanup; } break; default: goto cleanup; } *hasBadSeLinux = TRUE; cleanup: if(!CENTERROR_IS_OK(ceError)) *hasBadSeLinux = TRUE; CT_SAFE_FREE_STRING(output); DJFreeDistroInfo(&distro); return ceError; } static QueryResult QueryNsswitch(const JoinProcessOptions *options, LWException **exc) { QueryResult result = FullyConfigured; BOOLEAN configured; BOOLEAN exists; BOOLEAN hasBadSeLinux; NsswitchConf conf; CENTERROR ceError = CENTERROR_SUCCESS; memset(&conf, 0, sizeof(conf)); if (options->joiningDomain) { ceError = ReadNsswitchConf(&conf, NULL, FALSE); if(ceError == CENTERROR_INVALID_FILENAME) { ceError = CENTERROR_SUCCESS; DJ_LOG_WARNING("Warning: Could not find nsswitch file"); goto cleanup; } LW_CLEANUP_CTERR(exc, ceError); LW_TRY(exc, result = RemoveCompat(&conf, NULL, &LW_EXC)); if(result == CannotConfigure || result == NotConfigured) { goto cleanup; } LW_CLEANUP_CTERR(exc, UpdateNsswitchConf(&conf, TRUE)); if(conf.modified) { LW_CLEANUP_CTERR(exc, UnsuportedSeLinuxEnabled(&hasBadSeLinux)); if(hasBadSeLinux) result = CannotConfigure; else result = NotConfigured; goto cleanup; } LW_CLEANUP_CTERR(exc, DJHasMethodsCfg(&exists)); if(exists) { LW_CLEANUP_CTERR(exc, DJIsMethodsCfgConfigured(&configured)); if(!configured) { result = NotConfigured; goto cleanup; } } LW_CLEANUP_CTERR(exc, IsApparmorConfigured(&configured)); if(!configured) { result = NotConfigured; goto cleanup; } } cleanup: FreeNsswitchConfContents(&conf); return result; } static void RestartDtloginIfRunning(JoinProcessOptions *options, LWException **exc) { BOOLEAN doRestart; BOOLEAN inX; LWException *inner = NULL; DistroInfo distro; memset(&distro, 0, sizeof(distro)); DJGetDaemonStatus("dtlogin", &doRestart, &inner); if(!LW_IS_OK(inner) && inner->code == CENTERROR_DOMAINJOIN_MISSING_DAEMON) { LW_HANDLE(&inner); goto cleanup; } LW_CLEANUP(exc, inner); if(doRestart) { /* Dtlogin will only be restarted if no one is logged in. */ LW_CLEANUP_CTERR(exc, CTIsUserInX(&inX)); if(inX) { doRestart = FALSE; /* If we're disabling domain logins, it isn't critical that * dtlogin is restarted. Without lwiauthd running, domain * users won't be able to log in anyway. */ if(options->joiningDomain) { LW_CLEANUP_CTERR(exc, DJGetDistroInfo(NULL, &distro)); if(distro.os == OS_SUNOS) { options->warningCallback(options, "Unable to restart dtlogin", "The dtlogin process needs to be restarted for domain users to interactively login graphically, but it cannot be restarted at this time because a user is currently logged in. After the user exits, please run these commands as root, outside of an Xwindows session:\n\n" "/etc/init.d/dtlogin stop\n" "/etc/init.d/dtlogin start"); } else if(distro.os == OS_HPUX) { options->warningCallback(options, "Unable to restart dtlogin", "The dtlogin process needs to be restarted for domain users to interactively login graphically, but it cannot be restarted at this time because a user is currently logged in. After the user exits, please run these commands as root, outside of an Xwindows session:\n\n" "/sbin/init.d/dtlogin.rc stop\n" "/sbin/init.d/dtlogin.rc start"); } else if(distro.os == OS_AIX) { options->warningCallback(options, "Unable to restart dtlogin", "The dtlogin process needs to be restarted for domain users to interactively login graphically, but it cannot be restarted at this time because a user is currently logged in. After the user exits, please run these commands as root, outside of an Xwindows session:\n\n" "kill `cat /var/dt/Xpid`\n" "/etc/rc.dt"); } else { options->warningCallback(options, "Unable to restart dtlogin", "The dtlogin process needs to be restarted for domain users to interactively login graphically, but it cannot be restarted at this time because a user is currently logged in. After the user exits, please restart dtlogin."); } } } } if(doRestart) { LW_TRY(exc, DJStartStopDaemon("dtlogin", FALSE, &LW_EXC)); LW_TRY(exc, DJStartStopDaemon("dtlogin", TRUE, &LW_EXC)); } cleanup: LW_HANDLE(&inner); DJFreeDistroInfo(&distro); } static void DoNsswitch(JoinProcessOptions *options, LWException **exc) { LWException *restartException = NULL; NsswitchConf conf; CENTERROR ceError = CENTERROR_SUCCESS; memset(&conf, 0, sizeof(conf)); LW_TRY(exc, ConfigureApparmor(options->joiningDomain, &LW_EXC)); if(options->joiningDomain) LW_CLEANUP_CTERR(exc, DJFixMethodsConfigFile()); else LW_CLEANUP_CTERR(exc, DJUnconfigMethodsConfigFile()); ceError = ReadNsswitchConf(&conf, "", TRUE); if(ceError == CENTERROR_INVALID_FILENAME) { ceError = CENTERROR_SUCCESS; options->warningCallback(options, "Could not find file", "Could not find nsswitch file"); goto cleanup; } LW_CLEANUP_CTERR(exc, ceError); if(options->joiningDomain) { LW_TRY(exc, RemoveCompat(&conf, NULL, &LW_EXC)); } LW_CLEANUP_CTERR(exc, UpdateNsswitchConf(&conf, options->joiningDomain)); if(conf.modified) LW_CLEANUP_CTERR(exc, WriteNsswitchConfiguration("", &conf)); else DJ_LOG_INFO("nsswitch not modified"); CTCaptureOutputToExc( SCRIPTDIR "/ConfigureLogin nsswitch_restart", &restartException); if(restartException != NULL && restartException->code == CENTERROR_COMMAND_FAILED) { options->warningCallback(options, "Some services require manual restart", restartException->longMsg); LW_HANDLE(&restartException); } LW_CLEANUP(exc, restartException); LW_TRY(exc, RestartDtloginIfRunning(options, &LW_EXC)); options->warningCallback(options, "System restart required", "Your system has been configured to authenticate to " "Active Directory for the first time. It is recommended " "that you restart your system to ensure that all applications " "recognize the new settings."); cleanup: FreeNsswitchConfContents(&conf); } static PSTR GetNsswitchDescription(const JoinProcessOptions *options, LWException **exc) { PSTR ret = NULL; PCSTR configureSteps; BOOLEAN hasBadSeLinux; QueryResult compatResult = FullyConfigured; PSTR compatDescription = NULL; NsswitchConf conf; CENTERROR ceError = CENTERROR_SUCCESS; memset(&conf, 0, sizeof(conf)); LW_CLEANUP_CTERR(exc, UnsuportedSeLinuxEnabled(&hasBadSeLinux)); if(hasBadSeLinux) { LW_CLEANUP_CTERR(exc, CTAllocateStringPrintf(&ret, "Your machine is using an unsupported SeLinux policy. This must be disabled before nsswitch can be modified to allow active directory users. Please run '/usr/sbin/setenforce Permissive' and then re-run this program.")); goto cleanup; } ceError = ReadNsswitchConf(&conf, "", TRUE); if(ceError == CENTERROR_INVALID_FILENAME) { ceError = CENTERROR_SUCCESS; options->warningCallback(options, "Could not find file", "Could not find nsswitch file"); goto cleanup; } LW_CLEANUP_CTERR(exc, ceError); if(options->joiningDomain) { LW_TRY(exc, compatResult = RemoveCompat(&conf, &compatDescription, &LW_EXC)); } if(compatResult == FullyConfigured) { CT_SAFE_FREE_STRING(compatDescription); LW_CLEANUP_CTERR(exc, CTStrdup("", &compatDescription)); } conf.modified = FALSE; LW_CLEANUP_CTERR(exc, UpdateNsswitchConf(&conf, options->joiningDomain)); if(options->joiningDomain && conf.modified) configureSteps = "The following steps are required and can be performed automatically:\n" "\t* Edit nsswitch apparmor profile to allow libraries in the " PREFIXDIR "/lib and " PREFIXDIR "/lib64 directories\n" "\t* List lwidentity module in /usr/lib/security/methods.cfg (AIX only)\n" "\t* Add lwidentity to passwd and group/groups line /etc/nsswitch.conf or /etc/netsvc.conf\n"; else if(conf.modified) configureSteps = "The following steps are required and can be performed automatically:\n" "\t* Remove lwidentity module from /usr/lib/security/methods.cfg (AIX only)\n" "\t* Remove lwidentity from passwd and group/groups line /etc/nsswitch.conf or /etc/netsvc.conf\n" "The following step is optional:\n" "\t* Remove apparmor exception for likewise nsswitch libraries\n"; else configureSteps = ""; if(strlen(compatDescription) || strlen(configureSteps)) { LW_CLEANUP_CTERR(exc, CTAllocateStringPrintf(&ret, "%s%sIf any changes are performed, then the following services must be restarted:\n" "\t* GDM\n" "\t* XDM\n" "\t* Cron\n" "\t* Dbus\n" "\t* Nscd", compatDescription, configureSteps)); } else LW_CLEANUP_CTERR(exc, CTStrdup("Fully Configured", &ret)); cleanup: CT_SAFE_FREE_STRING(compatDescription); FreeNsswitchConfContents(&conf); return ret; } const JoinModule DJNsswitchModule = { TRUE, "nsswitch", "enable/disable Likewise nsswitch module", QueryNsswitch, DoNsswitch, GetNsswitchDescription };