1/* See LICENSE file for copyright and license details. */
2
3#include <sys/stat.h>
4
5#include <errno.h>
6#include <stdio.h>
7#include <stdlib.h>
8#include <string.h>
9
10#include "util.h"
11
12static int mflag = 0;
13static int oflag = 0;
14
15static FILE *
16parsefile(const char *fname)
17{
18 struct stat st;
19 int ret;
20
21 if (!strcmp(fname, "/dev/stdout") || !strcmp(fname, "-"))
22 return stdout;
23 ret = lstat(fname, &st);
24 /* if it is a new file, try to open it */
25 if (ret < 0 && errno == ENOENT)
26 goto tropen;
27 if (ret < 0) {
28 weprintf("lstat %s:", fname);
29 return NULL;
30 }
31 if (!S_ISREG(st.st_mode)) {
32 weprintf(
33 "for safety uudecode operates only on regular files "
34 "and /dev/stdout\n"
35 );
36 return NULL;
37 }
38tropen:
39 return fopen(fname, "w");
40}
41
42static void
43parseheader(FILE *fp, const char *s, char **header, mode_t *mode, char **fname)
44{
45 static char bufs[PATH_MAX + 18]; /* len header + mode + maxname */
46 char *p, *q;
47 size_t n;
48
49 if (!fgets(bufs, sizeof(bufs), fp))
50 if (ferror(fp))
51 eprintf("%s: read error:", s);
52 if (bufs[0] == '\0' || feof(fp))
53 eprintf("empty or nil header string\n");
54 if (!(p = strchr(bufs, '\n')))
55 eprintf(
56 "header string too long or non-newline terminated "
57 "file\n"
58 );
59 p = bufs;
60 if (!(q = strchr(p, ' ')))
61 eprintf("malformed mode string in header, expected ' '\n");
62 *header = bufs;
63 *q++ = '\0';
64 p = q;
65 /* now header should be null terminated, q points to mode */
66 if (!(q = strchr(p, ' ')))
67 eprintf("malformed mode string in header, expected ' '\n");
68 *q++ = '\0';
69 /* now mode should be null terminated, q points to fname */
70 *mode = parsemode(p, *mode, 0);
71 n = strlen(q);
72 while (n > 0 && (q[n - 1] == '\n' || q[n - 1] == '\r'))
73 q[--n] = '\0';
74 if (n > 0)
75 *fname = q;
76 else
77 eprintf("header string does not contain output file\n");
78}
79
80static const char b64dt[] = {
81 -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -2, -2, -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
82 -1, -1, -1, -1, -1, -1, -1, -1, -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
83 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6,
84 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
85 -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
86 49, 50, 51, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
87 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
88 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
89 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
90 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
91 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
92};
93
94static void
95uudecodeb64(FILE *fp, FILE *outfp)
96{
97 char bufb[60], *pb;
98 char out[45], *po;
99 size_t n;
100 int b = 0, e, t = -1, l = 1;
101 unsigned char b24[3] = {0, 0, 0};
102
103 while ((n = fread(bufb, 1, sizeof(bufb), fp))) {
104 for (pb = bufb, po = out; pb < bufb + n; pb++) {
105 if (*pb == '\n') {
106 l++;
107 continue;
108 } else if (*pb == '=') {
109 switch (b) {
110 case 0:
111 /* expected '=' remaining
112 * including footer */
113 if (--t) {
114 fwrite(out, 1, (po - out), outfp);
115 return;
116 }
117 continue;
118 case 1:
119 eprintf(
120 "%d: unexpected \"=\""
121 "appeared\n",
122 l
123 );
124 /* fallthrough */
125 case 2:
126 *po++ = b24[0];
127 b = 0;
128 t = 5; /* expect 5 '=' */
129 continue;
130 case 3:
131 *po++ = b24[0];
132 *po++ = b24[1];
133 b = 0;
134 t = 6; /* expect 6 '=' */
135 continue;
136 }
137 } else if ((e = b64dt[(int)*pb]) == -1)
138 eprintf("%d: invalid byte \"%c\"\n", l, *pb);
139 else if (e == -2) /* whitespace */
140 continue;
141 else if (t > 0) /* state is parsing pad/footer */
142 eprintf(
143 "%d: invalid byte \"%c\""
144 " after padding\n",
145 l,
146 *pb
147 );
148 switch (b) { /* decode next base64 chr based on state */
149 case 0:
150 b24[0] |= e << 2;
151 break;
152 case 1:
153 b24[0] |= (e >> 4) & 0x3;
154 b24[1] |= (e & 0xf) << 4;
155 break;
156 case 2:
157 b24[1] |= (e >> 2) & 0xf;
158 b24[2] |= (e & 0x3) << 6;
159 break;
160 case 3:
161 b24[2] |= e;
162 break;
163 }
164 if (++b == 4) { /* complete decoding an octet */
165 *po++ = b24[0];
166 *po++ = b24[1];
167 *po++ = b24[2];
168 b24[0] = b24[1] = b24[2] = 0;
169 b = 0;
170 }
171 }
172 fwrite(out, 1, (po - out), outfp);
173 }
174 eprintf("%d: invalid uudecode footer \"====\" not found\n", l);
175}
176
177static void
178uudecode(FILE *fp, FILE *outfp)
179{
180 char *bufb = NULL, *p;
181 size_t n = 0;
182 ssize_t len;
183 int ch, i;
184
185#define DEC(c) (((c) - ' ') & 077) /* single character decode */
186#define IS_DEC(c) ((((c) - ' ') >= 0) && (((c) - ' ') <= 077 + 1))
187#define OUT_OF_RANGE(c) eprintf("character %c out of range: [%d-%d]\n", (c), 1 + ' ', 077 + ' ' + 1)
188
189 while ((len = getline(&bufb, &n, fp)) > 0) {
190 p = bufb;
191 /* trim newlines */
192 if (!len || bufb[len - 1] != '\n')
193 eprintf("no newline found, aborting\n");
194 bufb[len - 1] = '\0';
195
196 /* check for last line */
197 if ((i = DEC(*p)) <= 0)
198 break;
199 for (++p; i > 0; p += 4, i -= 3) {
200 if (i >= 3) {
201 if (!(IS_DEC(*p) && IS_DEC(*(p + 1)) && IS_DEC(*(p + 2)) && IS_DEC(*(p + 3))))
202 OUT_OF_RANGE(*p);
203
204 ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
205 putc(ch, outfp);
206 ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
207 putc(ch, outfp);
208 ch = DEC(p[2]) << 6 | DEC(p[3]);
209 putc(ch, outfp);
210 } else {
211 if (i >= 1) {
212 if (!(IS_DEC(*p) && IS_DEC(*(p + 1))))
213 OUT_OF_RANGE(*p);
214
215 ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
216 putc(ch, outfp);
217 }
218 if (i >= 2) {
219 if (!(IS_DEC(*(p + 1)) && IS_DEC(*(p + 2))))
220 OUT_OF_RANGE(*p);
221
222 ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
223 putc(ch, outfp);
224 }
225 }
226 }
227 if (ferror(fp))
228 eprintf("read error:");
229 }
230 /* check for end or fail */
231 if ((len = getline(&bufb, &n, fp)) < 0)
232 eprintf("getline:");
233 if (len < 3 || strncmp(bufb, "end", 3) || bufb[3] != '\n')
234 eprintf("invalid uudecode footer \"end\" not found\n");
235 free(bufb);
236}
237
238static void
239usage(void)
240{
241 eprintf("usage: %s [-m] [-o output] [file]\n", argv0);
242}
243
244// ?man uudecode: decode uuencoded file
245// ?man arguments: file
246// ?man decode a file created by uuencode
247int
248main(int argc, char *argv[])
249{
250 FILE *fp = NULL, *nfp = NULL;
251 mode_t mode = 0;
252 int ret = 0;
253 char *fname, *header, *ifname, *ofname = NULL;
254 void (*d)(FILE *, FILE *) = NULL;
255
256 ARGBEGIN
257 {
258 // ?man -m: specify mode or limit
259 case 'm':
260 mflag = 1; /* accepted but unused (autodetect file type) */
261 break;
262 // ?man -o:str: specify output file
263 case 'o':
264 oflag = 1;
265 ofname = EARGF(usage());
266 break;
267 default:
268 usage();
269 }
270 ARGEND
271
272 if (argc > 1)
273 usage();
274
275 if (!argc || !strcmp(argv[0], "-")) {
276 fp = stdin;
277 ifname = "<stdin>";
278 } else {
279 if (!(fp = fopen(argv[0], "r")))
280 eprintf("fopen %s:", argv[0]);
281 ifname = argv[0];
282 }
283
284 parseheader(fp, ifname, &header, &mode, &fname);
285
286 if (!strncmp(header, "begin", sizeof("begin")))
287 d = uudecode;
288 else if (!strncmp(header, "begin-base64", sizeof("begin-base64")))
289 d = uudecodeb64;
290 else
291 eprintf("unknown header %s:", header);
292
293 if (oflag)
294 fname = ofname;
295 if (!(nfp = parsefile(fname)))
296 eprintf("fopen %s:", fname);
297
298 d(fp, nfp);
299
300 if (nfp != stdout && chmod(fname, mode) < 0)
301 eprintf("chmod %s:", fname);
302
303 ret |= fshut(fp, (fp == stdin) ? "<stdin>" : argv[0]);
304 ret |= fshut(nfp, (nfp == stdout) ? "<stdout>" : fname);
305
306 return ret;
307}