mdoc: Add NetBSD 6.0 (used in wbsio.4).
[dragonfly.git] / contrib / sendmail-8.14 / libmilter / smfi.c
blob138623e1793f8f108601ed8151f20adcaee19873
1 /*
2 * Copyright (c) 1999-2007 Sendmail, Inc. and its suppliers.
3 * All rights reserved.
5 * By using this file, you agree to the terms and conditions set
6 * forth in the LICENSE file which can be found at the top level of
7 * the sendmail distribution.
9 */
11 #include <sm/gen.h>
12 SM_RCSID("@(#)$Id: smfi.c,v 8.83 2007/04/23 16:44:39 ca Exp $")
13 #include <sm/varargs.h>
14 #include "libmilter.h"
16 static int smfi_header __P((SMFICTX *, int, int, char *, char *));
17 static int myisenhsc __P((const char *, int));
19 /* for smfi_set{ml}reply, let's be generous. 256/16 should be sufficient */
20 #define MAXREPLYLEN 980 /* max. length of a reply string */
21 #define MAXREPLIES 32 /* max. number of reply strings */
24 ** SMFI_HEADER -- send a header to the MTA
26 ** Parameters:
27 ** ctx -- Opaque context structure
28 ** cmd -- Header modification command
29 ** hdridx -- Header index
30 ** headerf -- Header field name
31 ** headerv -- Header field value
33 ** Returns:
34 ** MI_SUCCESS/MI_FAILURE
37 static int
38 smfi_header(ctx, cmd, hdridx, headerf, headerv)
39 SMFICTX *ctx;
40 int cmd;
41 int hdridx;
42 char *headerf;
43 char *headerv;
45 size_t len, l1, l2, offset;
46 int r;
47 mi_int32 v;
48 char *buf;
49 struct timeval timeout;
51 if (headerf == NULL || *headerf == '\0' || headerv == NULL)
52 return MI_FAILURE;
53 timeout.tv_sec = ctx->ctx_timeout;
54 timeout.tv_usec = 0;
55 l1 = strlen(headerf) + 1;
56 l2 = strlen(headerv) + 1;
57 len = l1 + l2;
58 if (hdridx >= 0)
59 len += MILTER_LEN_BYTES;
60 buf = malloc(len);
61 if (buf == NULL)
62 return MI_FAILURE;
63 offset = 0;
64 if (hdridx >= 0)
66 v = htonl(hdridx);
67 (void) memcpy(&(buf[0]), (void *) &v, MILTER_LEN_BYTES);
68 offset += MILTER_LEN_BYTES;
70 (void) memcpy(buf + offset, headerf, l1);
71 (void) memcpy(buf + offset + l1, headerv, l2);
72 r = mi_wr_cmd(ctx->ctx_sd, &timeout, cmd, buf, len);
73 free(buf);
74 return r;
78 ** SMFI_ADDHEADER -- send a new header to the MTA
80 ** Parameters:
81 ** ctx -- Opaque context structure
82 ** headerf -- Header field name
83 ** headerv -- Header field value
85 ** Returns:
86 ** MI_SUCCESS/MI_FAILURE
89 int
90 smfi_addheader(ctx, headerf, headerv)
91 SMFICTX *ctx;
92 char *headerf;
93 char *headerv;
95 if (!mi_sendok(ctx, SMFIF_ADDHDRS))
96 return MI_FAILURE;
98 return smfi_header(ctx, SMFIR_ADDHEADER, -1, headerf, headerv);
102 ** SMFI_INSHEADER -- send a new header to the MTA (to be inserted)
104 ** Parameters:
105 ** ctx -- Opaque context structure
106 ** hdridx -- index into header list where insertion should occur
107 ** headerf -- Header field name
108 ** headerv -- Header field value
110 ** Returns:
111 ** MI_SUCCESS/MI_FAILURE
115 smfi_insheader(ctx, hdridx, headerf, headerv)
116 SMFICTX *ctx;
117 int hdridx;
118 char *headerf;
119 char *headerv;
121 if (!mi_sendok(ctx, SMFIF_ADDHDRS) || hdridx < 0)
122 return MI_FAILURE;
124 return smfi_header(ctx, SMFIR_INSHEADER, hdridx, headerf, headerv);
128 ** SMFI_CHGHEADER -- send a changed header to the MTA
130 ** Parameters:
131 ** ctx -- Opaque context structure
132 ** headerf -- Header field name
133 ** hdridx -- Header index value
134 ** headerv -- Header field value
136 ** Returns:
137 ** MI_SUCCESS/MI_FAILURE
141 smfi_chgheader(ctx, headerf, hdridx, headerv)
142 SMFICTX *ctx;
143 char *headerf;
144 mi_int32 hdridx;
145 char *headerv;
147 if (!mi_sendok(ctx, SMFIF_CHGHDRS) || hdridx < 0)
148 return MI_FAILURE;
149 if (headerv == NULL)
150 headerv = "";
152 return smfi_header(ctx, SMFIR_CHGHEADER, hdridx, headerf, headerv);
155 #if 0
157 ** BUF_CRT_SEND -- construct buffer to send from arguments
159 ** Parameters:
160 ** ctx -- Opaque context structure
161 ** cmd -- command
162 ** arg0 -- first argument
163 ** argv -- list of arguments (NULL terminated)
165 ** Returns:
166 ** MI_SUCCESS/MI_FAILURE
169 static int
170 buf_crt_send __P((SMFICTX *, int cmd, char *, char **));
172 static int
173 buf_crt_send(ctx, cmd, arg0, argv)
174 SMFICTX *ctx;
175 int cmd;
176 char *arg0;
177 char **argv;
179 size_t len, l0, l1, offset;
180 int r;
181 char *buf, *arg, **argvl;
182 struct timeval timeout;
184 if (arg0 == NULL || *arg0 == '\0')
185 return MI_FAILURE;
186 timeout.tv_sec = ctx->ctx_timeout;
187 timeout.tv_usec = 0;
188 l0 = strlen(arg0) + 1;
189 len = l0;
190 argvl = argv;
191 while (argvl != NULL && (arg = *argv) != NULL && *arg != '\0')
193 l1 = strlen(arg) + 1;
194 len += l1;
195 SM_ASSERT(len > l1);
198 buf = malloc(len);
199 if (buf == NULL)
200 return MI_FAILURE;
201 (void) memcpy(buf, arg0, l0);
202 offset = l0;
204 argvl = argv;
205 while (argvl != NULL && (arg = *argv) != NULL && *arg != '\0')
207 l1 = strlen(arg) + 1;
208 SM_ASSERT(offset < len);
209 SM_ASSERT(offset + l1 <= len);
210 (void) memcpy(buf + offset, arg, l1);
211 offset += l1;
212 SM_ASSERT(offset > l1);
215 r = mi_wr_cmd(ctx->ctx_sd, &timeout, cmd, buf, len);
216 free(buf);
217 return r;
219 #endif /* 0 */
222 ** SEND2 -- construct buffer to send from arguments
224 ** Parameters:
225 ** ctx -- Opaque context structure
226 ** cmd -- command
227 ** arg0 -- first argument
228 ** argv -- list of arguments (NULL terminated)
230 ** Returns:
231 ** MI_SUCCESS/MI_FAILURE
234 static int
235 send2 __P((SMFICTX *, int cmd, char *, char *));
237 static int
238 send2(ctx, cmd, arg0, arg1)
239 SMFICTX *ctx;
240 int cmd;
241 char *arg0;
242 char *arg1;
244 size_t len, l0, l1, offset;
245 int r;
246 char *buf;
247 struct timeval timeout;
249 if (arg0 == NULL || *arg0 == '\0')
250 return MI_FAILURE;
251 timeout.tv_sec = ctx->ctx_timeout;
252 timeout.tv_usec = 0;
253 l0 = strlen(arg0) + 1;
254 len = l0;
255 if (arg1 != NULL)
257 l1 = strlen(arg1) + 1;
258 len += l1;
259 SM_ASSERT(len > l1);
262 buf = malloc(len);
263 if (buf == NULL)
264 return MI_FAILURE;
265 (void) memcpy(buf, arg0, l0);
266 offset = l0;
268 if (arg1 != NULL)
270 l1 = strlen(arg1) + 1;
271 SM_ASSERT(offset < len);
272 SM_ASSERT(offset + l1 <= len);
273 (void) memcpy(buf + offset, arg1, l1);
274 offset += l1;
275 SM_ASSERT(offset > l1);
278 r = mi_wr_cmd(ctx->ctx_sd, &timeout, cmd, buf, len);
279 free(buf);
280 return r;
284 ** SMFI_CHGFROM -- change enveloper sender ("from") address
286 ** Parameters:
287 ** ctx -- Opaque context structure
288 ** from -- new envelope sender address ("MAIL From")
289 ** args -- ESMTP arguments
291 ** Returns:
292 ** MI_SUCCESS/MI_FAILURE
296 smfi_chgfrom(ctx, from, args)
297 SMFICTX *ctx;
298 char *from;
299 char *args;
301 if (from == NULL || *from == '\0')
302 return MI_FAILURE;
303 if (!mi_sendok(ctx, SMFIF_CHGFROM))
304 return MI_FAILURE;
305 return send2(ctx, SMFIR_CHGFROM, from, args);
309 ** SMFI_SETSYMLIST -- set list of macros that the MTA should send.
311 ** Parameters:
312 ** ctx -- Opaque context structure
313 ** where -- SMTP stage
314 ** macros -- list of macros
316 ** Returns:
317 ** MI_SUCCESS/MI_FAILURE
321 smfi_setsymlist(ctx, where, macros)
322 SMFICTX *ctx;
323 int where;
324 char *macros;
326 SM_ASSERT(ctx != NULL);
328 if (macros == NULL || *macros == '\0')
329 return MI_FAILURE;
330 if (where < SMFIM_FIRST || where > SMFIM_LAST)
331 return MI_FAILURE;
332 if (where < 0 || where >= MAX_MACROS_ENTRIES)
333 return MI_FAILURE;
335 if (ctx->ctx_mac_list[where] != NULL)
336 return MI_FAILURE;
338 ctx->ctx_mac_list[where] = strdup(macros);
339 if (ctx->ctx_mac_list[where] == NULL)
340 return MI_FAILURE;
342 return MI_SUCCESS;
346 ** SMFI_ADDRCPT_PAR -- send an additional recipient to the MTA
348 ** Parameters:
349 ** ctx -- Opaque context structure
350 ** rcpt -- recipient address
351 ** args -- ESMTP arguments
353 ** Returns:
354 ** MI_SUCCESS/MI_FAILURE
358 smfi_addrcpt_par(ctx, rcpt, args)
359 SMFICTX *ctx;
360 char *rcpt;
361 char *args;
363 if (rcpt == NULL || *rcpt == '\0')
364 return MI_FAILURE;
365 if (!mi_sendok(ctx, SMFIF_ADDRCPT_PAR))
366 return MI_FAILURE;
367 return send2(ctx, SMFIR_ADDRCPT_PAR, rcpt, args);
371 ** SMFI_ADDRCPT -- send an additional recipient to the MTA
373 ** Parameters:
374 ** ctx -- Opaque context structure
375 ** rcpt -- recipient address
377 ** Returns:
378 ** MI_SUCCESS/MI_FAILURE
382 smfi_addrcpt(ctx, rcpt)
383 SMFICTX *ctx;
384 char *rcpt;
386 size_t len;
387 struct timeval timeout;
389 if (rcpt == NULL || *rcpt == '\0')
390 return MI_FAILURE;
391 if (!mi_sendok(ctx, SMFIF_ADDRCPT))
392 return MI_FAILURE;
393 timeout.tv_sec = ctx->ctx_timeout;
394 timeout.tv_usec = 0;
395 len = strlen(rcpt) + 1;
396 return mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_ADDRCPT, rcpt, len);
400 ** SMFI_DELRCPT -- send a recipient to be removed to the MTA
402 ** Parameters:
403 ** ctx -- Opaque context structure
404 ** rcpt -- recipient address
406 ** Returns:
407 ** MI_SUCCESS/MI_FAILURE
411 smfi_delrcpt(ctx, rcpt)
412 SMFICTX *ctx;
413 char *rcpt;
415 size_t len;
416 struct timeval timeout;
418 if (rcpt == NULL || *rcpt == '\0')
419 return MI_FAILURE;
420 if (!mi_sendok(ctx, SMFIF_DELRCPT))
421 return MI_FAILURE;
422 timeout.tv_sec = ctx->ctx_timeout;
423 timeout.tv_usec = 0;
424 len = strlen(rcpt) + 1;
425 return mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_DELRCPT, rcpt, len);
429 ** SMFI_REPLACEBODY -- send a body chunk to the MTA
431 ** Parameters:
432 ** ctx -- Opaque context structure
433 ** bodyp -- body chunk
434 ** bodylen -- length of body chunk
436 ** Returns:
437 ** MI_SUCCESS/MI_FAILURE
441 smfi_replacebody(ctx, bodyp, bodylen)
442 SMFICTX *ctx;
443 unsigned char *bodyp;
444 int bodylen;
446 int len, off, r;
447 struct timeval timeout;
449 if (bodylen < 0 ||
450 (bodyp == NULL && bodylen > 0))
451 return MI_FAILURE;
452 if (!mi_sendok(ctx, SMFIF_CHGBODY))
453 return MI_FAILURE;
454 timeout.tv_sec = ctx->ctx_timeout;
455 timeout.tv_usec = 0;
457 /* split body chunk if necessary */
458 off = 0;
461 len = (bodylen >= MILTER_CHUNK_SIZE) ? MILTER_CHUNK_SIZE :
462 bodylen;
463 if ((r = mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_REPLBODY,
464 (char *) (bodyp + off), len)) != MI_SUCCESS)
465 return r;
466 off += len;
467 bodylen -= len;
468 } while (bodylen > 0);
469 return MI_SUCCESS;
473 ** SMFI_QUARANTINE -- quarantine an envelope
475 ** Parameters:
476 ** ctx -- Opaque context structure
477 ** reason -- why?
479 ** Returns:
480 ** MI_SUCCESS/MI_FAILURE
484 smfi_quarantine(ctx, reason)
485 SMFICTX *ctx;
486 char *reason;
488 size_t len;
489 int r;
490 char *buf;
491 struct timeval timeout;
493 if (reason == NULL || *reason == '\0')
494 return MI_FAILURE;
495 if (!mi_sendok(ctx, SMFIF_QUARANTINE))
496 return MI_FAILURE;
497 timeout.tv_sec = ctx->ctx_timeout;
498 timeout.tv_usec = 0;
499 len = strlen(reason) + 1;
500 buf = malloc(len);
501 if (buf == NULL)
502 return MI_FAILURE;
503 (void) memcpy(buf, reason, len);
504 r = mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_QUARANTINE, buf, len);
505 free(buf);
506 return r;
510 ** MYISENHSC -- check whether a string contains an enhanced status code
512 ** Parameters:
513 ** s -- string with possible enhanced status code.
514 ** delim -- delim for enhanced status code.
516 ** Returns:
517 ** 0 -- no enhanced status code.
518 ** >4 -- length of enhanced status code.
520 ** Side Effects:
521 ** none.
524 static int
525 myisenhsc(s, delim)
526 const char *s;
527 int delim;
529 int l, h;
531 if (s == NULL)
532 return 0;
533 if (!((*s == '2' || *s == '4' || *s == '5') && s[1] == '.'))
534 return 0;
535 h = 0;
536 l = 2;
537 while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h]))
538 ++h;
539 if (h == 0 || s[l + h] != '.')
540 return 0;
541 l += h + 1;
542 h = 0;
543 while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h]))
544 ++h;
545 if (h == 0 || s[l + h] != delim)
546 return 0;
547 return l + h;
551 ** SMFI_SETREPLY -- set the reply code for the next reply to the MTA
553 ** Parameters:
554 ** ctx -- Opaque context structure
555 ** rcode -- The three-digit (RFC 821) SMTP reply code.
556 ** xcode -- The extended (RFC 2034) reply code.
557 ** message -- The text part of the SMTP reply.
559 ** Returns:
560 ** MI_SUCCESS/MI_FAILURE
564 smfi_setreply(ctx, rcode, xcode, message)
565 SMFICTX *ctx;
566 char *rcode;
567 char *xcode;
568 char *message;
570 size_t len;
571 char *buf;
573 if (rcode == NULL || ctx == NULL)
574 return MI_FAILURE;
576 /* ### <sp> \0 */
577 len = strlen(rcode) + 2;
578 if (len != 5)
579 return MI_FAILURE;
580 if ((rcode[0] != '4' && rcode[0] != '5') ||
581 !isascii(rcode[1]) || !isdigit(rcode[1]) ||
582 !isascii(rcode[2]) || !isdigit(rcode[2]))
583 return MI_FAILURE;
584 if (xcode != NULL)
586 if (!myisenhsc(xcode, '\0'))
587 return MI_FAILURE;
588 len += strlen(xcode) + 1;
590 if (message != NULL)
592 size_t ml;
594 /* XXX check also for unprintable chars? */
595 if (strpbrk(message, "\r\n") != NULL)
596 return MI_FAILURE;
597 ml = strlen(message);
598 if (ml > MAXREPLYLEN)
599 return MI_FAILURE;
600 len += ml + 1;
602 buf = malloc(len);
603 if (buf == NULL)
604 return MI_FAILURE; /* oops */
605 (void) sm_strlcpy(buf, rcode, len);
606 (void) sm_strlcat(buf, " ", len);
607 if (xcode != NULL)
608 (void) sm_strlcat(buf, xcode, len);
609 if (message != NULL)
611 if (xcode != NULL)
612 (void) sm_strlcat(buf, " ", len);
613 (void) sm_strlcat(buf, message, len);
615 if (ctx->ctx_reply != NULL)
616 free(ctx->ctx_reply);
617 ctx->ctx_reply = buf;
618 return MI_SUCCESS;
622 ** SMFI_SETMLREPLY -- set multiline reply code for the next reply to the MTA
624 ** Parameters:
625 ** ctx -- Opaque context structure
626 ** rcode -- The three-digit (RFC 821) SMTP reply code.
627 ** xcode -- The extended (RFC 2034) reply code.
628 ** txt, ... -- The text part of the SMTP reply,
629 ** MUST be terminated with NULL.
631 ** Returns:
632 ** MI_SUCCESS/MI_FAILURE
636 #if SM_VA_STD
637 smfi_setmlreply(SMFICTX *ctx, const char *rcode, const char *xcode, ...)
638 #else /* SM_VA_STD */
639 smfi_setmlreply(ctx, rcode, xcode, va_alist)
640 SMFICTX *ctx;
641 const char *rcode;
642 const char *xcode;
643 va_dcl
644 #endif /* SM_VA_STD */
646 size_t len;
647 size_t rlen;
648 int args;
649 char *buf, *txt;
650 const char *xc;
651 char repl[16];
652 SM_VA_LOCAL_DECL
654 if (rcode == NULL || ctx == NULL)
655 return MI_FAILURE;
657 /* ### <sp> */
658 len = strlen(rcode) + 1;
659 if (len != 4)
660 return MI_FAILURE;
661 if ((rcode[0] != '4' && rcode[0] != '5') ||
662 !isascii(rcode[1]) || !isdigit(rcode[1]) ||
663 !isascii(rcode[2]) || !isdigit(rcode[2]))
664 return MI_FAILURE;
665 if (xcode != NULL)
667 if (!myisenhsc(xcode, '\0'))
668 return MI_FAILURE;
669 xc = xcode;
671 else
673 if (rcode[0] == '4')
674 xc = "4.0.0";
675 else
676 xc = "5.0.0";
679 /* add trailing space */
680 len += strlen(xc) + 1;
681 rlen = len;
682 args = 0;
683 SM_VA_START(ap, xcode);
684 while ((txt = SM_VA_ARG(ap, char *)) != NULL)
686 size_t tl;
688 tl = strlen(txt);
689 if (tl > MAXREPLYLEN)
690 break;
692 /* this text, reply codes, \r\n */
693 len += tl + 2 + rlen;
694 if (++args > MAXREPLIES)
695 break;
697 /* XXX check also for unprintable chars? */
698 if (strpbrk(txt, "\r\n") != NULL)
699 break;
701 SM_VA_END(ap);
702 if (txt != NULL)
703 return MI_FAILURE;
705 /* trailing '\0' */
706 ++len;
707 buf = malloc(len);
708 if (buf == NULL)
709 return MI_FAILURE; /* oops */
710 (void) sm_strlcpyn(buf, len, 3, rcode, args == 1 ? " " : "-", xc);
711 (void) sm_strlcpyn(repl, sizeof repl, 4, rcode, args == 1 ? " " : "-",
712 xc, " ");
713 SM_VA_START(ap, xcode);
714 txt = SM_VA_ARG(ap, char *);
715 if (txt != NULL)
717 (void) sm_strlcat2(buf, " ", txt, len);
718 while ((txt = SM_VA_ARG(ap, char *)) != NULL)
720 if (--args <= 1)
721 repl[3] = ' ';
722 (void) sm_strlcat2(buf, "\r\n", repl, len);
723 (void) sm_strlcat(buf, txt, len);
726 if (ctx->ctx_reply != NULL)
727 free(ctx->ctx_reply);
728 ctx->ctx_reply = buf;
729 SM_VA_END(ap);
730 return MI_SUCCESS;
734 ** SMFI_SETPRIV -- set private data
736 ** Parameters:
737 ** ctx -- Opaque context structure
738 ** privatedata -- pointer to private data
740 ** Returns:
741 ** MI_SUCCESS/MI_FAILURE
745 smfi_setpriv(ctx, privatedata)
746 SMFICTX *ctx;
747 void *privatedata;
749 if (ctx == NULL)
750 return MI_FAILURE;
751 ctx->ctx_privdata = privatedata;
752 return MI_SUCCESS;
756 ** SMFI_GETPRIV -- get private data
758 ** Parameters:
759 ** ctx -- Opaque context structure
761 ** Returns:
762 ** pointer to private data
765 void *
766 smfi_getpriv(ctx)
767 SMFICTX *ctx;
769 if (ctx == NULL)
770 return NULL;
771 return ctx->ctx_privdata;
775 ** SMFI_GETSYMVAL -- get the value of a macro
777 ** See explanation in mfapi.h about layout of the structures.
779 ** Parameters:
780 ** ctx -- Opaque context structure
781 ** symname -- name of macro
783 ** Returns:
784 ** value of macro (NULL in case of failure)
787 char *
788 smfi_getsymval(ctx, symname)
789 SMFICTX *ctx;
790 char *symname;
792 int i;
793 char **s;
794 char one[2];
795 char braces[4];
797 if (ctx == NULL || symname == NULL || *symname == '\0')
798 return NULL;
800 if (strlen(symname) == 3 && symname[0] == '{' && symname[2] == '}')
802 one[0] = symname[1];
803 one[1] = '\0';
805 else
806 one[0] = '\0';
807 if (strlen(symname) == 1)
809 braces[0] = '{';
810 braces[1] = *symname;
811 braces[2] = '}';
812 braces[3] = '\0';
814 else
815 braces[0] = '\0';
817 /* search backwards through the macro array */
818 for (i = MAX_MACROS_ENTRIES - 1 ; i >= 0; --i)
820 if ((s = ctx->ctx_mac_ptr[i]) == NULL ||
821 ctx->ctx_mac_buf[i] == NULL)
822 continue;
823 while (s != NULL && *s != NULL)
825 if (strcmp(*s, symname) == 0)
826 return *++s;
827 if (one[0] != '\0' && strcmp(*s, one) == 0)
828 return *++s;
829 if (braces[0] != '\0' && strcmp(*s, braces) == 0)
830 return *++s;
831 ++s; /* skip over macro value */
832 ++s; /* points to next macro name */
835 return NULL;
839 ** SMFI_PROGRESS -- send "progress" message to the MTA to prevent premature
840 ** timeouts during long milter-side operations
842 ** Parameters:
843 ** ctx -- Opaque context structure
845 ** Return value:
846 ** MI_SUCCESS/MI_FAILURE
850 smfi_progress(ctx)
851 SMFICTX *ctx;
853 struct timeval timeout;
855 if (ctx == NULL)
856 return MI_FAILURE;
858 timeout.tv_sec = ctx->ctx_timeout;
859 timeout.tv_usec = 0;
861 return mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_PROGRESS, NULL, 0);
865 ** SMFI_VERSION -- return (runtime) version of libmilter
867 ** Parameters:
868 ** major -- (pointer to) major version
869 ** minor -- (pointer to) minor version
870 ** patchlevel -- (pointer to) patchlevel version
872 ** Return value:
873 ** MI_SUCCESS
877 smfi_version(major, minor, patchlevel)
878 unsigned int *major;
879 unsigned int *minor;
880 unsigned int *patchlevel;
882 if (major != NULL)
883 *major = SM_LM_VRS_MAJOR(SMFI_VERSION);
884 if (minor != NULL)
885 *minor = SM_LM_VRS_MINOR(SMFI_VERSION);
886 if (patchlevel != NULL)
887 *patchlevel = SM_LM_VRS_PLVL(SMFI_VERSION);
888 return MI_SUCCESS;