static char rcsid[] = "@(#)$Id: headers.c,v 1.37 2006/07/24 18:45:00 hurtta Exp $";
/******************************************************************************
* The Elm (ME+) Mail System - $Revision: 1.37 $ $State: Exp $
*
* Author: Kari Hurtta <hurtta+elm@posti.FMI.FI> (was hurtta+elm@ozone.FMI.FI)
*****************************************************************************/
#include "headers.h"
#include "hdr_imp.h"
#include "s_me.h"
DEBUG_VAR(Debug,__FILE__,"header");
static unsigned char * us_str P_((char *str));
static unsigned char * us_str(str)
char *str;
{
return (unsigned char *)str;
}
/* MIME header decode routines ......................................... */
static struct string * hdr_decode_from_text P_((header_list_ptr hdr,
int demime,
charset_t defcharset));
static struct string * hdr_decode_from_text(hdr,demime,defcharset)
header_list_ptr hdr;
int demime;
charset_t defcharset;
{
struct string * result = NULL;
result = hdr_to_string(HDR_TEXT,hdr->body,defcharset,demime);
return result;
}
static struct string * hdr_decode_from_raw P_((header_list_ptr hdr,
int demime,
charset_t defcharset));
static struct string * hdr_decode_from_raw(hdr,demime,defcharset)
header_list_ptr hdr;
int demime;
charset_t defcharset;
{
struct string * result = NULL;
result = new_string2(defcharset,us_str(hdr->body));
return result;
}
static struct string * decode_comment P_((char * token,int demime,
charset_t defcharset));
static struct string * decode_comment(token,demime,defcharset)
char * token;
int demime;
charset_t defcharset;
{
struct string *result = new_string(defcharset);
struct string *newresult = NULL;
struct string *R = NULL;
int k,j=0;
char buffer[1000];
for (k = 1; (token[k] != ')' || token[k+1]) &&
j < sizeof buffer -1; k++)
buffer[j++] = token[k];
buffer[j] = '\0';
R = hdr_to_string(HDR_COMMENT,buffer,defcharset,demime);
/* HACK */
add_streambyte_to_string(result,'(');
add_streambyte_to_string(R,')');
/* Now compine strings */
newresult = cat_strings(result,R,1);
free_string(&result);
result = newresult;
free_string(&R);
return result;
}
static struct string * hdr_decode_from_comment P_((header_list_ptr hdr,
int demime,
charset_t defcharset));
static struct string * hdr_decode_from_comment(hdr,demime,defcharset)
header_list_ptr hdr;
int demime;
charset_t defcharset;
{
struct string * result = new_string(defcharset);
int i;
char ** tokens = rfc822_tokenize(hdr->body);
for (i = 0; tokens[i]; i++) {
struct string * R = NULL, * newresult;
if ('(' == tokens[i][0]) {
R = decode_comment(tokens[i],demime,defcharset);
} else {
R = new_string2(defcharset,us_str(tokens[i]));
}
/* Now compine strings */
newresult = cat_strings(result,R,1);
free_string(&result);
result = newresult;
free_string(&R);
}
free_rfc822tokenized(tokens);
return result;
}
static struct string * hdr_decode_from_phrase P_((header_list_ptr hdr,
int demime,
charset_t defcharset));
static struct string * hdr_decode_from_phrase(hdr,demime,defcharset)
header_list_ptr hdr;
int demime;
charset_t defcharset;
{
struct string * result = new_string(defcharset);
int i;
char ** tokens = rfc822_tokenize(hdr->body);
for (i = 0; tokens[i]; i++) {
struct string * R = NULL, * newresult;
if ('\n' == tokens[i][0] &&
!tokens[i+1])
break;
if (whitespace(tokens[i][0]) || '\n' == tokens[i][0]) {
R = new_string(defcharset);
add_ascii_to_string(R,us_str(tokens[i]));
} else if ('(' == tokens[i][0]) {
R = decode_comment(tokens[i],demime,defcharset);
} else if ('<' == tokens[i][0]) {
int j;
/* Add mising space wetween phrase and < > */
if (i > 0)
add_ascii_to_string(result,us_str(" "));
R = new_string(defcharset);
add_ascii_to_string(R,us_str(tokens[i]));
for (j = i+1; tokens[j] && tokens[j][0] != '>'; j++) {
if ('(' == tokens[i][0]) {
struct string * R1 = decode_comment(tokens[j],
demime,defcharset);
/* Now combine strings */
newresult = cat_strings(result,R1,1);
free_string(&R);
R = newresult;
} else
add_ascii_to_string(R,us_str(tokens[j]));
i = j;
}
if (tokens[j]) {
add_ascii_to_string(R,us_str(tokens[j]));
i = j;
}
} else {
char * buffer = NULL;
int j;
int ph = give_dt_enumerate_as_int(&phrase_display_mode);
for (j = i; tokens[j]; j++) {
if ('(' == tokens[j][0])
break;
else if ('<' == tokens[j][0])
break;
else if ('\n' == tokens[j][0] && !tokens[j+1])
break;
i = j;
buffer = strmcat(buffer,tokens[j]);
}
R = hdr_to_string(HDR_PHRASE,buffer,defcharset,demime);
switch (ph) {
case 0:
break;
default:
/* We do not (backslash) quote displayed (only) text */
add_ascii_to_string(result,us_str("\""));
add_ascii_to_string(R,us_str("\""));
break;
}
free(buffer);
}
/* Now combine strings */
newresult = cat_strings(result,R,1);
free_string(&result);
result = newresult;
free_string(&R);
}
free_rfc822tokenized(tokens);
return result;
}
static struct string * hdr_decode_from_addr P_((header_list_ptr hdr,
int demime,
charset_t defcharset));
static struct string * hdr_decode_from_addr(hdr,demime,defcharset)
header_list_ptr hdr;
int demime;
charset_t defcharset;
{
struct string * result = new_string(defcharset);
char ** tokens = rfc822_tokenize(hdr->body);
int tokcount = 0;
int ptr = 0;
int bracket = 0;
int lastptr = 0;
while(tokens[tokcount])
tokcount++;
/* Remove LF from end of header */
if (tokcount > 0 &&
'\n' == tokens[tokcount-1][0])
tokcount--;
/* Walk through addressess ... */
for (ptr = 0; ptr < tokcount; ptr = lastptr) {
char **scanned = NULL;
struct string *comments = NULL;
struct string *newresult = NULL;
char tok = '\0';
look_special_tokens(tokens,":<>,;",ptr,&lastptr,demime,
defcharset,&comments,&scanned);
if (lastptr < tokcount) {
tok = tokens[lastptr][0];
DPRINT(Debug,25,(&Debug,
"hdr_decode_from_addr: [%d] token=%c (%s)\n",
lastptr,tok,tokens[lastptr]));
} else {
tok = '\0';
DPRINT(Debug,25,(&Debug,
"hdr_decode_from_addr: [%d] token=EOS\n",
lastptr));
}
if (ptr < lastptr) {
struct string *R = NULL;
if (bracket <= 0 &&
( ':' == tok ||
'<' == tok)) {
int ph = give_dt_enumerate_as_int(&phrase_display_mode);
DPRINT(Debug,25,(&Debug,
"hdr_decode_from_addr: Adding phrase\n"));
R = scanned_to_phrase(scanned,
demime,
defcharset);
switch (ph) {
case 0:
break;
default:
/* We do not (backslash) quote displayed (only) text */
add_ascii_to_string(result,us_str("\""));
add_ascii_to_string(R,us_str("\""));
break;
}
} else {
int z;
DPRINT(Debug,25,(&Debug,
"hdr_decode_from_addr: Adding address\n"));
R = new_string(defcharset);
for (z = 0; scanned[z]; z++) {
if ('\n' == scanned[z][0] && !scanned[z+1]) {
/* HACK: Ignore */
DPRINT(Debug,25,(&Debug,
"hdr_decode_from_addr: Ignoring ending LF\n"));
} else
add_ascii_to_string(R,us_str(scanned[z]));
}
}
/* Now compine strings */
newresult = cat_strings(result,R,1);
free_string(&result);
result = newresult;
free_string(&R);
} else {
DPRINT(Debug,25,(&Debug,
"hdr_decode_from_addr: (empty) \n"));
}
if (comments) {
DPRINT(Debug,25,(&Debug,
"hdr_decode_from_addr: Adding comments\n"));
add_ascii_to_string(result,us_str(" ("));
/* Now compine strings */
newresult = cat_strings(result,comments,1);
free_string(&result);
result = newresult;
add_ascii_to_string(result,us_str(")"));
}
if (lastptr < tokcount) {
switch (tok) {
case '<':
bracket++;
if (ptr < lastptr)
add_ascii_to_string(result,us_str(" "));
DPRINT(Debug,25,(&Debug,
"hdr_decode_from_addr: bracket=%d\n",
bracket));
break;
case '>':
bracket--;
DPRINT(Debug,25,(&Debug,
"hdr_decode_from_addr: bracket=%d\n",
bracket));
break;
}
DPRINT(Debug,25,(&Debug,
"hdr_decode_from_addr: Adding token %s\n",
tokens[lastptr]));
add_ascii_to_string(result,us_str(tokens[lastptr]));
lastptr++;
switch (tok) {
case ':':
case ',':
if (bracket <= 0)
add_ascii_to_string(result,us_str(" "));
break;
}
}
if (comments)
free_string(&comments);
if (scanned)
free_rfc822tokenized(scanned);
}
free_rfc822tokenized(tokens);
return result;
}
#ifdef ANSI_C
static hdr_add_to_mailing_hdr no_add_to_mail_hdr;
#endif
static int no_add_to_mail_hdr(headers,X,value,demime,defcharset,replace)
struct mailing_headers * headers;
header_ptr X;
CONST char *value;
int demime;
charset_t defcharset;
int replace;
{
return 0;
}
/* ------------------------------------------------------------------------ */
static struct header_info header_types[] = {
/* From STD 11 (RFC 822): */
{ "Subject", hdr_decode_from_text, add_subject_hdr },
{ "Comments", hdr_decode_from_text, no_add_to_mail_hdr },
{ "Return-path", hdr_decode_from_comment, add_env_from_hdr },
{ "Received", hdr_decode_from_comment, no_add_to_mail_hdr },
{ "Reply-To", hdr_decode_from_addr, add_replyto_hdr },
{ "From", hdr_decode_from_addr, add_from_hdr },
{ "Sender", hdr_decode_from_addr, no_add_to_mail_hdr },
{ "Resent-Reply-To", hdr_decode_from_addr, no_add_to_mail_hdr },
{ "Resent-From", hdr_decode_from_addr, no_add_to_mail_hdr },
{ "Resent-Sender", hdr_decode_from_addr, no_add_to_mail_hdr },
{ "Date", hdr_decode_from_comment, no_add_to_mail_hdr },
{ "Resent-Date", hdr_decode_from_comment, no_add_to_mail_hdr },
{ "To", hdr_decode_from_addr, add_to_hdr },
{ "Resent-To", hdr_decode_from_addr, no_add_to_mail_hdr },
{ "cc", hdr_decode_from_addr, add_cc_hdr },
{ "Resent-cc", hdr_decode_from_addr, no_add_to_mail_hdr },
{ "bcc", hdr_decode_from_addr, add_bcc_hdr },
{ "Resent-bcc", hdr_decode_from_addr, no_add_to_mail_hdr },
{ "Message-ID", hdr_decode_from_comment, no_add_to_mail_hdr },
{ "Resent-Message-ID", hdr_decode_from_comment, no_add_to_mail_hdr },
{ "In-Reply-To", hdr_decode_from_phrase, no_add_to_mail_hdr },
{ "References", hdr_decode_from_phrase, no_add_to_mail_hdr },
{ "Keywords", hdr_decode_from_phrase, no_add_to_mail_hdr },
{ "Encrypted", hdr_decode_from_comment, no_add_to_mail_hdr },
/* From MIME (RFC 1521) */
{ "MIME-Version", hdr_decode_from_comment, no_add_to_mail_hdr },
{ "Content-Type", hdr_decode_from_comment, no_add_to_mail_hdr },
{ "Content-Transfer-Encoding", hdr_decode_from_comment,
no_add_to_mail_hdr },
{ "Content-ID", hdr_decode_from_comment, no_add_to_mail_hdr },
{ "Content-Description", hdr_decode_from_text, no_add_to_mail_hdr },
/* From RFC 1806 */
{ "Content-Disposition", hdr_decode_from_comment, no_add_to_mail_hdr },
/* From RFC 1864 */
{ "Content-MD5", hdr_decode_from_raw, no_add_to_mail_hdr },
/* From RFC 2919 */
{ "List-Id", hdr_decode_from_phrase, no_add_to_mail_hdr },
/* From RFC 2369 */
{ "List-Help", hdr_decode_from_comment, no_add_to_mail_hdr },
{ "List-Subscribe", hdr_decode_from_comment, no_add_to_mail_hdr },
{ "List-Unsubscribe", hdr_decode_from_comment, no_add_to_mail_hdr },
{ "List-Post", hdr_decode_from_comment, no_add_to_mail_hdr },
{ "List-Owner", hdr_decode_from_comment, no_add_to_mail_hdr },
{ "List-Archive", hdr_decode_from_comment, no_add_to_mail_hdr },
/* mailbox format */
{ "Content-Length", hdr_decode_from_raw, no_add_to_mail_hdr },
{ "Status", hdr_decode_from_raw, no_add_to_mail_hdr },
/* Sendmail */
{ "Full-Name", hdr_decode_from_text, no_add_to_mail_hdr },
{ "Return-Receipt-To", hdr_decode_from_addr, no_add_to_mail_hdr },
{ "Auto-Submitted", hdr_decode_from_comment,
no_add_to_mail_hdr },
{ "Precedence", hdr_decode_from_raw, no_add_to_mail_hdr },
/* IDA Sendmail */
{ "X-Charset", hdr_decode_from_raw, no_add_to_mail_hdr },
{ "X-Char-Esc", hdr_decode_from_raw, no_add_to_mail_hdr },
/* Unknown source */
{ "Action", hdr_decode_from_raw, no_add_to_mail_hdr },
{ "Priority", hdr_decode_from_comment, no_add_to_mail_hdr },
{ "Expires", hdr_decode_from_comment, no_add_to_mail_hdr },
{ "Importance", hdr_decode_from_comment, no_add_to_mail_hdr },
{ "Sensitivity", hdr_decode_from_comment, no_add_to_mail_hdr },
/* Our non-standard headers */
{ "X-ELM-OSV", hdr_decode_from_comment, no_add_to_mail_hdr },
{ "X-Mailer", hdr_decode_from_text, no_add_to_mail_hdr },
{ "Content-Name", hdr_decode_from_text, no_add_to_mail_hdr },
/* Tailer */
{ NULL, hdr_decode_from_raw, no_add_to_mail_hdr }
};
static struct header_info * malloc_header_info P_((const char *name));
static struct header_info * malloc_header_info(name)
CONST char *name;
{
struct header_info *ret = safe_malloc(sizeof (*ret));
bzero((void *)ret, sizeof(*ret));
ret->header = safe_strdup(name);
if (strincmp(name,"X-",2) == 0) {
/* Default for user defined headers */
ret->hdr_decode_from_it =
hdr_decode_from_text;
ret->hdr_add_to_it = add_user_hdr;
} else {
/* We don't know it, right? */
ret->hdr_decode_from_it = hdr_decode_from_raw;
ret->hdr_add_to_it = no_add_to_mail_hdr;
}
return ret;
}
#define HDR_SCAN_magic 0xFC06
static struct header_scan_list {
unsigned short magic; /* HDR_SCAN_magic */
struct header_info * hdr;
struct header_scan_list * smaller;
struct header_scan_list * bigger;
} * find_header_1 P_((struct header_scan_list **root,
const char *name, int create_flag,
struct header_info *init));
static struct header_scan_list * find_header_1(root,name,create_flag,init)
struct header_scan_list **root;
const char *name;
int create_flag;
struct header_info *init;
{
struct header_scan_list *ptr = *root;
int r;
if (!ptr) {
if (!create_flag)
return NULL;
ptr = safe_malloc(sizeof(*ptr));
bzero((void *)ptr, sizeof(*ptr));
ptr->magic = HDR_SCAN_magic;
if (init)
ptr->hdr = init;
else
ptr->hdr = malloc_header_info(name);
ptr->smaller = NULL;
ptr->bigger = NULL;
*root = ptr;
return ptr;
}
if (ptr->magic != HDR_SCAN_magic)
panic("MBX VIEW PANIC",__FILE__,__LINE__,"find_header_1",
"Bad magic number",0);
r = istrcmp(name,ptr->hdr->header);
if (0 == r) {
return ptr;
} else if (0 < r) {
return find_header_1(& (ptr->bigger), name, create_flag,
init);
} else {
return find_header_1(& (ptr->smaller), name, create_flag,
init);
}
}
header_ptr find_header(name, create_flag)
CONST char *name;
int create_flag;
{
static struct header_scan_list * root = NULL;
struct header_scan_list * tmp;
if (! root) {
int i;
for (i = 0; header_types[i].header; i++) {
if (! find_header_1(&root, header_types[i].header,
1, & (header_types[i])))
panic("MBX VIEW PANIC",__FILE__,__LINE__,"find_header",
"Init failure",0);
}
}
tmp = find_header_1(&root, name, create_flag, NULL);
if (tmp) {
return tmp->hdr;
}
return NULL;
}
header_list_ptr locate_header(h,n)
header_list_ptr h;
header_ptr n; /* NOTE: n may be also NULL */
{
header_list_ptr walk;
for (walk = h;
walk != NULL;
walk = walk -> next_other_header) {
if (walk -> magic != HEADER_magic)
panic("HEADERS PANIC",__FILE__,__LINE__,"locate_header",
"Bad magic number",0);
if (n == walk -> header_name)
return walk;
}
return NULL;
}
void update_header_list(list,last1, name, value)
header_list_ptr *list;
header_list_ptr *last1;
CONST char * name;
CONST char * value;
{
header_list_ptr result = *list, last = *last1;
header_list_ptr item, last_this = NULL, walk;
if (result && !last) {
last = result;
DPRINT(Debug,9,(&Debug,
"update_header_list: Moving 'last' to end of list ... \n"));
while (last -> next_header)
last = last -> next_header;
}
item = (struct header_list *) safe_malloc(sizeof (struct header_list));
item -> header_name = find_header(name,1);
item -> next_header = NULL;
item -> next_this_header = NULL;
item -> next_other_header = NULL;
item -> body = safe_strdup(value);
item -> magic = HEADER_magic;
for (walk = result;
walk != NULL;
last_this = walk, walk = walk -> next_other_header) {
if (walk -> magic != HEADER_magic)
panic("HEADERS PANIC",__FILE__,__LINE__,"update_header_list",
"Bad magic number",0);
if (item -> header_name == walk -> header_name)
break;
}
if (!walk) {
DPRINT(Debug,9,(&Debug,
"update_header_list: name='%s': (%p) Not in chain!\n",
item -> header_name->header,
item -> header_name));
}
if (walk) {
while (walk->next_this_header != NULL) {
if (walk -> magic != HEADER_magic)
panic("HEADERS PANIC",__FILE__,__LINE__,"update_header_list",
"Bad magic number",0);
walk = walk->next_this_header;
}
walk->next_this_header = item;
DPRINT(Debug,9,(&Debug,
"update_header_list: name='%s' -- append next_this_header (%s)\n",
item->header_name->header,
walk->header_name->header));
} else if (last_this) {
last_this -> next_other_header = item;
DPRINT(Debug,9,(&Debug,
"update_header_list: name='%s' -- append next_other_header (%s)\n",
item->header_name->header,
last_this->header_name->header));
}
if (last) {
last -> next_header = item;
DPRINT(Debug,9,(&Debug,
"update_header_list: name='%s' -- append next_header (%s)\n",
item->header_name->header,
last->header_name->header));
} else {
result = item;
DPRINT(Debug,9,(&Debug,
"update_header_list: name='%s' -- head of next_header\n",
item->header_name->header));
}
last = item;
*list = result;
*last1 = last;
}
void delete_headers(hdr)
header_list_ptr *hdr;
{
header_list_ptr next = *hdr;
while(next) {
if (next -> magic != HEADER_magic)
panic("HEADERS PANIC",__FILE__,__LINE__,"delete_headers",
"Bad magic number",0);
(*hdr) = next;
next = next -> next_header;
if((*hdr) -> body) {
free((*hdr) -> body);
(*hdr) -> body = NULL;
}
(*hdr) -> next_header = NULL;
(*hdr) -> next_this_header = NULL;
(*hdr) -> next_other_header = NULL;
(*hdr) -> magic = 0;
free((void *)(*hdr));
}
(*hdr) = next;
}
/* Returns ponter to static storage */
CONST char * give_header_name(X)
header_ptr X;
{
return X->header;
}
int is_mailing_header(X)
header_ptr X;
{
return X->hdr_add_to_it != no_add_to_mail_hdr;
}
struct string * give_decoded_header(hdr,demime,defcharset)
header_list_ptr hdr;
int demime;
charset_t defcharset;
{
struct string * result = NULL;
if (hdr -> magic != HEADER_magic)
panic("HEADERS PANIC",__FILE__,__LINE__,"give_decoded_header",
"Bad magic number",0);
result = hdr->header_name->hdr_decode_from_it(hdr,demime,defcharset);
return result;
}
int add_to_mailing_header(headers,X,value,demime,defcharset,replace)
struct mailing_headers * headers;
header_ptr X;
CONST char *value;
int demime;
charset_t defcharset;
int replace;
{
int ret;
if (headers->magic != MAIL_HDR_magic)
panic("HEADERS PANIC",__FILE__,__LINE__,"add_to_mailing_header",
"Bad magic number",0);
ret = X->hdr_add_to_it(headers,X,value,demime,defcharset,replace);
return ret;
}
int read_header_line (fp, buf, size,flag)
FILE *fp;
char *buf;
int size;
int flag;
{
in_state_t state;
int result;
in_state_clear(&state,STATE_in_file);
set_in_state_file(fp,&state);
result = state_read_hdr_line(&state,buf,size,flag);
in_state_destroy(&state);
return result;
}
int state_read_hdr_line (s, buf, size,flag)
in_state_t *s;
char *buf;
int size;
int flag;
{
/* Read and unfold header line -- stores maximum size-1 bytes to buffer
* (plus \0). Also handle case when headers are eneded either CR LF or LF.
* Returns number of bytes stored. Always _read_ end of header
* (even when buffer fills). Returns 0 when reads empty line (only CR LF).
* That indicates end of headers.
*
* If flag & 1 (RHL_MARK_FOLDING) then folding is marked with '\n'
* (instead of ' ' and buffer ended with '\n' before '\0'
* If flag & 2 (RHL_CHECK_HEADER) then check that this was header line...
*/
int len = 0,c;
int col_seen = 0;
long pos = -1;
DPRINT(Debug,12,(&Debug,
"state_read_hdr_line: size=%d, flag=%d\n",size,flag));
size--; /* Place for \0 */
if ( (!in_state_seekable(s) ||
(pos = in_state_ftell(s)) < 0) &&
(flag & RHL_CHECK_HEADER)) {
DPRINT(Debug,5,(&Debug,
"state_read_hdr_line=0; not seekable or ftell failed!"));
buf[0] = '\0';
return 0;
}
#define PUTC(c) { if (len < size) buf[len++] = (c); }
while (EOF != (c = state_getc(s))) {
if ('\r' == c) { /* Is this CR LF sequence ? */
if (EOF == (c = state_getc(s)))
break;
if (c != '\n')
PUTC('\r');
}
if (c == '\n') { /* Read CR LF or LF, check folding */
if (!col_seen && len > 0 && (flag & RHL_CHECK_HEADER)) {
bad_header_line:
DPRINT(Debug,12,(&Debug,
"state_read_hdr_line: Not ':' seen. Not a header line!\n"));
if (0 != in_state_fseek(s,pos)) {
DPRINT(Debug,5,(&Debug,
"read_header_line: seek failed!\n"));
}
len = 0;
}
if (len == 0)
break; /* End of headers ! */
if (EOF == (c = state_getc(s)))
break;
if (c != ' ' && c != '\t') { /* Not a continuation line */
state_ungetc(c,s);
break;
}
/* CRLF LWSP sequence should be replaced with ' ' */
c = ' ';
if (flag & RHL_MARK_FOLDING)
c = '\n';
}
/* Space before ':' ? */
if ((' ' == c || '\t' == c) && !col_seen && (flag & RHL_CHECK_HEADER)) {
/* Skip to next ':' */
while (' ' == c || '\t' == c)
c = state_getc(s);
if (':' != c)
goto bad_header_line;
}
if (':' == c)
col_seen = 1;
PUTC(c);
}
if (flag & RHL_MARK_FOLDING) {
PUTC('\n');
}
#undef PUTC
buf[len] = 0;
DPRINT(Debug,12,(&Debug,
"state_read_hdr_line: len=%d, buf=%s\n",len,buf));
return len;
}
/*
* Local Variables:
* mode:c
* c-basic-offset:4
* buffer-file-coding-system: iso-8859-1
* End:
*/
syntax highlighted by Code2HTML, v. 0.9.1