void IpVerify::PrintAuthTable(int dprintf_level) { struct in6_addr host; UserPerm_t * ptable; PermHashTable->startIterations(); while (PermHashTable->iterate(host, ptable)) { MyString userid; perm_mask_t mask; ptable->startIterations(); while( ptable->iterate(userid,mask) ) { // Call has_user() to get the full mask has_user(ptable, userid.Value(), mask); MyString auth_entry_str; AuthEntryToString(host,userid.Value(),mask, auth_entry_str); dprintf(dprintf_level,"%s\n", auth_entry_str.Value()); } } dprintf(dprintf_level,"Authorizations yet to be resolved:\n"); DCpermission perm; for ( perm=FIRST_PERM; perm < LAST_PERM; perm=NEXT_PERM(perm) ) { PermTypeEntry* pentry = PermTypeArray[perm]; ASSERT( pentry ); MyString allow_users,deny_users; if( pentry->allow_users ) { UserHashToString(pentry->allow_users,allow_users); } if( pentry->deny_users ) { UserHashToString(pentry->deny_users,deny_users); } if( allow_users.Length() ) { dprintf(dprintf_level,"allow %s: %s\n", PermString(perm), allow_users.Value()); } if( deny_users.Length() ) { dprintf(dprintf_level,"deny %s: %s\n", PermString(perm), deny_users.Value()); } } }
// FillHole - plug up a dynamically punched authorization hole // bool IpVerify::FillHole(DCpermission perm, MyString& id) { HolePunchTable_t* table = PunchedHoleArray[perm]; if (table == NULL) { return false; } int count; if (table->lookup(id, count) == -1) { return false; } if (table->remove(id) == -1) { EXCEPT("IpVerify::FillHole: table entry removal error"); } count--; if (count != 0) { if (table->insert(id, count) == -1) { EXCEPT("IpVerify::FillHole: " "table entry insertion error"); } } if (count == 0) { dprintf(D_SECURITY, "IpVerify::FillHole: " "removed %s-level opening for %s\n", PermString(perm), id.Value()); } else { dprintf(D_SECURITY, "IpVerify::FillHole: " "open count at level %s for %s now %d\n", PermString(perm), id.Value(), count); } DCpermissionHierarchy hierarchy( perm ); DCpermission const *implied_perms=hierarchy.getImpliedPerms(); for(; implied_perms[0] != LAST_PERM; implied_perms++ ) { if( perm != implied_perms[0] ) { FillHole(implied_perms[0],id); } } return true; }
void IpVerify::PermMaskToString(perm_mask_t mask, MyString &mask_str) { DCpermission perm; for(perm=FIRST_PERM; perm<LAST_PERM; perm=NEXT_PERM(perm)) { if( mask & allow_mask(perm) ) { mask_str.append_to_list(PermString(perm)); } if( mask & deny_mask(perm) ) { mask_str.append_to_list("DENY_"); mask_str += PermString(perm); } } }
// PunchHole - dynamically opens up a perm level to the // given user / IP. The hole can be removed with FillHole. // Additions persist across a reconfig. This is intended // for transient permissions (like to automatic permission // granted to a remote startd host when a shadow starts up.) // bool IpVerify::PunchHole(DCpermission perm, MyString& id) { int count = 0; if (PunchedHoleArray[perm] == NULL) { PunchedHoleArray[perm] = new HolePunchTable_t(compute_host_hash); ASSERT(PunchedHoleArray[perm] != NULL); } else { int c; if (PunchedHoleArray[perm]->lookup(id, c) != -1) { count = c; if (PunchedHoleArray[perm]->remove(id) == -1) { EXCEPT("IpVerify::PunchHole: " "table entry removal error"); } } } count++; if (PunchedHoleArray[perm]->insert(id, count) == -1) { EXCEPT("IpVerify::PunchHole: table entry insertion error"); } if (count == 1) { dprintf(D_SECURITY, "IpVerify::PunchHole: opened %s level to %s\n", PermString(perm), id.Value()); } else { dprintf(D_SECURITY, "IpVerify::PunchHole: " "open count at level %s for %s now %d\n", PermString(perm), id.Value(), count); } DCpermissionHierarchy hierarchy( perm ); DCpermission const *implied_perms=hierarchy.getImpliedPerms(); for(; implied_perms[0] != LAST_PERM; implied_perms++ ) { if( perm != implied_perms[0] ) { PunchHole(implied_perms[0],id); } } return true; }
DCpermission StringToDCpermission(char const *str) { DCpermission perm; for(perm = FIRST_PERM;perm!=LAST_PERM;perm=NEXT_PERM(perm)) { if(str && !strcasecmp(str,PermString(perm)) ) { return perm; } } return LAST_PERM; }
int IpVerify::Verify( DCpermission perm, const condor_sockaddr& addr, const char * user, MyString *allow_reason, MyString *deny_reason ) { perm_mask_t mask; in6_addr sin6_addr; const char *thehost; const char * who = user; MyString peer_description; // we build this up as we go along (DNS etc.) if( !did_init ) { Init(); } /* * Be Warned: careful about parameter "sin" being NULL. It could be, in * which case we should return FALSE (unless perm is ALLOW) * */ switch ( perm ) { case ALLOW: return USER_AUTH_SUCCESS; break; default: break; } sin6_addr = addr.to_ipv6_address(); mask = 0; // must initialize to zero because we logical-or bits into this if (who == NULL || *who == '\0') { who = TotallyWild; } if ( perm >= LAST_PERM || !PermTypeArray[perm] ) { EXCEPT("IpVerify::Verify: called with unknown permission %d\n",perm); } // see if a authorization hole has been dyamically punched (via // PunchHole) for this perm / user / IP // Note that the permission hierarchy is dealt with in // PunchHole(), by punching a hole for all implied levels. // Therefore, if there is a hole or an implied hole, we will // always find it here before we get into the subsequent code // which recursively calls Verify() to traverse the hierarchy. // This is important, because we do not want holes to find // there way into the authorization cache. // if ( PunchedHoleArray[perm] != NULL ) { HolePunchTable_t* hpt = PunchedHoleArray[perm]; MyString ip_str_buf = addr.to_ip_string(); const char* ip_str = ip_str_buf.Value(); MyString id_with_ip; MyString id; int count; if ( who != TotallyWild ) { id_with_ip.sprintf("%s/%s", who, ip_str); id = who; if ( hpt->lookup(id, count) != -1 ) { if( allow_reason ) { allow_reason->sprintf( "%s authorization has been made automatic for %s", PermString(perm), id.Value() ); } return USER_AUTH_SUCCESS; } if ( hpt->lookup(id_with_ip, count) != -1 ) { if( allow_reason ) { allow_reason->sprintf( "%s authorization has been made automatic for %s", PermString(perm), id_with_ip.Value() ); } return USER_AUTH_SUCCESS; } } id = ip_str; if ( hpt->lookup(id, count) != -1 ) { if( allow_reason ) { allow_reason->sprintf( "%s authorization has been made automatic for %s", PermString(perm), id.Value() ); } return USER_AUTH_SUCCESS; } } if ( PermTypeArray[perm]->behavior == USERVERIFY_ALLOW ) { // allow if no HOSTALLOW_* or HOSTDENY_* restrictions // specified. if( allow_reason ) { allow_reason->sprintf( "%s authorization policy allows access by anyone", PermString(perm)); } return USER_AUTH_SUCCESS; } if ( PermTypeArray[perm]->behavior == USERVERIFY_DENY ) { // deny if( deny_reason ) { deny_reason->sprintf( "%s authorization policy denies all access", PermString(perm)); } return USER_AUTH_FAILURE; } if( LookupCachedVerifyResult(perm,sin6_addr,who,mask) ) { if( deny_reason && (mask&deny_mask(perm)) ) { deny_reason->sprintf( "cached result for %s; see first case for the full reason", PermString(perm)); } else if( allow_reason && (mask&allow_mask(perm)) ) { allow_reason->sprintf( "cached result for %s; see first case for the full reason", PermString(perm)); } } else { mask = 0; // if the deny bit is already set, skip further DENY analysis perm_mask_t const deny_resolved = deny_mask(perm); // if the allow or deny bit is already set, // skip further ALLOW analysis perm_mask_t const allow_resolved = allow_mask(perm)|deny_mask(perm); // check for matching subnets in ip/mask style char ipstr[INET6_ADDRSTRLEN] = { 0, }; addr.to_ip_string(ipstr, INET6_ADDRSTRLEN); peer_description = addr.to_ip_string(); if ( !(mask&deny_resolved) && lookup_user_ip_deny(perm,who,ipstr)) { mask |= deny_mask(perm); if( deny_reason ) { deny_reason->sprintf( "%s authorization policy denies IP address %s", PermString(perm), addr.to_ip_string().Value() ); } } if ( !(mask&allow_resolved) && lookup_user_ip_allow(perm,who,ipstr)) { mask |= allow_mask(perm); if( allow_reason ) { allow_reason->sprintf( "%s authorization policy allows IP address %s", PermString(perm), addr.to_ip_string().Value() ); } } std::vector<MyString> hostnames; // now scan through hostname strings if( !(mask&allow_resolved) || !(mask&deny_resolved) ) { hostnames = get_hostname_with_alias(addr); } for (unsigned int i = 0; i < hostnames.size(); ++i) { thehost = hostnames[i].Value(); peer_description.append_to_list(thehost); if ( !(mask&deny_resolved) && lookup_user_host_deny(perm,who,thehost) ) { mask |= deny_mask(perm); if( deny_reason ) { deny_reason->sprintf( "%s authorization policy denies hostname %s", PermString(perm), thehost ); } } if ( !(mask&allow_resolved) && lookup_user_host_allow(perm,who,thehost) ) { mask |= allow_mask(perm); if( allow_reason ) { allow_reason->sprintf( "%s authorization policy allows hostname %s", PermString(perm), thehost ); } } } // if we found something via our hostname or subnet mactching, we now have // a mask, and we should add it into our table so we need not // do a gethostbyaddr() next time. if we still do not have a mask // (perhaps because this host doesn't appear in any list), create one // and then add to the table. // But first, check our parent permission levels in the // authorization heirarchy. // DAEMON and ADMINISTRATOR imply WRITE. // WRITE, NEGOTIATOR, and CONFIG_PERM imply READ. bool determined_by_parent = false; if ( mask == 0 ) { if ( PermTypeArray[perm]->behavior == USERVERIFY_ONLY_DENIES ) { dprintf(D_SECURITY,"IPVERIFY: %s at %s not matched to deny list, so allowing.\n",who, addr.to_sinful().Value()); if( allow_reason ) { allow_reason->sprintf( "%s authorization policy does not deny, so allowing", PermString(perm)); } mask |= allow_mask(perm); } else { DCpermissionHierarchy hierarchy( perm ); DCpermission const *parent_perms = hierarchy.getPermsIAmDirectlyImpliedBy(); bool parent_allowed = false; for( ; *parent_perms != LAST_PERM; parent_perms++ ) { if( Verify( *parent_perms, addr, user, allow_reason, NULL ) == USER_AUTH_SUCCESS ) { determined_by_parent = true; parent_allowed = true; dprintf(D_SECURITY,"IPVERIFY: allowing %s at %s for %s because %s is allowed\n",who, addr.to_sinful().Value(),PermString(perm),PermString(*parent_perms)); if( allow_reason ) { MyString tmp = *allow_reason; allow_reason->sprintf( "%s is implied by %s; %s", PermString(perm), PermString(*parent_perms), tmp.Value()); } break; } } if( parent_allowed ) { mask |= allow_mask(perm); } else { mask |= deny_mask(perm); if( !determined_by_parent && deny_reason ) { // We don't just allow anyone, and this request // did not match any of the entries we do allow. // In case the reason we didn't match is // because of a typo or a DNS problem, record // all the hostnames we searched for. deny_reason->sprintf( "%s authorization policy contains no matching " "ALLOW entry for this request" "; identifiers used for this host: %s, hostname size = %lu, " "original ip address = %s", PermString(perm), peer_description.Value(), (unsigned long)hostnames.size(), ipstr); } } } } if( !determined_by_parent && (mask&allow_mask(perm)) ) { // In case we are allowing because of not matching a DENY // entry that the user expected us to match (e.g. because // of typo or DNS problem), record all the hostnames we // searched for. if( allow_reason && !peer_description.IsEmpty() ) { allow_reason->sprintf_cat( "; identifiers used for this remote host: %s", peer_description.Value()); } } // finally, add the mask we computed into the table with this IP addr add_hash_entry(sin6_addr, who, mask); } // end of if find_match is FALSE // decode the mask and return True or False to the user. if ( mask & deny_mask(perm) ) { return USER_AUTH_FAILURE; } if ( mask & allow_mask(perm) ) { return USER_AUTH_SUCCESS; } return USER_AUTH_FAILURE; }
int IpVerify::Init() { char *pAllow = NULL, *pDeny = NULL, *pOldAllow = NULL, *pOldDeny = NULL, *pNewAllow = NULL, *pNewDeny = NULL; DCpermission perm; const char* const ssysname = get_mySubSystem()->getName(); did_init = TRUE; // Make sure that perm_mask_t is big enough to hold all possible // results of allow_mask() and deny_mask(). ASSERT( sizeof(perm_mask_t)*8 - 2 > LAST_PERM ); // Clear the Permission Hash Table in case re-initializing if (PermHashTable) { // iterate through the table and delete the entries struct in6_addr key; UserPerm_t * value; PermHashTable->startIterations(); while (PermHashTable->iterate(key, value)) { delete value; } PermHashTable->clear(); } // and Clear the Permission Type Array for (perm=FIRST_PERM; perm<LAST_PERM; perm=NEXT_PERM(perm)) { if ( PermTypeArray[perm] ) { delete PermTypeArray[perm]; PermTypeArray[perm] = NULL; } } // This is the new stuff for ( perm=FIRST_PERM; perm < LAST_PERM; perm=NEXT_PERM(perm) ) { PermTypeEntry* pentry = new PermTypeEntry(); ASSERT( pentry ); PermTypeArray[perm] = pentry; MyString allow_param, deny_param; dprintf(D_SECURITY,"IPVERIFY: Subsystem %s\n",ssysname); dprintf(D_SECURITY,"IPVERIFY: Permission %s\n",PermString(perm)); if(strcmp(ssysname,"TOOL")==0 || strcmp(ssysname,"SUBMIT")==0){ // to avoid unneccesary DNS activity, the TOOL and SUBMIT // subsystems only load the CLIENT lists, since they have no // command port and don't need the other authorization lists. if(strcmp(PermString(perm),"CLIENT")==0){ pNewAllow = SecMan::getSecSetting("ALLOW_%s",perm,&allow_param, ssysname ); pOldAllow = SecMan::getSecSetting("HOSTALLOW_%s",perm,&allow_param, ssysname ); pNewDeny = SecMan::getSecSetting("DENY_%s",perm,&deny_param, ssysname ); pOldDeny = SecMan::getSecSetting("HOSTDENY_%s",perm,&deny_param, ssysname ); } } else { pNewAllow = SecMan::getSecSetting("ALLOW_%s",perm,&allow_param, ssysname ); pOldAllow = SecMan::getSecSetting("HOSTALLOW_%s",perm,&allow_param, ssysname ); pNewDeny = SecMan::getSecSetting("DENY_%s",perm,&deny_param, ssysname ); pOldDeny = SecMan::getSecSetting("HOSTDENY_%s",perm,&deny_param, ssysname ); } // concat the two pAllow = merge(pNewAllow, pOldAllow); // concat the two pDeny = merge(pNewDeny, pOldDeny); if( pAllow ) { dprintf ( D_SECURITY, "IPVERIFY: allow %s: %s (from config value %s)\n", PermString(perm),pAllow,allow_param.Value()); } if( pDeny ) { dprintf ( D_SECURITY, "IPVERIFY: deny %s: %s (from config value %s)\n", PermString(perm),pDeny,deny_param.Value()); } // Treat a "*", "*/*" for ALLOW_XXX as if it's just undefined, // because that's the optimized default, except for // CONFIG_PERM which has a different default (see below). if( perm != CONFIG_PERM ) { if(pAllow && (!strcmp(pAllow, "*") || !strcmp(pAllow, "*/*"))) { free( pAllow ); pAllow = NULL; } } if ( !pAllow && !pDeny ) { if (perm == CONFIG_PERM) { // deny all CONFIG requests pentry->behavior = USERVERIFY_DENY; // by default dprintf( D_SECURITY, "ipverify: %s optimized to deny everyone\n", PermString(perm) ); } else { pentry->behavior = USERVERIFY_ALLOW; if( perm != ALLOW ) { dprintf( D_SECURITY, "ipverify: %s optimized to allow anyone\n", PermString(perm) ); } } } else { if ( pDeny && !pAllow && perm != CONFIG_PERM ) { pentry->behavior = USERVERIFY_ONLY_DENIES; } else { pentry->behavior = USERVERIFY_USE_TABLE; } if ( pAllow ) { fill_table( pentry, pAllow, true ); free(pAllow); pAllow = NULL; } if ( pDeny ) { fill_table( pentry, pDeny, false ); free(pDeny); pDeny = NULL; } } if (pAllow) { free(pAllow); pAllow = NULL; } if (pDeny) { free(pDeny); pDeny = NULL; } if (pOldAllow) { free(pOldAllow); pOldAllow = NULL; } if (pOldDeny) { free(pOldDeny); pOldDeny = NULL; } if (pNewAllow) { free(pNewAllow); pNewAllow = NULL; } if (pNewDeny) { free(pNewDeny); pNewDeny = NULL; } } dprintf(D_FULLDEBUG|D_SECURITY,"Initialized the following authorization table:\n"); if(PermHashTable) PrintAuthTable(D_FULLDEBUG|D_SECURITY); return TRUE; }