/*
 * $Id: maildirquota.c,v 1.13 2007/05/22 03:58:59 rwidmer Exp $
 * Copyright (C) 1999-2003 Inter7 Internet Technologies, Inc.
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 *
 */

/* include files */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#include <time.h>
#include <sys/uio.h>
#include "vauth.h"
#include "vpopmail.h"
#include "vlimits.h"
#include "maildirquota.h"
#include "config.h"

/* private functions - no name clashes with courier */
static char *makenewmaildirsizename(const char *, int *);
static int countcurnew(const char *, time_t *, off_t *, unsigned *);
static int countsubdir(const char *, const char *,
		time_t *, off_t *, unsigned *);
static int statcurnew(const char *, time_t *);
static int statsubdir(const char *, const char *, time_t *);
static int doaddquota(const char *, int, const char *, long, int, int);
static int docheckquota(const char *dir, int *maildirsize_fdptr,
	const char *quota_type, long xtra_size, int xtra_cnt, int *percentage);
static int docount(const char *, time_t *, off_t *, unsigned *);
static int maildir_checkquota(const char *dir, int *maildirsize_fdptr,
	const char *quota_type, long xtra_size, int xtra_cnt);
/* moved into maildirquota.h as non-static
static int maildir_addquota(const char *dir, int maildirsize_fd,
	const char *quota_type, long maildirsize_size, int maildirsize_cnt);
*/
static int maildir_safeopen(const char *path, int mode, int perm);
static char *str_pid_t(pid_t t, char *arg);
static char *str_time_t(time_t t, char *arg);
static int maildir_parsequota(const char *n, unsigned long *s);


#define  NUMBUFSIZE      60
#define	MDQUOTA_SIZE	'S'	/* Total size of all messages in maildir */
#define	MDQUOTA_BLOCKS	'B'	/* Total # of blocks for all messages in
				maildir -- NOT IMPLEMENTED */
#define	MDQUOTA_COUNT	'C'	/* Total number of messages in maildir */


/* bk: add domain limits functionality */
int domain_over_maildirquota(const char *userdir)
{
struct  stat    stat_buf;
char	domdir[MAX_PW_DIR];
char	*p;
char	domain[256];
unsigned long size = 0;
unsigned long maxsize = 0;
int	cnt = 0;
int	maxcnt = 0;
struct vlimits limits;

        if (fstat(0, &stat_buf) == 0 && S_ISREG(stat_buf.st_mode) &&
                stat_buf.st_size > 0)
        {

		/* locate the domain directory */
		p = maildir_to_email(userdir);
		if (p == NULL) return -1;

		p = strchr (p, '@');
		if (p == NULL) return -1;

		strcpy(domain, p + 1);

		/* get the domain quota */
		if (vget_limits(domain, &limits)) return 0;
		/* convert from MB to bytes */
		maxsize = limits.diskquota * 1024 * 1024;
		maxcnt = limits.maxmsgcount;

		/* only check the quota if one is set. */
		if ((maxsize == 0) && (maxcnt == 0)) return 0;

		if (vget_assign (domain, domdir, sizeof(domdir), NULL, NULL) == NULL)
			return -1;

		/* get the domain usage */
		if (readdomainquota(domdir, &size, &cnt)) return -1;

		/* check if either quota (size/count) would be exceeded */
		if (maxsize > 0 && (size + stat_buf.st_size) > maxsize) return 1;
		else if (maxcnt > 0 && cnt >= maxcnt) return 1;
        }

        return 0;
}

int readdomainquota(const char *dir, long *sizep, int *cntp)
{
int tries;
char	checkdir[256];
DIR	*dirp;
struct dirent *de;


	if (dir == NULL || sizep == NULL || cntp == NULL)
		return -1;

	*sizep = 0;
	*cntp = 0;

	dirp=opendir(dir);
	while (dirp && (de=readdir(dirp)) != 0)
	{
		if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
			continue;

#ifdef USERS_BIG_DIR
		if (strlen(de->d_name) == 1) {
			/* recursive call for hashed directory */
			snprintf (checkdir, sizeof(checkdir), "%s/%s", dir, de->d_name);
			if (readdomainquota (checkdir, sizep, cntp) == -1) {
				return -1;
			}
		} else
#endif
		{
			snprintf(checkdir, sizeof(checkdir), "%s/%s/Maildir/", dir, de->d_name);
			tries = 5;
			while (tries-- && readuserquota(checkdir, sizep, cntp))
			{
				if (errno != EAGAIN) return -1;
				sleep(1);
			}
			if (tries <= 0)
				return -1;
		}
	}
	if (dirp)
	{
#if	CLOSEDIR_VOID
		closedir(dirp);
#else
		if (closedir(dirp))
		{
			return (-1);
		}
#endif
	}

	return 0;
}

