Blame apache2/libinjection/libinjection_xss.c

Packit 284210
Packit 284210
#include "libinjection.h"
Packit 284210
#include "libinjection_xss.h"
Packit 284210
#include "libinjection_html5.h"
Packit 284210
Packit 284210
#include <assert.h>
Packit 284210
#include <stdio.h>
Packit 284210
Packit 284210
typedef enum attribute {
Packit 284210
    TYPE_NONE
Packit 284210
    , TYPE_BLACK     /* ban always */
Packit 284210
    , TYPE_ATTR_URL   /* attribute value takes a URL-like object */
Packit 284210
    , TYPE_STYLE
Packit 284210
    , TYPE_ATTR_INDIRECT  /* attribute *name* is given in *value* */
Packit 284210
} attribute_t;
Packit 284210
Packit 284210
Packit 284210
static attribute_t is_black_attr(const char* s, size_t len);
Packit 284210
static int is_black_tag(const char* s, size_t len);
Packit 284210
static int is_black_url(const char* s, size_t len);
Packit 284210
static int cstrcasecmp_with_null(const char *a, const char *b, size_t n);
Packit 284210
static int html_decode_char_at(const char* src, size_t len, size_t* consumed);
Packit 284210
static int htmlencode_startswith(const char* prefix, const char *src, size_t n);
Packit 284210
Packit 284210
Packit 284210
typedef struct stringtype {
Packit 284210
    const char* name;
Packit 284210
    attribute_t atype;
Packit 284210
} stringtype_t;
Packit 284210
Packit 284210
Packit 284210
static const int gsHexDecodeMap[256] = {
Packit 284210
    256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
Packit 284210
    256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
Packit 284210
    256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
Packit 284210
    256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
Packit 284210
    0,   1,   2,   3,   4,   5,   6,   7,   8,   9, 256, 256,
Packit 284210
    256, 256, 256, 256, 256,  10,  11,  12,  13,  14,  15, 256,
Packit 284210
    256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
Packit 284210
    256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
Packit 284210
    256,  10,  11,  12,  13,  14,  15, 256, 256, 256, 256, 256,
Packit 284210
    256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
Packit 284210
    256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
Packit 284210
    256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
Packit 284210
    256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
Packit 284210
    256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
Packit 284210
    256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
Packit 284210
    256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
Packit 284210
    256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
Packit 284210
    256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
Packit 284210
    256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
Packit 284210
    256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
Packit 284210
    256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
Packit 284210
    256, 256, 256, 256
Packit 284210
};
Packit 284210
Packit 284210
static int html_decode_char_at(const char* src, size_t len, size_t* consumed)
Packit 284210
{
Packit 284210
    int val = 0;
Packit 284210
    size_t i;
Packit 284210
    int ch;
Packit 284210
Packit 284210
    if (len == 0 || src == NULL) {
Packit 284210
        *consumed = 0;
Packit 284210
        return -1;
Packit 284210
    }
Packit 284210
Packit 284210
    *consumed = 1;
Packit 284210
    if (*src != '&' || len < 2) {
Packit 284210
        return (unsigned char)(*src);
Packit 284210
    }
Packit 284210
Packit 284210
Packit 284210
    if (*(src+1) != '#') {
Packit 284210
        /* normally this would be for named entities
Packit 284210
         * but for this case we don't actually care
Packit 284210
         */
Packit 284210
        return '&';
Packit 284210
    }
Packit 284210
Packit 284210
    if (*(src+2) == 'x' || *(src+2) == 'X') {
Packit 284210
        ch = (unsigned char) (*(src+3));
Packit 284210
        ch = gsHexDecodeMap[ch];
Packit 284210
        if (ch == 256) {
Packit 284210
            /* degenerate case  '&#[?]' */
Packit 284210
            return '&';
Packit 284210
        }
Packit 284210
        val = ch;
Packit 284210
        i = 4;
Packit 284210
        while (i < len) {
Packit 284210
            ch = (unsigned char) src[i];
Packit 284210
            if (ch == ';') {
Packit 284210
                *consumed = i + 1;
Packit 284210
                return val;
Packit 284210
            }
Packit 284210
            ch = gsHexDecodeMap[ch];
Packit 284210
            if (ch == 256) {
Packit 284210
                *consumed = i;
Packit 284210
                return val;
Packit 284210
            }
Packit 284210
            val = (val * 16) + ch;
Packit 284210
            if (val > 0x1000FF) {
Packit 284210
                return '&';
Packit 284210
            }
Packit 284210
            ++i;
Packit 284210
        }
Packit 284210
        *consumed = i;
Packit 284210
        return val;
Packit 284210
    } else {
Packit 284210
        i = 2;
Packit 284210
        ch = (unsigned char) src[i];
Packit 284210
        if (ch < '0' || ch > '9') {
Packit 284210
            return '&';
Packit 284210
        }
Packit 284210
        val = ch - '0';
Packit 284210
        i += 1;
Packit 284210
        while (i < len) {
Packit 284210
            ch = (unsigned char) src[i];
Packit 284210
            if (ch == ';') {
Packit 284210
                *consumed = i + 1;
Packit 284210
                return val;
Packit 284210
            }
Packit 284210
            if (ch < '0' || ch > '9') {
Packit 284210
                *consumed = i;
Packit 284210
                return val;
Packit 284210
            }
Packit 284210
            val = (val * 10) + (ch - '0');
Packit 284210
            if (val > 0x1000FF) {
Packit 284210
                return '&';
Packit 284210
            }
Packit 284210
            ++i;
Packit 284210
        }
Packit 284210
        *consumed = i;
Packit 284210
        return val;
Packit 284210
    }
Packit 284210
}
Packit 284210
Packit 284210
Packit 284210
/*
Packit 284210
 * view-source:
Packit 284210
 * data:
Packit 284210
 * javascript:
Packit 284210
 */
