1171802Sdelphij/*	$NetBSD$	*/
2170808Sdelphij
3182739Sdelphij/*++
4171802Sdelphij/* NAME
5170808Sdelphij/*	dsn_util 3
6170808Sdelphij/* SUMMARY
7170808Sdelphij/*	DSN status parsing routines
8170808Sdelphij/* SYNOPSIS
9170808Sdelphij/*	#include <dsn_util.h>
10170808Sdelphij/*
11170808Sdelphij/*	#define DSN_SIZE ...
12170808Sdelphij/*
13170808Sdelphij/*	typedef struct { ... } DSN_BUF;
14170808Sdelphij/*
15170808Sdelphij/*	typedef struct {
16170808Sdelphij/* .in +4
17170808Sdelphij/*		DSN_STAT dsn;		/* RFC 3463 status */
18170808Sdelphij/*		const char *text;	/* Free text */
19170808Sdelphij/* .in -4
20170808Sdelphij/*	} DSN_SPLIT;
21170808Sdelphij/*
22170808Sdelphij/*	DSN_SPLIT *dsn_split(dp, def_dsn, text)
23170808Sdelphij/*	DSN_SPLIT *dp;
24170808Sdelphij/*	const char *def_dsn;
25170808Sdelphij/*	const char *text;
26170808Sdelphij/*
27170808Sdelphij/*	char	*dsn_prepend(def_dsn, text)
28170808Sdelphij/*	const char *def_dsn;
29170808Sdelphij/*	const char *text;
30170808Sdelphij/*
31170808Sdelphij/*	size_t	dsn_valid(text)
32170808Sdelphij/*	const char *text;
33170808Sdelphij/*
34170808Sdelphij/*	void	DSN_UPDATE(dsn_buf, dsn, len)
35170808Sdelphij/*	DSN_BUF	dsn_buf;
36170808Sdelphij/*	const char *dsn;
37170808Sdelphij/*	size_t	len;
38170808Sdelphij/*
39170808Sdelphij/*	const char *DSN_CODE(dsn_buf)
40170808Sdelphij/*	DSN_BUF dsn_buf;
41170808Sdelphij/*
42170808Sdelphij/*	char	*DSN_CLASS(dsn_buf)
43170808Sdelphij/*	DSN_BUF	dsn_buf;
44170808Sdelphij/* DESCRIPTION
45170808Sdelphij/*	The functions in this module manipulate pairs of RFC 3463
46170808Sdelphij/*	status codes and descriptive free text.
47170808Sdelphij/*
48245115Sgleb/*	dsn_split() splits text into an RFC 3463 status code and
49170808Sdelphij/*	descriptive free text.  When the text does not start with
50170808Sdelphij/*	a status code, the specified default status code is used
51170808Sdelphij/*	instead.  Whitespace before the optional status code or
52170808Sdelphij/*	text is skipped.  dsn_split() returns a copy of the RFC
53171087Sdelphij/*	3463 status code, and returns a pointer to (not copy of)
54170808Sdelphij/*	the remainder of the text.  The result value is the first
55170808Sdelphij/*	argument.
56170808Sdelphij/*
57170808Sdelphij/*	dsn_prepend() prepends the specified default RFC 3463 status
58245115Sgleb/*	code to the specified text if no status code is present in
59245115Sgleb/*	the text. This function produces the same result as calling
60245115Sgleb/*	concatenate() with the results from dsn_split().  The result
61170808Sdelphij/*	should be passed to myfree(). Whitespace before the optional
62245115Sgleb/*	status code or text is skipped.
63245115Sgleb/*
64245115Sgleb/*	dsn_valid() returns the length of the RFC 3463 status code
65245115Sgleb/*	at the beginning of text, or zero. It does not skip initial
66245115Sgleb/*	whitespace.
67245115Sgleb/*
68245115Sgleb/*	Arguments:
69245115Sgleb/* .IP def_dsn
70245115Sgleb/*	Null-terminated default RFC 3463 status code that will be
71170808Sdelphij/*	used when the free text does not start with one.
72245115Sgleb/* .IP dp
73245115Sgleb/*	Pointer to storage for copy of DSN status code, and for
74245115Sgleb/*	pointer to free text.
75245115Sgleb/* .IP dsn
76245115Sgleb/*	Null-terminated RFC 3463 status code.
77245115Sgleb/* .IP text
78170808Sdelphij/*	Null-terminated free text.
79245115Sgleb/* SEE ALSO
80245115Sgleb/*	msg(3) diagnostics interface
81245115Sgleb/* DIAGNOSTICS
82170808Sdelphij/*	Panic: invalid default DSN code.
83211598Sed/* LICENSE
84211598Sed/* .ad
85170808Sdelphij/* .fi
86245115Sgleb/*	The Secure Mailer license must be distributed with this software.
87245115Sgleb/* AUTHOR(S)
88245115Sgleb/*	Wietse Venema
89245115Sgleb/*	IBM T.J. Watson Research
90245115Sgleb/*	P.O. Box 704
91245115Sgleb/*	Yorktown Heights, NY 10598, USA
92245115Sgleb/*--*/
93245115Sgleb
94245115Sgleb/* System library. */
95170808Sdelphij
96170808Sdelphij#include <sys_defs.h>
97245115Sgleb#include <stdarg.h>
98170808Sdelphij#include <string.h>
99170808Sdelphij#include <ctype.h>
100245115Sgleb
101170808Sdelphij/* Utility library. */
102170808Sdelphij
103245115Sgleb#include <msg.h>
104170808Sdelphij#include <mymalloc.h>
105170808Sdelphij#include <vstring.h>
106170808Sdelphij#include <stringops.h>
107170808Sdelphij
108170808Sdelphij/* Global library. */
109245115Sgleb
110170808Sdelphij#include <dsn_util.h>
111171802Sdelphij
112171802Sdelphij/* dsn_valid - check RFC 3463 enhanced status code, return length or zero */
113245115Sgleb
114245115Sglebsize_t  dsn_valid(const char *text)
115171802Sdelphij{
116171802Sdelphij    const unsigned char *cp = (unsigned char *) text;
117245115Sgleb    size_t  len;
118245115Sgleb
119171802Sdelphij    /* First portion is one digit followed by dot. */
120245115Sgleb    if ((cp[0] != '2' && cp[0] != '4' && cp[0] != '5') || cp[1] != '.')
121245115Sgleb	return (0);
122170808Sdelphij
123245115Sgleb    /* Second portion is 1-3 digits followed by dot. */
124245115Sgleb    cp += 2;
125245115Sgleb    if ((len = strspn((char *) cp, "0123456789")) < 1 || len > DSN_DIGS2
126245115Sgleb	|| cp[len] != '.')
127245115Sgleb	return (0);
128245115Sgleb
129245115Sgleb    /* Last portion is 1-3 digits followed by end-of-string or whitespace. */
130245115Sgleb    cp += len + 1;
131245115Sgleb    if ((len = strspn((char *) cp, "0123456789")) < 1 || len > DSN_DIGS3
132245115Sgleb	|| (cp[len] != 0 && !ISSPACE(cp[len])))
133171802Sdelphij	return (0);
134170808Sdelphij
135170808Sdelphij    return (((char *) cp - text) + len);
136170808Sdelphij}
137170808Sdelphij
138170808Sdelphij/* dsn_split - split text into DSN and text */
139170808Sdelphij
140170808SdelphijDSN_SPLIT *dsn_split(DSN_SPLIT *dp, const char *def_dsn, const char *text)
141170808Sdelphij{
142170808Sdelphij    const char *myname = "dsn_split";
143170808Sdelphij    const char *cp = text;
144170808Sdelphij    size_t  len;
145170808Sdelphij
146170808Sdelphij    /*
147170808Sdelphij     * Look for an optional RFC 3463 enhanced status code.
148170808Sdelphij     *
149170808Sdelphij     * XXX If we want to enforce that the first digit of the status code in the
150170808Sdelphij     * text matches the default status code, then pipe_command() needs to be
151170808Sdelphij     * changed. It currently auto-detects the reply code without knowing in
152170808Sdelphij     * advance if the result will start with '4' or '5'.
153170808Sdelphij     */
154170808Sdelphij    while (ISSPACE(*cp))
155170808Sdelphij	cp++;
156170808Sdelphij    if ((len = dsn_valid(cp)) > 0) {
157170808Sdelphij	strncpy(dp->dsn.data, cp, len);
158170808Sdelphij	dp->dsn.data[len] = 0;
159170808Sdelphij	cp += len + 1;
160170808Sdelphij    } else if ((len = dsn_valid(def_dsn)) > 0) {
161170808Sdelphij	strncpy(dp->dsn.data, def_dsn, len);
162170808Sdelphij	dp->dsn.data[len] = 0;
163170808Sdelphij    } else {
164170808Sdelphij	msg_panic("%s: bad default status \"%s\"", myname, def_dsn);
165170808Sdelphij    }
166170808Sdelphij
167170808Sdelphij    /*
168170808Sdelphij     * The remainder is free text.
169170808Sdelphij     */
170170808Sdelphij    while (ISSPACE(*cp))
171170808Sdelphij	cp++;
172170808Sdelphij    dp->text = cp;
173170808Sdelphij
174170808Sdelphij    return (dp);
175170808Sdelphij}
176170808Sdelphij
177170808Sdelphij/* dsn_prepend - prepend optional status to text, result on heap */
178248597Spjd
179170808Sdelphijchar   *dsn_prepend(const char *def_dsn, const char *text)
180170808Sdelphij{
181170808Sdelphij    DSN_SPLIT dp;
182170808Sdelphij
183170808Sdelphij    dsn_split(&dp, def_dsn, text);
184170808Sdelphij    return (concatenate(DSN_STATUS(dp.dsn), " ", dp.text, (char *) 0));
185170808Sdelphij}
186170808Sdelphij