int wrapreaduserquota(const char* dir, off_t *sizep, int *cntp)
{
time_t	tm;
time_t	maxtime;
DIR	*dirp;
struct dirent *de;

	maxtime=0;

	if (countcurnew(dir, &maxtime, sizep, cntp))
	{
		return (-1);
	}

	dirp=opendir(dir);
	while (dirp && (de=readdir(dirp)) != 0)
	{
		if (countsubdir(dir, de->d_name, &maxtime, sizep, cntp))
		{
			closedir(dirp);
			return (-1);
		}
	}
	if (dirp)
	{
#if	CLOSEDIR_VOID
		closedir(dirp);
#else
		if (closedir(dirp))
		{
			return (-1);
		}
#endif
	}

	/* make sure nothing changed while calculating this... */
	tm=0;

	if (statcurnew(dir, &tm))
	{
		return (-1);
	}

	dirp=opendir(dir);
	while (dirp && (de=readdir(dirp)) != 0)
	{
		if (statsubdir(dir, de->d_name, &tm))
		{
			closedir(dirp);
			return (-1);
		}
	}
	if (dirp)
	{
#if	CLOSEDIR_VOID
		closedir(dirp);
#else
		if (closedir(dirp))
		{
			return (-1);
		}
#endif
	}

	if (tm != maxtime)	/* Race condition, someone changed something */
	{
		errno=EAGAIN;
		return (-1);
	}
	errno=0;

	return 0;
}
int readuserquota(const char* dir, long *sizep, int *cntp)
{
	int retval;
	off_t s;
	
	s = (off_t) *sizep;
	retval = wrapreaduserquota(dir, &s, cntp);
	*sizep = (long) s;
	return retval;
}

int user_over_maildirquota( const char *dir, const char *q)
{
struct  stat    stat_buf;
int     quotafd = -1;
int     ret_value = 0;

        if (fstat(0, &stat_buf) == 0 && S_ISREG(stat_buf.st_mode) &&
                stat_buf.st_size > 0 && *q)
        {
                ret_value = (maildir_checkquota(dir, &quotafd, q, stat_buf.st_size, 1)
                        && errno != EAGAIN);
        }

        if (quotafd != -1)       close(quotafd);
        return(ret_value);
}

/* Read the maildirsize file */

static int maildirsize_read(const char *filename,	/* The filename */
	int *fdptr,	/* Keep the file descriptor open */
	off_t *sizeptr,	/* Grand total of maildir size */
	unsigned *cntptr, /* Grand total of message count */
	unsigned *nlines, /* # of lines in maildirsize */
	struct stat *statptr)	/* The stats on maildirsize */
{
 char buf[5120];
 int f;
 char *p;
 unsigned l;
 int n;
 int first;

	if ((f=maildir_safeopen(filename, O_RDWR|O_APPEND, 0)) < 0)
		return (-1);
	p=buf;
	l=sizeof(buf);

	while (l)
	{
		n=read(f, p, l);
		if (n < 0)
		{
			close(f);
			return (-1);
		}
		if (n == 0)	break;
		p += n;
		l -= n;
	}
	if (l == 0 || fstat(f, statptr))	/* maildir too big */
	{
		close(f);
		return (-1);
	}

	*sizeptr=0;
	*cntptr=0;
	*nlines=0;
	*p=0;
	p=buf;
	first=1;
	while (*p)
	{
	long n=0;
	int c=0;
	char	*q=p;

		while (*p)
			if (*p++ == '\n')
			{
				p[-1]=0;
				break;
			}

		if (first)
		{
			first=0;
			continue;
		}
		sscanf(q, "%ld %d", &n, &c);
		*sizeptr += n;
		*cntptr += c;
		++ *nlines;
	}
	*fdptr=f;
	return (0);
}

