
/* $Header: /afs/sipb.mit.edu/project/sipb-athena/repository/src/moira/gen/mailhub.dc,v 1.8 1996/06/02 07:42:59 ghudson Exp $
 *
 * This generates the /usr/lib/aliases file for the mailhub.
 *
 *  (c) Copyright 1988, 1990 by the Massachusetts Institute of Technology.
 *  For copying and distribution information, please see the file
 *  <mit-copyright.h>.
 */

#include <mit-copyright.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <moira.h>
#include <moira_site.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
EXEC SQL INCLUDE sqlca;


extern int errno;
char *whoami = "mailhub.gen";
char *perm_malloc();
char *pstrsave();
char *divide = "##############################################################";

#define ML_WID	72
#define AL_MAX_WID 592

#define FALSE 0
#define TRUE (!FALSE)

FILE *out= stdout;


main(argc, argv)
int argc;
char **argv;
{
    long tm = time(NULL);
    char filename[64], *targetfile;
    struct stat sb;
    EXEC SQL BEGIN DECLARE SECTION;
    int flag;
    EXEC SQL END DECLARE SECTION;

#ifsql INGRES
    EXEC SQL CONNECT moira;
    EXEC SQL SET LOCKMODE SESSION WHERE LEVEL=TABLE, READLOCK=SHARED;
#endsql
#ifsql INFORMIX
    EXEC SQL DATABASE moira;
#endsql

    if (argc == 2) {
	if (stat(argv[1], &sb) == 0) {
            if (ModDiff (&flag, "users", sb.st_mtime))
	      exit(MR_DATE);
	    if (flag < 0) {
		fprintf(stderr, "File %s does not need to be rebuilt.\n",
			argv[1]);
		exit(MR_NO_CHANGE);
	    }
	}
	targetfile = argv[1];
	sprintf(filename, "%s~", targetfile);
	if ((out = fopen(filename, "w")) == NULL) {
	    fprintf(stderr, "unable to open %s for output\n", filename);
	    exit(MR_OCONFIG);
	}
    } else if (argc != 1) {
	fprintf(stderr, "usage: %s [outfile]\n", argv[0]);
	exit(MR_ARGS);
    }

    fprintf(out, "%s\n# Aliases File Extract of %s", divide, ctime(&tm));
    fprintf(out, "# This file is automatically generated, do not edit it directly.\n%s\n\n", divide);

    get_info();

#ifsql INGRES
    EXEC SQL DISCONNECT;
#endsql
#ifsql INFORMIX
    EXEC SQL CLOSE DATABASE;
#endsql

    fprintf(stderr, "Sorting Info\n");
    sort_info();

    fprintf(stderr, "Dumping information\n");
    do_people();

    fprintf(out, "\n%s\n# End of aliases file\n", divide);

    if (fclose(out)) {
	perror("close failed");
	exit(MR_CCONFIG);
    }

    if (argc == 2)
      fix_file(targetfile);
    exit(MR_SUCCESS);
}



struct hash *users, *machines, *strings, *lists, *names;
struct user {
    char *login;
    char *first;
    char *last;
    char mi;
    char *pobox;
};
struct member {
    struct member *next;
    char *name;
    int list_id;
};
struct list {
    char *name;
    char maillist;
    char *description;
    char acl_t;
    int acl_id;
    struct member *m;
};
struct names {
    char *name;
    struct names *next;
    int keep:1;
    int id:31;
};


