#!/usr/bin/python # Unix SMB/CIFS implementation. # Copyright (C) Jelmer Vernooij 2007-2008 # # Based on the original in EJS: # Copyright (C) Andrew Tridgell 2005 # # 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 3 of the License, or # (at your option) any later version. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # """Convenience functions for using the SAM.""" import samba import glue import ldb from samba.idmap import IDmapDB import pwd import time import base64 __docformat__ = "restructuredText" class SamDB(samba.Ldb): """The SAM database.""" def __init__(self, url=None, session_info=None, credentials=None, modules_dir=None, lp=None, options=None): """Open the Sam Database. :param url: URL of the database. """ self.lp = lp super(SamDB, self).__init__(session_info=session_info, credentials=credentials, modules_dir=modules_dir, lp=lp, options=options) glue.dsdb_set_global_schema(self) if url: self.connect(url) else: self.connect(lp.get("sam database")) def connect(self, url): super(SamDB, self).connect(self.lp.private_path(url)) def add_foreign(self, domaindn, sid, desc): """Add a foreign security principle.""" add = """ dn: CN=%s,CN=ForeignSecurityPrincipals,%s objectClass: top objectClass: foreignSecurityPrincipal description: %s """ % (sid, domaindn, desc) # deliberately ignore errors from this, as the records may # already exist for msg in self.parse_ldif(add): self.add(msg[1]) def add_stock_foreign_sids(self): domaindn = self.domain_dn() self.add_foreign(domaindn, "S-1-5-7", "Anonymous") self.add_foreign(domaindn, "S-1-1-0", "World") self.add_foreign(domaindn, "S-1-5-2", "Network") self.add_foreign(domaindn, "S-1-5-18", "System") self.add_foreign(domaindn, "S-1-5-11", "Authenticated Users") def enable_account(self, user_dn): """Enable an account. :param user_dn: Dn of the account to enable. """ res = self.search(user_dn, ldb.SCOPE_BASE, None, ["userAccountControl"]) assert len(res) == 1 userAccountControl = res[0]["userAccountControl"][0] userAccountControl = int(userAccountControl) if (userAccountControl & 0x2): userAccountControl = userAccountControl & ~0x2 # remove disabled bit if (userAccountControl & 0x20): userAccountControl = userAccountControl & ~0x20 # remove 'no password required' bit mod = """ dn: %s changetype: modify replace: userAccountControl userAccountControl: %u """ % (user_dn, userAccountControl) self.modify_ldif(mod) def domain_dn(self): # find the DNs for the domain and the domain users group res = self.search("", scope=ldb.SCOPE_BASE, expression="(defaultNamingContext=*)", attrs=["defaultNamingContext"]) assert(len(res) == 1 and res[0]["defaultNamingContext"] is not None) return res[0]["defaultNamingContext"][0] def newuser(self, username, unixname, password): """add a new user record. :param username: Name of the new user. :param unixname: Name of the unix user to map to. :param password: Password for the new user """ # connect to the sam self.transaction_start() try: domain_dn = self.domain_dn() assert(domain_dn is not None) user_dn = "CN=%s,CN=Users,%s" % (username, domain_dn) # # the new user record. note the reliance on the samdb module to # fill in a sid, guid etc # # now the real work self.add({"dn": user_dn, "sAMAccountName": username, "userPassword": password, "objectClass": "user"}) res = self.search(user_dn, scope=ldb.SCOPE_BASE, expression="objectclass=*", attrs=["objectSid"]) assert len(res) == 1 user_sid = self.schema_format_value("objectSid", res[0]["objectSid"][0]) try: idmap = IDmapDB(lp=self.lp) user = pwd.getpwnam(unixname) # setup ID mapping for this UID idmap.setup_name_mapping(user_sid, idmap.TYPE_UID, user[2]) except KeyError: pass # modify the userAccountControl to remove the disabled bit self.enable_account(user_dn) except: self.transaction_cancel() raise self.transaction_commit() def setpassword(self, filter, password): """Set a password on a user record :param filter: LDAP filter to find the user (eg samccountname=name) :param password: Password for the user """ # connect to the sam self.transaction_start() try: # find the DNs for the domain res = self.search("", scope=ldb.SCOPE_BASE, expression="(defaultNamingContext=*)", attrs=["defaultNamingContext"]) assert(len(res) == 1 and res[0]["defaultNamingContext"] is not None) domain_dn = res[0]["defaultNamingContext"][0] assert(domain_dn is not None) res = self.search(domain_dn, scope=ldb.SCOPE_SUBTREE, expression=filter, attrs=[]) assert(len(res) == 1) user_dn = res[0].dn setpw = """ dn: %s changetype: modify replace: userPassword userPassword:: %s """ % (user_dn, base64.b64encode(password)) self.modify_ldif(setpw) # modify the userAccountControl to remove the disabled bit self.enable_account(user_dn) except: self.transaction_cancel() raise self.transaction_commit() def set_domain_sid(self, sid): """Change the domain SID used by this SamDB. :param sid: The new domain sid to use. """ glue.samdb_set_domain_sid(self, sid) def attach_schema_from_ldif(self, pf, df): glue.dsdb_attach_schema_from_ldif(self, pf, df) def convert_schema_to_openldap(self, target, mapping): return glue.dsdb_convert_schema_to_openldap(self, target, mapping) def set_invocation_id(self, invocation_id): """Set the invocation id for this SamDB handle. :param invocation_id: GUID of the invocation id. """ glue.dsdb_set_ntds_invocation_id(self, invocation_id) def setexpiry(self, user, expiry_seconds, noexpiry): """Set the password expiry for a user :param expiry_seconds: expiry time from now in seconds :param noexpiry: if set, then don't expire password """ self.transaction_start() try: res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE, expression=("(samAccountName=%s)" % user), attrs=["userAccountControl", "accountExpires"]) assert len(res) == 1 userAccountControl = int(res[0]["userAccountControl"][0]) accountExpires = int(res[0]["accountExpires"][0]) if noexpiry: userAccountControl = userAccountControl | 0x10000 accountExpires = 0 else: userAccountControl = userAccountControl & ~0x10000 accountExpires = glue.unix2nttime(expiry_seconds + int(time.time())) mod = """ dn: %s changetype: modify replace: userAccountControl userAccountControl: %u replace: accountExpires accountExpires: %u """ % (res[0].dn, userAccountControl, accountExpires) # now change the database self.modify_ldif(mod) except: self.transaction_cancel() raise self.transaction_commit(); /a> 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
/* pmrfc3164.c
 * This is a parser module for RFC3164(legacy syslog)-formatted messages.
 *
 * NOTE: read comments in module-template.h to understand how this file
 *       works!
 *
 * File begun on 2009-11-04 by RGerhards
 *
 * Copyright 2007, 2009 Rainer Gerhards and Adiscon GmbH.
 *
 * This file is part of rsyslog.
 *
 * Rsyslog 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 3 of the License, or
 * (at your option) any later version.
 *
 * Rsyslog 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Rsyslog.  If not, see <http://www.gnu.org/licenses/>.
 *
 * A copy of the GPL can be found in the file "COPYING" in this distribution.
 */
