Home | History | Annotate | Line # | Download | only in global
      1 /*	$NetBSD: dsn_util.c,v 1.2 2026/05/09 18:49:16 christos 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 
    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 
    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;
    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 
    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