8322 nl: misleading-indentation
[unleashed/tickless.git] / usr / src / cmd / sendmail / libmilter / smfi.c
blobaf034481ee90d4dd8b021e0507a17d988a0b4070
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 #pragma ident "%Z%%M% %I% %E% SMI"
13 #include <sm/gen.h>
14 SM_RCSID("@(#)$Id: smfi.c,v 8.83 2007/04/23 16:44:39 ca Exp $")
15 #include <sm/varargs.h>
16 #include "libmilter.h"
18 static int smfi_header __P((SMFICTX *, int, int, char *, char *));
19 static int myisenhsc __P((const char *, int));
21 /* for smfi_set{ml}reply, let's be generous. 256/16 should be sufficient */
22 #define MAXREPLYLEN 980 /* max. length of a reply string */
23 #define MAXREPLIES 32 /* max. number of reply strings */
26 ** SMFI_HEADER -- send a header to the MTA
28 ** Parameters:
29 ** ctx -- Opaque context structure
30 ** cmd -- Header modification command
31 ** hdridx -- Header index
32 ** headerf -- Header field name
33 ** headerv -- Header field value
35 ** Returns:
36 ** MI_SUCCESS/MI_FAILURE
39 static int
40 smfi_header(ctx, cmd, hdridx, headerf, headerv)
41 SMFICTX *ctx;
42 int cmd;
43 int hdridx;
44 char *headerf;
45 char *headerv;
47 size_t len, l1, l2, offset;
48 int r;
49 mi_int32 v;
50 char *buf;
51 struct timeval timeout;
53 if (headerf == NULL || *headerf == '\0' || headerv == NULL)
54 return MI_FAILURE;
55 timeout.tv_sec = ctx->ctx_timeout;
56 timeout.tv_usec = 0;
57 l1 = strlen(headerf) + 1;
58 l2 = strlen(headerv) + 1;
59 len = l1 + l2;
60 if (hdridx >= 0)
61 len += MILTER_LEN_BYTES;
62 buf = malloc(len);
63 if (buf == NULL)
64 return MI_FAILURE;
65 offset = 0;
66 if (hdridx >= 0)
68 v = htonl(hdridx);
69 (void) memcpy(&(buf[0]), (void *) &v, MILTER_LEN_BYTES);
70 offset += MILTER_LEN_BYTES;
72 (void) memcpy(buf + offset, headerf, l1);
73 (void) memcpy(buf + offset + l1, headerv, l2);
74 r = mi_wr_cmd(ctx->ctx_sd, &timeout, cmd, buf, len);
75 free(buf);
76 return r;
80 ** SMFI_ADDHEADER -- send a new header to the MTA
82 ** Parameters:
83 ** ctx -- Opaque context structure
84 ** headerf -- Header field name
85 ** headerv -- Header field value
87 ** Returns:
88 ** MI_SUCCESS/MI_FAILURE
91 int
92 smfi_addheader(ctx, headerf, headerv)
93 SMFICTX *ctx;
94 char *headerf;
95 char *headerv;
97 if (!mi_sendok(ctx, SMFIF_ADDHDRS))
98 return MI_FAILURE;
100 return smfi_header(ctx, SMFIR_ADDHEADER, -1, headerf, headerv);
104 ** SMFI_INSHEADER -- send a new header to the MTA (to be inserted)
106 ** Parameters:
107 ** ctx -- Opaque context structure
108 ** hdridx -- index into header list where insertion should occur
109 ** headerf -- Header field name
110 ** headerv -- Header field value
112 ** Returns:
113 ** MI_SUCCESS/MI_FAILURE
117 smfi_insheader(ctx, hdridx, headerf, headerv)
118 SMFICTX *ctx;
119 int hdridx;
120 char *headerf;
121 char *headerv;
123 if (!mi_sendok(ctx, SMFIF_ADDHDRS) || hdridx < 0)
124 return MI_FAILURE;
126 return smfi_header(ctx, SMFIR_INSHEADER, hdridx, headerf, headerv);
130 ** SMFI_CHGHEADER -- send a changed header to the MTA
132 ** Parameters:
133 ** ctx -- Opaque context structure
134 ** headerf -- Header field name
135 ** hdridx -- Header index value
136 ** headerv -- Header field value
138 ** Returns:
139 ** MI_SUCCESS/MI_FAILURE
143 smfi_chgheader(ctx, headerf, hdridx, headerv)
144 SMFICTX *ctx;
145 char *headerf;
146 mi_int32 hdridx;
147 char *headerv;
149 if (!mi_sendok(ctx, SMFIF_CHGHDRS) || hdridx < 0)
150 return MI_FAILURE;
151 if (headerv == NULL)
152 headerv = "";
154 return smfi_header(ctx, SMFIR_CHGHEADER, hdridx, headerf, headerv);
157 #if 0
159 ** BUF_CRT_SEND -- construct buffer to send from arguments
161 ** Parameters:
162 ** ctx -- Opaque context structure
163 ** cmd -- command
164 ** arg0 -- first argument
165 ** argv -- list of arguments (NULL terminated)
167 ** Returns:
168 ** MI_SUCCESS/MI_FAILURE
171 static int
172 buf_crt_send __P((SMFICTX *, int cmd, char *, char **));
174 static int
175 buf_crt_send(ctx, cmd, arg0, argv)
176 SMFICTX *ctx;
177 int cmd;
178 char *arg0;
179 char **argv;
181 size_t len, l0, l1, offset;
182 int r;
183 char *buf, *arg, **argvl;
184 struct timeval timeout;
186 if (arg0 == NULL || *arg0 == '\0')
187 return MI_FAILURE;
188 timeout.tv_sec = ctx->ctx_timeout;
189 timeout.tv_usec = 0;
190 l0 = strlen(arg0) + 1;
191 len = l0;
192 argvl = argv;
193 while (argvl != NULL && (arg = *argv) != NULL && *arg != '\0')
195 l1 = strlen(arg) + 1;
196 len += l1;
197 SM_ASSERT(len > l1);
200 buf = malloc(len);
201 if (buf == NULL)
202 return MI_FAILURE;
203 (void) memcpy(buf, arg0, l0);
204 offset = l0;
206 argvl = argv;
207 while (argvl != NULL && (arg = *argv) != NULL && *arg != '\0')
209 l1 = strlen(arg) + 1;
210 SM_ASSERT(offset < len);
211 SM_ASSERT(offset + l1 <= len);
212 (void) memcpy(buf + offset, arg, l1);
213 offset += l1;
214 SM_ASSERT(offset > l1);
217 r = mi_wr_cmd(ctx->ctx_sd, &timeout, cmd, buf, len);
218 free(buf);
219 return r;
221 #endif /* 0 */
224 ** SEND2 -- construct buffer to send from arguments
226 ** Parameters:
227 ** ctx -- Opaque context structure
228 ** cmd -- command
229 ** arg0 -- first argument
230 ** argv -- list of arguments (NULL terminated)
232 ** Returns:
233 ** MI_SUCCESS/MI_FAILURE
236 static int
237 send2 __P((SMFICTX *, int cmd, char *, char *));
239 static int
240 send2(ctx, cmd, arg0, arg1)
241 SMFICTX *ctx;
242 int cmd;
243 char *arg0;
244 char *arg1;
246 size_t len, l0, l1, offset;
247 int r;
248 char *buf;
249 struct timeval timeout;
251 if (arg0 == NULL || *arg0 == '\0')
252 return MI_FAILURE;
253 timeout.tv_sec = ctx->ctx_timeout;
254 timeout.tv_usec = 0;
255 l0 = strlen(arg0) + 1;
256 len = l0;
257 if (arg1 != NULL)
259 l1 = strlen(arg1) + 1;
260 len += l1;
261 SM_ASSERT(len > l1);
264 buf = malloc(len);
265 if (buf == NULL)
266 return MI_FAILURE;
267 (void) memcpy(buf, arg0, l0);
268 offset = l0;
270 if (arg1 != NULL)
272 l1 = strlen(arg1) + 1;
273 SM_ASSERT(offset < len);
274 SM_ASSERT(offset + l1 <= len);
275 (void) memcpy(buf + offset, arg1, l1);
276 offset += l1;
277 SM_ASSERT(offset > l1);
280 r = mi_wr_cmd(ctx->ctx_sd, &timeout, cmd, buf, len);
281 free(buf);
282 return r;
286 ** SMFI_CHGFROM -- change enveloper sender ("from") address
288 ** Parameters:
289 ** ctx -- Opaque context structure
290 ** from -- new envelope sender address ("MAIL From")
291 ** args -- ESMTP arguments
293 ** Returns:
294 ** MI_SUCCESS/MI_FAILURE
298 smfi_chgfrom(ctx, from, args)
299 SMFICTX *ctx;
300 char *from;
301 char *args;
303 if (from == NULL || *from == '\0')
304 return MI_FAILURE;
305 if (!mi_sendok(ctx, SMFIF_CHGFROM))
306 return MI_FAILURE;
307 return send2(ctx, SMFIR_CHGFROM, from, args);
311 ** SMFI_SETSYMLIST -- set list of macros that the MTA should send.
313 ** Parameters:
314 ** ctx -- Opaque context structure
315 ** where -- SMTP stage
316 ** macros -- list of macros
318 ** Returns:
319 ** MI_SUCCESS/MI_FAILURE
323 smfi_setsymlist(ctx, where, macros)
324 SMFICTX *ctx;
325 int where;
326 char *macros;
328 SM_ASSERT(ctx != NULL);
330 if (macros == NULL || *macros == '\0')
331 return MI_FAILURE;
332 if (where < SMFIM_FIRST || where > SMFIM_LAST)
333 return MI_FAILURE;
334 if (where < 0 || where >= MAX_MACROS_ENTRIES)
335 return MI_FAILURE;
337 if (ctx->ctx_mac_list[where] != NULL)
338 return MI_FAILURE;
340 ctx->ctx_mac_list[where] = strdup(macros);
341 if (ctx->ctx_mac_list[where] == NULL)
342 return MI_FAILURE;
344 return MI_SUCCESS;
348 ** SMFI_ADDRCPT_PAR -- send an additional recipient to the MTA
350 ** Parameters:
351 ** ctx -- Opaque context structure
352 ** rcpt -- recipient address
353 ** args -- ESMTP arguments
355 ** Returns:
356 ** MI_SUCCESS/MI_FAILURE
360 smfi_addrcpt_par(ctx, rcpt, args)
361 SMFICTX *ctx;
362 char *rcpt;
363 char *args;
365 if (rcpt == NULL || *rcpt == '\0')
366 return MI_FAILURE;
367 if (!mi_sendok(ctx, SMFIF_ADDRCPT_PAR))
368 return MI_FAILURE;
369 return send2(ctx, SMFIR_ADDRCPT_PAR, rcpt, args);
373 ** SMFI_ADDRCPT -- send an additional recipient to the MTA
375 ** Parameters:
376 ** ctx -- Opaque context structure
377 ** rcpt -- recipient address
379 ** Returns:
380 ** MI_SUCCESS/MI_FAILURE
384 smfi_addrcpt(ctx, rcpt)
385 SMFICTX *ctx;
386 char *rcpt;
388 size_t len;
389 struct timeval timeout;
391 if (rcpt == NULL || *rcpt == '\0')
392 return MI_FAILURE;
393 if (!mi_sendok(ctx, SMFIF_ADDRCPT))
394 return MI_FAILURE;
395 timeout.tv_sec = ctx->ctx_timeout;
396 timeout.tv_usec = 0;
397 len = strlen(rcpt) + 1;
398 return mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_ADDRCPT, rcpt, len);
402 ** SMFI_DELRCPT -- send a recipient to be removed to the MTA
404 ** Parameters:
405 ** ctx -- Opaque context structure
406 ** rcpt -- recipient address
408 ** Returns:
409 ** MI_SUCCESS/MI_FAILURE
413 smfi_delrcpt(ctx, rcpt)
414 SMFICTX *ctx;
415 char *rcpt;
417 size_t len;
418 struct timeval timeout;
420 if (rcpt == NULL || *rcpt == '\0')
421 return MI_FAILURE;
422 if (!mi_sendok(ctx, SMFIF_DELRCPT))
423 return MI_FAILURE;
424 timeout.tv_sec = ctx->ctx_timeout;
425 timeout.tv_usec = 0;
426 len = strlen(rcpt) + 1;
427 return mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_DELRCPT, rcpt, len);
431 ** SMFI_REPLACEBODY -- send a body chunk to the MTA
433 ** Parameters:
434 ** ctx -- Opaque context structure
435 ** bodyp -- body chunk
436 ** bodylen -- length of body chunk
438 ** Returns:
439 ** MI_SUCCESS/MI_FAILURE
443 smfi_replacebody(ctx, bodyp, bodylen)
444 SMFICTX *ctx;
445 unsigned char *bodyp;
446 int bodylen;
448 int len, off, r;
449 struct timeval timeout;
451 if (bodylen < 0 ||
452 (bodyp == NULL && bodylen > 0))
453 return MI_FAILURE;
454 if (!mi_sendok(ctx, SMFIF_CHGBODY))
455 return MI_FAILURE;
456 timeout.tv_sec = ctx->ctx_timeout;
457 timeout.tv_usec = 0;
459 /* split body chunk if necessary */
460 off = 0;
463 len = (bodylen >= MILTER_CHUNK_SIZE) ? MILTER_CHUNK_SIZE :
464 bodylen;
465 if ((r = mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_REPLBODY,
466 (char *) (bodyp + off), len)) != MI_SUCCESS)
467 return r;
468 off += len;
469 bodylen -= len;
470 } while (bodylen > 0);
471 return MI_SUCCESS;
475 ** SMFI_QUARANTINE -- quarantine an envelope
477 ** Parameters:
478 ** ctx -- Opaque context structure
479 ** reason -- why?
481 ** Returns:
482 ** MI_SUCCESS/MI_FAILURE
486 smfi_quarantine(ctx, reason)
487 SMFICTX *ctx;
488 char *reason;
490 size_t len;
491 int r;
492 char *buf;
493 struct timeval timeout;
495 if (reason == NULL || *reason == '\0')
496 return MI_FAILURE;
497 if (!mi_sendok(ctx, SMFIF_QUARANTINE))
498 return MI_FAILURE;
499 timeout.tv_sec = ctx->ctx_timeout;
500 timeout.tv_usec = 0;
501 len = strlen(reason) + 1;
502 buf = malloc(len);
503 if (buf == NULL)
504 return MI_FAILURE;
505 (void) memcpy(buf, reason, len);
506 r = mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_QUARANTINE, buf, len);
507 free(buf);
508 return r;
512 ** MYISENHSC -- check whether a string contains an enhanced status code
514 ** Parameters:
515 ** s -- string with possible enhanced status code.
516 ** delim -- delim for enhanced status code.
518 ** Returns:
519 ** 0 -- no enhanced status code.
520 ** >4 -- length of enhanced status code.
522 ** Side Effects:
523 ** none.
526 static int
527 myisenhsc(s, delim)
528 const char *s;
529 int delim;
531 int l, h;
533 if (s == NULL)
534 return 0;
535 if (!((*s == '2' || *s == '4' || *s == '5') && s[1] == '.'))
536 return 0;
537 h = 0;
538 l = 2;
539 while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h]))
540 ++h;
541 if (h == 0 || s[l + h] != '.')
542 return 0;
543 l += h + 1;
544 h = 0;
545 while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h]))
546 ++h;
547 if (h == 0 || s[l + h] != delim)
548 return 0;
549 return l + h;
553 ** SMFI_SETREPLY -- set the reply code for the next reply to the MTA
555 ** Parameters:
556 ** ctx -- Opaque context structure
557 ** rcode -- The three-digit (RFC 821) SMTP reply code.
558 ** xcode -- The extended (RFC 2034) reply code.
559 ** message -- The text part of the SMTP reply.
561 ** Returns:
562 ** MI_SUCCESS/MI_FAILURE
566 smfi_setreply(ctx, rcode, xcode, message)
567 SMFICTX *ctx;
568 char *rcode;
569 char *xcode;
570 char *message;
572 size_t len;
573 char *buf;
575 if (rcode == NULL || ctx == NULL)
576 return MI_FAILURE;
578 /* ### <sp> \0 */
579 len = strlen(rcode) + 2;
580 if (len != 5)
581 return MI_FAILURE;
582 if ((rcode[0] != '4' && rcode[0] != '5') ||
583 !isascii(rcode[1]) || !isdigit(rcode[1]) ||
584 !isascii(rcode[2]) || !isdigit(rcode[2]))
585 return MI_FAILURE;
586 if (xcode != NULL)
588 if (!myisenhsc(xcode, '\0'))
589 return MI_FAILURE;
590 len += strlen(xcode) + 1;
592 if (message != NULL)
594 size_t ml;
596 /* XXX check also for unprintable chars? */
597 if (strpbrk(message, "\r\n") != NULL)
598 return MI_FAILURE;
599 ml = strlen(message);
600 if (ml > MAXREPLYLEN)
601 return MI_FAILURE;
602 len += ml + 1;
604 buf = malloc(len);
605 if (buf == NULL)
606 return MI_FAILURE; /* oops */
607 (void) sm_strlcpy(buf, rcode, len);
608 (void) sm_strlcat(buf, " ", len);
609 if (xcode != NULL)
610 (void) sm_strlcat(buf, xcode, len);
611 if (message != NULL)
613 if (xcode != NULL)
614 (void) sm_strlcat(buf, " ", len);
615 (void) sm_strlcat(buf, message, len);
617 if (ctx->ctx_reply != NULL)
618 free(ctx->ctx_reply);
619 ctx->ctx_reply = buf;
620 return MI_SUCCESS;
624 ** SMFI_SETMLREPLY -- set multiline reply code for the next reply to the MTA
626 ** Parameters:
627 ** ctx -- Opaque context structure
628 ** rcode -- The three-digit (RFC 821) SMTP reply code.
629 ** xcode -- The extended (RFC 2034) reply code.
630 ** txt, ... -- The text part of the SMTP reply,
631 ** MUST be terminated with NULL.
633 ** Returns:
634 ** MI_SUCCESS/MI_FAILURE
638 #if SM_VA_STD
639 smfi_setmlreply(SMFICTX *ctx, const char *rcode, const char *xcode, ...)
640 #else /* SM_VA_STD */
641 smfi_setmlreply(ctx, rcode, xcode, va_alist)
642 SMFICTX *ctx;
643 const char *rcode;
644 const char *xcode;
645 va_dcl
646 #endif /* SM_VA_STD */
648 size_t len;
649 size_t rlen;
650 int args;
651 char *buf, *txt;
652 const char *xc;
653 char repl[16];
654 SM_VA_LOCAL_DECL
656 if (rcode == NULL || ctx == NULL)
657 return MI_FAILURE;
659 /* ### <sp> */
660 len = strlen(rcode) + 1;
661 if (len != 4)
662 return MI_FAILURE;
663 if ((rcode[0] != '4' && rcode[0] != '5') ||
664 !isascii(rcode[1]) || !isdigit(rcode[1]) ||
665 !isascii(rcode[2]) || !isdigit(rcode[2]))
666 return MI_FAILURE;
667 if (xcode != NULL)
669 if (!myisenhsc(xcode, '\0'))
670 return MI_FAILURE;
671 xc = xcode;
673 else
675 if (rcode[0] == '4')
676 xc = "4.0.0";
677 else
678 xc = "5.0.0";
681 /* add trailing space */
682 len += strlen(xc) + 1;
683 rlen = len;
684 args = 0;
685 SM_VA_START(ap, xcode);
686 while ((txt = SM_VA_ARG(ap, char *)) != NULL)
688 size_t tl;
690 tl = strlen(txt);
691 if (tl > MAXREPLYLEN)
692 break;
694 /* this text, reply codes, \r\n */
695 len += tl + 2 + rlen;
696 if (++args > MAXREPLIES)
697 break;
699 /* XXX check also for unprintable chars? */
700 if (strpbrk(txt, "\r\n") != NULL)
701 break;
703 SM_VA_END(ap);
704 if (txt != NULL)
705 return MI_FAILURE;
707 /* trailing '\0' */
708 ++len;
709 buf = malloc(len);
710 if (buf == NULL)
711 return MI_FAILURE; /* oops */
712 (void) sm_strlcpyn(buf, len, 3, rcode, args == 1 ? " " : "-", xc);
713 (void) sm_strlcpyn(repl, sizeof repl, 4, rcode, args == 1 ? " " : "-",
714 xc, " ");
715 SM_VA_START(ap, xcode);
716 txt = SM_VA_ARG(ap, char *);
717 if (txt != NULL)
719 (void) sm_strlcat2(buf, " ", txt, len);
720 while ((txt = SM_VA_ARG(ap, char *)) != NULL)
722 if (--args <= 1)
723 repl[3] = ' ';
724 (void) sm_strlcat2(buf, "\r\n", repl, len);
725 (void) sm_strlcat(buf, txt, len);
728 if (ctx->ctx_reply != NULL)
729 free(ctx->ctx_reply);
730 ctx->ctx_reply = buf;
731 SM_VA_END(ap);
732 return MI_SUCCESS;
736 ** SMFI_SETPRIV -- set private data
738 ** Parameters:
739 ** ctx -- Opaque context structure
740 ** privatedata -- pointer to private data
742 ** Returns:
743 ** MI_SUCCESS/MI_FAILURE
747 smfi_setpriv(ctx, privatedata)
748 SMFICTX *ctx;
749 void *privatedata;
751 if (ctx == NULL)
752 return MI_FAILURE;
753 ctx->ctx_privdata = privatedata;
754 return MI_SUCCESS;
758 ** SMFI_GETPRIV -- get private data
760 ** Parameters:
761 ** ctx -- Opaque context structure
763 ** Returns:
764 ** pointer to private data
767 void *
768 smfi_getpriv(ctx)
769 SMFICTX *ctx;
771 if (ctx == NULL)
772 return NULL;
773 return ctx->ctx_privdata;
777 ** SMFI_GETSYMVAL -- get the value of a macro
779 ** See explanation in mfapi.h about layout of the structures.
781 ** Parameters:
782 ** ctx -- Opaque context structure
783 ** symname -- name of macro
785 ** Returns:
786 ** value of macro (NULL in case of failure)
789 char *
790 smfi_getsymval(ctx, symname)
791 SMFICTX *ctx;
792 char *symname;
794 int i;
795 char **s;
796 char one[2];
797 char braces[4];
799 if (ctx == NULL || symname == NULL || *symname == '\0')
800 return NULL;
802 if (strlen(symname) == 3 && symname[0] == '{' && symname[2] == '}')
804 one[0] = symname[1];
805 one[1] = '\0';
807 else
808 one[0] = '\0';
809 if (strlen(symname) == 1)
811 braces[0] = '{';
812 braces[1] = *symname;
813 braces[2] = '}';
814 braces[3] = '\0';
816 else
817 braces[0] = '\0';
819 /* search backwards through the macro array */
820 for (i = MAX_MACROS_ENTRIES - 1 ; i >= 0; --i)
822 if ((s = ctx->ctx_mac_ptr[i]) == NULL ||
823 ctx->ctx_mac_buf[i] == NULL)
824 continue;
825 while (s != NULL && *s != NULL)
827 if (strcmp(*s, symname) == 0)
828 return *++s;
829 if (one[0] != '\0' && strcmp(*s, one) == 0)
830 return *++s;
831 if (braces[0] != '\0' && strcmp(*s, braces) == 0)
832 return *++s;
833 ++s; /* skip over macro value */
834 ++s; /* points to next macro name */
837 return NULL;
841 ** SMFI_PROGRESS -- send "progress" message to the MTA to prevent premature
842 ** timeouts during long milter-side operations
844 ** Parameters:
845 ** ctx -- Opaque context structure
847 ** Return value:
848 ** MI_SUCCESS/MI_FAILURE
852 smfi_progress(ctx)
853 SMFICTX *ctx;
855 struct timeval timeout;
857 if (ctx == NULL)
858 return MI_FAILURE;
860 timeout.tv_sec = ctx->ctx_timeout;
861 timeout.tv_usec = 0;
863 return mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_PROGRESS, NULL, 0);
867 ** SMFI_VERSION -- return (runtime) version of libmilter
869 ** Parameters:
870 ** major -- (pointer to) major version
871 ** minor -- (pointer to) minor version
872 ** patchlevel -- (pointer to) patchlevel version
874 ** Return value:
875 ** MI_SUCCESS
879 smfi_version(major, minor, patchlevel)
880 unsigned int *major;
881 unsigned int *minor;
882 unsigned int *patchlevel;
884 if (major != NULL)
885 *major = SM_LM_VRS_MAJOR(SMFI_VERSION);
886 if (minor != NULL)
887 *minor = SM_LM_VRS_MINOR(SMFI_VERSION);
888 if (patchlevel != NULL)
889 *patchlevel = SM_LM_VRS_PLVL(SMFI_VERSION);
890 return MI_SUCCESS;