/*
* sma -- Sendmail log analyser
*
* Copyright (c) 2000 - 2003 Jarkko Turkulainen. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY JARKKO TURKULAINEN ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL JARKKO TURKULAINEN BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Date: 2003/04/27 15:43:33 $
*/
#include "sma.h"
#include <ctype.h>
/* Delivery status flags */
#define STATUS_SENT 1
#define STATUS_DEF 2
#define STATUS_USER 3
#define STATUS_QUEUE 4
#define STATUS_HOST 5
#define STATUS_SERV 6
#define STATUS_OTHER 7
/* Message ID flags: */
#define NOT_DELIVERED 0
#define FIRST_DELIVERY 1
#define DELIVERED 2
/* Message ID checking mode */
#define KEEP 0
#define REMOVE 1
/* Action flags */
#define ACTION_FROM 0
#define ACTION_TO 1
#define ACTION_SYSERR 2
#define ACTION_RULESET 3
#define ACTION_DAEMON 4
#define ACTION_DATAB 5
#define ACTION_DSN 6
#define ACTION_USER 7
#define ACTION_UNKNOWN 100
/* ACTION_SYSERR flags */
#define OTHER 0
#define HOPCOUNT 1
#define LOOP 2
#ifdef USE_REGEXP
int check_filter(const char *, regex_t *, const char *);
#else
int check_filter(const char *, const char *);
#endif
/*
* Do parsing for each input file
*/
void
parse(FILE *fp, const char *file) {
/* input buffer */
char line[2048];
/* plenty of pointers for line splitting: */
static const char *env_rec[128];
char *head[64];
char *info[64];
/* collecting */
char *action, *dsn, *idptr;
char *messageid = NULL;
const char *tmphost;
int logformat, hcount, icount;
int status = 0, idsize = 0, gf, pf = 0, actn = 0, sunos = 0;
int actflag, year, mon, day, hh, min, sec;
struct msgid *tmsgid = NULL;
/* general purpose: */
const char *p = NULL;
const char *s = NULL;
int i, k, lcount;
/* status and ruleset pointers */
const char *rulestr = NULL;
const char *relstr = NULL;
const char *statstr = NULL;
/* temporary pointer to host struct: */
struct host *hptr;
/* Start read file line by line: */
lcount = 0;
while (fgets(line, 2048, fp)) {
lcount++;
/* null-terminate: */
line[strlen(line)-1] = '\0';
memset(head, 0, 64);
memset(info, 0, 64);
/* split line initially with ',': */
info[0] = line;
for (i = 1, k = 0; line[k] != '\0'; ) {
if (line[k] == ',') {
line[k] = '\0';
k++;
if (line[k] == ' ')
k++;
info[i] = &line[k];
i++;
} else
k++;
}
icount = i;
/* split first info with ' ': */
head[0] = info[0];
for (i = 1, k = 0; info[0][k] != '\0'; ) {
if (info[0][k] == ' ') {
info[0][k] = '\0';
k++;
while(info[0][k] == ' ')
k++;
head[i] = &info[0][k];
i++;
} else
k++;
}
hcount = i;
/*
* Initial sanity check:
* Try to figure out the input file format
* Currently supported are syslog and NT
*
* Sendmail for NT
* Assume that the second field is "sendmail" ..
* This may be wrong, don't know NT version enough.
*/
idptr = NULL; action = NULL; actn = 0; dsn = NULL;
if (head[5] && !strncmp("sendmail", head[2], 7)) {
logformat = 1;
if (sscanf(head[1], "%d:%d:%d", &hh, &min, &sec) != 3) {
if (!qflag) fprintf(stderr,
"%s: not NT log, skipping line %d, "
"file %s\n", pname, lcount, file);
continue;
}
if (sscanf(head[0], "%d/%d/%d",&mon,&day,&year) != 3) {
if (!qflag) fprintf(stderr,
"%s: not NT log, skipping line %d, "
"file %s\n", pname, lcount, file);
continue;
}
/* We assume things fixed with NT log.. */
if (hcount >= 6) {
idptr = head[4];
action = head[5];
actn = 5; dsn = head[6];
mon--;
}
/*
* Normal syslog
*/
} else {
logformat = 0;
if (!head[5] || (Lchar && strncmp(Lchar, head[4],
strlen(Lchar)))) {
if (!qflag) fprintf(stderr,
"%s: not syslog, skipping line %d, "
"file %s\n", pname, lcount, file);
continue;
}
if (sscanf(head[2], "%d:%d:%d", &hh, &min, &sec) != 3) {
if (!qflag) fprintf(stderr,
"%s: not syslog, skipping line %d, "
"file %s\n", pname, lcount, file);
continue;
}
day = atoi(head[1]);
mon = conv_mon(head[0]);
/* SunOS 5.x with ID generation enabled: */
if (!strncmp("[ID", head[5], 3)) {
sunos = 3;
} else sunos = 0;
/*
* Now, we run through the first info-vector (head)
* and try to find useful keywords. If found, copy
* the pointers as idptr, action and dsn. Action
* flags are later set according to these keywords.
*/
for (k = 6+sunos; k < hcount; k++ ) {
/* "normal" delivery lines */
if (!strncmp("daemon", head[k], 6) ||
!strncmp("ruleset", head[k], 7) ||
!strncmp("to=", head[k], 3) ||
!strncmp("from=", head[k], 5) ||
!strncmp("SYSERR", head[k], 6) ||
!strncmp("database", head[k], 8)) {
actn = k;
idptr = head[k-1];
action = head[k];
break;
/* DSN, and other strange stuff: */
} else if (!strncmp("DSN", head[k], 3) ||
!strncmp("User", head[k], 4) ||
!strncmp("postmaster", head[k],10) ||
!strncmp("return", head[k], 6)) {
idptr = head[k-2];
action = head[k-1];
dsn = head[k];
break;
}
}
}
/*
* Permitted actions are defined here.
*
* We test the action, dsn, idptr and other fields for
* keywords and set the flag actflag according to a
* defined action. These actions are later performed
* with a switch(actflag) statement.
*
*/
actflag = ACTION_UNKNOWN;
if (idptr && !strncmp("ruleset", idptr, 7)) {
actflag = ACTION_RULESET;
} else if (action && !strncmp("ruleset", action, 7)) {
actflag = ACTION_RULESET;
} else if (action && !strncmp("from=", action, 5)) {
actflag = ACTION_FROM;
} else if (action && !strncmp("to=", action, 3)) {
actflag = ACTION_TO;
} else if (action && !strncmp("SYSERR", action, 6)) {
actflag = ACTION_SYSERR;
} else if (action && !strncmp("daemon", action, 6)) {
actflag = ACTION_DAEMON;
} else if (action && !strncmp("database", action, 8)) {
actflag = ACTION_DATAB;
} else if (dsn && (!strncmp("User", dsn, 4))) {
actflag = ACTION_USER;
} else if (dsn) {
actflag = ACTION_DSN;
} else continue;
/*
* Initialize the host structure
* If the hostname is not given, use syslog or
* (in case of NT) default string
*/
if (Hchar)
tmphost = Hchar;
else if (logformat)
tmphost = HOSTNAME;
else
tmphost = head[3];
hptr = init_host(tmphost);
/*
* Rejected before SMTP "MAIL FROM"
* This should be under switch(actflag) but isn't really
* a delivery, so we test it here..
*/
if (!strncmp("ruleset", idptr, 7)) {
hptr->rule++;
/* Find the reject-field: */
for (k = 1; k < icount; k++ ) {
if (!strncmp("relay=", info[k], 6))
relstr = get_relay_name(info[k]+6);
else if (!strncmp("reject=", info[k], 7))
rulestr = info[k]+7;
}
if (!rulestr) continue;
if (!relstr) relstr = BOUNCE_HOST;
if (rsnum) update_ruleset(hptr, rulestr, relstr);
continue;
}
/* Take the current time: */
hptr->cdtime = (time_t)conv_time(mon, day, hh, min, sec);
/* Check if we are allowed to proceed: */
if (sstime && (hptr->cdtime < sstime))
continue;
if (eetime && (hptr->cdtime > eetime))
continue;
/* is the host brand new? */
if (hptr->fhost == 0) {
/* Make the first time as ftime, cdtime and ltime: */
hptr->fhost = 1;
hptr->ftime = hptr->cdtime;
hptr->ltime = hptr->ftime;
}
/* Check ftime and ltime: */
if (hptr->cdtime > hptr->ltime)
hptr->ltime = hptr->cdtime;
if (hptr->cdtime < hptr->ftime)
hptr->ftime = hptr->cdtime;
/* Calculate weekday: */
hptr->lday = (localtime(&hptr->cdtime))->tm_wday;
/* Debug */
if (vflag && (actflag == ACTION_DSN || actflag == ACTION_FROM ||
actflag == ACTION_TO || actflag == ACTION_RULESET ||
actflag == ACTION_USER)) {
fprintf(stderr, "\n<< DEBUG >> file %s, line %d\n",
file, lcount);
fprintf(stderr, " msg id=%s\n", idptr);
}
/*
* Switch the possible actions
* DSN stuff
* These include DSN, postmaster notify, return to sender...
*/
switch(actflag) {
case ACTION_DSN:
if (vflag) fprintf(stderr, " DSN: %s\n", idptr);
/* Find the message ID */
tmsgid = get_msgid(hptr, idptr);
if (tmsgid == NULL)
continue;
/* Remove it */
remove_msgid(hptr, idptr);
/* This is a completely new mail */
p = bechar;
s = BOUNCE_HOST;
/*
* Sender Envelope and Relay filtering
*/
#ifdef USE_REGEXP
if (check_filter(sef, &csef, p) &&
check_filter(srf, &csrf, s)) {
#else
if (check_filter(sef, p) && check_filter(srf, s)) {
#endif
/* Message ID */
update_msgid(hptr, p, s, action, hh,
day, 1, 200, "<DSN>");
/* Nope, we are filtered: */
} else
continue;
break;
/* User unkown: */
case ACTION_USER:
if (vflag) fprintf(stderr,
" User unkown: %s\n", idptr);
update_omsgid(hptr, idptr, 0);
break;
/* "from" envelope: */
case ACTION_FROM:
p = NULL;
s = NULL;
/* check omsgidtab */
if (check_omsgid(hptr, idptr))
continue;
/* fetch the name: */
p = get_name(action+5);
if (vflag) fprintf(stderr, " from=%s\n", p);
/* follow on: */
for (k = 1; k < icount; k++) {
/* size of the message: */
if (!strncmp("size", info[k], 4)) {
hptr->lsize = atoi(info[k]+5);
/* msgid */
} else if (!strncmp("msgid", info[k], 5)) {
messageid = info[k]+6;
/* input relay */
} else if (!strncmp("relay", info[k], 5)) {
s = get_relay_name(info[k]+6);
/* no of recipients to follow */
} else if (!strncmp("nrcpts", info[k], 6))
idsize = atoi(info[k]+7);
}
/* Check relay */
if (!s) s = BOUNCE_HOST;
if (vflag) fprintf(stderr, " relay=%s\n", s);
/* Initialize messageid */
if (!messageid) messageid = "";
/*
* Sender Envelope and Relay filtering
*/
#ifdef USE_REGEXP
if (check_filter(sef, &csef, p) &&
check_filter(srf, &csrf, s)) {
#else
if (check_filter(sef, p) && check_filter(srf, s)) {
#endif
update_msgid(hptr, p, s, idptr, hh, day,
idsize, hptr->lsize, messageid);
/* Nope, we are filtered: */
} else
continue;
break;
/* "to" envelpe: */
case ACTION_TO:
p = NULL;
s = NULL;
/* Check msgidtab */
tmsgid = get_msgid(hptr, idptr);
if (tmsgid == NULL)
/* Bogus address or (from=) side filter */
continue;
hptr->lsize = tmsgid->size;
/* Check delivery flag: */
if (tmsgid->flag == NOT_DELIVERED)
/* This is a fresh one: */
tmsgid->flag = FIRST_DELIVERY;
/* fetch name and make entry: */
i = 0;
memset(env_rec, 0, (size_t)sizeof(char) * 128);
p = get_name(action+3);
env_rec[i] = p;
for (k = 1; k < icount; k++) {
if (!strncmp("delay", info[k], 5) ||
!strncmp("ctladdr", info[k], 7))
break;
else
/* more recipients: */
env_rec[++i] = get_name(info[k]);
}
/* now for the rest of the cases: */
for (; k < icount; k++ ) {
if (!strncmp("stat", info[k], 4)) {
statstr = info[k]+5;
if (!strncmp("Sent", info[k]+5, 4) ||
!strncmp("sent", info[k]+5, 4))
status = STATUS_SENT;
else if (!strncmp("Deferred",
info[k]+5, 8))
status = STATUS_DEF;
else if (!strncmp("User",
info[k]+5, 4))
status = STATUS_USER;
else if (!strncmp("Host",
info[k]+5, 4))
status = STATUS_HOST;
else if (!strncmp("queued",
info[k]+5, 6))
status = STATUS_QUEUE;
else if (!strncmp("Service",
info[k]+5, 7))
status = STATUS_SERV;
else
status = STATUS_OTHER;
} else if (!strncmp("relay", info[k], 5)) {
/* output relay */
s = get_relay_name(info[k]+6);
}
}
/* If no relay host, make as bouncehost: */
if (!s) s = BOUNCE_HOST;
if (vflag) fprintf(stderr, " relay=%s\n", s);
i = 0;
gf = 0;
while (env_rec[i]) {
if (vflag) fprintf(stderr,
" to=%s\n", env_rec[i]);
/*
* Recipient Envelope and Relay filtering
*/
#ifdef USE_REGEXP
if (check_filter(ref, &cref, env_rec[i]) &&
check_filter(rrf, &crrf, s)) {
gf = 1;
pf = 1;
#else
if (check_filter(ref, env_rec[i]) &&
check_filter(rrf, s)) {
gf = 1;
pf = 1;
#endif
} else pf = 0;
if (pf) {
/*
* Update output side stats if
* the format is ASCII or HTML:
*/
if (Oflag != FORMAT_CLOG) {
if (status == STATUS_SENT) {
update_out(hptr,
env_rec[i],
hptr->lsize);
if (epnum)
update_envpair(hptr,
tmsgid->sender,
env_rec[i],
hptr->lsize);
} else {
update_out(hptr,
env_rec[i], 0);
if (epnum)
update_envpair(hptr,
tmsgid->sender,
env_rec[i], 0);
}
hptr->onum++;
/* Custom log: */
} else if (!clsflag || (clsflag &&
(status == STATUS_SENT))) {
printclog(
hptr->cdtime,
tmphost,
(int)hptr->lsize,
tmsgid->msgid,
tmsgid->sender,
tmsgid->relay,
env_rec[i], s,
(status == STATUS_SENT)
? 1 : 0);
}
/* Checkout msgid */
check_msgid(hptr, idptr, KEEP);
}
i++;
}
/* Remove msgid if not ASCII or HTML: */
if (Oflag == FORMAT_CLOG) {
if (gf) {
if (status == STATUS_SENT)
check_msgid(hptr, idptr, REMOVE);
} else
/* Remove useless message ID: */
remove_msgid(hptr, idptr);
continue;
}
/*
* Update global stats only once per delivery
*/
if (gf) {
/* Update output relay: */
hptr->ronum++;
if (status == STATUS_SENT) {
update_rout(hptr, s, hptr->lsize);
/* update relay pairs */
if (rpnum) update_relpair(hptr,
tmsgid->relay,
s, hptr->lsize);
} else {
update_rout(hptr, s, 0);
if (rpnum) update_relpair(hptr,
tmsgid->relay, s, 0);
}
/* Update the global stats: */
hptr->gonum++;
hptr->ohh[hh]++;
hptr->odd[hptr->lday]++;
if (status == STATUS_SENT)
hptr->osize += hptr->lsize;
/* Check status flag: */
if (status == STATUS_SENT) {
hptr->sent++;
if (stnum) update_status(hptr, "Sent");
} else {
if (stnum) update_status(hptr, statstr);
switch(status) {
case STATUS_DEF:
hptr->defe++;
break;
case STATUS_HOST:
hptr->hunk++;
break;
case STATUS_USER:
hptr->uunk++;
break;
case STATUS_QUEUE:
hptr->queu++;
break;
case STATUS_SERV:
hptr->service++;
break;
case STATUS_OTHER:
hptr->other++;
break;
}
}
/*
* If this is a first delivery, update
* input side statistics:
*/
if (tmsgid->flag == FIRST_DELIVERY) {
/* Add envelope: */
hptr->inum++;
update_in(hptr, tmsgid->sender,
hptr->lsize);
/* Add relay: */
hptr->rinum++;
update_rin(hptr, tmsgid->relay,
hptr->lsize);
/* Update statistics: */
hptr->ihh[tmsgid->hh]++;
hptr->idd[hptr->lday]++;
hptr->size += hptr->lsize;
hptr->isize += hptr->lsize;
tmsgid->flag = DELIVERED;
}
/*
* If the message was sent, remove useless
* message ID from the chain
*/
if (status == STATUS_SENT)
check_msgid(hptr, idptr, REMOVE);
} else
/* Remove useless message ID: */
remove_msgid(hptr, idptr);
break;
/* SYSERR: */
case ACTION_SYSERR:
if (vflag) {
fprintf(stderr, "\n<< DEBUG >> file %s, line %d\n",
file, lcount);
fprintf(stderr, " SYSERR");
}
for (i = 0, k = actn; k < hcount; k++) {
/* Too many hops: */
if (!strncmp("Too", head[k], 3)) {
if (!head[k+2])
continue;
if (!strncmp("many", head[k+1], 4) &&
!strncmp("hops", head[k+2], 4))
i = HOPCOUNT;
/* Local configuration error: */
} else if (!strncmp("config", head[k], 6)) {
if (!head[k+1])
continue;
if (!strncmp("error:", head[k+1], 6))
i = LOOP;
}
/* Other SYSERR (i = 0)*/
}
switch(i) {
case HOPCOUNT:
if (vflag)
fprintf(stderr, " (hop count)\n");
hptr->hopc++;
break;
case LOOP:
if (vflag)
fprintf(stderr, " (mail loop)\n");
hptr->lcerror++;
break;
case OTHER:
if (vflag)
fprintf(stderr, " (other)\n");
hptr->oserror++;
break;
}
break;
/* ruleset based rejection: */
case ACTION_RULESET:
if (vflag) fprintf(stderr, " ruleset: %s\n", idptr);
update_omsgid(hptr, idptr, 0);
hptr->rule++;
/* Find the reject-field: */
for (k = 1; k < icount; k++ ) {
if (!strncmp("relay=", info[k], 6))
relstr = get_relay_name(info[k]+6);
else if (!strncmp("reject=", info[k], 7))
rulestr = info[k]+7;
}
if (!rulestr) continue;
if (!relstr) relstr = BOUNCE_HOST;
if (rsnum) update_ruleset(hptr, rulestr, relstr);
break;
/* sendmail daemon started: */
case ACTION_DAEMON:
if (vflag) {
fprintf(stderr, "\n<< DEBUG >> file %s, line %d\n",
file, lcount);
fprintf(stderr, " daemon restart\n");
}
hptr->dstart++;
break;
/* alias database rebuild: */
case ACTION_DATAB:
if (vflag) {
fprintf(stderr, "\n<< DEBUG >> file %s, line %d\n",
file, lcount);
fprintf(stderr, " alias table rebuilt\n");
}
hptr->alias++;
break;
default:
continue;
}
}
}
/*
* get_name - strip sender/recipient name/domain.
*/
const char *
get_name(char *name) {
char *tmp, *tmp2;
int i;
/* downcase everything if requested */
if (dcaddrflag) {
for ( i=0; name[i]; i++) {
name[i] = tolower(name[i]);
}
}
/* null-sender? */
if (!strncmp("<>", name, 2))
return("<>");
/* find the separating '@'-char: */
while (*name == '<' || *name == '"' || *name == '\'')
name++;
tmp = tmp2 = name;
while (*name++ != '@' && *name)
;
if (!*name)
name = tmp;
/* find the first char of the return pointer: */
if (dflag)
tmp = name;
else {
while (name != tmp2) {
if ((*name == '<') || (*name == '"') ||
(*name == '\'') || (*name == ' ')) {
name++;
break;
} else
name--;
}
tmp = name;
}
/* cut the string: */
while (*name) {
if ((*name == '>') || (*name == '"') ||
(*name == '\'') || (*name == ' ')) {
*name = '\0';
break;
} else
name++;
}
return tmp;
}
/* get relay name: */
const char *
get_relay_name(char *name) {
char *tmp;
tmp = name;
/*
* Search for space or (in case of identd)
* '@'-char. Make the return string point
* to next char.
*/
while (*name) {
if (*name == '@') {
tmp = name + 1;
break;
}
name++;
}
if (!*name)
name = tmp;
/*
* Do not take the IP-address in brackets
*/
while (*name) {
if ((*name == ' ') || (*name == ',')) {
*name = '\0';
break;
} else
name++;
}
if (*(name-1) == '.')
*(name-1) = '\0';
return tmp;
}
#ifdef USE_REGEXP
int
check_filter(const char *s, regex_t *rgp, const char *text) {
if (!s)
return(1);
else if (*s == '!') {
if (!regexec(rgp, text, 0, NULL, 0))
return(0);
else
return(1);
} else if (!regexec(rgp, text, 0, NULL, 0))
return(1);
else
return(0);
}
#else
int
check_filter(const char *s, const char *text) {
if (!s)
return(1);
else if (*s == '!') {
if (strstr(text, s+1))
return(0);
else
return(1);
} else if (strstr(text, s))
return(1);
else
return(0);
}
#endif
/*
* Printing routine for Custom Log format
*/
void
printclog(time_t sma_time, const char *host, int size, const char *id,
const char *from, const char *frelay, const char *to,
const char *trelay, int status) {
/* time struct: */
struct tm *tmptime;
/* Month tab */
const char *montab[] = {
"Jan", "Feb", "Mar", "Apr", "May",
"Jun", "Jul", "Aug", "Sep", "Oct",
"Nov", "Dec"
};
/* Temporary pointer */
const char *frm;
/* default format: */
if (!cfchar) {
fprintf(ofp, "%d %s %d %s %s %s %s %d\n",
(int)sma_time, host, size, from, frelay, to, trelay, status);
/* We have the format string */
} else {
/* Break down the time struct: */
tmptime = localtime(&sma_time);
for (frm = cfchar; *frm != '\0'; frm++)
switch (*frm) {
/*
* If the char after '%'-char is something we know
* print it, otherwise print literally
*/
case '%':
if (*(frm+1))
switch (*(frm+1)) {
case 'd':
fprintf(ofp,"%.2d",
tmptime->tm_mday);
frm++;
break;
case 'D':
fprintf(ofp,"%s",
stripn(asctime(tmptime)));
frm++;
break;
case 'f':
fprintf(ofp,"%s", from);
frm++;
break;
case 'F':
fprintf(ofp, "%s",
frelay);
frm++;
break;
case 'h':
fprintf(ofp,"%.2d",
tmptime->tm_hour);
frm++;
break;
case 'H':
fprintf(ofp,"%s", host);
frm++;
break;
case 'i':
fprintf(ofp,"%s", id);
frm++;
break;
case 'm':
fprintf(ofp,"%.2d",
tmptime->tm_mon+1);
frm++;
break;
case 'n':
fprintf(ofp,"%.2d",
tmptime->tm_min);
frm++;
break;
case 'M':
fprintf(ofp,"%s",
montab[tmptime->tm_mon]);
frm++;
break;
case 's':
fprintf(ofp,"%.2d",
tmptime->tm_sec);
frm++;
break;
case 'S':
fprintf(ofp,"%d",
status);
frm++;
break;
case 't':
fprintf(ofp,"%s", to);
frm++;
break;
case 'T':
fprintf(ofp,"%s",
trelay);
frm++;
break;
case 'U':
fprintf(ofp,"%d",
(int)sma_time);
frm++;
break;
case 'z':
fprintf(ofp,"%d", size);
frm++;
break;
case 'y':
fprintf(ofp,"%d",
tmptime->tm_year + 1900);
frm++;
break;
case '%':
fputc('%', ofp);
frm++;
break;
default:
fputc('%', ofp);
break;
}
break;
/* backslashed special characters: */
case '\\':
if (*(frm+1))
switch (*(frm+1)) {
case 'n':
fputc('\n', ofp);
frm++;
break;
case 't':
fputc('\t', ofp);
frm++;
break;
case '\\':
fputc('\\', ofp);
frm++;
break;
default:
fputc(*(frm+1), ofp);
frm++;
break;
}
break;
/*
* If not flag or any other special char, copy the
* format string character to stream:
*/
default:
fputc(*frm, ofp);
break;
}
/* End line always with newline: */
fputc('\n', ofp);
/* Flush output stream */
fflush(ofp);
}
}
syntax highlighted by Code2HTML, v. 0.9.1