Packit 284210
static stringtype_t BLACKATTR[] = {
Packit 284210
    { "ACTION", TYPE_ATTR_URL }     /* form */
Packit 284210
    , { "ATTRIBUTENAME", TYPE_ATTR_INDIRECT } /* SVG allow indirection of attribute names */
Packit 284210
    , { "BY", TYPE_ATTR_URL }         /* SVG */
Packit 284210
    , { "BACKGROUND", TYPE_ATTR_URL } /* IE6, O11 */
Packit 284210
    , { "DATAFORMATAS", TYPE_BLACK }  /* IE */
Packit 284210
    , { "DATASRC", TYPE_BLACK }       /* IE */
Packit 284210
    , { "DYNSRC", TYPE_ATTR_URL }     /* Obsolete img attribute */
Packit 284210
    , { "FILTER", TYPE_STYLE }        /* Opera, SVG inline style */
Packit 284210
    , { "FORMACTION", TYPE_ATTR_URL } /* HTML 5 */
Packit 284210
    , { "FOLDER", TYPE_ATTR_URL }     /* Only on A tags, IE-only */
Packit 284210
    , { "FROM", TYPE_ATTR_URL }       /* SVG */
Packit 284210
    , { "HANDLER", TYPE_ATTR_URL }    /* SVG Tiny, Opera */
Packit 284210
    , { "HREF", TYPE_ATTR_URL }
Packit 284210
    , { "LOWSRC", TYPE_ATTR_URL }     /* Obsolete img attribute */
Packit 284210
    , { "POSTER", TYPE_ATTR_URL }     /* Opera 10,11 */
Packit 284210
    , { "SRC", TYPE_ATTR_URL }
Packit 284210
    , { "STYLE", TYPE_STYLE }
Packit 284210
    , { "TO", TYPE_ATTR_URL }         /* SVG */
Packit 284210
    , { "VALUES", TYPE_ATTR_URL }     /* SVG */
Packit 284210
    , { "XLINK:HREF", TYPE_ATTR_URL }
Packit 284210
    , { NULL, TYPE_NONE }
Packit 284210
};
Packit 284210
Packit 284210
/* xmlns */
Packit 284210
/* `xml-stylesheet` > <eval>, <if expr=> */
Packit 284210
Packit 284210
/*
Packit 284210
  static const char* BLACKATTR[] = {
Packit 284210
  "ATTRIBUTENAME",
Packit 284210
  "BACKGROUND",
Packit 284210
  "DATAFORMATAS",
Packit 284210
  "HREF",
Packit 284210
  "SCROLL",
Packit 284210
  "SRC",
Packit 284210
  "STYLE",
Packit 284210
  "SRCDOC",
Packit 284210
  NULL
Packit 284210
  };
Packit 284210
*/
Packit 284210
Packit 284210
static const char* BLACKTAG[] = {
Packit 284210
    "APPLET"
Packit 284210
    /*    , "AUDIO" */
Packit 284210
    , "BASE"
Packit 284210
    , "COMMENT"  /* IE http://html5sec.org/#38 */
Packit 284210
    , "EMBED"
Packit 284210
    /*   ,  "FORM" */
Packit 284210
    , "FRAME"
Packit 284210
    , "FRAMESET"
Packit 284210
    , "HANDLER" /* Opera SVG, effectively a script tag */
Packit 284210
    , "IFRAME"
Packit 284210
    , "IMPORT"
Packit 284210
    , "ISINDEX"
Packit 284210
    , "LINK"
Packit 284210
    , "LISTENER"
Packit 284210
    /*    , "MARQUEE" */
Packit 284210
    , "META"
Packit 284210
    , "NOSCRIPT"
Packit 284210
    , "OBJECT"
Packit 284210
    , "SCRIPT"
Packit 284210
    , "STYLE"
Packit 284210
    /*    , "VIDEO" */
Packit 284210
    , "VMLFRAME"
Packit 284210
    , "XML"
Packit 284210
    , "XSS"
Packit 284210
    , NULL
Packit 284210
};
Packit 284210
Packit 284210
Packit 284210
static int cstrcasecmp_with_null(const char *a, const char *b, size_t n)
Packit 284210
{
Packit 284210
    char ca;
Packit 284210
    char cb;
Packit 284210
    /* printf("Comparing to %s %.*s\n", a, (int)n, b); */
Packit 284210
    while (n-- > 0) {
Packit 284210
        cb = *b++;
Packit 284210
        if (cb == '\0') continue;
Packit 284210
Packit 284210
        ca = *a++;
Packit 284210
Packit 284210
        if (cb >= 'a' && cb <= 'z') {
Packit 284210
            cb -= 0x20;
Packit 284210
        }
Packit 284210
        /* printf("Comparing %c vs %c with %d left\n", ca, cb, (int)n); */
Packit 284210
        if (ca != cb) {
Packit 284210
            return 1;
Packit 284210
        }
Packit 284210
    }
Packit 284210
Packit 284210
    if (*a == 0) {
Packit 284210
        /* printf(" MATCH \n"); */
Packit 284210
        return 0;
Packit 284210
    } else {
Packit 284210
        return 1;
Packit 284210
    }
Packit 284210
}
Packit 284210
Packit 284210
/*
Packit 284210
 * Does an HTML encoded  binary string (const char*, length) start with
Packit 284210
 * a all uppercase c-string (null terminated), case insensitive!
Packit 284210
 *
Packit 284210
 * also ignore any embedded nulls in the HTML string!
Packit 284210
 *
Packit 284210
 * return 1 if match / starts with
Packit 284210
 * return 0 if not
Packit 284210
 */
