Commit | Line | Data |
---|---|---|
15637ed4 | 1 | #if !defined(lint) && !defined(LINT) |
5ba07240 | 2 | static char rcsid[] = "$Header: /a/cvs/386BSD/src/libexec/crond/misc.c,v 1.1.1.1 1993/06/12 14:55:03 rgrimes Exp $"; |
15637ed4 RG |
3 | #endif |
4 | ||
5 | /* vix 26jan87 [RCS has the rest of the log] | |
6 | * vix 15jan87 [added TIOCNOTTY, thanks csg@pyramid] | |
7 | * vix 30dec86 [written] | |
8 | * | |
9 | * PATCHES MAGIC LEVEL PATCH THAT GOT US HERE | |
10 | * -------------------- ----- ---------------------- | |
11 | * CURRENT PATCH LEVEL: 1 00131 | |
12 | * -------------------- ----- ---------------------- | |
13 | * | |
14 | * 06 Apr 93 Adam Glass Fixes so it compiles quitely | |
15 | * | |
16 | */ | |
17 | ||
18 | /* Copyright 1988,1990 by Paul Vixie | |
19 | * All rights reserved | |
20 | * | |
21 | * Distribute freely, except: don't remove my name from the source or | |
22 | * documentation (don't take credit for my work), mark your changes (don't | |
23 | * get me blamed for your possible bugs), don't alter or remove this | |
24 | * notice. May be sold if buildable source is provided to buyer. No | |
25 | * warrantee of any kind, express or implied, is included with this | |
26 | * software; use at your own risk, responsibility for damages (if any) to | |
27 | * anyone resulting from the use of this software rests entirely with the | |
28 | * user. | |
29 | * | |
30 | * Send bug reports, bug fixes, enhancements, requests, flames, etc., and | |
31 | * I'll try to keep a version up to date. I can be reached as follows: | |
32 | * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013, | |
33 | * paul@vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul | |
34 | */ | |
35 | ||
36 | ||
37 | #include "cron.h" | |
38 | #include <sys/time.h> | |
39 | #include <sys/resource.h> | |
40 | #include <sys/ioctl.h> | |
41 | #include <sys/file.h> | |
42 | #include <errno.h> | |
5ba07240 | 43 | #include <string.h> |
15637ed4 RG |
44 | #if defined(ATT) |
45 | # include <fcntl.h> | |
46 | #endif | |
47 | ||
48 | ||
49 | void log_it(), be_different(), acquire_daemonlock(); | |
50 | ||
51 | ||
52 | char * | |
53 | savestr(str) | |
54 | char *str; | |
55 | { | |
15637ed4 RG |
56 | extern char *malloc(), *strcpy(); |
57 | /**/ char *temp; | |
58 | ||
59 | temp = malloc((unsigned) (strlen(str) + 1)); | |
60 | (void) strcpy(temp, str); | |
61 | return temp; | |
62 | } | |
63 | ||
64 | ||
65 | int | |
66 | nocase_strcmp(left, right) | |
67 | char *left; | |
68 | char *right; | |
69 | { | |
70 | while (*left && (MkLower(*left) == MkLower(*right))) | |
71 | { | |
72 | left++; | |
73 | right++; | |
74 | } | |
75 | return MkLower(*left) - MkLower(*right); | |
76 | } | |
77 | ||
78 | ||
79 | int | |
80 | strcmp_until(left, right, until) | |
81 | char *left; | |
82 | char *right; | |
83 | char until; | |
84 | { | |
85 | register int diff; | |
86 | ||
87 | Debug(DPARS|DEXT, ("strcmp_until(%s,%s,%c) ... ", left, right, until)) | |
88 | ||
89 | while (*left && *left != until && *left == *right) | |
90 | { | |
91 | left++; | |
92 | right++; | |
93 | } | |
94 | ||
95 | if ( (*left=='\0' || *left == until) | |
96 | && (*right=='\0' || *right == until) | |
97 | ) | |
98 | diff = 0; | |
99 | else | |
100 | diff = *left - *right; | |
101 | ||
102 | Debug(DPARS|DEXT, ("%d\n", diff)) | |
103 | ||
104 | return diff; | |
105 | } | |
106 | ||
107 | ||
108 | /* strdtb(s) - delete trailing blanks in string 's' and return new length | |
109 | */ | |
110 | int | |
111 | strdtb(s) | |
112 | register char *s; | |
113 | { | |
114 | register char *x = s; | |
115 | ||
116 | /* scan forward to the null | |
117 | */ | |
118 | while (*x) | |
119 | x++; | |
120 | ||
121 | /* scan backward to either the first character before the string, | |
122 | * or the last non-blank in the string, whichever comes first. | |
123 | */ | |
124 | do {x--;} | |
125 | while (x >= s && isspace(*x)); | |
126 | ||
127 | /* one character beyond where we stopped above is where the null | |
128 | * goes. | |
129 | */ | |
130 | *++x = '\0'; | |
131 | ||
132 | /* the difference between the position of the null character and | |
133 | * the position of the first character of the string is the length. | |
134 | */ | |
135 | return x - s; | |
136 | } | |
137 | ||
138 | ||
139 | int | |
140 | set_debug_flags(flags) | |
141 | char *flags; | |
142 | { | |
143 | /* debug flags are of the form flag[,flag ...] | |
144 | * | |
145 | * if an error occurs, print a message to stdout and return FALSE. | |
146 | * otherwise return TRUE after setting ERROR_FLAGS. | |
147 | */ | |
148 | ||
149 | #if !DEBUGGING | |
150 | ||
151 | printf("this program was compiled without debugging enabled\n"); | |
152 | return FALSE; | |
153 | ||
154 | #else /* DEBUGGING */ | |
155 | ||
156 | char *pc = flags; | |
157 | ||
158 | DebugFlags = 0; | |
159 | ||
160 | while (*pc) | |
161 | { | |
162 | char **test; | |
163 | int mask; | |
164 | ||
165 | /* try to find debug flag name in our list. | |
166 | */ | |
167 | for ( test = DebugFlagNames, mask = 1; | |
168 | *test && strcmp_until(*test, pc, ','); | |
169 | test++, mask <<= 1 | |
170 | ) | |
171 | ; | |
172 | ||
173 | if (!*test) | |
174 | { | |
175 | fprintf(stderr, | |
176 | "unrecognized debug flag <%s> <%s>\n", | |
177 | flags, pc); | |
178 | return FALSE; | |
179 | } | |
180 | ||
181 | DebugFlags |= mask; | |
182 | ||
183 | /* skip to the next flag | |
184 | */ | |
185 | while (*pc && *pc != ',') | |
186 | pc++; | |
187 | if (*pc == ',') | |
188 | pc++; | |
189 | } | |
190 | ||
191 | if (DebugFlags) | |
192 | { | |
193 | int flag; | |
194 | ||
195 | fprintf(stderr, "debug flags enabled:"); | |
196 | ||
197 | for (flag = 0; DebugFlagNames[flag]; flag++) | |
198 | if (DebugFlags & (1 << flag)) | |
199 | fprintf(stderr, " %s", DebugFlagNames[flag]); | |
200 | fprintf(stderr, "\n"); | |
201 | } | |
202 | ||
203 | return TRUE; | |
204 | ||
205 | #endif /* DEBUGGING */ | |
206 | } | |
207 | ||
208 | ||
209 | #if defined(BSD) | |
210 | void | |
211 | set_cron_uid() | |
212 | { | |
213 | int seteuid(); | |
214 | ||
215 | if (seteuid(ROOT_UID) < OK) | |
216 | { | |
217 | perror("seteuid"); | |
218 | exit(ERROR_EXIT); | |
219 | } | |
220 | } | |
221 | #endif | |
222 | ||
223 | #if defined(ATT) | |
224 | void | |
225 | set_cron_uid() | |
226 | { | |
227 | int setuid(); | |
228 | ||
229 | if (setuid(ROOT_UID) < OK) | |
230 | { | |
231 | perror("setuid"); | |
232 | exit(ERROR_EXIT); | |
233 | } | |
234 | } | |
235 | #endif | |
236 | ||
237 | void | |
238 | set_cron_cwd() | |
239 | { | |
240 | extern int errno; | |
241 | struct stat sb; | |
242 | ||
243 | /* first check for CRONDIR ("/var/cron" or some such) | |
244 | */ | |
245 | if (stat(CRONDIR, &sb) < OK && errno == ENOENT) { | |
246 | perror(CRONDIR); | |
247 | if (OK == mkdir(CRONDIR, 0700)) { | |
248 | fprintf(stderr, "%s: created\n", CRONDIR); | |
249 | stat(CRONDIR, &sb); | |
250 | } else { | |
251 | fprintf(stderr, "%s: ", CRONDIR); | |
252 | perror("mkdir"); | |
253 | exit(ERROR_EXIT); | |
254 | } | |
255 | } | |
256 | if (!(sb.st_mode & S_IFDIR)) { | |
257 | fprintf(stderr, "'%s' is not a directory, bailing out.\n", | |
258 | CRONDIR); | |
259 | exit(ERROR_EXIT); | |
260 | } | |
261 | if (chdir(CRONDIR) < OK) { | |
262 | fprintf(stderr, "cannot chdir(%s), bailing out.\n", CRONDIR); | |
263 | perror(CRONDIR); | |
264 | exit(ERROR_EXIT); | |
265 | } | |
266 | ||
267 | /* CRONDIR okay (now==CWD), now look at SPOOL_DIR ("tabs" or some such) | |
268 | */ | |
269 | if (stat(SPOOL_DIR, &sb) < OK && errno == ENOENT) { | |
270 | perror(SPOOL_DIR); | |
271 | if (OK == mkdir(SPOOL_DIR, 0700)) { | |
272 | fprintf(stderr, "%s: created\n", SPOOL_DIR); | |
273 | stat(SPOOL_DIR, &sb); | |
274 | } else { | |
275 | fprintf(stderr, "%s: ", SPOOL_DIR); | |
276 | perror("mkdir"); | |
277 | exit(ERROR_EXIT); | |
278 | } | |
279 | } | |
280 | if (!(sb.st_mode & S_IFDIR)) { | |
281 | fprintf(stderr, "'%s' is not a directory, bailing out.\n", | |
282 | SPOOL_DIR); | |
283 | exit(ERROR_EXIT); | |
284 | } | |
285 | } | |
286 | ||
287 | ||
288 | #if defined(BSD) | |
289 | void | |
290 | be_different() | |
291 | { | |
292 | /* release the control terminal: | |
293 | * get new pgrp (name after our PID) | |
294 | * do an IOCTL to void tty association | |
295 | */ | |
296 | ||
297 | auto int fd; | |
298 | ||
299 | (void) setpgrp(0, getpid()); | |
300 | ||
301 | if ((fd = open("/dev/tty", 2)) >= 0) | |
302 | { | |
303 | (void) ioctl(fd, TIOCNOTTY, (char*)0); | |
304 | (void) close(fd); | |
305 | } | |
306 | } | |
307 | #endif /*BSD*/ | |
308 | ||
309 | #if defined(ATT) | |
310 | void | |
311 | be_different() | |
312 | { | |
313 | /* not being a system V wiz, I don't know if this is what you have | |
314 | * to do to release your control terminal. what I want to accomplish | |
315 | * is to keep this process from getting any signals from the tty. | |
316 | * | |
317 | * some system V person should let me know if this works... (vixie) | |
318 | */ | |
319 | int setpgrp(), close(), open(); | |
320 | ||
321 | (void) setpgrp(); | |
322 | ||
323 | (void) close(STDIN); (void) open("/dev/null", 0); | |
324 | (void) close(STDOUT); (void) open("/dev/null", 1); | |
325 | (void) close(STDERR); (void) open("/dev/null", 2); | |
326 | } | |
327 | #endif /*ATT*/ | |
328 | ||
329 | ||
330 | /* acquire_daemonlock() - write our PID into /etc/crond.pid, unless | |
331 | * another daemon is already running, which we detect here. | |
332 | */ | |
333 | void | |
334 | acquire_daemonlock() | |
335 | { | |
336 | int fd = open(PIDFILE, O_RDWR|O_CREAT, 0644); | |
337 | FILE *fp = fdopen(fd, "r+"); | |
338 | int pid = getpid(), otherpid; | |
339 | char buf[MAX_TEMPSTR]; | |
340 | ||
341 | if (fd < 0 || fp == NULL) { | |
342 | sprintf(buf, "can't open or create %s, errno %d", PIDFILE, errno); | |
343 | log_it("CROND", pid, "DEATH", buf); | |
344 | exit(ERROR_EXIT); | |
345 | } | |
346 | ||
347 | if (flock(fd, LOCK_EX|LOCK_NB) < OK) { | |
348 | int save_errno = errno; | |
349 | ||
350 | fscanf(fp, "%d", &otherpid); | |
351 | sprintf(buf, "can't lock %s, otherpid may be %d, errno %d", | |
352 | PIDFILE, otherpid, save_errno); | |
353 | log_it("CROND", pid, "DEATH", buf); | |
354 | exit(ERROR_EXIT); | |
355 | } | |
356 | ||
357 | rewind(fp); | |
358 | fprintf(fp, "%d\n", pid); | |
359 | fflush(fp); | |
360 | ftruncate(fd, ftell(fp)); | |
361 | ||
362 | /* abandon fd and fp even though the file is open. we need to | |
363 | * keep it open and locked, but we don't need the handles elsewhere. | |
364 | */ | |
365 | } | |
366 | ||
367 | /* get_char(file) : like getc() but increment LineNumber on newlines | |
368 | */ | |
369 | int | |
370 | get_char(file) | |
371 | FILE *file; | |
372 | { | |
373 | int ch; | |
374 | ||
375 | ch = getc(file); | |
376 | if (ch == '\n') | |
377 | Set_LineNum(LineNumber + 1) | |
378 | return ch; | |
379 | } | |
380 | ||
381 | ||
382 | /* unget_char(ch, file) : like ungetc but do LineNumber processing | |
383 | */ | |
384 | void | |
385 | unget_char(ch, file) | |
386 | int ch; | |
387 | FILE *file; | |
388 | { | |
389 | ungetc(ch, file); | |
390 | if (ch == '\n') | |
391 | Set_LineNum(LineNumber - 1) | |
392 | } | |
393 | ||
394 | ||
395 | /* get_string(str, max, file, termstr) : like fgets() but | |
396 | * (1) has terminator string which should include \n | |
397 | * (2) will always leave room for the null | |
398 | * (3) uses get_char() so LineNumber will be accurate | |
399 | * (4) returns EOF or terminating character, whichever | |
400 | */ | |
401 | int | |
402 | get_string(string, size, file, terms) | |
403 | char *string; | |
404 | int size; | |
405 | FILE *file; | |
406 | char *terms; | |
407 | { | |
408 | int ch; | |
409 | char *index(); | |
410 | ||
411 | while (EOF != (ch = get_char(file)) && !index(terms, ch)) | |
412 | if (size > 1) | |
413 | { | |
414 | *string++ = (char) ch; | |
415 | size--; | |
416 | } | |
417 | ||
418 | if (size > 0) | |
419 | *string = '\0'; | |
420 | ||
421 | return ch; | |
422 | } | |
423 | ||
424 | ||
425 | /* skip_comments(file) : read past comment (if any) | |
426 | */ | |
427 | void | |
428 | skip_comments(file) | |
429 | FILE *file; | |
430 | { | |
431 | int ch; | |
432 | ||
433 | while (EOF != (ch = get_char(file))) | |
434 | { | |
435 | /* ch is now the first character of a line. | |
436 | */ | |
437 | ||
438 | while (ch == ' ' || ch == '\t') | |
439 | ch = get_char(file); | |
440 | ||
441 | if (ch == EOF) | |
442 | break; | |
443 | ||
444 | /* ch is now the first non-blank character of a line. | |
445 | */ | |
446 | ||
447 | if (ch != '\n' && ch != '#') | |
448 | break; | |
449 | ||
450 | /* ch must be a newline or comment as first non-blank | |
451 | * character on a line. | |
452 | */ | |
453 | ||
454 | while (ch != '\n' && ch != EOF) | |
455 | ch = get_char(file); | |
456 | ||
457 | /* ch is now the newline of a line which we're going to | |
458 | * ignore. | |
459 | */ | |
460 | } | |
461 | unget_char(ch, file); | |
462 | } | |
463 | ||
464 | /* int in_file(char *string, FILE *file) | |
465 | * return TRUE if one of the lines in file matches string exactly, | |
466 | * FALSE otherwise. | |
467 | */ | |
468 | int | |
469 | in_file(string, file) | |
470 | char *string; | |
471 | FILE *file; | |
472 | { | |
473 | char line[MAX_TEMPSTR]; | |
474 | ||
475 | /* let's be persnickety today. | |
476 | */ | |
477 | if (!file) { | |
478 | if (!string) | |
479 | string = "0x0"; | |
480 | fprintf(stderr, | |
481 | "in_file(\"%s\", 0x%x): called with NULL file--botch", | |
482 | string, file); | |
483 | exit(ERROR_EXIT); | |
484 | } | |
485 | ||
486 | rewind(file); | |
487 | while (fgets(line, MAX_TEMPSTR, file)) { | |
488 | if (line[0] != '\0') | |
489 | line[strlen(line)-1] = '\0'; | |
490 | if (0 == strcmp(line, string)) | |
491 | return TRUE; | |
492 | } | |
493 | return FALSE; | |
494 | } | |
495 | ||
496 | ||
497 | /* int allowed(char *username) | |
498 | * returns TRUE if (ALLOW_FILE exists and user is listed) | |
499 | * or (DENY_FILE exists and user is NOT listed) | |
500 | * or (neither file exists but user=="root" so it's okay) | |
501 | */ | |
502 | int | |
503 | allowed(username) | |
504 | char *username; | |
505 | { | |
506 | static int init = FALSE; | |
507 | static FILE *allow, *deny; | |
508 | ||
509 | if (!init) { | |
510 | init = TRUE; | |
511 | #if defined(ALLOW_FILE) && defined(DENY_FILE) | |
512 | allow = fopen(ALLOW_FILE, "r"); | |
513 | deny = fopen(DENY_FILE, "r"); | |
514 | Debug(DMISC, ("allow/deny enabled, %d/%d\n", !!allow, !!deny)) | |
515 | #else | |
516 | allow = NULL; | |
517 | deny = NULL; | |
518 | #endif | |
519 | } | |
520 | ||
521 | if (allow) | |
522 | return (in_file(username, allow)); | |
523 | if (deny) | |
524 | return (!in_file(username, deny)); | |
525 | ||
526 | #if defined(ALLOW_ONLY_ROOT) | |
527 | return (strcmp(username, ROOT_USER) == 0); | |
528 | #else | |
529 | return TRUE; | |
530 | #endif | |
531 | } | |
532 | ||
533 | ||
534 | #if defined(LOG_FILE) || defined(SYSLOG) | |
535 | void | |
536 | log_it(username, pid, event, detail) | |
537 | char *username; | |
538 | int pid; | |
539 | char *event; | |
540 | char *detail; | |
541 | { | |
542 | #if defined(LOG_FILE) | |
543 | extern struct tm *localtime(); | |
544 | extern long time(); | |
545 | extern char *malloc(); | |
546 | auto char *msg; | |
547 | auto long now = time((long *) 0); | |
548 | register struct tm *t = localtime(&now); | |
549 | static int log_fd = -1; | |
550 | #endif /*LOG_FILE*/ | |
551 | ||
552 | #if defined(SYSLOG) | |
553 | static int syslog_open = 0; | |
554 | #endif | |
555 | ||
556 | ||
557 | #if defined(LOG_FILE) | |
558 | /* we assume that MAX_TEMPSTR will hold the date, time, &punctuation. | |
559 | */ | |
560 | msg = malloc(strlen(username) | |
561 | + strlen(event) | |
562 | + strlen(detail) | |
563 | + MAX_TEMPSTR); | |
564 | ||
565 | if (log_fd < OK) { | |
566 | log_fd = open(LOG_FILE, O_WRONLY|O_APPEND|O_CREAT, 0600); | |
567 | if (log_fd < OK) { | |
568 | fprintf(stderr, "%s: can't open log file\n", ProgramName); | |
569 | perror(LOG_FILE); | |
570 | } | |
571 | } | |
572 | ||
573 | /* we have to sprintf() it because fprintf() doesn't always write | |
574 | * everything out in one chunk and this has to be atomically appended | |
575 | * to the log file. | |
576 | */ | |
577 | sprintf(msg, "%s (%02d/%02d-%02d:%02d:%02d-%d) %s (%s)\n", | |
578 | username, | |
579 | t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, pid, | |
580 | event, detail); | |
581 | ||
582 | /* we have to run strlen() because sprintf() returns (char*) on BSD | |
583 | */ | |
584 | if (log_fd < OK || write(log_fd, msg, strlen(msg)) < OK) { | |
585 | fprintf(stderr, "%s: can't write to log file\n", ProgramName); | |
586 | if (log_fd >= OK) | |
587 | perror(LOG_FILE); | |
588 | write(STDERR, msg, strlen(msg)); | |
589 | } | |
590 | ||
591 | /* I suppose we could use alloca()... | |
592 | */ | |
593 | free(msg); | |
594 | #endif /*LOG_FILE*/ | |
595 | ||
596 | #if defined(SYSLOG) | |
597 | if (!syslog_open) { | |
598 | /* we don't use LOG_PID since the pid passed to us by | |
599 | * our client may not be our own. therefore we want to | |
600 | * print the pid ourselves. | |
601 | */ | |
602 | # ifdef LOG_CRON | |
603 | openlog(ProgramName, 0, LOG_CRON); | |
604 | # else | |
605 | # ifdef LOG_DAEMON | |
606 | openlog(ProgramName, 0, LOG_DAEMON); | |
607 | # else | |
608 | openlog(ProgramName, 0); | |
609 | # endif /*LOG_DAEMON*/ | |
610 | # endif /*LOG_CRON*/ | |
611 | syslog_open = TRUE; /* assume openlog success */ | |
612 | } | |
613 | ||
614 | syslog(LOG_INFO, "(%s %d) %s (%s)\n", | |
615 | username, pid, event, detail); | |
616 | ||
617 | #endif /*SYSLOG*/ | |
618 | ||
619 | if (DebugFlags) { | |
620 | fprintf(stderr, "log_it: (%s %d) %s (%s)", | |
621 | username, pid, event, detail); | |
622 | } | |
623 | } | |
624 | #endif /*LOG_FILE || SYSLOG */ | |
625 | ||
626 | ||
627 | /* two warnings: | |
628 | * (1) this routine is fairly slow | |
629 | * (2) it returns a pointer to static storage | |
630 | */ | |
631 | char * | |
632 | first_word(s, t) | |
633 | local char *s; /* string we want the first word of */ | |
634 | local char *t; /* terminators, implicitly including \0 */ | |
635 | { | |
636 | static char retbuf[2][MAX_TEMPSTR + 1]; /* sure wish I had GC */ | |
637 | static int retsel = 0; | |
638 | local char *rb, *rp; | |
639 | extern char *index(); | |
640 | ||
641 | /* select a return buffer */ | |
642 | retsel = 1-retsel; | |
643 | rb = &retbuf[retsel][0]; | |
644 | rp = rb; | |
645 | ||
646 | /* skip any leading terminators */ | |
647 | while (*s && (NULL != index(t, *s))) {s++;} | |
648 | ||
649 | /* copy until next terminator or full buffer */ | |
650 | while (*s && (NULL == index(t, *s)) && (rp < &rb[MAX_TEMPSTR])) { | |
651 | *rp++ = *s++; | |
652 | } | |
653 | ||
654 | /* finish the return-string and return it */ | |
655 | *rp = '\0'; | |
656 | return rb; | |
657 | } | |
658 | ||
659 | ||
660 | /* warning: | |
661 | * heavily ascii-dependent. | |
662 | */ | |
663 | ||
664 | void | |
665 | mkprint(dst, src, len) | |
666 | register char *dst; | |
667 | register unsigned char *src; | |
668 | register int len; | |
669 | { | |
670 | while (len-- > 0) | |
671 | { | |
672 | register unsigned char ch = *src++; | |
673 | ||
674 | if (ch < ' ') { /* control character */ | |
675 | *dst++ = '^'; | |
676 | *dst++ = ch + '@'; | |
677 | } else if (ch < 0177) { /* printable */ | |
678 | *dst++ = ch; | |
679 | } else if (ch == 0177) { /* delete/rubout */ | |
680 | *dst++ = '^'; | |
681 | *dst++ = '?'; | |
682 | } else { /* parity character */ | |
683 | sprintf(dst, "\\%03o", ch); | |
684 | dst += 4; | |
685 | } | |
686 | } | |
687 | *dst = NULL; | |
688 | } | |
689 | ||
690 | ||
691 | /* warning: | |
692 | * returns a pointer to malloc'd storage, you must call free yourself. | |
693 | */ | |
694 | ||
695 | char * | |
696 | mkprints(src, len) | |
697 | register unsigned char *src; | |
698 | register unsigned int len; | |
699 | { | |
700 | extern char *malloc(); | |
701 | register char *dst = malloc(len*4 + 1); | |
702 | ||
703 | mkprint(dst, src, len); | |
704 | ||
705 | return dst; | |
706 | } |