get_info()
{
    EXEC SQL BEGIN DECLARE SECTION;
    int id, pid, bid, cnt, maillistp, acl, mid;
    char name[129], type[9], fname[17], mname[17], lname[17], buf[257];
    EXEC SQL END DECLARE SECTION;
    char *s;
    register struct user *u;
    struct list *l, *memberlist;
    register struct member *m;

    /* The following is declarative, not executed,
     * and so is dependent on where it is in the file,
     * not in the order of execution of statements.
     */
    EXEC SQL WHENEVER SQLERROR GOTO sqlerr;

    EXEC SQL SELECT modtime INTO :buf FROM users WHERE users_id = 0;
    EXEC SQL SELECT modtime INTO :buf FROM list WHERE list_id = 0;

    cnt = 0;
    machines = create_hash(1000);

    EXEC SQL DECLARE m_cursor CURSOR FOR
      SELECT mach_id, name
      FROM machine
      WHERE status=1
      ORDER BY mach_id;
    EXEC SQL OPEN m_cursor;
    while (1) {
	EXEC SQL FETCH m_cursor INTO :id, :name;
	if (sqlca.sqlcode != 0) break;
	if (s = index(name, '.'))
	  *s = 0;
	else
	  strtrim(name);
#ifdef ATHENA
	strcat(name, ".LOCAL");
#endif
	if (hash_store(machines, id, pstrsave(name)) < 0) {
	    fprintf(stderr, "Out of memory!\n");
	    exit(MR_NO_MEM);
	}
	cnt++;
    }
    EXEC SQL CLOSE m_cursor;

    fprintf(stderr, "Loaded %d machines\n", cnt);

    cnt = 0;
    strings = create_hash(11001);

    EXEC SQL DECLARE s_cursor CURSOR FOR
      SELECT string_id, trim(string)
      FROM strings
      ORDER BY string_id;
    EXEC SQL OPEN s_cursor;
    while (1) {
	EXEC SQL FETCH s_cursor INTO :id, :name;
	if (sqlca.sqlcode != 0) break;
	if (hash_store(strings, id, pstrsave(name)) < 0) {
	    fprintf(stderr, "Out of memory!\n");
	    exit(MR_NO_MEM);
	}
	cnt++;
    }
    EXEC SQL CLOSE s_cursor;

    fprintf(stderr, "Loaded %d strings\n", cnt);

    cnt = 0;
    users = create_hash(13001);

    EXEC SQL DECLARE u_cursor CURSOR FOR
      SELECT users_id, login, first, middle, last, potype, pop_id, box_id
      FROM users
      WHERE status != 3
      ORDER BY users_id;
    EXEC SQL OPEN u_cursor;
    while (1) {
        EXEC SQL FETCH u_cursor INTO :id, :name, :fname, :mname, :lname,
                                   :type, :pid, :bid;
        if (sqlca.sqlcode != 0) break;
	u = (struct user *) perm_malloc(sizeof(struct user));
	u->login = pstrsave(strtrim(name));
	u->first = pstrsave(strtrim(fname));
	u->last  = pstrsave(strtrim(lname));
	if (mname[0] != ' ')
	  u->mi = mname[0];
	else
	  u->mi = 0;

	if (type[0] == 'P' && (s = hash_lookup(machines, pid))) {
	    sprintf(buf, "%s@%s", u->login, s);
	    u->pobox = pstrsave(buf);
	} else if (type[0] ==  'S') {
	    u->pobox = hash_lookup(strings, bid);
	} else
	  u->pobox = (char *) NULL;
	if (hash_store(users, id, u) < 0) {
	    fprintf(stderr, "Out of memory!\n");
	    exit(MR_NO_MEM);
	}
	cnt++;
    }
    EXEC SQL CLOSE u_cursor;
    EXEC SQL COMMIT;
    fprintf(stderr, "Loaded %d users\n", cnt);

    cnt = 0;
    lists = create_hash(15000);

    EXEC SQL DECLARE l_cursor CURSOR FOR
      SELECT list_id, name, maillist, description, acl_type, acl_id
      FROM list
      WHERE active != 0
      ORDER BY list_id;
    EXEC SQL OPEN l_cursor;
    while (1) {
        EXEC SQL FETCH l_cursor INTO :id, :name, :maillistp, :buf, :type, :acl;
        if (sqlca.sqlcode != 0) break;
	l = (struct list *) perm_malloc(sizeof(struct list));
	l->name = pstrsave(strtrim(name));
	l->maillist = maillistp;
	l->description = pstrsave(strtrim(buf));
	l->acl_t = type[0];
	l->acl_id = acl;
	l->m = (struct member *) NULL;
	if (hash_store(lists, id, l) < 0) {
	    fprintf(stderr, "Out of memory!\n");
	    exit(MR_NO_MEM);
	}
	cnt++;
    }
    EXEC SQL CLOSE l_cursor;
    EXEC SQL COMMIT;
    fprintf(stderr, "Loaded %d lists\n", cnt);

    cnt = 0;

    EXEC SQL DECLARE m_cursor2 CURSOR FOR
      SELECT list_id, member_type, member_id
      FROM imembers
      WHERE direct = 1
      ORDER BY list_id;
    EXEC SQL OPEN m_cursor2;
    while (1) {
        EXEC SQL FETCH m_cursor2 INTO :id, :type, :mid;
        if (sqlca.sqlcode != 0) break;
	cnt++;
	if (l = (struct list *) hash_lookup(lists, id)) {
	    m = (struct member *) perm_malloc(sizeof(struct member));
	    if (type[0] == 'U' &&
		(u = (struct user *) hash_lookup(users, mid))) {
	      /*
	       * If the user has no pobox, don't include him as a 
	       * list member.
	       */
		if(!u->pobox)  {
		  fprintf(stderr, "user %s has no pobox\n", u->login);
		  continue;
		}
		m->list_id = 0;
		m->name = u->login;
		m->next = l->m;
		l->m = m;
	    } else if (type[0] == 'L' && 
		       (memberlist = (struct list *) hash_lookup(lists, mid))) {
		m->list_id = mid;
		m->name = memberlist->name;
		m->next = l->m;
		l->m = m;
	    } else if (type[0] == 'S' &&
		       (s = hash_lookup(strings, mid))) {
		m->list_id = 0;
		m->next = l->m;
		l->m = m;
		m->name = s;
	    }
	}
    }
    EXEC SQL CLOSE m_cursor2;
    fprintf(stderr, "Loaded %d members\n", cnt);
    return;
 sqlerr:
    com_err(whoami, MR_INGRES_ERR, " code %d\n", sqlca.sqlcode);
    critical_alert("DCM", "Hesiod build encountered DATABASE ERROR %d",
		   sqlca.sqlcode);
    exit(MR_INGRES_ERR);
}


