This is uudecode.c in view mode; [Download] [Up]
/* Copyright (c) 1995 John E. Davis (davis@space.mit.edu)
* All rights reserved.
*/
#include "config.h"
#include "features.h"
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#ifdef HAVE_STDLIB_H
# include <stdlib.h>
#endif
#define MAX_ARTICLE_LINE_LEN 4096
#include "uudecode.h"
#ifdef STANDALONE
# define SLRN_ERROR(x) fprintf(stderr, (x))
# define SLRN_MESSAGE(x) fprintf(stdout, (x))
# define SLRN_ERROR_1(x,y) fprintf(stderr, (x), (y))
# define SLRN_MESSAGE_1(x,y) fprintf(stdout, (x), (y))
# define SLRN_MESSAGE_2(x,y,z) fprintf(stdout, (x), (y), (z))
# define SLRN_POPEN popen
# define SLRN_PCLOSE pclose
# define slrn_fclose slrn_fclose_standalone
# define UPPER_CASE(x) ((x) >= 'a') && ((x) <= 'z') ? (x) - 32 : (x)
# define LOWER_CASE(x) ((x) >= 'A') && ((x) <= 'Z') ? (x) + 32 : (x)
static int slrn_fclose_standalone (FILE *fp)
{
if (0 == fclose (fp)) return 0;
SLRN_ERROR ("Error closing file. File system full?");
return -1;
}
#else
# include <slang.h>
# include "jdmacros.h"
# include "slrn.h"
# include "misc.h"
# include "uudecode.h"
# define SLRN_ERROR slrn_error
# define SLRN_ERROR_1 slrn_error
# define SLRN_MESSAGE(x) slrn_message(x); slrn_refresh ()
# define SLRN_MESSAGE_1(x,y) slrn_message((x), (y)); slrn_smg_refresh ()
# define SLRN_MESSAGE_2(x,y,z) slrn_message((x), (y), (z)); slrn_smg_refresh ()
# define SLRN_POPEN slrn_popen
# define SLRN_PCLOSE slrn_pclose
char *Slrn_Decode_Directory;
#endif
/* Note!!! These routines assume a flat address space !! */
int slrn_case_strncmp (unsigned char *a, register unsigned char *b, register unsigned int n)
{
register unsigned char cha, chb, *bmax;
register int diff = a - b;
bmax = b + n;
while (b < bmax)
{
cha = UPPER_CASE(b[diff]);
chb = UPPER_CASE(*b);
if (cha != chb)
{
return (int) cha - (int) chb;
}
else if (chb == 0) return 0;
b++;
}
return 0;
}
#ifndef STANDALONE
int slrn_case_strcmp (unsigned char *a, register unsigned char *b)
{
register unsigned char cha, chb;
register int diff = a - b;
while (1)
{
cha = UPPER_CASE(b[diff]);
chb = UPPER_CASE(*b);
if (cha != chb)
{
return (int) cha - (int) chb;
}
else if (chb == 0) break;
b++;
}
return 0;
}
#endif
static int unpack_as_shell_archive (FILE *fp, char *buf, int size)
{
#if SLRN_HAS_PIPING
FILE *pp;
size--;
pp = SLRN_POPEN ("/bin/sh", "w");
if (pp == NULL)
{
SLRN_ERROR ("Unable to open /bin/sh\n");
return -1;
}
while (fgets (buf, size, fp) != NULL)
{
fputs (buf, pp);
if (!strcmp (buf, "exit 0\n"))
break;
}
if (-1 == SLRN_PCLOSE (pp))
{
SLRN_ERROR ("Error encountered while processing shell archive.\n");
return -1;
}
return 0;
#else
slrn_error ("Piping not implemented on this system.");
return -1;
#endif /* HAS_PIPING */
}
static FILE *open_output_file (char *name, char *type)
{
FILE *fp;
fp = fopen (name, "wb");
if (fp == NULL)
{
SLRN_ERROR_1("Unable to create %s\n", name);
return NULL;
}
SLRN_MESSAGE_2("creating %s (%s)\n", name, type);
return fp;
}
static unsigned char Base64_Table [256];
static void initialize_base64 (void)
{
int i;
for (i = 0; i < 256; i++) Base64_Table[i] = 0xFF;
for (i = 'A'; i <= 'Z'; i++) Base64_Table[i] = (unsigned char) (i - 'A');
for (i = 'a'; i <= 'z'; i++) Base64_Table[i] = 26 + (unsigned char) (i - 'a');
for (i = '0'; i <= '9'; i++) Base64_Table[i] = 52 + (unsigned char) (i - '0');
Base64_Table['+'] = 62;
Base64_Table['/'] = 63;
}
/* returns 0 if entire line was decoded, or 1if line appears to be padded, or
* -1 if line looks bad.
*/
/* Calling routine guarantees at least 4 characters in line and multiple of 4 */
static int base64_decode_line (char *line, FILE *fp)
{
unsigned char ch;
unsigned char ch1;
unsigned char bytes[3];
unsigned char *p;
p = (unsigned char *) line;
/* Perform simple syntax check. The loop following this one
* assumes this.
*/
while ((ch = *p++) != 0)
{
if (Base64_Table[ch] == 0xFF)
return -1;
ch = *p++;
if (Base64_Table[ch] == 0xFF)
return -1;
ch = *p++;
if ((Base64_Table[ch] == 0xFF) && (ch != '='))
{
return -1;
}
ch = *p++;
if ((Base64_Table[ch] == 0xFF) && (ch != '='))
{
return -1;
}
}
while ((ch = (unsigned char) *line++) != 0)
{
ch1 = Base64_Table[ch];
bytes[0] = ch1 << 2;
ch = *line++;
ch1 = Base64_Table[ch];
bytes[0] |= ch1 >> 4;
bytes[1] = ch1 << 4;
ch = *line++;
ch1 = Base64_Table[ch];
if (ch1 == 0xFF)
{
fwrite (bytes, 1, 1, fp);
return 1;
}
bytes[1] |= ch1 >> 2;
bytes[2] = ch1 << 6;
ch = *line++;
ch1 = Base64_Table[ch];
if (ch1 == 0xFF)
{
fwrite (bytes, 1, 2, fp);
return 1;
}
bytes[2] |= ch1;
fwrite (bytes, 1, 3, fp);
}
return 0;
}
static char Base64_Filename[256];
static int Base64_Unknown_Number;
/* Returns 1 if padded line which indicates end of encoded file, 0 if
* decoded something or -1 if nothing.
*/
static int decode_base64 (FILE *fp, FILE *fpout, char *line, unsigned int buflen)
{
int decoding = 0;
unsigned int len;
int ret;
while (NULL != fgets (line, buflen, fp))
{
if (Base64_Table[(unsigned char) *line] == 0xFF)
{
if (decoding) return 0;
else return -1;
}
len = strlen (line);
if (len)
{
if (line [len - 1] == '\n')
{
line[len - 1] = 0;
len--;
}
}
if ((len % 4) != 0)
{
if (decoding) return 0;
else return -1;
}
ret = base64_decode_line (line, fpout);
if (ret)
{
if ((ret == -1) && decoding) ret = 0;
return ret;
}
decoding = 1;
}
return 0;
}
static char *skip_whitespace (char *p)
{
while ((*p == ' ') || (*p == '\t') || (*p == '\n')) p++;
return p;
}
static char *skip_header_string (char *p, char *name)
{
unsigned int len;
p = skip_whitespace (p);
len = strlen (name);
if (0 != slrn_case_strncmp ((unsigned char *)p, (unsigned char *)name, len))
return NULL;
return p + len;
}
static char *skip_beyond_name_eqs (char *p, char *name)
{
int ch;
unsigned int len;
len = strlen (name);
while (1)
{
p = skip_whitespace (p);
if (*p == 0) return NULL;
if (0 != slrn_case_strncmp ((unsigned char *) p, (unsigned char *) name, len))
{
while (((ch = *p) != 0) && (ch != ' ') && (ch != '\t') && (ch != '\n'))
p++;
continue;
}
p = skip_whitespace (p + len);
if (*p != '=') continue;
p = skip_whitespace (p + 1);
if (*p == 0)
return NULL;
return p;
}
}
static int parse_name_eqs_int (char *p, char *name, int *val)
{
int ch;
int ival;
p = skip_beyond_name_eqs (p, name);
if (p == NULL) return -1;
ival = 0;
while (((ch = *p) != 0) && isdigit(ch))
{
ival = ival * 10 + (ch - '0');
p++;
}
*val = ival;
return 0;
}
static int parse_name_eqs_string (char *p, char *name, char *str, unsigned int len)
{
char ch;
char *pmax;
int quote = 0;
p = skip_beyond_name_eqs (p, name);
if (p == NULL) return -1;
if (*p == '"')
{
quote = 1;
p++;
}
pmax = (p + len) - 1;
while ((p < pmax)
&& ((ch = *p) != '"')
&& (ch != '\n')
&& (ch != 0))
{
if ((quote == 0) && ((ch == ' ') || (ch == '\t')))
break;
*str++ = ch;
p++;
}
*str = 0;
return 0;
}
static void parse_content_disposition (char *p)
{
(void) parse_name_eqs_string (p, "filename", Base64_Filename, sizeof (Base64_Filename));
}
static int is_encoding_base64 (char *p)
{
p = skip_whitespace (p);
if (slrn_case_strncmp ((unsigned char *)p, (unsigned char *)"base64", 6))
return 0;
return 1;
}
/* Looking for message/partial; id="bla-bla"; number=10; total=20
* The following is from RFC 1522. It also illustrates what is wrong with
* MIME:
*
* Three parameters must be specified in the Content-Type field of type
* message/partial: The first, "id", is a unique identifier, as close to
* a world-unique identifier as possible, to be used to match the parts
* together. (In general, the identifier is essentially a message-id;
* if placed in double quotes, it can be any message-id, in accordance
* with the BNF for "parameter" given earlier in this specification.)
* The second, "number", an integer, is the part number, which indicates
* where this part fits into the sequence of fragments. The third,
* "total", another integer, is the total number of parts. This third
* subfield is required on the final part, and is optional (though
* encouraged) on the earlier parts. Note also that these parameters
* may be given in any order.
*
* Thus, part 2 of a 3-part message may have either of the following
* header fields:
*
* Content-Type: Message/Partial;
* number=2; total=3;
* id="oc=jpbe0M2Yt4s@thumper.bellcore.com"
*
* Content-Type: Message/Partial;
* id="oc=jpbe0M2Yt4s@thumper.bellcore.com";
* number=2
*
* But part 3 MUST specify the total number of parts:
*
* Content-Type: Message/Partial;
* number=3; total=3;
* id="oc=jpbe0M2Yt4s@thumper.bellcore.com"
*
* Note that part numbering begins with 1, not 0.
*/
typedef struct
{
int number; /* >= 1 */
int total; /* not required!! */
char id[256]; /* required */
}
Mime_Message_Partial_Type;
static int parse_content_type (char *p, Mime_Message_Partial_Type *mpt)
{
#if 1
(void) parse_name_eqs_string (p, "name",
Base64_Filename, sizeof (Base64_Filename));
#endif
if ((NULL == (p = skip_header_string (p, "message")))
|| (NULL == (p = skip_header_string (p, "/")))
|| (NULL == (p = skip_header_string (p, "partial")))
|| (NULL == (p = skip_header_string (p, ";"))))
return -1;
if (-1 == parse_name_eqs_int (p, "number", &mpt->number))
return -1;
if (-1 == parse_name_eqs_int (p, "total", &mpt->total))
mpt->total = 0; /* not reqired on all parts */
if (-1 == parse_name_eqs_string (p, "id", mpt->id, sizeof (mpt->id)))
{
*mpt->id = 0;
return -1;
}
return 0;
}
static void read_in_whole_header_line (char *line, FILE *fp)
{
unsigned int len;
unsigned int space;
int c;
/* We have already read in one line. Prepare to read in continuation lines */
len = strlen (line);
space = MAX_ARTICLE_LINE_LEN - len;
while (space)
{
c = getc (fp);
if (c == EOF) return;
if ((c != ' ') && (c != '\t'))
{
ungetc (c, fp);
return;
}
line += len;
if (NULL == fgets (line, space, fp)) return;
len = strlen (line);
space -= len;
}
}
static int check_and_decode_base64 (FILE *fp)
{
char *p;
int found_base64_signature = 0;
FILE *fpout = NULL;
char line[MAX_ARTICLE_LINE_LEN];
Mime_Message_Partial_Type *current_pmt, pmt_buf;
int part = 1;
initialize_base64 ();
current_pmt = NULL;
while (NULL != fgets (line, sizeof (line), fp))
{
try_again:
if (*line == '\n')
{
if (found_base64_signature)
{
int ret;
if (*Base64_Filename == 0)
{
sprintf (Base64_Filename, "unknown-64.%d", Base64_Unknown_Number);
Base64_Unknown_Number++;
}
if (fpout == NULL)
fpout = open_output_file (Base64_Filename, "base64");
if (fpout == NULL) return -1;
ret = decode_base64 (fp, fpout, line, sizeof (line));
/* decode_base64 performs fgets. So if nothing was decoded
* skip fgets at top of loop
*/
if (ret == -1) goto try_again;
if ((current_pmt == NULL)
|| (current_pmt->number == current_pmt->total)
|| (part != current_pmt->number)
|| (ret == 1))
{
fclose (fpout);
fpout = NULL;
found_base64_signature = 0;
current_pmt = NULL;
part = 0;
*Base64_Filename = 0;
}
part++;
}
continue;
}
/* Look for 'Content-' */
if (slrn_case_strncmp ((unsigned char *) "Content-", (unsigned char *) line, 8))
continue;
read_in_whole_header_line (line, fp);
p = line + 8;
if (0 == slrn_case_strncmp ((unsigned char *) p, (unsigned char *) "Transfer-Encoding: ", 19))
{
if (is_encoding_base64 (p + 19))
{
if (found_base64_signature)
{
if (fpout != NULL)
{
fclose (fpout);
fpout = NULL;
current_pmt = NULL;
*Base64_Filename = 0;
part = 1;
}
}
found_base64_signature = 1;
}
continue;
}
if (0 == slrn_case_strncmp ((unsigned char *)p, (unsigned char *)"Disposition: ", 13))
{
parse_content_disposition (p + 13);
continue;
}
if (0 == slrn_case_strncmp ((unsigned char *)p, (unsigned char *)"Type: ", 6))
{
Mime_Message_Partial_Type pmt;
if (-1 == parse_content_type (p + 6, &pmt))
{
/* current_pmt = NULL; */
continue;
}
if ((current_pmt == NULL)
|| (pmt.number == 1)
|| (part != pmt.number)
|| strcmp (current_pmt->id, pmt.id))
{
if (fpout != NULL)
{
fclose (fpout);
fpout = NULL;
*Base64_Filename = 0;
found_base64_signature = 0;
part = 1;
}
}
current_pmt = &pmt_buf;
memcpy ((char *) current_pmt, (char *) &pmt, sizeof (Mime_Message_Partial_Type));
continue;
}
}
if (fpout != NULL) fclose (fpout);
return 0;
}
static int parse_uuencode_begin_line (unsigned char *b, char **file, int *mode)
{
int m = 0;
unsigned char *bmax;
while (*b == ' ') b++;
if (0 == isdigit (*b)) return -1;
while (isdigit (*b))
{
m = m * 8 + (*b - '0');
b++;
}
if (*b != ' ') return -1;
while (*b == ' ') b++;
if (*b < ' ') return -1;
bmax = b + strlen ((char *) b);
while (*bmax <= ' ') *bmax-- = 0;
*mode = m;
*file = (char *) b;
return 0;
}
static int check_and_uudecode (FILE *fp)
{
FILE *outfp = NULL;
char buf[MAX_ARTICLE_LINE_LEN];
while (NULL != fgets (buf, sizeof (buf), fp))
{
char *file;
int mode = 0;
/* We are looking for 'begin ddd filename' */
if (strncmp (buf, "begin ", 6))
{
if ((buf[0] == '#') && (buf[1] == '!'))
{
char *binsh = buf + 2;
if (*binsh == ' ') binsh++;
if (!strcmp (binsh, "/bin/sh\n"))
{
unpack_as_shell_archive (fp, buf, sizeof (buf));
}
}
continue;
}
if (-1 == parse_uuencode_begin_line ((unsigned char *)buf + 6, &file, &mode))
continue;
outfp = open_output_file (file, "uuencoded");
if (outfp == NULL)
{
return -1;
}
chmod (file, mode);
/* Now read parts of the file in. */
while (fgets (buf, sizeof(buf) - 1, fp) != NULL)
{
unsigned int len;
unsigned int buflen;
unsigned char out[60], *outp, *outmax, *b, *bmax;
if (((*buf & 0x3F) == ' ') && (*(buf + 1) == '\n'))
{
/* we may be on the last line before the end. Lets check */
if (NULL == fgets (buf, sizeof(buf) - 1, fp))
{
SLRN_ERROR ("Unexpected end of file.\n");
return -1;
}
if (!strcmp (buf, "end\n"))
{
slrn_fclose (outfp);
outfp = NULL;
break;
}
}
/* Now perform some sanity checking */
len = *buf;
if ((len <= ' ')
|| ((buflen = strlen (buf)) > 62) || (buflen < 2)
|| ((buflen - 2) % 4) != 0)
{
if (strncmp (buf, "begin ", 6)
|| (-1 == parse_uuencode_begin_line ((unsigned char *)buf + 6,
&file, &mode)))
continue;
if (outfp == NULL) slrn_fclose (outfp);
outfp = open_output_file (file, "uuencoded");
if (outfp == NULL)
{
return -1;
}
continue;
}
buflen -= 2;
len -= ' ';
/* In general, I cannot make the test:
* @ if ((3 * buflen) != (len * 4)) continue;
* The problem is that the last line may be padded. Instead, do
* it just for normal length lines and handle padding for others.
*/
if (*buf == 'M')
{
if ((3 * buflen) != (len * 4)) continue;
}
else
{
if (buflen != ((len + 2) / 3) * 4) continue;
}
b = (unsigned char *) buf + 1;
bmax = b + buflen;
while (b < bmax)
{
*b = (*b - 32) & 0x3F;
*(b + 1) = (*(b + 1) - 32) & 0x3F;
*(b + 2) = (*(b + 2) - 32) & 0x3F;
*(b + 3) = (*(b + 3) - 32) & 0x3F;
b += 4;
}
b = (unsigned char *) buf + 1;
outp = out;
outmax = outp + len;
while (outp < outmax)
{
register unsigned char b1, b2;
b1 = *(b + 1);
b2 = *(b + 2);
*outp++ = (*b << 2) | (b1 >> 4);
if (outp < outmax)
{
*outp++ = (b1 << 4) | (b2 >> 2);
if (outp < outmax)
*outp++ = (b2 << 6) | *(b + 3);
}
b += 4;
}
if (len != fwrite ((char *) out, 1, len, outfp))
{
SLRN_ERROR ("write to file failed.\n");
slrn_fclose (outfp);
return -1;
}
}
/* end of part reading */
/* back to looking for something else */
}
if (outfp != NULL)
{
slrn_fclose (outfp);
outfp = NULL;
}
return 0;
}
int slrn_uudecode_file (char *file, char *newdir, int base64_only)
{
FILE *fp;
#ifdef __os2__
char olddir_buf[256];
#endif
int ret;
if (newdir == NULL)
{
#ifndef STANDALONE
char newdir_buf[256];
#ifdef __os2__
if (getwd(olddir_buf) == 0)
{
SLRN_ERROR_1 ("Unable to save old directory name: %s\n", olddir_buf);
return -1;
}
#endif
if (Slrn_Decode_Directory == NULL)
Slrn_Decode_Directory = "News";
newdir = Slrn_Decode_Directory;
slrn_make_home_dirname (newdir, newdir_buf);
newdir = newdir_buf;
#endif
}
if ((newdir != NULL)
&& (-1 == chdir(newdir)))
{
SLRN_ERROR_1 ("Unable to chdir to %s.\n", newdir);
return -1;
}
if (file == NULL) fp = stdin;
else fp = fopen (file, "r");
if (fp == NULL)
{
SLRN_ERROR_1("Unable to open %s.\n", file);
return -1;
}
if (base64_only == 0)
{
ret = check_and_uudecode (fp);
if ((ret == 0) && (fp != stdin))
{
rewind (fp);
ret = check_and_decode_base64 (fp);
}
}
else ret = check_and_decode_base64 (fp);
if (fp != stdin)
slrn_fclose (fp);
#ifdef __os2__
if (-1 == chdir(olddir_buf))
{
SLRN_ERROR_1 ("Unable to chdir back to %s.\n", olddir_buf);
return -1;
}
#endif
#ifdef STANDALONE
SLRN_MESSAGE ("No more files found.\n");
#endif
return ret;
}
#ifdef STANDALONE
static void usage (void)
{
fprintf (stderr, "Usage: uudecode [-64] [filename ...]\n");
}
int main (int argc, char **argv)
{
int i;
int base64_only = 0;
if (argc > 1)
{
if (!strcmp ("--help", argv[1]))
{
usage ();
return 1;
}
if (!strcmp ("-64", argv[1]))
{
base64_only = 1;
argc--;
argv++;
}
}
if (argc <= 1)
{
if (!isatty (fileno(stdin)))
{
return slrn_uudecode_file (NULL, NULL, base64_only);
}
usage ();
return 1;
}
for (i = 1; i < argc; i++)
{
slrn_uudecode_file (argv[i], NULL, base64_only);
}
return 0;
}
#endif
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.