Packit 284210
static int htmlencode_startswith(const char *a, const char *b, size_t n)
Packit 284210
{
Packit 284210
    size_t consumed;
Packit 284210
    int cb;
Packit 284210
    int first = 1;
Packit 284210
    /* printf("Comparing %s with %.*s\n", a,(int)n,b); */
Packit 284210
    while (n > 0) {
Packit 284210
        if (*a == 0) {
Packit 284210
            /* printf("Match EOL!\n"); */
Packit 284210
            return 1;
Packit 284210
        }
Packit 284210
        cb = html_decode_char_at(b, n, &consumed);
Packit 284210
        b += consumed;
Packit 284210
        n -= consumed;
Packit 284210
Packit 284210
        if (first && cb <= 32) {
Packit 284210
            /* ignore all leading whitespace and control characters */
Packit 284210
            continue;
Packit 284210
        }
Packit 284210
        first = 0;
Packit 284210
Packit 284210
        if (cb == 0) {
Packit 284210
            /* always ignore null characters in user input */
Packit 284210
            continue;
Packit 284210
        }
Packit 284210
Packit 284210
        if (cb == 10) {
Packit 284210
            /* always ignore vertical tab characters in user input */
Packit 284210
            /* who allows this?? */
Packit 284210
            continue;
Packit 284210
        }
Packit 284210
Packit 284210
        if (cb >= 'a' && cb <= 'z') {
Packit 284210
            /* upcase */
Packit 284210
            cb -= 0x20;
Packit 284210
        }
Packit 284210
Packit 284210
        if (*a != (char) cb) {
Packit 284210
            /* printf("    %c != %c\n", *a, cb); */
Packit 284210
            /* mismatch */
Packit 284210
            return 0;
Packit 284210
        }
Packit 284210
        a++;
Packit 284210
    }
Packit 284210
Packit 284210
    return (*a == 0) ? 1 : 0;
Packit 284210
}
Packit 284210
Packit 284210
static int is_black_tag(const char* s, size_t len)
Packit 284210
{
Packit 284210
    const char** black;
Packit 284210
Packit 284210
    if (len < 3) {
Packit 284210
        return 0;
Packit 284210
    }
Packit 284210
Packit 284210
    black = BLACKTAG;
Packit 284210
    while (*black != NULL) {
Packit 284210
        if (cstrcasecmp_with_null(*black, s, len) == 0) {
Packit 284210
            /* printf("Got black tag %s\n", *black); */
Packit 284210
            return 1;
Packit 284210
        }
Packit 284210
        black += 1;
Packit 284210
    }
Packit 284210
Packit 284210
    /* anything SVG related */
Packit 284210
    if ((s[0] == 's' || s[0] == 'S') &&
Packit 284210
        (s[1] == 'v' || s[1] == 'V') &&
Packit 284210
        (s[2] == 'g' || s[2] == 'G')) {
Packit 284210
        /*        printf("Got SVG tag \n"); */
Packit 284210
        return 1;
Packit 284210
    }
Packit 284210
Packit 284210
    /* Anything XSL(t) related */
Packit 284210
    if ((s[0] == 'x' || s[0] == 'X') &&
Packit 284210
        (s[1] == 's' || s[1] == 'S') &&
Packit 284210
        (s[2] == 'l' || s[2] == 'L')) {
Packit 284210
        /*      printf("Got XSL tag\n"); */
Packit 284210
        return 1;
Packit 284210
    }
Packit 284210
Packit 284210
    return 0;
Packit 284210
}
Packit 284210
Packit 284210
static attribute_t is_black_attr(const char* s, size_t len)
Packit 284210
{
Packit 284210
    stringtype_t* black;
Packit 284210
Packit 284210
    if (len < 2) {
Packit 284210
        return TYPE_NONE;
Packit 284210
    }
Packit 284210
Packit 284210
    if (len >= 5) {
Packit 284210
        /* JavaScript on.* */
Packit 284210
        if ((s[0] == 'o' || s[0] == 'O') && (s[1] == 'n' || s[1] == 'N')) {
Packit 284210
            /* printf("Got JavaScript on- attribute name\n"); */
Packit 284210
            return TYPE_BLACK;
Packit 284210
        }
Packit 284210
Packit 284210
Packit 284210
Packit 284210
        /* XMLNS can be used to create arbitrary tags */
Packit 284210
        if (cstrcasecmp_with_null("XMLNS", s, 5) == 0 || cstrcasecmp_with_null("XLINK", s, 5) == 0) {
Packit 284210
            /*      printf("Got XMLNS and XLINK tags\n"); */
Packit 284210
            return TYPE_BLACK;
Packit 284210
        }
Packit 284210
    }
Packit 284210
Packit 284210
    black = BLACKATTR;
Packit 284210
    while (black->name != NULL) {
Packit 284210
        if (cstrcasecmp_with_null(black->name, s, len) == 0) {
Packit 284210
            /*      printf("Got banned attribute name %s\n", black->name); */
Packit 284210
            return black->atype;
Packit 284210
        }
Packit 284210
        black += 1;
Packit 284210
    }
Packit 284210
Packit 284210
    return TYPE_NONE;
Packit 284210
}
Packit 284210
Packit 284210
static int is_black_url(const char* s, size_t len)
Packit 284210
{
Packit 284210
Packit 284210
    static const char* data_url = "DATA";
Packit 284210
    static const char* viewsource_url = "VIEW-SOURCE";
Packit 284210
Packit 284210
    /* obsolete but interesting signal */
Packit 284210
    static const char* vbscript_url = "VBSCRIPT";
Packit 284210
Packit 284210
    /* covers JAVA, JAVASCRIPT, + colon */
Packit 284210
    static const char* javascript_url = "JAVA";
Packit 284210
Packit 284210
    /* skip whitespace */
Packit 284210
    while (len > 0 && (*s <= 32 || *s >= 127)) {
Packit 284210
        /*
Packit 284210
         * HEY: this is a signed character.
Packit 284210
         *  We are intentionally skipping high-bit characters too
Packit 284210
         *  since they are not ASCII, and Opera sometimes uses UTF-8 whitespace.
Packit 284210
         *
Packit 284210
         * Also in EUC-JP some of the high bytes are just ignored.
Packit 284210
         */
Packit 284210
        ++s;
Packit 284210
        --len;
Packit 284210
    }
Packit 284210
Packit 284210
    if (htmlencode_startswith(data_url, s, len)) {
Packit 284210
        return 1;
Packit 284210
    }
Packit 284210
Packit 284210
    if (htmlencode_startswith(viewsource_url, s, len)) {
Packit 284210
        return 1;
Packit 284210
    }
Packit 284210
Packit 284210
    if (htmlencode_startswith(javascript_url, s, len)) {
Packit 284210
        return 1;
Packit 284210
    }
Packit 284210
Packit 284210
    if (htmlencode_startswith(vbscript_url, s, len)) {
Packit 284210
        return 1;
Packit 284210
    }
Packit 284210
    return 0;
Packit 284210
}
Packit 284210
Packit 284210
int libinjection_is_xss(const char* s, size_t len, int flags)
Packit 284210
{
Packit 284210
    h5_state_t h5;
Packit 284210
    attribute_t attr = TYPE_NONE;
Packit 284210
Packit 284210
    libinjection_h5_init(&h5, s, len, (enum html5_flags) flags);
Packit 284210
    while (libinjection_h5_next(&h5)) {
Packit 284210
        if (h5.token_type != ATTR_VALUE) {
Packit 284210
            attr = TYPE_NONE;
Packit 284210
        }
Packit 284210
Packit 284210
        if (h5.token_type == DOCTYPE) {
Packit 284210
            return 1;
Packit 284210
        } else if (h5.token_type == TAG_NAME_OPEN) {
Packit 284210
            if (is_black_tag(h5.token_start, h5.token_len)) {
Packit 284210
                return 1;
Packit 284210
            }
Packit 284210
        } else if (h5.token_type == ATTR_NAME) {
Packit 284210
            attr = is_black_attr(h5.token_start, h5.token_len);
Packit 284210
        } else if (h5.token_type == ATTR_VALUE) {
Packit 284210
            /*
Packit 284210
             * IE6,7,8 parsing works a bit differently so
Packit 284210
             * a whole <script> or other black tag might be hiding
Packit 284210
             * inside an attribute value under HTML 5 parsing
Packit 284210
             * See http://html5sec.org/#102
Packit 284210
             * to avoid doing a full reparse of the value, just
Packit 284210
             * look for "<".  This probably need adjusting to
Packit 284210
             * handle escaped characters
Packit 284210
             */
Packit 284210
            /*
Packit 284210
              if (memchr(h5.token_start, '<', h5.token_len) != NULL) {
Packit 284210
              return 1;
Packit 284210
              }
Packit 284210
            */
Packit 284210
Packit 284210
            switch (attr) {
Packit 284210
            case TYPE_NONE:
Packit 284210
                break;
Packit 284210
            case TYPE_BLACK:
Packit 284210
                return 1;
Packit 284210
            case TYPE_ATTR_URL:
Packit 284210
                if (is_black_url(h5.token_start, h5.token_len)) {
Packit 284210
                    return 1;
Packit 284210
                }
Packit 284210
                break;
Packit 284210
            case TYPE_STYLE:
Packit 284210
                return 1;
Packit 284210
            case TYPE_ATTR_INDIRECT:
Packit 284210
                /* an attribute name is specified in a _value_ */
Packit 284210
                if (is_black_attr(h5.token_start, h5.token_len)) {
Packit 284210
                    return 1;
Packit 284210
                }
Packit 284210
                break;
Packit 284210
/*
Packit 284210
  default:
Packit 284210
  assert(0);
Packit 284210
*/
Packit 284210
            }
Packit 284210
            attr = TYPE_NONE;
Packit 284210
        } else if (h5.token_type == TAG_COMMENT) {
Packit 284210
            /* IE uses a "`" as a tag ending char */
Packit 284210
            if (memchr(h5.token_start, '`', h5.token_len) != NULL) {
Packit 284210
                return 1;
Packit 284210
            }
Packit 284210
Packit 284210
            /* IE conditional comment */
Packit 284210
            if (h5.token_len > 3) {
Packit 284210
                if (h5.token_start[0] == '[' &&
Packit 284210
                    (h5.token_start[1] == 'i' || h5.token_start[1] == 'I') &&
Packit 284210
                    (h5.token_start[2] == 'f' || h5.token_start[2] == 'F')) {
Packit 284210
                    return 1;
Packit 284210
                }
Packit 284210
                if ((h5.token_start[0] == 'x' || h5.token_start[0] == 'X') &&
Packit 284210
                    (h5.token_start[1] == 'm' || h5.token_start[1] == 'M') &&
Packit 284210
                    (h5.token_start[2] == 'l' || h5.token_start[2] == 'L')) {
Packit 284210
                    return 1;
Packit 284210
                }
Packit 284210
            }
Packit 284210
Packit 284210
            if (h5.token_len > 5) {
Packit 284210
                /*  IE 
Packit 284210
                if (cstrcasecmp_with_null("IMPORT", h5.token_start, 6) == 0) {
Packit 284210
                    return 1;
Packit 284210
                }
Packit 284210
Packit 284210
                /*  XML Entity definition */
Packit 284210
                if (cstrcasecmp_with_null("ENTITY", h5.token_start, 6) == 0) {
Packit 284210
                    return 1;
Packit 284210
                }
Packit 284210
            }
Packit 284210
        }
Packit 284210
    }
Packit 284210
    return 0;
Packit 284210
}
Packit 284210
Packit 284210
Packit 284210
/*
Packit 284210
 * wrapper
Packit 284210
 */
Packit 284210
int libinjection_xss(const char* s, size_t len)
Packit 284210
{
Packit 284210
    if (libinjection_is_xss(s, len, DATA_STATE)) {
Packit 284210
        return 1;
Packit 284210
    }
Packit 284210
    if (libinjection_is_xss(s, len, VALUE_NO_QUOTE)) {
Packit 284210
        return 1;
Packit 284210
    }
Packit 284210
    if (libinjection_is_xss(s, len, VALUE_SINGLE_QUOTE)) {
Packit 284210
        return 1;
Packit 284210
    }
Packit 284210
    if (libinjection_is_xss(s, len, VALUE_DOUBLE_QUOTE)) {
Packit 284210
        return 1;
Packit 284210
    }
Packit 284210
    if (libinjection_is_xss(s, len, VALUE_BACK_QUOTE)) {
Packit 284210
        return 1;
Packit 284210
    }
Packit 284210
Packit 284210
    return 0;
Packit 284210
}