static int qcalc(off_t s, unsigned n, const char *quota, int *percentage)
{
off_t i;
int	spercentage=0;
int	npercentage=0;

	errno=ENOSPC;
	while (quota && *quota)
	{
		int x=1;

		if (*quota < '0' || *quota > '9')
		{
			++quota;
			continue;
		}
		i=0;
		while (*quota >= '0' && *quota <= '9')
			i=i*10 + (*quota++ - '0');
		switch (*quota)	{
		default:
			if (i < s)
			{
				*percentage=100;
				return (-1);
			}

			/*
			** For huge quotas, over 20mb,
			** divide numerator & denominator by 1024 to prevent
			** an overflow when multiplying by 100
			*/

			x=1;
			if (i > 20000000) x=1024;

			spercentage = i ? (s/x) * 100 / (i/x):100;
			break;
		case 'C':

			if (i < n)
			{
				*percentage=100;
				return (-1);
			}

			/* Ditto */

			x=1;
			if (i > 20000000) x=1024;

			npercentage = i ? ((off_t)n/x) * 100 / (i/x):100;
			break;
		}
	}
	*percentage = spercentage > npercentage ? spercentage:npercentage;
	return (0);
}


static int maildir_checkquota(const char *dir,
	int *maildirsize_fdptr,
	const char *quota_type,
	long xtra_size,
	int xtra_cnt)
{
int	dummy;

	return (docheckquota(dir, maildirsize_fdptr, quota_type,
		xtra_size, xtra_cnt, &dummy));
}

int vmaildir_readquota(const char *dir, const char *quota_type)
{
int	percentage=0;
int	fd=-1;

	(void)docheckquota(dir, &fd, quota_type, 0, 0, &percentage);
	if (fd >= 0)
		close(fd);
	return (percentage);
}

static int docheckquota(const char *dir,
	int *maildirsize_fdptr,
	const char *quota_type,
	long xtra_size,
	int xtra_cnt,
	int *percentage)
{
char	*checkfolder=(char *)malloc(strlen(dir)+sizeof("/maildirfolder"));
char	*newmaildirsizename;
struct stat stat_buf;
int	maildirsize_fd = -1;
off_t	maildirsize_size;
unsigned maildirsize_cnt;
unsigned maildirsize_nlines;
int	n;
time_t	tm;
time_t	maxtime;
DIR	*dirp;
struct dirent *de;

	if (checkfolder == 0)	return (-1);
	*maildirsize_fdptr= -1;
	strcat(strcpy(checkfolder, dir), "/maildirfolder");
	if (stat(checkfolder, &stat_buf) == 0)	/* Go to parent */
	{
		strcat(strcpy(checkfolder, dir), "/..");
		n=docheckquota(checkfolder, maildirsize_fdptr,
			quota_type, xtra_size, xtra_cnt, percentage);
		free(checkfolder);
		return (n);
	}
	if (!quota_type || !*quota_type)	return (0);

	strcat(strcpy(checkfolder, dir), "/maildirsize");
	time(&tm);
	if (maildirsize_read(checkfolder, &maildirsize_fd,
		&maildirsize_size, &maildirsize_cnt,
		&maildirsize_nlines, &stat_buf) == 0)
	{
		n=qcalc(maildirsize_size+xtra_size, maildirsize_cnt+xtra_cnt,
			quota_type, percentage);

		if (n == 0)
		{
			free(checkfolder);
			*maildirsize_fdptr=maildirsize_fd;
			return (0);
		}
		close(maildirsize_fd);

		if (maildirsize_nlines == 1 && tm < stat_buf.st_mtime + 15*60)
			return (n);
	}

	/* rebuild the maildirsize file */

	maxtime=0;
	maildirsize_size=0;
	maildirsize_cnt=0;

	if (countcurnew(dir, &maxtime, &maildirsize_size, &maildirsize_cnt))
	{
		free(checkfolder);
		return (-1);
	}

	dirp=opendir(dir);
	while (dirp && (de=readdir(dirp)) != 0)
	{
		if (countsubdir(dir, de->d_name, &maxtime, &maildirsize_size,
			&maildirsize_cnt))
		{
			free(checkfolder);
			closedir(dirp);
			return (-1);
		}
	}
	if (dirp)
	{
#if	CLOSEDIR_VOID
		closedir(dirp);
#else
		if (closedir(dirp))
		{
			free(checkfolder);
			return (-1);
		}
#endif
	}

	newmaildirsizename=makenewmaildirsizename(dir, &maildirsize_fd);
	if (!newmaildirsizename)
	{
		free(checkfolder);
		return (-1);
	}

	*maildirsize_fdptr=maildirsize_fd;

	if (doaddquota(dir, maildirsize_fd, quota_type, maildirsize_size,
		maildirsize_cnt, 1))
	{
		unlink(newmaildirsizename);
		close(maildirsize_fd);
		*maildirsize_fdptr= -1;
		free(newmaildirsizename);
		free(checkfolder);
		return (-1);
	}

	strcat(strcpy(checkfolder, dir), "/maildirsize");

	if (rename(newmaildirsizename, checkfolder))
	{
		unlink(newmaildirsizename);
		close(maildirsize_fd);
		*maildirsize_fdptr= -1;
	}
	free(checkfolder);
	free(newmaildirsizename);

	tm=0;

	if (statcurnew(dir, &tm))
	{
		close(maildirsize_fd);
		*maildirsize_fdptr= -1;
		return (-1);
	}

	dirp=opendir(dir);
	while (dirp && (de=readdir(dirp)) != 0)
	{
		if (statsubdir(dir, de->d_name, &tm))
		{
			close(maildirsize_fd);
			*maildirsize_fdptr= -1;
			closedir(dirp);
			return (-1);
		}
	}
	if (dirp)
	{
#if	CLOSEDIR_VOID
		closedir(dirp);
#else
		if (closedir(dirp))
		{
			close(maildirsize_fd);
			*maildirsize_fdptr= -1;
			return (-1);
		}
#endif
	}

	if (tm != maxtime)	/* Race condition, someone changed something */
	{
		errno=EAGAIN;
		return (-1);
	}

	return (qcalc(maildirsize_size+xtra_size, maildirsize_cnt+xtra_cnt,
		quota_type, percentage));
}

