1 /*        $NetBSD: dsn_util.c,v 1.1.1.1 2009/06/23 10:08:45 tron Exp $          */
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 
dsn_valid(const char * text)114 size_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 
dsn_split(DSN_SPLIT * dp,const char * def_dsn,const char * text)140 DSN_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 
dsn_prepend(const char * def_dsn,const char * text)179 char   *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