1/*	$NetBSD$	*/
2
3/*++
4/* NAME
5/*	dsn_util 3
6/* SUMMARY
7/*	DSN status parsing routines
8/* SYNOPSIS
9/*	#include <dsn_util.h>
10/*
11/*	#define DSN_SIZE ...
12/*
13/*	typedef struct { ... } DSN_BUF;
14/*
15/*	typedef struct {
16/* .in +4
17/*		DSN_STAT dsn;		/* RFC 3463 status */
18/*		const char *text;	/* Free text */
19/* .in -4
20/*	} DSN_SPLIT;
21/*
22/*	DSN_SPLIT *dsn_split(dp, def_dsn, text)
23/*	DSN_SPLIT *dp;
24/*	const char *def_dsn;
25/*	const char *text;
26/*
27/*	char	*dsn_prepend(def_dsn, text)
28/*	const char *def_dsn;
29/*	const char *text;
30/*
31/*	size_t	dsn_valid(text)
32/*	const char *text;
33/*
34/*	void	DSN_UPDATE(dsn_buf, dsn, len)
35/*	DSN_BUF	dsn_buf;
36/*	const char *dsn;
37/*	size_t	len;
38/*
39/*	const char *DSN_CODE(dsn_buf)
40/*	DSN_BUF dsn_buf;
41/*
42/*	char	*DSN_CLASS(dsn_buf)
43/*	DSN_BUF	dsn_buf;
44/* DESCRIPTION
45/*	The functions in this module manipulate pairs of RFC 3463
46/*	status codes and descriptive free text.
47/*
48/*	dsn_split() splits text into an RFC 3463 status code and
49/*	descriptive free text.  When the text does not start with
50/*	a status code, the specified default status code is used
51/*	instead.  Whitespace before the optional status code or
52/*	text is skipped.  dsn_split() returns a copy of the RFC
53/*	3463 status code, and returns a pointer to (not copy of)
54/*	the remainder of the text.  The result value is the first
55/*	argument.
56/*
57/*	dsn_prepend() prepends the specified default RFC 3463 status
58/*	code to the specified text if no status code is present in
59/*	the text. This function produces the same result as calling
60/*	concatenate() with the results from dsn_split().  The result
61/*	should be passed to myfree(). Whitespace before the optional
62/*	status code or text is skipped.
63/*
64/*	dsn_valid() returns the length of the RFC 3463 status code
65/*	at the beginning of text, or zero. It does not skip initial
66/*	whitespace.
67/*
68/*	Arguments:
69/* .IP def_dsn
70/*	Null-terminated default RFC 3463 status code that will be
71/*	used when the free text does not start with one.
72/* .IP dp
73/*	Pointer to storage for copy of DSN status code, and for
74/*	pointer to free text.
75/* .IP dsn
76/*	Null-terminated RFC 3463 status code.
77/* .IP text
78/*	Null-terminated free text.
79/* SEE ALSO
80/*	msg(3) diagnostics interface
81/* DIAGNOSTICS
82/*	Panic: invalid default DSN code.
83/* LICENSE
84/* .ad
85/* .fi
86/*	The Secure Mailer license must be distributed with this software.
87/* AUTHOR(S)
88/*	Wietse Venema
89/*	IBM T.J. Watson Research
90/*	P.O. Box 704
91/*	Yorktown Heights, NY 10598, USA
92/*--*/
93
94/* System library. */
95
96#include <sys_defs.h>
97#include <stdarg.h>
98#include <string.h>
99#include <ctype.h>
100
101/* Utility library. */
102
103#include <msg.h>
104#include <mymalloc.h>
105#include <vstring.h>
106#include <stringops.h>
107
108/* Global library. */
109
110#include <dsn_util.h>
111
112/* dsn_valid - check RFC 3463 enhanced status code, return length or zero */
113
114size_t  dsn_valid(const char *text)
115{
116    const unsigned char *cp = (unsigned char *) text;
117    size_t  len;
118
119    /* First portion is one digit followed by dot. */
120    if ((cp[0] != '2' && cp[0] != '4' && cp[0] != '5') || cp[1] != '.')
121	return (0);
122
123    /* Second portion is 1-3 digits followed by dot. */
124    cp += 2;
125    if ((len = strspn((char *) cp, "0123456789")) < 1 || len > DSN_DIGS2
126	|| cp[len] != '.')
127	return (0);
128
129    /* Last portion is 1-3 digits followed by end-of-string or whitespace. */
130    cp += len + 1;
131    if ((len = strspn((char *) cp, "0123456789")) < 1 || len > DSN_DIGS3
132	|| (cp[len] != 0 && !ISSPACE(cp[len])))
133	return (0);
134
135    return (((char *) cp - text) + len);
136}
137
138/* dsn_split - split text into DSN and text */
139
140DSN_SPLIT *dsn_split(DSN_SPLIT *dp, const char *def_dsn, const char *text)
141{
142    const char *myname = "dsn_split";
143    const char *cp = text;
144    size_t  len;
145
146    /*
147     * Look for an optional RFC 3463 enhanced status code.
148     *
149     * XXX If we want to enforce that the first digit of the status code in the
150     * text matches the default status code, then pipe_command() needs to be
151     * changed. It currently auto-detects the reply code without knowing in
152     * advance if the result will start with '4' or '5'.
153     */
154    while (ISSPACE(*cp))
155	cp++;
156    if ((len = dsn_valid(cp)) > 0) {
157	strncpy(dp->dsn.data, cp, len);
158	dp->dsn.data[len] = 0;
159	cp += len + 1;
160    } else if ((len = dsn_valid(def_dsn)) > 0) {
161	strncpy(dp->dsn.data, def_dsn, len);
162	dp->dsn.data[len] = 0;
163    } else {
164	msg_panic("%s: bad default status \"%s\"", myname, def_dsn);
165    }
166
167    /*
168     * The remainder is free text.
169     */
170    while (ISSPACE(*cp))
171	cp++;
172    dp->text = cp;
173
174    return (dp);
175}
176
177/* dsn_prepend - prepend optional status to text, result on heap */
178
179char   *dsn_prepend(const char *def_dsn, const char *text)
180{
181    DSN_SPLIT dp;
182
183    dsn_split(&dp, def_dsn, text);
184    return (concatenate(DSN_STATUS(dp.dsn), " ", dp.text, (char *) 0));
185}
186