int	maildir_addquota(const char *dir, int maildirsize_fd,
	const char *quota_type, long maildirsize_size, int maildirsize_cnt)
{
	if (!quota_type || !*quota_type)	return (0);
	return (doaddquota(dir, maildirsize_fd, quota_type, maildirsize_size,
			maildirsize_cnt, 0));
}

static int doaddquota(const char *dir, int maildirsize_fd,
	const char *quota_type, long maildirsize_size, int maildirsize_cnt,
	int isnew)
{
union	{
	char	buf[100];
	struct stat stat_buf;
	} u;				/* Scrooge */
char	*newname2=0;
char	*newmaildirsizename=0;
struct	iovec	iov[3];
int	niov;
struct	iovec	*p;
int	n;

	niov=0;
	if ( maildirsize_fd < 0)
	{
		newname2=(char *)malloc(strlen(dir)+sizeof("/maildirfolder"));
		if (!newname2)	return (-1);
		strcat(strcpy(newname2, dir), "/maildirfolder");
		if (stat(newname2, &u.stat_buf) == 0)
		{
			strcat(strcpy(newname2, dir), "/..");
			n=doaddquota(newname2, maildirsize_fd, quota_type,
					maildirsize_size, maildirsize_cnt,
					isnew);
			free(newname2);
			return (n);
		}

		strcat(strcpy(newname2, dir), "/maildirsize");

		if ((maildirsize_fd=maildir_safeopen(newname2,
			O_RDWR|O_APPEND, 0644)) < 0)
		{
			newmaildirsizename=makenewmaildirsizename(dir, &maildirsize_fd);
			if (!newmaildirsizename)
			{
				free(newname2);
				return (-1);
			}

			maildirsize_fd=maildir_safeopen(newmaildirsizename,
				O_CREAT|O_RDWR|O_APPEND, 0644);

			if (maildirsize_fd < 0)
			{
				free(newname2);
				return (-1);
			}
			isnew=1;
		}
	}

	if (isnew)
	{
		iov[0].iov_base=(void *)quota_type;
		iov[0].iov_len=strlen(quota_type);
		iov[1].iov_base="\n";
		iov[1].iov_len=1;
		niov=2;
	}


	sprintf(u.buf, "%ld %d\n", maildirsize_size, maildirsize_cnt);
	iov[niov].iov_base=u.buf;
	iov[niov].iov_len=strlen(u.buf);

	p=iov;
	++niov;
	n=0;
	while (niov)
	{
		if (n)
		{
			if (n < p->iov_len)
			{
				p->iov_base=
					((char *)p->iov_base + n);
				p->iov_len -= n;
			}
			else
			{
				n -= p->iov_len;
				++p;
				--niov;
				continue;
			}
		}

		n=writev( maildirsize_fd, p, niov);

		if (n <= 0)
		{
			if (newname2)
			{
				close(maildirsize_fd);
				free(newname2);
			}
			return (-1);
		}
	}
	if (newname2)
	{
		close(maildirsize_fd);

		if (newmaildirsizename)
		{
			rename(newmaildirsizename, newname2);
			free(newmaildirsizename);
		}
		free(newname2);
	}
	return (0);
}