#include "config.h"
#include "rsyslog.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <ctype.h>
#include "syslogd.h"
#include "conf.h"
#include "syslogd-types.h"
#include "template.h"
#include "msg.h"
#include "module-template.h"
#include "glbl.h"
#include "errmsg.h"
#include "parser.h"
#include "datetime.h"
#include "unicode-helper.h"

MODULE_TYPE_PARSER
MODULE_TYPE_NOKEEP
PARSER_NAME("rsyslog.rfc3164")

/* internal structures
 */
DEF_PMOD_STATIC_DATA
DEFobjCurrIf(errmsg)
DEFobjCurrIf(glbl)
DEFobjCurrIf(parser)
DEFobjCurrIf(datetime)


/* static data */
static int bParseHOSTNAMEandTAG;	/* cache for the equally-named global param - performance enhancement */


BEGINisCompatibleWithFeature
CODESTARTisCompatibleWithFeature
	if(eFeat == sFEATUREAutomaticSanitazion)
		iRet = RS_RET_OK;
	if(eFeat == sFEATUREAutomaticPRIParsing)
		iRet = RS_RET_OK;
ENDisCompatibleWithFeature


/* parse a legay-formatted syslog message.
 */
BEGINparse
	uchar *p2parse;
	int lenMsg;
	int bTAGCharDetected;
	int i;	/* general index for parsing */
	uchar bufParseTAG[CONF_TAG_MAXSIZE];
	uchar bufParseHOSTNAME[CONF_HOSTNAME_MAXSIZE];
