/* This file is part of pam-modules. Copyright (C) 2008-2022 Sergey Poznyakoff 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 . */ #include #define GRAY_SLIST_BUCKET_SIZE 1024 struct gray_slist_bucket { struct gray_slist_bucket *next; char *buf; size_t level; size_t size; }; struct gray_slist { struct gray_slist_bucket *head, *tail; struct gray_slist_bucket *free; int ec; /* error code */ }; static struct gray_slist_bucket * alloc_bucket(size_t size) { struct gray_slist_bucket *p = malloc(sizeof(*p) + size); if (p) { p->buf = (char*)(p + 1); p->level = 0; p->size = size; p->next = NULL; } return p; } static int alloc_pool(gray_slist_t slist, size_t size) { struct gray_slist_bucket *p = alloc_bucket(GRAY_SLIST_BUCKET_SIZE); if (!p) { slist->ec = errno; return 1; } if (slist->tail) slist->tail->next = p; else slist->head = p; slist->tail = p; return 0; } static ssize_t copy_chars(gray_slist_t slist, const char *str, size_t n) { size_t rest; if (!slist->head || slist->tail->level == slist->tail->size) { if (alloc_pool(slist, GRAY_SLIST_BUCKET_SIZE)) return -1; } rest = slist->tail->size - slist->tail->level; if (n > rest) n = rest; memcpy(slist->tail->buf + slist->tail->level, str, n); slist->tail->level += n; return n; } /* Create new string list. */ gray_slist_t gray_slist_create(void) { gray_slist_t slist = malloc(sizeof(*slist)); if (slist) { slist->head = slist->tail = slist->free = 0; slist->ec = 0; } return slist; } /* Return error code associated with the string list */ int gray_slist_err(gray_slist_t slist) { return slist->ec; } /* Clear error code associated with the string list */ void gray_slist_clerr(gray_slist_t slist) { slist->ec = 0; } /* Clear content of the string list */ void gray_slist_clear(gray_slist_t slist) { if (slist->tail) { slist->tail->next = slist->free; slist->free = slist->head; slist->head = slist->tail = NULL; } gray_slist_clerr(slist); } /* Deallocate memory used by the string list. Store NULL in *SLIST */ void gray_slist_free(gray_slist_t *slist) { if (*slist) { struct gray_slist_bucket *p; gray_slist_clear(*slist); for (p = (*slist)->free; p; ) { struct gray_slist_bucket *next = p->next; free(p); p = next; } } free(*slist); *slist = NULL; } /* Append N bytes from the string STR to the list. Return the number of bytes actually stored. */ ssize_t gray_slist_append(gray_slist_t slist, const char *str, size_t n) { ssize_t total; if (slist->ec) return -1; total = 0; while (total < n) { ssize_t s = copy_chars(slist, str + total, n - total); if (s == -1) break; total += s; } return total; } /* Append one character to the list. Return 1 on success. */ ssize_t gray_slist_append_char(gray_slist_t slist, char c) { return gray_slist_append(slist, &c, 1); } /* Return the total size of strings stored in the list. */ size_t gray_slist_size(gray_slist_t slist) { size_t size = 0; struct gray_slist_bucket *p; for (p = slist->head; p; p = p->next) size += p->level; return size; } /* Coalesce all segments in the list into a single contiguous string. Return its length. */ ssize_t gray_slist_coalesce(gray_slist_t slist) { size_t size; if (slist->ec) return -1; else if (slist->head && slist->head->next == NULL) size = slist->head->level; else { size = gray_slist_size(slist); struct gray_slist_bucket *bucket, *p; bucket = alloc_bucket(size); if (!bucket) return -1; for (p = slist->head; p; ) { struct gray_slist_bucket *next = p->next; memcpy(bucket->buf + bucket->level, p->buf, p->level); bucket->level += p->level; free(p); p = next; } slist->head = slist->tail = bucket; } return size; } /* Return the pointer to the head of the list. */ void * gray_slist_head(gray_slist_t slist, size_t *psize) { if (*psize) *psize = slist->head ? slist->head->level : 0; return slist->head ? slist->head->buf : NULL; } /* Finish building the string list. Coalesce all segments, and clear the unused memory. On success, return the pointer to the newly-built string. On error (out of memory), return NULL and set error status in the SLIST. */ void * gray_slist_finish(gray_slist_t slist) { if (slist->ec) return NULL; if (gray_slist_coalesce(slist) == -1) return NULL; gray_slist_clear(slist); return slist->free->buf; } #define to_num(c) \ (isdigit(c) ? c - '0' : (isxdigit(c) ? toupper(c) - 'A' + 10 : 255 )) int gray_slist_grow_backslash_num(gray_slist_t slist, char *text, char **pend, int len, int base) { int i; int val = 0; char *start = text; if (slist->ec) return -1; if (text[0] == '\\') { text++; if (base == 16) text++; } for (i = 0; i < len; i++) { int n = (unsigned char)text[i]; if (n > 127 || (n = to_num(n)) >= base) break; val = val*base + n; } if (i == 0) { if (gray_slist_append(slist, start, 1) != 1) return -1; if (pend) *pend = start + 1; } else { if (gray_slist_append_char(slist, val) != 1) return -1; if (pend) *pend = text + i; } return 0; } int gray_decode_backslash(int c) { static char transtab[] = "a\ab\bf\fn\nr\rt\t"; char *p; for (p = transtab; *p; p += 2) { if (*p == c) return p[1]; } return c; } int gray_slist_grow_backslash(gray_slist_t slist, char *text, char **endp) { if (text[1] == '\\' || (unsigned char)text[1] > 127) { if (gray_slist_append_char(slist, text[1]) != 1) return -1; text += 2; } else if (isdigit(text[1])) { if (gray_slist_grow_backslash_num(slist, text, &text, 3, 8)) return -1; } else if (text[1] == 'x' || text[1] == 'X') { if (gray_slist_grow_backslash_num(slist, text, &text, 2, 16)) return -1; } else { int c = gray_decode_backslash(text[1]); if (gray_slist_append_char(slist, c) != 1) return -1; text += 2; } *endp = text; return 0; }