save_mlist(id, l, force)
int id;
struct list *l;
int force;
{
    register struct member *m;
    register struct list *l1;

    if (l->maillist > 1 ||
	(l->maillist == 0 && !force))
      return;

    if (l->m && l->m->next == NULL &&
	!strcasecmp(l->name, l->m->name)) {
	l->maillist = 3;
	return;
    }
    l->maillist = 2;
    insert_name(l->name, -1, TRUE, FALSE);
    output_mlist(id, l);

    if (l->acl_t == 'L' && (l1 = (struct list *)hash_lookup(lists, l->acl_id)))
      save_mlist(0, l1, TRUE);
    
    for (m = l->m; m; m = m->next) {
	if (m->list_id && (l1 = (struct list *)hash_lookup(lists, m->list_id)))
	  save_mlist(0, l1, TRUE);
    }
}


insert_login(id, u, dummy)
int id;
struct user *u;
int dummy;
{
    if (u->pobox && u->login[0] != '#')
      insert_name(u->login, id, TRUE, FALSE);
}

void insert_names(id, u, dummy)
int id;
struct user *u;
int dummy;
{
    char buffer[256];

    insert_name(u->last, id, FALSE, FALSE);
    sprintf(buffer, "%s_%s", u->first, u->last);
    insert_name(buffer, id, FALSE, TRUE);
/*    sprintf(buffer, "%c_%s", u->first[0], u->last);
    insert_name(buffer, id, FALSE, TRUE); */
    if (u->mi) {
	sprintf(buffer, "%s_%c_%s", u->first, u->mi, u->last);
	insert_name(buffer, id, FALSE, TRUE);
    }
}

