mirror of
https://github.com/samba-team/samba.git
synced 2025-06-24 15:17:06 +03:00
683 lines
17 KiB
C
683 lines
17 KiB
C
/* -*- c-file-style: "python"; indent-tabs-mode: nil; -*-
|
|
|
|
Python wrapper for Samba tdb pack/unpack functions
|
|
Copyright (C) Martin Pool 2002
|
|
|
|
|
|
NOTE PYTHON STYLE GUIDE
|
|
http://www.python.org/peps/pep-0007.html
|
|
|
|
|
|
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.
|
|
|
|
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, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
|
|
|
|
#include "Python.h"
|
|
|
|
static PyObject * pytdbpack_number(char ch, PyObject *val_iter, PyObject *packed_list);
|
|
static PyObject * pytdbpack_str_850(PyObject *val_iter, PyObject *packed_list);
|
|
static PyObject * pytdbpack_buffer(PyObject *val_iter, PyObject *packed_list);
|
|
|
|
static PyObject *pytdbpack_unpack_item(char, char **pbuf, int *plen, PyObject *);
|
|
|
|
static PyObject *pytdbpack_data(const char *format_str,
|
|
PyObject *val_seq,
|
|
PyObject *val_list);
|
|
|
|
static void pack_le_uint32(unsigned long val_long, unsigned char *pbuf);
|
|
|
|
|
|
static PyObject *pytdbpack_bad_type(char ch,
|
|
const char *expected,
|
|
PyObject *val_obj);
|
|
|
|
static const char * pytdbpack_docstring =
|
|
"Convert between Python values and Samba binary encodings.
|
|
|
|
This module is conceptually similar to the standard 'struct' module, but it
|
|
uses both a different binary format and a different description string.
|
|
|
|
Samba's encoding is based on that used inside DCE-RPC and SMB: a
|
|
little-endian, unpadded, non-self-describing binary format. It is intended
|
|
that these functions be as similar as possible to the routines in Samba's
|
|
tdb/tdbutil module, with appropriate adjustments for Python datatypes.
|
|
|
|
Python strings are used to specify the format of data to be packed or
|
|
unpacked.
|
|
|
|
Strings are always stored in codepage 850. Unicode objects are translated
|
|
to cp850; plain strings are assumed to be in latin-1 and are also
|
|
translated.
|
|
|
|
This may be a problem in the future if it is different to the Samba codepage.
|
|
It might be better to have the caller do the conversion, but that would conflict
|
|
with existing CMI code.
|
|
|
|
tdbpack format strings:
|
|
|
|
'f': NULL-terminated string in codepage 850
|
|
|
|
'P': same as 'f'
|
|
|
|
'd': 4 byte little-endian unsigned number
|
|
|
|
'w': 2 byte little-endian unsigned number
|
|
|
|
'P': \"Pointer\" value -- in the subset of DCERPC used by Samba, this is
|
|
really just an \"exists\" or \"does not exist\" flag. The boolean
|
|
value of the Python object is used.
|
|
|
|
'B': 4-byte LE length, followed by that many bytes of binary data.
|
|
Corresponds to a Python integer giving the length, followed by a byte
|
|
string of the appropriate length.
|
|
|
|
'$': Special flag indicating that the preceding format code should be
|
|
repeated while data remains. This is only supported for unpacking.
|
|
|
|
Every code corresponds to a single Python object, except 'B' which
|
|
corresponds to two values (length and contents), and '$', which produces
|
|
however many make sense.
|
|
";
|
|
|
|
|
|
static char const pytdbpack_doc[] =
|
|
"pack(format, values) -> buffer
|
|
Pack Python objects into Samba binary format according to format string.
|
|
|
|
arguments:
|
|
format -- string of tdbpack format characters
|
|
values -- sequence of value objects corresponding 1:1 to format characters
|
|
|
|
returns:
|
|
buffer -- string containing packed data
|
|
|
|
raises:
|
|
IndexError -- if there are too few values for the format
|
|
ValueError -- if any of the format characters is illegal
|
|
TypeError -- if the format is not a string, or values is not a sequence,
|
|
or any of the values is of the wrong type for the corresponding
|
|
format character
|
|
|
|
notes:
|
|
For historical reasons, it is not an error to pass more values than are consumed
|
|
by the format.
|
|
";
|
|
|
|
|
|
static char const pytdbpack_unpack_doc[] =
|
|
"unpack(format, buffer) -> (values, rest)
|
|
Unpack Samba binary data according to format string.
|
|
|
|
arguments:
|
|
format -- string of tdbpack characters
|
|
buffer -- string of packed binary data
|
|
|
|
returns:
|
|
2-tuple of:
|
|
values -- sequence of values corresponding 1:1 to format characters
|
|
rest -- string containing data that was not decoded, or '' if the
|
|
whole string was consumed
|
|
|
|
raises:
|
|
IndexError -- if there is insufficient data in the buffer for the
|
|
format (or if the data is corrupt and contains a variable-length
|
|
field extending past the end)
|
|
ValueError -- if any of the format characters is illegal
|
|
|
|
notes:
|
|
Because unconsumed data is returned, you can feed it back in to the
|
|
unpacker to extract further fields. Alternatively, if you wish to modify
|
|
some fields near the start of the data, you may be able to save time by
|
|
only unpacking and repacking the necessary part.
|
|
";
|
|
|
|
|
|
|
|
|
|
/*
|
|
* Pack objects to bytes.
|
|
*
|
|
* All objects are first individually encoded onto a list, and then the list
|
|
* of strings is concatenated. This is faster than concatenating strings,
|
|
* and reasonably simple to code.
|
|
*/
|
|
static PyObject *
|
|
pytdbpack(PyObject *self,
|
|
PyObject *args)
|
|
{
|
|
char *format_str;
|
|
PyObject *val_seq, *val_iter = NULL,
|
|
*packed_list = NULL, *packed_str = NULL,
|
|
*empty_str = NULL;
|
|
|
|
/* TODO: Test passing wrong types or too many arguments */
|
|
if (!PyArg_ParseTuple(args, "sO", &format_str, &val_seq))
|
|
return NULL;
|
|
|
|
if (!(val_iter = PyObject_GetIter(val_seq)))
|
|
goto out;
|
|
|
|
/* Create list to hold strings until we're done, then join them all. */
|
|
if (!(packed_list = PyList_New(0)))
|
|
goto out;
|
|
|
|
if (!pytdbpack_data(format_str, val_iter, packed_list))
|
|
goto out;
|
|
|
|
/* this function is not officially documented but it works */
|
|
if (!(empty_str = PyString_InternFromString("")))
|
|
goto out;
|
|
|
|
packed_str = _PyString_Join(empty_str, packed_list);
|
|
|
|
out:
|
|
Py_XDECREF(empty_str);
|
|
Py_XDECREF(val_iter);
|
|
Py_XDECREF(packed_list);
|
|
|
|
return packed_str;
|
|
}
|
|
|
|
|
|
/*
|
|
Pack data according to FORMAT_STR from the elements of VAL_SEQ into
|
|
PACKED_BUF.
|
|
|
|
The string has already been checked out, so we know that VAL_SEQ is large
|
|
enough to hold the packed data, and that there are enough value items.
|
|
(However, their types may not have been thoroughly checked yet.)
|
|
|
|
In addition, val_seq is a Python Fast sequence.
|
|
|
|
Returns NULL for error (with exception set), or None.
|
|
*/
|
|
PyObject *
|
|
pytdbpack_data(const char *format_str,
|
|
PyObject *val_iter,
|
|
PyObject *packed_list)
|
|
{
|
|
int format_i, val_i = 0;
|
|
|
|
for (format_i = 0, val_i = 0; format_str[format_i]; format_i++) {
|
|
char ch = format_str[format_i];
|
|
|
|
switch (ch) {
|
|
/* dispatch to the appropriate packer for this type,
|
|
which should pull things off the iterator, and
|
|
append them to the packed_list */
|
|
case 'w':
|
|
case 'd':
|
|
case 'p':
|
|
if (!(packed_list = pytdbpack_number(ch, val_iter, packed_list)))
|
|
return NULL;
|
|
break;
|
|
|
|
case 'f':
|
|
case 'P':
|
|
if (!(packed_list = pytdbpack_str_850(val_iter, packed_list)))
|
|
return NULL;
|
|
break;
|
|
|
|
case 'B':
|
|
if (!(packed_list = pytdbpack_buffer(val_iter, packed_list)))
|
|
return NULL;
|
|
break;
|
|
|
|
default:
|
|
PyErr_Format(PyExc_ValueError,
|
|
"%s: format character '%c' is not supported",
|
|
__FUNCTION__, ch);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return packed_list;
|
|
}
|
|
|
|
|
|
static PyObject *
|
|
pytdbpack_number(char ch, PyObject *val_iter, PyObject *packed_list)
|
|
{
|
|
unsigned long val_long;
|
|
PyObject *val_obj = NULL, *long_obj = NULL, *result_obj = NULL;
|
|
PyObject *new_list = NULL;
|
|
unsigned char pack_buf[4];
|
|
|
|
if (!(val_obj = PyIter_Next(val_iter)))
|
|
goto out;
|
|
|
|
if (!(long_obj = PyNumber_Long(val_obj))) {
|
|
pytdbpack_bad_type(ch, "Number", val_obj);
|
|
goto out;
|
|
}
|
|
|
|
val_long = PyLong_AsUnsignedLong(long_obj);
|
|
pack_le_uint32(val_long, pack_buf);
|
|
|
|
/* pack as 32-bit; if just packing a 'w' 16-bit word then only take
|
|
the first two bytes. */
|
|
|
|
if (!(result_obj = PyString_FromStringAndSize(pack_buf, ch == 'w' ? 2 : 4)))
|
|
goto out;
|
|
|
|
if (PyList_Append(packed_list, result_obj) != -1)
|
|
new_list = packed_list;
|
|
|
|
out:
|
|
Py_XDECREF(val_obj);
|
|
Py_XDECREF(long_obj);
|
|
Py_XDECREF(result_obj);
|
|
|
|
return new_list;
|
|
}
|
|
|
|
|
|
/*
|
|
* Take one string from the iterator val_iter, convert it to 8-bit CP850, and
|
|
* return it.
|
|
*
|
|
* If the input is neither a string nor Unicode, an exception is raised.
|
|
*
|
|
* If the input is Unicode, then it is converted to CP850.
|
|
*
|
|
* If the input is a String, then it is converted to Unicode using the default
|
|
* decoding method, and then converted to CP850. This in effect gives
|
|
* conversion from latin-1 (currently the PSA's default) to CP850, without
|
|
* needing a custom translation table.
|
|
*
|
|
* I hope this approach avoids being too fragile w.r.t. being passed either
|
|
* Unicode or String objects.
|
|
*/
|
|
static PyObject *
|
|
pytdbpack_str_850(PyObject *val_iter, PyObject *packed_list)
|
|
{
|
|
PyObject *val_obj = NULL;
|
|
PyObject *unicode_obj = NULL;
|
|
PyObject *cp850_str = NULL;
|
|
PyObject *nul_str = NULL;
|
|
PyObject *new_list = NULL;
|
|
|
|
if (!(val_obj = PyIter_Next(val_iter)))
|
|
goto out;
|
|
|
|
if (PyUnicode_Check(val_obj)) {
|
|
unicode_obj = val_obj;
|
|
}
|
|
else {
|
|
/* string */
|
|
if (!(unicode_obj = PyString_AsDecodedObject(val_obj, NULL, NULL)))
|
|
goto out;
|
|
Py_XDECREF(val_obj);
|
|
val_obj = NULL;
|
|
}
|
|
|
|
if (!(cp850_str = PyUnicode_AsEncodedString(unicode_obj, "cp850", NULL)))
|
|
goto out;
|
|
|
|
if (!nul_str)
|
|
/* this is constant and often-used; hold it forever */
|
|
if (!(nul_str = PyString_FromStringAndSize("", 1)))
|
|
goto out;
|
|
|
|
if ((PyList_Append(packed_list, cp850_str) != -1)
|
|
&& (PyList_Append(packed_list, nul_str) != -1))
|
|
new_list = packed_list;
|
|
|
|
out:
|
|
Py_XDECREF(unicode_obj);
|
|
Py_XDECREF(cp850_str);
|
|
|
|
return new_list;
|
|
}
|
|
|
|
|
|
/*
|
|
* Pack (LENGTH, BUFFER) pair onto the list.
|
|
*
|
|
* The buffer must already be a String, not Unicode, because it contains 8-bit
|
|
* untranslated data. In some cases it will actually be UTF_16_LE data.
|
|
*/
|
|
static PyObject *
|
|
pytdbpack_buffer(PyObject *val_iter, PyObject *packed_list)
|
|
{
|
|
PyObject *val_obj;
|
|
PyObject *new_list = NULL;
|
|
|
|
/* pull off integer and stick onto list */
|
|
if (!(packed_list = pytdbpack_number('d', val_iter, packed_list)))
|
|
return NULL;
|
|
|
|
/* this assumes that the string is the right length; the old code did the same. */
|
|
if (!(val_obj = PyIter_Next(val_iter)))
|
|
return NULL;
|
|
|
|
if (!PyString_Check(val_obj)) {
|
|
pytdbpack_bad_type('B', "String", val_obj);
|
|
goto out;
|
|
}
|
|
|
|
if (PyList_Append(packed_list, val_obj) != -1)
|
|
new_list = packed_list;
|
|
|
|
out:
|
|
Py_XDECREF(val_obj);
|
|
return new_list;
|
|
}
|
|
|
|
|
|
|
|
static PyObject *
|
|
pytdbpack_unpack(PyObject *self,
|
|
PyObject *args)
|
|
{
|
|
char *format_str, *packed_str, *ppacked;
|
|
PyObject *val_list = NULL, *ret_tuple = NULL;
|
|
PyObject *rest_string = NULL;
|
|
int format_len, packed_len;
|
|
char last_format = '#'; /* invalid */
|
|
int i;
|
|
|
|
/* get arguments */
|
|
if (!PyArg_ParseTuple(args, "ss#", &format_str, &packed_str, &packed_len))
|
|
return NULL;
|
|
|
|
format_len = strlen(format_str);
|
|
|
|
/* Allocate list to hold results. Initially empty, and we append
|
|
results as we go along. */
|
|
val_list = PyList_New(0);
|
|
if (!val_list)
|
|
goto failed;
|
|
ret_tuple = PyTuple_New(2);
|
|
if (!ret_tuple)
|
|
goto failed;
|
|
|
|
/* For every object, unpack. */
|
|
for (ppacked = packed_str, i = 0; i < format_len && format_str[i] != '$'; i++) {
|
|
last_format = format_str[i];
|
|
/* packed_len is reduced in place */
|
|
if (!pytdbpack_unpack_item(format_str[i], &ppacked, &packed_len, val_list))
|
|
goto failed;
|
|
}
|
|
|
|
/* If the last character was '$', keep going until out of space */
|
|
if (format_str[i] == '$') {
|
|
if (i == 0) {
|
|
PyErr_Format(PyExc_ValueError,
|
|
"%s: '$' may not be first character in format",
|
|
__FUNCTION__);
|
|
return NULL;
|
|
}
|
|
while (packed_len > 0)
|
|
if (!pytdbpack_unpack_item(last_format, &ppacked, &packed_len, val_list))
|
|
goto failed;
|
|
}
|
|
|
|
/* save leftovers for next time */
|
|
rest_string = PyString_FromStringAndSize(ppacked, packed_len);
|
|
if (!rest_string)
|
|
goto failed;
|
|
|
|
/* return (values, rest) tuple; give up references to them */
|
|
PyTuple_SET_ITEM(ret_tuple, 0, val_list);
|
|
val_list = NULL;
|
|
PyTuple_SET_ITEM(ret_tuple, 1, rest_string);
|
|
val_list = NULL;
|
|
return ret_tuple;
|
|
|
|
failed:
|
|
/* handle failure: deallocate anything. XDECREF forms handle NULL
|
|
pointers for objects that haven't been allocated yet. */
|
|
Py_XDECREF(val_list);
|
|
Py_XDECREF(ret_tuple);
|
|
Py_XDECREF(rest_string);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
|
|
static PyObject *pytdbpack_bad_type(char ch,
|
|
const char *expected,
|
|
PyObject *val_obj)
|
|
{
|
|
PyObject *r = PyObject_Repr(val_obj);
|
|
if (!r)
|
|
return NULL;
|
|
PyErr_Format(PyExc_TypeError,
|
|
"tdbpack: format '%c' requires %s, not %s",
|
|
ch, expected, PyString_AS_STRING(r));
|
|
Py_DECREF(r);
|
|
return val_obj;
|
|
}
|
|
|
|
|
|
/*
|
|
XXX: glib and Samba have quicker macro for doing the endianness conversions,
|
|
but I don't know of one in plain libc, and it's probably not a big deal. I
|
|
realize this is kind of dumb because we'll almost always be on x86, but
|
|
being safe is important.
|
|
*/
|
|
static void pack_le_uint32(unsigned long val_long, unsigned char *pbuf)
|
|
{
|
|
pbuf[0] = val_long & 0xff;
|
|
pbuf[1] = (val_long >> 8) & 0xff;
|
|
pbuf[2] = (val_long >> 16) & 0xff;
|
|
pbuf[3] = (val_long >> 24) & 0xff;
|
|
}
|
|
|
|
|
|
static void pack_bytes(long len, const char *from,
|
|
unsigned char **pbuf)
|
|
{
|
|
memcpy(*pbuf, from, len);
|
|
(*pbuf) += len;
|
|
}
|
|
|
|
|
|
static void
|
|
unpack_err_too_short(void)
|
|
{
|
|
PyErr_Format(PyExc_IndexError,
|
|
__FUNCTION__ ": data too short for unpack format");
|
|
}
|
|
|
|
|
|
static PyObject *
|
|
unpack_uint32(char **pbuf, int *plen)
|
|
{
|
|
unsigned long v;
|
|
unsigned char *b;
|
|
|
|
if (*plen < 4) {
|
|
unpack_err_too_short();
|
|
return NULL;
|
|
}
|
|
|
|
b = *pbuf;
|
|
v = b[0] | b[1]<<8 | b[2]<<16 | b[3]<<24;
|
|
|
|
(*pbuf) += 4;
|
|
(*plen) -= 4;
|
|
|
|
return PyLong_FromUnsignedLong(v);
|
|
}
|
|
|
|
|
|
static PyObject *unpack_int16(char **pbuf, int *plen)
|
|
{
|
|
long v;
|
|
unsigned char *b;
|
|
|
|
if (*plen < 2) {
|
|
unpack_err_too_short();
|
|
return NULL;
|
|
}
|
|
|
|
b = *pbuf;
|
|
v = b[0] | b[1]<<8;
|
|
|
|
(*pbuf) += 2;
|
|
(*plen) -= 2;
|
|
|
|
return PyInt_FromLong(v);
|
|
}
|
|
|
|
|
|
static PyObject *
|
|
unpack_string(char **pbuf, int *plen)
|
|
{
|
|
int len;
|
|
char *nul_ptr, *start;
|
|
|
|
start = *pbuf;
|
|
|
|
nul_ptr = memchr(start, '\0', *plen);
|
|
if (!nul_ptr) {
|
|
unpack_err_too_short();
|
|
return NULL;
|
|
}
|
|
|
|
len = nul_ptr - start;
|
|
|
|
*pbuf += len + 1; /* skip \0 */
|
|
*plen -= len + 1;
|
|
|
|
return PyString_FromStringAndSize(start, len);
|
|
}
|
|
|
|
|
|
static PyObject *
|
|
unpack_buffer(char **pbuf, int *plen, PyObject *val_list)
|
|
{
|
|
/* first get 32-bit len */
|
|
long slen;
|
|
unsigned char *b;
|
|
unsigned char *start;
|
|
PyObject *str_obj = NULL, *len_obj = NULL;
|
|
|
|
if (*plen < 4) {
|
|
unpack_err_too_short();
|
|
return NULL;
|
|
}
|
|
|
|
b = *pbuf;
|
|
slen = b[0] | b[1]<<8 | b[2]<<16 | b[3]<<24;
|
|
|
|
if (slen < 0) { /* surely you jest */
|
|
PyErr_Format(PyExc_ValueError,
|
|
__FUNCTION__ ": buffer seems to have negative length");
|
|
return NULL;
|
|
}
|
|
|
|
(*pbuf) += 4;
|
|
(*plen) -= 4;
|
|
start = *pbuf;
|
|
|
|
if (*plen < slen) {
|
|
PyErr_Format(PyExc_IndexError,
|
|
__FUNCTION__ ": not enough data to unpack buffer: "
|
|
"need %d bytes, have %d",
|
|
(int) slen, *plen);
|
|
return NULL;
|
|
}
|
|
|
|
(*pbuf) += slen;
|
|
(*plen) -= slen;
|
|
|
|
if (!(len_obj = PyInt_FromLong(slen)))
|
|
goto failed;
|
|
|
|
if (PyList_Append(val_list, len_obj) == -1)
|
|
goto failed;
|
|
|
|
if (!(str_obj = PyString_FromStringAndSize(start, slen)))
|
|
goto failed;
|
|
|
|
if (PyList_Append(val_list, str_obj) == -1)
|
|
goto failed;
|
|
|
|
return val_list;
|
|
|
|
failed:
|
|
Py_XDECREF(len_obj); /* handles NULL */
|
|
Py_XDECREF(str_obj);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/* Unpack a single field from packed data, according to format character CH.
|
|
Remaining data is at *PBUF, of *PLEN.
|
|
|
|
*PBUF is advanced, and *PLEN reduced to reflect the amount of data that has
|
|
been consumed.
|
|
|
|
Returns a reference to None, or NULL for failure.
|
|
*/
|
|
static PyObject *pytdbpack_unpack_item(char ch,
|
|
char **pbuf,
|
|
int *plen,
|
|
PyObject *val_list)
|
|
{
|
|
PyObject *result;
|
|
|
|
if (ch == 'w') { /* 16-bit int */
|
|
result = unpack_int16(pbuf, plen);
|
|
}
|
|
else if (ch == 'd' || ch == 'p') { /* 32-bit int */
|
|
/* pointers can just come through as integers */
|
|
result = unpack_uint32(pbuf, plen);
|
|
}
|
|
else if (ch == 'f' || ch == 'P') { /* nul-term string */
|
|
result = unpack_string(pbuf, plen);
|
|
}
|
|
else if (ch == 'B') { /* length, buffer */
|
|
return unpack_buffer(pbuf, plen, val_list);
|
|
}
|
|
else {
|
|
PyErr_Format(PyExc_ValueError,
|
|
__FUNCTION__ ": format character '%c' is not supported",
|
|
ch);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* otherwise OK */
|
|
if (!result)
|
|
return NULL;
|
|
if (PyList_Append(val_list, result) == -1)
|
|
return NULL;
|
|
|
|
return val_list;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static PyMethodDef pytdbpack_methods[] = {
|
|
{ "pack", pytdbpack, METH_VARARGS, (char *) pytdbpack_doc },
|
|
{ "unpack", pytdbpack_unpack, METH_VARARGS, (char *) pytdbpack_unpack_doc },
|
|
};
|
|
|
|
DL_EXPORT(void)
|
|
inittdbpack(void)
|
|
{
|
|
Py_InitModule3("tdbpack", pytdbpack_methods,
|
|
(char *) pytdbpack_docstring);
|
|
}
|