1 /***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at https://curl.se/docs/copyright.html.
13 *
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 * SPDX-License-Identifier: curl
22 *
23 ***************************************************************************/
24 #include "tool_setup.h"
25
26 #include "strcase.h"
27
28 #include "curlx.h"
29
30 #include "tool_cfgable.h"
31 #include "tool_getparam.h"
32 #include "tool_getpass.h"
33 #include "tool_msgs.h"
34 #include "tool_paramhlp.h"
35 #include "tool_libinfo.h"
36 #include "tool_util.h"
37 #include "tool_version.h"
38 #include "dynbuf.h"
39
40 #include "memdebug.h" /* keep this as LAST include */
41
new_getout(struct OperationConfig * config)42 struct getout *new_getout(struct OperationConfig *config)
43 {
44 struct getout *node = calloc(1, sizeof(struct getout));
45 struct getout *last = config->url_last;
46 if(node) {
47 static int outnum = 0;
48
49 /* append this new node last in the list */
50 if(last)
51 last->next = node;
52 else
53 config->url_list = node; /* first node */
54
55 /* move the last pointer */
56 config->url_last = node;
57
58 node->flags = config->default_node_flags;
59 node->num = outnum++;
60 }
61 return node;
62 }
63
64 #define ISCRLF(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0'))
65
66 /* memcrlf() has two modes. Both operate on a given memory area with
67 a specified size.
68
69 countcrlf FALSE - return number of bytes from the start that DO NOT include
70 any CR or LF or NULL
71
72 countcrlf TRUE - return number of bytes from the start that are ONLY CR or
73 LF or NULL.
74
75 */
memcrlf(char * orig,bool countcrlf,size_t max)76 static size_t memcrlf(char *orig,
77 bool countcrlf, /* TRUE if we count CRLF, FALSE
78 if we count non-CRLF */
79 size_t max)
80 {
81 char *ptr;
82 size_t total = max;
83 for(ptr = orig; max; max--, ptr++) {
84 bool crlf = ISCRLF(*ptr);
85 if(countcrlf ^ crlf)
86 return ptr - orig;
87 }
88 return total; /* no delimiter found */
89 }
90
91 #define MAX_FILE2STRING MAX_FILE2MEMORY
92
file2string(char ** bufp,FILE * file)93 ParameterError file2string(char **bufp, FILE *file)
94 {
95 struct curlx_dynbuf dyn;
96 curlx_dyn_init(&dyn, MAX_FILE2STRING);
97 if(file) {
98 do {
99 char buffer[4096];
100 char *ptr;
101 size_t nread = fread(buffer, 1, sizeof(buffer), file);
102 if(ferror(file)) {
103 curlx_dyn_free(&dyn);
104 *bufp = NULL;
105 return PARAM_READ_ERROR;
106 }
107 ptr = buffer;
108 while(nread) {
109 size_t nlen = memcrlf(ptr, FALSE, nread);
110 if(curlx_dyn_addn(&dyn, ptr, nlen))
111 return PARAM_NO_MEM;
112 nread -= nlen;
113
114 if(nread) {
115 ptr += nlen;
116 nlen = memcrlf(ptr, TRUE, nread);
117 ptr += nlen;
118 nread -= nlen;
119 }
120 }
121 } while(!feof(file));
122 }
123 *bufp = curlx_dyn_ptr(&dyn);
124 return PARAM_OK;
125 }
126
file2memory(char ** bufp,size_t * size,FILE * file)127 ParameterError file2memory(char **bufp, size_t *size, FILE *file)
128 {
129 if(file) {
130 size_t nread;
131 struct curlx_dynbuf dyn;
132 /* The size needs to fit in an int later */
133 curlx_dyn_init(&dyn, MAX_FILE2MEMORY);
134 do {
135 char buffer[4096];
136 nread = fread(buffer, 1, sizeof(buffer), file);
137 if(ferror(file)) {
138 curlx_dyn_free(&dyn);
139 *size = 0;
140 *bufp = NULL;
141 return PARAM_READ_ERROR;
142 }
143 if(nread)
144 if(curlx_dyn_addn(&dyn, buffer, nread))
145 return PARAM_NO_MEM;
146 } while(!feof(file));
147 *size = curlx_dyn_len(&dyn);
148 *bufp = curlx_dyn_ptr(&dyn);
149 }
150 else {
151 *size = 0;
152 *bufp = NULL;
153 }
154 return PARAM_OK;
155 }
156
157 /*
158 * Parse the string and write the long in the given address. Return PARAM_OK
159 * on success, otherwise a parameter specific error enum.
160 *
161 * Since this function gets called with the 'nextarg' pointer from within the
162 * getparameter a lot, we must check it for NULL before accessing the str
163 * data.
164 */
getnum(long * val,const char * str,int base)165 static ParameterError getnum(long *val, const char *str, int base)
166 {
167 if(str) {
168 char *endptr = NULL;
169 long num;
170 if(!str[0])
171 return PARAM_BLANK_STRING;
172 errno = 0;
173 num = strtol(str, &endptr, base);
174 if(errno == ERANGE)
175 return PARAM_NUMBER_TOO_LARGE;
176 if((endptr != str) && (*endptr == '\0')) {
177 *val = num;
178 return PARAM_OK; /* Ok */
179 }
180 }
181 return PARAM_BAD_NUMERIC; /* badness */
182 }
183
str2num(long * val,const char * str)184 ParameterError str2num(long *val, const char *str)
185 {
186 return getnum(val, str, 10);
187 }
188
oct2nummax(long * val,const char * str,long max)189 ParameterError oct2nummax(long *val, const char *str, long max)
190 {
191 ParameterError result = getnum(val, str, 8);
192 if(result != PARAM_OK)
193 return result;
194 else if(*val > max)
195 return PARAM_NUMBER_TOO_LARGE;
196 else if(*val < 0)
197 return PARAM_NEGATIVE_NUMERIC;
198
199 return PARAM_OK;
200 }
201
202 /*
203 * Parse the string and write the long in the given address. Return PARAM_OK
204 * on success, otherwise a parameter error enum. ONLY ACCEPTS POSITIVE NUMBERS!
205 *
206 * Since this function gets called with the 'nextarg' pointer from within the
207 * getparameter a lot, we must check it for NULL before accessing the str
208 * data.
209 */
210
str2unum(long * val,const char * str)211 ParameterError str2unum(long *val, const char *str)
212 {
213 ParameterError result = getnum(val, str, 10);
214 if(result != PARAM_OK)
215 return result;
216 if(*val < 0)
217 return PARAM_NEGATIVE_NUMERIC;
218
219 return PARAM_OK;
220 }
221
222 /*
223 * Parse the string and write the long in the given address if it is below the
224 * maximum allowed value. Return PARAM_OK on success, otherwise a parameter
225 * error enum. ONLY ACCEPTS POSITIVE NUMBERS!
226 *
227 * Since this function gets called with the 'nextarg' pointer from within the
228 * getparameter a lot, we must check it for NULL before accessing the str
229 * data.
230 */
231
str2unummax(long * val,const char * str,long max)232 ParameterError str2unummax(long *val, const char *str, long max)
233 {
234 ParameterError result = str2unum(val, str);
235 if(result != PARAM_OK)
236 return result;
237 if(*val > max)
238 return PARAM_NUMBER_TOO_LARGE;
239
240 return PARAM_OK;
241 }
242
243
244 /*
245 * Parse the string and write the double in the given address. Return PARAM_OK
246 * on success, otherwise a parameter specific error enum.
247 *
248 * The 'max' argument is the maximum value allowed, as the numbers are often
249 * multiplied when later used.
250 *
251 * Since this function gets called with the 'nextarg' pointer from within the
252 * getparameter a lot, we must check it for NULL before accessing the str
253 * data.
254 */
255
str2double(double * val,const char * str,double max)256 static ParameterError str2double(double *val, const char *str, double max)
257 {
258 if(str) {
259 char *endptr;
260 double num;
261 errno = 0;
262 num = strtod(str, &endptr);
263 if(errno == ERANGE)
264 return PARAM_NUMBER_TOO_LARGE;
265 if(num > max) {
266 /* too large */
267 return PARAM_NUMBER_TOO_LARGE;
268 }
269 if((endptr != str) && (endptr == str + strlen(str))) {
270 *val = num;
271 return PARAM_OK; /* Ok */
272 }
273 }
274 return PARAM_BAD_NUMERIC; /* badness */
275 }
276
277 /*
278 * Parse the string as seconds with decimals, and write the number of
279 * milliseconds that corresponds in the given address. Return PARAM_OK on
280 * success, otherwise a parameter error enum. ONLY ACCEPTS POSITIVE NUMBERS!
281 *
282 * The 'max' argument is the maximum value allowed, as the numbers are often
283 * multiplied when later used.
284 *
285 * Since this function gets called with the 'nextarg' pointer from within the
286 * getparameter a lot, we must check it for NULL before accessing the str
287 * data.
288 */
289
secs2ms(long * valp,const char * str)290 ParameterError secs2ms(long *valp, const char *str)
291 {
292 double value;
293 ParameterError result = str2double(&value, str, (double)LONG_MAX/1000);
294 if(result != PARAM_OK)
295 return result;
296 if(value < 0)
297 return PARAM_NEGATIVE_NUMERIC;
298
299 *valp = (long)(value*1000);
300 return PARAM_OK;
301 }
302
303 /*
304 * Implement protocol sets in null-terminated array of protocol name pointers.
305 */
306
307 /* Return index of prototype token in set, card(set) if not found.
308 Can be called with proto == NULL to get card(set). */
protoset_index(const char * const * protoset,const char * proto)309 static size_t protoset_index(const char * const *protoset, const char *proto)
310 {
311 const char * const *p = protoset;
312
313 DEBUGASSERT(proto == proto_token(proto)); /* Ensure it is tokenized. */
314
315 for(; *p; p++)
316 if(proto == *p)
317 break;
318 return p - protoset;
319 }
320
321 /* Include protocol token in set. */
protoset_set(const char ** protoset,const char * proto)322 static void protoset_set(const char **protoset, const char *proto)
323 {
324 if(proto) {
325 size_t n = protoset_index(protoset, proto);
326
327 if(!protoset[n]) {
328 DEBUGASSERT(n < proto_count);
329 protoset[n] = proto;
330 protoset[n + 1] = NULL;
331 }
332 }
333 }
334
335 /* Exclude protocol token from set. */
protoset_clear(const char ** protoset,const char * proto)336 static void protoset_clear(const char **protoset, const char *proto)
337 {
338 if(proto) {
339 size_t n = protoset_index(protoset, proto);
340
341 if(protoset[n]) {
342 size_t m = protoset_index(protoset, NULL) - 1;
343
344 protoset[n] = protoset[m];
345 protoset[m] = NULL;
346 }
347 }
348 }
349
350 /*
351 * Parse the string and provide an allocated libcurl compatible protocol
352 * string output. Return non-zero on failure, zero on success.
353 *
354 * The string is a list of protocols
355 *
356 * Since this function gets called with the 'nextarg' pointer from within the
357 * getparameter a lot, we must check it for NULL before accessing the str
358 * data.
359 */
360
361 #define MAX_PROTOSTRING (64*11) /* Enough room for 64 10-chars proto names. */
362
proto2num(struct OperationConfig * config,const char * const * val,char ** ostr,const char * str)363 ParameterError proto2num(struct OperationConfig *config,
364 const char * const *val, char **ostr, const char *str)
365 {
366 char *buffer;
367 const char *sep = ",";
368 char *token;
369 const char **protoset;
370 struct curlx_dynbuf obuf;
371 size_t proto;
372 CURLcode result;
373
374 curlx_dyn_init(&obuf, MAX_PROTOSTRING);
375
376 if(!str)
377 return PARAM_OPTION_AMBIGUOUS;
378
379 buffer = strdup(str); /* because strtok corrupts it */
380 if(!buffer)
381 return PARAM_NO_MEM;
382
383 protoset = malloc((proto_count + 1) * sizeof(*protoset));
384 if(!protoset) {
385 free(buffer);
386 return PARAM_NO_MEM;
387 }
388
389 /* Preset protocol set with default values. */
390 protoset[0] = NULL;
391 for(; *val; val++) {
392 const char *p = proto_token(*val);
393
394 if(p)
395 protoset_set(protoset, p);
396 }
397
398 /* Allow strtok() here since this is not used threaded */
399 /* !checksrc! disable BANNEDFUNC 2 */
400 for(token = strtok(buffer, sep);
401 token;
402 token = strtok(NULL, sep)) {
403 enum e_action { allow, deny, set } action = allow;
404
405 /* Process token modifiers */
406 while(!ISALNUM(*token)) { /* may be NULL if token is all modifiers */
407 switch(*token++) {
408 case '=':
409 action = set;
410 break;
411 case '-':
412 action = deny;
413 break;
414 case '+':
415 action = allow;
416 break;
417 default: /* Includes case of terminating NULL */
418 free(buffer);
419 free((char *) protoset);
420 return PARAM_BAD_USE;
421 }
422 }
423
424 if(curl_strequal(token, "all")) {
425 switch(action) {
426 case deny:
427 protoset[0] = NULL;
428 break;
429 case allow:
430 case set:
431 memcpy((char *) protoset,
432 built_in_protos, (proto_count + 1) * sizeof(*protoset));
433 break;
434 }
435 }
436 else {
437 const char *p = proto_token(token);
438
439 if(p)
440 switch(action) {
441 case deny:
442 protoset_clear(protoset, p);
443 break;
444 case set:
445 protoset[0] = NULL;
446 FALLTHROUGH();
447 case allow:
448 protoset_set(protoset, p);
449 break;
450 }
451 else { /* unknown protocol */
452 /* If they have specified only this protocol, we say treat it as
453 if no protocols are allowed */
454 if(action == set)
455 protoset[0] = NULL;
456 warnf(config->global, "unrecognized protocol '%s'", token);
457 }
458 }
459 }
460 free(buffer);
461
462 /* We need the protocols in alphabetic order for CI tests requirements. */
463 qsort((char *) protoset, protoset_index(protoset, NULL), sizeof(*protoset),
464 struplocompare4sort);
465
466 result = curlx_dyn_addn(&obuf, "", 0);
467 for(proto = 0; protoset[proto] && !result; proto++)
468 result = curlx_dyn_addf(&obuf, "%s,", protoset[proto]);
469 free((char *) protoset);
470 curlx_dyn_setlen(&obuf, curlx_dyn_len(&obuf) - 1);
471 free(*ostr);
472 *ostr = curlx_dyn_ptr(&obuf);
473
474 return *ostr ? PARAM_OK : PARAM_NO_MEM;
475 }
476
477 /**
478 * Check if the given string is a protocol supported by libcurl
479 *
480 * @param str the protocol name
481 * @return PARAM_OK protocol supported
482 * @return PARAM_LIBCURL_UNSUPPORTED_PROTOCOL protocol not supported
483 * @return PARAM_REQUIRES_PARAMETER missing parameter
484 */
check_protocol(const char * str)485 ParameterError check_protocol(const char *str)
486 {
487 if(!str)
488 return PARAM_REQUIRES_PARAMETER;
489
490 if(proto_token(str))
491 return PARAM_OK;
492 return PARAM_LIBCURL_UNSUPPORTED_PROTOCOL;
493 }
494
495 /**
496 * Parses the given string looking for an offset (which may be a
497 * larger-than-integer value). The offset CANNOT be negative!
498 *
499 * @param val the offset to populate
500 * @param str the buffer containing the offset
501 * @return PARAM_OK if successful, a parameter specific error enum if failure.
502 */
str2offset(curl_off_t * val,const char * str)503 ParameterError str2offset(curl_off_t *val, const char *str)
504 {
505 char *endptr;
506 if(str[0] == '-')
507 /* offsets are not negative, this indicates weird input */
508 return PARAM_NEGATIVE_NUMERIC;
509
510 #if(SIZEOF_CURL_OFF_T > SIZEOF_LONG)
511 {
512 CURLofft offt = curlx_strtoofft(str, &endptr, 10, val);
513 if(CURL_OFFT_FLOW == offt)
514 return PARAM_NUMBER_TOO_LARGE;
515 else if(CURL_OFFT_INVAL == offt)
516 return PARAM_BAD_NUMERIC;
517 }
518 #else
519 errno = 0;
520 *val = strtol(str, &endptr, 0);
521 if((*val == LONG_MIN || *val == LONG_MAX) && errno == ERANGE)
522 return PARAM_NUMBER_TOO_LARGE;
523 #endif
524 if((endptr != str) && (endptr == str + strlen(str)))
525 return PARAM_OK;
526
527 return PARAM_BAD_NUMERIC;
528 }
529
530 #define MAX_USERPWDLENGTH (100*1024)
checkpasswd(const char * kind,const size_t i,const bool last,char ** userpwd)531 static CURLcode checkpasswd(const char *kind, /* for what purpose */
532 const size_t i, /* operation index */
533 const bool last, /* TRUE if last operation */
534 char **userpwd) /* pointer to allocated string */
535 {
536 char *psep;
537 char *osep;
538
539 if(!*userpwd)
540 return CURLE_OK;
541
542 /* Attempt to find the password separator */
543 psep = strchr(*userpwd, ':');
544
545 /* Attempt to find the options separator */
546 osep = strchr(*userpwd, ';');
547
548 if(!psep && **userpwd != ';') {
549 /* no password present, prompt for one */
550 char passwd[2048] = "";
551 char prompt[256];
552 struct curlx_dynbuf dyn;
553
554 curlx_dyn_init(&dyn, MAX_USERPWDLENGTH);
555 if(osep)
556 *osep = '\0';
557
558 /* build a nice-looking prompt */
559 if(!i && last)
560 msnprintf(prompt, sizeof(prompt),
561 "Enter %s password for user '%s':",
562 kind, *userpwd);
563 else
564 msnprintf(prompt, sizeof(prompt),
565 "Enter %s password for user '%s' on URL #%zu:",
566 kind, *userpwd, i + 1);
567
568 /* get password */
569 getpass_r(prompt, passwd, sizeof(passwd));
570 if(osep)
571 *osep = ';';
572
573 if(curlx_dyn_addf(&dyn, "%s:%s", *userpwd, passwd))
574 return CURLE_OUT_OF_MEMORY;
575
576 /* return the new string */
577 free(*userpwd);
578 *userpwd = curlx_dyn_ptr(&dyn);
579 }
580
581 return CURLE_OK;
582 }
583
add2list(struct curl_slist ** list,const char * ptr)584 ParameterError add2list(struct curl_slist **list, const char *ptr)
585 {
586 struct curl_slist *newlist = curl_slist_append(*list, ptr);
587 if(newlist)
588 *list = newlist;
589 else
590 return PARAM_NO_MEM;
591
592 return PARAM_OK;
593 }
594
ftpfilemethod(struct OperationConfig * config,const char * str)595 int ftpfilemethod(struct OperationConfig *config, const char *str)
596 {
597 if(curl_strequal("singlecwd", str))
598 return CURLFTPMETHOD_SINGLECWD;
599 if(curl_strequal("nocwd", str))
600 return CURLFTPMETHOD_NOCWD;
601 if(curl_strequal("multicwd", str))
602 return CURLFTPMETHOD_MULTICWD;
603
604 warnf(config->global, "unrecognized ftp file method '%s', using default",
605 str);
606
607 return CURLFTPMETHOD_MULTICWD;
608 }
609
ftpcccmethod(struct OperationConfig * config,const char * str)610 int ftpcccmethod(struct OperationConfig *config, const char *str)
611 {
612 if(curl_strequal("passive", str))
613 return CURLFTPSSL_CCC_PASSIVE;
614 if(curl_strequal("active", str))
615 return CURLFTPSSL_CCC_ACTIVE;
616
617 warnf(config->global, "unrecognized ftp CCC method '%s', using default",
618 str);
619
620 return CURLFTPSSL_CCC_PASSIVE;
621 }
622
delegation(struct OperationConfig * config,const char * str)623 long delegation(struct OperationConfig *config, const char *str)
624 {
625 if(curl_strequal("none", str))
626 return CURLGSSAPI_DELEGATION_NONE;
627 if(curl_strequal("policy", str))
628 return CURLGSSAPI_DELEGATION_POLICY_FLAG;
629 if(curl_strequal("always", str))
630 return CURLGSSAPI_DELEGATION_FLAG;
631
632 warnf(config->global, "unrecognized delegation method '%s', using none",
633 str);
634
635 return CURLGSSAPI_DELEGATION_NONE;
636 }
637
638 /*
639 * my_useragent: returns allocated string with default user agent
640 */
my_useragent(void)641 static char *my_useragent(void)
642 {
643 return strdup(CURL_NAME "/" CURL_VERSION);
644 }
645
646 #define isheadersep(x) ((((x)==':') || ((x)==';')))
647
648 /*
649 * inlist() returns true if the given 'checkfor' header is present in the
650 * header list.
651 */
inlist(const struct curl_slist * head,const char * checkfor)652 static bool inlist(const struct curl_slist *head,
653 const char *checkfor)
654 {
655 size_t thislen = strlen(checkfor);
656 DEBUGASSERT(thislen);
657 DEBUGASSERT(checkfor[thislen-1] != ':');
658
659 for(; head; head = head->next) {
660 if(curl_strnequal(head->data, checkfor, thislen) &&
661 isheadersep(head->data[thislen]) )
662 return TRUE;
663 }
664
665 return FALSE;
666 }
667
get_args(struct OperationConfig * config,const size_t i)668 CURLcode get_args(struct OperationConfig *config, const size_t i)
669 {
670 CURLcode result = CURLE_OK;
671 bool last = (config->next ? FALSE : TRUE);
672
673 if(config->jsoned) {
674 ParameterError err = PARAM_OK;
675 /* --json also implies json Content-Type: and Accept: headers - if
676 they are not set with -H */
677 if(!inlist(config->headers, "Content-Type"))
678 err = add2list(&config->headers, "Content-Type: application/json");
679 if(!err && !inlist(config->headers, "Accept"))
680 err = add2list(&config->headers, "Accept: application/json");
681 if(err)
682 return CURLE_OUT_OF_MEMORY;
683 }
684
685 /* Check we have a password for the given host user */
686 if(config->userpwd && !config->oauth_bearer) {
687 result = checkpasswd("host", i, last, &config->userpwd);
688 if(result)
689 return result;
690 }
691
692 /* Check we have a password for the given proxy user */
693 if(config->proxyuserpwd) {
694 result = checkpasswd("proxy", i, last, &config->proxyuserpwd);
695 if(result)
696 return result;
697 }
698
699 /* Check we have a user agent */
700 if(!config->useragent) {
701 config->useragent = my_useragent();
702 if(!config->useragent) {
703 errorf(config->global, "out of memory");
704 result = CURLE_OUT_OF_MEMORY;
705 }
706 }
707
708 return result;
709 }
710
711 /*
712 * Parse the string and modify ssl_version in the val argument. Return PARAM_OK
713 * on success, otherwise a parameter error enum. ONLY ACCEPTS POSITIVE NUMBERS!
714 *
715 * Since this function gets called with the 'nextarg' pointer from within the
716 * getparameter a lot, we must check it for NULL before accessing the str
717 * data.
718 */
719
str2tls_max(long * val,const char * str)720 ParameterError str2tls_max(long *val, const char *str)
721 {
722 static struct s_tls_max {
723 const char *tls_max_str;
724 long tls_max;
725 } const tls_max_array[] = {
726 { "default", CURL_SSLVERSION_MAX_DEFAULT },
727 { "1.0", CURL_SSLVERSION_MAX_TLSv1_0 },
728 { "1.1", CURL_SSLVERSION_MAX_TLSv1_1 },
729 { "1.2", CURL_SSLVERSION_MAX_TLSv1_2 },
730 { "1.3", CURL_SSLVERSION_MAX_TLSv1_3 }
731 };
732 size_t i = 0;
733 if(!str)
734 return PARAM_REQUIRES_PARAMETER;
735 for(i = 0; i < sizeof(tls_max_array)/sizeof(tls_max_array[0]); i++) {
736 if(!strcmp(str, tls_max_array[i].tls_max_str)) {
737 *val = tls_max_array[i].tls_max;
738 return PARAM_OK;
739 }
740 }
741 return PARAM_BAD_USE;
742 }
743