/* New maildirsize is built in the tmp subdirectory */

static char *makenewmaildirsizename(const char *dir, int *fd)
{
char	hostname[256];
struct	stat stat_buf;
time_t	t;
char	*p;
int i;

	hostname[0]=0;
	hostname[sizeof(hostname)-1]=0;
	gethostname(hostname, sizeof(hostname)-1);
	p=(char *)malloc(strlen(dir)+strlen(hostname)+130);
	if (!p)	return (0);

        /* do not hang forever */
	for (i=0;i<3;++i)
	{
	char	tbuf[NUMBUFSIZE];
	char	pbuf[NUMBUFSIZE];

		time(&t);
		strcat(strcpy(p, dir), "/tmp/");
		sprintf(p+strlen(p), "%s.%s_NeWmAiLdIrSiZe.%s",
			str_time_t(t, tbuf),
			str_pid_t(getpid(), pbuf), hostname);

		if (stat( (const char *)p, &stat_buf) < 0 &&
			(*fd=maildir_safeopen(p,
				O_CREAT|O_RDWR|O_APPEND, 0644)) >= 0)
			break;
		usleep(100);
	}
	return (p);
}

static int statcurnew(const char *dir, time_t *maxtimestamp)
{
char	*p=(char *)malloc(strlen(dir)+5);
struct	stat	stat_buf;

	if (!p)	return (-1);
	strcat(strcpy(p, dir), "/cur");
	if ( stat(p, &stat_buf) == 0 && stat_buf.st_mtime > *maxtimestamp)
		*maxtimestamp=stat_buf.st_mtime;
	strcat(strcpy(p, dir), "/new");
	if ( stat(p, &stat_buf) == 0 && stat_buf.st_mtime > *maxtimestamp)
		*maxtimestamp=stat_buf.st_mtime;
	free(p);
	return (0);
}

static int statsubdir(const char *dir, const char *subdir, time_t *maxtime)
{
char	*p;
int	n;

	if ( *subdir != '.' || strcmp(subdir, ".") == 0 ||
		strcmp(subdir, "..") == 0 || strcmp(subdir, ".Trash") == 0)
		return (0);

	p=(char *)malloc(strlen(dir)+strlen(subdir)+2);
	if (!p)	return (-1);
	strcat(strcat(strcpy(p, dir), "/"), subdir);
	n=statcurnew(p, maxtime);
	free(p);
	return (n);
}

static int countcurnew(const char *dir, time_t *maxtime,
	off_t *sizep, unsigned *cntp)
{
char	*p=(char *)malloc(strlen(dir)+5);
int	n;

	if (!p)	return (-1);
	strcat(strcpy(p, dir), "/new");
	n=docount(p, maxtime, sizep, cntp);
	if (n == 0)
	{
		strcat(strcpy(p, dir), "/cur");
		n=docount(p, maxtime, sizep, cntp);
	}
	free(p);
	return (n);
}