CODESTARTparse
	dbgprintf("Message will now be parsed by the legacy syslog parser (one size fits all... ;)).\n");
	assert(pMsg != NULL);
	assert(pMsg->pszRawMsg != NULL);
	lenMsg = pMsg->iLenRawMsg - pMsg->offAfterPRI; /* note: offAfterPRI is already the number of PRI chars (do not add one!) */
	p2parse = pMsg->pszRawMsg + pMsg->offAfterPRI; /* point to start of text, after PRI */
	setProtocolVersion(pMsg, 0);

	/* Check to see if msg contains a timestamp. We start by assuming
	 * that the message timestamp is the time of reception (which we 
	 * generated ourselfs and then try to actually find one inside the
	 * message. There we go from high-to low precison and are done
	 * when we find a matching one. -- rgerhards, 2008-09-16
	 */
	if(datetime.ParseTIMESTAMP3339(&(pMsg->tTIMESTAMP), &p2parse, &lenMsg) == RS_RET_OK) {
		/* we are done - parse pointer is moved by ParseTIMESTAMP3339 */;
	} else if(datetime.ParseTIMESTAMP3164(&(pMsg->tTIMESTAMP), &p2parse, &lenMsg) == RS_RET_OK) {
		/* we are done - parse pointer is moved by ParseTIMESTAMP3164 */;
	} else if(*p2parse == ' ' && lenMsg > 1) { /* try to see if it is slighly malformed - HP procurve seems to do that sometimes */
		++p2parse;	/* move over space */
		--lenMsg;
		if(datetime.ParseTIMESTAMP3164(&(pMsg->tTIMESTAMP), &p2parse, &lenMsg) == RS_RET_OK) {
			/* indeed, we got it! */
			/* we are done - parse pointer is moved by ParseTIMESTAMP3164 */;
		} else {/* parse pointer needs to be restored, as we moved it off-by-one
			 * for this try.
			 */
			--p2parse;
			++lenMsg;
		}
	}

	if(pMsg->msgFlags & IGNDATE) {
		/* we need to ignore the msg data, so simply copy over reception date */
		memcpy(&pMsg->tTIMESTAMP, &pMsg->tRcvdAt, sizeof(struct syslogTime));
	}

	/* rgerhards, 2006-03-13: next, we parse the hostname and tag. But we 
	 * do this only when the user has not forbidden this. I now introduce some
	 * code that allows a user to configure rsyslogd to treat the rest of the
	 * message as MSG part completely. In this case, the hostname will be the
	 * machine that we received the message from and the tag will be empty. This
	 * is meant to be an interim solution, but for now it is in the code.
	 */
	if(bParseHOSTNAMEandTAG && !(pMsg->msgFlags & INTERNAL_MSG)) {
		/* parse HOSTNAME - but only if this is network-received!
		 * rger, 2005-11-14: we still have a problem with BSD messages. These messages
		 * do NOT include a host name. In most cases, this leads to the TAG to be treated
		 * as hostname and the first word of the message as the TAG. Clearly, this is not
		 * of advantage ;) I think I have now found a way to handle this situation: there
		 * are certain characters which are frequently used in TAG (e.g. ':'), which are
		 * *invalid* in host names. So while parsing the hostname, I check for these characters.
		 * If I find them, I set a simple flag but continue. After parsing, I check the flag.
		 * If it was set, then we most probably do not have a hostname but a TAG. Thus, I change
		 * the fields. I think this logic shall work with any type of syslog message.
		 * rgerhards, 2009-06-23: and I now have extended this logic to every character
		 * that is not a valid hostname.
		 */
		bTAGCharDetected = 0;
		if(lenMsg > 0 && pMsg->msgFlags & PARSE_HOSTNAME) {
			i = 0;
			while(i < lenMsg && (isalnum(p2parse[i]) || p2parse[i] == '.' || p2parse[i] == '.'
				|| p2parse[i] == '_' || p2parse[i] == '-') && i < (CONF_HOSTNAME_MAXSIZE - 1)) {
				bufParseHOSTNAME[i] = p2parse[i];
				++i;
			}

			if(i == lenMsg) {
				/* we have a message that is empty immediately after the hostname,
				* but the hostname thus is valid! -- rgerhards, 2010-02-22
				*/
				p2parse += i;
				lenMsg -= i;
				bufParseHOSTNAME[i] = '\0';
				MsgSetHOSTNAME(pMsg, bufParseHOSTNAME, i);
			} else if(i > 0 && p2parse[i] == ' ' && isalnum(p2parse[i-1])) {
				/* we got a hostname! */
				p2parse += i + 1; /* "eat" it (including SP delimiter) */
				lenMsg -= i + 1;
				bufParseHOSTNAME[i] = '\0';
				MsgSetHOSTNAME(pMsg, bufParseHOSTNAME, i);
			}
		}

		/* now parse TAG - that should be present in message from all sources.
		 * This code is somewhat not compliant with RFC 3164. As of 3164,
		 * the TAG field is ended by any non-alphanumeric character. In
		 * practice, however, the TAG often contains dashes and other things,
		 * which would end the TAG. So it is not desirable. As such, we only
		 * accept colon and SP to be terminators. Even there is a slight difference:
		 * a colon is PART of the TAG, while a SP is NOT part of the tag
		 * (it is CONTENT). Starting 2008-04-04, we have removed the 32 character