Una cosa alla volta.
Sia che la malloc ti serva o meno, ho capito cosa sta tentando di fare con:
recipe[20] = malloc(MAX * sizeof(ricetta*));
e il procedimento corretto per farlo è:
for(int i = 0; i < 20, i++) {
recipe[i] = malloc(MAX * sizeof(ricetta*));
}
in modo che venga allocata dinamicamente una struttura per ogni elemento di recipe, il quale conterrà gli indirizzi delle 20 strutture.
Alla fine del programma, per liberare questa memoria, fai così:
for(int i = 0; i < 20, i++) {
free(recipe[i]);
}
deallocando ogni singola struttura.
Ad ogni modo tu sai che dovrai usare 20 ricette quindi non serve l'allocazione dinamica.
E per il procedimento? Tu non sai quanto "lungo" può essere, ma questo non influisce sul numero di ricette. Le ricette sono sempre 20 e puoi allocarle staticamente. Vedendo come hai memorizzato il procedimento penso sia meglio che approfondisci gli array di caratteri e i puntatori a stringhe.
Ipotizzando che la lunghezza massima di un procedimento siano 1000 caratteri (limite scelto a priori), potresti avere un campo "char procedimento[1000]" o un campo "char * procedimento"; dopodiché passerai in entrambi i casi "procedimento" alla fgets.
Attenzione, nel secondo caso, per ogni ricetta, dovrai allocare i 1000 caratteri:
ricetta.procedimento = (char*) malloc(1000 * sizeof(char));
e deallocarli alla fine:
free(ricetta.procedimento);
Se, per qualche strano motivo, scegli di allocare anche le ricette dinamicamente, assicurati di allocare prima una ricetta e poi il suo procedimento e di deallocare prima un procedimento e poi la ricetta alla quale esso appartiene. Ovviamente non puoi allocare un procedimento senza una ricetta, mentre sarebbe possibile deallocare la ricetta e non il procedimento. Così facendo perderesti la possibilità di deallocare il procedimento = memory leak.
Un'altra cosa: fflush() agisce solo sugli stream di OUTPUT, perciò dimentica il prima possibile che scrivere fflush(stdin); sia una cosa da fare.