int incount = 0;

insert_name(s, id, nodups, copy)
char *s;
int id;
int nodups;
int copy;
{
    int code;
    register struct names *ns;

    incount++;
    code = hashstr(s);
    ns = (struct names *) hash_lookup(names, code);
    if (ns == NULL) {
	if ((ns = (struct names *) perm_malloc(sizeof(struct names))) == NULL) {
	    fprintf(stderr, "ran out of memory inserting name (sorting)\n");
	    exit(MR_NO_MEM);
	}
	if (copy)
	  ns->name = pstrsave(s);
	else
	  ns->name = s;
	ns->keep = nodups;
	ns->id = id;
	ns->next = NULL;
	if (hash_store(names, code, ns) < 0) {
	    fprintf(stderr, "Out of memory!\n");
	    exit(MR_NO_MEM);
	}
	return;
    }
    if (strcasecmp(ns->name, s)) {
	while (ns->next) {
	    ns = ns->next;
	    if (!strcasecmp(ns->name, s))
	      goto foundns;
	}
	if ((ns->next = (struct names *) perm_malloc(sizeof(struct names)))
	    	== NULL) {
	    fprintf(stderr, "ran out of memory insterting name (sorting)\n");
	    exit(MR_NO_MEM);
	}
	ns = ns->next;
	if (copy)
	  ns->name = pstrsave(s);
	else
	  ns->name = s;
	ns->keep = nodups;
	ns->id = id;
	ns->next = NULL;
	return;
    }
 foundns:
    if (nodups || ns->keep) {
	if (nodups && ns->keep)
	  fprintf(stderr, "duplicated name: %s\n", s);
	return;
    }
    ns->id = 0;
}


/* Illegal chars: ! " % ( ) , / : ; < = > @ [ \ ] ^ { | } */

static int illegalchars[] = {
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* ^@ - ^O */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* ^P - ^_ */
    0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, /* SPACE - / */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, /* 0 - ? */
    1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* @ - O */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, /* P - _ */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ` - o */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, /* p - ^? */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
};


/* While hashing the string, punt any illegal characters */

int hashstr(s)
register char *s;
{
    register int result;
    register int c;

    for (result = 0; c = *s; s++) {
	if (illegalchars[c]) {
	    register char *p;
	    for (p = s; *p; p++)
	      *p = p[1];
	    continue;
	}
	if (isupper(c))
	  c = *s = tolower(c);
/*	result = result * 31 + *s; */
	result = (result << 5) - result + c - '`';
    }
    return(result < 0 ? -result : result);
}


sort_info()
{
    names = create_hash(20001);
    hash_step(users, insert_login, NULL);
    incount = 0;
    fprintf(out, "\n%s\n# Mailing lists\n%s\n", divide, divide);
    hash_step(lists, save_mlist, FALSE);
    fprintf(stderr, "Output %d lists\n", incount);
    hash_step(users, insert_names, NULL);
    fprintf(stderr, "Inserted %d names\n", incount);
}


output_data(dummy, nms, out)
int dummy;
struct names *nms;
FILE *out;
{
    register struct names *ns;
    register struct user *u;

    incount++;
    for (ns = nms; ns; ns = ns->next) {
	if (ns->name[0] == 0 || ns->name[1] == 0) {
	    fprintf(stderr, "punting %s due to short name\n", ns->name);
	    continue;
	}
	if (ns->id > 0) {
	    u = (struct user *) hash_lookup(users, ns->id);
	    if (u->pobox) {
		fprintf(out, "%s: %s\n", ns->name, u->pobox);
	    } else {
		fprintf(out, "%s: =%s=@nobox\n", ns->name, ns->name);
	    }
	} else if (ns->id == 0) {
	    fprintf(out, "%s: =%s=@ambig\n", ns->name, ns->name);
	}
    }
}


int lwid, bol, awid;

