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