static int countsubdir(const char *dir, const char *subdir, time_t *maxtime,
	off_t *sizep, unsigned *cntp)
{
char	*p;
int	n;

	if ( *subdir != '.' || strcmp(subdir, ".") == 0 ||
		strcmp(subdir, "..") == 0 || strcmp(subdir, ".Trash") == 0)
		return (0);

	p=(char *)malloc(strlen(dir)+strlen(subdir)+2);
	if (!p)	return (2);
	strcat(strcat(strcpy(p, dir), "/"), subdir);
	n=countcurnew(p, maxtime, sizep, cntp);
	free(p);
	return (n);
}

static int docount(const char *dir, time_t *dirstamp,
	off_t *sizep, unsigned *cntp)
{
struct	stat	stat_buf;
char	*p;
DIR	*dirp;
struct dirent *de;
unsigned long	s;

	if (stat(dir, &stat_buf))	return (0);	/* Ignore */
	if (stat_buf.st_mtime > *dirstamp)	*dirstamp=stat_buf.st_mtime;
	if ((dirp=opendir(dir)) == 0)	return (0);
	while ((de=readdir(dirp)) != 0)
	{
	const char *n=de->d_name;

		if (*n == '.')	continue;

		/* PATCH - do not count msgs marked as deleted */

		for ( ; *n; n++)
		{
			if (n[0] != ':' || n[1] != '2' ||
				n[2] != ',')	continue;
			n += 3;
			while (*n >= 'A' && *n <= 'Z')
			{
				if (*n == 'T')	break;
				++n;
			}
			break;
		}
		if (*n == 'T')	continue;
		n=de->d_name;


		if (maildir_parsequota(n, &s) == 0)
			stat_buf.st_size=s;
		else
		{
			p=(char *)malloc(strlen(dir)+strlen(n)+2);
			if (!p)
			{
				closedir(dirp);
				return (-1);
			}
			strcat(strcat(strcpy(p, dir), "/"), n);
			if (stat(p, &stat_buf))
			{
				free(p);
				continue;
			}
			free(p);
		}
		*sizep += stat_buf.st_size;
		++*cntp;
	}

#if	CLOSEDIR_VOID
	closedir(dirp);
#else
	if (closedir(dirp))
		return (-1);
#endif
	return (0);
}

static int maildir_safeopen(const char *path, int mode, int perm)
{
struct  stat    stat1, stat2;

int     fd=open(path, mode
#ifdef  O_NONBLOCK
                        | O_NONBLOCK
#else
                        | O_NDELAY
#endif
                                , perm);

        if (fd < 0)     return (fd);
        if (fcntl(fd, F_SETFL, (mode & O_APPEND)) || fstat(fd, &stat1)
            || lstat(path, &stat2))
        {
                close(fd);
                return (-1);
        }

        if (stat1.st_dev != stat2.st_dev || stat1.st_ino != stat2.st_ino)
        {
                close(fd);
                errno=ENOENT;
                return (-1);
        }

        return (fd);
}

static char *str_pid_t(pid_t t, char *arg)
{
char    buf[NUMBUFSIZE];
char    *p=buf+sizeof(buf)-1;

        *p=0;
        do
        {
                *--p= '0' + (t % 10);
                t=t / 10;
        } while(t);
        return (strcpy(arg, p));
}

static char *str_time_t(time_t t, char *arg)
{
char    buf[NUMBUFSIZE];
char    *p=buf+sizeof(buf)-1;

        *p=0;
        do
        {
                *--p= '0' + (t % 10);
                t=t / 10;
        } while(t);
        return (strcpy(arg, p));
}

static int maildir_parsequota(const char *n, unsigned long *s)
{
const char *o;
int     yes;

        if ((o=strrchr(n, '/')) == 0)   o=n;

        for (; *o; o++)
                if (*o == ':')  break;
        yes=0;
        for ( ; o >= n; --o)
        {
                if (*o == '/')  break;

                if (*o == ',' && o[1] == 'S' && o[2] == '=')
                {
                        yes=1;
                        o += 3;
                        break;
                }
        }
        if (yes)
        {
                *s=0;
                while (*o >= '0' && *o <= '9')
                        *s= *s*10 + (*o++ - '0');
                return (0);
        }
        return (-1);
}


syntax highlighted by Code2HTML, v. 0.9.1