output_mlist(id, l)
int id;
register struct list *l;
{
    struct list *l1;
    register struct member *m;
    register struct user *u;

    put_fill(out, l->description);
    if (l->acl_t ==  'L' &&
	(l1 = (struct list *) hash_lookup(lists, l->acl_id)))
      fprintf(out, "owner-%s: %s\n%s: ", l->name, l1->name, l->name);
    else if (l->acl_t ==  'U' &&
	     (u = (struct user *) hash_lookup(users, l->acl_id))) {
      /*
       * If the owner-foo membership contains a user with no pobox
       * say this user no longer exists.
       *.
	if(!u->pobox)
	  fprintf(out, "owner-%s: =%s=@dboxed\n%s: ", 
		  l->name, u->login, l->name);
	else
	  fprintf(out, "owner-%s: %s\n%s: ", l->name, u->login, l->name);
      }
    else
      fprintf(out, "%s: ", l->name);
      

    lwid = strlen(l->name) + 2;
    bol = 1;
    for (m = l->m; m; m = m->next) {
	do_member(out, m->name);
    }
    /*
     * the list membership or user pobox is NULL. Invoke the bye bye
     * mailer.
     */
    if (l->m == (struct member *) NULL)
      fprintf(out, "=%s=@dboxed", l->name);
    fprintf(out, "\n\n");
    incount++;
}


/* print out strings separated by commas, doing line breaks as appropriate */

do_member(out, s)
FILE *out;
register char *s;
{
    register wwid;
    static int cont = 1;

    wwid = strlen(s);

    if (!bol && awid + wwid + 2 > AL_MAX_WID) {
	fprintf(out, ",\n\tcontinuation%d\ncontinuation%d: ", cont, cont);
	cont++;
	awid = lwid = 17 + wwid;
	fputs(s, out);
	return;
    }

    if (bol) {
	lwid += wwid;
	awid = lwid;
	fputs(s, out);
	bol = 0;
	return;
    }
    if (lwid + wwid + 2 > ML_WID) {
	fprintf(out, ",\n\t%s", s);
	awid += lwid + wwid + 2;
	lwid = wwid + 8;
	return;
    }
    lwid += wwid + 2;
    fprintf(out, ", %s", s);
}


put_fill(aliases, string)
FILE *aliases;
register char *string;
{
    register char *c;
    register int lwid;
    register int wwid;

    if (string == 0 || *string == 0) return;
    fputs("#  ", aliases);
    lwid = 3;

    while (1) {
	while (*string && *string == ' ') string++;
	c = (char *)index(string, ' ');
	if (c == 0) {
	    wwid = strlen(string);
	} else {
	    wwid = c - string;
	    *c = 0;
	}

	if ((lwid + wwid) > ML_WID) {
	    fputs("\n#  ", aliases);
	    lwid = 3;
	    fputs(string, aliases);
	} else {
	    fputs(string, aliases);
	}

	if (c == (char *)0) break;
	/* add a space after the word */
	(void) fputc(' ', aliases);
	wwid++;
	lwid += wwid;
	string += wwid;
	/* add another if after a period */
	if (*--c == '.') {
	    (void) fputc(' ', aliases);
	    lwid++;
	}
    }

    (void) fputc('\n', aliases);
}


do_people()
{
    incount = 0;
    fprintf(out, "\n%s\n# People\n%s\n", divide, divide);
    hash_step(names, output_data, out);
    fprintf(stderr, "Output %d entries\n", incount);
}


#define chunk_size	102400

char *perm_malloc(size)
unsigned size;
{
    static char *pool = NULL;
    static unsigned pool_size = 0;
    register char *ret;

    if (size > pool_size) {
	pool = (char *) malloc(chunk_size);
	pool_size = chunk_size;
    }
    ret = pool;
    pool += size;
    pool = (char *)(((unsigned) (pool + 3)) & ~3);
    pool_size -= (pool - ret);
    return(ret);
}


/*
 * Make a (permenant) copy of a string.
 */
char *
pstrsave(s)
    char *s;
{
    register int len;
    register char *p;
    /* Kludge for sloppy string semantics */
    if (!s) {
	    printf("NULL != \"\" !!!!\r\n");
	    p = perm_malloc(1);
	    *p = '\0';
	    return p;
    }
    len = strlen(s) + 1;
    p = perm_malloc((u_int)len);
    if (p) bcopy(s, p, len);
    return p;
}

#define hash_func(h, key) (key >= 0 ? (key % h->size) : (-key % h->size))

/* Create a hash table.  The size is just a hint, not a maximum. */

struct hash *create_hash(size)
int size;
{
    struct hash *h;

    h = (struct hash *) perm_malloc(sizeof(struct hash));
    if (h == (struct hash *) NULL)
      return((struct hash *) NULL);
    h->size = size;
    h->data = (struct bucket **) perm_malloc(size * sizeof(char *));
    if (h->data == (struct bucket **) NULL) {
	free(h);
	return((struct hash *) NULL);
    }
    bzero(h->data, size * sizeof(char *));
    return(h);
}

/* Lookup an object in the hash table.  Returns the value associated with
 * the key, or NULL (thus NULL is not a very good value to store...)
 */

char *hash_lookup(h, key)
struct hash *h;
register int key;
{
    register struct bucket *b;

    b = h->data[hash_func(h, key)];
    while (b && b->key != key)
      b = b->next;
    if (b && b->key == key)
      return(b->data);
    else
      return(NULL);
}


/* Update an existing object in the hash table.  Returns 1 if the object
 * existed, or 0 if not.
 */

int hash_update(h, key, value)
struct hash *h;
register int key;
char *value;
{
    register struct bucket *b;

    b = h->data[hash_func(h, key)];
    while (b && b->key != key)
      b = b->next;
    if (b && b->key == key) {
	b->data = value;
	return(1);
    } else
      return(0);
}


/* Store an item in the hash table.  Returns 0 if the key was not previously
 * there, 1 if it was, or -1 if we ran out of memory.
 */

int hash_store(h, key, value)
struct hash *h;
register int key;
char *value;
{
    register struct bucket *b, **p;

    p = &(h->data[hash_func(h, key)]);
    if (*p == NULL) {
	b = *p = (struct bucket *) perm_malloc(sizeof(struct bucket));
	if (b == (struct bucket *) NULL)
	  return(-1);
	b->next = NULL;
	b->key = key;
	b->data = value;
	return(0);
    }

    for (b = *p; b && b->key != key; b = *p)
      p = (struct bucket **) *p;
    if (b && b->key == key) {
	b->data = value;
	return(1);
    }
    b = *p = (struct bucket *) perm_malloc(sizeof(struct bucket));
    if (b == (struct bucket *) NULL)
      return(-1);
    b->next = NULL;
    b->key = key;
    b->data = value;
    return(0);
}


/* Search through the hash table for a given value.  For each piece of
 * data with that value, call the callback proc with the corresponding key.
 */

hash_search(h, value, callback)
struct hash *h;
register char *value;
void (*callback)();
{
    register struct bucket *b, **p;

    for (p = &(h->data[h->size - 1]); p >= h->data; p--) {
	for (b = *p; b; b = b->next) {
	    if (b->data == value)
	      (*callback)(b->key);
	}
    }
}


/* Step through the hash table, calling the callback proc with each key.
 */

hash_step(h, callback, hint)
struct hash *h;
void (*callback)();
char *hint;
{
    register struct bucket *b, **p;

    for (p = &(h->data[h->size - 1]); p >= h->data; p--) {
	for (b = *p; b; b = b->next) {
	    (*callback)(b->key, b->data, hint);
	}
    }
}


/* Deallocate all of the memory associated with a table */

hash_destroy(h)
struct hash *h;
{
    register struct bucket *b, **p, *b1;

    for (p = &(h->data[h->size - 1]); p >= h->data; p--) {
	for (b = *p; b; b = b1) {
	    b1 = b->next;
	    free(b);
	}
    }
}
